fix(channels): sync preserve locally cleared dialogs

This commit is contained in:
iamxvbaba 2026-07-29 16:19:04 +08:00
parent e22fac1c3f
commit 73cf6a8184
50 changed files with 1525 additions and 327 deletions

View file

@ -485,23 +485,29 @@ func (c Channel) MembersListAdminOnly() bool {
// ChannelMember is one user's channel membership and read state.
type ChannelMember struct {
ChannelID int64
UserID int64
InviterUserID int64
Role ChannelMemberRole
Status ChannelMemberStatus
JoinedAt int
LeftAt int
AdminRights ChannelAdminRights
BannedRights ChannelBannedRights
Rank string
AvailableMinID int
AvailableMinPts int
ReadInboxMaxID int
ReadInboxDate int
ReadOutboxMaxID int
UnreadMark bool
SlowmodeLastSendDate int
ChannelID int64
UserID int64
InviterUserID int64
Role ChannelMemberRole
Status ChannelMemberStatus
JoinedAt int
LeftAt int
AdminRights ChannelAdminRights
BannedRights ChannelBannedRights
Rank string
AvailableMinID int
AvailableMinPts int
// HistoryClearAnchorID/Date identify an owner-local channels.deleteHistory
// boundary. They are deliberately separate from AvailableMinID: joining a
// hidden-prehistory channel also advances AvailableMinID but must not
// manufacture a "History cleared" service message.
HistoryClearAnchorID int
HistoryClearAnchorDate int
ReadInboxMaxID int
ReadInboxDate int
ReadOutboxMaxID int
UnreadMark bool
SlowmodeLastSendDate int
// Guest is a computed, non-persisted view used for subscribers accessing a
// private linked discussion group without joining it. Guest must never be
// written to channel_members and is still projected to clients as left.
@ -545,22 +551,24 @@ func (m ChannelMember) CanPostChannelMessages() bool {
// ChannelDialog is the current user's owner-view dialog state for a channel.
type ChannelDialog struct {
UserID int64
ChannelID int64
FolderID int
TopMessageID int
TopMessageDate int
ReadInboxMaxID int
ReadOutboxMaxID int
UnreadCount int
UnreadMentions int
UnreadReactions int
Pinned bool
PinnedOrder int
UnreadMark bool
ViewForumAsMessages bool
HasScheduled bool
DefaultSendAs *Peer
UserID int64
ChannelID int64
FolderID int
TopMessageID int
TopMessageDate int
HistoryClearAnchorID int
HistoryClearAnchorDate int
ReadInboxMaxID int
ReadOutboxMaxID int
UnreadCount int
UnreadMentions int
UnreadReactions int
Pinned bool
PinnedOrder int
UnreadMark bool
ViewForumAsMessages bool
HasScheduled bool
DefaultSendAs *Peer
}
// ChannelMessageActionType identifies service messages generated by channel operations.
@ -573,7 +581,11 @@ const (
ChannelActionChatDelete ChannelMessageActionType = "chat_delete_user"
ChannelActionChatEditPhoto ChannelMessageActionType = "chat_edit_photo"
ChannelActionChatDeletePhoto ChannelMessageActionType = "chat_delete_photo"
ChannelActionChatJoined ChannelMessageActionType = "chat_joined"
// ChannelActionHistoryClear is an owner-local projection at the member's
// channels.deleteHistory boundary. It is never persisted into the shared
// channel_messages row and never produces channel PTS.
ChannelActionHistoryClear ChannelMessageActionType = "history_clear"
ChannelActionChatJoined ChannelMessageActionType = "chat_joined"
// ChannelActionChatJoinedByLink 是经邀请链接加入的服务消息,
// 渲染为 "X joined the group via invite link"。
ChannelActionChatJoinedByLink ChannelMessageActionType = "chat_joined_by_link"
@ -710,6 +722,39 @@ type ChannelMessage struct {
Deleted bool
}
// ProjectChannelHistoryClearMessage returns the owner-local service-message
// projection for one channel history boundary. Identity fields from the shared
// source are retained when available, while all user payload, media, reply,
// reaction, pin, TTL and edit state is removed. A zero source is valid for a
// retained anchor whose shared row has already been physically pruned.
func ProjectChannelHistoryClearMessage(source ChannelMessage, channelID int64, messageID, messageDate int) ChannelMessage {
if source.ChannelID == 0 {
source.ChannelID = channelID
}
if source.ID == 0 {
source.ID = messageID
}
if source.Date == 0 {
source.Date = messageDate
}
return ChannelMessage{
ChannelID: source.ChannelID,
ID: source.ID,
SenderUserID: source.SenderUserID,
From: source.From,
Date: source.Date,
Post: source.Post,
Silent: source.Silent,
Action: &ChannelMessageAction{Type: ChannelActionHistoryClear},
}
}
// IsChannelHistoryClearMessage reports whether msg is an owner-local history
// clear projection rather than a shared channel service message.
func IsChannelHistoryClearMessage(msg ChannelMessage) bool {
return msg.Action != nil && msg.Action.Type == ChannelActionHistoryClear
}
// MessageReactionType identifies one stored reaction constructor without depending on TL types.
type MessageReactionType string
@ -1290,19 +1335,27 @@ type ChannelDifference struct {
Events []ChannelUpdateEvent
NewMessages []ChannelMessage
OtherUpdates []ChannelUpdateEvent
Users []User
Channels []Channel
Pts int
Final bool
TooLong bool
Dialog ChannelDialog
Timeout int
// AvailableMinID is an owner-local, absolute no-PTS boundary appended to a
// normal channel difference. It must never be represented as a synthetic
// ChannelUpdateEvent because updateChannelAvailableMessages has no pts.
AvailableMinID int
Users []User
Channels []Channel
Pts int
Final bool
TooLong bool
Dialog ChannelDialog
Timeout int
}
// DirtyChannel identifies an active channel with channel-scoped updates after an account difference date.
// DirtyChannel identifies an active channel with shared channel updates and/or
// an owner-local no-PTS history-clear boundary after an account difference date.
type DirtyChannel struct {
ChannelID int64
Pts int
ChannelID int64
Pts int
ChannelUpdatesDirty bool
AvailableMinID int
HistoryClearDate int
}
// ChannelUpdateRetentionCheckpoint is the durable recovery boundary for one channel.
@ -1980,6 +2033,9 @@ type DeleteChannelHistoryResult struct {
Recipients []int64
Offset int
AvailableMinID int
// AvailableMinChanged is true only when this owner-local request advanced
// the member boundary and installed a new history-clear anchor.
AvailableMinChanged bool
}
// UpdateChannelPinnedMessageRequest pins or unpins one channel/supergroup message.
@ -2192,6 +2248,10 @@ type ChannelHistoryFilter struct {
SenderUserID int64
PinnedOnly bool
MusicOnly bool
// IncludeHistoryClearAnchor is set only by messages.getHistory. Search,
// media and topic projections must not count the owner-local service marker
// as shared channel content.
IncludeHistoryClearAnchor bool
// NeedTotalCount requests the exact number of messages matching the static
// filters before offset/add_offset pagination. Ordinary history pages leave
// this false and keep the bounded len(page)+has-more hint.

View file

@ -69,24 +69,29 @@ func (p Peer) IsSelfUser(userID int64) bool {
// Dialog 是账号的一条会话摘要。
type Dialog struct {
Peer Peer
ChannelLeft bool
FolderID int
TopMessage int
TopMessageDate int
ReadInboxMaxID int
ReadOutboxMaxID int
UnreadCount int
UnreadMentions int
UnreadReactions int
TTLPeriod int
ThemeEmoticon string
HasScheduled bool
Pinned bool
PinnedOrder int
UnreadMark bool
ViewForumAsMessages bool
PeerSettingsBarHidden bool
Peer Peer
ChannelLeft bool
FolderID int
TopMessage int
TopMessageDate int
// HistoryClearAnchorID/Date are internal owner-view projection metadata.
// They are not TL dialog fields; dialog response assembly uses them to
// materialize the matching messageActionHistoryClear top payload.
HistoryClearAnchorID int
HistoryClearAnchorDate int
ReadInboxMaxID int
ReadOutboxMaxID int
UnreadCount int
UnreadMentions int
UnreadReactions int
TTLPeriod int
ThemeEmoticon string
HasScheduled bool
Pinned bool
PinnedOrder int
UnreadMark bool
ViewForumAsMessages bool
PeerSettingsBarHidden bool
// Pts 是 channel peer 当前 channel pts客户端用 dialog.pts 初始化本地
// channel 序列并决定 getChannelDifference 起点channel dialog 必填。
Pts int

View file

@ -44,7 +44,6 @@ const (
UpdateEventDialogFilterOrder UpdateEventType = "dialog_filter_order"
UpdateEventDialogFilters UpdateEventType = "dialog_filters"
UpdateEventFolderPeers UpdateEventType = "folder_peers"
UpdateEventChannelAvailable UpdateEventType = "channel_available_messages"
UpdateEventChannelViewForum UpdateEventType = "channel_view_forum_as_messages"
UpdateEventStory UpdateEventType = "story"
UpdateEventReadStories UpdateEventType = "read_stories"
@ -141,7 +140,6 @@ func (e UpdateEvent) LacksWirePts() bool {
UpdateEventDialogFilter,
UpdateEventDialogFilterOrder,
UpdateEventDialogFilters,
UpdateEventChannelAvailable,
UpdateEventChannelViewForum,
UpdateEventStory,
UpdateEventReadStories,
@ -178,7 +176,10 @@ type UpdateDifference struct {
// ChannelDifferenceNudge is a computed account-level hint that a channel diff is dirty.
type ChannelDifferenceNudge struct {
ChannelID int64
Pts int
Channel *ChannelView
ChannelID int64
Pts int
ChannelUpdatesDirty bool
AvailableMinID int
HistoryClearDate int
Channel *ChannelView
}