600 lines
19 KiB
Go
600 lines
19 KiB
Go
package rpc
|
||
|
||
import (
|
||
"github.com/gotd/td/tg"
|
||
"telesrv/internal/domain"
|
||
)
|
||
|
||
func tgUpdateState(st domain.UpdateState) tg.UpdatesState {
|
||
return tg.UpdatesState{Pts: st.Pts, Qts: st.Qts, Date: st.Date, Seq: st.Seq}
|
||
}
|
||
|
||
func tgUpdatesDifference(viewerUserID int64, diff domain.UpdateDifference) tg.UpdatesDifferenceClass {
|
||
out := &tg.UpdatesDifference{
|
||
NewMessages: make([]tg.MessageClass, 0, len(diff.Events)),
|
||
OtherUpdates: make([]tg.UpdateClass, 0, len(diff.Events)),
|
||
Users: make([]tg.UserClass, 0, 1),
|
||
Chats: make([]tg.ChatClass, 0, 1),
|
||
}
|
||
seenUsers := make(map[int64]struct{})
|
||
seenChats := make(map[int64]struct{})
|
||
for _, event := range diff.Events {
|
||
addUsers(out, seenUsers, viewerUserID, event.Users)
|
||
addChannels(out, seenChats, event.UserID, event.Channels)
|
||
switch event.Type {
|
||
case domain.UpdateEventNewMessage:
|
||
if msg := tgMessage(event.Message); msg != nil {
|
||
out.NewMessages = append(out.NewMessages, msg)
|
||
addMessageUsers(out, seenUsers, event.Message)
|
||
}
|
||
case domain.UpdateEventReadHistoryInbox:
|
||
if update := tgReadHistoryInboxUpdate(event); update != nil {
|
||
out.OtherUpdates = append(out.OtherUpdates, update)
|
||
}
|
||
case domain.UpdateEventReadHistoryOutbox:
|
||
if update := tgReadHistoryOutboxUpdate(event); update != nil {
|
||
out.OtherUpdates = append(out.OtherUpdates, update)
|
||
}
|
||
case domain.UpdateEventMessageReactions, domain.UpdateEventMessagePoll:
|
||
// 同时下发消息快照(含最新聚合)与对应通知 update;事件无 TL pts,
|
||
// pts 推进靠 difference state 本身。
|
||
if msg := tgMessage(event.Message); msg != nil {
|
||
out.NewMessages = append(out.NewMessages, msg)
|
||
addMessageUsers(out, seenUsers, event.Message)
|
||
}
|
||
if update := tgOtherUpdateFromEvent(event); update != nil {
|
||
out.OtherUpdates = append(out.OtherUpdates, update)
|
||
}
|
||
default:
|
||
if update := tgOtherUpdateFromEvent(event); update != nil {
|
||
out.OtherUpdates = append(out.OtherUpdates, update)
|
||
}
|
||
}
|
||
}
|
||
for _, nudge := range diff.ChannelNudges {
|
||
if nudge.ChannelID == 0 {
|
||
continue
|
||
}
|
||
update := &tg.UpdateChannelTooLong{ChannelID: nudge.ChannelID}
|
||
if nudge.Pts > 0 {
|
||
update.SetPts(nudge.Pts)
|
||
}
|
||
out.OtherUpdates = append(out.OtherUpdates, update)
|
||
if nudge.Channel != nil && nudge.Channel.Channel.ID != 0 {
|
||
addChannelNudgeChat(out, seenChats, tgChannelChatForView(viewerUserID, *nudge.Channel))
|
||
}
|
||
}
|
||
// Partial:连续事件被 limit 截断、后面还有 → updates.differenceSlice,客户端据 IntermediateState 续拉。
|
||
if diff.Partial {
|
||
return &tg.UpdatesDifferenceSlice{
|
||
NewMessages: out.NewMessages,
|
||
OtherUpdates: out.OtherUpdates,
|
||
Chats: out.Chats,
|
||
Users: out.Users,
|
||
IntermediateState: tgUpdateState(diff.State),
|
||
}
|
||
}
|
||
out.State = tgUpdateState(diff.State)
|
||
return out
|
||
}
|
||
|
||
func tgChannelDifference(viewerUserID int64, diff domain.ChannelDifference) tg.UpdatesChannelDifferenceClass {
|
||
if diff.TooLong {
|
||
messages := make([]tg.MessageClass, 0, len(diff.NewMessages))
|
||
for _, msg := range diff.NewMessages {
|
||
if item := tgChannelMessage(viewerUserID, msg); item != nil {
|
||
messages = append(messages, item)
|
||
}
|
||
}
|
||
dialog := tgDialog(domain.Dialog{
|
||
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: diff.Channel.ID},
|
||
TopMessage: diff.Dialog.TopMessageID,
|
||
TopMessageDate: diff.Dialog.TopMessageDate,
|
||
ReadInboxMaxID: diff.Dialog.ReadInboxMaxID,
|
||
ReadOutboxMaxID: diff.Dialog.ReadOutboxMaxID,
|
||
UnreadCount: diff.Dialog.UnreadCount,
|
||
UnreadMentions: diff.Dialog.UnreadMentions,
|
||
UnreadReactions: diff.Dialog.UnreadReactions,
|
||
UnreadMark: diff.Dialog.UnreadMark,
|
||
FolderID: diff.Dialog.FolderID,
|
||
})
|
||
if dialog != nil {
|
||
dialog.SetPts(diff.Pts)
|
||
}
|
||
return &tg.UpdatesChannelDifferenceTooLong{
|
||
Final: diff.Final,
|
||
Timeout: diff.Timeout,
|
||
Dialog: dialog,
|
||
Messages: messages,
|
||
Chats: tgChannelDifferenceChats(viewerUserID, diff),
|
||
Users: tgUsersForViewer(viewerUserID, diff.Users),
|
||
}
|
||
}
|
||
if len(diff.Events) == 0 && len(diff.NewMessages) == 0 && len(diff.OtherUpdates) == 0 {
|
||
return &tg.UpdatesChannelDifferenceEmpty{
|
||
Final: diff.Final,
|
||
Pts: diff.Pts,
|
||
Timeout: diff.Timeout,
|
||
}
|
||
}
|
||
messages := make([]tg.MessageClass, 0, len(diff.NewMessages))
|
||
for _, msg := range diff.NewMessages {
|
||
if item := tgChannelMessage(viewerUserID, msg); item != nil {
|
||
messages = append(messages, item)
|
||
}
|
||
}
|
||
updates := make([]tg.UpdateClass, 0, len(diff.OtherUpdates))
|
||
// 断线后经差量首次看到自己消息时,updateMessageID 让客户端把本地
|
||
// pending 按 random_id 对账,避免短暂的重复气泡。
|
||
for _, msg := range diff.NewMessages {
|
||
if msg.SenderUserID == viewerUserID && viewerUserID != 0 && msg.RandomID != 0 && msg.ID > 0 {
|
||
updates = append(updates, &tg.UpdateMessageID{ID: msg.ID, RandomID: msg.RandomID})
|
||
}
|
||
}
|
||
for _, event := range diff.OtherUpdates {
|
||
if update := tgChannelUpdate(viewerUserID, event); update != nil {
|
||
updates = append(updates, update)
|
||
}
|
||
}
|
||
chats := tgChannelDifferenceChats(viewerUserID, diff)
|
||
users := tgUsersForViewer(viewerUserID, diff.Users)
|
||
return &tg.UpdatesChannelDifference{
|
||
Final: diff.Final,
|
||
Pts: diff.Pts,
|
||
Timeout: diff.Timeout,
|
||
NewMessages: messages,
|
||
OtherUpdates: updates,
|
||
Chats: chats,
|
||
Users: users,
|
||
}
|
||
}
|
||
|
||
func tgChannelDifferenceChats(viewerUserID int64, diff domain.ChannelDifference) []tg.ChatClass {
|
||
return tgChannelChatsWithPrimarySelf(viewerUserID, diff.Channel, diff.Channels, diff.Self)
|
||
}
|
||
|
||
func tgChannelUpdate(viewerUserID int64, event domain.ChannelUpdateEvent) tg.UpdateClass {
|
||
switch event.Type {
|
||
case domain.ChannelUpdateNewMessage:
|
||
msg := tgChannelMessage(viewerUserID, event.Message)
|
||
if msg == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateNewChannelMessage{Message: msg, Pts: event.Pts, PtsCount: event.PtsCount}
|
||
case domain.ChannelUpdateEditMessage:
|
||
msg := tgChannelMessage(viewerUserID, event.Message)
|
||
if msg == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateEditChannelMessage{Message: msg, Pts: event.Pts, PtsCount: event.PtsCount}
|
||
case domain.ChannelUpdateWebPage:
|
||
// 频道链接预览就地替换:updateChannelWebPage 仅携已解析 webPage(按 id 关联消息里的
|
||
// pending 占位),客户端就地换卡片、不触碰 edit_date。
|
||
if event.Message.Media == nil || event.Message.Media.WebPage == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateChannelWebPage{
|
||
ChannelID: event.ChannelID,
|
||
Webpage: tgWebPage(*event.Message.Media.WebPage),
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
case domain.ChannelUpdateDeleteMessages:
|
||
if len(event.MessageIDs) == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateDeleteChannelMessages{
|
||
ChannelID: event.ChannelID,
|
||
Messages: append([]int(nil), event.MessageIDs...),
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
case domain.ChannelUpdatePinnedMessages:
|
||
if len(event.MessageIDs) == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdatePinnedChannelMessages{
|
||
Pinned: event.Pinned,
|
||
ChannelID: event.ChannelID,
|
||
Messages: append([]int(nil), event.MessageIDs...),
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
case domain.ChannelUpdateParticipant:
|
||
userID := event.Participant.UserID
|
||
if userID == 0 {
|
||
userID = event.Previous.UserID
|
||
}
|
||
if userID == 0 {
|
||
return nil
|
||
}
|
||
actorID := event.SenderUserID
|
||
if actorID == 0 {
|
||
actorID = userID
|
||
}
|
||
update := &tg.UpdateChannelParticipant{
|
||
ChannelID: event.ChannelID,
|
||
Date: event.Date,
|
||
ActorID: actorID,
|
||
UserID: userID,
|
||
}
|
||
if event.Previous.UserID != 0 {
|
||
update.SetPrevParticipant(tgChannelParticipantForUpdate(viewerUserID, event.Previous))
|
||
}
|
||
if event.Participant.UserID != 0 {
|
||
update.SetNewParticipant(tgChannelParticipantForUpdate(viewerUserID, event.Participant))
|
||
}
|
||
return update
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func tgOtherUpdateFromEvent(event domain.UpdateEvent) tg.UpdateClass {
|
||
switch event.Type {
|
||
case domain.UpdateEventUserPhone:
|
||
if event.UserID == 0 || event.Phone == "" {
|
||
return nil
|
||
}
|
||
return &tg.UpdateUserPhone{UserID: event.UserID, Phone: event.Phone}
|
||
case domain.UpdateEventChannelState:
|
||
if event.Peer.Type != domain.PeerTypeChannel || event.Peer.ID == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateChannel{ChannelID: event.Peer.ID}
|
||
case domain.UpdateEventStory:
|
||
peer := tgPeer(event.Peer)
|
||
if peer == nil {
|
||
peer = tgPeer(event.Story.Owner)
|
||
}
|
||
if peer == nil || event.Story.ID == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateStory{Peer: peer, Story: tgStoryItem(event.Story)}
|
||
case domain.UpdateEventReadStories:
|
||
peer := tgPeer(event.Peer)
|
||
if peer == nil || event.MaxID <= 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateReadStories{Peer: peer, MaxID: event.MaxID}
|
||
case domain.UpdateEventSentStoryReaction:
|
||
peer := tgPeer(event.Peer)
|
||
if peer == nil || event.MaxID <= 0 {
|
||
return nil
|
||
}
|
||
reaction := tg.ReactionClass(&tg.ReactionEmpty{})
|
||
if event.Reaction != nil {
|
||
reaction = tgMessageReaction(*event.Reaction)
|
||
}
|
||
return &tg.UpdateSentStoryReaction{Peer: peer, StoryID: event.MaxID, Reaction: reaction}
|
||
case domain.UpdateEventNewStoryReaction:
|
||
peer := tgPeer(event.Peer)
|
||
if peer == nil || event.MaxID <= 0 || event.Reaction == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateNewStoryReaction{Peer: peer, StoryID: event.MaxID, Reaction: tgMessageReaction(*event.Reaction)}
|
||
case domain.UpdateEventQuickReplies:
|
||
return &tg.UpdateQuickReplies{QuickReplies: tgQuickReplies(event.QuickReplies)}
|
||
case domain.UpdateEventNewQuickReply:
|
||
return &tg.UpdateNewQuickReply{QuickReply: tgQuickReply(event.QuickReply)}
|
||
case domain.UpdateEventDeleteQuickReply:
|
||
if event.MaxID <= 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateDeleteQuickReply{ShortcutID: event.MaxID}
|
||
case domain.UpdateEventQuickReplyMessage:
|
||
msg := tgQuickReplyMessage(event.QuickReplyMessage)
|
||
if msg == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateQuickReplyMessage{Message: msg}
|
||
case domain.UpdateEventDeleteQuickReplyMessages:
|
||
if event.MaxID <= 0 || len(event.MessageIDs) == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateDeleteQuickReplyMessages{
|
||
ShortcutID: event.MaxID,
|
||
Messages: append([]int(nil), event.MessageIDs...),
|
||
}
|
||
case domain.UpdateEventContactsReset:
|
||
return &tg.UpdateContactsReset{}
|
||
case domain.UpdateEventDialogPinned:
|
||
// archive folder 行自身的置顶:peer 是 dialogPeerFolder 且绝不能带
|
||
// folder_id flag(TDesktop Folder::applyPinnedUpdate 视其为
|
||
// "Nested folders" 错误)。
|
||
if event.Peer.Type == domain.PeerTypeFolder {
|
||
if event.Peer.ID == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateDialogPinned{Pinned: event.Bool, Peer: &tg.DialogPeerFolder{FolderID: int(event.Peer.ID)}}
|
||
}
|
||
peer := tgDialogPeer(event.Peer)
|
||
if peer == nil {
|
||
return nil
|
||
}
|
||
// FolderID 零值不编码 flag(EncodeBare 自动 SetFlags),归档内置顶
|
||
// 必须带 folder_id=1,否则离线重放会把它应用到主列表。
|
||
return &tg.UpdateDialogPinned{Pinned: event.Bool, Peer: peer, FolderID: event.FolderID}
|
||
case domain.UpdateEventPinnedDialogs:
|
||
update := &tg.UpdatePinnedDialogs{FolderID: event.FolderID}
|
||
if len(event.Peers) > 0 {
|
||
update.Order = tgDialogPeers(event.Peers)
|
||
}
|
||
return update
|
||
case domain.UpdateEventSavedDialogPinned:
|
||
peer := tgDialogPeer(event.Peer)
|
||
if peer == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateSavedDialogPinned{Pinned: event.Bool, Peer: peer}
|
||
case domain.UpdateEventPinnedSavedDialogs:
|
||
update := &tg.UpdatePinnedSavedDialogs{}
|
||
if len(event.Peers) > 0 {
|
||
update.SetOrder(tgDialogPeers(event.Peers))
|
||
}
|
||
return update
|
||
case domain.UpdateEventDialogUnreadMark:
|
||
peer := tgDialogPeer(event.Peer)
|
||
if peer == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateDialogUnreadMark{Unread: event.Bool, Peer: peer}
|
||
case domain.UpdateEventDraftMessage:
|
||
peer := tgPeer(event.Peer)
|
||
if peer == nil {
|
||
return nil
|
||
}
|
||
// Draft 由 enrichDraftMessageEvent 按当前权威态填充;nil/空 = 草稿已删。
|
||
var draft tg.DraftMessageClass
|
||
if event.Draft != nil && !event.Draft.Empty() {
|
||
draft = tgDialogDraft(*event.Draft)
|
||
} else {
|
||
empty := &tg.DraftMessageEmpty{}
|
||
if event.Date > 0 {
|
||
empty.SetDate(event.Date)
|
||
}
|
||
draft = empty
|
||
}
|
||
update := &tg.UpdateDraftMessage{Peer: peer, Draft: draft}
|
||
if event.MaxID > 0 {
|
||
update.SetTopMsgID(event.MaxID)
|
||
}
|
||
return update
|
||
case domain.UpdateEventChannelViewForum:
|
||
if event.Peer.Type != domain.PeerTypeChannel || event.Peer.ID == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateChannelViewForumAsMessages{ChannelID: event.Peer.ID, Enabled: event.Bool}
|
||
case domain.UpdateEventReadChannelDiscussionInbox:
|
||
if event.Peer.Type != domain.PeerTypeChannel || event.Peer.ID == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateReadChannelDiscussionInbox{ChannelID: event.Peer.ID, TopMsgID: event.TopMsgID, ReadMaxID: event.MaxID}
|
||
case domain.UpdateEventReadChannelDiscussionOutbox:
|
||
if event.Peer.Type != domain.PeerTypeChannel || event.Peer.ID == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateReadChannelDiscussionOutbox{ChannelID: event.Peer.ID, TopMsgID: event.TopMsgID, ReadMaxID: event.MaxID}
|
||
case domain.UpdateEventPeerSettings:
|
||
peer := tgPeer(event.Peer)
|
||
if peer == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdatePeerSettings{Peer: peer, Settings: tgPeerSettings(event.Settings)}
|
||
case domain.UpdateEventPeerStoryBlocked:
|
||
peer := tgPeer(event.Peer)
|
||
if peer == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdatePeerBlocked{PeerID: peer, Blocked: event.Bool, BlockedMyStoriesFrom: true}
|
||
case domain.UpdateEventDeleteMessages:
|
||
if len(event.MessageIDs) == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateDeleteMessages{
|
||
Messages: append([]int(nil), event.MessageIDs...),
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
case domain.UpdateEventPinnedMessages:
|
||
peer := tgPeer(event.Peer)
|
||
if peer == nil || len(event.MessageIDs) == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdatePinnedMessages{
|
||
Pinned: event.Bool,
|
||
Peer: peer,
|
||
Messages: append([]int(nil), event.MessageIDs...),
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
case domain.UpdateEventReadHistoryOutbox:
|
||
return tgReadHistoryOutbox(event)
|
||
case domain.UpdateEventReadMessageContents:
|
||
if len(event.MessageIDs) == 0 {
|
||
return nil
|
||
}
|
||
contents := &tg.UpdateReadMessagesContents{
|
||
Messages: append([]int(nil), event.MessageIDs...),
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
if event.Date > 0 {
|
||
// date 是内容被读取的时刻:客户端用它调度 TTL 媒体的
|
||
// "自读取起算"删除与 sender 侧已听时间。
|
||
contents.SetDate(event.Date)
|
||
}
|
||
return contents
|
||
case domain.UpdateEventEditMessage:
|
||
msg := tgMessage(event.Message)
|
||
if msg == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateEditMessage{
|
||
Message: msg,
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
case domain.UpdateEventWebPage:
|
||
// 链接预览就地替换:updateWebPage 仅携带已解析 webPage(按 webPage id 与消息里的
|
||
// pending 占位关联),客户端就地换卡片、不触碰 edit_date。
|
||
if event.Message.Media == nil || event.Message.Media.WebPage == nil {
|
||
return nil
|
||
}
|
||
return &tg.UpdateWebPage{
|
||
Webpage: tgWebPage(*event.Message.Media.WebPage),
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
case domain.UpdateEventMessagePoll:
|
||
if event.Message.ID <= 0 || event.Message.ID > domain.MaxMessageBoxID {
|
||
return nil
|
||
}
|
||
pollPeer := event.Message.Peer
|
||
if pollPeer.Type == "" || pollPeer.ID == 0 {
|
||
pollPeer = event.Peer
|
||
}
|
||
media := event.Message.Media
|
||
if media == nil || media.Kind != domain.MessageMediaKindPoll || media.Poll == nil {
|
||
return nil
|
||
}
|
||
return tgUpdateMessagePoll(pollPeer, event.Message.ID, media.Poll)
|
||
case domain.UpdateEventMessageReactions:
|
||
if event.Message.ID <= 0 || event.Message.ID > domain.MaxMessageBoxID {
|
||
return nil
|
||
}
|
||
peer := event.Message.Peer
|
||
if peer.Type == "" || peer.ID == 0 {
|
||
peer = event.Peer
|
||
}
|
||
outPeer := tgPeer(peer)
|
||
if outPeer == nil {
|
||
return nil
|
||
}
|
||
reactions := event.Message.Reactions
|
||
if reactions == nil {
|
||
empty := domain.ChannelMessageReactions{CanSeeList: true, Results: []domain.ChannelMessageReactionCount{}, Recent: []domain.ChannelMessagePeerReaction{}}
|
||
reactions = &empty
|
||
}
|
||
converted := tgMessageReactions(event.UserID, reactions)
|
||
if converted == nil {
|
||
converted = &tg.MessageReactions{Results: []tg.ReactionCount{}}
|
||
}
|
||
return &tg.UpdateMessageReactions{
|
||
Peer: outPeer,
|
||
MsgID: event.Message.ID,
|
||
Reactions: *converted,
|
||
}
|
||
case domain.UpdateEventDialogFilter:
|
||
update := &tg.UpdateDialogFilter{ID: event.FilterID}
|
||
if event.DialogFilter != nil {
|
||
update.SetFilter(tgDialogFilter(*event.DialogFilter))
|
||
}
|
||
return update
|
||
case domain.UpdateEventDialogFilterOrder:
|
||
return &tg.UpdateDialogFilterOrder{Order: append([]int(nil), event.FilterOrder...)}
|
||
case domain.UpdateEventDialogFilters:
|
||
return &tg.UpdateDialogFilters{}
|
||
case domain.UpdateEventFolderPeers:
|
||
if len(event.FolderPeers) == 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateFolderPeers{
|
||
FolderPeers: tgFolderPeers(event.FolderPeers),
|
||
Pts: event.Pts,
|
||
PtsCount: event.PtsCount,
|
||
}
|
||
case domain.UpdateEventChannelAvailable:
|
||
if event.Peer.Type != domain.PeerTypeChannel || event.Peer.ID == 0 || event.MaxID <= 0 {
|
||
return nil
|
||
}
|
||
return &tg.UpdateChannelAvailableMessages{
|
||
ChannelID: event.Peer.ID,
|
||
AvailableMinID: event.MaxID,
|
||
}
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func addChannels(out *tg.UpdatesDifference, seen map[int64]struct{}, viewerUserID int64, channels []domain.Channel) {
|
||
for _, ch := range channels {
|
||
if ch.ID == 0 {
|
||
continue
|
||
}
|
||
if _, ok := seen[ch.ID]; ok {
|
||
continue
|
||
}
|
||
seen[ch.ID] = struct{}{}
|
||
out.Chats = append(out.Chats, tgChannelChatMin(viewerUserID, ch))
|
||
}
|
||
}
|
||
|
||
func addChannelNudgeChat(out *tg.UpdatesDifference, seen map[int64]struct{}, chat tg.ChatClass) {
|
||
channelID, ok := channelChatID(chat)
|
||
if !ok || channelID == 0 {
|
||
return
|
||
}
|
||
if _, exists := seen[channelID]; exists {
|
||
upgradeMinChannelChat(out, channelID, chat)
|
||
return
|
||
}
|
||
seen[channelID] = struct{}{}
|
||
out.Chats = append(out.Chats, chat)
|
||
}
|
||
|
||
func upgradeMinChannelChat(out *tg.UpdatesDifference, channelID int64, candidate tg.ChatClass) {
|
||
next, ok := candidate.(*tg.Channel)
|
||
if !ok || next.Min {
|
||
return
|
||
}
|
||
for i, existing := range out.Chats {
|
||
existingID, ok := channelChatID(existing)
|
||
if !ok || existingID != channelID {
|
||
continue
|
||
}
|
||
if current, ok := existing.(*tg.Channel); ok && current.Min {
|
||
out.Chats[i] = candidate
|
||
}
|
||
return
|
||
}
|
||
}
|
||
|
||
func channelChatID(chat tg.ChatClass) (int64, bool) {
|
||
switch v := chat.(type) {
|
||
case *tg.Channel:
|
||
return v.ID, true
|
||
case *tg.ChannelForbidden:
|
||
return v.ID, true
|
||
default:
|
||
return 0, false
|
||
}
|
||
}
|
||
|
||
func tgChannelParticipantForUpdate(selfUserID int64, member domain.ChannelMember) tg.ChannelParticipantClass {
|
||
if member.UserID == 0 {
|
||
return nil
|
||
}
|
||
if member.Status == domain.ChannelMemberKicked || member.Status == domain.ChannelMemberBanned || member.BannedRights.ViewMessages {
|
||
return &tg.ChannelParticipantBanned{
|
||
Left: member.Status != domain.ChannelMemberActive,
|
||
Peer: &tg.PeerUser{UserID: member.UserID},
|
||
KickedBy: member.InviterUserID,
|
||
Date: member.LeftAt,
|
||
BannedRights: tgChatBannedRights(member.BannedRights),
|
||
}
|
||
}
|
||
if member.Status == domain.ChannelMemberLeft {
|
||
return &tg.ChannelParticipantLeft{Peer: &tg.PeerUser{UserID: member.UserID}}
|
||
}
|
||
return tgChannelParticipant(selfUserID, member)
|
||
}
|
||
|
||
func tgLangPackDifference(pack domain.LangPack) *tg.LangPackDifference {
|
||
return &tg.LangPackDifference{
|
||
LangCode: pack.LangCode,
|
||
FromVersion: pack.FromVersion,
|
||
Version: pack.Version,
|
||
Strings: tgLangPackStrings(pack.Strings),
|
||
}
|
||
}
|