Skip to main content

layer_client/
update.rs

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