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(Clone)]
16pub struct IncomingMessage {
17    /// The underlying TL message object.
18    pub raw: tl::enums::Message,
19    /// An embedded client reference, populated for messages received via
20    /// `stream_updates()` and returned from send/search/history APIs.
21    /// When present, the clientless action methods (`reply`, `respond`,
22    /// `edit`, `delete`, `pin`, `unpin`, `react`, …) can be called without
23    /// passing a `&Client` argument.
24    pub client: Option<Client>,
25}
26
27impl std::fmt::Debug for IncomingMessage {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        f.debug_struct("IncomingMessage")
30            .field("raw", &self.raw)
31            .field("has_client", &self.client.is_some())
32            .finish()
33    }
34}
35
36impl IncomingMessage {
37    pub fn from_raw(raw: tl::enums::Message) -> Self {
38        Self { raw, client: None }
39    }
40
41    /// Attach a `Client` so the clientless action methods work.
42    ///
43    /// Returns `self` for chaining:
44    /// ```rust,no_run
45    /// # use layer_client::update::IncomingMessage;
46    /// # fn ex(raw: layer_tl_types::enums::Message, client: layer_client::Client) {
47    /// let msg = IncomingMessage::from_raw(raw).with_client(client);
48    /// # }
49    /// ```
50    pub fn with_client(mut self, client: Client) -> Self {
51        self.client = Some(client);
52        self
53    }
54
55    /// Convenience: return an error when no client is embedded.
56    fn require_client(&self, method: &str) -> Result<&Client, Error> {
57        self.client.as_ref().ok_or_else(|| {
58            Error::Deserialize(format!(
59                "{method}: this IncomingMessage has no embedded client: \
60                 use the `_with` variant and pass a &Client explicitly"
61            ))
62        })
63    }
64
65    /// The message text (or caption for media messages).
66    pub fn text(&self) -> Option<&str> {
67        match &self.raw {
68            tl::enums::Message::Message(m) => {
69                if m.message.is_empty() {
70                    None
71                } else {
72                    Some(&m.message)
73                }
74            }
75            _ => None,
76        }
77    }
78
79    /// Unique message ID within the chat.
80    pub fn id(&self) -> i32 {
81        match &self.raw {
82            tl::enums::Message::Message(m) => m.id,
83            tl::enums::Message::Service(m) => m.id,
84            tl::enums::Message::Empty(m) => m.id,
85        }
86    }
87
88    /// The peer (chat) this message belongs to.
89    pub fn peer_id(&self) -> Option<&tl::enums::Peer> {
90        match &self.raw {
91            tl::enums::Message::Message(m) => Some(&m.peer_id),
92            tl::enums::Message::Service(m) => Some(&m.peer_id),
93            _ => None,
94        }
95    }
96
97    /// The sender peer, if available (not set for anonymous channel posts).
98    pub fn sender_id(&self) -> Option<&tl::enums::Peer> {
99        match &self.raw {
100            tl::enums::Message::Message(m) => m.from_id.as_ref(),
101            tl::enums::Message::Service(m) => m.from_id.as_ref(),
102            _ => None,
103        }
104    }
105
106    /// `true` if the message was sent by the logged-in account.
107    pub fn outgoing(&self) -> bool {
108        match &self.raw {
109            tl::enums::Message::Message(m) => m.out,
110            tl::enums::Message::Service(m) => m.out,
111            _ => false,
112        }
113    }
114
115    /// Unix timestamp when the message was sent.
116    pub fn date(&self) -> i32 {
117        match &self.raw {
118            tl::enums::Message::Message(m) => m.date,
119            tl::enums::Message::Service(m) => m.date,
120            _ => 0,
121        }
122    }
123
124    /// Unix timestamp of the last edit, if the message has been edited.
125    pub fn edit_date(&self) -> Option<i32> {
126        match &self.raw {
127            tl::enums::Message::Message(m) => m.edit_date,
128            _ => None,
129        }
130    }
131
132    /// `true` if the logged-in user was mentioned in this message.
133    pub fn mentioned(&self) -> bool {
134        match &self.raw {
135            tl::enums::Message::Message(m) => m.mentioned,
136            tl::enums::Message::Service(m) => m.mentioned,
137            _ => false,
138        }
139    }
140
141    /// `true` if the message was sent silently (no notification).
142    pub fn silent(&self) -> bool {
143        match &self.raw {
144            tl::enums::Message::Message(m) => m.silent,
145            tl::enums::Message::Service(m) => m.silent,
146            _ => false,
147        }
148    }
149
150    /// `true` if this is a channel post (no sender).
151    pub fn post(&self) -> bool {
152        match &self.raw {
153            tl::enums::Message::Message(m) => m.post,
154            _ => false,
155        }
156    }
157
158    /// `true` if this message is currently pinned.
159    pub fn pinned(&self) -> bool {
160        match &self.raw {
161            tl::enums::Message::Message(m) => m.pinned,
162            _ => false,
163        }
164    }
165
166    /// Number of times the message has been forwarded (channels only).
167    pub fn forward_count(&self) -> Option<i32> {
168        match &self.raw {
169            tl::enums::Message::Message(m) => m.forwards,
170            _ => None,
171        }
172    }
173
174    /// View count for channel posts.
175    pub fn view_count(&self) -> Option<i32> {
176        match &self.raw {
177            tl::enums::Message::Message(m) => m.views,
178            _ => None,
179        }
180    }
181
182    /// Reply count (number of replies in a thread).
183    pub fn reply_count(&self) -> Option<i32> {
184        match &self.raw {
185            tl::enums::Message::Message(m) => m.replies.as_ref().map(|r| match r {
186                tl::enums::MessageReplies::MessageReplies(x) => x.replies,
187            }),
188            _ => None,
189        }
190    }
191
192    /// ID of the message this one is replying to.
193    pub fn reply_to_message_id(&self) -> Option<i32> {
194        match &self.raw {
195            tl::enums::Message::Message(m) => m.reply_to.as_ref().and_then(|r| match r {
196                tl::enums::MessageReplyHeader::MessageReplyHeader(h) => h.reply_to_msg_id,
197                _ => None,
198            }),
199            _ => None,
200        }
201    }
202
203    /// Fetch the message that this one is replying to.
204    ///
205    /// Returns `None` if this message is not a reply or if the peer is unknown.
206    /// Unlike [`reply_to_message_id`] this actually performs an API call to
207    /// retrieve the full message object.
208    ///
209    /// [`reply_to_message_id`]: IncomingMessage::reply_to_message_id
210    pub async fn reply_to_message(
211        &self,
212        client: &Client,
213    ) -> Result<Option<IncomingMessage>, Error> {
214        let reply_id = match self.reply_to_message_id() {
215            Some(id) => id,
216            None => return Ok(None),
217        };
218        let peer = match self.peer_id() {
219            Some(p) => p.clone(),
220            None => return Ok(None),
221        };
222        let msgs = client.get_messages_by_id(peer, &[reply_id]).await?;
223        Ok(msgs.into_iter().next())
224    }
225
226    /// The message's send time as a [`chrono::DateTime<chrono::Utc>`].
227    ///
228    /// This is a typed wrapper around the raw `date()` Unix timestamp.
229    pub fn date_utc(&self) -> Option<chrono::DateTime<chrono::Utc>> {
230        use chrono::TimeZone;
231        let ts = self.date();
232        if ts == 0 {
233            return None;
234        }
235        chrono::Utc.timestamp_opt(ts as i64, 0).single()
236    }
237
238    /// The last edit time as a [`chrono::DateTime<chrono::Utc>`], if edited.
239    pub fn edit_date_utc(&self) -> Option<chrono::DateTime<chrono::Utc>> {
240        use chrono::TimeZone;
241        self.edit_date()
242            .and_then(|ts| chrono::Utc.timestamp_opt(ts as i64, 0).single())
243    }
244
245    /// The media attached to this message, if any.
246    pub fn media(&self) -> Option<&tl::enums::MessageMedia> {
247        match &self.raw {
248            tl::enums::Message::Message(m) => m.media.as_ref(),
249            _ => None,
250        }
251    }
252
253    /// Formatting entities (bold, italic, code, links, etc).
254    pub fn entities(&self) -> Option<&Vec<tl::enums::MessageEntity>> {
255        match &self.raw {
256            tl::enums::Message::Message(m) => m.entities.as_ref(),
257            _ => None,
258        }
259    }
260
261    /// Group ID for album messages (multiple media in one).
262    pub fn grouped_id(&self) -> Option<i64> {
263        match &self.raw {
264            tl::enums::Message::Message(m) => m.grouped_id,
265            _ => None,
266        }
267    }
268
269    /// `true` if this message was sent from a scheduled one.
270    pub fn from_scheduled(&self) -> bool {
271        match &self.raw {
272            tl::enums::Message::Message(m) => m.from_scheduled,
273            _ => false,
274        }
275    }
276
277    /// `true` if the edit date is hidden from recipients.
278    pub fn edit_hide(&self) -> bool {
279        match &self.raw {
280            tl::enums::Message::Message(m) => m.edit_hide,
281            _ => false,
282        }
283    }
284
285    /// `true` if the media in this message has not been read yet.
286    pub fn media_unread(&self) -> bool {
287        match &self.raw {
288            tl::enums::Message::Message(m) => m.media_unread,
289            tl::enums::Message::Service(m) => m.media_unread,
290            _ => false,
291        }
292    }
293
294    /// ID of the bot that sent this message via inline mode, if any.
295    pub fn via_bot_id(&self) -> Option<i64> {
296        match &self.raw {
297            tl::enums::Message::Message(m) => m.via_bot_id,
298            _ => None,
299        }
300    }
301
302    /// Signature of the post author in a channel, if set.
303    pub fn post_author(&self) -> Option<&str> {
304        match &self.raw {
305            tl::enums::Message::Message(m) => m.post_author.as_deref(),
306            _ => None,
307        }
308    }
309
310    /// Number of reactions on this message, if any.
311    pub fn reaction_count(&self) -> i32 {
312        match &self.raw {
313            tl::enums::Message::Message(m) => m
314                .reactions
315                .as_ref()
316                .map(|r| match r {
317                    tl::enums::MessageReactions::MessageReactions(x) => x
318                        .results
319                        .iter()
320                        .map(|res| match res {
321                            tl::enums::ReactionCount::ReactionCount(c) => c.count,
322                        })
323                        .sum(),
324                })
325                .unwrap_or(0),
326            _ => 0,
327        }
328    }
329
330    /// Restriction reasons (why this message is unavailable in some regions).
331    pub fn restriction_reason(&self) -> Option<&Vec<tl::enums::RestrictionReason>> {
332        match &self.raw {
333            tl::enums::Message::Message(m) => m.restriction_reason.as_ref(),
334            _ => None,
335        }
336    }
337
338    /// Reply markup (inline keyboards, etc).
339    pub fn reply_markup(&self) -> Option<&tl::enums::ReplyMarkup> {
340        match &self.raw {
341            tl::enums::Message::Message(m) => m.reply_markup.as_ref(),
342            _ => None,
343        }
344    }
345
346    /// Forward info header, if this message was forwarded.
347    pub fn forward_header(&self) -> Option<&tl::enums::MessageFwdHeader> {
348        match &self.raw {
349            tl::enums::Message::Message(m) => m.fwd_from.as_ref(),
350            _ => None,
351        }
352    }
353
354    /// `true` if forwarding this message is restricted.
355    pub fn noforwards(&self) -> bool {
356        match &self.raw {
357            tl::enums::Message::Message(m) => m.noforwards,
358            _ => false,
359        }
360    }
361
362    /// Reconstruct Markdown from the message text and its formatting entities.
363    ///
364    /// Returns plain text if there are no entities.
365    pub fn markdown_text(&self) -> Option<String> {
366        let text = self.text()?;
367        let entities = self.entities().map(|e| e.as_slice()).unwrap_or(&[]);
368        Some(crate::parsers::generate_markdown(text, entities))
369    }
370
371    /// Reconstruct HTML from the message text and its formatting entities.
372    ///
373    /// Returns plain text if there are no entities.
374    pub fn html_text(&self) -> Option<String> {
375        let text = self.text()?;
376        let entities = self.entities().map(|e| e.as_slice()).unwrap_or(&[]);
377        Some(crate::parsers::generate_html(text, entities))
378    }
379
380    /// Service message action (e.g. "user joined", "call started").\
381    /// Returns `None` for regular text/media messages.
382    pub fn action(&self) -> Option<&tl::enums::MessageAction> {
383        match &self.raw {
384            tl::enums::Message::Service(m) => Some(&m.action),
385            _ => None,
386        }
387    }
388
389    /// Extract a `Photo` from the message media, if present.
390    ///
391    /// Shorthand for `Photo::from_media(msg.media()?)`.
392    pub fn photo(&self) -> Option<crate::media::Photo> {
393        crate::media::Photo::from_media(self.media()?)
394    }
395
396    /// Extract a `Document` from the message media, if present.
397    ///
398    /// Shorthand for `Document::from_media(msg.media()?)`.
399    pub fn document(&self) -> Option<crate::media::Document> {
400        crate::media::Document::from_media(self.media()?)
401    }
402
403    // Convenience action methods
404    //
405    // Two tiers for every action:
406    //  1. Clientless : `msg.reply("hi").await?`
407    // Uses the embedded `self.client`. Returns an error if the message was
408    // constructed without `.with_client(…)`.
409    //  2. Explicit   : `msg.reply_with(&client, "hi").await?`
410    // Always works, even when no client is embedded.
411
412    // reply
413
414    /// Reply to this message (clientless: requires an embedded client).
415    ///
416    /// Returns the sent message so you can chain further operations on it.
417    pub async fn reply(&self, text: impl Into<String>) -> Result<IncomingMessage, Error> {
418        let client = self.require_client("reply")?.clone();
419        self.reply_with(&client, text).await
420    }
421
422    /// Reply to this message with a plain string.
423    ///
424    /// Returns the sent message so you can chain further operations on it.
425    pub async fn reply_with(
426        &self,
427        client: &Client,
428        text: impl Into<String>,
429    ) -> Result<IncomingMessage, Error> {
430        let peer = match self.peer_id() {
431            Some(p) => p.clone(),
432            None => return Err(Error::Deserialize("cannot reply: unknown peer".into())),
433        };
434        let msg_id = self.id();
435        client
436            .send_message_to_peer_ex(
437                peer,
438                &crate::InputMessage::text(text.into()).reply_to(Some(msg_id)),
439            )
440            .await
441    }
442
443    /// Reply with a full [`InputMessage`](crate::InputMessage) (clientless).
444    pub async fn reply_ex(&self, msg: crate::InputMessage) -> Result<IncomingMessage, Error> {
445        let client = self.require_client("reply_ex")?.clone();
446        self.reply_ex_with(&client, msg).await
447    }
448
449    /// Reply with a full [`InputMessage`](crate::InputMessage).
450    pub async fn reply_ex_with(
451        &self,
452        client: &Client,
453        msg: crate::InputMessage,
454    ) -> Result<IncomingMessage, Error> {
455        let peer = self
456            .peer_id()
457            .cloned()
458            .ok_or_else(|| Error::Deserialize("cannot reply_ex: unknown peer".into()))?;
459        client
460            .send_message_to_peer_ex(peer, &msg.reply_to(Some(self.id())))
461            .await
462    }
463
464    // respond
465
466    /// Send to the same chat without quoting (clientless).
467    pub async fn respond(&self, text: impl Into<String>) -> Result<IncomingMessage, Error> {
468        let client = self.require_client("respond")?.clone();
469        self.respond_with(&client, text).await
470    }
471
472    /// Send to the same chat without quoting.
473    pub async fn respond_with(
474        &self,
475        client: &Client,
476        text: impl Into<String>,
477    ) -> Result<IncomingMessage, Error> {
478        let peer = self
479            .peer_id()
480            .cloned()
481            .ok_or_else(|| Error::Deserialize("cannot respond: unknown peer".into()))?;
482        client
483            .send_message_to_peer_ex(peer, &crate::InputMessage::text(text.into()))
484            .await
485    }
486
487    /// Full [`InputMessage`] to the same chat without quoting (clientless).
488    pub async fn respond_ex(&self, msg: crate::InputMessage) -> Result<IncomingMessage, Error> {
489        let client = self.require_client("respond_ex")?.clone();
490        self.respond_ex_with(&client, msg).await
491    }
492
493    /// Full [`InputMessage`] to the same chat without quoting.
494    pub async fn respond_ex_with(
495        &self,
496        client: &Client,
497        msg: crate::InputMessage,
498    ) -> Result<IncomingMessage, Error> {
499        let peer = self
500            .peer_id()
501            .cloned()
502            .ok_or_else(|| Error::Deserialize("cannot respond_ex: unknown peer".into()))?;
503        client.send_message_to_peer_ex(peer, &msg).await
504    }
505
506    // edit
507
508    /// Edit this message (clientless).
509    pub async fn edit(&self, new_text: impl Into<String>) -> Result<(), Error> {
510        let client = self.require_client("edit")?.clone();
511        self.edit_with(&client, new_text).await
512    }
513
514    /// Edit this message.
515    pub async fn edit_with(
516        &self,
517        client: &Client,
518        new_text: impl Into<String>,
519    ) -> Result<(), Error> {
520        let peer = self
521            .peer_id()
522            .cloned()
523            .ok_or_else(|| Error::Deserialize("cannot edit: unknown peer".into()))?;
524        client
525            .edit_message(peer, self.id(), new_text.into().as_str())
526            .await
527    }
528
529    // delete
530
531    /// Delete this message (clientless).
532    pub async fn delete(&self) -> Result<(), Error> {
533        let client = self.require_client("delete")?.clone();
534        self.delete_with(&client).await
535    }
536
537    /// Delete this message.
538    pub async fn delete_with(&self, client: &Client) -> Result<(), Error> {
539        client.delete_messages(vec![self.id()], true).await
540    }
541
542    // mark_as_read
543
544    /// Mark this message (and all before it) as read (clientless).
545    pub async fn mark_as_read(&self) -> Result<(), Error> {
546        let client = self.require_client("mark_as_read")?.clone();
547        self.mark_as_read_with(&client).await
548    }
549
550    /// Mark this message (and all before it) as read.
551    pub async fn mark_as_read_with(&self, client: &Client) -> Result<(), Error> {
552        let peer = self
553            .peer_id()
554            .cloned()
555            .ok_or_else(|| Error::Deserialize("cannot mark_as_read: unknown peer".into()))?;
556        client.mark_as_read(peer).await
557    }
558
559    // pin
560
561    /// Pin this message silently (clientless).
562    pub async fn pin(&self) -> Result<(), Error> {
563        let client = self.require_client("pin")?.clone();
564        self.pin_with(&client).await
565    }
566
567    /// Pin this message silently.
568    pub async fn pin_with(&self, client: &Client) -> Result<(), Error> {
569        let peer = self
570            .peer_id()
571            .cloned()
572            .ok_or_else(|| Error::Deserialize("cannot pin: unknown peer".into()))?;
573        client
574            .pin_message(peer, self.id(), true, false, false)
575            .await
576    }
577
578    // unpin
579
580    /// Unpin this message (clientless).
581    pub async fn unpin(&self) -> Result<(), Error> {
582        let client = self.require_client("unpin")?.clone();
583        self.unpin_with(&client).await
584    }
585
586    /// Unpin this message.
587    pub async fn unpin_with(&self, client: &Client) -> Result<(), Error> {
588        let peer = self
589            .peer_id()
590            .cloned()
591            .ok_or_else(|| Error::Deserialize("cannot unpin: unknown peer".into()))?;
592        client.unpin_message(peer, self.id()).await
593    }
594
595    // forward_to
596
597    /// Forward this message to another chat (clientless).
598    ///
599    /// Returns the forwarded message in the destination chat.
600    pub async fn forward_to(
601        &self,
602        destination: impl Into<crate::PeerRef>,
603    ) -> Result<IncomingMessage, Error> {
604        let client = self.require_client("forward_to")?.clone();
605        self.forward_to_with(&client, destination).await
606    }
607
608    /// Forward this message to another chat.
609    ///
610    /// Returns the forwarded message in the destination chat.
611    pub async fn forward_to_with(
612        &self,
613        client: &Client,
614        destination: impl Into<crate::PeerRef>,
615    ) -> Result<IncomingMessage, Error> {
616        let src = self
617            .peer_id()
618            .cloned()
619            .ok_or_else(|| Error::Deserialize("cannot forward: unknown source peer".into()))?;
620        client
621            .forward_messages_returning(destination, &[self.id()], src)
622            .await
623            .and_then(|v| {
624                v.into_iter()
625                    .next()
626                    .ok_or_else(|| Error::Deserialize("forward returned no message".into()))
627            })
628    }
629
630    // refetch
631
632    /// Re-fetch this message from Telegram (clientless).
633    ///
634    /// Useful to get updated view/forward counts, reactions, edit state, etc.
635    /// Updates `self` in place; returns an error if the message was deleted.
636    pub async fn refetch(&mut self) -> Result<(), Error> {
637        let client = self.require_client("refetch")?.clone();
638        self.refetch_with(&client).await
639    }
640
641    /// Re-fetch this message from Telegram.
642    pub async fn refetch_with(&mut self, client: &Client) -> Result<(), Error> {
643        let peer = self
644            .peer_id()
645            .cloned()
646            .ok_or_else(|| Error::Deserialize("cannot refetch: unknown peer".into()))?;
647        let mut msgs = client.get_messages_by_id(peer, &[self.id()]).await?;
648        match msgs.pop() {
649            Some(m) => {
650                self.raw = m.raw;
651                Ok(())
652            }
653            None => Err(Error::Deserialize(
654                "refetch: message not found (deleted?)".into(),
655            )),
656        }
657    }
658
659    // download_media
660
661    /// Download attached media to `path` (clientless).
662    pub async fn download_media(&self, path: impl AsRef<std::path::Path>) -> Result<bool, Error> {
663        let client = self.require_client("download_media")?.clone();
664        self.download_media_with(&client, path).await
665    }
666
667    /// Download attached media to `path`. Returns `true` if media was found.
668    pub async fn download_media_with(
669        &self,
670        client: &Client,
671        path: impl AsRef<std::path::Path>,
672    ) -> Result<bool, Error> {
673        if let Some(loc) = crate::media::download_location_from_media(self.media()) {
674            client.download_media_to_file(loc, path).await?;
675            Ok(true)
676        } else {
677            Ok(false)
678        }
679    }
680
681    // react
682
683    /// Send a reaction (clientless).
684    ///
685    /// # Example
686    /// ```rust,no_run
687    /// # async fn f(msg: layer_client::update::IncomingMessage)
688    /// #   -> Result<(), layer_client::InvocationError> {
689    /// use layer_client::reactions::InputReactions;
690    /// msg.react(InputReactions::emoticon("👍")).await?;
691    /// # Ok(()) }
692    /// ```
693    pub async fn react(
694        &self,
695        reactions: impl Into<crate::reactions::InputReactions>,
696    ) -> Result<(), Error> {
697        let client = self.require_client("react")?.clone();
698        self.react_with(&client, reactions).await
699    }
700
701    /// Send a reaction.
702    pub async fn react_with(
703        &self,
704        client: &Client,
705        reactions: impl Into<crate::reactions::InputReactions>,
706    ) -> Result<(), Error> {
707        let peer = self
708            .peer_id()
709            .cloned()
710            .ok_or_else(|| Error::Deserialize("cannot react: unknown peer".into()))?;
711        client.send_reaction(peer, self.id(), reactions).await
712    }
713
714    // get_reply
715
716    /// Fetch the message this is a reply to (clientless).
717    pub async fn get_reply(&self) -> Result<Option<IncomingMessage>, Error> {
718        let client = self.require_client("get_reply")?.clone();
719        self.get_reply_with(&client).await
720    }
721
722    /// Fetch the message this is a reply to.
723    pub async fn get_reply_with(&self, client: &Client) -> Result<Option<IncomingMessage>, Error> {
724        client.get_reply_to_message(self).await
725    }
726
727    // sender helpers
728
729    /// The sender's bare user-ID, if this is a user message.
730    ///
731    /// Returns `None` for anonymous channel posts.
732    pub fn sender_user_id(&self) -> Option<i64> {
733        match self.sender_id()? {
734            tl::enums::Peer::User(u) => Some(u.user_id),
735            _ => None,
736        }
737    }
738
739    /// The chat/channel-ID the sender belongs to (non-user senders).
740    pub fn sender_chat_id(&self) -> Option<i64> {
741        match self.sender_id()? {
742            tl::enums::Peer::Chat(c) => Some(c.chat_id),
743            tl::enums::Peer::Channel(c) => Some(c.channel_id),
744            _ => None,
745        }
746    }
747
748    /// Fetch the sender as a typed [`User`](crate::types::User) (clientless, async).
749    ///
750    /// Returns `None` if the sender is not a user, or if the user is not in
751    /// the local peer cache.  Performs a network call if needed.
752    pub async fn sender_user(&self) -> Result<Option<crate::types::User>, Error> {
753        let uid = match self.sender_user_id() {
754            Some(id) => id,
755            None => return Ok(None),
756        };
757        let client = self.require_client("sender_user")?.clone();
758        let users = client.get_users_by_id(&[uid]).await?;
759        Ok(users.into_iter().next().flatten())
760    }
761}
762
763// MessageDeletion
764
765/// One or more messages were deleted.
766#[derive(Debug, Clone)]
767pub struct MessageDeletion {
768    /// IDs of the deleted messages.
769    pub message_ids: Vec<i32>,
770    /// Channel ID, if the deletion happened in a channel / supergroup.
771    pub channel_id: Option<i64>,
772}
773
774impl MessageDeletion {
775    /// Consume self and return the deleted message IDs without cloning.
776    pub fn into_messages(self) -> Vec<i32> {
777        self.message_ids
778    }
779}
780
781// CallbackQuery
782
783/// A user pressed an inline keyboard button on a bot message.
784#[derive(Debug, Clone)]
785pub struct CallbackQuery {
786    pub query_id: i64,
787    pub user_id: i64,
788    pub message_id: Option<i32>,
789    pub chat_instance: i64,
790    /// Raw `data` bytes from the button.
791    pub data_raw: Option<Vec<u8>>,
792    /// Game short name (if a game button was pressed).
793    pub game_short_name: Option<String>,
794    /// The peer (chat/channel/user) where the button was pressed.
795    /// `None` for inline-message callback queries.
796    pub chat_peer: Option<tl::enums::Peer>,
797    /// For inline-message callbacks: the message ID token.
798    pub inline_msg_id: Option<tl::enums::InputBotInlineMessageId>,
799}
800
801impl CallbackQuery {
802    /// Button data as a UTF-8 string, if valid.
803    pub fn data(&self) -> Option<&str> {
804        self.data_raw
805            .as_ref()
806            .and_then(|d| std::str::from_utf8(d).ok())
807    }
808
809    /// Begin building an answer for this callback query.
810    ///
811    /// Finish with `.send(&client).await`:
812    ///
813    /// ```rust,no_run
814    /// query.answer().text("Done!").send(&client).await?;
815    /// query.answer().alert("No permission!").send(&client).await?;
816    /// query.answer().url("https://example.com/game").send(&client).await?;
817    /// query.answer()
818    /// .text("Cached")
819    /// .cache_time(std::time::Duration::from_secs(60))
820    /// .send(&client).await?;
821    /// ```
822    pub fn answer(&self) -> Answer<'_> {
823        Answer {
824            query_id: self.query_id,
825            message: None,
826            alert: false,
827            url: None,
828            cache_time: 0,
829            _marker: std::marker::PhantomData,
830        }
831    }
832
833    /// Answer the callback query (flat helper: prefer `answer()` builder).
834    pub async fn answer_flat(&self, client: &Client, text: Option<&str>) -> Result<(), Error> {
835        client
836            .answer_callback_query(self.query_id, text, false)
837            .await
838            .map(|_| ())
839    }
840
841    /// Answer with a popup alert (flat helper: prefer `answer().alert(…)`).
842    pub async fn answer_alert(&self, client: &Client, text: &str) -> Result<(), Error> {
843        client
844            .answer_callback_query(self.query_id, Some(text), true)
845            .await
846            .map(|_| ())
847    }
848}
849
850/// Fluent builder returned by [`CallbackQuery::answer`]. Finalize with `.send(&client).await`.
851pub struct Answer<'a> {
852    query_id: i64,
853    message: Option<String>,
854    alert: bool,
855    url: Option<String>,
856    cache_time: i32,
857    _marker: std::marker::PhantomData<&'a ()>,
858}
859
860impl<'a> Answer<'a> {
861    /// Show `text` as a toast notification (fades automatically).
862    pub fn text<S: Into<String>>(mut self, text: S) -> Self {
863        self.message = Some(text.into());
864        self.alert = false;
865        self
866    }
867
868    /// Show `text` as a modal alert the user must dismiss.
869    pub fn alert<S: Into<String>>(mut self, text: S) -> Self {
870        self.message = Some(text.into());
871        self.alert = true;
872        self
873    }
874
875    /// Open `url` on the client (e.g. to launch a game).
876    pub fn url<S: Into<String>>(mut self, url: S) -> Self {
877        self.url = Some(url.into());
878        self
879    }
880
881    /// Cache this answer for `duration` so repeated presses don't reach the bot.
882    pub fn cache_time(mut self, duration: std::time::Duration) -> Self {
883        self.cache_time = duration.as_secs().min(i32::MAX as u64) as i32;
884        self
885    }
886
887    /// Send the answer to Telegram.
888    pub async fn send(self, client: &Client) -> Result<(), Error> {
889        let req = tl::functions::messages::SetBotCallbackAnswer {
890            alert: self.alert,
891            query_id: self.query_id,
892            message: self.message,
893            url: self.url,
894            cache_time: self.cache_time,
895        };
896        client.rpc_call_raw_pub(&req).await.map(|_| ())
897    }
898}
899
900// InlineQuery
901
902/// A user is typing an inline query (`@bot something`).
903#[derive(Debug, Clone)]
904pub struct InlineQuery {
905    pub query_id: i64,
906    pub user_id: i64,
907    pub query: String,
908    pub offset: String,
909    /// Peer of the chat the user sent the inline query from, if available.
910    pub peer: Option<tl::enums::Peer>,
911}
912
913impl InlineQuery {
914    /// The text the user typed after the bot username.
915    pub fn query(&self) -> &str {
916        &self.query
917    }
918}
919
920// InlineSend
921
922/// A user chose an inline result and sent it.
923#[derive(Debug, Clone)]
924pub struct InlineSend {
925    pub user_id: i64,
926    pub query: String,
927    pub id: String,
928    /// Message ID of the sent message, if available.
929    pub msg_id: Option<tl::enums::InputBotInlineMessageId>,
930}
931
932impl InlineSend {
933    /// Edit the inline message that was sent as a result of this inline query.
934    ///
935    /// Requires that [`msg_id`] is present (i.e. the result had `peer_type` set).
936    /// Returns `Err` with a descriptive message if `msg_id` is `None`.
937    ///
938    /// [`msg_id`]: InlineSend::msg_id
939    ///
940    /// # Example
941    /// ```rust,no_run
942    /// # async fn f(client: layer_client::Client, send: layer_client::update::InlineSend)
943    /// # -> Result<(), Box<dyn std::error::Error>> {
944    /// send.edit_message(&client, "updated text", None).await?;
945    /// # Ok(()) }
946    /// ```
947    pub async fn edit_message(
948        &self,
949        client: &Client,
950        new_text: &str,
951        reply_markup: Option<tl::enums::ReplyMarkup>,
952    ) -> Result<bool, Error> {
953        let msg_id =
954            match self.msg_id.clone() {
955                Some(id) => id,
956                None => return Err(Error::Deserialize(
957                    "InlineSend::edit_message: msg_id is None (bot_inline_send had no peer_type)"
958                        .into(),
959                )),
960            };
961        let req = tl::functions::messages::EditInlineBotMessage {
962            no_webpage: false,
963            invert_media: false,
964            id: msg_id,
965            message: Some(new_text.to_string()),
966            media: None,
967            reply_markup,
968            entities: None,
969        };
970        let body = client.rpc_call_raw(&req).await?;
971        // Returns Bool
972        Ok(!body.is_empty())
973    }
974}
975
976// RawUpdate
977
978/// A TL update that has no dedicated high-level variant yet.
979#[derive(Debug, Clone)]
980pub struct RawUpdate {
981    /// Constructor ID of the inner update.
982    pub constructor_id: u32,
983}
984
985/// A user's online / offline status changed.
986///
987/// Delivered as [`Update::UserStatus`].
988///
989/// # Example
990/// ```rust,no_run
991/// # use layer_client::{Update, update::UserStatusUpdate};
992/// # async fn example(mut stream: layer_client::UpdateStream) {
993/// while let Some(upd) = stream.next().await {
994/// if let Update::UserStatus(s) = upd {
995///     println!("user {} status: {:?}", s.user_id, s.status);
996/// }
997/// }
998/// # }
999/// ```
1000#[derive(Debug, Clone)]
1001pub struct UserStatusUpdate {
1002    /// The bare user ID whose status changed.
1003    pub user_id: i64,
1004    /// New online/offline/recently/etc. status.
1005    pub status: tl::enums::UserStatus,
1006}
1007
1008/// A user is performing a chat action (typing, uploading, recording…).
1009///
1010/// Delivered as [`Update::UserTyping`].  Covers DMs, groups, and channels.
1011///
1012/// # Example
1013/// ```rust,no_run
1014/// # use layer_client::{Update, update::ChatActionUpdate};
1015/// # async fn example(mut stream: layer_client::UpdateStream) {
1016/// while let Some(upd) = stream.next().await {
1017/// if let Update::UserTyping(a) = upd {
1018///     println!("user {} is typing in {:?}", a.user_id, a.peer);
1019/// }
1020/// }
1021/// # }
1022/// ```
1023#[derive(Debug, Clone)]
1024pub struct ChatActionUpdate {
1025    /// The peer (chat / channel) the action is happening in.
1026    /// For DM typing updates (`updateUserTyping`) this is the user's own peer.
1027    pub peer: tl::enums::Peer,
1028    /// The bare user ID performing the action.
1029    pub user_id: i64,
1030    /// What the user is currently doing (typing, uploading video, etc.).
1031    pub action: tl::enums::SendMessageAction,
1032}
1033
1034/// A high-level event received from Telegram.
1035#[non_exhaustive]
1036#[derive(Debug, Clone)]
1037pub enum Update {
1038    /// A new message (personal chat, group, channel, or bot command).
1039    NewMessage(IncomingMessage),
1040    /// An existing message was edited.
1041    MessageEdited(IncomingMessage),
1042    /// One or more messages were deleted.
1043    MessageDeleted(MessageDeletion),
1044    /// An inline keyboard button was pressed on a bot message.
1045    CallbackQuery(CallbackQuery),
1046    /// A user typed an inline query for the bot.
1047    InlineQuery(InlineQuery),
1048    /// A user chose an inline result and sent it (bots only).
1049    InlineSend(InlineSend),
1050    /// A user's online status changed.
1051    UserStatus(UserStatusUpdate),
1052    /// A user is typing / uploading / recording in a chat.
1053    UserTyping(ChatActionUpdate),
1054    /// A raw TL update not mapped to any of the above variants.
1055    Raw(RawUpdate),
1056}
1057
1058// MTProto update container IDs
1059
1060#[allow(dead_code)]
1061const ID_UPDATES_TOO_LONG: u32 = 0xe317af7e;
1062#[allow(dead_code)]
1063const ID_UPDATE_SHORT_MESSAGE: u32 = 0x313bc7f8;
1064#[allow(dead_code)]
1065const ID_UPDATE_SHORT_CHAT_MSG: u32 = 0x4d6deea5;
1066#[allow(dead_code)]
1067const ID_UPDATE_SHORT: u32 = 0x78d4dec1;
1068#[allow(dead_code)]
1069const ID_UPDATES: u32 = 0x74ae4240;
1070#[allow(dead_code)]
1071const ID_UPDATES_COMBINED: u32 = 0x725b04c3;
1072#[allow(dead_code)]
1073const ID_UPDATE_SHORT_SENT_MSG: u32 = 0x9015e101;
1074
1075// Parser
1076
1077/// Parse raw update container bytes into high-level [`Update`] values.
1078#[allow(dead_code)]
1079pub(crate) fn parse_updates(bytes: &[u8]) -> Vec<Update> {
1080    if bytes.len() < 4 {
1081        return vec![];
1082    }
1083    let cid = u32::from_le_bytes(bytes[..4].try_into().unwrap());
1084
1085    match cid {
1086        ID_UPDATES_TOO_LONG => {
1087            tracing::warn!(
1088                "[layer] updatesTooLong: call client.get_difference() to recover missed updates"
1089            );
1090            vec![]
1091        }
1092
1093        ID_UPDATE_SHORT_MESSAGE => {
1094            let mut cur = Cursor::from_slice(&bytes[4..]); // skip constructor prefix
1095            match tl::types::UpdateShortMessage::deserialize(&mut cur) {
1096                Ok(m) => vec![Update::NewMessage(make_short_dm(m))],
1097                Err(e) => {
1098                    tracing::debug!(
1099                        "[layer] updateShortMessage parse error (unknown constructor or newer layer): {e}"
1100                    );
1101                    vec![]
1102                }
1103            }
1104        }
1105
1106        ID_UPDATE_SHORT_CHAT_MSG => {
1107            let mut cur = Cursor::from_slice(&bytes[4..]); // skip constructor prefix
1108            match tl::types::UpdateShortChatMessage::deserialize(&mut cur) {
1109                Ok(m) => vec![Update::NewMessage(make_short_chat(m))],
1110                Err(e) => {
1111                    tracing::debug!(
1112                        "[layer] updateShortChatMessage parse error (unknown constructor or newer layer): {e}"
1113                    );
1114                    vec![]
1115                }
1116            }
1117        }
1118
1119        ID_UPDATE_SHORT => {
1120            let mut cur = Cursor::from_slice(&bytes[4..]); // skip constructor prefix
1121            match tl::types::UpdateShort::deserialize(&mut cur) {
1122                Ok(m) => from_single_update(m.update),
1123                Err(e) => {
1124                    tracing::debug!(
1125                        "[layer] updateShort parse error (unknown constructor or newer layer): {e}"
1126                    );
1127                    vec![]
1128                }
1129            }
1130        }
1131
1132        ID_UPDATES => {
1133            let mut cur = Cursor::from_slice(bytes);
1134            match tl::enums::Updates::deserialize(&mut cur) {
1135                Ok(tl::enums::Updates::Updates(u)) => {
1136                    u.updates.into_iter().flat_map(from_single_update).collect()
1137                }
1138                Err(e) => {
1139                    tracing::debug!(
1140                        "[layer] Updates parse error (unknown constructor or newer layer): {e}"
1141                    );
1142                    vec![]
1143                }
1144                _ => vec![],
1145            }
1146        }
1147
1148        ID_UPDATES_COMBINED => {
1149            let mut cur = Cursor::from_slice(bytes);
1150            match tl::enums::Updates::deserialize(&mut cur) {
1151                Ok(tl::enums::Updates::Combined(u)) => {
1152                    u.updates.into_iter().flat_map(from_single_update).collect()
1153                }
1154                Err(e) => {
1155                    tracing::debug!(
1156                        "[layer] UpdatesCombined parse error (unknown constructor or newer layer): {e}"
1157                    );
1158                    vec![]
1159                }
1160                _ => vec![],
1161            }
1162        }
1163
1164        // updateShortSentMessage: pts is now handled by dispatch_updates/route_frame
1165        // directly (via EnvelopeResult::Pts or the push branch). parse_updates is only
1166        // called for the old code path; we absorb here as a safe fallback.
1167        ID_UPDATE_SHORT_SENT_MSG => vec![],
1168
1169        _ => vec![],
1170    }
1171}
1172
1173/// Convert a single `tl::enums::Update` into a `Vec<Update>`.
1174pub fn from_single_update_pub(upd: tl::enums::Update) -> Vec<Update> {
1175    from_single_update(upd)
1176}
1177
1178/// Convert a single `tl::enums::Update` into a `Vec<Update>`.
1179fn from_single_update(upd: tl::enums::Update) -> Vec<Update> {
1180    use tl::enums::Update::*;
1181    match upd {
1182        NewMessage(u) => vec![Update::NewMessage(IncomingMessage::from_raw(u.message))],
1183        NewChannelMessage(u) => vec![Update::NewMessage(IncomingMessage::from_raw(u.message))],
1184        EditMessage(u) => vec![Update::MessageEdited(IncomingMessage::from_raw(u.message))],
1185        EditChannelMessage(u) => vec![Update::MessageEdited(IncomingMessage::from_raw(u.message))],
1186        DeleteMessages(u) => vec![Update::MessageDeleted(MessageDeletion {
1187            message_ids: u.messages,
1188            channel_id: None,
1189        })],
1190        DeleteChannelMessages(u) => vec![Update::MessageDeleted(MessageDeletion {
1191            message_ids: u.messages,
1192            channel_id: Some(u.channel_id),
1193        })],
1194        BotCallbackQuery(u) => vec![Update::CallbackQuery(CallbackQuery {
1195            query_id: u.query_id,
1196            user_id: u.user_id,
1197            message_id: Some(u.msg_id),
1198            chat_instance: u.chat_instance,
1199            data_raw: u.data,
1200            game_short_name: u.game_short_name,
1201            chat_peer: Some(u.peer),
1202            inline_msg_id: None,
1203        })],
1204        InlineBotCallbackQuery(u) => vec![Update::CallbackQuery(CallbackQuery {
1205            query_id: u.query_id,
1206            user_id: u.user_id,
1207            message_id: None,
1208            chat_instance: u.chat_instance,
1209            data_raw: u.data,
1210            game_short_name: u.game_short_name,
1211            chat_peer: None,
1212            inline_msg_id: Some(u.msg_id),
1213        })],
1214        BotInlineQuery(u) => vec![Update::InlineQuery(InlineQuery {
1215            query_id: u.query_id,
1216            user_id: u.user_id,
1217            query: u.query,
1218            offset: u.offset,
1219            peer: None,
1220        })],
1221        BotInlineSend(u) => vec![Update::InlineSend(InlineSend {
1222            user_id: u.user_id,
1223            query: u.query,
1224            id: u.id,
1225            msg_id: u.msg_id,
1226        })],
1227        // typed UserStatus variant
1228        UserStatus(u) => vec![Update::UserStatus(UserStatusUpdate {
1229            user_id: u.user_id,
1230            status: u.status,
1231        })],
1232        // typed ChatAction variant: DM typing
1233        UserTyping(u) => vec![Update::UserTyping(ChatActionUpdate {
1234            peer: tl::enums::Peer::User(tl::types::PeerUser { user_id: u.user_id }),
1235            user_id: u.user_id,
1236            action: u.action,
1237        })],
1238        // group typing
1239        ChatUserTyping(u) => vec![Update::UserTyping(ChatActionUpdate {
1240            peer: tl::enums::Peer::Chat(tl::types::PeerChat { chat_id: u.chat_id }),
1241            user_id: match u.from_id {
1242                tl::enums::Peer::User(ref p) => p.user_id,
1243                tl::enums::Peer::Chat(ref p) => p.chat_id,
1244                tl::enums::Peer::Channel(ref p) => p.channel_id,
1245            },
1246            action: u.action,
1247        })],
1248        // channel / supergroup typing
1249        ChannelUserTyping(u) => vec![Update::UserTyping(ChatActionUpdate {
1250            peer: tl::enums::Peer::Channel(tl::types::PeerChannel {
1251                channel_id: u.channel_id,
1252            }),
1253            user_id: match u.from_id {
1254                tl::enums::Peer::User(ref p) => p.user_id,
1255                tl::enums::Peer::Chat(ref p) => p.chat_id,
1256                tl::enums::Peer::Channel(ref p) => p.channel_id,
1257            },
1258            action: u.action,
1259        })],
1260        other => {
1261            let cid = tl_constructor_id(&other);
1262            vec![Update::Raw(RawUpdate {
1263                constructor_id: cid,
1264            })]
1265        }
1266    }
1267}
1268
1269/// Extract constructor ID from a `tl::enums::Update` variant.
1270fn tl_constructor_id(upd: &tl::enums::Update) -> u32 {
1271    use tl::enums::Update::*;
1272    match upd {
1273        AttachMenuBots => 0x17b7a20b,
1274        AutoSaveSettings => 0xec05b097,
1275        BotBusinessConnect(_) => 0x8ae5c97a,
1276        BotCallbackQuery(_) => 0xb9cfc48d,
1277        BotChatBoost(_) => 0x904dd49c,
1278        BotChatInviteRequester(_) => 0x11dfa986,
1279        BotCommands(_) => 0x4d712f2e,
1280        BotDeleteBusinessMessage(_) => 0xa02a982e,
1281        BotEditBusinessMessage(_) => 0x7df587c,
1282        BotInlineQuery(_) => 0x496f379c,
1283        BotInlineSend(_) => 0x12f12a07,
1284        BotMenuButton(_) => 0x14b85813,
1285        BotMessageReaction(_) => 0xac21d3ce,
1286        BotMessageReactions(_) => 0x9cb7759,
1287        BotNewBusinessMessage(_) => 0x9ddb347c,
1288        BotPrecheckoutQuery(_) => 0x8caa9a96,
1289        BotPurchasedPaidMedia(_) => 0x283bd312,
1290        BotShippingQuery(_) => 0xb5aefd7d,
1291        BotStopped(_) => 0xc4870a49,
1292        BotWebhookJson(_) => 0x8317c0c3,
1293        BotWebhookJsonquery(_) => 0x9b9240a6,
1294        BusinessBotCallbackQuery(_) => 0x1ea2fda7,
1295        Channel(_) => 0x635b4c09,
1296        ChannelAvailableMessages(_) => 0xb23fc698,
1297        ChannelMessageForwards(_) => 0xd29a27f4,
1298        ChannelMessageViews(_) => 0xf226ac08,
1299        ChannelParticipant(_) => 0x985d3abb,
1300        ChannelReadMessagesContents(_) => 0x25f324f7,
1301        ChannelTooLong(_) => 0x108d941f,
1302        ChannelUserTyping(_) => 0x8c88c923,
1303        ChannelViewForumAsMessages(_) => 0x7b68920,
1304        ChannelWebPage(_) => 0x2f2ba99f,
1305        Chat(_) => 0xf89a6a4e,
1306        ChatDefaultBannedRights(_) => 0x54c01850,
1307        ChatParticipant(_) => 0xd087663a,
1308        ChatParticipantAdd(_) => 0x3dda5451,
1309        ChatParticipantAdmin(_) => 0xd7ca61a2,
1310        ChatParticipantDelete(_) => 0xe32f3d77,
1311        ChatParticipants(_) => 0x7761198,
1312        ChatUserTyping(_) => 0x83487af0,
1313        Config => 0xa229dd06,
1314        ContactsReset => 0x7084a7be,
1315        DcOptions(_) => 0x8e5e9873,
1316        DeleteChannelMessages(_) => 0xc32d5b12,
1317        DeleteGroupCallMessages(_) => 0x3e85e92c,
1318        DeleteMessages(_) => 0xa20db0e5,
1319        DeleteQuickReply(_) => 0x53e6f1ec,
1320        DeleteQuickReplyMessages(_) => 0x566fe7cd,
1321        DeleteScheduledMessages(_) => 0xf2a71983,
1322        DialogFilter(_) => 0x26ffde7d,
1323        DialogFilterOrder(_) => 0xa5d72105,
1324        DialogFilters => 0x3504914f,
1325        DialogPinned(_) => 0x6e6fe51c,
1326        DialogUnreadMark(_) => 0xb658f23e,
1327        DraftMessage(_) => 0xedfc111e,
1328        EditChannelMessage(_) => 0x1b3f4df7,
1329        EditMessage(_) => 0xe40370a3,
1330        EmojiGameInfo(_) => 0xfb9c547a,
1331        EncryptedChatTyping(_) => 0x1710f156,
1332        EncryptedMessagesRead(_) => 0x38fe25b7,
1333        Encryption(_) => 0xb4a2e88d,
1334        FavedStickers => 0xe511996d,
1335        FolderPeers(_) => 0x19360dc0,
1336        GeoLiveViewed(_) => 0x871fb939,
1337        GroupCall(_) => 0x9d2216e0,
1338        GroupCallChainBlocks(_) => 0xa477288f,
1339        GroupCallConnection(_) => 0xb783982,
1340        GroupCallEncryptedMessage(_) => 0xc957a766,
1341        GroupCallMessage(_) => 0xd8326f0d,
1342        GroupCallParticipants(_) => 0xf2ebdb4e,
1343        InlineBotCallbackQuery(_) => 0x691e9052,
1344        LangPack(_) => 0x56022f4d,
1345        LangPackTooLong(_) => 0x46560264,
1346        LoginToken => 0x564fe691,
1347        MessageExtendedMedia(_) => 0xd5a41724,
1348        MessageId(_) => 0x4e90bfd6,
1349        MessagePoll(_) => 0xaca1657b,
1350        MessagePollVote(_) => 0x24f40e77,
1351        MessageReactions(_) => 0x1e297bfa,
1352        MonoForumNoPaidException(_) => 0x9f812b08,
1353        MoveStickerSetToTop(_) => 0x86fccf85,
1354        NewAuthorization(_) => 0x8951abef,
1355        NewChannelMessage(_) => 0x62ba04d9,
1356        NewEncryptedMessage(_) => 0x12bcbd9a,
1357        NewMessage(_) => 0x1f2b0afd,
1358        NewQuickReply(_) => 0xf53da717,
1359        NewScheduledMessage(_) => 0x39a51dfb,
1360        NewStickerSet(_) => 0x688a30aa,
1361        NewStoryReaction(_) => 0x1824e40b,
1362        NotifySettings(_) => 0xbec268ef,
1363        PaidReactionPrivacy(_) => 0x8b725fce,
1364        PeerBlocked(_) => 0xebe07752,
1365        PeerHistoryTtl(_) => 0xbb9bb9a5,
1366        PeerLocated(_) => 0xb4afcfb0,
1367        PeerSettings(_) => 0x6a7e7366,
1368        PeerWallpaper(_) => 0xae3f101d,
1369        PendingJoinRequests(_) => 0x7063c3db,
1370        PhoneCall(_) => 0xab0f6b1e,
1371        PhoneCallSignalingData(_) => 0x2661bf09,
1372        PinnedChannelMessages(_) => 0x5bb98608,
1373        PinnedDialogs(_) => 0xfa0f3ca2,
1374        PinnedForumTopic(_) => 0x683b2c52,
1375        PinnedForumTopics(_) => 0xdef143d0,
1376        PinnedMessages(_) => 0xed85eab5,
1377        PinnedSavedDialogs(_) => 0x686c85a6,
1378        Privacy(_) => 0xee3b272a,
1379        PtsChanged => 0x3354678f,
1380        QuickReplies(_) => 0xf9470ab2,
1381        QuickReplyMessage(_) => 0x3e050d0f,
1382        ReadChannelDiscussionInbox(_) => 0xd6b19546,
1383        ReadChannelDiscussionOutbox(_) => 0x695c9e7c,
1384        ReadChannelInbox(_) => 0x922e6e10,
1385        ReadChannelOutbox(_) => 0xb75f99a9,
1386        ReadFeaturedEmojiStickers => 0xfb4c496c,
1387        ReadFeaturedStickers => 0x571d2742,
1388        ReadHistoryInbox(_) => 0x9e84bc99,
1389        ReadHistoryOutbox(_) => 0x2f2f21bf,
1390        ReadMessagesContents(_) => 0xf8227181,
1391        ReadMonoForumInbox(_) => 0x77b0e372,
1392        ReadMonoForumOutbox(_) => 0xa4a79376,
1393        ReadStories(_) => 0xf74e932b,
1394        RecentEmojiStatuses => 0x30f443db,
1395        RecentReactions => 0x6f7863f4,
1396        RecentStickers => 0x9a422c20,
1397        SavedDialogPinned(_) => 0xaeaf9e74,
1398        SavedGifs => 0x9375341e,
1399        SavedReactionTags => 0x39c67432,
1400        SavedRingtones => 0x74d8be99,
1401        SentPhoneCode(_) => 0x504aa18f,
1402        SentStoryReaction(_) => 0x7d627683,
1403        ServiceNotification(_) => 0xebe46819,
1404        SmsJob(_) => 0xf16269d4,
1405        StarGiftAuctionState(_) => 0x48e246c2,
1406        StarGiftAuctionUserState(_) => 0xdc58f31e,
1407        StarGiftCraftFail => 0xac072444,
1408        StarsBalance(_) => 0x4e80a379,
1409        StarsRevenueStatus(_) => 0xa584b019,
1410        StickerSets(_) => 0x31c24808,
1411        StickerSetsOrder(_) => 0xbb2d201,
1412        StoriesStealthMode(_) => 0x2c084dc1,
1413        Story(_) => 0x75b3b798,
1414        StoryId(_) => 0x1bf335b9,
1415        Theme(_) => 0x8216fba3,
1416        TranscribedAudio(_) => 0x84cd5a,
1417        User(_) => 0x20529438,
1418        UserEmojiStatus(_) => 0x28373599,
1419        UserName(_) => 0xa7848924,
1420        UserPhone(_) => 0x5492a13,
1421        UserStatus(_) => 0xe5bdf8de,
1422        UserTyping(_) => 0x2a17bf5c,
1423        WebPage(_) => 0x7f891213,
1424        WebViewResultSent(_) => 0x1592b79d,
1425        ChatParticipantRank(_) => 0xbd8367b9,
1426        ManagedBot(_) => 0x4880ed9a,
1427    }
1428}
1429
1430// Short message helpers
1431
1432pub(crate) fn make_short_dm(m: tl::types::UpdateShortMessage) -> IncomingMessage {
1433    let msg = tl::types::Message {
1434        out: m.out,
1435        mentioned: m.mentioned,
1436        media_unread: m.media_unread,
1437        silent: m.silent,
1438        post: false,
1439        from_scheduled: false,
1440        legacy: false,
1441        edit_hide: false,
1442        pinned: false,
1443        noforwards: false,
1444        invert_media: false,
1445        offline: false,
1446        video_processing_pending: false,
1447        id: m.id,
1448        from_id: Some(tl::enums::Peer::User(tl::types::PeerUser {
1449            user_id: m.user_id,
1450        })),
1451        peer_id: tl::enums::Peer::User(tl::types::PeerUser { user_id: m.user_id }),
1452        saved_peer_id: None,
1453        fwd_from: m.fwd_from,
1454        via_bot_id: m.via_bot_id,
1455        via_business_bot_id: None,
1456        reply_to: m.reply_to,
1457        date: m.date,
1458        message: m.message,
1459        media: None,
1460        reply_markup: None,
1461        entities: m.entities,
1462        views: None,
1463        forwards: None,
1464        replies: None,
1465        edit_date: None,
1466        post_author: None,
1467        grouped_id: None,
1468        reactions: None,
1469        restriction_reason: None,
1470        ttl_period: None,
1471        quick_reply_shortcut_id: None,
1472        effect: None,
1473        factcheck: None,
1474        report_delivery_until_date: None,
1475        paid_message_stars: None,
1476        suggested_post: None,
1477        from_rank: None,
1478        from_boosts_applied: None,
1479        paid_suggested_post_stars: false,
1480        paid_suggested_post_ton: false,
1481        schedule_repeat_period: None,
1482        summary_from_language: None,
1483    };
1484    IncomingMessage {
1485        raw: tl::enums::Message::Message(msg),
1486        client: None,
1487    }
1488}
1489
1490pub(crate) fn make_short_chat(m: tl::types::UpdateShortChatMessage) -> IncomingMessage {
1491    let msg = tl::types::Message {
1492        out: m.out,
1493        mentioned: m.mentioned,
1494        media_unread: m.media_unread,
1495        silent: m.silent,
1496        post: false,
1497        from_scheduled: false,
1498        legacy: false,
1499        edit_hide: false,
1500        pinned: false,
1501        noforwards: false,
1502        invert_media: false,
1503        offline: false,
1504        video_processing_pending: false,
1505        id: m.id,
1506        from_id: Some(tl::enums::Peer::User(tl::types::PeerUser {
1507            user_id: m.from_id,
1508        })),
1509        peer_id: tl::enums::Peer::Chat(tl::types::PeerChat { chat_id: m.chat_id }),
1510        saved_peer_id: None,
1511        fwd_from: m.fwd_from,
1512        via_bot_id: m.via_bot_id,
1513        via_business_bot_id: None,
1514        reply_to: m.reply_to,
1515        date: m.date,
1516        message: m.message,
1517        media: None,
1518        reply_markup: None,
1519        entities: m.entities,
1520        views: None,
1521        forwards: None,
1522        replies: None,
1523        edit_date: None,
1524        post_author: None,
1525        grouped_id: None,
1526        reactions: None,
1527        restriction_reason: None,
1528        ttl_period: None,
1529        quick_reply_shortcut_id: None,
1530        effect: None,
1531        factcheck: None,
1532        report_delivery_until_date: None,
1533        paid_message_stars: None,
1534        suggested_post: None,
1535        from_rank: None,
1536        from_boosts_applied: None,
1537        paid_suggested_post_stars: false,
1538        paid_suggested_post_ton: false,
1539        schedule_repeat_period: None,
1540        summary_from_language: None,
1541    };
1542    IncomingMessage {
1543        raw: tl::enums::Message::Message(msg),
1544        client: None,
1545    }
1546}