chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -0,0 +1,830 @@
package rpc
import (
"context"
"errors"
"github.com/gotd/td/tg"
"github.com/gotd/td/tgerr"
"go.uber.org/zap"
"sort"
"strings"
"telesrv/internal/domain"
"unicode/utf8"
)
func (r *Router) onChannelsGetAdminedPublicChannels(ctx context.Context, req *tg.ChannelsGetAdminedPublicChannelsRequest) (tg.MessagesChatsClass, error) {
if r.deps.Channels == nil {
return &tg.MessagesChats{}, nil
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if req.ByLocation {
return &tg.MessagesChats{}, nil
}
channels, err := r.deps.Channels.ListAdminedPublicChannels(ctx, userID)
if err != nil {
return nil, internalErr()
}
return &tg.MessagesChats{Chats: tgChannels(userID, channels)}, nil
}
func (r *Router) onChannelsDeleteParticipantHistory(ctx context.Context, req *tg.ChannelsDeleteParticipantHistoryRequest) (*tg.MessagesAffectedHistory, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromInput(ctx, userID, req.Channel)
if err != nil {
return nil, err
}
peer, ok := r.domainPeerFromInputPeer(0, req.Participant)
if !ok || peer.Type != domain.PeerTypeUser || peer.ID == 0 {
return nil, peerIDInvalidErr()
}
res, err := r.deps.Channels.DeleteParticipantHistory(ctx, userID, domain.DeleteChannelParticipantHistoryRequest{
UserID: userID,
ChannelID: channelID,
ParticipantUserID: peer.ID,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelDeleteErr(err)
}
if res.Event.Pts != 0 {
// deleteParticipantHistory fan-out 异步化(设计 Phase 0与已异步的 messages.deleteMessages
// 频道分支对齐。builder 纯 viewer 无关(仅 delete update + ChatMin无 Users无需预热。
r.enqueueChannelFanout(ctx, channelFanoutMembers, userID, res.Channel.ID, res.Event.Pts, res.Recipients, func(_ context.Context, viewerUserID int64) *tg.Updates {
return r.channelDeleteMessagesUpdates(viewerUserID, res.Channel, res.Event)
})
}
return &tg.MessagesAffectedHistory{Pts: res.Channel.Pts, PtsCount: res.Event.PtsCount, Offset: res.Offset}, nil
}
func (r *Router) onChannelsToggleJoinToSend(ctx context.Context, req *tg.ChannelsToggleJoinToSendRequest) (tg.UpdatesClass, error) {
return r.applyChannelAdminStateMutation(ctx, req.Channel, func(ctx context.Context, userID, channelID int64) (domain.Channel, error) {
return r.deps.Channels.SetJoinToSend(ctx, userID, channelID, req.Enabled)
})
}
func (r *Router) onChannelsToggleJoinRequest(ctx context.Context, req *tg.ChannelsToggleJoinRequestRequest) (tg.UpdatesClass, error) {
return r.applyChannelAdminStateMutation(ctx, req.Channel, func(ctx context.Context, userID, channelID int64) (domain.Channel, error) {
return r.deps.Channels.SetJoinRequest(ctx, userID, channelID, req.Enabled)
})
}
func (r *Router) onChannelsToggleParticipantsHidden(ctx context.Context, req *tg.ChannelsToggleParticipantsHiddenRequest) (tg.UpdatesClass, error) {
return r.applyChannelAdminStateMutation(ctx, req.Channel, func(ctx context.Context, userID, channelID int64) (domain.Channel, error) {
return r.deps.Channels.SetParticipantsHidden(ctx, userID, channelID, req.Enabled)
})
}
func (r *Router) onChannelsGetMessageAuthor(ctx context.Context, req *tg.ChannelsGetMessageAuthorRequest) (tg.UserClass, error) {
userID, view, err := r.channelView(ctx, req.Channel)
if err != nil {
return nil, err
}
author, err := r.deps.Channels.GetMessageAuthor(ctx, userID, domain.GetChannelMessageAuthorRequest{
UserID: userID,
ChannelID: view.Channel.ID,
ID: req.ID,
})
if err != nil {
if errors.Is(err, domain.ErrMessageIDInvalid) {
return nil, messageIDInvalidErr()
}
return nil, channelInvalidErr(err)
}
users := r.tgUsersForIDs(ctx, userID, []int64{author.SenderUserID})
if len(users) == 0 {
return nil, peerIDInvalidErr()
}
return users[0], nil
}
func (r *Router) onChannelsGetParticipants(ctx context.Context, req *tg.ChannelsGetParticipantsRequest) (tg.ChannelsChannelParticipantsClass, error) {
if r.deps.Channels == nil {
return &tg.ChannelsChannelParticipants{}, nil
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
ref, ok := inputChannelRef(req.Channel)
if !ok {
return nil, channelInvalidErr(domain.ErrChannelInvalid)
}
filter := domainChannelParticipantsFilter(req.Filter)
if utf8.RuneCountInString(filter.Query) > domain.MaxChannelParticipantsQueryLength {
return nil, limitInvalidErr()
}
list, err := r.deps.Channels.GetParticipants(ctx, userID, ref.ID, filter, req.Offset, req.Limit)
if err != nil {
return nil, channelInvalidErr(err)
}
if !inputChannelAccessHashMatches(ref, list.Channel) {
return nil, channelInvalidErr(domain.ErrChannelPrivate)
}
if req.Hash != 0 && list.Hash == req.Hash {
return &tg.ChannelsChannelParticipantsNotModified{}, nil
}
participants := make([]tg.ChannelParticipantClass, 0, len(list.Participants))
userIDs := make([]int64, 0, len(list.Participants))
seenUserIDs := make(map[int64]struct{}, len(list.Participants)*2)
addUserID := func(id int64) {
if id == 0 {
return
}
if _, ok := seenUserIDs[id]; ok {
return
}
seenUserIDs[id] = struct{}{}
userIDs = append(userIDs, id)
}
for _, member := range list.Participants {
participant := tgChannelParticipant(userID, member)
participants = append(participants, participant)
for _, id := range channelParticipantUserRefs(participant) {
addUserID(id)
}
}
users := r.tgUsers(list.Users)
if len(users) == 0 {
users = r.tgUsersForIDs(ctx, userID, userIDs)
} else {
present := make(map[int64]struct{}, len(users))
for _, item := range users {
if u, ok := item.(*tg.User); ok {
present[u.ID] = struct{}{}
}
}
missing := make([]int64, 0, len(userIDs))
for _, id := range userIDs {
if _, ok := present[id]; !ok {
missing = append(missing, id)
}
}
if len(missing) > 0 {
users = append(users, r.tgUsersForIDs(ctx, userID, missing)...)
}
}
r.applyStoryMaxIDsToPeerObjects(ctx, userID, users, nil)
r.log.Debug("channels.getParticipants result",
zap.Int64("channel_id", ref.ID),
zap.String("filter", string(filter.Kind)),
zap.Int("count", list.Count),
zap.Int("participants", len(participants)),
zap.Int("users", len(users)),
)
return &tg.ChannelsChannelParticipants{
Count: list.Count,
Participants: participants,
Chats: []tg.ChatClass{},
Users: users,
}, nil
}
func (r *Router) onChannelsGetParticipant(ctx context.Context, req *tg.ChannelsGetParticipantRequest) (*tg.ChannelsChannelParticipant, error) {
if r.deps.Channels == nil {
return &tg.ChannelsChannelParticipant{}, nil
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromInput(ctx, userID, req.Channel)
if err != nil {
return nil, err
}
peer, ok := r.domainPeerFromInputPeer(userID, req.Participant)
if !ok || peer.Type != domain.PeerTypeUser || peer.ID == 0 {
return nil, peerIDInvalidErr()
}
member, err := r.deps.Channels.GetParticipant(ctx, userID, channelID, peer.ID)
if err != nil {
return nil, channelInvalidErr(err)
}
participant := tgChannelParticipant(userID, member)
users := r.tgUsersForIDs(ctx, userID, channelParticipantUserRefs(participant))
r.applyStoryMaxIDsToPeerObjects(ctx, userID, users, nil)
return &tg.ChannelsChannelParticipant{
Participant: participant,
Users: users,
}, nil
}
func channelParticipantUserRefs(participant tg.ChannelParticipantClass) []int64 {
ids := make([]int64, 0, 3)
add := func(id int64) {
if id == 0 {
return
}
for _, existing := range ids {
if existing == id {
return
}
}
ids = append(ids, id)
}
addPeer := func(peer tg.PeerClass) {
if p, ok := peer.(*tg.PeerUser); ok {
add(p.UserID)
}
}
switch p := participant.(type) {
case *tg.ChannelParticipantCreator:
add(p.UserID)
case *tg.ChannelParticipantAdmin:
add(p.UserID)
add(p.PromotedBy)
if inviterID, ok := p.GetInviterID(); ok {
add(inviterID)
}
case *tg.ChannelParticipantSelf:
add(p.UserID)
add(p.InviterID)
case *tg.ChannelParticipant:
add(p.UserID)
case *tg.ChannelParticipantLeft:
addPeer(p.Peer)
case *tg.ChannelParticipantBanned:
addPeer(p.Peer)
add(p.KickedBy)
}
return ids
}
func (r *Router) onChannelsInviteToChannel(ctx context.Context, req *tg.ChannelsInviteToChannelRequest) (*tg.MessagesInvitedUsers, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
if len(req.Users) == 0 || len(req.Users) > domain.MaxChannelInviteUsers {
return nil, limitInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromInput(ctx, userID, req.Channel)
if err != nil {
return nil, err
}
userIDs, err := r.userIDsFromInputUsers(ctx, userID, req.Users)
if err != nil {
return nil, err
}
res, err := r.deps.Channels.InviteToChannel(ctx, userID, channelID, userIDs, int(r.clock.Now().Unix()))
if err != nil {
return nil, channelInviteErr(err)
}
r.invalidateChannelFullBotInfoCacheForChannel(res.Channel.ID)
r.addOnlineChannelMemberships(res.Channel.ID, channelMemberUserIDs(res.Members)...)
cache := newViewerPeerCache(r)
updates := r.channelOperationUpdatesWithPeerCache(ctx, userID, res, cache)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelOperationUpdatesWithPeerCache(ctx, viewerUserID, res, cache)
})
return &tg.MessagesInvitedUsers{Updates: updates, MissingInvitees: []tg.MissingInvitee{}}, nil
}
func (r *Router) onChannelsJoinChannel(ctx context.Context, input tg.InputChannelClass) (tg.MessagesChatInviteJoinResultClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
ref, ok := inputChannelRef(input)
if !ok {
return nil, channelInvalidErr(domain.ErrChannelInvalid)
}
if ref.CheckAccessHash {
channel, err := r.deps.Channels.GetJoinableChannel(ctx, userID, ref.ID)
if err != nil {
return nil, channelInvalidErr(err)
}
if !inputChannelAccessHashMatches(ref, channel) {
return nil, channelInvalidErr(domain.ErrChannelPrivate)
}
}
res, err := r.deps.Channels.JoinChannel(ctx, userID, ref.ID, int(r.clock.Now().Unix()))
if err != nil {
if errors.Is(err, domain.ErrInviteRequestSent) && res.Channel.ID != 0 {
r.pushPendingJoinRequestsToAdmins(ctx, res.Channel)
}
return nil, channelInviteErr(err)
}
r.invalidateChannelFullBotInfoCacheForChannel(res.Channel.ID)
r.addOnlineChannelMemberships(res.Channel.ID, channelMemberUserIDs(res.Members)...)
updates := r.channelOperationUpdates(ctx, userID, res)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelOperationUpdates(ctx, viewerUserID, res)
})
// Layer 227channels.joinChannel 返回 messages.ChatInviteJoinResult
// 正常加入即 chatInviteJoinResultOk 包裹本次操作的 updates。
return &tg.MessagesChatInviteJoinResultOk{Updates: updates}, nil
}
func (r *Router) onChannelsLeaveChannel(ctx context.Context, input tg.InputChannelClass) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromInput(ctx, userID, input)
if err != nil {
return nil, err
}
res, err := r.deps.Channels.LeaveChannel(ctx, userID, channelID, int(r.clock.Now().Unix()))
if err != nil {
return nil, channelAdminErr(err)
}
r.invalidateChannelFullBotInfoCacheForChannel(res.Channel.ID)
r.removeOnlineChannelMemberships(res.Channel.ID, userID)
r.recordChannelStateForUser(ctx, userID, res.Channel.ID, true)
updates := r.channelOperationUpdates(ctx, userID, res)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelOperationUpdates(ctx, viewerUserID, res)
})
return updates, nil
}
// recordChannelStateForUser 给该账号写一条 durable channel 状态事件:
// 离线设备经 difference 收到 updateChannel 后重拉 channel发现 left/
// forbidden 并移除会话excludeCurrent 时当前 session 由 RPC 响应承担。
func (r *Router) recordChannelStateForUser(ctx context.Context, userID, channelID int64, excludeCurrent bool) {
if r.deps.Updates == nil || userID == 0 || channelID == 0 {
return
}
authKeyID := [8]byte{}
excludeSessionID := int64(0)
if excludeCurrent {
authKeyID, _ = AuthKeyIDFrom(ctx)
excludeSessionID, _ = SessionIDFrom(ctx)
}
event, _, err := r.deps.Updates.RecordChannelState(ctx, authKeyID, userID, channelID, excludeSessionID)
if err != nil {
return
}
if excludeCurrent {
r.bookkeepAuxPtsForCurrentSession(ctx, event)
}
}
func (r *Router) onChannelsEditAdmin(ctx context.Context, req *tg.ChannelsEditAdminRequest) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromInput(ctx, userID, req.Channel)
if err != nil {
return nil, err
}
target, found, err := r.userFromInput(ctx, userID, req.UserID)
if err != nil {
return nil, internalErr()
}
if !found || target.ID == 0 {
return nil, peerIDInvalidErr()
}
res, err := r.deps.Channels.EditAdmin(ctx, userID, domain.EditChannelAdminRequest{
UserID: userID,
ChannelID: channelID,
MemberID: target.ID,
AdminRights: domainChannelAdminRights(req.AdminRights),
Rank: req.Rank,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelAdminErr(err)
}
r.invalidateChannelFullBotInfoCacheForChannel(res.Channel.ID)
if res.Participant.Status == domain.ChannelMemberActive {
r.addOnlineChannelMemberships(res.Channel.ID, res.Participant.UserID)
} else {
r.removeOnlineChannelMemberships(res.Channel.ID, res.Participant.UserID)
}
cache := newViewerPeerCache(r)
updates := r.channelParticipantUpdatesWithPeerCache(ctx, userID, userID, res.Channel, res.Previous, res.Participant, res.Date, cache)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelParticipantUpdatesWithPeerCache(ctx, viewerUserID, userID, res.Channel, res.Previous, res.Participant, res.Date, cache)
})
return updates, nil
}
func (r *Router) onChannelsEditBanned(ctx context.Context, req *tg.ChannelsEditBannedRequest) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromInput(ctx, userID, req.Channel)
if err != nil {
return nil, err
}
participant, ok := r.domainPeerFromInputPeer(userID, req.Participant)
if !ok || participant.Type != domain.PeerTypeUser || participant.ID == 0 {
return nil, peerIDInvalidErr()
}
res, err := r.deps.Channels.EditBanned(ctx, userID, domain.EditChannelBannedRequest{
UserID: userID,
ChannelID: channelID,
Participant: participant,
BannedRights: domainChannelBannedRights(req.BannedRights),
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelAdminErr(err)
}
r.invalidateChannelFullBotInfoCacheForChannel(res.Channel.ID)
if res.Participant.Status == domain.ChannelMemberKicked && res.Previous.Status == domain.ChannelMemberActive {
r.recordChannelStateForUser(ctx, res.Participant.UserID, res.Channel.ID, false)
}
cache := newViewerPeerCache(r)
build := func(viewerUserID int64) *tg.Updates {
updates := r.channelParticipantUpdatesWithPeerCache(ctx, viewerUserID, userID, res.Channel, res.Previous, res.Participant, res.Date, cache)
if updates != nil && res.ServiceEvent.Pts != 0 {
// megagroup 踢人服务消息占 channel pts必须先于 participant
// update 应用,让成员面板/人数与消息流一起收敛。
if update := tgChannelUpdate(viewerUserID, res.ServiceEvent); update != nil {
updates.Updates = append([]tg.UpdateClass{update}, updates.Updates...)
}
}
return updates
}
updates := build(userID)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, build)
return updates, nil
}
func (r *Router) onChannelsGetAdminLog(ctx context.Context, req *tg.ChannelsGetAdminLogRequest) (*tg.ChannelsAdminLogResults, error) {
if r.deps.Channels == nil {
return &tg.ChannelsAdminLogResults{}, nil
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromInput(ctx, userID, req.Channel)
if err != nil {
return nil, err
}
adminIDs := []int64(nil)
if admins, ok := req.GetAdmins(); ok && len(admins) > 0 {
if len(admins) > domain.MaxChannelAdminLogAdmins {
return nil, limitInvalidErr()
}
adminIDs, err = r.userIDsFromInputUsers(ctx, userID, admins)
if err != nil {
return nil, err
}
}
res, err := r.deps.Channels.ListAdminLog(ctx, userID, domain.ChannelAdminLogRequest{
UserID: userID,
ChannelID: channelID,
Query: req.Q,
AdminUserIDs: adminIDs,
MaxID: req.MaxID,
MinID: req.MinID,
Limit: req.Limit,
Filter: domainChannelAdminLogFilter(req),
})
if err != nil {
return nil, channelAdminErr(err)
}
events := tgChannelAdminLogEvents(userID, res.Events)
chats := []tg.ChatClass{tgChannelChatMin(userID, res.Channel)}
users := r.channelAdminLogUsers(ctx, userID, res.Events)
r.applyStoryMaxIDsToPeerObjects(ctx, userID, users, chats)
return &tg.ChannelsAdminLogResults{
Events: events,
Chats: chats,
Users: users,
}, nil
}
func (r *Router) onMessagesGetAdminsWithInvites(ctx context.Context, peer tg.InputPeerClass) (*tg.MessagesChatAdminsWithInvites, error) {
userID, view, err := r.inviteManagementChannelView(ctx, peer)
if err != nil {
return nil, err
}
counts, err := r.deps.Channels.ListAdminsWithInvites(ctx, userID, view.Channel.ID)
if err != nil {
return nil, channelInviteErr(err)
}
admins := make([]tg.ChatAdminWithInvites, 0, len(counts))
userIDs := make([]int64, 0, len(counts))
for _, count := range counts {
admins = append(admins, tg.ChatAdminWithInvites{
AdminID: count.AdminUserID,
InvitesCount: count.InvitesCount,
RevokedInvitesCount: count.RevokedInvitesCount,
})
userIDs = append(userIDs, count.AdminUserID)
}
return &tg.MessagesChatAdminsWithInvites{
Admins: admins,
Users: r.tgUsersForIDs(ctx, userID, userIDs),
}, nil
}
func (r *Router) onMessagesHideChatJoinRequest(ctx context.Context, req *tg.MessagesHideChatJoinRequestRequest) (tg.UpdatesClass, error) {
userID, view, err := r.inviteManagementChannelView(ctx, req.Peer)
if err != nil {
return nil, err
}
targets, err := r.userIDsFromInputUsers(ctx, userID, []tg.InputUserClass{req.UserID})
if err != nil {
return nil, err
}
if len(targets) == 0 {
return nil, userIDInvalidErr()
}
res, err := r.deps.Channels.HideChatJoinRequest(ctx, userID, domain.HideChannelJoinRequestRequest{
UserID: userID,
ChannelID: view.Channel.ID,
TargetUserID: targets[0],
Approved: req.Approved,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelInviteErr(err)
}
r.invalidateChannelFullBotInfoCacheForChannel(res.Channel.ID)
r.addOnlineChannelMemberships(res.Channel.ID, channelMemberUserIDs(res.Members)...)
updates := r.channelOperationUpdates(ctx, userID, res)
r.appendPendingJoinRequestsUpdate(ctx, userID, updates, res.Channel)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelOperationUpdates(ctx, viewerUserID, res)
})
r.pushPendingJoinRequestsToAdmins(ctx, res.Channel)
return updates, nil
}
func (r *Router) onMessagesHideAllChatJoinRequests(ctx context.Context, req *tg.MessagesHideAllChatJoinRequestsRequest) (tg.UpdatesClass, error) {
userID, view, err := r.inviteManagementChannelView(ctx, req.Peer)
if err != nil {
return nil, err
}
link, hasLink := req.GetLink()
if hasLink && len(link) > maxChatInviteLinkLength {
return nil, limitInvalidErr()
}
hash := ""
if hasLink && strings.TrimSpace(link) != "" {
hash, err = channelInviteHashFromLink(link)
if err != nil {
return nil, err
}
}
res, err := r.deps.Channels.HideAllChatJoinRequests(ctx, userID, domain.HideChannelJoinRequestsRequest{
UserID: userID,
ChannelID: view.Channel.ID,
Hash: hash,
Approved: req.Approved,
Limit: domain.MaxChannelHideJoinRequests,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
return nil, channelInviteErr(err)
}
r.invalidateChannelFullBotInfoCacheForChannel(res.Channel.ID)
r.addOnlineChannelMemberships(res.Channel.ID, channelMemberUserIDs(res.Members)...)
updates := r.channelOperationUpdates(ctx, userID, res)
r.appendPendingJoinRequestsUpdate(ctx, userID, updates, res.Channel)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelOperationUpdates(ctx, viewerUserID, res)
})
r.pushPendingJoinRequestsToAdmins(ctx, res.Channel)
return updates, nil
}
func canViewChannelJoinRequests(member domain.ChannelMember) bool {
return member.Role == domain.ChannelRoleCreator ||
(member.Role == domain.ChannelRoleAdmin && (member.AdminRights.InviteUsers || member.AdminRights.ChangeInfo))
}
func (r *Router) pendingJoinRequestsUpdates(ctx context.Context, viewerUserID int64, channel domain.Channel) *tg.Updates {
if r.deps.Channels == nil || channel.ID == 0 {
return nil
}
pending, err := r.deps.Channels.PendingJoinRequests(ctx, channel.ID, domain.MaxChannelPendingJoinRecentRequesters)
if err != nil {
return nil
}
return &tg.Updates{
Updates: []tg.UpdateClass{&tg.UpdatePendingJoinRequests{
Peer: &tg.PeerChannel{ChannelID: channel.ID},
RequestsPending: pending.Count,
RecentRequesters: pending.RecentRequesters,
}},
Users: r.tgUsersForIDs(ctx, viewerUserID, pending.RecentRequesters),
Chats: []tg.ChatClass{tgChannelChatMin(viewerUserID, channel)},
Date: int(r.clock.Now().Unix()),
Seq: 0,
}
}
func (r *Router) appendPendingJoinRequestsUpdate(ctx context.Context, viewerUserID int64, updates *tg.Updates, channel domain.Channel) {
if updates == nil {
return
}
pending := r.pendingJoinRequestsUpdates(ctx, viewerUserID, channel)
if pending == nil {
return
}
updates.Updates = append(updates.Updates, pending.Updates...)
updates.Users = append(updates.Users, pending.Users...)
}
func (r *Router) pushPendingJoinRequestsToAdmins(ctx context.Context, channel domain.Channel) {
if r.deps.Channels == nil || r.deps.Sessions == nil || channel.ID == 0 {
return
}
adminIDs, err := r.deps.Channels.InviteAdminMemberIDs(ctx, channel.ID, domain.MaxChannelRealtimeFanout)
if err != nil || len(adminIDs) == 0 {
adminIDs = []int64{channel.CreatorUserID}
}
seen := make(map[int64]struct{}, len(adminIDs))
for _, adminID := range adminIDs {
if adminID == 0 {
continue
}
if _, ok := seen[adminID]; ok {
continue
}
seen[adminID] = struct{}{}
updates := r.pendingJoinRequestsUpdates(ctx, adminID, channel)
if updates == nil {
continue
}
r.pushUserUpdates(ctx, adminID, updates)
}
}
func (r *Router) channelParticipantUpdates(ctx context.Context, viewerUserID, actorUserID int64, channel domain.Channel, previous, participant domain.ChannelMember, date int) *tg.Updates {
return r.channelParticipantUpdatesWithPeerCache(ctx, viewerUserID, actorUserID, channel, previous, participant, date, newViewerPeerCache(r))
}
func (r *Router) channelParticipantUpdatesWithPeerCache(ctx context.Context, viewerUserID, actorUserID int64, channel domain.Channel, previous, participant domain.ChannelMember, date int, cache *viewerPeerCache) *tg.Updates {
if cache == nil {
cache = newViewerPeerCache(r)
}
update := &tg.UpdateChannelParticipant{
ChannelID: channel.ID,
Date: date,
ActorID: actorUserID,
UserID: participant.UserID,
}
if update.ActorID == 0 {
update.ActorID = viewerUserID
}
if previous.UserID != 0 {
update.SetPrevParticipant(tgChannelParticipantForUpdate(viewerUserID, previous))
}
if participant.UserID != 0 {
update.SetNewParticipant(tgChannelParticipantForUpdate(viewerUserID, participant))
}
// 当事成员必须收到完整 channel 投影:被踢/被封禁状态只有通过非 min
// 对象的 left/banned_rights 才会被客户端应用min 形态会让被踢者
// 永远不知道自己已离开会话。其它接收者用 min 保护各自本地权限。
var chat tg.ChatClass
if viewerUserID != 0 && viewerUserID == participant.UserID {
self := participant
chat = tgChannelChat(viewerUserID, channel, &self)
} else {
chat = tgChannelChatMin(viewerUserID, channel)
}
return &tg.Updates{
Updates: []tg.UpdateClass{update, &tg.UpdateChannel{ChannelID: channel.ID}},
Users: tgUsersForViewer(viewerUserID, cache.usersForIDs(ctx, viewerUserID, []int64{participant.UserID, participant.InviterUserID, previous.UserID, previous.InviterUserID, update.ActorID})),
Chats: []tg.ChatClass{chat},
Date: int(r.clock.Now().Unix()),
Seq: 0,
}
}
func domainChannelAdminLogFilter(req *tg.ChannelsGetAdminLogRequest) domain.ChannelAdminLogFilter {
filter, ok := req.GetEventsFilter()
if !ok {
return domain.ChannelAdminLogFilter{}
}
return domain.ChannelAdminLogFilter{
Join: filter.GetJoin(),
Leave: filter.GetLeave(),
Invite: filter.GetInvite(),
Ban: filter.GetBan(),
Unban: filter.GetUnban(),
Kick: filter.GetKick(),
Unkick: filter.GetUnkick(),
Promote: filter.GetPromote(),
Demote: filter.GetDemote(),
Info: filter.GetInfo(),
Settings: filter.GetSettings(),
Pinned: filter.GetPinned(),
Edit: filter.GetEdit(),
Delete: filter.GetDelete(),
Send: filter.GetSend(),
Invites: filter.GetInvites(),
Forums: filter.GetForums(),
SubExtend: filter.GetSubExtend(),
EditRank: filter.GetEditRank(),
}
}
func (r *Router) channelAdminLogUsers(ctx context.Context, currentUserID int64, events []domain.ChannelAdminLogEvent) []tg.UserClass {
if r.deps.Users == nil || len(events) == 0 {
return nil
}
ids := make(map[int64]struct{}, len(events))
add := func(id int64) {
if id != 0 {
ids[id] = struct{}{}
}
}
addMember := func(member *domain.ChannelMember) {
if member != nil {
add(member.UserID)
add(member.InviterUserID)
}
}
addMessage := func(msg *domain.ChannelMessage) {
if msg != nil {
add(msg.SenderUserID)
if msg.From.Type == domain.PeerTypeUser {
add(msg.From.ID)
}
}
}
for _, event := range events {
add(event.UserID)
addMember(event.PrevParticipant)
addMember(event.NewParticipant)
addMember(event.Participant)
addMessage(event.Message)
addMessage(event.PrevMessage)
addMessage(event.NewMessage)
}
userIDs := make([]int64, 0, len(ids))
for id := range ids {
userIDs = append(userIDs, id)
}
sort.Slice(userIDs, func(i, j int) bool { return userIDs[i] < userIDs[j] })
return r.tgUsersForIDs(ctx, currentUserID, userIDs)
}
func domainChannelParticipantsFilter(filter tg.ChannelParticipantsFilterClass) domain.ChannelParticipantsFilter {
switch f := filter.(type) {
case *tg.ChannelParticipantsAdmins:
return domain.ChannelParticipantsFilter{Kind: domain.ChannelParticipantsAdmins}
case *tg.ChannelParticipantsKicked:
return domain.ChannelParticipantsFilter{Kind: domain.ChannelParticipantsKicked, Query: f.Q}
case *tg.ChannelParticipantsBanned:
return domain.ChannelParticipantsFilter{Kind: domain.ChannelParticipantsBanned, Query: f.Q}
case *tg.ChannelParticipantsSearch:
return domain.ChannelParticipantsFilter{Kind: domain.ChannelParticipantsSearch, Query: f.Q}
case *tg.ChannelParticipantsBots:
return domain.ChannelParticipantsFilter{Kind: domain.ChannelParticipantsBots}
case *tg.ChannelParticipantsContacts:
return domain.ChannelParticipantsFilter{Kind: domain.ChannelParticipantsContacts, Query: f.Q}
case *tg.ChannelParticipantsMentions:
return domain.ChannelParticipantsFilter{Kind: domain.ChannelParticipantsMentions, Query: f.Q}
default:
return domain.ChannelParticipantsFilter{Kind: domain.ChannelParticipantsRecent}
}
}
func channelAdminErr(err error) error {
switch {
case errors.Is(err, domain.ErrChannelNotModified):
return tgerr400("CHAT_NOT_MODIFIED")
case errors.Is(err, domain.ErrChatPublicRequired):
return tgerr400("CHAT_PUBLIC_REQUIRED")
case errors.Is(err, domain.ErrChatDiscussionUnallowed):
return tgerr400("CHAT_DISCUSSION_UNALLOWED")
case errors.Is(err, domain.ErrChannelRightForbidden):
return tgerr.New(403, "RIGHT_FORBIDDEN")
case errors.Is(err, domain.ErrChannelUserCreator):
return tgerr400("USER_CREATOR")
case errors.Is(err, domain.ErrUserNotParticipant):
return tgerr400("USER_NOT_PARTICIPANT")
case errors.Is(err, domain.ErrMegagroupIDInvalid):
return tgerr400("MEGAGROUP_ID_INVALID")
case errors.Is(err, domain.ErrMessageIDInvalid):
return messageIDInvalidErr()
default:
return channelInvalidErr(err)
}
}