internal/domain/message.go
Rama main · réplica de lectura del repositorio original.
package domain
// MessageEntityType identifies a message entity type.
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 = 2 * MaxMessageTextLength // UTF-16 units, including surrogate pairs.
// MaxMessageEntityCount limits styled text entity vectors in message text and quotes.
MaxMessageEntityCount = 256
// MaxMessageBoxID is the largest message id safely expressible by a TL int / PostgreSQL int4.
MaxMessageBoxID = 1<<31 - 1
// MaxDeleteMessageIDs limits the owner-perspective id count of a single deleteMessages/updateDeleteMessages.
// Large history cleanup goes through deleteHistory in batches, so a single RPC cannot build a huge array or durable payload.
MaxDeleteMessageIDs = 1000
// MaxGetMessageIDs limits the exact-ID batch of getMessages / channels.getMessages.
MaxGetMessageIDs = 100
// MaxDeleteHistoryBatch limits the number of message boxes a single deleteHistory actually cleans.
// affectedHistory.Offset > 0 lets the client keep calling, and the server does not RETURN the whole history at once.
MaxDeleteHistoryBatch = 1000
// MaxForwardMessageIDs limits the owner-perspective id count of a single forwardMessages.
MaxForwardMessageIDs = 100
// MaxMessageHistoryAddOffset limits the absolute value of history/search add_offset.
// TDesktop normally uses only small window offsets; the server must refuse to turn an oversized client value into a SQL OFFSET or 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
}
// A story reply (StoryID>0) carries no MessageID/TopMessageID; a normal reply has at least one.
if reply.MessageID == 0 && reply.TopMessageID == 0 && reply.StoryID == 0 && reply.External == nil {
return ErrReplyMessageIDInvalid
}
if reply.QuoteOffset < 0 || reply.QuoteOffset > MaxMessageReplyQuoteOffset {
return ErrReplyMessageIDInvalid
}
return nil
}
// MessageEntity is a business-layer message entity, independent of TL types.
type MessageEntity struct {
Type MessageEntityType
Offset int
Length int
// URL is used only by text_url.
URL string
// UserID is used only by mention_name.
UserID int64
// Language is used only by pre.
Language string
// DocumentID is used only by custom_emoji.
DocumentID int64
// Collapsed is used only by blockquote.
Collapsed bool
// Date / Relative / ShortTime / LongTime / ShortDate / LongDate / DayOfWeek
// are used only by formatted_date.
Date int
Relative bool
ShortTime bool
LongTime bool
ShortDate bool
LongDate bool
DayOfWeek bool
// OldText is used only by AI compose diff_replace; ordinary message input does not accept this entity.
OldText string
}
// Message is one private chat message from an account's perspective.
type Message struct {
ID int // message box id from the current owner's perspective, exposed to Telegram clients
UID int64 // shared private-message body id, not exposed to clients
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 is the album grouping id: the messages of one sendMultiMedia share the same non-zero value,
// and the client renders them as a single album group. Non-album messages are always 0. Both boxes hold the same value.
GroupedID int64
// Effect is the message effect id (message.effect, flags2.2?long): the animation effect exclusive to a 1-1 private chat
// (🎉/👍 etc.); both the sender and recipient boxes hold the same non-zero value and each plays it once; non-effect messages are always 0.
// A forward carries no effect (the new message is always 0). Private chats only; groups/channels do not render it.
Effect int64
// ReplyMarkup is the reply/inline keyboard snapshot carried by a bot message. Only a bot outbound message may be
// non-nil; an ordinary user message is always nil (the send-side is_bot gate). Both boxes hold the same snapshot (no per-viewer difference).
ReplyMarkup *MessageReplyMarkup
// RichMessage is the Layer 228 rich text message (richMessage) snapshot, optional; an ordinary message is always nil.
RichMessage *MessageRichMessage
// Pinned is the owner-perspective pin flag (official private-chat multi-pin semantics: each side's
// box row holds it independently, and a non-pm_oneside operation flips both sides together).
Pinned bool
// Deleted is the owner-perspective soft-delete visibility marker. It is used only by update replay/difference recovery
// to decide whether a message snapshot can still be sent; an ordinary history query should filter deleted boxes directly at the store layer.
Deleted bool
// SavedPeer is the Saved Messages sub-chat grouping key (message.saved_peer_id).
// Only a self-chat box row is non-zero: a directly written note = self; a forward into saved messages = the source chat peer;
// a backfill fallback uses the hidden-author placeholder user 2666000. A non-self-chat row is always the zero value.
SavedPeer Peer
PaidStars int64
}
// NewHistoryClearMessage projects an existing owner-perspective box in place into a
// messageActionHistoryClear service message. The caller passes only the identity fields that must be kept;
// the remaining body, media, reply, reaction, TTL, pin and other payloads are all cleared per the invariant.
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 reports whether the box is already a history-clear anchor.
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 is the protocol-neutral snapshot of a rich text message (richMessage): a set of IV
// PageBlocks (Blocks) + embedded resolved Photos/Documents.
//
// Blocks stores the opaque bytes of the gotd TL-serialized []tg.PageBlockClass — the PageBlock system is huge and
// input (inputRichMessage.blocks) and output (richMessage.blocks) are isomorphic and passed through verbatim, so it is not modeled per type
// in domain; the rpc layer serializes the tg.PageBlock vector ↔ bytes (domain does not depend on tg).
// As with message media, Photos/Documents store resolved snapshots (including the viewer-independent access_hash),
// and projection reuses tgPhoto/tgDocument. HTML/Markdown input is also normalized to the same set of Blocks at the RPC boundary.
//
// BlocksLayer is the exact TL profile of these persisted bytes, not the sending client's Layer. Historical records
// lack this field; 0 is the formal storage-v1 marker, always interpreted as Layer 228. New writes must explicitly store
// the profile used at encoding time, and reads must not rely on trial-decoding constructors or falling back on failure.
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 is the Bot API RichMessage JSON derived by the RPC boundary from the same
// set of validated PageBlocks. It is not a second source of truth: the write boundary may only
// generate it from Blocks, and the HTTP Bot API projection is read-only, avoiding a reverse dependency of the botapi package on tg types.
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 means no rich text payload (skip an empty snapshot on persist, do not send rich_message on projection).
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
// External is an immutable source snapshot resolved by the owning store.
// It is not client input and does not authorize a source message lookup.
External *MessageReplyExternal `json:",omitempty"`
// StoryID > 0 means this is a reply (comment) to a story: MessageID is 0, Peer is the story author,
// and it projects as messageReplyStoryHeader rather than an ordinary messageReplyHeader.
StoryID int
}
// MessageForward describes the original author information of a forwarded message.
type MessageForward struct {
From Peer
FromName string
Date int
ChannelPost int
SavedFrom Peer
SavedFromMsgID int
}
// MessageList is a message query result from an account's perspective.
type MessageList struct {
Messages []Message
Users []User
Count int
Hash int64
}
// MessageFilter describes history/search query conditions.
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
// SenderUserID intersects all other predicates; zero means no sender filter.
SenderUserID int64
// CountOnly ignores pagination and returns the exact filtered total without
// loading message payloads or users. History's default limit is separate.
CountOnly bool
// PinnedOnly returns only pinned messages (the query path of messages.search filterPinned and
// 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
// When SavedPeer is non-zero, return only the messages of that saved sub-chat in the self-chat
// (messages.getSavedHistory); Peer must also be self.
SavedPeer Peer
// When SavedReactions is non-empty, return only Saved Messages carrying at least one of the tags.
// Used only by messages.search(peer=self); an ordinary private-chat reaction does not participate in matching.
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 is the private text/media send command.
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 is the SHA-256 computed by the call boundary over the original, immutable send request.
// The RPC layer should prefer the original TL request fingerprint, so server-derived fields such as link previews, dice results and
// uploaded media do not make a legitimate replay look different; when an internal call leaves it empty, the store generates an equivalent
// fingerprint from the domain command's immutable fields.
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 is the album grouping id (the same sendMultiMedia group shares a non-zero value; non-album is always 0).
GroupedID int64
// Effect is the message effect id (private-chat only, 0 means no effect; the caller has validated legality against the catalog).
Effect int64
// BusinessAutomationKind is internal app-layer metadata used to suppress
// recursive greeting/away automation for server-generated replies.
BusinessAutomationKind BusinessAutomationKind
// ReplyMarkup is the reply/inline keyboard snapshot of a bot outbound message; an ordinary user send is always nil.
ReplyMarkup *MessageReplyMarkup
// RichMessage is the Layer 228 rich text message (richMessage) snapshot, optional; an ordinary message is always nil.
RichMessage *MessageRichMessage
PaidStars int64
}
// 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 describes the two-sided result of one private text send.
type PaidStarsCharge struct {
Amount int64
SenderBalance int64
RecipientBalance int64
}
type SendPrivateTextResult struct {
SenderMessage Message
RecipientMessage Message
SenderEvent UpdateEvent
RecipientEvent UpdateEvent
Duplicate bool
PaidStars *PaidStarsCharge
// 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 is the viewer's per-user limit tier (Premium dual tier);
// 0 means unfilled, and the store trims to the default tier.
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 is the private text message forward command.
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 describes the owner-dimension result of one private forward.
type ForwardPrivateMessagesResult struct {
OwnerUserID int64
SenderMessages []Message
RecipientMessages []Message
SenderEvents []UpdateEvent
RecipientEvents []UpdateEvent
Duplicates []bool
ReplayDeleteEvents []*UpdateEvent
}
const PrivateNoForwardsRequestExpirePeriod = 24 * 60 * 60
// PrivateNoForwardsState is the single content-protection authority for a pair of ordinary users.
// EnabledByUserID is 0 or one of the participants; when non-zero, chats shared by both sides are protected.
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 is the atomic state + service message command for private content protection.
type TogglePrivateNoForwardsRequest struct {
ActorUserID int64
PeerUserID int64
Enabled bool
RequestMsgID int
RandomID int64
Date int
OriginAuthKeyID [8]byte
OriginSessionID int64
}
// TogglePrivateNoForwardsResult returns both the committed authoritative state and this call's real service message.
// Changed=false with an empty Send means the officially defined no-op.
type TogglePrivateNoForwardsResult struct {
State PrivateNoForwardsState
Changed bool
Send SendPrivateTextResult
}
// ReadHistoryRequest is the messages.readHistory command from an account's perspective.
type ReadHistoryRequest struct {
OwnerUserID int64
Peer Peer
MaxID int
Date int
OriginAuthKeyID [8]byte
OriginSessionID int64
}
// ReadHistoryResult describes the business result of one chat read operation.
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 are the content read receipts for the peer senders: after the reader finishes a voice/round,
// each original sender receives an updateReadMessagesContents, where messages uses the sender's
// own-perspective box id.
SenderEvents []UpdateEvent
}
// OutboxReadDateRequest is the messages.getOutboxReadDate query.
type OutboxReadDateRequest struct {
OwnerUserID int64
Peer Peer
ID int
}
// EditMessageRequest is the command to edit a sent private message from an account's perspective.
// A non-nil Media replaces the message media snapshot wholesale (the only current caller is live location update/stop,
// and the rpc layer limits the media kind); nil means a text-only edit.
type EditMessageRequest struct {
OwnerUserID int64
Peer Peer
ID int
Message string
Entities []MessageEntity
Media *MessageMedia
EditDate int
HideEdited bool
OriginAuthKeyID [8]byte
OriginSessionID int64
// When SetReplyMarkup is set, replace reply_markup (a nil/empty ReplyMarkup = clear the keyboard);
// when unset, the original markup is preserved. The RPC layer sets it only when a bot edits its own message (P3).
SetReplyMarkup bool
ReplyMarkup *MessageReplyMarkup
// When SetRichMessage is set, replace rich_message (a nil/empty RichMessage = clear the rich text).
// TDesktop EditRichMessage carries only f_rich_message, not f_message; the store must not mistake it for an empty edit.
SetRichMessage bool
RichMessage *MessageRichMessage
// A non-zero ViaBotEditBotID allows the corresponding bot to edit inline private messages sent through it.
ViaBotEditBotID int64
// AllowTodoParticipantMutation lets a checklist participant replace the todo media snapshot through an
// edit event under the others_can_* grant. Only the RPC todo handler sets it; the store still restricts it to
// todo->todo with an unchanged body/entities/markup, preventing an ordinary message from being edited without permission.
AllowTodoParticipantMutation bool
// When WebPageResolve is set it is a server-internal "replace link preview in place": only the media is replaced (body/
// entities/edit_date untouched, so it is not marked "edited") and UpdateEventWebPage is generated rather than edit_message.
// Idempotency guard: replace only when the target's current media is still a pending link preview with ID==ExpectedWebPageID,
// otherwise return ErrMessageNotModified (the message was deleted/changed/resolved).
WebPageResolve bool
ExpectedWebPageID int64
// WidgetData patches only a bot widget's public/chat data in place
// (messages.editMessageWidgetData). Like WebPageResolve it leaves
// body/entities/edit_date untouched, so the message is not marked edited and
// updateMessageWidgetData is generated instead of edit_message.
WidgetData *MessageWidgetDataPatch
}
// MessageWidgetDataPatch is the in-place widget data change carried by an edit.
// A nil Public/Char pointer field means "leave unchanged".
type MessageWidgetDataPatch struct {
PublicData *string
ChatData *string
}
// EditedMessageForUser describes the effect of one edit on a given owner's perspective.
type EditedMessageForUser struct {
UserID int64
Message Message
Event UpdateEvent
}
// EditMessageResult describes the owner-dimension result after a message edit.
type EditMessageResult struct {
OwnerUserID int64
Edited []EditedMessageForUser
}
// Self returns the edit result for the current requesting account.
func (r EditMessageResult) Self() EditedMessageForUser {
for _, item := range r.Edited {
if item.UserID == r.OwnerUserID {
return item
}
}
return EditedMessageForUser{UserID: r.OwnerUserID}
}
// Changed reports whether this edit actually affected any owner's perspective.
func (r EditMessageResult) Changed() bool {
return len(r.Edited) > 0
}
// DeleteMessagesRequest is the command to delete messages by id from an account's perspective.
type DeleteMessagesRequest struct {
OwnerUserID int64
IDs []int
Revoke bool
Date int
OriginAuthKeyID [8]byte
OriginSessionID int64
}
// DeleteHistoryRequest is the command to clear a peer's history from an account's perspective.
type DeleteHistoryRequest struct {
OwnerUserID int64
Peer Peer
MaxID int
// MinDate/MaxDate are the closed interval of a "delete by date" (unix seconds, 0 means unbounded);
// the client destroys locally before sending the request, so a server that silently ignores it would resurrect the deletions.
MinDate int
MaxDate int
JustClear bool
Revoke bool
Date int
OriginAuthKeyID [8]byte
OriginSessionID int64
}
// DeletedMessagesForUser describes the effect of one deletion on a given owner's perspective.
type DeletedMessagesForUser struct {
UserID int64
MessageIDs []int
// Event keeps a single delete_messages event for the ordinary deleteMessages path and
// the replay receipt; just_clear additionally carries read/edit events in Events.
Event UpdateEvent
Events []UpdateEvent
// Pts/PtsCount is the affected watermark summary of this method for that owner.
// For an ordinary delete it equals Event; for just_clear it equals the pts of the last real update and
// the sum of the delete/read/edit three-segment pts_count.
Pts int
PtsCount int
}
// AffectedPts returns the final PTS and this call's total increment that messages.Affected* should use.
func (d DeletedMessagesForUser) AffectedPts() (int, int) {
if d.Pts != 0 {
return d.Pts, d.PtsCount
}
return d.Event.Pts, d.Event.PtsCount
}
// DeleteMessagesResult describes the owner-dimension result after message deletion.
type DeleteMessagesResult struct {
OwnerUserID int64
Deleted []DeletedMessagesForUser
Offset int
}
// Self returns the deletion result for the current requesting account.
func (r DeleteMessagesResult) Self() DeletedMessagesForUser {
for _, item := range r.Deleted {
if item.UserID == r.OwnerUserID {
return item
}
}
return DeletedMessagesForUser{UserID: r.OwnerUserID}
}
// Changed reports whether this deletion actually affected any owner's perspective.
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 is the private-chat command of messages.updatePinnedMessage
// (including Saved Messages = self peer).
type PinPrivateMessageRequest struct {
OwnerUserID int64
Peer Peer
MessageID int
Pinned bool
// PmOneside pins only on this side (when the official private-chat pin box's "pin for the other side too" is unchecked);
// it does not flip the peer and generates no service message. unpin has no such semantics and always clears both sides.
PmOneside bool
Silent bool
// RecipientBlocked applies the normal private-service delivery policy.
RecipientBlocked bool
Date int
OriginAuthKeyID [8]byte
OriginSessionID int64
}
// PinnedMessagesForUser describes the effect of a pin state change on a given owner's perspective.
type PinnedMessagesForUser struct {
UserID int64
Peer Peer
MessageIDs []int
Pinned bool
Event UpdateEvent
}
// MaxUnpinAllBatch limits the pin count a single unpinAllMessages actually clears;
// the rest is cleared by the client continuing per affectedHistory.Offset>0, keeping a single
// updatePinnedMessages messages vector bounded.
const MaxUnpinAllBatch = 1000
// PinPrivateMessageResult describes the owner-dimension result of a private pin/unpin.
// An empty Updated means the state did not change (an idempotent no-op that burns no pts).
type PinPrivateMessageResult struct {
OwnerUserID int64
Updated []PinnedMessagesForUser
// ServiceMessage is committed atomically with a changed shared private pin.
ServiceMessage SendPrivateTextResult
// A non-zero Offset means unpinAll still has remaining batches to clear.
Offset int
}
// Self returns the pin result for the current requesting account.
func (r PinPrivateMessageResult) Self() PinnedMessagesForUser {
for _, item := range r.Updated {
if item.UserID == r.OwnerUserID {
return item
}
}
return PinnedMessagesForUser{UserID: r.OwnerUserID}
}
// Changed reports whether this operation actually flipped the pin state for any owner's perspective.
func (r PinPrivateMessageResult) Changed() bool {
for _, item := range r.Updated {
if len(item.MessageIDs) > 0 {
return true
}
}
return false
}
// UnpinAllPrivateMessagesRequest is the private-chat command of 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
PaidStars int64
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
PaidStars int64
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
}