Skip to main content

layer_client/
update.rs

1//! High-level update types delivered by [`crate::Client::stream_updates`].
2//!
3//! Every update the Telegram server pushes is classified into one of the
4//! variants of [`Update`].  The raw constructor ID is always available
5//! via [`Update::Raw`] for anything not yet wrapped.
6
7use layer_tl_types as tl;
8use layer_tl_types::{Cursor, Deserializable};
9
10use crate::{Client, InvocationError as Error};
11
12// ─── IncomingMessage ─────────────────────────────────────────────────────────
13
14/// A new or edited message.
15#[derive(Debug, Clone)]
16pub struct IncomingMessage {
17    /// The underlying TL message object.
18    pub raw: tl::enums::Message,
19}
20
21impl IncomingMessage {
22    pub(crate) fn from_raw(raw: tl::enums::Message) -> Self {
23        Self { raw }
24    }
25
26    /// The message text (or caption for media messages).
27    pub fn text(&self) -> Option<&str> {
28        match &self.raw {
29            tl::enums::Message::Message(m) => {
30                if m.message.is_empty() { None } else { Some(&m.message) }
31            }
32            _ => None,
33        }
34    }
35
36    /// Unique message ID within the chat.
37    pub fn id(&self) -> i32 {
38        match &self.raw {
39            tl::enums::Message::Message(m) => m.id,
40            tl::enums::Message::Service(m) => m.id,
41            tl::enums::Message::Empty(m)   => m.id,
42        }
43    }
44
45    /// The peer (chat) this message belongs to.
46    pub fn peer_id(&self) -> Option<&tl::enums::Peer> {
47        match &self.raw {
48            tl::enums::Message::Message(m) => Some(&m.peer_id),
49            tl::enums::Message::Service(m) => Some(&m.peer_id),
50            _ => None,
51        }
52    }
53
54    /// The sender peer, if available (not set for anonymous channel posts).
55    pub fn sender_id(&self) -> Option<&tl::enums::Peer> {
56        match &self.raw {
57            tl::enums::Message::Message(m) => m.from_id.as_ref(),
58            tl::enums::Message::Service(m) => m.from_id.as_ref(),
59            _ => None,
60        }
61    }
62
63    /// `true` if the message was sent by the logged-in account.
64    pub fn outgoing(&self) -> bool {
65        match &self.raw {
66            tl::enums::Message::Message(m) => m.out,
67            tl::enums::Message::Service(m) => m.out,
68            _ => false,
69        }
70    }
71
72    /// Unix timestamp when the message was sent.
73    pub fn date(&self) -> i32 {
74        match &self.raw {
75            tl::enums::Message::Message(m) => m.date,
76            tl::enums::Message::Service(m) => m.date,
77            _ => 0,
78        }
79    }
80
81    /// Unix timestamp of the last edit, if the message has been edited.
82    pub fn edit_date(&self) -> Option<i32> {
83        match &self.raw {
84            tl::enums::Message::Message(m) => m.edit_date,
85            _ => None,
86        }
87    }
88
89    /// `true` if the logged-in user was mentioned in this message.
90    pub fn mentioned(&self) -> bool {
91        match &self.raw {
92            tl::enums::Message::Message(m) => m.mentioned,
93            tl::enums::Message::Service(m) => m.mentioned,
94            _ => false,
95        }
96    }
97
98    /// `true` if the message was sent silently (no notification).
99    pub fn silent(&self) -> bool {
100        match &self.raw {
101            tl::enums::Message::Message(m) => m.silent,
102            tl::enums::Message::Service(m) => m.silent,
103            _ => false,
104        }
105    }
106
107    /// `true` if this is a channel post (no sender).
108    pub fn post(&self) -> bool {
109        match &self.raw {
110            tl::enums::Message::Message(m) => m.post,
111            _ => false,
112        }
113    }
114
115    /// `true` if this message is currently pinned.
116    pub fn pinned(&self) -> bool {
117        match &self.raw {
118            tl::enums::Message::Message(m) => m.pinned,
119            _ => false,
120        }
121    }
122
123    /// Number of times the message has been forwarded (channels only).
124    pub fn forward_count(&self) -> Option<i32> {
125        match &self.raw {
126            tl::enums::Message::Message(m) => m.forwards,
127            _ => None,
128        }
129    }
130
131    /// View count for channel posts.
132    pub fn view_count(&self) -> Option<i32> {
133        match &self.raw {
134            tl::enums::Message::Message(m) => m.views,
135            _ => None,
136        }
137    }
138
139    /// Reply count (number of replies in a thread).
140    pub fn reply_count(&self) -> Option<i32> {
141        match &self.raw {
142            tl::enums::Message::Message(m) => {
143                m.replies.as_ref().map(|r| match r {
144                    tl::enums::MessageReplies::MessageReplies(x) => x.replies,
145                })
146            }
147            _ => None,
148        }
149    }
150
151    /// ID of the message this one is replying to.
152    pub fn reply_to_message_id(&self) -> Option<i32> {
153        match &self.raw {
154            tl::enums::Message::Message(m) => {
155                m.reply_to.as_ref().and_then(|r| match r {
156                    tl::enums::MessageReplyHeader::MessageReplyHeader(h) => h.reply_to_msg_id,
157                    _ => None,
158                })
159            }
160            _ => None,
161        }
162    }
163
164    /// Fetch the message that this one is replying to.
165    ///
166    /// Returns `None` if this message is not a reply or if the peer is unknown.
167    /// Unlike [`reply_to_message_id`] this actually performs an API call to
168    /// retrieve the full message object.
169    ///
170    /// [`reply_to_message_id`]: IncomingMessage::reply_to_message_id
171    pub async fn reply_to_message(
172        &self,
173        client: &Client,
174    ) -> Result<Option<IncomingMessage>, Error> {
175        let reply_id = match self.reply_to_message_id() {
176            Some(id) => id,
177            None     => return Ok(None),
178        };
179        let peer = match self.peer_id() {
180            Some(p) => p.clone(),
181            None    => return Ok(None),
182        };
183        let msgs = client.get_messages_by_id(peer, &[reply_id]).await?;
184        Ok(msgs.into_iter().next())
185    }
186
187    /// The message's send time as a [`chrono::DateTime<chrono::Utc>`].
188    ///
189    /// This is a typed wrapper around the raw `date()` Unix timestamp.
190    pub fn date_utc(&self) -> Option<chrono::DateTime<chrono::Utc>> {
191        use chrono::TimeZone;
192        let ts = self.date();
193        if ts == 0 { return None; }
194        chrono::Utc.timestamp_opt(ts as i64, 0).single()
195    }
196
197    /// The last edit time as a [`chrono::DateTime<chrono::Utc>`], if edited.
198    pub fn edit_date_utc(&self) -> Option<chrono::DateTime<chrono::Utc>> {
199        use chrono::TimeZone;
200        self.edit_date().and_then(|ts| chrono::Utc.timestamp_opt(ts as i64, 0).single())
201    }
202
203    /// The media attached to this message, if any.
204    pub fn media(&self) -> Option<&tl::enums::MessageMedia> {
205        match &self.raw {
206            tl::enums::Message::Message(m) => m.media.as_ref(),
207            _ => None,
208        }
209    }
210
211    /// Formatting entities (bold, italic, code, links, etc).
212    pub fn entities(&self) -> Option<&Vec<tl::enums::MessageEntity>> {
213        match &self.raw {
214            tl::enums::Message::Message(m) => m.entities.as_ref(),
215            _ => None,
216        }
217    }
218
219    /// Group ID for album messages (multiple media in one).
220    pub fn grouped_id(&self) -> Option<i64> {
221        match &self.raw {
222            tl::enums::Message::Message(m) => m.grouped_id,
223            _ => None,
224        }
225    }
226
227    /// Reply markup (inline keyboards, etc).
228    pub fn reply_markup(&self) -> Option<&tl::enums::ReplyMarkup> {
229        match &self.raw {
230            tl::enums::Message::Message(m) => m.reply_markup.as_ref(),
231            _ => None,
232        }
233    }
234
235    /// Forward info header, if this message was forwarded.
236    pub fn forward_header(&self) -> Option<&tl::enums::MessageFwdHeader> {
237        match &self.raw {
238            tl::enums::Message::Message(m) => m.fwd_from.as_ref(),
239            _ => None,
240        }
241    }
242
243    /// `true` if forwarding this message is restricted.
244    pub fn noforwards(&self) -> bool {
245        match &self.raw {
246            tl::enums::Message::Message(m) => m.noforwards,
247            _ => false,
248        }
249    }
250
251    /// Reply to this message with plain text.
252    pub async fn reply(&self, client: &mut Client, text: impl Into<String>) -> Result<(), Error> {
253        let peer = match self.peer_id() {
254            Some(p) => p.clone(),
255            None    => return Err(Error::Deserialize("cannot reply: unknown peer".into())),
256        };
257        let msg_id = self.id();
258        client.send_message_to_peer_ex(peer, &crate::InputMessage::text(text.into())
259            .reply_to(Some(msg_id))).await
260    }
261}
262
263// ─── MessageDeletion ─────────────────────────────────────────────────────────
264
265/// One or more messages were deleted.
266#[derive(Debug, Clone)]
267pub struct MessageDeletion {
268    /// IDs of the deleted messages.
269    pub message_ids: Vec<i32>,
270    /// Channel ID, if the deletion happened in a channel / supergroup.
271    pub channel_id:  Option<i64>,
272}
273
274// ─── CallbackQuery ───────────────────────────────────────────────────────────
275
276/// A user pressed an inline keyboard button on a bot message.
277#[derive(Debug, Clone)]
278pub struct CallbackQuery {
279    pub query_id:        i64,
280    pub user_id:         i64,
281    pub message_id:      Option<i32>,
282    pub chat_instance:   i64,
283    /// Raw `data` bytes from the button.
284    pub data_raw:        Option<Vec<u8>>,
285    /// Game short name (if a game button was pressed).
286    pub game_short_name: Option<String>,
287}
288
289impl CallbackQuery {
290    /// Button data as a UTF-8 string, if valid.
291    pub fn data(&self) -> Option<&str> {
292        self.data_raw.as_ref().and_then(|d| std::str::from_utf8(d).ok())
293    }
294
295    /// Answer the callback query (removes the loading indicator on the client).
296    pub async fn answer(
297        &self,
298        client: &mut Client,
299        text:   Option<&str>,
300    ) -> Result<(), Error> {
301        client.answer_callback_query(self.query_id, text, false).await.map(|_| ())
302    }
303
304    /// Answer with a popup alert.
305    pub async fn answer_alert(
306        &self,
307        client: &mut Client,
308        text:   &str,
309    ) -> Result<(), Error> {
310        client.answer_callback_query(self.query_id, Some(text), true).await.map(|_| ())
311    }
312}
313
314// ─── InlineQuery ─────────────────────────────────────────────────────────────
315
316/// A user is typing an inline query (`@bot something`).
317#[derive(Debug, Clone)]
318pub struct InlineQuery {
319    pub query_id: i64,
320    pub user_id:  i64,
321    pub query:    String,
322    pub offset:   String,
323    /// Peer of the chat the user sent the inline query from, if available.
324    pub peer:     Option<tl::enums::Peer>,
325}
326
327impl InlineQuery {
328    /// The text the user typed after the bot username.
329    pub fn query(&self) -> &str { &self.query }
330}
331
332// ─── InlineSend ──────────────────────────────────────────────────────────────
333
334/// A user chose an inline result and sent it.
335#[derive(Debug, Clone)]
336pub struct InlineSend {
337    pub user_id:  i64,
338    pub query:    String,
339    pub id:       String,
340    /// Message ID of the sent message, if available.
341    pub msg_id:   Option<tl::enums::InputBotInlineMessageId>,
342}
343
344// ─── RawUpdate ───────────────────────────────────────────────────────────────
345
346/// A TL update that has no dedicated high-level variant yet.
347#[derive(Debug, Clone)]
348pub struct RawUpdate {
349    /// Constructor ID of the inner update.
350    pub constructor_id: u32,
351}
352
353// ─── Update ───────────────────────────────────────────────────────────────────
354
355/// A high-level event received from Telegram.
356#[non_exhaustive]
357#[derive(Debug, Clone)]
358pub enum Update {
359    /// A new message (personal chat, group, channel, or bot command).
360    NewMessage(IncomingMessage),
361    /// An existing message was edited.
362    MessageEdited(IncomingMessage),
363    /// One or more messages were deleted.
364    MessageDeleted(MessageDeletion),
365    /// An inline keyboard button was pressed on a bot message.
366    CallbackQuery(CallbackQuery),
367    /// A user typed an inline query for the bot.
368    InlineQuery(InlineQuery),
369    /// A user chose an inline result and sent it (bots only).
370    InlineSend(InlineSend),
371    /// A raw TL update not mapped to any of the above variants.
372    Raw(RawUpdate),
373}
374
375// ─── MTProto update container IDs ────────────────────────────────────────────
376
377const ID_UPDATES_TOO_LONG:      u32 = 0xe317af7e;
378const ID_UPDATE_SHORT_MESSAGE:  u32 = 0x313bc7f8;
379const ID_UPDATE_SHORT_CHAT_MSG: u32 = 0x4d6deea5;
380const ID_UPDATE_SHORT:          u32 = 0x78d4dec1;
381const ID_UPDATES:               u32 = 0x74ae4240;
382const ID_UPDATES_COMBINED:      u32 = 0x725b04c3;
383
384// ─── Parser ──────────────────────────────────────────────────────────────────
385
386/// Parse raw update container bytes into high-level [`Update`] values.
387pub(crate) fn parse_updates(bytes: &[u8]) -> Vec<Update> {
388    if bytes.len() < 4 {
389        return vec![];
390    }
391    let cid = u32::from_le_bytes(bytes[..4].try_into().unwrap());
392
393    match cid {
394        ID_UPDATES_TOO_LONG => {
395            log::warn!("[layer] updatesTooLong — call client.get_difference() to recover missed updates");
396            vec![]
397        }
398
399        ID_UPDATE_SHORT_MESSAGE => {
400            let mut cur = Cursor::from_slice(&bytes[4..]); // skip constructor prefix
401            match tl::types::UpdateShortMessage::deserialize(&mut cur) {
402                Ok(m)  => vec![Update::NewMessage(make_short_dm(m))],
403                Err(e) => { log::debug!("[layer] updateShortMessage parse error (unknown constructor or newer layer): {e}"); vec![] }
404            }
405        }
406
407        ID_UPDATE_SHORT_CHAT_MSG => {
408            let mut cur = Cursor::from_slice(&bytes[4..]); // skip constructor prefix
409            match tl::types::UpdateShortChatMessage::deserialize(&mut cur) {
410                Ok(m)  => vec![Update::NewMessage(make_short_chat(m))],
411                Err(e) => { log::debug!("[layer] updateShortChatMessage parse error (unknown constructor or newer layer): {e}"); vec![] }
412            }
413        }
414
415        ID_UPDATE_SHORT => {
416            let mut cur = Cursor::from_slice(&bytes[4..]); // skip constructor prefix
417            match tl::types::UpdateShort::deserialize(&mut cur) {
418                Ok(m)  => from_single_update(m.update),
419                Err(e) => { log::debug!("[layer] updateShort parse error (unknown constructor or newer layer): {e}"); vec![] }
420            }
421        }
422
423        ID_UPDATES => {
424            let mut cur = Cursor::from_slice(bytes);
425            match tl::enums::Updates::deserialize(&mut cur) {
426                Ok(tl::enums::Updates::Updates(u)) => {
427                    u.updates.into_iter().flat_map(from_single_update).collect()
428                }
429                Err(e) => { log::debug!("[layer] Updates parse error (unknown constructor or newer layer): {e}"); vec![] }
430                _ => vec![],
431            }
432        }
433
434        ID_UPDATES_COMBINED => {
435            let mut cur = Cursor::from_slice(bytes);
436            match tl::enums::Updates::deserialize(&mut cur) {
437                Ok(tl::enums::Updates::Combined(u)) => {
438                    u.updates.into_iter().flat_map(from_single_update).collect()
439                }
440                Err(e) => { log::debug!("[layer] UpdatesCombined parse error (unknown constructor or newer layer): {e}"); vec![] }
441                _ => vec![],
442            }
443        }
444
445        _ => vec![],
446    }
447}
448
449/// Convert a single `tl::enums::Update` into a `Vec<Update>`.
450pub fn from_single_update_pub(upd: tl::enums::Update) -> Vec<Update> {
451    from_single_update(upd)
452}
453
454/// Convert a single `tl::enums::Update` into a `Vec<Update>`.
455fn from_single_update(upd: tl::enums::Update) -> Vec<Update> {
456    use tl::enums::Update::*;
457    match upd {
458        NewMessage(u) => vec![Update::NewMessage(IncomingMessage::from_raw(u.message))],
459        NewChannelMessage(u) => vec![Update::NewMessage(IncomingMessage::from_raw(u.message))],
460        EditMessage(u) => vec![Update::MessageEdited(IncomingMessage::from_raw(u.message))],
461        EditChannelMessage(u) => vec![Update::MessageEdited(IncomingMessage::from_raw(u.message))],
462        DeleteMessages(u) => vec![Update::MessageDeleted(MessageDeletion {
463            message_ids: u.messages,
464            channel_id: None,
465        })],
466        DeleteChannelMessages(u) => vec![Update::MessageDeleted(MessageDeletion {
467            message_ids: u.messages,
468            channel_id: Some(u.channel_id),
469        })],
470        BotCallbackQuery(u) => vec![Update::CallbackQuery(CallbackQuery {
471            query_id:        u.query_id,
472            user_id:         u.user_id,
473            message_id:      Some(u.msg_id),
474            chat_instance:   u.chat_instance,
475            data_raw:        u.data,
476            game_short_name: u.game_short_name,
477        })],
478        InlineBotCallbackQuery(u) => vec![Update::CallbackQuery(CallbackQuery {
479            query_id:        u.query_id,
480            user_id:         u.user_id,
481            message_id:      None,
482            chat_instance:   u.chat_instance,
483            data_raw:        u.data,
484            game_short_name: u.game_short_name,
485        })],
486        BotInlineQuery(u) => vec![Update::InlineQuery(InlineQuery {
487            query_id: u.query_id,
488            user_id:  u.user_id,
489            query:    u.query,
490            offset:   u.offset,
491            peer:     None,
492        })],
493        BotInlineSend(u) => vec![Update::InlineSend(InlineSend {
494            user_id: u.user_id,
495            query:   u.query,
496            id:      u.id,
497            msg_id:  u.msg_id,
498        })],
499        other => {
500            let cid = tl_constructor_id(&other);
501            vec![Update::Raw(RawUpdate { constructor_id: cid })]
502        }
503    }
504}
505
506/// Extract constructor ID from a `tl::enums::Update` variant.
507fn tl_constructor_id(upd: &tl::enums::Update) -> u32 {
508    use tl::enums::Update::*;
509    match upd {
510        AttachMenuBots => 0x17b7a20b,
511        AutoSaveSettings => 0xec05b097,
512        BotBusinessConnect(_) => 0x8ae5c97a,
513        BotCallbackQuery(_) => 0xb9cfc48d,
514        BotChatBoost(_) => 0x904dd49c,
515        BotChatInviteRequester(_) => 0x11dfa986,
516        BotCommands(_) => 0x4d712f2e,
517        BotDeleteBusinessMessage(_) => 0xa02a982e,
518        BotEditBusinessMessage(_) => 0x7df587c,
519        BotInlineQuery(_) => 0x496f379c,
520        BotInlineSend(_) => 0x12f12a07,
521        BotMenuButton(_) => 0x14b85813,
522        BotMessageReaction(_) => 0xac21d3ce,
523        BotMessageReactions(_) => 0x9cb7759,
524        BotNewBusinessMessage(_) => 0x9ddb347c,
525        BotPrecheckoutQuery(_) => 0x8caa9a96,
526        BotPurchasedPaidMedia(_) => 0x283bd312,
527        BotShippingQuery(_) => 0xb5aefd7d,
528        BotStopped(_) => 0xc4870a49,
529        BotWebhookJson(_) => 0x8317c0c3,
530        BotWebhookJsonquery(_) => 0x9b9240a6,
531        BusinessBotCallbackQuery(_) => 0x1ea2fda7,
532        Channel(_) => 0x635b4c09,
533        ChannelAvailableMessages(_) => 0xb23fc698,
534        ChannelMessageForwards(_) => 0xd29a27f4,
535        ChannelMessageViews(_) => 0xf226ac08,
536        ChannelParticipant(_) => 0x985d3abb,
537        ChannelReadMessagesContents(_) => 0x25f324f7,
538        ChannelTooLong(_) => 0x108d941f,
539        ChannelUserTyping(_) => 0x8c88c923,
540        ChannelViewForumAsMessages(_) => 0x7b68920,
541        ChannelWebPage(_) => 0x2f2ba99f,
542        Chat(_) => 0xf89a6a4e,
543        ChatDefaultBannedRights(_) => 0x54c01850,
544        ChatParticipant(_) => 0xd087663a,
545        ChatParticipantAdd(_) => 0x3dda5451,
546        ChatParticipantAdmin(_) => 0xd7ca61a2,
547        ChatParticipantDelete(_) => 0xe32f3d77,
548        ChatParticipants(_) => 0x7761198,
549        ChatUserTyping(_) => 0x83487af0,
550        Config => 0xa229dd06,
551        ContactsReset => 0x7084a7be,
552        DcOptions(_) => 0x8e5e9873,
553        DeleteChannelMessages(_) => 0xc32d5b12,
554        DeleteGroupCallMessages(_) => 0x3e85e92c,
555        DeleteMessages(_) => 0xa20db0e5,
556        DeleteQuickReply(_) => 0x53e6f1ec,
557        DeleteQuickReplyMessages(_) => 0x566fe7cd,
558        DeleteScheduledMessages(_) => 0xf2a71983,
559        DialogFilter(_) => 0x26ffde7d,
560        DialogFilterOrder(_) => 0xa5d72105,
561        DialogFilters => 0x3504914f,
562        DialogPinned(_) => 0x6e6fe51c,
563        DialogUnreadMark(_) => 0xb658f23e,
564        DraftMessage(_) => 0xedfc111e,
565        EditChannelMessage(_) => 0x1b3f4df7,
566        EditMessage(_) => 0xe40370a3,
567        EmojiGameInfo(_) => 0xfb9c547a,
568        EncryptedChatTyping(_) => 0x1710f156,
569        EncryptedMessagesRead(_) => 0x38fe25b7,
570        Encryption(_) => 0xb4a2e88d,
571        FavedStickers => 0xe511996d,
572        FolderPeers(_) => 0x19360dc0,
573        GeoLiveViewed(_) => 0x871fb939,
574        GroupCall(_) => 0x9d2216e0,
575        GroupCallChainBlocks(_) => 0xa477288f,
576        GroupCallConnection(_) => 0xb783982,
577        GroupCallEncryptedMessage(_) => 0xc957a766,
578        GroupCallMessage(_) => 0xd8326f0d,
579        GroupCallParticipants(_) => 0xf2ebdb4e,
580        InlineBotCallbackQuery(_) => 0x691e9052,
581        LangPack(_) => 0x56022f4d,
582        LangPackTooLong(_) => 0x46560264,
583        LoginToken => 0x564fe691,
584        MessageExtendedMedia(_) => 0xd5a41724,
585        MessageId(_) => 0x4e90bfd6,
586        MessagePoll(_) => 0xaca1657b,
587        MessagePollVote(_) => 0x24f40e77,
588        MessageReactions(_) => 0x1e297bfa,
589        MonoForumNoPaidException(_) => 0x9f812b08,
590        MoveStickerSetToTop(_) => 0x86fccf85,
591        NewAuthorization(_) => 0x8951abef,
592        NewChannelMessage(_) => 0x62ba04d9,
593        NewEncryptedMessage(_) => 0x12bcbd9a,
594        NewMessage(_) => 0x1f2b0afd,
595        NewQuickReply(_) => 0xf53da717,
596        NewScheduledMessage(_) => 0x39a51dfb,
597        NewStickerSet(_) => 0x688a30aa,
598        NewStoryReaction(_) => 0x1824e40b,
599        NotifySettings(_) => 0xbec268ef,
600        PaidReactionPrivacy(_) => 0x8b725fce,
601        PeerBlocked(_) => 0xebe07752,
602        PeerHistoryTtl(_) => 0xbb9bb9a5,
603        PeerLocated(_) => 0xb4afcfb0,
604        PeerSettings(_) => 0x6a7e7366,
605        PeerWallpaper(_) => 0xae3f101d,
606        PendingJoinRequests(_) => 0x7063c3db,
607        PhoneCall(_) => 0xab0f6b1e,
608        PhoneCallSignalingData(_) => 0x2661bf09,
609        PinnedChannelMessages(_) => 0x5bb98608,
610        PinnedDialogs(_) => 0xfa0f3ca2,
611        PinnedForumTopic(_) => 0x683b2c52,
612        PinnedForumTopics(_) => 0xdef143d0,
613        PinnedMessages(_) => 0xed85eab5,
614        PinnedSavedDialogs(_) => 0x686c85a6,
615        Privacy(_) => 0xee3b272a,
616        PtsChanged => 0x3354678f,
617        QuickReplies(_) => 0xf9470ab2,
618        QuickReplyMessage(_) => 0x3e050d0f,
619        ReadChannelDiscussionInbox(_) => 0xd6b19546,
620        ReadChannelDiscussionOutbox(_) => 0x695c9e7c,
621        ReadChannelInbox(_) => 0x922e6e10,
622        ReadChannelOutbox(_) => 0xb75f99a9,
623        ReadFeaturedEmojiStickers => 0xfb4c496c,
624        ReadFeaturedStickers => 0x571d2742,
625        ReadHistoryInbox(_) => 0x9e84bc99,
626        ReadHistoryOutbox(_) => 0x2f2f21bf,
627        ReadMessagesContents(_) => 0xf8227181,
628        ReadMonoForumInbox(_) => 0x77b0e372,
629        ReadMonoForumOutbox(_) => 0xa4a79376,
630        ReadStories(_) => 0xf74e932b,
631        RecentEmojiStatuses => 0x30f443db,
632        RecentReactions => 0x6f7863f4,
633        RecentStickers => 0x9a422c20,
634        SavedDialogPinned(_) => 0xaeaf9e74,
635        SavedGifs => 0x9375341e,
636        SavedReactionTags => 0x39c67432,
637        SavedRingtones => 0x74d8be99,
638        SentPhoneCode(_) => 0x504aa18f,
639        SentStoryReaction(_) => 0x7d627683,
640        ServiceNotification(_) => 0xebe46819,
641        SmsJob(_) => 0xf16269d4,
642        StarGiftAuctionState(_) => 0x48e246c2,
643        StarGiftAuctionUserState(_) => 0xdc58f31e,
644        StarGiftCraftFail => 0xac072444,
645        StarsBalance(_) => 0x4e80a379,
646        StarsRevenueStatus(_) => 0xa584b019,
647        StickerSets(_) => 0x31c24808,
648        StickerSetsOrder(_) => 0xbb2d201,
649        StoriesStealthMode(_) => 0x2c084dc1,
650        Story(_) => 0x75b3b798,
651        StoryId(_) => 0x1bf335b9,
652        Theme(_) => 0x8216fba3,
653        TranscribedAudio(_) => 0x84cd5a,
654        User(_) => 0x20529438,
655        UserEmojiStatus(_) => 0x28373599,
656        UserName(_) => 0xa7848924,
657        UserPhone(_) => 0x5492a13,
658        UserStatus(_) => 0xe5bdf8de,
659        UserTyping(_) => 0x2a17bf5c,
660        WebPage(_) => 0x7f891213,
661        WebViewResultSent(_) => 0x1592b79d,
662        ChatParticipantRank(_) => 0xbd8367b9,
663    }
664}
665
666// ─── Short message helpers ────────────────────────────────────────────────────
667
668fn make_short_dm(m: tl::types::UpdateShortMessage) -> IncomingMessage {
669    let msg = tl::types::Message {
670        out:               m.out,
671        mentioned:         m.mentioned,
672        media_unread:      m.media_unread,
673        silent:            m.silent,
674        post:              false,
675        from_scheduled:    false,
676        legacy:            false,
677        edit_hide:         false,
678        pinned:            false,
679        noforwards:        false,
680        invert_media:      false,
681        offline:           false,
682        video_processing_pending: false,
683        id:                m.id,
684        from_id:           Some(tl::enums::Peer::User(tl::types::PeerUser { user_id: m.user_id })),
685        peer_id:           tl::enums::Peer::User(tl::types::PeerUser { user_id: m.user_id }),
686        saved_peer_id:     None,
687        fwd_from:          m.fwd_from,
688        via_bot_id:        m.via_bot_id,
689        via_business_bot_id: None,
690        reply_to:          m.reply_to,
691        date:              m.date,
692        message:           m.message,
693        media:             None,
694        reply_markup:      None,
695        entities:          m.entities,
696        views:             None,
697        forwards:          None,
698        replies:           None,
699        edit_date:         None,
700        post_author:       None,
701        grouped_id:        None,
702        reactions:         None,
703        restriction_reason: None,
704        ttl_period:        None,
705        quick_reply_shortcut_id: None,
706        effect:            None,
707        factcheck:         None,
708        report_delivery_until_date: None,
709        paid_message_stars: None,
710        suggested_post:    None,
711        from_rank:           None,
712        from_boosts_applied: None,
713        paid_suggested_post_stars: false,
714        paid_suggested_post_ton: false,
715        schedule_repeat_period: None,
716        summary_from_language: None,
717    };
718    IncomingMessage { raw: tl::enums::Message::Message(msg) }
719}
720
721fn make_short_chat(m: tl::types::UpdateShortChatMessage) -> IncomingMessage {
722    let msg = tl::types::Message {
723        out:               m.out,
724        mentioned:         m.mentioned,
725        media_unread:      m.media_unread,
726        silent:            m.silent,
727        post:              false,
728        from_scheduled:    false,
729        legacy:            false,
730        edit_hide:         false,
731        pinned:            false,
732        noforwards:        false,
733        invert_media:      false,
734        offline:           false,
735        video_processing_pending: false,
736        id:                m.id,
737        from_id:           Some(tl::enums::Peer::User(tl::types::PeerUser { user_id: m.from_id })),
738        peer_id:           tl::enums::Peer::Chat(tl::types::PeerChat { chat_id: m.chat_id }),
739        saved_peer_id:     None,
740        fwd_from:          m.fwd_from,
741        via_bot_id:        m.via_bot_id,
742        via_business_bot_id: None,
743        reply_to:          m.reply_to,
744        date:              m.date,
745        message:           m.message,
746        media:             None,
747        reply_markup:      None,
748        entities:          m.entities,
749        views:             None,
750        forwards:          None,
751        replies:           None,
752        edit_date:         None,
753        post_author:       None,
754        grouped_id:        None,
755        reactions:         None,
756        restriction_reason: None,
757        ttl_period:        None,
758        quick_reply_shortcut_id: None,
759        effect:            None,
760        factcheck:         None,
761        report_delivery_until_date: None,
762        paid_message_stars: None,
763        suggested_post:    None,
764        from_rank:           None,
765        from_boosts_applied: None,
766        paid_suggested_post_stars: false,
767        paid_suggested_post_ton: false,
768        schedule_repeat_period: None,
769        summary_from_language: None,
770    };
771    IncomingMessage { raw: tl::enums::Message::Message(msg) }
772}