849 lines
30 KiB
Go
849 lines
30 KiB
Go
package domain
|
||
|
||
// MessageEntityType 标识消息实体类型。
|
||
type MessageEntityType string
|
||
|
||
const (
|
||
MessageEntityBold MessageEntityType = "bold"
|
||
MessageEntityItalic MessageEntityType = "italic"
|
||
MessageEntityUnderline MessageEntityType = "underline"
|
||
MessageEntityStrike MessageEntityType = "strike"
|
||
MessageEntityCode MessageEntityType = "code"
|
||
MessageEntityPre MessageEntityType = "pre"
|
||
MessageEntityTextURL MessageEntityType = "text_url"
|
||
MessageEntityMentionName MessageEntityType = "mention_name"
|
||
MessageEntitySpoiler MessageEntityType = "spoiler"
|
||
MessageEntityBlockquote MessageEntityType = "blockquote"
|
||
MessageEntityCustomEmoji MessageEntityType = "custom_emoji"
|
||
MessageEntityMention MessageEntityType = "mention"
|
||
MessageEntityHashtag MessageEntityType = "hashtag"
|
||
MessageEntityCashtag MessageEntityType = "cashtag"
|
||
MessageEntityBotCommand MessageEntityType = "bot_command"
|
||
MessageEntityURL MessageEntityType = "url"
|
||
MessageEntityEmail MessageEntityType = "email"
|
||
MessageEntityPhone MessageEntityType = "phone"
|
||
MessageEntityBankCard MessageEntityType = "bank_card"
|
||
MessageEntityFormattedDate MessageEntityType = "formatted_date"
|
||
MessageEntityDiffInsert MessageEntityType = "diff_insert"
|
||
MessageEntityDiffReplace MessageEntityType = "diff_replace"
|
||
MessageEntityDiffDelete MessageEntityType = "diff_delete"
|
||
)
|
||
|
||
const (
|
||
// MaxMessageTextLength matches the first-stage text message limit exposed to Telegram clients.
|
||
MaxMessageTextLength = 4096
|
||
// MaxMessageReplyQuoteLength matches TDesktop's quote_length_max app config default.
|
||
MaxMessageReplyQuoteLength = 1024
|
||
// MaxMessageReplyQuoteOffset bounds quote_offset, which is an offset inside message text, not a message id.
|
||
MaxMessageReplyQuoteOffset = MaxMessageTextLength
|
||
// MaxMessageEntityCount limits styled text entity vectors in message text and quotes.
|
||
MaxMessageEntityCount = 256
|
||
// MaxMessageBoxID 是 TL int / PostgreSQL int4 可安全表达的最大 message id。
|
||
MaxMessageBoxID = 1<<31 - 1
|
||
// MaxDeleteMessageIDs 限制单次 deleteMessages/updateDeleteMessages 的 owner 视角 id 数量。
|
||
// 大批量历史清理走 deleteHistory 分批推进,避免单个 RPC 构造超大数组或 durable payload。
|
||
MaxDeleteMessageIDs = 1000
|
||
// MaxGetMessageIDs 限制 getMessages / channels.getMessages 精确 ID 批量。
|
||
MaxGetMessageIDs = 100
|
||
// MaxDeleteHistoryBatch 限制单次 deleteHistory 实际清理的 message box 数量。
|
||
// affectedHistory.Offset > 0 时客户端可继续调用,服务端不一次性 RETURNING 全历史。
|
||
MaxDeleteHistoryBatch = 1000
|
||
// MaxForwardMessageIDs 限制单次 forwardMessages 的 owner 视角 id 数量。
|
||
MaxForwardMessageIDs = 100
|
||
// MaxMessageHistoryAddOffset 限制 history/search 的 add_offset 绝对值。
|
||
// TDesktop 正常只使用小窗口偏移;服务端必须拒绝把客户端传入的超大值变成 SQL OFFSET 或 slice capacity。
|
||
MaxMessageHistoryAddOffset = 100
|
||
)
|
||
|
||
// ClampMessageHistoryAddOffset bounds Telegram history/search add_offset to a small local window.
|
||
func ClampMessageHistoryAddOffset(v int) int {
|
||
if v > MaxMessageHistoryAddOffset {
|
||
return MaxMessageHistoryAddOffset
|
||
}
|
||
if v < -MaxMessageHistoryAddOffset {
|
||
return -MaxMessageHistoryAddOffset
|
||
}
|
||
return v
|
||
}
|
||
|
||
// ValidateMessageReplyBounds validates reply fields that are independent of peer visibility.
|
||
func ValidateMessageReplyBounds(reply *MessageReply) error {
|
||
if reply == nil {
|
||
return nil
|
||
}
|
||
if reply.MessageID < 0 || reply.MessageID > MaxMessageBoxID {
|
||
return ErrReplyMessageIDInvalid
|
||
}
|
||
if reply.TopMessageID < 0 || reply.TopMessageID > MaxMessageBoxID {
|
||
return ErrReplyMessageIDInvalid
|
||
}
|
||
if reply.StoryID < 0 || reply.StoryID > MaxStoryID {
|
||
return ErrReplyMessageIDInvalid
|
||
}
|
||
// story 回复(StoryID>0)不携带 MessageID/TopMessageID;普通回复至少有其一。
|
||
if reply.MessageID == 0 && reply.TopMessageID == 0 && reply.StoryID == 0 {
|
||
return ErrReplyMessageIDInvalid
|
||
}
|
||
if reply.QuoteOffset < 0 || reply.QuoteOffset > MaxMessageReplyQuoteOffset {
|
||
return ErrReplyMessageIDInvalid
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// MessageEntity 是业务层消息实体,不依赖 TL 类型。
|
||
type MessageEntity struct {
|
||
Type MessageEntityType
|
||
Offset int
|
||
Length int
|
||
// URL 仅 text_url 使用。
|
||
URL string
|
||
// UserID 仅 mention_name 使用。
|
||
UserID int64
|
||
// Language 仅 pre 使用。
|
||
Language string
|
||
// DocumentID 仅 custom_emoji 使用。
|
||
DocumentID int64
|
||
// Collapsed 仅 blockquote 使用。
|
||
Collapsed bool
|
||
// Date / Relative / ShortTime / LongTime / ShortDate / LongDate / DayOfWeek
|
||
// 仅 formatted_date 使用。
|
||
Date int
|
||
Relative bool
|
||
ShortTime bool
|
||
LongTime bool
|
||
ShortDate bool
|
||
LongDate bool
|
||
DayOfWeek bool
|
||
// OldText 仅 AI compose diff_replace 使用;普通消息输入不会接受该实体。
|
||
OldText string
|
||
}
|
||
|
||
// Message 是账号视角下的一条私聊消息。
|
||
type Message struct {
|
||
ID int // 当前 owner 视角下的 message box id,暴露给 Telegram 客户端。
|
||
UID int64 // 共享私聊消息主体 id,不暴露给客户端。
|
||
RandomID int64
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
From Peer
|
||
Date int
|
||
EditDate int
|
||
HideEdited bool
|
||
Out bool
|
||
Silent bool
|
||
NoForwards bool
|
||
Body string
|
||
Entities []MessageEntity
|
||
ReplyTo *MessageReply
|
||
Forward *MessageForward
|
||
Reactions *ChannelMessageReactions
|
||
Pts int
|
||
TTLPeriod int
|
||
ExpiresAt int
|
||
Media *MessageMedia
|
||
MediaUnread bool
|
||
ReactionUnread bool
|
||
ViaBotID int64
|
||
// GroupedID 是相册分组 id:同一次 sendMultiMedia 的各条消息共享同一非零值,
|
||
// 客户端据此把它们渲染成一个相册组。非相册消息恒 0。双盒持同一值。
|
||
GroupedID int64
|
||
// Effect 是消息特效 id(message.effect,flags2.2?long):私聊 1-1 专属动画特效
|
||
// (🎉/👍 等),发送方与接收方双盒持同一非零值并各自播放一次;非特效消息恒 0。
|
||
// 转发不携带特效(新消息恒 0)。仅私聊;群/频道不渲染。
|
||
Effect int64
|
||
// ReplyMarkup 是 bot 消息携带的 reply/inline keyboard 快照。仅 bot 出站消息可
|
||
// 非空;普通用户消息恒 nil(发送侧 is_bot 闸门)。双盒持同一快照(无 per-viewer 差异)。
|
||
ReplyMarkup *MessageReplyMarkup
|
||
// RichMessage 是 Layer 228 富文本消息(richMessage)快照,可选;普通消息恒 nil。
|
||
RichMessage *MessageRichMessage
|
||
// Pinned 是 owner 视角的置顶标志(官方私聊多置顶语义:双方各自
|
||
// 的 box 行独立持有,非 pm_oneside 操作两侧同步翻转)。
|
||
Pinned bool
|
||
// Deleted 是 owner 视角的软删除可见性标记。它只用于更新重放/差分恢复
|
||
// 判断是否还能下发消息快照;普通历史查询应在 store 层直接过滤 deleted box。
|
||
Deleted bool
|
||
// SavedPeer 是 Saved Messages 分会话分组键(message.saved_peer_id)。
|
||
// 仅 self-chat box 行非零:直发笔记 = self;转发进收藏夹 = 源会话 peer;
|
||
// 存量回填兜底 hidden author 占位 user 2666000。非 self-chat 行恒零值。
|
||
SavedPeer Peer
|
||
}
|
||
|
||
// NewHistoryClearMessage 把一个 owner 视角的现有 box 原位投影为
|
||
// messageActionHistoryClear 服务消息。调用方只传递仍需保留的身份字段;
|
||
// 其余正文、媒体、reply、reaction、TTL、pin 等载荷全部按不变量清空。
|
||
func NewHistoryClearMessage(ownerUserID int64, peer Peer, boxID int, uid int64, date, pts int) Message {
|
||
self := Peer{Type: PeerTypeUser, ID: ownerUserID}
|
||
return Message{
|
||
ID: boxID,
|
||
UID: uid,
|
||
OwnerUserID: ownerUserID,
|
||
Peer: peer,
|
||
From: self,
|
||
Date: date,
|
||
Out: true,
|
||
Pts: pts,
|
||
Media: &MessageMedia{
|
||
Kind: MessageMediaKindService,
|
||
ServiceAction: &MessageServiceAction{
|
||
Kind: MessageServiceActionHistoryClear,
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
// IsHistoryClearServiceMessage 报告该 box 是否已经是清空历史锚点。
|
||
func IsHistoryClearServiceMessage(msg Message) bool {
|
||
return msg.Media != nil &&
|
||
msg.Media.Kind == MessageMediaKindService &&
|
||
msg.Media.ServiceAction != nil &&
|
||
msg.Media.ServiceAction.Kind == MessageServiceActionHistoryClear
|
||
}
|
||
|
||
const MessageRichBlocksLegacyLayer = 228
|
||
|
||
// MessageRichMessage 是富文本消息(richMessage)的协议中立快照:一组 IV
|
||
// PageBlock(Blocks)+ 内嵌已解析的 Photos/Documents。
|
||
//
|
||
// Blocks 存 gotd TL 序列化后的 []tg.PageBlockClass 不透明字节——PageBlock 体系庞大且
|
||
// input(inputRichMessage.blocks) 与 output(richMessage.blocks) 同构、原样透传,故不在
|
||
// domain 逐类型建模;rpc 层负责 tg.PageBlock 向量 ↔ bytes 的序列化(domain 不依赖 tg)。
|
||
// 与 message media 同理,Photos/Documents 存已解析快照(含 viewer 无关的 access_hash),
|
||
// 投影复用 tgPhoto/tgDocument。HTML/Markdown 输入也会在 RPC 边界归一为同一组 Blocks。
|
||
//
|
||
// BlocksLayer 是这份持久化字节的 exact TL profile,而不是发送客户端的 Layer。历史记录
|
||
// 没有该字段;0 是正式的 storage-v1 标记,固定解释为 Layer 228。新写入必须显式保存
|
||
// 编码时的 profile,读取不得靠 constructor 试解或失败后回退。
|
||
type MessageRichMessage struct {
|
||
Rtl bool `json:"rtl,omitempty"`
|
||
Part bool `json:"part,omitempty"`
|
||
BlocksLayer int `json:"blocks_layer,omitempty"`
|
||
Blocks []byte `json:"blocks,omitempty"`
|
||
Photos []Photo `json:"photos,omitempty"`
|
||
Documents []Document `json:"documents,omitempty"`
|
||
// BotAPIProjection 是由 RPC 边界从同一组已校验 PageBlock 派生出的
|
||
// Bot API RichMessage JSON。它不是第二事实源:写入边界只允许从 Blocks
|
||
// 生成,HTTP Bot API 投影只读,避免 botapi 包反向依赖 tg 类型。
|
||
BotAPIProjection []byte `json:"bot_api_projection,omitempty"`
|
||
}
|
||
|
||
// EffectiveBlocksLayer returns the deterministic storage grammar for Blocks.
|
||
// A missing JSON field is the original storage-v1 format and therefore Layer
|
||
// 228; it is not an adaptive decode fallback.
|
||
func (m *MessageRichMessage) EffectiveBlocksLayer() int {
|
||
if m == nil || m.BlocksLayer == 0 {
|
||
return MessageRichBlocksLegacyLayer
|
||
}
|
||
return m.BlocksLayer
|
||
}
|
||
|
||
// IsZero 表示无富文本载荷(落库时跳过空快照、投影时不下发 rich_message)。
|
||
func (m *MessageRichMessage) IsZero() bool {
|
||
return m == nil || (len(m.Blocks) == 0 && len(m.Photos) == 0 && len(m.Documents) == 0)
|
||
}
|
||
|
||
// MessageReply describes a message reply/thread header without depending on TL types.
|
||
type MessageReply struct {
|
||
MessageID int
|
||
Peer Peer
|
||
TopMessageID int
|
||
ForumTopic bool
|
||
QuoteText string
|
||
QuoteEntities []MessageEntity
|
||
QuoteOffset int
|
||
// StoryID > 0 表示这是一条对 story 的回复(评论):MessageID 为 0,Peer 为 story 作者,
|
||
// 投影为 messageReplyStoryHeader 而非普通 messageReplyHeader。
|
||
StoryID int
|
||
}
|
||
|
||
// MessageForward 描述一条转发消息的原始作者信息。
|
||
type MessageForward struct {
|
||
From Peer
|
||
FromName string
|
||
Date int
|
||
ChannelPost int
|
||
SavedFrom Peer
|
||
SavedFromMsgID int
|
||
}
|
||
|
||
// MessageList 是账号视角下的消息查询结果。
|
||
type MessageList struct {
|
||
Messages []Message
|
||
Users []User
|
||
Count int
|
||
Hash int64
|
||
}
|
||
|
||
// MessageFilter 描述历史/搜索查询条件。
|
||
type MessageFilter struct {
|
||
HasPeer bool
|
||
Peer Peer
|
||
Query string
|
||
OffsetID int
|
||
OffsetDate int
|
||
MinDate int
|
||
MaxDate int
|
||
AddOffset int
|
||
Limit int
|
||
MaxID int
|
||
MinID int
|
||
Hash int64
|
||
// PinnedOnly 仅返回置顶消息(messages.search filterPinned 与
|
||
// userFull.pinned_msg_id 的查询路径)。
|
||
PinnedOnly bool
|
||
MusicOnly bool
|
||
PhoneCallsOnly bool
|
||
// MissedPhoneCallsOnly narrows PhoneCallsOnly to incoming calls that ended
|
||
// with the protocol-level "missed" reason.
|
||
MissedPhoneCallsOnly bool
|
||
NeedTotalCount bool
|
||
// SavedPeer 非零时仅返回 self-chat 中该 saved 子会话的消息
|
||
// (messages.getSavedHistory);Peer 必须同时是 self。
|
||
SavedPeer Peer
|
||
// SavedReactions 非空时仅返回至少带其中一个 tag 的 Saved Messages。
|
||
// 仅 messages.search(peer=self) 使用;普通私聊 reaction 不参与匹配。
|
||
SavedReactions []MessageReaction
|
||
// PeerIDs restricts a global private search to these user peers. Empty is a
|
||
// valid restricted set, so RestrictPeerIDs carries presence separately.
|
||
PeerIDs []int64
|
||
RestrictPeerIDs bool
|
||
}
|
||
|
||
// SendPrivateTextRequest 是私聊文本/媒体发送命令。
|
||
type SendPrivateTextRequest struct {
|
||
SenderUserID int64
|
||
RecipientUserID int64
|
||
RandomID int64
|
||
Message string
|
||
Entities []MessageEntity
|
||
Media *MessageMedia
|
||
Silent bool
|
||
NoForwards bool
|
||
ReplyTo *MessageReply
|
||
Forward *MessageForward
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
// OriginClientSession carries the exact request's initConnection language
|
||
// into an in-process bot responder. Stores and idempotency fingerprints
|
||
// intentionally ignore this ephemeral metadata.
|
||
OriginClientSession ClientSessionMetadata
|
||
// OriginUserID identifies the authenticated initiator when a server-generated
|
||
// service message is authored by another user. Zero preserves the ordinary
|
||
// send path where the sender is the initiator.
|
||
OriginUserID int64
|
||
RecipientBlocked bool
|
||
// IdempotencyFingerprint 是调用边界对原始、不可变发送请求计算的 SHA-256。
|
||
// RPC 层应优先填入原始 TL 请求指纹,避免链接预览、骰子结果、上传媒体
|
||
// 等服务端派生字段让合法重放看起来不同;内部调用留空时 store 会基于
|
||
// domain command 的不可变字段生成等价指纹。
|
||
IdempotencyFingerprint []byte
|
||
// IdempotencyPreflighted is internal execution metadata. A trusted caller sets it only
|
||
// after a read-only replay lookup returned absent, allowing the app/store layers to avoid
|
||
// repeating the same indexed lookup. The transactional unique-key path still fences a
|
||
// concurrent first writer; this flag is never part of the durable request fingerprint.
|
||
IdempotencyPreflighted bool
|
||
TTLPeriod int
|
||
ViaBotID int64
|
||
// GroupedID 相册分组 id(sendMultiMedia 同组共享非零值,非相册恒 0)。
|
||
GroupedID int64
|
||
// Effect 消息特效 id(私聊专属,0 表无特效;调用方已对 catalog 校验过合法性)。
|
||
Effect int64
|
||
// BusinessAutomationKind is internal app-layer metadata used to suppress
|
||
// recursive greeting/away automation for server-generated replies.
|
||
BusinessAutomationKind BusinessAutomationKind
|
||
// ReplyMarkup 是 bot 出站消息的 reply/inline keyboard 快照;普通用户发送恒 nil。
|
||
ReplyMarkup *MessageReplyMarkup
|
||
// RichMessage 是 Layer 228 富文本消息(richMessage)快照,可选;普通消息恒 nil。
|
||
RichMessage *MessageRichMessage
|
||
}
|
||
|
||
// HasContent reports whether the command contains a client-visible message payload.
|
||
// TDesktop rich-message posting intentionally leaves Message empty, so every send
|
||
// boundary must treat text, media, and rich_message as equivalent content sources.
|
||
func (r SendPrivateTextRequest) HasContent() bool {
|
||
return r.Message != "" || !r.Media.IsZero() || !r.RichMessage.IsZero()
|
||
}
|
||
|
||
// PrivateSendReplayRequest identifies one already-committed private send without carrying any
|
||
// mutable or resolver-derived message fields. The fingerprint is computed at the original
|
||
// request boundary and must be a complete SHA-256 value.
|
||
type PrivateSendReplayRequest struct {
|
||
SenderUserID int64
|
||
RecipientUserID int64
|
||
RandomID int64
|
||
IdempotencyFingerprint []byte
|
||
}
|
||
|
||
// SendPrivateTextResult 描述一次私聊文本发送的双端结果。
|
||
type SendPrivateTextResult struct {
|
||
SenderMessage Message
|
||
RecipientMessage Message
|
||
SenderEvent UpdateEvent
|
||
RecipientEvent UpdateEvent
|
||
Duplicate bool
|
||
// ReplayDeleteEvent is the already-durable sender-side deletion that must
|
||
// follow the first-send snapshot in an exact random_id replay. It never
|
||
// represents a newly allocated event.
|
||
ReplayDeleteEvent *UpdateEvent
|
||
}
|
||
|
||
// SetPrivateChatThemeRequest changes the shared theme token for a private dialog.
|
||
type SetPrivateChatThemeRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
Emoticon string
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
RecipientBlocked bool
|
||
}
|
||
|
||
// SetPrivateChatThemeResult describes the state change and optional service message.
|
||
type SetPrivateChatThemeResult struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
Emoticon string
|
||
Changed bool
|
||
Send SendPrivateTextResult
|
||
}
|
||
|
||
// SetPrivateMessageReactionsRequest replaces the current user's reactions for one private message.
|
||
type SetPrivateMessageReactionsRequest struct {
|
||
UserID int64
|
||
Peer Peer
|
||
MessageID int
|
||
Reactions []MessageReaction
|
||
Big bool
|
||
AddToRecent bool
|
||
Date int
|
||
// ReactionsPerUserMax 是 viewer 的每用户上限档位(premium 双档);
|
||
// 0 表示未填,store 侧按默认档裁剪。
|
||
ReactionsPerUserMax int
|
||
}
|
||
|
||
// PrivateMessageReactionsRequest fetches reaction summaries for exact private message ids.
|
||
type PrivateMessageReactionsRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
IDs []int
|
||
}
|
||
|
||
// PrivateMessageReactionsResult describes private reaction updates in owner-visible boxes.
|
||
type PrivateMessageReactionsResult struct {
|
||
Messages []Message
|
||
Reactions ChannelMessageReactions
|
||
}
|
||
|
||
// ForwardPrivateMessagesRequest 是私聊文本消息转发命令。
|
||
type ForwardPrivateMessagesRequest struct {
|
||
OwnerUserID int64
|
||
FromPeer Peer
|
||
ToUserID int64
|
||
MessageIDs []int
|
||
RandomIDs []int64
|
||
Silent bool
|
||
NoForwards bool
|
||
DropAuthor bool
|
||
ReplyTo *MessageReply
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
RecipientBlocked bool
|
||
TTLPeriod int
|
||
}
|
||
|
||
// ForwardPrivateMessagesResult 描述一次私聊转发的 owner 维度结果。
|
||
type ForwardPrivateMessagesResult struct {
|
||
OwnerUserID int64
|
||
SenderMessages []Message
|
||
RecipientMessages []Message
|
||
SenderEvents []UpdateEvent
|
||
RecipientEvents []UpdateEvent
|
||
Duplicates []bool
|
||
ReplayDeleteEvents []*UpdateEvent
|
||
}
|
||
|
||
const PrivateNoForwardsRequestExpirePeriod = 24 * 60 * 60
|
||
|
||
// PrivateNoForwardsState 是一对普通用户唯一的内容保护权威。
|
||
// EnabledByUserID 为 0 或参与者之一;非零时双方共享的会话均受保护。
|
||
type PrivateNoForwardsState struct {
|
||
UserLowID int64
|
||
UserHighID int64
|
||
EnabledByUserID int64
|
||
}
|
||
|
||
func (s PrivateNoForwardsState) Enabled() bool {
|
||
return s.EnabledByUserID != 0
|
||
}
|
||
|
||
func (s PrivateNoForwardsState) ForViewer(viewerUserID int64) (myEnabled, peerEnabled bool) {
|
||
if s.EnabledByUserID == 0 {
|
||
return false, false
|
||
}
|
||
return s.EnabledByUserID == viewerUserID, s.EnabledByUserID != viewerUserID
|
||
}
|
||
|
||
// TogglePrivateNoForwardsRequest 是私聊内容保护的原子状态+服务消息命令。
|
||
type TogglePrivateNoForwardsRequest struct {
|
||
ActorUserID int64
|
||
PeerUserID int64
|
||
Enabled bool
|
||
RequestMsgID int
|
||
RandomID int64
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
}
|
||
|
||
// TogglePrivateNoForwardsResult 同时返回提交后的权威状态与本次真实服务消息。
|
||
// Changed=false 且 Send 为空表示官方定义的 no-op。
|
||
type TogglePrivateNoForwardsResult struct {
|
||
State PrivateNoForwardsState
|
||
Changed bool
|
||
Send SendPrivateTextResult
|
||
}
|
||
|
||
// ReadHistoryRequest 是账号视角的 messages.readHistory 命令。
|
||
type ReadHistoryRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
MaxID int
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
}
|
||
|
||
// ReadHistoryResult 描述一次会话已读操作的业务结果。
|
||
type ReadHistoryResult struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
MaxID int
|
||
StillUnreadCount int
|
||
ChannelPts int
|
||
Changed bool
|
||
InboxEvent UpdateEvent
|
||
OutboxChanged bool
|
||
OutboxUserID int64
|
||
OutboxEvent UpdateEvent
|
||
}
|
||
|
||
// ReadMessageContentsRequest marks media/mention contents as read for exact owner-visible messages.
|
||
type ReadMessageContentsRequest struct {
|
||
OwnerUserID int64
|
||
IDs []int
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
}
|
||
|
||
// ReadMessageContentsResult contains owner-visible message IDs whose content unread state changed.
|
||
type ReadMessageContentsResult struct {
|
||
OwnerUserID int64
|
||
MessageIDs []int
|
||
Event UpdateEvent
|
||
// SenderEvents 是对端发送者的内容已读回执:reader 听完 voice/round 后,
|
||
// 每个原 sender 收到一条 updateReadMessagesContents,messages 用 sender
|
||
// 自己视角的 box id。
|
||
SenderEvents []UpdateEvent
|
||
}
|
||
|
||
// OutboxReadDateRequest 是 messages.getOutboxReadDate 查询。
|
||
type OutboxReadDateRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
ID int
|
||
}
|
||
|
||
// EditMessageRequest 是账号视角下编辑一条已发送私聊消息的命令。
|
||
// Media 非 nil 时整体替换消息媒体快照(当前唯一调用方是 live location 续报/停止,
|
||
// rpc 层负责限定媒体种类);nil 表示纯文本编辑。
|
||
type EditMessageRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
ID int
|
||
Message string
|
||
Entities []MessageEntity
|
||
Media *MessageMedia
|
||
EditDate int
|
||
HideEdited bool
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
// SetReplyMarkup 置位时替换 reply_markup(ReplyMarkup 为 nil/空 = 清空键盘);
|
||
// 未置位则保留原 markup。仅 bot 编辑自己消息时由 RPC 层置位(P3)。
|
||
SetReplyMarkup bool
|
||
ReplyMarkup *MessageReplyMarkup
|
||
// SetRichMessage 置位时替换 rich_message(RichMessage nil/空 = 清空富文本)。
|
||
// TDesktop EditRichMessage 只带 f_rich_message,不带 f_message;store 不能把它误判为空编辑。
|
||
SetRichMessage bool
|
||
RichMessage *MessageRichMessage
|
||
// ViaBotEditBotID 非零时允许对应 bot 编辑经由它发送的 inline 私聊消息。
|
||
ViaBotEditBotID int64
|
||
// AllowTodoParticipantMutation 允许 checklist 参与者在 others_can_* 授权下通过
|
||
// edit 事件替换 todo 媒体快照。仅 RPC todo handler 设置;store 仍会限制为
|
||
// todo->todo 且正文/entities/markup 不变,避免普通消息越权编辑。
|
||
AllowTodoParticipantMutation bool
|
||
// WebPageResolve 置位时为服务端内部「链接预览就地替换」:仅替换 media(不碰 body/
|
||
// entities/edit_date,故不标记「已编辑」),生成 UpdateEventWebPage 而非 edit_message。
|
||
// 幂等守卫:仅当目标当前 media 仍是 ID==ExpectedWebPageID 的 pending 链接预览才替换,
|
||
// 否则返回 ErrMessageNotModified(消息已删/已改/已解析)。
|
||
WebPageResolve bool
|
||
ExpectedWebPageID int64
|
||
}
|
||
|
||
// EditedMessageForUser 描述一次编辑对某个 owner 视角造成的影响。
|
||
type EditedMessageForUser struct {
|
||
UserID int64
|
||
Message Message
|
||
Event UpdateEvent
|
||
}
|
||
|
||
// EditMessageResult 描述消息编辑后的 owner 维度结果。
|
||
type EditMessageResult struct {
|
||
OwnerUserID int64
|
||
Edited []EditedMessageForUser
|
||
}
|
||
|
||
// Self 返回当前请求账号的编辑结果。
|
||
func (r EditMessageResult) Self() EditedMessageForUser {
|
||
for _, item := range r.Edited {
|
||
if item.UserID == r.OwnerUserID {
|
||
return item
|
||
}
|
||
}
|
||
return EditedMessageForUser{UserID: r.OwnerUserID}
|
||
}
|
||
|
||
// Changed 表示本次编辑是否实际影响了任何 owner 视角。
|
||
func (r EditMessageResult) Changed() bool {
|
||
return len(r.Edited) > 0
|
||
}
|
||
|
||
// DeleteMessagesRequest 是账号视角下按消息 ID 删除消息的命令。
|
||
type DeleteMessagesRequest struct {
|
||
OwnerUserID int64
|
||
IDs []int
|
||
Revoke bool
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
}
|
||
|
||
// DeleteHistoryRequest 是账号视角下清空某个 peer 历史的命令。
|
||
type DeleteHistoryRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
MaxID int
|
||
// MinDate/MaxDate 是"按日期删除"的闭区间(unix 秒,0 表示不限);
|
||
// 客户端先本地销毁再发请求,服务端静默忽略会造成删除复活。
|
||
MinDate int
|
||
MaxDate int
|
||
JustClear bool
|
||
Revoke bool
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
}
|
||
|
||
// DeletedMessagesForUser 描述一次删除对某个 owner 视角造成的影响。
|
||
type DeletedMessagesForUser struct {
|
||
UserID int64
|
||
MessageIDs []int
|
||
// Event 保留单一 delete_messages 事件,供普通 deleteMessages 路径与
|
||
// replay receipt 使用;just_clear 还会在 Events 中携带 read/edit 事件。
|
||
Event UpdateEvent
|
||
Events []UpdateEvent
|
||
// Pts/PtsCount 是本次 method 对该 owner 的 affected watermark 汇总。
|
||
// 普通删除等于 Event;just_clear 等于最后一条真实 update 的 pts 与
|
||
// delete/read/edit 三段 pts_count 之和。
|
||
Pts int
|
||
PtsCount int
|
||
}
|
||
|
||
// AffectedPts 返回 messages.Affected* 应使用的最终 PTS 与本次总增量。
|
||
func (d DeletedMessagesForUser) AffectedPts() (int, int) {
|
||
if d.Pts != 0 {
|
||
return d.Pts, d.PtsCount
|
||
}
|
||
return d.Event.Pts, d.Event.PtsCount
|
||
}
|
||
|
||
// DeleteMessagesResult 描述消息删除后的 owner 维度结果。
|
||
type DeleteMessagesResult struct {
|
||
OwnerUserID int64
|
||
Deleted []DeletedMessagesForUser
|
||
Offset int
|
||
}
|
||
|
||
// Self 返回当前请求账号的删除结果。
|
||
func (r DeleteMessagesResult) Self() DeletedMessagesForUser {
|
||
for _, item := range r.Deleted {
|
||
if item.UserID == r.OwnerUserID {
|
||
return item
|
||
}
|
||
}
|
||
return DeletedMessagesForUser{UserID: r.OwnerUserID}
|
||
}
|
||
|
||
// Changed 表示本次删除是否实际影响了任何 owner 视角。
|
||
func (r DeleteMessagesResult) Changed() bool {
|
||
for _, item := range r.Deleted {
|
||
pts, _ := item.AffectedPts()
|
||
if len(item.MessageIDs) > 0 || pts > 0 {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// PinPrivateMessageRequest 是 messages.updatePinnedMessage 的私聊命令
|
||
// (含 Saved Messages = self peer)。
|
||
type PinPrivateMessageRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
MessageID int
|
||
Pinned bool
|
||
// PmOneside 仅置顶在本侧(官方私聊置顶框"同时为对方置顶"未勾选时),
|
||
// 不向对端翻转、不生成服务消息。unpin 无此语义,恒双侧清除。
|
||
PmOneside bool
|
||
Silent bool
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
}
|
||
|
||
// PinnedMessagesForUser 描述置顶状态变化对某个 owner 视角的影响。
|
||
type PinnedMessagesForUser struct {
|
||
UserID int64
|
||
Peer Peer
|
||
MessageIDs []int
|
||
Pinned bool
|
||
Event UpdateEvent
|
||
}
|
||
|
||
// MaxUnpinAllBatch 限制单次 unpinAllMessages 实际清除的置顶数量;
|
||
// 超出部分由客户端按 affectedHistory.Offset>0 续发清除,单条
|
||
// updatePinnedMessages 的 messages 向量随之有界。
|
||
const MaxUnpinAllBatch = 1000
|
||
|
||
// PinPrivateMessageResult 描述私聊置顶/取消置顶的 owner 维度结果。
|
||
// Updated 为空表示状态未变化(幂等 no-op,不烧 pts)。
|
||
type PinPrivateMessageResult struct {
|
||
OwnerUserID int64
|
||
Updated []PinnedMessagesForUser
|
||
// Offset 非 0 表示 unpinAll 还有剩余批次待清。
|
||
Offset int
|
||
}
|
||
|
||
// Self 返回当前请求账号的置顶结果。
|
||
func (r PinPrivateMessageResult) Self() PinnedMessagesForUser {
|
||
for _, item := range r.Updated {
|
||
if item.UserID == r.OwnerUserID {
|
||
return item
|
||
}
|
||
}
|
||
return PinnedMessagesForUser{UserID: r.OwnerUserID}
|
||
}
|
||
|
||
// Changed 表示本次操作是否实际翻转了任何 owner 视角的置顶状态。
|
||
func (r PinPrivateMessageResult) Changed() bool {
|
||
for _, item := range r.Updated {
|
||
if len(item.MessageIDs) > 0 {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// UnpinAllPrivateMessagesRequest 是 messages.unpinAllMessages 的私聊命令。
|
||
type UnpinAllPrivateMessagesRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
Date int
|
||
OriginAuthKeyID [8]byte
|
||
OriginSessionID int64
|
||
}
|
||
|
||
// ScheduledMessage is a pending account-owned message that has not entered
|
||
// normal peer history yet.
|
||
type ScheduledMessage struct {
|
||
OwnerUserID int64
|
||
ID int
|
||
Peer Peer
|
||
RandomID int64
|
||
Message string
|
||
Entities []MessageEntity
|
||
Media *MessageMedia
|
||
RichMessage *MessageRichMessage
|
||
Silent bool
|
||
NoForwards bool
|
||
ReplyTo *MessageReply
|
||
Forward *MessageForward
|
||
SendAs *Peer
|
||
ScheduleDate int
|
||
ScheduleRepeatPeriod int
|
||
CreatedAt int
|
||
UpdatedAt int
|
||
State string
|
||
SentMessageID int
|
||
}
|
||
|
||
// ScheduledMessageList is a bounded scheduled queue page.
|
||
type ScheduledMessageList struct {
|
||
Messages []ScheduledMessage
|
||
Count int
|
||
Hash int64
|
||
}
|
||
|
||
// ScheduleMessageRequest creates one scheduled message.
|
||
type ScheduleMessageRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
RandomID int64
|
||
Message string
|
||
Entities []MessageEntity
|
||
Media *MessageMedia
|
||
RichMessage *MessageRichMessage
|
||
Silent bool
|
||
NoForwards bool
|
||
ReplyTo *MessageReply
|
||
Forward *MessageForward
|
||
SendAs *Peer
|
||
ScheduleDate int
|
||
ScheduleRepeatPeriod int
|
||
Date int
|
||
}
|
||
|
||
// EditScheduledMessageRequest updates one pending scheduled message before it
|
||
// enters normal history.
|
||
type EditScheduledMessageRequest struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
ID int
|
||
SetMessage bool
|
||
Message string
|
||
Entities []MessageEntity
|
||
SetRichMessage bool
|
||
RichMessage *MessageRichMessage
|
||
ScheduleDate int
|
||
Date int
|
||
}
|
||
|
||
// ScheduledMessageFilter selects scheduled messages for one owner/peer.
|
||
type ScheduledMessageFilter struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
IDs []int
|
||
Limit int
|
||
Hash int64
|
||
}
|
||
|
||
// ScheduledMessageClaim describes a due/manual scheduled dispatch claim.
|
||
type ScheduledMessageClaim struct {
|
||
OwnerUserID int64
|
||
Peer Peer
|
||
IDs []int
|
||
Now int
|
||
Limit int
|
||
LeaseUntil int
|
||
}
|