tg_flows/types/
message.rs

1#![allow(clippy::large_enum_variant)]
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use url::Url;
6
7use crate::types::{
8    Animation, Audio, BareChatId, Chat, ChatId, Contact, Dice, Document, ForumTopicClosed,
9    ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden,
10    GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location,
11    MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, PassportData,
12    PhotoSize, Poll, ProximityAlertTriggered, Sticker, SuccessfulPayment, True, User, Venue, Video,
13    VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote,
14    Voice, WebAppData, WriteAccessAllowed,
15};
16
17/// This object represents a message.
18///
19/// [The official docs](https://core.telegram.org/bots/api#message).
20#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct Message {
22    /// Unique message identifier inside this chat.
23    #[serde(flatten)]
24    pub id: MessageId,
25
26    /// Unique identifier of a message thread to which the message belongs; for
27    /// supergroups only.
28    // FIXME: MessageThreadId or such
29    #[serde(rename = "message_thread_id")]
30    pub thread_id: Option<i32>,
31
32    /// Date the message was sent in Unix time.
33    #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
34    pub date: DateTime<Utc>,
35
36    /// Conversation the message belongs to.
37    pub chat: Chat,
38
39    /// Bot through which the message was sent.
40    pub via_bot: Option<User>,
41
42    #[serde(flatten)]
43    pub kind: MessageKind,
44}
45
46// FIXME: this could be a use-case for serde mixed-tags, some variants need to
47//        untagged (`MessageCommon` as an example), while other need to be
48//        tagged (e.g.: Forum*)
49#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
50#[serde(untagged)]
51pub enum MessageKind {
52    Common(MessageCommon),
53    NewChatMembers(MessageNewChatMembers),
54    LeftChatMember(MessageLeftChatMember),
55    NewChatTitle(MessageNewChatTitle),
56    NewChatPhoto(MessageNewChatPhoto),
57    DeleteChatPhoto(MessageDeleteChatPhoto),
58    GroupChatCreated(MessageGroupChatCreated),
59    SupergroupChatCreated(MessageSupergroupChatCreated),
60    ChannelChatCreated(MessageChannelChatCreated),
61    MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged),
62    Pinned(MessagePinned),
63    Invoice(MessageInvoice),
64    SuccessfulPayment(MessageSuccessfulPayment),
65    ConnectedWebsite(MessageConnectedWebsite),
66    WriteAccessAllowed(MessageWriteAccessAllowed),
67    PassportData(MessagePassportData),
68    Dice(MessageDice),
69    ProximityAlertTriggered(MessageProximityAlertTriggered),
70    ForumTopicCreated(MessageForumTopicCreated),
71    ForumTopicEdited(MessageForumTopicEdited),
72    ForumTopicClosed(MessageForumTopicClosed),
73    ForumTopicReopened(MessageForumTopicReopened),
74    GeneralForumTopicHidden(MessageGeneralForumTopicHidden),
75    GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden),
76    VideoChatScheduled(MessageVideoChatScheduled),
77    VideoChatStarted(MessageVideoChatStarted),
78    VideoChatEnded(MessageVideoChatEnded),
79    VideoChatParticipantsInvited(MessageVideoChatParticipantsInvited),
80    WebAppData(MessageWebAppData),
81}
82
83#[serde_with_macros::skip_serializing_none]
84#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
85pub struct MessageCommon {
86    /// Sender, empty for messages sent to channels.
87    pub from: Option<User>,
88
89    /// Sender of the message, sent on behalf of a chat. The channel itself for
90    /// channel messages. The supergroup itself for messages from anonymous
91    /// group administrators. The linked channel for messages automatically
92    /// forwarded to the discussion group
93    pub sender_chat: Option<Chat>,
94
95    /// Signature of the post author for messages in channels, or the custom
96    /// title of an anonymous group administrator.
97    pub author_signature: Option<String>,
98
99    /// For forwarded messages, information about the forward
100    #[serde(flatten)]
101    pub forward: Option<Forward>,
102
103    /// For replies, the original message. Note that the Message object in this
104    /// field will not contain further `reply_to_message` fields even if it
105    /// itself is a reply.
106    pub reply_to_message: Option<Box<Message>>,
107
108    /// Date the message was last edited in Unix time.
109    #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")]
110    pub edit_date: Option<DateTime<Utc>>,
111
112    #[serde(flatten)]
113    pub media_kind: MediaKind,
114
115    /// Inline keyboard attached to the message. `login_url` buttons are
116    /// represented as ordinary `url` buttons.
117    pub reply_markup: Option<InlineKeyboardMarkup>,
118
119    /// `true`, if the message is sent to a forum topic.
120    // FIXME: `is_topic_message` is included even in service messages, like ForumTopicCreated.
121    //        more this to `Message`
122    #[serde(default)]
123    pub is_topic_message: bool,
124
125    /// `true`, if the message is a channel post that was automatically
126    /// forwarded to the connected discussion group.
127    #[serde(default)]
128    pub is_automatic_forward: bool,
129
130    /// `true`, if the message can't be forwarded.
131    #[serde(default)]
132    pub has_protected_content: bool,
133}
134
135#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
136pub struct MessageNewChatMembers {
137    /// New members that were added to the group or supergroup and
138    /// information about them (the bot itself may be one of these
139    /// members).
140    pub new_chat_members: Vec<User>,
141}
142
143#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
144pub struct MessageLeftChatMember {
145    /// A member was removed from the group, information about them (this
146    /// member may be the bot itself).
147    pub left_chat_member: User,
148}
149
150#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
151pub struct MessageNewChatTitle {
152    /// A chat title was changed to this value.
153    pub new_chat_title: String,
154}
155
156#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
157pub struct MessageNewChatPhoto {
158    /// A chat photo was change to this value.
159    pub new_chat_photo: Vec<PhotoSize>,
160}
161
162#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
163pub struct MessageDeleteChatPhoto {
164    /// Service message: the chat photo was deleted.
165    pub delete_chat_photo: True,
166}
167
168#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
169pub struct MessageGroupChatCreated {
170    /// Service message: the group has been created.
171    pub group_chat_created: True,
172}
173
174#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
175pub struct MessageSupergroupChatCreated {
176    /// Service message: the supergroup has been created. This field can‘t
177    /// be received in a message coming through updates, because bot can’t
178    /// be a member of a supergroup when it is created. It can only be
179    /// found in `reply_to_message` if someone replies to a very first
180    /// message in a directly created supergroup.
181    pub supergroup_chat_created: True,
182}
183
184#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
185pub struct MessageChannelChatCreated {
186    /// Service message: the channel has been created. This field can‘t be
187    /// received in a message coming through updates, because bot can’t be
188    /// a member of a channel when it is created. It can only be found in
189    /// `reply_to_message` if someone replies to a very first message in a
190    /// channel.
191    pub channel_chat_created: True,
192}
193
194#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
195pub struct MessageMessageAutoDeleteTimerChanged {
196    /// Service message: auto-delete timer settings changed in the chat.
197    pub message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged,
198}
199
200/// Represents group migration to a supergroup or a supergroup migration from a
201/// group.
202///
203/// Note that bot receives **both** updates. For example: a group with id `0`
204/// migrates to a supergroup with id `1` bots in that group will receive 2
205/// updates:
206/// - `message.chat.id = 0`, `message.chat_migration() = ChatMigration::To {
207///   chat_id: 1 }`
208/// - `message.chat.id = 1`, `message.chat_migration() = ChatMigration::From {
209///   chat_id: 0 }`
210#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
211#[serde(untagged)]
212pub enum ChatMigration {
213    /// The group has been migrated to a supergroup with the specified
214    /// identifier `chat_id`.
215    To {
216        #[serde(rename = "migrate_to_chat_id")]
217        chat_id: ChatId,
218    },
219
220    /// The supergroup has been migrated from a group with the specified
221    /// identifier `chat_id`.
222    From {
223        #[serde(rename = "migrate_from_chat_id")]
224        chat_id: ChatId,
225    },
226}
227
228#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
229pub struct MessagePinned {
230    /// Specified message was pinned. Note that the Message object in this
231    /// field will not contain further `reply_to_message` fields even if it
232    /// is itself a reply.
233    #[serde(rename = "pinned_message")]
234    pub pinned: Box<Message>,
235}
236
237#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
238pub struct MessageInvoice {
239    /// Message is an invoice for a [payment], information about the
240    /// invoice. [More about payments »].
241    ///
242    /// [payment]: https://core.telegram.org/bots/api#payments
243    /// [More about payments »]: https://core.telegram.org/bots/api#payments
244    pub invoice: Invoice,
245}
246
247#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
248pub struct MessageSuccessfulPayment {
249    /// Message is a service message about a successful payment,
250    /// information about the payment. [More about payments »].
251    ///
252    /// [More about payments »]: https://core.telegram.org/bots/api#payments
253    pub successful_payment: SuccessfulPayment,
254}
255
256#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
257pub struct MessageConnectedWebsite {
258    /// The domain name of the website on which the user has logged in.
259    /// [More about Telegram Login »].
260    ///
261    /// [More about Telegram Login »]: https://core.telegram.org/widgets/login
262    pub connected_website: String,
263}
264
265#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
266pub struct MessagePassportData {
267    /// Telegram Passport data.
268    pub passport_data: PassportData,
269}
270
271/// Information about forwarded message.
272#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
273pub struct Forward {
274    /// Date the original message was sent in Unix time.
275    #[serde(rename = "forward_date")]
276    #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
277    pub date: DateTime<Utc>,
278
279    /// The entity that sent the original message.
280    #[serde(flatten)]
281    pub from: ForwardedFrom,
282
283    /// For messages forwarded from channels, signature of the post author if
284    /// present. For messages forwarded from anonymous admins, authors title, if
285    /// present.
286    #[serde(rename = "forward_signature")]
287    pub signature: Option<String>,
288
289    /// For messages forwarded from channels, identifier of the original message
290    /// in the channel
291    #[serde(rename = "forward_from_message_id")]
292    pub message_id: Option<i32>,
293}
294
295/// The entity that sent the original message that later was forwarded.
296#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
297pub enum ForwardedFrom {
298    /// The message was sent by a user.
299    #[serde(rename = "forward_from")]
300    User(User),
301    /// The message was sent by an anonymous user on behalf of a group or
302    /// channel.
303    #[serde(rename = "forward_from_chat")]
304    Chat(Chat),
305    /// The message was sent by a user who disallow adding a link to their
306    /// account in forwarded messages.
307    #[serde(rename = "forward_sender_name")]
308    SenderName(String),
309}
310
311#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
312#[serde(untagged)]
313pub enum MediaKind {
314    // Note:
315    // - `Venue` must be in front of `Location`
316    // - `Animation` must be in front of `Document`
317    //
318    // This is needed so serde doesn't parse `Venue` as `Location` or `Animation` as `Document`
319    // (for backward compatability telegram duplicates some fields)
320    //
321    // See <https://github.com/teloxide/teloxide/issues/481>
322    Animation(MediaAnimation),
323    Audio(MediaAudio),
324    Contact(MediaContact),
325    Document(MediaDocument),
326    Game(MediaGame),
327    Venue(MediaVenue),
328    Location(MediaLocation),
329    Photo(MediaPhoto),
330    Poll(MediaPoll),
331    Sticker(MediaSticker),
332    Text(MediaText),
333    Video(MediaVideo),
334    VideoNote(MediaVideoNote),
335    Voice(MediaVoice),
336    Migration(ChatMigration),
337}
338
339#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
340pub struct MediaAnimation {
341    /// Message is an animation, information about the animation. For
342    /// backward compatibility, when this field is set, the document field
343    /// will also be set.
344    pub animation: Animation,
345
346    /// Caption for the animation, 0-1024 characters.
347    pub caption: Option<String>,
348
349    /// For messages with a caption, special entities like usernames, URLs,
350    /// bot commands, etc. that appear in the caption.
351    #[serde(default = "Vec::new")]
352    pub caption_entities: Vec<MessageEntity>,
353
354    /// `true`, if the message media is covered by a spoiler animation.
355    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
356    pub has_media_spoiler: bool,
357    // Note: for backward compatibility telegram also sends `document` field, but we ignore it
358}
359
360#[serde_with_macros::skip_serializing_none]
361#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
362pub struct MediaAudio {
363    /// Message is an audio file, information about the file.
364    pub audio: Audio,
365
366    /// Caption for the audio, 0-1024 characters.
367    pub caption: Option<String>,
368
369    /// For messages with a caption, special entities like usernames, URLs,
370    /// bot commands, etc. that appear in the caption.
371    #[serde(default = "Vec::new")]
372    pub caption_entities: Vec<MessageEntity>,
373
374    /// The unique identifier of a media message group this message belongs
375    /// to.
376    pub media_group_id: Option<String>,
377}
378
379#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
380pub struct MediaContact {
381    /// Message is a shared contact, information about the contact.
382    pub contact: Contact,
383}
384
385#[serde_with_macros::skip_serializing_none]
386#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
387pub struct MediaDocument {
388    /// Message is a general file, information about the file.
389    pub document: Document,
390
391    /// Caption for the document, 0-1024 characters.
392    pub caption: Option<String>,
393
394    /// For messages with a caption, special entities like usernames, URLs,
395    /// bot commands, etc. that appear in the caption.
396    #[serde(default)]
397    pub caption_entities: Vec<MessageEntity>,
398
399    /// The unique identifier of a media message group this message belongs
400    /// to.
401    pub media_group_id: Option<String>,
402}
403
404#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
405pub struct MediaGame {
406    /// Message is a game, information about the game. [More
407    /// about games »].
408    ///
409    /// [More about games »]: https://core.telegram.org/bots/api#games
410    pub game: Game,
411}
412
413#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
414pub struct MediaLocation {
415    /// Message is a shared location, information about the location.
416    pub location: Location,
417}
418
419#[serde_with_macros::skip_serializing_none]
420#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
421pub struct MediaPhoto {
422    /// Message is a photo, available sizes of the photo.
423    pub photo: Vec<PhotoSize>,
424
425    /// Caption for the photo, 0-1024 characters.
426    pub caption: Option<String>,
427
428    /// For messages with a caption, special entities like usernames, URLs,
429    /// bot commands, etc. that appear in the caption.
430    #[serde(default = "Vec::new")]
431    pub caption_entities: Vec<MessageEntity>,
432
433    /// `true`, if the message media is covered by a spoiler animation.
434    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
435    pub has_media_spoiler: bool,
436
437    /// The unique identifier of a media message group this message belongs
438    /// to.
439    pub media_group_id: Option<String>,
440}
441
442#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
443pub struct MediaPoll {
444    /// Message is a native poll, information about the poll.
445    pub poll: Poll,
446}
447
448#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
449pub struct MediaSticker {
450    /// Message is a sticker, information about the sticker.
451    pub sticker: Sticker,
452}
453
454#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
455pub struct MediaText {
456    /// For text messages, the actual UTF-8 text of the message, 0-4096
457    /// characters.
458    pub text: String,
459
460    /// For text messages, special entities like usernames, URLs, bot
461    /// commands, etc. that appear in the text.
462    #[serde(default = "Vec::new")]
463    pub entities: Vec<MessageEntity>,
464}
465
466#[serde_with_macros::skip_serializing_none]
467#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
468pub struct MediaVideo {
469    /// Message is a video, information about the video.
470    pub video: Video,
471
472    /// Caption for the video, 0-1024 characters.
473    pub caption: Option<String>,
474
475    /// For messages with a caption, special entities like usernames, URLs,
476    /// bot commands, etc. that appear in the caption.
477    #[serde(default = "Vec::new")]
478    pub caption_entities: Vec<MessageEntity>,
479
480    /// `true`, if the message media is covered by a spoiler animation.
481    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
482    pub has_media_spoiler: bool,
483
484    /// The unique identifier of a media message group this message belongs
485    /// to.
486    pub media_group_id: Option<String>,
487}
488
489#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
490pub struct MediaVideoNote {
491    /// Message is a [video note], information about the video message.
492    ///
493    /// [video note]: https://telegram.org/blog/video-messages-and-telescope
494    pub video_note: VideoNote,
495}
496
497#[serde_with_macros::skip_serializing_none]
498#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
499pub struct MediaVoice {
500    /// Message is a voice message, information about the file.
501    pub voice: Voice,
502
503    /// Caption for the voice, 0-1024 characters.
504    pub caption: Option<String>,
505
506    /// For messages with a caption, special entities like usernames, URLs,
507    /// bot commands, etc. that appear in the caption.
508    #[serde(default = "Vec::new")]
509    pub caption_entities: Vec<MessageEntity>,
510}
511
512#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
513pub struct MediaVenue {
514    /// Message is a venue, information about the venue.
515    pub venue: Venue,
516    // Note: for backward compatibility telegram also sends `location` field, but we ignore it
517}
518
519#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
520pub struct MessageDice {
521    /// Message is a dice with random value from 1 to 6.
522    pub dice: Dice,
523}
524
525#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
526pub struct MessageProximityAlertTriggered {
527    /// Service message. A user in the chat triggered another user's proximity
528    /// alert while sharing Live Location.
529    pub proximity_alert_triggered: ProximityAlertTriggered,
530}
531
532#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
533pub struct MessageWriteAccessAllowed {
534    /// Service message: the user allowed the bot added to the attachment menu
535    /// to write messages.
536    pub write_access_allowed: WriteAccessAllowed,
537}
538
539#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
540pub struct MessageForumTopicCreated {
541    /// Service message: forum topic created.
542    pub forum_topic_created: ForumTopicCreated,
543}
544
545#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
546pub struct MessageForumTopicEdited {
547    /// Service message: forum topic edited.
548    pub forum_topic_edited: ForumTopicEdited,
549}
550
551#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
552pub struct MessageForumTopicClosed {
553    /// Service message: forum topic closed.
554    pub forum_topic_closed: ForumTopicClosed,
555}
556
557#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
558pub struct MessageForumTopicReopened {
559    /// Service message: forum topic reopened.
560    pub forum_topic_reopened: ForumTopicReopened,
561}
562
563#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
564pub struct MessageGeneralForumTopicHidden {
565    /// Service message: the 'General' forum topic hidden.
566    pub general_forum_topic_hidden: GeneralForumTopicHidden,
567}
568
569#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
570pub struct MessageGeneralForumTopicUnhidden {
571    /// Service message: the 'General' forum topic unhidden.
572    pub general_forum_topic_unhidden: GeneralForumTopicUnhidden,
573}
574
575#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
576pub struct MessageVideoChatScheduled {
577    /// Service message: video chat scheduled
578    pub video_chat_scheduled: VideoChatScheduled,
579}
580
581#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
582pub struct MessageVideoChatStarted {
583    /// Service message: video chat started.
584    pub video_chat_started: VideoChatStarted,
585}
586
587#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
588pub struct MessageVideoChatEnded {
589    /// Service message: video chat ended.
590    pub video_chat_ended: VideoChatEnded,
591}
592
593#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
594pub struct MessageVideoChatParticipantsInvited {
595    /// Service message: new participants invited to a video chat.
596    pub video_chat_participants_invited: VideoChatParticipantsInvited,
597}
598
599#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
600pub struct MessageWebAppData {
601    /// Service message: data sent by a Web App.
602    pub web_app_data: WebAppData,
603}
604
605mod getters {
606    use chrono::{DateTime, Utc};
607    use std::ops::Deref;
608
609    use crate::types::{
610        self, message::MessageKind::*, Chat, ChatId, ChatMigration, Forward, ForwardedFrom,
611        MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind,
612        MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue, MediaVideo,
613        MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon,
614        MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity,
615        MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers,
616        MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned,
617        MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated,
618        MessageVideoChatParticipantsInvited, PhotoSize, True, User,
619    };
620
621    /// Getters for [Message] fields from [telegram docs].
622    ///
623    /// [Message]: crate::types::Message
624    /// [telegram docs]: https://core.telegram.org/bots/api#message
625    impl Message {
626        /// Returns the user who sent the message.
627        #[must_use]
628        pub fn from(&self) -> Option<&User> {
629            match &self.kind {
630                Common(MessageCommon { from, .. }) => from.as_ref(),
631                _ => None,
632            }
633        }
634
635        #[must_use]
636        pub fn author_signature(&self) -> Option<&str> {
637            match &self.kind {
638                Common(MessageCommon { author_signature, .. }) => author_signature.as_deref(),
639                _ => None,
640            }
641        }
642
643        #[must_use]
644        pub fn sender_chat(&self) -> Option<&Chat> {
645            match &self.kind {
646                Common(MessageCommon { sender_chat, .. }) => sender_chat.as_ref(),
647                _ => None,
648            }
649        }
650
651        #[deprecated(since = "0.4.2", note = "use `.chat.id` field instead")]
652        #[must_use]
653        pub fn chat_id(&self) -> ChatId {
654            self.chat.id
655        }
656
657        #[must_use]
658        pub fn forward(&self) -> Option<&Forward> {
659            self.common().and_then(|m| m.forward.as_ref())
660        }
661
662        #[must_use]
663        pub fn forward_date(&self) -> Option<DateTime<Utc>> {
664            self.forward().map(|f| f.date)
665        }
666
667        #[must_use]
668        pub fn forward_from(&self) -> Option<&ForwardedFrom> {
669            self.forward().map(|f| &f.from)
670        }
671
672        #[must_use]
673        pub fn forward_from_user(&self) -> Option<&User> {
674            self.forward_from().and_then(|from| match from {
675                ForwardedFrom::User(user) => Some(user),
676                _ => None,
677            })
678        }
679
680        #[must_use]
681        pub fn forward_from_chat(&self) -> Option<&Chat> {
682            self.forward_from().and_then(|from| match from {
683                ForwardedFrom::Chat(chat) => Some(chat),
684                _ => None,
685            })
686        }
687
688        #[must_use]
689        pub fn forward_from_sender_name(&self) -> Option<&str> {
690            self.forward_from().and_then(|from| match from {
691                ForwardedFrom::SenderName(sender_name) => Some(&**sender_name),
692                _ => None,
693            })
694        }
695
696        #[must_use]
697        pub fn forward_from_message_id(&self) -> Option<i32> {
698            self.forward().and_then(|f| f.message_id)
699        }
700
701        #[must_use]
702        pub fn forward_signature(&self) -> Option<&str> {
703            self.forward().and_then(|f| f.signature.as_deref())
704        }
705
706        #[must_use]
707        pub fn reply_to_message(&self) -> Option<&Message> {
708            self.common().and_then(|m| m.reply_to_message.as_deref())
709        }
710
711        #[must_use]
712        pub fn edit_date(&self) -> Option<&DateTime<Utc>> {
713            match &self.kind {
714                Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(),
715                _ => None,
716            }
717        }
718
719        #[must_use]
720        pub fn media_group_id(&self) -> Option<&str> {
721            match &self.kind {
722                Common(MessageCommon {
723                    media_kind: MediaKind::Video(MediaVideo { media_group_id, .. }),
724                    ..
725                })
726                | Common(MessageCommon {
727                    media_kind: MediaKind::Photo(MediaPhoto { media_group_id, .. }),
728                    ..
729                })
730                | Common(MessageCommon {
731                    media_kind: MediaKind::Document(MediaDocument { media_group_id, .. }),
732                    ..
733                })
734                | Common(MessageCommon {
735                    media_kind: MediaKind::Audio(MediaAudio { media_group_id, .. }),
736                    ..
737                }) => media_group_id.as_ref().map(Deref::deref),
738                _ => None,
739            }
740        }
741
742        #[must_use]
743        pub fn text(&self) -> Option<&str> {
744            match &self.kind {
745                Common(MessageCommon {
746                    media_kind: MediaKind::Text(MediaText { text, .. }),
747                    ..
748                }) => Some(text),
749                _ => None,
750            }
751        }
752
753        /// Returns message entities that represent text formatting.
754        ///
755        /// **Note:** you probably want to use [`parse_entities`] instead.
756        ///
757        /// This function returns `Some(entities)` for **text messages** and
758        /// `None` for all other kinds of messages (including photos with
759        /// captions).
760        ///
761        /// See also: [`caption_entities`].
762        ///
763        /// [`parse_entities`]: Message::parse_entities
764        /// [`caption_entities`]: Message::caption_entities
765        #[must_use]
766        pub fn entities(&self) -> Option<&[MessageEntity]> {
767            match &self.kind {
768                Common(MessageCommon {
769                    media_kind: MediaKind::Text(MediaText { entities, .. }),
770                    ..
771                }) => Some(entities),
772                _ => None,
773            }
774        }
775
776        /// Returns message entities that represent text formatting.
777        ///
778        /// **Note:** you probably want to use [`parse_caption_entities`]
779        /// instead.
780        ///
781        /// This function returns `Some(entities)` for **media messages** and
782        /// `None` for all other kinds of messages (including text messages).
783        ///
784        /// See also: [`entities`].
785        ///
786        /// [`parse_caption_entities`]: Message::parse_caption_entities
787        /// [`entities`]: Message::entities
788        #[must_use]
789        pub fn caption_entities(&self) -> Option<&[MessageEntity]> {
790            match &self.kind {
791                Common(MessageCommon {
792                    media_kind: MediaKind::Animation(MediaAnimation { caption_entities, .. }),
793                    ..
794                })
795                | Common(MessageCommon {
796                    media_kind: MediaKind::Audio(MediaAudio { caption_entities, .. }),
797                    ..
798                })
799                | Common(MessageCommon {
800                    media_kind: MediaKind::Document(MediaDocument { caption_entities, .. }),
801                    ..
802                })
803                | Common(MessageCommon {
804                    media_kind: MediaKind::Photo(MediaPhoto { caption_entities, .. }),
805                    ..
806                })
807                | Common(MessageCommon {
808                    media_kind: MediaKind::Video(MediaVideo { caption_entities, .. }),
809                    ..
810                })
811                | Common(MessageCommon {
812                    media_kind: MediaKind::Voice(MediaVoice { caption_entities, .. }),
813                    ..
814                }) => Some(caption_entities),
815                _ => None,
816            }
817        }
818
819        /// Returns `true` if the message media is covered by a spoiler
820        /// animation.
821        ///
822        /// Getter for [`MediaPhoto::has_media_spoiler`],
823        /// [`MediaVideo::has_media_spoiler`] and
824        /// [`MediaAnimation::has_media_spoiler`].
825        #[must_use]
826        pub fn has_media_spoiler(&self) -> bool {
827            self.common()
828                .map(|m| match m.media_kind {
829                    MediaKind::Animation(MediaAnimation { has_media_spoiler, .. })
830                    | MediaKind::Photo(MediaPhoto { has_media_spoiler, .. })
831                    | MediaKind::Video(MediaVideo { has_media_spoiler, .. }) => has_media_spoiler,
832                    MediaKind::Audio(_)
833                    | MediaKind::Contact(_)
834                    | MediaKind::Document(_)
835                    | MediaKind::Game(_)
836                    | MediaKind::Venue(_)
837                    | MediaKind::Location(_)
838                    | MediaKind::Poll(_)
839                    | MediaKind::Sticker(_)
840                    | MediaKind::Text(_)
841                    | MediaKind::VideoNote(_)
842                    | MediaKind::Voice(_)
843                    | MediaKind::Migration(_) => false,
844                })
845                .unwrap_or(false)
846        }
847
848        #[must_use]
849        pub fn audio(&self) -> Option<&types::Audio> {
850            match &self.kind {
851                Common(MessageCommon {
852                    media_kind: MediaKind::Audio(MediaAudio { audio, .. }),
853                    ..
854                }) => Some(audio),
855                _ => None,
856            }
857        }
858
859        #[must_use]
860        pub fn document(&self) -> Option<&types::Document> {
861            match &self.kind {
862                Common(MessageCommon {
863                    media_kind: MediaKind::Document(MediaDocument { document, .. }),
864                    ..
865                }) => Some(document),
866                _ => None,
867            }
868        }
869
870        #[must_use]
871        pub fn animation(&self) -> Option<&types::Animation> {
872            match &self.kind {
873                Common(MessageCommon {
874                    media_kind: MediaKind::Animation(MediaAnimation { animation, .. }),
875                    ..
876                }) => Some(animation),
877                _ => None,
878            }
879        }
880
881        #[must_use]
882        pub fn game(&self) -> Option<&types::Game> {
883            match &self.kind {
884                Common(MessageCommon {
885                    media_kind: MediaKind::Game(MediaGame { game, .. }),
886                    ..
887                }) => Some(game),
888                _ => None,
889            }
890        }
891
892        #[must_use]
893        pub fn photo(&self) -> Option<&[PhotoSize]> {
894            match &self.kind {
895                Common(MessageCommon {
896                    media_kind: MediaKind::Photo(MediaPhoto { photo, .. }),
897                    ..
898                }) => Some(photo),
899                _ => None,
900            }
901        }
902
903        #[must_use]
904        pub fn sticker(&self) -> Option<&types::Sticker> {
905            match &self.kind {
906                Common(MessageCommon {
907                    media_kind: MediaKind::Sticker(MediaSticker { sticker, .. }),
908                    ..
909                }) => Some(sticker),
910                _ => None,
911            }
912        }
913
914        #[must_use]
915        pub fn video(&self) -> Option<&types::Video> {
916            match &self.kind {
917                Common(MessageCommon {
918                    media_kind: MediaKind::Video(MediaVideo { video, .. }),
919                    ..
920                }) => Some(video),
921                _ => None,
922            }
923        }
924
925        #[must_use]
926        pub fn voice(&self) -> Option<&types::Voice> {
927            match &self.kind {
928                Common(MessageCommon {
929                    media_kind: MediaKind::Voice(MediaVoice { voice, .. }),
930                    ..
931                }) => Some(voice),
932                _ => None,
933            }
934        }
935
936        #[must_use]
937        pub fn video_note(&self) -> Option<&types::VideoNote> {
938            match &self.kind {
939                Common(MessageCommon {
940                    media_kind: MediaKind::VideoNote(MediaVideoNote { video_note, .. }),
941                    ..
942                }) => Some(video_note),
943                _ => None,
944            }
945        }
946
947        #[must_use]
948        pub fn caption(&self) -> Option<&str> {
949            match &self.kind {
950                Common(MessageCommon { media_kind, .. }) => match media_kind {
951                    MediaKind::Animation(MediaAnimation { caption, .. })
952                    | MediaKind::Audio(MediaAudio { caption, .. })
953                    | MediaKind::Document(MediaDocument { caption, .. })
954                    | MediaKind::Photo(MediaPhoto { caption, .. })
955                    | MediaKind::Video(MediaVideo { caption, .. })
956                    | MediaKind::Voice(MediaVoice { caption, .. }) => {
957                        caption.as_ref().map(Deref::deref)
958                    }
959                    _ => None,
960                },
961                _ => None,
962            }
963        }
964
965        #[must_use]
966        pub fn contact(&self) -> Option<&types::Contact> {
967            match &self.kind {
968                Common(MessageCommon {
969                    media_kind: MediaKind::Contact(MediaContact { contact, .. }),
970                    ..
971                }) => Some(contact),
972                _ => None,
973            }
974        }
975
976        #[must_use]
977        pub fn location(&self) -> Option<&types::Location> {
978            match &self.kind {
979                Common(MessageCommon {
980                    media_kind: MediaKind::Location(MediaLocation { location, .. }),
981                    ..
982                }) => Some(location),
983                _ => None,
984            }
985        }
986
987        #[must_use]
988        pub fn venue(&self) -> Option<&types::Venue> {
989            match &self.kind {
990                Common(MessageCommon {
991                    media_kind: MediaKind::Venue(MediaVenue { venue, .. }),
992                    ..
993                }) => Some(venue),
994                _ => None,
995            }
996        }
997
998        #[must_use]
999        pub fn poll(&self) -> Option<&types::Poll> {
1000            match &self.kind {
1001                Common(MessageCommon {
1002                    media_kind: MediaKind::Poll(MediaPoll { poll, .. }),
1003                    ..
1004                }) => Some(poll),
1005                _ => None,
1006            }
1007        }
1008
1009        #[must_use]
1010        pub fn new_chat_members(&self) -> Option<&[User]> {
1011            match &self.kind {
1012                NewChatMembers(MessageNewChatMembers { new_chat_members }) => {
1013                    Some(new_chat_members.as_ref())
1014                }
1015                _ => None,
1016            }
1017        }
1018
1019        #[must_use]
1020        pub fn left_chat_member(&self) -> Option<&User> {
1021            match &self.kind {
1022                LeftChatMember(MessageLeftChatMember { left_chat_member }) => {
1023                    Some(left_chat_member)
1024                }
1025                _ => None,
1026            }
1027        }
1028
1029        #[must_use]
1030        pub fn new_chat_title(&self) -> Option<&str> {
1031            match &self.kind {
1032                NewChatTitle(MessageNewChatTitle { new_chat_title }) => Some(new_chat_title),
1033                _ => None,
1034            }
1035        }
1036
1037        #[must_use]
1038        pub fn new_chat_photo(&self) -> Option<&[PhotoSize]> {
1039            match &self.kind {
1040                NewChatPhoto(MessageNewChatPhoto { new_chat_photo }) => Some(new_chat_photo),
1041                _ => None,
1042            }
1043        }
1044
1045        // TODO: OK, `Option<True>` is weird, can we do something with it?
1046        //       mb smt like `is_delete_chat_photo(&self) -> bool`?
1047        #[must_use]
1048        pub fn delete_chat_photo(&self) -> Option<True> {
1049            match &self.kind {
1050                DeleteChatPhoto(MessageDeleteChatPhoto { delete_chat_photo }) => {
1051                    Some(*delete_chat_photo)
1052                }
1053                _ => None,
1054            }
1055        }
1056
1057        #[must_use]
1058        pub fn group_chat_created(&self) -> Option<True> {
1059            match &self.kind {
1060                GroupChatCreated(MessageGroupChatCreated { group_chat_created }) => {
1061                    Some(*group_chat_created)
1062                }
1063                _ => None,
1064            }
1065        }
1066
1067        #[must_use]
1068        pub fn super_group_chat_created(&self) -> Option<True> {
1069            match &self.kind {
1070                SupergroupChatCreated(MessageSupergroupChatCreated { supergroup_chat_created }) => {
1071                    Some(*supergroup_chat_created)
1072                }
1073                _ => None,
1074            }
1075        }
1076
1077        #[must_use]
1078        pub fn channel_chat_created(&self) -> Option<True> {
1079            match &self.kind {
1080                ChannelChatCreated(MessageChannelChatCreated { channel_chat_created }) => {
1081                    Some(*channel_chat_created)
1082                }
1083                _ => None,
1084            }
1085        }
1086
1087        #[must_use]
1088        pub fn chat_migration(&self) -> Option<ChatMigration> {
1089            match &self.kind {
1090                Common(MessageCommon {
1091                    media_kind: MediaKind::Migration(chat_migration), ..
1092                }) => Some(*chat_migration),
1093                _ => None,
1094            }
1095        }
1096
1097        #[must_use]
1098        pub fn migrate_to_chat_id(&self) -> Option<ChatId> {
1099            match &self.kind {
1100                Common(MessageCommon {
1101                    media_kind: MediaKind::Migration(ChatMigration::To { chat_id }),
1102                    ..
1103                }) => Some(*chat_id),
1104                _ => None,
1105            }
1106        }
1107
1108        #[must_use]
1109        pub fn migrate_from_chat_id(&self) -> Option<ChatId> {
1110            match &self.kind {
1111                Common(MessageCommon {
1112                    media_kind: MediaKind::Migration(ChatMigration::From { chat_id }),
1113                    ..
1114                }) => Some(*chat_id),
1115                _ => None,
1116            }
1117        }
1118
1119        #[must_use]
1120        pub fn pinned_message(&self) -> Option<&Message> {
1121            match &self.kind {
1122                Pinned(MessagePinned { pinned }) => Some(pinned),
1123                _ => None,
1124            }
1125        }
1126
1127        #[must_use]
1128        pub fn invoice(&self) -> Option<&types::Invoice> {
1129            match &self.kind {
1130                Invoice(MessageInvoice { invoice }) => Some(invoice),
1131                _ => None,
1132            }
1133        }
1134
1135        #[must_use]
1136        pub fn successful_payment(&self) -> Option<&types::SuccessfulPayment> {
1137            match &self.kind {
1138                SuccessfulPayment(MessageSuccessfulPayment { successful_payment }) => {
1139                    Some(successful_payment)
1140                }
1141                _ => None,
1142            }
1143        }
1144
1145        #[must_use]
1146        pub fn connected_website(&self) -> Option<&str> {
1147            match &self.kind {
1148                ConnectedWebsite(MessageConnectedWebsite { connected_website }) => {
1149                    Some(connected_website)
1150                }
1151                _ => None,
1152            }
1153        }
1154
1155        #[must_use]
1156        pub fn passport_data(&self) -> Option<&types::PassportData> {
1157            match &self.kind {
1158                PassportData(MessagePassportData { passport_data }) => Some(passport_data),
1159                _ => None,
1160            }
1161        }
1162
1163        #[must_use]
1164        pub fn dice(&self) -> Option<&types::Dice> {
1165            match &self.kind {
1166                Dice(MessageDice { dice }) => Some(dice),
1167                _ => None,
1168            }
1169        }
1170
1171        #[must_use]
1172        pub fn proximity_alert_triggered(&self) -> Option<&types::ProximityAlertTriggered> {
1173            match &self.kind {
1174                ProximityAlertTriggered(MessageProximityAlertTriggered {
1175                    proximity_alert_triggered,
1176                }) => Some(proximity_alert_triggered),
1177                _ => None,
1178            }
1179        }
1180
1181        #[must_use]
1182        pub fn video_chat_participants_invited(
1183            &self,
1184        ) -> Option<&types::VideoChatParticipantsInvited> {
1185            match &self.kind {
1186                VideoChatParticipantsInvited(MessageVideoChatParticipantsInvited {
1187                    video_chat_participants_invited,
1188                }) => Some(video_chat_participants_invited),
1189                _ => None,
1190            }
1191        }
1192
1193        #[must_use]
1194        pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> {
1195            match &self.kind {
1196                Common(MessageCommon { reply_markup, .. }) => reply_markup.as_ref(),
1197                _ => None,
1198            }
1199        }
1200
1201        #[must_use]
1202        pub fn is_automatic_forward(&self) -> bool {
1203            match &self.kind {
1204                Common(MessageCommon { is_automatic_forward, .. }) => *is_automatic_forward,
1205                _ => false,
1206            }
1207        }
1208
1209        #[must_use]
1210        pub fn has_protected_content(&self) -> bool {
1211            match &self.kind {
1212                Common(MessageCommon { has_protected_content, .. }) => *has_protected_content,
1213                _ => false,
1214            }
1215        }
1216
1217        /// Common message (text, image, etc)
1218        fn common(&self) -> Option<&MessageCommon> {
1219            match &self.kind {
1220                Common(message) => Some(message),
1221                _ => None,
1222            }
1223        }
1224
1225        // FIXME: add more getters for other types of messages
1226    }
1227}
1228
1229impl Message {
1230    /// Produces a direct link to this message.
1231    ///
1232    /// Note that for private groups the link will only be accessible for group
1233    /// members.
1234    ///
1235    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1236    /// supergroups).
1237    #[must_use]
1238    pub fn url(&self) -> Option<Url> {
1239        Self::url_of(self.chat.id, self.chat.username(), self.id)
1240    }
1241
1242    /// Produces a direct link to a message in a chat.
1243    ///
1244    /// If you have a `Message` object, use [`url`] instead.
1245    /// This function should only be used if you have limited information about
1246    /// the message (chat id, username of the chat, if any and its id).
1247    ///
1248    /// Note that for private groups the link will only be accessible for group
1249    /// members.
1250    ///
1251    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1252    /// supergroups).
1253    ///
1254    /// [`url`]: Message::url
1255    #[track_caller]
1256    #[must_use]
1257    pub fn url_of(
1258        chat_id: ChatId,
1259        chat_username: Option<&str>,
1260        message_id: MessageId,
1261    ) -> Option<Url> {
1262        use BareChatId::*;
1263
1264        // Note: `t.me` links use bare chat ids
1265        let chat_id = match chat_id.to_bare() {
1266            // For private chats (i.e.: DMs) we can't produce "normal" t.me link.
1267            //
1268            // There are "tg://openmessage?user_id={0}&message_id={1}" links, which are
1269            // supposed to open any chat, including private messages, but they
1270            // are only supported by some telegram clients (e.g. Plus Messenger,
1271            // Telegram for Android 4.9+).
1272            User(_) => return None,
1273            // Similarly to user chats, there is no way to create a link to a message in a normal,
1274            // private group.
1275            //
1276            // (public groups are always supergroup which are in turn channels).
1277            Group(_) => return None,
1278            Channel(id) => id,
1279        };
1280
1281        let url = match chat_username {
1282            // If it's public group (i.e. not DM, not private group), we can produce
1283            // "normal" t.me link (accessible to everyone).
1284            Some(username) => format!("https://t.me/{0}/{1}", username, message_id.0),
1285            // For private supergroups and channels we produce "private" t.me/c links. These are
1286            // only accessible to the group members.
1287            None => format!("https://t.me/c/{0}/{1}", chat_id, message_id.0),
1288        };
1289
1290        // UNWRAP:
1291        //
1292        // The `url` produced by formatting is correct since username is
1293        // /[a-zA-Z0-9_]{5,32}/ and chat/message ids are integers.
1294        Some(url::Url::parse(&url).unwrap())
1295    }
1296
1297    /// Produces a direct link to a comment on this post.
1298    ///
1299    /// Note that for private channels the link will only be accessible for
1300    /// channel members.
1301    ///
1302    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1303    /// supergroups).
1304    #[must_use]
1305    pub fn comment_url(&self, comment_id: MessageId) -> Option<Url> {
1306        Self::comment_url_of(self.chat.id, self.chat.username(), self.id, comment_id)
1307    }
1308
1309    /// Produces a direct link to a comment on a post.
1310    ///
1311    /// If you have a `Message` object of the channel post, use [`comment_url`]
1312    /// instead. This function should only be used if you have limited
1313    /// information about the message (channel id, username of the channel,
1314    /// if any, post id and comment id).
1315    ///
1316    /// Note that for private channels the link will only be accessible for
1317    /// channel members.
1318    ///
1319    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1320    /// supergroups).
1321    ///
1322    /// [`comment_url`]: Message::comment_url
1323    #[must_use]
1324    pub fn comment_url_of(
1325        channel_id: ChatId,
1326        channel_username: Option<&str>,
1327        post_id: MessageId,
1328        comment_id: MessageId,
1329    ) -> Option<Url> {
1330        Self::url_of(channel_id, channel_username, post_id).map(|mut url| {
1331            url.set_query(Some(&format!("comment={}", comment_id.0)));
1332            url
1333        })
1334    }
1335
1336    /// Produces a direct link to this message in a given thread.
1337    ///
1338    /// "Thread" is a group of messages that reply to each other in a tree-like
1339    /// structure. `thread_starter_msg_id` is the id of the first message in
1340    /// the thread, the root of the tree.
1341    ///
1342    /// Note that for private groups the link will only be accessible for group
1343    /// members.
1344    ///
1345    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1346    /// supergroups).
1347    #[must_use]
1348    pub fn url_in_thread(&self, thread_starter_msg_id: MessageId) -> Option<Url> {
1349        Self::url_in_thread_of(self.chat.id, self.chat.username(), thread_starter_msg_id, self.id)
1350    }
1351
1352    /// Produces a direct link to a message in a given thread.
1353    ///
1354    /// If you have a `Message` object of the channel post, use
1355    /// [`url_in_thread`] instead. This function should only be used if you
1356    /// have limited information about the message (chat id, username of the
1357    /// chat, if any, thread starter id and message id).
1358    ///
1359    /// "Thread" is a group of messages that reply to each other in a tree-like
1360    /// structure. `thread_starter_msg_id` is the id of the first message in
1361    /// the thread, the root of the tree.
1362    ///
1363    /// Note that for private groups the link will only be accessible for group
1364    /// members.
1365    ///
1366    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1367    /// supergroups).
1368    ///
1369    /// [`url_in_thread`]: Message::url_in_thread
1370    #[must_use]
1371    pub fn url_in_thread_of(
1372        chat_id: ChatId,
1373        chat_username: Option<&str>,
1374        thread_starter_msg_id: MessageId,
1375        message_id: MessageId,
1376    ) -> Option<Url> {
1377        Self::url_of(chat_id, chat_username, message_id).map(|mut url| {
1378            url.set_query(Some(&format!("thread={}", thread_starter_msg_id.0)));
1379            url
1380        })
1381    }
1382
1383    /// Returns message entities that represent text formatting.
1384    ///
1385    /// This function returns `Some(entities)` for **text messages** and
1386    /// `None` for all other kinds of messages (including photos with
1387    /// captions).
1388    ///
1389    /// See also: [`parse_caption_entities`].
1390    ///
1391    /// [`parse_caption_entities`]: Message::parse_caption_entities
1392    #[must_use]
1393    pub fn parse_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
1394        self.text().zip(self.entities()).map(|(t, e)| MessageEntityRef::parse(t, e))
1395    }
1396
1397    /// Returns message entities that represent text formatting.
1398    ///
1399    /// This function returns `Some(entities)` for **media messages** and
1400    /// `None` for all other kinds of messages (including text messages).
1401    ///
1402    /// See also: [`parse_entities`].
1403    ///
1404    /// [`parse_entities`]: Message::parse_entities
1405    #[must_use]
1406    pub fn parse_caption_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
1407        self.caption().zip(self.caption_entities()).map(|(t, e)| MessageEntityRef::parse(t, e))
1408    }
1409
1410    /// Returns all users that are "contained" in this `Message` structure.
1411    ///
1412    /// This might be useful to track information about users.
1413    ///
1414    /// Note that this function may return quite a few users as it scans
1415    /// replies, pinned messages, message entities and more. Also note that this
1416    /// function can return duplicate users.
1417    pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
1418        use crate::util::{flatten, mentioned_users_from_entities};
1419
1420        // Lets just hope we didn't forget something here...
1421
1422        self.from()
1423            .into_iter()
1424            .chain(self.via_bot.as_ref())
1425            .chain(self.chat.mentioned_users_rec())
1426            .chain(flatten(self.reply_to_message().map(Self::mentioned_users_rec)))
1427            .chain(flatten(self.new_chat_members()))
1428            .chain(self.left_chat_member())
1429            .chain(self.forward_from_user())
1430            .chain(flatten(self.forward_from_chat().map(Chat::mentioned_users_rec)))
1431            .chain(flatten(self.game().map(Game::mentioned_users)))
1432            .chain(flatten(self.entities().map(mentioned_users_from_entities)))
1433            .chain(flatten(self.caption_entities().map(mentioned_users_from_entities)))
1434            .chain(flatten(self.poll().map(Poll::mentioned_users)))
1435            .chain(flatten(self.proximity_alert_triggered().map(|a| [&a.traveler, &a.watcher])))
1436            .chain(flatten(self.video_chat_participants_invited().and_then(|i| i.users.as_deref())))
1437    }
1438
1439    /// `Message::mentioned_users` is recursive (due to replies), as such we
1440    /// can't use `->impl Iterator` everywhere, as it would make an infinite
1441    /// type. So we need to box somewhere.
1442    pub(crate) fn mentioned_users_rec(&self) -> Box<dyn Iterator<Item = &User> + Send + Sync + '_> {
1443        Box::new(self.mentioned_users())
1444    }
1445}
1446
1447#[cfg(test)]
1448mod tests {
1449    use serde_json::from_str;
1450
1451    use crate::types::*;
1452
1453    #[test]
1454    fn de_media_forwarded() {
1455        let json = r#"{
1456          "message_id": 198283,
1457          "from": {
1458            "id": 250918540,
1459            "is_bot": false,
1460            "first_name": "Андрей",
1461            "last_name": "Власов",
1462            "username": "aka_dude",
1463            "language_code": "en"
1464          },
1465          "chat": {
1466            "id": 250918540,
1467            "first_name": "Андрей",
1468            "last_name": "Власов",
1469            "username": "aka_dude",
1470            "type": "private"
1471          },
1472          "date": 1567927221,
1473          "video": {
1474            "duration": 13,
1475            "width": 512,
1476            "height": 640,
1477            "mime_type": "video/mp4",
1478            "thumb": {
1479              "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE",
1480              "file_unique_id":"",
1481              "file_size": 10339,
1482              "width": 256,
1483              "height": 320
1484            },
1485            "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE",
1486            "file_unique_id":"",
1487            "file_size": 1381334
1488          }
1489        }"#;
1490        let message = from_str::<Message>(json);
1491        assert!(message.is_ok());
1492    }
1493
1494    #[test]
1495    fn de_media_group_forwarded() {
1496        let json = r#"{
1497          "message_id": 198283,
1498          "from": {
1499            "id": 250918540,
1500            "is_bot": false,
1501            "first_name": "Андрей",
1502            "last_name": "Власов",
1503            "username": "aka_dude",
1504            "language_code": "en"
1505          },
1506          "chat": {
1507            "id": 250918540,
1508            "first_name": "Андрей",
1509            "last_name": "Власов",
1510            "username": "aka_dude",
1511            "type": "private"
1512          },
1513          "date": 1567927221,
1514          "media_group_id": "12543417770506682",
1515          "video": {
1516            "duration": 13,
1517            "width": 512,
1518            "height": 640,
1519            "mime_type": "video/mp4",
1520            "thumb": {
1521              "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE",
1522              "file_unique_id":"",
1523              "file_size": 10339,
1524              "width": 256,
1525              "height": 320
1526            },
1527            "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE",
1528            "file_unique_id":"",
1529            "file_size": 1381334
1530          }
1531        }"#;
1532        let message = from_str::<Message>(json);
1533        assert!(message.is_ok());
1534    }
1535
1536    #[test]
1537    fn de_text() {
1538        let json = r#"{
1539          "message_id": 199785,
1540          "from": {
1541           "id": 250918540,
1542           "is_bot": false,
1543           "first_name": "Андрей",
1544           "last_name": "Власов",
1545           "username": "aka_dude",
1546           "language_code": "en"
1547          },
1548          "chat": {
1549           "id": 250918540,
1550           "first_name": "Андрей",
1551           "last_name": "Власов",
1552           "username": "aka_dude",
1553           "type": "private"
1554          },
1555          "date": 1568289890,
1556          "text": "Лол кек 😂"
1557         }"#;
1558        let message = from_str::<Message>(json);
1559        assert!(message.is_ok());
1560    }
1561
1562    #[test]
1563    fn de_sticker() {
1564        let json = r#"{
1565            "message_id": 199787,
1566            "from": {
1567                "id": 250918540,
1568                "is_bot": false,
1569                "first_name": "Андрей",
1570                "last_name": "Власов",
1571                "username": "aka_dude",
1572                "language_code": "en"
1573            },
1574            "chat": {
1575                "id": 250918540,
1576                "first_name": "Андрей",
1577                "last_name": "Власов",
1578                "username": "aka_dude",
1579                "type": "private"
1580            },
1581            "date": 1568290188,
1582            "sticker": {
1583                "width": 512,
1584                "height": 512,
1585                "emoji": "😡",
1586                "set_name": "AdvenTimeAnim",
1587                "is_animated": true,
1588                "is_video": false,
1589                "type": "regular",
1590                "thumb": {
1591                    "file_id": "AAMCAgADGQEAARIt0GMwiZ6n4nRbxdpM3pL8vPX6PVAhAAIjAAOw0PgMaabKAcaXKCABAAdtAAMpBA",
1592                    "file_unique_id": "AQADIwADsND4DHI",
1593                    "file_size": 4118,
1594                    "width": 128,
1595                    "height": 128
1596                },
1597                "file_id": "CAACAgIAAxkBAAESLdBjMImep-J0W8XaTN6S_Lz1-j1QIQACIwADsND4DGmmygHGlyggKQQ",
1598                "file_unique_id": "AgADIwADsND4DA",
1599                "file_size": 16639
1600            }
1601        }"#;
1602        from_str::<Message>(json).unwrap();
1603    }
1604
1605    #[test]
1606    fn de_image() {
1607        let json = r#"{
1608          "message_id": 199791,
1609          "from": {
1610           "id": 250918540,
1611           "is_bot": false,
1612           "first_name": "Андрей",
1613           "last_name": "Власов",
1614           "username": "aka_dude",
1615           "language_code": "en"
1616          },
1617          "chat": {
1618           "id": 250918540,
1619           "first_name": "Андрей",
1620           "last_name": "Власов",
1621           "username": "aka_dude",
1622           "type": "private"
1623          },
1624          "date": 1568290622,
1625          "photo": [
1626           {
1627            "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA20AAybcBAABFgQ",
1628            "file_unique_id":"",
1629            "file_size": 18188,
1630            "width": 320,
1631            "height": 239
1632           },
1633           {
1634            "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3gAAyfcBAABFgQ",
1635            "file_unique_id":"",
1636            "file_size": 62123,
1637            "width": 800,
1638            "height": 598
1639           },
1640           {
1641            "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3kAAyTcBAABFgQ",
1642            "file_unique_id":"",
1643            "file_size": 75245,
1644            "width": 962,
1645            "height": 719
1646           }
1647          ]
1648         }"#;
1649        let message = from_str::<Message>(json);
1650        assert!(message.is_ok());
1651    }
1652
1653    /// Regression test for <https://github.com/teloxide/teloxide/issues/419>
1654    #[test]
1655    fn issue_419() {
1656        let json = r#"{
1657            "message_id": 1,
1658            "from": {
1659                "id": 1087968824,
1660                "is_bot": true,
1661                "first_name": "Group",
1662                "username": "GroupAnonymousBot"
1663            },
1664            "author_signature": "TITLE2",
1665            "sender_chat": {
1666                "id": -1001160242915,
1667                "title": "a",
1668                "type": "supergroup"
1669            },
1670            "chat": {
1671                "id": -1001160242915,
1672                "title": "a",
1673                "type": "supergroup"
1674            },
1675            "date": 1640359576,
1676            "forward_from_chat": {
1677                "id": -1001160242915,
1678                "title": "a",
1679                "type": "supergroup"
1680            },
1681            "forward_signature": "TITLE",
1682            "forward_date": 1640359544,
1683            "text": "text"
1684        }"#;
1685
1686        // Anonymous admin with title "TITLE2" forwards a message from anonymous
1687        // admin with title "TITLE" with text "a", everything is happening in
1688        // the same group.
1689        let message: Message = serde_json::from_str(json).unwrap();
1690
1691        let group = Chat {
1692            id: ChatId(-1001160242915),
1693            kind: ChatKind::Public(ChatPublic {
1694                title: Some("a".to_owned()),
1695                kind: PublicChatKind::Supergroup(PublicChatSupergroup {
1696                    username: None,
1697                    sticker_set_name: None,
1698                    can_set_sticker_set: None,
1699                    permissions: None,
1700                    slow_mode_delay: None,
1701                    linked_chat_id: None,
1702                    location: None,
1703                    join_by_request: None,
1704                    join_to_send_messages: None,
1705                    active_usernames: None,
1706                    is_forum: false,
1707                }),
1708                description: None,
1709                invite_link: None,
1710                has_protected_content: None,
1711            }),
1712            message_auto_delete_time: None,
1713            photo: None,
1714            pinned_message: None,
1715            has_hidden_members: false,
1716            has_aggressive_anti_spam_enabled: false,
1717        };
1718
1719        assert!(message.from().unwrap().is_anonymous());
1720        assert_eq!(message.author_signature().unwrap(), "TITLE2");
1721        assert_eq!(message.sender_chat().unwrap(), &group);
1722        assert_eq!(&message.chat, &group);
1723        assert_eq!(message.forward_from_chat().unwrap(), &group);
1724        assert_eq!(message.forward_signature().unwrap(), "TITLE");
1725        assert!(message.forward_date().is_some());
1726        assert_eq!(message.text().unwrap(), "text");
1727    }
1728
1729    /// Regression test for <https://github.com/teloxide/teloxide/issues/427>
1730    #[test]
1731    fn issue_427() {
1732        let old = ChatId(-599075523);
1733        let new = ChatId(-1001555296434);
1734
1735        // Migration to a supergroup
1736        let json = r#"{"chat":{"all_members_are_administrators":false,"id":-599075523,"title":"test","type":"group"},"date":1629404938,"from":{"first_name":"nullptr","id":729497414,"is_bot":false,"language_code":"en","username":"hex0x0000"},"message_id":16,"migrate_to_chat_id":-1001555296434}"#;
1737        let message: Message = from_str(json).unwrap();
1738
1739        assert_eq!(message.chat.id, old);
1740        assert_eq!(message.chat_migration(), Some(ChatMigration::To { chat_id: new }));
1741        assert_eq!(message.migrate_to_chat_id(), Some(new));
1742
1743        // The user who initialized the migration
1744        assert!(message.from().is_some());
1745
1746        // Migration from a common group
1747        let json = r#"{"chat":{"id":-1001555296434,"title":"test","type":"supergroup"},"date":1629404938,"from":{"first_name":"Group","id":1087968824,"is_bot":true,"username":"GroupAnonymousBot"},"message_id":1,"migrate_from_chat_id":-599075523,"sender_chat":{"id":-1001555296434,"title":"test","type":"supergroup"}}"#;
1748        let message: Message = from_str(json).unwrap();
1749
1750        assert_eq!(message.chat.id, new);
1751        assert_eq!(message.chat_migration(), Some(ChatMigration::From { chat_id: old }));
1752        assert_eq!(message.migrate_from_chat_id(), Some(old));
1753
1754        // Anonymous bot
1755        assert!(message.from().is_some());
1756
1757        // The chat to which the group migrated
1758        assert!(message.sender_chat().is_some());
1759    }
1760
1761    /// Regression test for <https://github.com/teloxide/teloxide/issues/481>
1762    #[test]
1763    fn issue_481() {
1764        let json = r#"
1765{
1766  "message_id": 0,
1767  "date": 0,
1768  "location": {
1769   "latitude": 0.0,
1770   "longitude": 0.0
1771  },
1772  "chat": {
1773   "id": 0,
1774   "first_name": "f",
1775   "type": "private"
1776  },
1777  "venue": {
1778   "location": {
1779    "latitude": 0.0,
1780    "longitude": 0.0
1781   },
1782   "title": "Title",
1783   "address": "Address",
1784   "foursquare_id": "some_foursquare_id"
1785  }
1786 }
1787"#;
1788        let message: Message = from_str(json).unwrap();
1789        assert_eq!(
1790            message.venue().unwrap(),
1791            &Venue {
1792                location: Location {
1793                    longitude: 0.0,
1794                    latitude: 0.0,
1795                    horizontal_accuracy: None,
1796                    live_period: None,
1797                    heading: None,
1798                    proximity_alert_radius: None
1799                },
1800                title: "Title".to_owned(),
1801                address: "Address".to_owned(),
1802                foursquare_id: Some("some_foursquare_id".to_owned()),
1803                foursquare_type: None,
1804                google_place_id: None,
1805                google_place_type: None,
1806            }
1807        )
1808    }
1809
1810    /// Regression test for <https://github.com/teloxide/teloxide/issues/475>
1811    #[test]
1812    fn issue_475() {
1813        let json = r#"{"message_id":198295,"from":{"id":1087968824,"is_bot":true,"first_name":"Group","username":"GroupAnonymousBot"},"sender_chat":{"id":-1001331354980,"title":"C++ Together 2.0","username":"cpptogether","type":"supergroup"},"chat":{"id":-1001331354980,"title":"C++ Together 2.0","username":"cpptogether","type":"supergroup"},"date":1638236631,"video_chat_started":{}}"#;
1814
1815        let message: Message = serde_json::from_str(json).unwrap();
1816
1817        assert!(matches!(message.kind, MessageKind::VideoChatStarted { .. }));
1818
1819        // FIXME(waffle): it seems like we are losing `sender_chat` in some
1820        // cases inclusing this
1821        // assert!(message.sender_chat().is_some());
1822    }
1823
1824    #[test]
1825    fn parse_caption_entities() {
1826        let json = r#"
1827        {
1828            "message_id": 3460,
1829            "from": {
1830              "id": 27433968,
1831              "is_bot": false,
1832              "first_name": "Crax | rats addict",
1833              "username": "tacocrasco",
1834              "language_code": "en"
1835            },
1836            "chat": {
1837              "id": 27433968,
1838              "first_name": "Crax | rats addict",
1839              "username": "tacocrasco",
1840              "type": "private"
1841            },
1842            "date": 1655671349,
1843            "photo": [
1844              {
1845                "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADcwADJAQ",
1846                "file_unique_id": "AQADabwxGxJmgVF4",
1847                "file_size": 2077,
1848                "width": 90,
1849                "height": 90
1850              },
1851              {
1852                "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADbQADJAQ",
1853                "file_unique_id": "AQADabwxGxJmgVFy",
1854                "file_size": 27640,
1855                "width": 320,
1856                "height": 320
1857              },
1858              {
1859                "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADeAADJAQ",
1860                "file_unique_id": "AQADabwxGxJmgVF9",
1861                "file_size": 99248,
1862                "width": 800,
1863                "height": 800
1864              },
1865              {
1866                "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADeQADJAQ",
1867                "file_unique_id": "AQADabwxGxJmgVF-",
1868                "file_size": 162061,
1869                "width": 1280,
1870                "height": 1280
1871              }
1872            ],
1873            "caption": "www.example.com",
1874            "caption_entities": [
1875              {
1876                "offset": 0,
1877                "length": 15,
1878                "type": "url"
1879              }
1880            ]
1881        }"#;
1882
1883        let message: Message = serde_json::from_str(json).unwrap();
1884        let entities = message.parse_caption_entities();
1885        assert!(entities.is_some());
1886
1887        let entities = entities.unwrap();
1888        assert!(!entities.is_empty());
1889        assert_eq!(entities[0].kind().clone(), MessageEntityKind::Url);
1890    }
1891
1892    #[test]
1893    fn topic_created() {
1894        let json = r#"{
1895            "chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},
1896            "date":1675229139,
1897            "forum_topic_created":{
1898                "icon_color":9367192,
1899                "icon_custom_emoji_id":"5312536423851630001",
1900                "name":"???"
1901            },
1902            "from":{
1903                "first_name":"вафель'",
1904                "id":1253681278,
1905                "is_bot":false,
1906                "language_code":"en",
1907                "username":"wafflelapkin"
1908            },
1909            "is_topic_message":true,
1910            "message_id":4,
1911            "message_thread_id":4
1912        }"#;
1913
1914        let _: Message = serde_json::from_str(json).unwrap();
1915    }
1916
1917    #[test]
1918    fn topic_message() {
1919        let json = r#"{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229140,"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":5,"message_thread_id":4,"reply_to_message":{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229139,"forum_topic_created":{"icon_color":9367192,"icon_custom_emoji_id":"5312536423851630001","name":"???"},"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":4,"message_thread_id":4},"text":"blah"}"#;
1920
1921        let _: Message = serde_json::from_str(json).unwrap();
1922    }
1923}