teloxide_core/types/
message.rs

1#![allow(clippy::large_enum_variant)]
2
3use chrono::{DateTime, Utc};
4use derive_more::derive::From;
5use serde::{Deserialize, Serialize};
6use url::Url;
7
8use crate::types::{
9    Animation, Audio, BareChatId, BusinessConnectionId, Chat, ChatBackground, ChatBoostAdded,
10    ChatId, ChatShared, Checklist, ChecklistTasksAdded, ChecklistTasksDone, Contact, Dice,
11    DirectMessagePriceChanged, Document, ExternalReplyInfo, ForumTopicClosed, ForumTopicCreated,
12    ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden, GeneralForumTopicUnhidden,
13    GiftInfo, Giveaway, GiveawayCompleted, GiveawayCreated, GiveawayWinners, InlineKeyboardMarkup,
14    Invoice, LinkPreviewOptions, Location, MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged,
15    MessageEntity, MessageEntityRef, MessageId, MessageOrigin, PaidMediaInfo,
16    PaidMessagePriceChanged, PassportData, PhotoSize, Poll, ProximityAlertTriggered,
17    RefundedPayment, Sticker, Story, SuccessfulPayment, TextQuote, ThreadId, True, UniqueGiftInfo,
18    User, UsersShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited,
19    VideoChatScheduled, VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed,
20};
21
22/// This object represents a message.
23///
24/// [The official docs](https://core.telegram.org/bots/api#message).
25#[serde_with::skip_serializing_none]
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
27pub struct Message {
28    /// Unique message identifier inside this chat.
29    #[serde(flatten)]
30    pub id: MessageId,
31
32    /// Unique identifier of a message thread to which the message belongs; for
33    /// supergroups only.
34    #[serde(rename = "message_thread_id")]
35    pub thread_id: Option<ThreadId>,
36
37    /// Sender, empty for messages sent to channels.
38    pub from: Option<User>,
39
40    /// Sender of the message, sent on behalf of a chat. The channel itself for
41    /// channel messages. The supergroup itself for messages from anonymous
42    /// group administrators. The linked channel for messages automatically
43    /// forwarded to the discussion group
44    pub sender_chat: Option<Chat>,
45
46    /// Date the message was sent in Unix time.
47    #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
48    pub date: DateTime<Utc>,
49
50    /// Conversation the message belongs to.
51    pub chat: Chat,
52
53    /// `true`, if the message is sent to a forum topic.
54    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
55    pub is_topic_message: bool,
56
57    /// Bot through which the message was sent.
58    pub via_bot: Option<User>,
59
60    /// The bot that actually sent the message on behalf of the business
61    /// account. Available only for outgoing messages sent on behalf of the
62    /// connected business account.
63    pub sender_business_bot: Option<User>,
64
65    #[serde(flatten)]
66    pub kind: MessageKind,
67}
68
69// FIXME: this could be a use-case for serde mixed-tags, some variants need to
70//        untagged (`MessageCommon` as an example), while other need to be
71//        tagged (e.g.: Forum*)
72#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
73#[serde(untagged)]
74pub enum MessageKind {
75    Common(MessageCommon),
76    NewChatMembers(MessageNewChatMembers),
77    LeftChatMember(MessageLeftChatMember),
78    NewChatTitle(MessageNewChatTitle),
79    NewChatPhoto(MessageNewChatPhoto),
80    DeleteChatPhoto(MessageDeleteChatPhoto),
81    GroupChatCreated(MessageGroupChatCreated),
82    SupergroupChatCreated(MessageSupergroupChatCreated),
83    ChannelChatCreated(MessageChannelChatCreated),
84    MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged),
85    Pinned(MessagePinned),
86    ChatShared(MessageChatShared),
87    UsersShared(MessageUsersShared),
88    Invoice(MessageInvoice),
89    SuccessfulPayment(MessageSuccessfulPayment),
90    RefundedPayment(MessageRefundedPayment),
91    ConnectedWebsite(MessageConnectedWebsite),
92    WriteAccessAllowed(MessageWriteAccessAllowed),
93    PassportData(MessagePassportData),
94    Dice(MessageDice),
95    ProximityAlertTriggered(MessageProximityAlertTriggered),
96    ChatBoostAdded(MessageChatBoostAdded),
97    ChatBackground(MessageChatBackground),
98    ChecklistTasksDone(MessageChecklistTasksDone),
99    ChecklistTasksAdded(MessageChecklistTasksAdded),
100    DirectMessagePriceChanged(MessageDirectMessagePriceChanged),
101    ForumTopicCreated(MessageForumTopicCreated),
102    ForumTopicEdited(MessageForumTopicEdited),
103    ForumTopicClosed(MessageForumTopicClosed),
104    ForumTopicReopened(MessageForumTopicReopened),
105    GeneralForumTopicHidden(MessageGeneralForumTopicHidden),
106    GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden),
107    Giveaway(MessageGiveaway),
108    GiveawayCompleted(MessageGiveawayCompleted),
109    GiveawayCreated(MessageGiveawayCreated),
110    GiveawayWinners(MessageGiveawayWinners),
111    PaidMessagePriceChanged(MessagePaidMessagePriceChanged),
112    GiftInfo(MessageGiftInfo),
113    UniqueGiftInfo(MessageUniqueGiftInfo),
114    VideoChatScheduled(MessageVideoChatScheduled),
115    VideoChatStarted(MessageVideoChatStarted),
116    VideoChatEnded(MessageVideoChatEnded),
117    VideoChatParticipantsInvited(MessageVideoChatParticipantsInvited),
118    WebAppData(MessageWebAppData),
119    /// An empty, content-less message, that can appear in callback queries
120    /// attached to old messages.
121    Empty {},
122}
123
124/// Unique identifier of the message effect added to the message
125#[derive(
126    Default,
127    Clone,
128    Debug,
129    derive_more::Display,
130    PartialEq,
131    Eq,
132    Hash,
133    Serialize,
134    Deserialize,
135    From
136)]
137#[serde(transparent)]
138#[from(&'static str, String)]
139pub struct EffectId(pub String);
140
141#[serde_with::skip_serializing_none]
142#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
143pub struct MessageCommon {
144    /// Signature of the post author for messages in channels, or the custom
145    /// title of an anonymous group administrator.
146    pub author_signature: Option<String>,
147
148    /// The number of Telegram Stars that were paid by the sender of the message
149    /// to send it
150    pub paid_star_count: Option<u32>,
151
152    /// Unique identifier of the message effect added to the message
153    pub effect_id: Option<EffectId>,
154
155    /// Information about the original message for forwarded messages
156    pub forward_origin: Option<MessageOrigin>,
157
158    /// For replies, the original message. Note that the Message object in this
159    /// field will not contain further `reply_to_message` fields even if it
160    /// itself is a reply.
161    pub reply_to_message: Option<Box<Message>>,
162
163    /// Information about the message that is being replied to, which may come
164    /// from another chat or forum topic
165    pub external_reply: Option<ExternalReplyInfo>,
166
167    /// For replies that quote part of the original message, the quoted part of
168    /// the message
169    pub quote: Option<TextQuote>,
170
171    /// For replies to a story, the original story
172    pub reply_to_story: Option<Story>,
173
174    /// If the sender of the message boosted the chat, the number of boosts
175    /// added by the user
176    pub sender_boost_count: Option<u16>,
177
178    /// Date the message was last edited in Unix time.
179    #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")]
180    pub edit_date: Option<DateTime<Utc>>,
181
182    #[serde(flatten)]
183    pub media_kind: MediaKind,
184
185    /// Inline keyboard attached to the message. `login_url` buttons are
186    /// represented as ordinary `url` buttons.
187    pub reply_markup: Option<InlineKeyboardMarkup>,
188
189    /// `true`, if the message is a channel post that was automatically
190    /// forwarded to the connected discussion group.
191    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
192    pub is_automatic_forward: bool,
193
194    /// `true`, if the message can't be forwarded.
195    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
196    pub has_protected_content: bool,
197
198    /// `true`, if the message was sent by an implicit action, for example, as
199    /// an away or a greeting business message, or as a scheduled message
200    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
201    pub is_from_offline: bool,
202
203    /// Unique identifier of the business connection from which the message was
204    /// received. If non-empty, the message belongs to a chat of the
205    /// corresponding business account that is independent from any potential
206    /// bot chat which might share the same identifier.
207    pub business_connection_id: Option<BusinessConnectionId>,
208}
209
210#[serde_with::skip_serializing_none]
211#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
212pub struct MessageNewChatMembers {
213    /// New members that were added to the group or supergroup and
214    /// information about them (the bot itself may be one of these
215    /// members).
216    pub new_chat_members: Vec<User>,
217}
218
219#[serde_with::skip_serializing_none]
220#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
221pub struct MessageLeftChatMember {
222    /// A member was removed from the group, information about them (this
223    /// member may be the bot itself).
224    pub left_chat_member: User,
225}
226
227#[serde_with::skip_serializing_none]
228#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
229pub struct MessageNewChatTitle {
230    /// A chat title was changed to this value.
231    pub new_chat_title: String,
232}
233
234#[serde_with::skip_serializing_none]
235#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
236pub struct MessageNewChatPhoto {
237    /// A chat photo was change to this value.
238    pub new_chat_photo: Vec<PhotoSize>,
239}
240
241#[serde_with::skip_serializing_none]
242#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
243pub struct MessageDeleteChatPhoto {
244    /// Service message: the chat photo was deleted.
245    pub delete_chat_photo: True,
246}
247
248#[serde_with::skip_serializing_none]
249#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
250pub struct MessageGroupChatCreated {
251    /// Service message: the group has been created.
252    pub group_chat_created: True,
253}
254
255#[serde_with::skip_serializing_none]
256#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
257pub struct MessageSupergroupChatCreated {
258    /// Service message: the supergroup has been created. This field can‘t
259    /// be received in a message coming through updates, because bot can’t
260    /// be a member of a supergroup when it is created. It can only be
261    /// found in `reply_to_message` if someone replies to a very first
262    /// message in a directly created supergroup.
263    pub supergroup_chat_created: True,
264}
265
266#[serde_with::skip_serializing_none]
267#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
268pub struct MessageChannelChatCreated {
269    /// Service message: the channel has been created. This field can‘t be
270    /// received in a message coming through updates, because bot can’t be
271    /// a member of a channel when it is created. It can only be found in
272    /// `reply_to_message` if someone replies to a very first message in a
273    /// channel.
274    pub channel_chat_created: True,
275}
276
277#[serde_with::skip_serializing_none]
278#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
279pub struct MessageMessageAutoDeleteTimerChanged {
280    /// Service message: auto-delete timer settings changed in the chat.
281    pub message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged,
282}
283
284/// Represents group migration to a supergroup or a supergroup migration from a
285/// group.
286///
287/// Note that bot receives **both** updates. For example: a group with id `0`
288/// migrates to a supergroup with id `1` bots in that group will receive 2
289/// updates:
290/// - `message.chat.id = 0`, `message.chat_migration() = ChatMigration::To {
291///   chat_id: 1 }`
292/// - `message.chat.id = 1`, `message.chat_migration() = ChatMigration::From {
293///   chat_id: 0 }`
294#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
295#[serde(untagged)]
296pub enum ChatMigration {
297    /// The group has been migrated to a supergroup with the specified
298    /// identifier `chat_id`.
299    To {
300        #[serde(rename = "migrate_to_chat_id")]
301        chat_id: ChatId,
302    },
303
304    /// The supergroup has been migrated from a group with the specified
305    /// identifier `chat_id`.
306    From {
307        #[serde(rename = "migrate_from_chat_id")]
308        chat_id: ChatId,
309    },
310}
311
312#[serde_with::skip_serializing_none]
313#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
314pub struct MessagePinned {
315    /// Specified message was pinned. Note that the Message object in this
316    /// field will not contain further `reply_to_message` fields even if it
317    /// is itself a reply.
318    #[serde(rename = "pinned_message")]
319    pub pinned: Box<MaybeInaccessibleMessage>,
320}
321
322#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
323pub struct MessageChatShared {
324    /// A chat was shared with the bot.
325    pub chat_shared: ChatShared,
326}
327
328#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
329pub struct MessageUsersShared {
330    /// Users were shared with the bot
331    pub users_shared: UsersShared,
332}
333
334#[serde_with::skip_serializing_none]
335#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
336pub struct MessageInvoice {
337    /// Message is an invoice for a [payment], information about the
338    /// invoice. [More about payments »].
339    ///
340    /// [payment]: https://core.telegram.org/bots/api#payments
341    /// [More about payments »]: https://core.telegram.org/bots/api#payments
342    pub invoice: Invoice,
343}
344
345#[serde_with::skip_serializing_none]
346#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
347pub struct MessageRefundedPayment {
348    /// Message is a service message about a successful payment, information
349    /// about the payment. [More about payments »].
350    ///
351    /// [More about payments »]: https://core.telegram.org/bots/api#payments
352    pub refunded_payment: RefundedPayment,
353}
354
355#[serde_with::skip_serializing_none]
356#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
357pub struct MessageSuccessfulPayment {
358    /// Message is a service message about a successful payment,
359    /// information about the payment. [More about payments »].
360    ///
361    /// [More about payments »]: https://core.telegram.org/bots/api#payments
362    pub successful_payment: SuccessfulPayment,
363}
364
365#[serde_with::skip_serializing_none]
366#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
367pub struct MessageConnectedWebsite {
368    /// The domain name of the website on which the user has logged in.
369    /// [More about Telegram Login »].
370    ///
371    /// [More about Telegram Login »]: https://core.telegram.org/widgets/login
372    pub connected_website: String,
373}
374
375#[serde_with::skip_serializing_none]
376#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
377pub struct MessagePassportData {
378    /// Telegram Passport data.
379    pub passport_data: PassportData,
380}
381
382#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
383#[serde(untagged)]
384pub enum MediaKind {
385    // Note:
386    // - `Venue` must be in front of `Location`
387    // - `Animation` must be in front of `Document`
388    //
389    // This is needed so serde doesn't parse `Venue` as `Location` or `Animation` as `Document`
390    // (for backward compatability telegram duplicates some fields)
391    //
392    // See <https://github.com/teloxide/teloxide/issues/481>
393    Animation(MediaAnimation),
394    Audio(MediaAudio),
395    Contact(MediaContact),
396    Document(MediaDocument),
397    PaidMedia(MediaPaid),
398    Game(MediaGame),
399    Venue(MediaVenue),
400    Location(MediaLocation),
401    Photo(MediaPhoto),
402    Poll(MediaPoll),
403    Checklist(MediaChecklist),
404    Sticker(MediaSticker),
405    Story(MediaStory),
406    Text(MediaText),
407    Video(MediaVideo),
408    VideoNote(MediaVideoNote),
409    Voice(MediaVoice),
410    Migration(ChatMigration),
411}
412
413#[serde_with::skip_serializing_none]
414#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
415pub struct MediaAnimation {
416    /// Message is an animation, information about the animation. For
417    /// backward compatibility, when this field is set, the document field
418    /// will also be set.
419    pub animation: Animation,
420
421    /// Caption for the animation, 0-1024 characters.
422    pub caption: Option<String>,
423
424    /// For messages with a caption, special entities like usernames, URLs,
425    /// bot commands, etc. that appear in the caption.
426    #[serde(default, skip_serializing_if = "Vec::is_empty")]
427    pub caption_entities: Vec<MessageEntity>,
428
429    /// `true`, if the caption must be shown above the message media.
430    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
431    pub show_caption_above_media: bool,
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    // Note: for backward compatibility telegram also sends `document` field, but we ignore it
437}
438
439/// The unique identifier of a media message group the message belongs to.
440#[derive(
441    Default,
442    Clone,
443    Debug,
444    derive_more::Display,
445    PartialEq,
446    Eq,
447    Hash,
448    Serialize,
449    Deserialize,
450    From
451)]
452#[serde(transparent)]
453#[from(&'static str, String)]
454pub struct MediaGroupId(pub String);
455
456#[serde_with::skip_serializing_none]
457#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
458pub struct MediaAudio {
459    /// Message is an audio file, information about the file.
460    pub audio: Audio,
461
462    /// Caption for the audio, 0-1024 characters.
463    pub caption: Option<String>,
464
465    /// For messages with a caption, special entities like usernames, URLs,
466    /// bot commands, etc. that appear in the caption.
467    #[serde(default, skip_serializing_if = "Vec::is_empty")]
468    pub caption_entities: Vec<MessageEntity>,
469
470    /// The unique identifier of a media message group this message belongs
471    /// to.
472    pub media_group_id: Option<MediaGroupId>,
473}
474
475#[serde_with::skip_serializing_none]
476#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
477pub struct MediaContact {
478    /// Message is a shared contact, information about the contact.
479    pub contact: Contact,
480}
481
482#[serde_with::skip_serializing_none]
483#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
484pub struct MediaDocument {
485    /// Message is a general file, information about the file.
486    pub document: Document,
487
488    /// Caption for the document, 0-1024 characters.
489    pub caption: Option<String>,
490
491    /// For messages with a caption, special entities like usernames, URLs,
492    /// bot commands, etc. that appear in the caption.
493    #[serde(default, skip_serializing_if = "Vec::is_empty")]
494    pub caption_entities: Vec<MessageEntity>,
495
496    /// The unique identifier of a media message group this message belongs
497    /// to.
498    pub media_group_id: Option<MediaGroupId>,
499}
500
501#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
502pub struct MediaPaid {
503    /// Message contains paid media; information about the paid media.
504    pub paid_media: PaidMediaInfo,
505}
506
507#[serde_with::skip_serializing_none]
508#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
509pub struct MediaGame {
510    /// Message is a game, information about the game. [More
511    /// about games »].
512    ///
513    /// [More about games »]: https://core.telegram.org/bots/api#games
514    pub game: Game,
515}
516
517#[serde_with::skip_serializing_none]
518#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
519pub struct MediaLocation {
520    /// Message is a shared location, information about the location.
521    pub location: Location,
522}
523
524#[serde_with::skip_serializing_none]
525#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
526pub struct MediaPhoto {
527    /// Message is a photo, available sizes of the photo.
528    pub photo: Vec<PhotoSize>,
529
530    /// Caption for the photo, 0-1024 characters.
531    pub caption: Option<String>,
532
533    /// For messages with a caption, special entities like usernames, URLs,
534    /// bot commands, etc. that appear in the caption.
535    #[serde(default, skip_serializing_if = "Vec::is_empty")]
536    pub caption_entities: Vec<MessageEntity>,
537
538    /// `true`, if the caption must be shown above the message media.
539    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
540    pub show_caption_above_media: bool,
541
542    /// `true`, if the message media is covered by a spoiler animation.
543    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
544    pub has_media_spoiler: bool,
545
546    /// The unique identifier of a media message group this message belongs
547    /// to.
548    pub media_group_id: Option<MediaGroupId>,
549}
550
551#[serde_with::skip_serializing_none]
552#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
553pub struct MediaPoll {
554    /// Message is a native poll, information about the poll.
555    pub poll: Poll,
556}
557
558#[serde_with::skip_serializing_none]
559#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
560pub struct MediaChecklist {
561    /// Message is a checklist, information about the checklist.
562    pub checklist: Checklist,
563}
564
565#[serde_with::skip_serializing_none]
566#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
567pub struct MediaSticker {
568    /// Message is a sticker, information about the sticker.
569    pub sticker: Sticker,
570}
571
572#[serde_with::skip_serializing_none]
573#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
574pub struct MediaStory {
575    /// Message is a forwarded story
576    pub story: Story,
577}
578
579#[serde_with::skip_serializing_none]
580#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
581pub struct MediaText {
582    /// For text messages, the actual UTF-8 text of the message, 0-4096
583    /// characters.
584    pub text: String,
585
586    /// For text messages, special entities like usernames, URLs, bot
587    /// commands, etc. that appear in the text.
588    #[serde(default, skip_serializing_if = "Vec::is_empty")]
589    pub entities: Vec<MessageEntity>,
590
591    /// Options used for link preview generation for the message, if it is a
592    /// text message and link preview options were changed
593    pub link_preview_options: Option<LinkPreviewOptions>,
594}
595
596#[serde_with::skip_serializing_none]
597#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
598pub struct MediaVideo {
599    /// Message is a video, information about the video.
600    pub video: Video,
601
602    /// Caption for the video, 0-1024 characters.
603    pub caption: Option<String>,
604
605    /// For messages with a caption, special entities like usernames, URLs,
606    /// bot commands, etc. that appear in the caption.
607    #[serde(default, skip_serializing_if = "Vec::is_empty")]
608    pub caption_entities: Vec<MessageEntity>,
609
610    /// `true`, if the caption must be shown above the message media.
611    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
612    pub show_caption_above_media: bool,
613
614    /// `true`, if the message media is covered by a spoiler animation.
615    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
616    pub has_media_spoiler: bool,
617
618    /// The unique identifier of a media message group this message belongs
619    /// to.
620    pub media_group_id: Option<MediaGroupId>,
621}
622
623#[serde_with::skip_serializing_none]
624#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
625pub struct MediaVideoNote {
626    /// Message is a [video note], information about the video message.
627    ///
628    /// [video note]: https://telegram.org/blog/video-messages-and-telescope
629    pub video_note: VideoNote,
630}
631
632#[serde_with::skip_serializing_none]
633#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
634pub struct MediaVoice {
635    /// Message is a voice message, information about the file.
636    pub voice: Voice,
637
638    /// Caption for the voice, 0-1024 characters.
639    pub caption: Option<String>,
640
641    /// For messages with a caption, special entities like usernames, URLs,
642    /// bot commands, etc. that appear in the caption.
643    #[serde(default, skip_serializing_if = "Vec::is_empty")]
644    pub caption_entities: Vec<MessageEntity>,
645}
646
647#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
648pub struct MediaVenue {
649    /// Message is a venue, information about the venue.
650    pub venue: Venue,
651    // Note: for backward compatibility telegram also sends `location` field, but we ignore it
652}
653
654#[serde_with::skip_serializing_none]
655#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
656pub struct MessageDice {
657    /// Message is a dice with random value from 1 to 6.
658    pub dice: Dice,
659}
660
661#[serde_with::skip_serializing_none]
662#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
663pub struct MessageProximityAlertTriggered {
664    /// Service message. A user in the chat triggered another user's proximity
665    /// alert while sharing Live Location.
666    pub proximity_alert_triggered: ProximityAlertTriggered,
667}
668
669#[serde_with::skip_serializing_none]
670#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
671pub struct MessageChatBoostAdded {
672    /// Service message. User boosted the chat.
673    pub boost_added: ChatBoostAdded,
674}
675
676#[serde_with::skip_serializing_none]
677#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
678pub struct MessageChatBackground {
679    /// Service message. Chat background set.
680    pub chat_background_set: ChatBackground,
681}
682
683#[serde_with::skip_serializing_none]
684#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
685pub struct MessageChecklistTasksDone {
686    /// Service message: some tasks in a checklist were marked as done or not
687    /// done.
688    pub checklist_tasks_done: ChecklistTasksDone,
689}
690
691#[serde_with::skip_serializing_none]
692#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
693pub struct MessageChecklistTasksAdded {
694    /// Service message: tasks were added to a checklist.
695    pub checklist_tasks_added: ChecklistTasksAdded,
696}
697
698#[serde_with::skip_serializing_none]
699#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
700pub struct MessageDirectMessagePriceChanged {
701    /// Service message: the price for paid messages in the corresponding direct
702    /// messages chat of a channel has changed.
703    pub direct_message_price_changed: DirectMessagePriceChanged,
704}
705
706#[serde_with::skip_serializing_none]
707#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
708pub struct MessageWriteAccessAllowed {
709    /// Service message: the user allowed the bot added to the attachment menu
710    /// to write messages.
711    pub write_access_allowed: WriteAccessAllowed,
712}
713
714#[serde_with::skip_serializing_none]
715#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
716pub struct MessageForumTopicCreated {
717    /// Service message: forum topic created.
718    pub forum_topic_created: ForumTopicCreated,
719}
720
721#[serde_with::skip_serializing_none]
722#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
723pub struct MessageForumTopicEdited {
724    /// Service message: forum topic edited.
725    pub forum_topic_edited: ForumTopicEdited,
726}
727
728#[serde_with::skip_serializing_none]
729#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
730pub struct MessageForumTopicClosed {
731    /// Service message: forum topic closed.
732    pub forum_topic_closed: ForumTopicClosed,
733}
734
735#[serde_with::skip_serializing_none]
736#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
737pub struct MessageForumTopicReopened {
738    /// Service message: forum topic reopened.
739    pub forum_topic_reopened: ForumTopicReopened,
740}
741
742#[serde_with::skip_serializing_none]
743#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
744pub struct MessageGeneralForumTopicHidden {
745    /// Service message: the 'General' forum topic hidden.
746    pub general_forum_topic_hidden: GeneralForumTopicHidden,
747}
748
749#[serde_with::skip_serializing_none]
750#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
751pub struct MessageGeneralForumTopicUnhidden {
752    /// Service message: the 'General' forum topic unhidden.
753    pub general_forum_topic_unhidden: GeneralForumTopicUnhidden,
754}
755
756#[serde_with::skip_serializing_none]
757#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
758pub struct MessageGiveaway {
759    /// Message is giveaway, information about a scheduled giveaway. [More about
760    /// giveaways »]
761    ///
762    /// [More about giveaways »]: https://core.telegram.org/api#giveaways-amp-gifts
763    pub giveaway: Giveaway,
764}
765
766#[serde_with::skip_serializing_none]
767#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
768pub struct MessageGiveawayCompleted {
769    /// Service message: a 'Giveaway' completed. [More about giveaways
770    /// »]
771    ///
772    /// [More about giveaways »]: https://core.telegram.org/api#giveaways-amp-gifts
773    pub giveaway_completed: GiveawayCompleted,
774}
775
776#[serde_with::skip_serializing_none]
777#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
778pub struct MessageGiveawayCreated {
779    /// Service message: a scheduled 'Giveaway' created. [More about giveaways
780    /// »]
781    ///
782    /// [More about giveaways »]: https://core.telegram.org/api#giveaways-amp-gifts
783    pub giveaway_created: GiveawayCreated,
784}
785
786#[serde_with::skip_serializing_none]
787#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
788pub struct MessageGiveawayWinners {
789    /// Message is giveaway winners, information about the completion of a
790    /// giveaway with public winners. [More about giveaways »]
791    ///
792    /// [More about giveaways »]: https://core.telegram.org/api#giveaways-amp-gifts
793    pub giveaway_winners: GiveawayWinners,
794}
795
796#[serde_with::skip_serializing_none]
797#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
798pub struct MessagePaidMessagePriceChanged {
799    /// Service message: the price for paid messages has changed in the chat
800    pub paid_message_price_changed: PaidMessagePriceChanged,
801}
802
803#[serde_with::skip_serializing_none]
804#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
805pub struct MessageGiftInfo {
806    /// Service message: a regular gift was sent or received
807    pub gift: GiftInfo,
808}
809
810#[serde_with::skip_serializing_none]
811#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
812pub struct MessageUniqueGiftInfo {
813    /// Service message: a unique gift was sent or received
814    pub unique_gift: UniqueGiftInfo,
815}
816
817#[serde_with::skip_serializing_none]
818#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
819pub struct MessageVideoChatScheduled {
820    /// Service message: video chat scheduled
821    pub video_chat_scheduled: VideoChatScheduled,
822}
823
824#[serde_with::skip_serializing_none]
825#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
826pub struct MessageVideoChatStarted {
827    /// Service message: video chat started.
828    pub video_chat_started: VideoChatStarted,
829}
830
831#[serde_with::skip_serializing_none]
832#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
833pub struct MessageVideoChatEnded {
834    /// Service message: video chat ended.
835    pub video_chat_ended: VideoChatEnded,
836}
837
838#[serde_with::skip_serializing_none]
839#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
840pub struct MessageVideoChatParticipantsInvited {
841    /// Service message: new participants invited to a video chat.
842    pub video_chat_participants_invited: VideoChatParticipantsInvited,
843}
844
845#[serde_with::skip_serializing_none]
846#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
847pub struct MessageWebAppData {
848    /// Service message: data sent by a Web App.
849    pub web_app_data: WebAppData,
850}
851
852mod getters {
853    use chrono::{DateTime, Utc};
854    use std::ops::Deref;
855
856    use crate::types::{
857        self, message::MessageKind::*, Chat, ChatId, ChatMigration, EffectId, LinkPreviewOptions,
858        MaybeInaccessibleMessage, MediaAnimation, MediaAudio, MediaChecklist, MediaContact,
859        MediaDocument, MediaGame, MediaKind, MediaLocation, MediaPaid, MediaPhoto, MediaPoll,
860        MediaSticker, MediaStory, MediaText, MediaVenue, MediaVideo, MediaVideoNote, MediaVoice,
861        Message, MessageChannelChatCreated, MessageChatShared, MessageChecklistTasksAdded,
862        MessageChecklistTasksDone, MessageCommon, MessageConnectedWebsite, MessageDeleteChatPhoto,
863        MessageDice, MessageDirectMessagePriceChanged, MessageEntity, MessageGroupChatCreated,
864        MessageId, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers,
865        MessageNewChatPhoto, MessageNewChatTitle, MessageOrigin, MessagePassportData,
866        MessagePinned, MessageProximityAlertTriggered, MessageSuccessfulPayment,
867        MessageSupergroupChatCreated, MessageUsersShared, MessageVideoChatParticipantsInvited,
868        PhotoSize, Story, TextQuote, User,
869    };
870
871    use super::{
872        MediaGroupId, MessageChatBackground, MessageChatBoostAdded, MessageForumTopicClosed,
873        MessageForumTopicCreated, MessageForumTopicEdited, MessageForumTopicReopened,
874        MessageGeneralForumTopicHidden, MessageGeneralForumTopicUnhidden, MessageGiftInfo,
875        MessageGiveaway, MessageGiveawayCompleted, MessageGiveawayCreated, MessageGiveawayWinners,
876        MessageMessageAutoDeleteTimerChanged, MessagePaidMessagePriceChanged,
877        MessageUniqueGiftInfo, MessageVideoChatEnded, MessageVideoChatScheduled,
878        MessageVideoChatStarted, MessageWebAppData, MessageWriteAccessAllowed,
879    };
880
881    /// Getters for [Message] fields from [telegram docs].
882    ///
883    /// [Message]: crate::types::Message
884    /// [telegram docs]: https://core.telegram.org/bots/api#message
885    impl Message {
886        /// Returns the user who sent the message.
887        #[deprecated(since = "0.13.0", note = "use `.from` field instead")]
888        #[must_use]
889        pub fn from(&self) -> Option<&User> {
890            self.from.as_ref()
891        }
892
893        #[must_use]
894        pub fn author_signature(&self) -> Option<&str> {
895            match &self.kind {
896                Common(MessageCommon { author_signature, .. }) => author_signature.as_deref(),
897                _ => None,
898            }
899        }
900
901        #[must_use]
902        pub fn effect_id(&self) -> Option<&EffectId> {
903            match &self.kind {
904                Common(MessageCommon { effect_id, .. }) => effect_id.as_ref(),
905                _ => None,
906            }
907        }
908
909        #[deprecated(since = "0.13.0", note = "use `.sender_chat` field instead")]
910        #[must_use]
911        pub fn sender_chat(&self) -> Option<&Chat> {
912            self.sender_chat.as_ref()
913        }
914
915        #[must_use]
916        pub fn forward_origin(&self) -> Option<&MessageOrigin> {
917            match &self.kind {
918                Common(MessageCommon { forward_origin, .. }) => forward_origin.as_ref(),
919                _ => None,
920            }
921        }
922
923        #[must_use]
924        pub fn quote(&self) -> Option<&TextQuote> {
925            match &self.kind {
926                Common(MessageCommon { quote, .. }) => quote.as_ref(),
927                _ => None,
928            }
929        }
930
931        #[must_use]
932        pub fn reply_to_story(&self) -> Option<&Story> {
933            match &self.kind {
934                Common(MessageCommon { reply_to_story, .. }) => reply_to_story.as_ref(),
935                _ => None,
936            }
937        }
938
939        #[must_use]
940        pub fn sender_boost_count(&self) -> Option<u16> {
941            match &self.kind {
942                Common(MessageCommon { sender_boost_count, .. }) => *sender_boost_count,
943                _ => None,
944            }
945        }
946
947        #[must_use]
948        pub fn forward_date(&self) -> Option<DateTime<Utc>> {
949            self.forward_origin().map(|f| f.date())
950        }
951
952        #[must_use]
953        pub fn forward_from_user(&self) -> Option<&User> {
954            self.forward_origin().and_then(|origin| match origin {
955                MessageOrigin::User { sender_user, .. } => Some(sender_user),
956                _ => None,
957            })
958        }
959
960        #[must_use]
961        pub fn forward_from_chat(&self) -> Option<&Chat> {
962            self.forward_origin().and_then(|origin| match origin {
963                MessageOrigin::Chat { sender_chat, .. } => Some(sender_chat),
964                _ => None,
965            })
966        }
967
968        #[must_use]
969        pub fn forward_from_sender_name(&self) -> Option<&str> {
970            self.forward_origin().and_then(|origin| match origin {
971                MessageOrigin::HiddenUser { sender_user_name, .. } => {
972                    Some(sender_user_name.as_str())
973                }
974                _ => None,
975            })
976        }
977
978        #[must_use]
979        pub fn forward_from_message_id(&self) -> Option<MessageId> {
980            self.forward_origin().and_then(|origin| match origin {
981                MessageOrigin::Channel { message_id, .. } => Some(*message_id),
982                _ => None,
983            })
984        }
985
986        #[must_use]
987        pub fn forward_author_signature(&self) -> Option<&str> {
988            self.forward_origin().and_then(|origin| match origin {
989                MessageOrigin::Chat { author_signature, .. } => {
990                    author_signature.as_ref().map(|a| a.as_str())
991                }
992                MessageOrigin::Channel { author_signature, .. } => {
993                    author_signature.as_ref().map(|a| a.as_str())
994                }
995                _ => None,
996            })
997        }
998
999        #[must_use]
1000        pub fn reply_to_message(&self) -> Option<&Message> {
1001            self.common().and_then(|m| m.reply_to_message.as_deref())
1002        }
1003
1004        #[must_use]
1005        pub fn edit_date(&self) -> Option<&DateTime<Utc>> {
1006            match &self.kind {
1007                Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(),
1008                _ => None,
1009            }
1010        }
1011
1012        #[must_use]
1013        pub fn media_group_id(&self) -> Option<&MediaGroupId> {
1014            match &self.kind {
1015                Common(MessageCommon {
1016                    media_kind: MediaKind::Video(MediaVideo { media_group_id, .. }),
1017                    ..
1018                })
1019                | Common(MessageCommon {
1020                    media_kind: MediaKind::Photo(MediaPhoto { media_group_id, .. }),
1021                    ..
1022                })
1023                | Common(MessageCommon {
1024                    media_kind: MediaKind::Document(MediaDocument { media_group_id, .. }),
1025                    ..
1026                })
1027                | Common(MessageCommon {
1028                    media_kind: MediaKind::Audio(MediaAudio { media_group_id, .. }),
1029                    ..
1030                }) => media_group_id.as_ref(),
1031                _ => None,
1032            }
1033        }
1034
1035        #[must_use]
1036        pub fn text(&self) -> Option<&str> {
1037            match &self.kind {
1038                Common(MessageCommon {
1039                    media_kind: MediaKind::Text(MediaText { text, .. }),
1040                    ..
1041                }) => Some(text),
1042                _ => None,
1043            }
1044        }
1045
1046        /// Returns message entities that represent text formatting.
1047        ///
1048        /// **Note:** you probably want to use [`parse_entities`] instead.
1049        ///
1050        /// This function returns `Some(entities)` for **text messages** and
1051        /// `None` for all other kinds of messages (including photos with
1052        /// captions).
1053        ///
1054        /// See also: [`caption_entities`].
1055        ///
1056        /// [`parse_entities`]: Message::parse_entities
1057        /// [`caption_entities`]: Message::caption_entities
1058        #[must_use]
1059        pub fn entities(&self) -> Option<&[MessageEntity]> {
1060            match &self.kind {
1061                Common(MessageCommon {
1062                    media_kind: MediaKind::Text(MediaText { entities, .. }),
1063                    ..
1064                }) => Some(entities),
1065                _ => None,
1066            }
1067        }
1068
1069        #[must_use]
1070        pub fn link_preview_options(&self) -> Option<&LinkPreviewOptions> {
1071            match &self.kind {
1072                Common(MessageCommon {
1073                    media_kind: MediaKind::Text(MediaText { link_preview_options, .. }),
1074                    ..
1075                }) => link_preview_options.as_ref(),
1076                _ => None,
1077            }
1078        }
1079
1080        /// Returns message entities that represent text formatting.
1081        ///
1082        /// **Note:** you probably want to use [`parse_caption_entities`]
1083        /// instead.
1084        ///
1085        /// This function returns `Some(entities)` for **media messages** and
1086        /// `None` for all other kinds of messages (including text messages).
1087        ///
1088        /// See also: [`entities`].
1089        ///
1090        /// [`parse_caption_entities`]: Message::parse_caption_entities
1091        /// [`entities`]: Message::entities
1092        #[must_use]
1093        pub fn caption_entities(&self) -> Option<&[MessageEntity]> {
1094            match &self.kind {
1095                Common(MessageCommon {
1096                    media_kind: MediaKind::Animation(MediaAnimation { caption_entities, .. }),
1097                    ..
1098                })
1099                | Common(MessageCommon {
1100                    media_kind: MediaKind::Audio(MediaAudio { caption_entities, .. }),
1101                    ..
1102                })
1103                | Common(MessageCommon {
1104                    media_kind: MediaKind::Document(MediaDocument { caption_entities, .. }),
1105                    ..
1106                })
1107                | Common(MessageCommon {
1108                    media_kind: MediaKind::Photo(MediaPhoto { caption_entities, .. }),
1109                    ..
1110                })
1111                | Common(MessageCommon {
1112                    media_kind: MediaKind::Video(MediaVideo { caption_entities, .. }),
1113                    ..
1114                })
1115                | Common(MessageCommon {
1116                    media_kind: MediaKind::Voice(MediaVoice { caption_entities, .. }),
1117                    ..
1118                }) => Some(caption_entities),
1119                _ => None,
1120            }
1121        }
1122
1123        /// Returns `true` if the caption must be shown above the message media.
1124        ///
1125        /// Getter for [`MediaPhoto::show_caption_above_media`],
1126        /// [`MediaVideo::show_caption_above_media`] and
1127        /// [`MediaAnimation::show_caption_above_media`].
1128        #[must_use]
1129        pub fn show_caption_above_media(&self) -> bool {
1130            self.common()
1131                .map(|m| match m.media_kind {
1132                    MediaKind::Animation(MediaAnimation { show_caption_above_media, .. })
1133                    | MediaKind::Photo(MediaPhoto { show_caption_above_media, .. })
1134                    | MediaKind::Video(MediaVideo { show_caption_above_media, .. }) => {
1135                        show_caption_above_media
1136                    }
1137                    MediaKind::Audio(_)
1138                    | MediaKind::Contact(_)
1139                    | MediaKind::Document(_)
1140                    | MediaKind::PaidMedia(_)
1141                    | MediaKind::Game(_)
1142                    | MediaKind::Venue(_)
1143                    | MediaKind::Location(_)
1144                    | MediaKind::Poll(_)
1145                    | MediaKind::Checklist(_)
1146                    | MediaKind::Sticker(_)
1147                    | MediaKind::Story(_)
1148                    | MediaKind::Text(_)
1149                    | MediaKind::VideoNote(_)
1150                    | MediaKind::Voice(_)
1151                    | MediaKind::Migration(_) => false,
1152                })
1153                .unwrap_or(false)
1154        }
1155
1156        /// Returns `true` if the message media is covered by a spoiler
1157        /// animation.
1158        ///
1159        /// Getter for [`MediaPhoto::has_media_spoiler`],
1160        /// [`MediaVideo::has_media_spoiler`] and
1161        /// [`MediaAnimation::has_media_spoiler`].
1162        #[must_use]
1163        pub fn has_media_spoiler(&self) -> bool {
1164            self.common()
1165                .map(|m| match m.media_kind {
1166                    MediaKind::Animation(MediaAnimation { has_media_spoiler, .. })
1167                    | MediaKind::Photo(MediaPhoto { has_media_spoiler, .. })
1168                    | MediaKind::Video(MediaVideo { has_media_spoiler, .. }) => has_media_spoiler,
1169                    MediaKind::Audio(_)
1170                    | MediaKind::Contact(_)
1171                    | MediaKind::Document(_)
1172                    | MediaKind::PaidMedia(_)
1173                    | MediaKind::Game(_)
1174                    | MediaKind::Venue(_)
1175                    | MediaKind::Location(_)
1176                    | MediaKind::Poll(_)
1177                    | MediaKind::Checklist(_)
1178                    | MediaKind::Sticker(_)
1179                    | MediaKind::Story(_)
1180                    | MediaKind::Text(_)
1181                    | MediaKind::VideoNote(_)
1182                    | MediaKind::Voice(_)
1183                    | MediaKind::Migration(_) => false,
1184                })
1185                .unwrap_or(false)
1186        }
1187
1188        #[must_use]
1189        pub fn audio(&self) -> Option<&types::Audio> {
1190            match &self.kind {
1191                Common(MessageCommon {
1192                    media_kind: MediaKind::Audio(MediaAudio { audio, .. }),
1193                    ..
1194                }) => Some(audio),
1195                _ => None,
1196            }
1197        }
1198
1199        #[must_use]
1200        pub fn document(&self) -> Option<&types::Document> {
1201            match &self.kind {
1202                Common(MessageCommon {
1203                    media_kind: MediaKind::Document(MediaDocument { document, .. }),
1204                    ..
1205                }) => Some(document),
1206                _ => None,
1207            }
1208        }
1209
1210        #[must_use]
1211        pub fn paid_media(&self) -> Option<&types::PaidMediaInfo> {
1212            match &self.kind {
1213                Common(MessageCommon {
1214                    media_kind: MediaKind::PaidMedia(MediaPaid { paid_media, .. }),
1215                    ..
1216                }) => Some(paid_media),
1217                _ => None,
1218            }
1219        }
1220
1221        #[must_use]
1222        pub fn animation(&self) -> Option<&types::Animation> {
1223            match &self.kind {
1224                Common(MessageCommon {
1225                    media_kind: MediaKind::Animation(MediaAnimation { animation, .. }),
1226                    ..
1227                }) => Some(animation),
1228                _ => None,
1229            }
1230        }
1231
1232        #[must_use]
1233        pub fn game(&self) -> Option<&types::Game> {
1234            match &self.kind {
1235                Common(MessageCommon {
1236                    media_kind: MediaKind::Game(MediaGame { game, .. }),
1237                    ..
1238                }) => Some(game),
1239                _ => None,
1240            }
1241        }
1242
1243        #[must_use]
1244        pub fn photo(&self) -> Option<&[PhotoSize]> {
1245            match &self.kind {
1246                Common(MessageCommon {
1247                    media_kind: MediaKind::Photo(MediaPhoto { photo, .. }),
1248                    ..
1249                }) => Some(photo),
1250                _ => None,
1251            }
1252        }
1253
1254        #[must_use]
1255        pub fn sticker(&self) -> Option<&types::Sticker> {
1256            match &self.kind {
1257                Common(MessageCommon {
1258                    media_kind: MediaKind::Sticker(MediaSticker { sticker, .. }),
1259                    ..
1260                }) => Some(sticker),
1261                _ => None,
1262            }
1263        }
1264
1265        #[must_use]
1266        pub fn story(&self) -> Option<&types::Story> {
1267            match &self.kind {
1268                Common(MessageCommon {
1269                    media_kind: MediaKind::Story(MediaStory { story, .. }),
1270                    ..
1271                }) => Some(story),
1272                _ => None,
1273            }
1274        }
1275
1276        #[must_use]
1277        pub fn video(&self) -> Option<&types::Video> {
1278            match &self.kind {
1279                Common(MessageCommon {
1280                    media_kind: MediaKind::Video(MediaVideo { video, .. }),
1281                    ..
1282                }) => Some(video),
1283                _ => None,
1284            }
1285        }
1286
1287        #[must_use]
1288        pub fn voice(&self) -> Option<&types::Voice> {
1289            match &self.kind {
1290                Common(MessageCommon {
1291                    media_kind: MediaKind::Voice(MediaVoice { voice, .. }),
1292                    ..
1293                }) => Some(voice),
1294                _ => None,
1295            }
1296        }
1297
1298        #[must_use]
1299        pub fn video_note(&self) -> Option<&types::VideoNote> {
1300            match &self.kind {
1301                Common(MessageCommon {
1302                    media_kind: MediaKind::VideoNote(MediaVideoNote { video_note, .. }),
1303                    ..
1304                }) => Some(video_note),
1305                _ => None,
1306            }
1307        }
1308
1309        #[must_use]
1310        pub fn caption(&self) -> Option<&str> {
1311            match &self.kind {
1312                Common(MessageCommon {
1313                    media_kind:
1314                        MediaKind::Animation(MediaAnimation { caption, .. })
1315                        | MediaKind::Audio(MediaAudio { caption, .. })
1316                        | MediaKind::Document(MediaDocument { caption, .. })
1317                        | MediaKind::Photo(MediaPhoto { caption, .. })
1318                        | MediaKind::Video(MediaVideo { caption, .. })
1319                        | MediaKind::Voice(MediaVoice { caption, .. }),
1320                    ..
1321                }) => caption.as_ref().map(Deref::deref),
1322                _ => None,
1323            }
1324        }
1325
1326        #[must_use]
1327        pub fn contact(&self) -> Option<&types::Contact> {
1328            match &self.kind {
1329                Common(MessageCommon {
1330                    media_kind: MediaKind::Contact(MediaContact { contact, .. }),
1331                    ..
1332                }) => Some(contact),
1333                _ => None,
1334            }
1335        }
1336
1337        #[must_use]
1338        pub fn location(&self) -> Option<&types::Location> {
1339            match &self.kind {
1340                Common(MessageCommon {
1341                    media_kind: MediaKind::Location(MediaLocation { location, .. }),
1342                    ..
1343                }) => Some(location),
1344                _ => None,
1345            }
1346        }
1347
1348        #[must_use]
1349        pub fn venue(&self) -> Option<&types::Venue> {
1350            match &self.kind {
1351                Common(MessageCommon {
1352                    media_kind: MediaKind::Venue(MediaVenue { venue, .. }),
1353                    ..
1354                }) => Some(venue),
1355                _ => None,
1356            }
1357        }
1358
1359        #[must_use]
1360        pub fn poll(&self) -> Option<&types::Poll> {
1361            match &self.kind {
1362                Common(MessageCommon {
1363                    media_kind: MediaKind::Poll(MediaPoll { poll, .. }),
1364                    ..
1365                }) => Some(poll),
1366                _ => None,
1367            }
1368        }
1369
1370        #[must_use]
1371        pub fn checklist(&self) -> Option<&types::Checklist> {
1372            match &self.kind {
1373                Common(MessageCommon {
1374                    media_kind: MediaKind::Checklist(MediaChecklist { checklist, .. }),
1375                    ..
1376                }) => Some(checklist),
1377                _ => None,
1378            }
1379        }
1380
1381        #[must_use]
1382        pub fn new_chat_members(&self) -> Option<&[User]> {
1383            match &self.kind {
1384                NewChatMembers(MessageNewChatMembers { new_chat_members }) => {
1385                    Some(new_chat_members.as_ref())
1386                }
1387                _ => None,
1388            }
1389        }
1390
1391        #[must_use]
1392        pub fn left_chat_member(&self) -> Option<&User> {
1393            match &self.kind {
1394                LeftChatMember(MessageLeftChatMember { left_chat_member }) => {
1395                    Some(left_chat_member)
1396                }
1397                _ => None,
1398            }
1399        }
1400
1401        #[must_use]
1402        pub fn new_chat_title(&self) -> Option<&str> {
1403            match &self.kind {
1404                NewChatTitle(MessageNewChatTitle { new_chat_title }) => Some(new_chat_title),
1405                _ => None,
1406            }
1407        }
1408
1409        #[must_use]
1410        pub fn new_chat_photo(&self) -> Option<&[PhotoSize]> {
1411            match &self.kind {
1412                NewChatPhoto(MessageNewChatPhoto { new_chat_photo }) => Some(new_chat_photo),
1413                _ => None,
1414            }
1415        }
1416
1417        /// Returns `true` if the incoming [`Message`] contains the
1418        /// `delete_chat_photo` Service message.
1419        ///
1420        /// [More on this](https://core.telegram.org/bots/api#message)
1421        #[must_use]
1422        pub fn is_delete_chat_photo(&self) -> bool {
1423            matches!(&self.kind, DeleteChatPhoto(..))
1424        }
1425
1426        #[must_use]
1427        pub fn delete_chat_photo(&self) -> Option<&MessageDeleteChatPhoto> {
1428            match &self.kind {
1429                DeleteChatPhoto(message_delete_chat_photo) => Some(message_delete_chat_photo),
1430                _ => None,
1431            }
1432        }
1433
1434        /// Returns `true` if the incoming [`Message`] contains the
1435        /// `group_chat_created` Service message.
1436        ///
1437        /// [More on this](https://core.telegram.org/bots/api#message)
1438        #[must_use]
1439        pub fn is_group_chat_created(&self) -> bool {
1440            matches!(&self.kind, GroupChatCreated(..))
1441        }
1442
1443        #[must_use]
1444        pub fn group_chat_created(&self) -> Option<&MessageGroupChatCreated> {
1445            match &self.kind {
1446                GroupChatCreated(message_group_chat_created) => Some(message_group_chat_created),
1447                _ => None,
1448            }
1449        }
1450
1451        /// Returns `true` if the incoming [`Message`] contains the
1452        /// `supergroup_chat_created` Service message.
1453        ///
1454        /// [More on this](https://core.telegram.org/bots/api#message)
1455        #[must_use]
1456        pub fn is_super_group_chat_created(&self) -> bool {
1457            matches!(&self.kind, SupergroupChatCreated(..))
1458        }
1459
1460        #[must_use]
1461        pub fn super_group_chat_created(&self) -> Option<&MessageSupergroupChatCreated> {
1462            match &self.kind {
1463                SupergroupChatCreated(message_supergroup_chat_created) => {
1464                    Some(message_supergroup_chat_created)
1465                }
1466                _ => None,
1467            }
1468        }
1469
1470        /// Returns `true` if the incoming [`Message`] contains the
1471        /// `channel_chat_created` Service message.
1472        ///
1473        /// [More on this](https://core.telegram.org/bots/api#message)
1474        #[must_use]
1475        pub fn is_channel_chat_created(&self) -> bool {
1476            matches!(&self.kind, ChannelChatCreated(..))
1477        }
1478
1479        #[must_use]
1480        pub fn channel_chat_created(&self) -> Option<&MessageChannelChatCreated> {
1481            match &self.kind {
1482                ChannelChatCreated(message_channel_chat_created) => {
1483                    Some(message_channel_chat_created)
1484                }
1485                _ => None,
1486            }
1487        }
1488
1489        #[must_use]
1490        pub fn message_auto_delete_timer_changed(
1491            &self,
1492        ) -> Option<&types::MessageAutoDeleteTimerChanged> {
1493            match &self.kind {
1494                MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged {
1495                    message_auto_delete_timer_changed,
1496                }) => Some(message_auto_delete_timer_changed),
1497                _ => None,
1498            }
1499        }
1500
1501        #[must_use]
1502        pub fn chat_migration(&self) -> Option<&ChatMigration> {
1503            match &self.kind {
1504                Common(MessageCommon {
1505                    media_kind: MediaKind::Migration(chat_migration), ..
1506                }) => Some(chat_migration),
1507                _ => None,
1508            }
1509        }
1510
1511        // FIXME: remove references to small values (requires changing
1512        // `define_message_ext`)
1513        #[must_use]
1514        pub fn migrate_to_chat_id(&self) -> Option<&ChatId> {
1515            match &self.kind {
1516                Common(MessageCommon {
1517                    media_kind: MediaKind::Migration(ChatMigration::To { chat_id }),
1518                    ..
1519                }) => Some(chat_id),
1520                _ => None,
1521            }
1522        }
1523
1524        // FIXME: remove references to small values (requires changing
1525        // `define_message_ext`)
1526        #[must_use]
1527        pub fn migrate_from_chat_id(&self) -> Option<&ChatId> {
1528            match &self.kind {
1529                Common(MessageCommon {
1530                    media_kind: MediaKind::Migration(ChatMigration::From { chat_id }),
1531                    ..
1532                }) => Some(chat_id),
1533                _ => None,
1534            }
1535        }
1536
1537        #[must_use]
1538        pub fn pinned_message(&self) -> Option<&MaybeInaccessibleMessage> {
1539            match &self.kind {
1540                Pinned(MessagePinned { pinned }) => Some(pinned),
1541                _ => None,
1542            }
1543        }
1544
1545        #[must_use]
1546        pub fn invoice(&self) -> Option<&types::Invoice> {
1547            match &self.kind {
1548                Invoice(MessageInvoice { invoice }) => Some(invoice),
1549                _ => None,
1550            }
1551        }
1552
1553        #[must_use]
1554        pub fn successful_payment(&self) -> Option<&types::SuccessfulPayment> {
1555            match &self.kind {
1556                SuccessfulPayment(MessageSuccessfulPayment { successful_payment }) => {
1557                    Some(successful_payment)
1558                }
1559                _ => None,
1560            }
1561        }
1562
1563        #[must_use]
1564        pub fn connected_website(&self) -> Option<&str> {
1565            match &self.kind {
1566                ConnectedWebsite(MessageConnectedWebsite { connected_website }) => {
1567                    Some(connected_website)
1568                }
1569                _ => None,
1570            }
1571        }
1572
1573        #[must_use]
1574        pub fn write_access_allowed(&self) -> Option<&types::WriteAccessAllowed> {
1575            match &self.kind {
1576                WriteAccessAllowed(MessageWriteAccessAllowed { write_access_allowed }) => {
1577                    Some(write_access_allowed)
1578                }
1579                _ => None,
1580            }
1581        }
1582
1583        #[must_use]
1584        pub fn passport_data(&self) -> Option<&types::PassportData> {
1585            match &self.kind {
1586                PassportData(MessagePassportData { passport_data }) => Some(passport_data),
1587                _ => None,
1588            }
1589        }
1590
1591        #[must_use]
1592        pub fn shared_chat(&self) -> Option<&types::ChatShared> {
1593            match &self.kind {
1594                ChatShared(MessageChatShared { chat_shared }) => Some(chat_shared),
1595                _ => None,
1596            }
1597        }
1598
1599        #[must_use]
1600        pub fn shared_users(&self) -> Option<&types::UsersShared> {
1601            match &self.kind {
1602                UsersShared(MessageUsersShared { users_shared }) => Some(users_shared),
1603                _ => None,
1604            }
1605        }
1606
1607        #[must_use]
1608        pub fn dice(&self) -> Option<&types::Dice> {
1609            match &self.kind {
1610                Dice(MessageDice { dice }) => Some(dice),
1611                _ => None,
1612            }
1613        }
1614
1615        #[must_use]
1616        pub fn proximity_alert_triggered(&self) -> Option<&types::ProximityAlertTriggered> {
1617            match &self.kind {
1618                ProximityAlertTriggered(MessageProximityAlertTriggered {
1619                    proximity_alert_triggered,
1620                }) => Some(proximity_alert_triggered),
1621                _ => None,
1622            }
1623        }
1624
1625        #[must_use]
1626        pub fn boost_added(&self) -> Option<&types::ChatBoostAdded> {
1627            match &self.kind {
1628                ChatBoostAdded(MessageChatBoostAdded { boost_added }) => Some(boost_added),
1629                _ => None,
1630            }
1631        }
1632
1633        #[must_use]
1634        pub fn chat_background_set(&self) -> Option<&types::ChatBackground> {
1635            match &self.kind {
1636                ChatBackground(MessageChatBackground { chat_background_set }) => {
1637                    Some(chat_background_set)
1638                }
1639                _ => None,
1640            }
1641        }
1642
1643        #[must_use]
1644        pub fn checklist_tasks_done(&self) -> Option<&types::ChecklistTasksDone> {
1645            match &self.kind {
1646                ChecklistTasksDone(MessageChecklistTasksDone { checklist_tasks_done }) => {
1647                    Some(checklist_tasks_done)
1648                }
1649                _ => None,
1650            }
1651        }
1652
1653        #[must_use]
1654        pub fn checklist_tasks_added(&self) -> Option<&types::ChecklistTasksAdded> {
1655            match &self.kind {
1656                ChecklistTasksAdded(MessageChecklistTasksAdded { checklist_tasks_added }) => {
1657                    Some(checklist_tasks_added)
1658                }
1659                _ => None,
1660            }
1661        }
1662
1663        #[must_use]
1664        pub fn direct_message_price_changed(&self) -> Option<&types::DirectMessagePriceChanged> {
1665            match &self.kind {
1666                DirectMessagePriceChanged(MessageDirectMessagePriceChanged {
1667                    direct_message_price_changed,
1668                }) => Some(direct_message_price_changed),
1669                _ => None,
1670            }
1671        }
1672
1673        #[must_use]
1674        pub fn forum_topic_created(&self) -> Option<&types::ForumTopicCreated> {
1675            match &self.kind {
1676                ForumTopicCreated(MessageForumTopicCreated { forum_topic_created }) => {
1677                    Some(forum_topic_created)
1678                }
1679                _ => None,
1680            }
1681        }
1682
1683        #[must_use]
1684        pub fn forum_topic_edited(&self) -> Option<&types::ForumTopicEdited> {
1685            match &self.kind {
1686                ForumTopicEdited(MessageForumTopicEdited { forum_topic_edited }) => {
1687                    Some(forum_topic_edited)
1688                }
1689                _ => None,
1690            }
1691        }
1692
1693        #[must_use]
1694        pub fn forum_topic_closed(&self) -> Option<&types::ForumTopicClosed> {
1695            match &self.kind {
1696                ForumTopicClosed(MessageForumTopicClosed { forum_topic_closed }) => {
1697                    Some(forum_topic_closed)
1698                }
1699                _ => None,
1700            }
1701        }
1702
1703        #[must_use]
1704        pub fn forum_topic_reopened(&self) -> Option<&types::ForumTopicReopened> {
1705            match &self.kind {
1706                ForumTopicReopened(MessageForumTopicReopened { forum_topic_reopened }) => {
1707                    Some(forum_topic_reopened)
1708                }
1709                _ => None,
1710            }
1711        }
1712
1713        #[must_use]
1714        pub fn general_forum_topic_hidden(&self) -> Option<&types::GeneralForumTopicHidden> {
1715            match &self.kind {
1716                GeneralForumTopicHidden(MessageGeneralForumTopicHidden {
1717                    general_forum_topic_hidden,
1718                }) => Some(general_forum_topic_hidden),
1719                _ => None,
1720            }
1721        }
1722
1723        #[must_use]
1724        pub fn general_forum_topic_unhidden(&self) -> Option<&types::GeneralForumTopicUnhidden> {
1725            match &self.kind {
1726                GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden {
1727                    general_forum_topic_unhidden,
1728                }) => Some(general_forum_topic_unhidden),
1729                _ => None,
1730            }
1731        }
1732
1733        #[must_use]
1734        pub fn giveaway(&self) -> Option<&types::Giveaway> {
1735            match &self.kind {
1736                Giveaway(MessageGiveaway { giveaway }) => Some(giveaway),
1737                _ => None,
1738            }
1739        }
1740
1741        #[must_use]
1742        pub fn giveaway_completed(&self) -> Option<&types::GiveawayCompleted> {
1743            match &self.kind {
1744                GiveawayCompleted(MessageGiveawayCompleted { giveaway_completed }) => {
1745                    Some(giveaway_completed)
1746                }
1747                _ => None,
1748            }
1749        }
1750
1751        #[must_use]
1752        pub fn giveaway_created(&self) -> Option<&types::GiveawayCreated> {
1753            match &self.kind {
1754                GiveawayCreated(MessageGiveawayCreated { giveaway_created }) => {
1755                    Some(giveaway_created)
1756                }
1757                _ => None,
1758            }
1759        }
1760
1761        #[must_use]
1762        pub fn giveaway_winners(&self) -> Option<&types::GiveawayWinners> {
1763            match &self.kind {
1764                GiveawayWinners(MessageGiveawayWinners { giveaway_winners }) => {
1765                    Some(giveaway_winners)
1766                }
1767                _ => None,
1768            }
1769        }
1770
1771        #[must_use]
1772        pub fn paid_message_price_changed(&self) -> Option<&types::PaidMessagePriceChanged> {
1773            match &self.kind {
1774                PaidMessagePriceChanged(MessagePaidMessagePriceChanged {
1775                    paid_message_price_changed,
1776                }) => Some(paid_message_price_changed),
1777                _ => None,
1778            }
1779        }
1780
1781        #[must_use]
1782        pub fn gift_info(&self) -> Option<&types::GiftInfo> {
1783            match &self.kind {
1784                GiftInfo(MessageGiftInfo { gift }) => Some(gift),
1785                _ => None,
1786            }
1787        }
1788
1789        #[must_use]
1790        pub fn unique_gift_info(&self) -> Option<&types::UniqueGiftInfo> {
1791            match &self.kind {
1792                UniqueGiftInfo(MessageUniqueGiftInfo { unique_gift }) => Some(unique_gift),
1793                _ => None,
1794            }
1795        }
1796
1797        #[must_use]
1798        pub fn video_chat_scheduled(&self) -> Option<&types::VideoChatScheduled> {
1799            match &self.kind {
1800                VideoChatScheduled(MessageVideoChatScheduled { video_chat_scheduled }) => {
1801                    Some(video_chat_scheduled)
1802                }
1803                _ => None,
1804            }
1805        }
1806
1807        #[must_use]
1808        pub fn video_chat_started(&self) -> Option<&types::VideoChatStarted> {
1809            match &self.kind {
1810                VideoChatStarted(MessageVideoChatStarted { video_chat_started }) => {
1811                    Some(video_chat_started)
1812                }
1813                _ => None,
1814            }
1815        }
1816
1817        #[must_use]
1818        pub fn video_chat_ended(&self) -> Option<&types::VideoChatEnded> {
1819            match &self.kind {
1820                VideoChatEnded(MessageVideoChatEnded { video_chat_ended }) => {
1821                    Some(video_chat_ended)
1822                }
1823                _ => None,
1824            }
1825        }
1826
1827        #[must_use]
1828        pub fn video_chat_participants_invited(
1829            &self,
1830        ) -> Option<&types::VideoChatParticipantsInvited> {
1831            match &self.kind {
1832                VideoChatParticipantsInvited(MessageVideoChatParticipantsInvited {
1833                    video_chat_participants_invited,
1834                }) => Some(video_chat_participants_invited),
1835                _ => None,
1836            }
1837        }
1838
1839        #[must_use]
1840        pub fn web_app_data(&self) -> Option<&types::WebAppData> {
1841            match &self.kind {
1842                WebAppData(MessageWebAppData { web_app_data }) => Some(web_app_data),
1843                _ => None,
1844            }
1845        }
1846
1847        #[must_use]
1848        pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> {
1849            match &self.kind {
1850                Common(MessageCommon { reply_markup, .. }) => reply_markup.as_ref(),
1851                _ => None,
1852            }
1853        }
1854
1855        #[must_use]
1856        pub fn is_automatic_forward(&self) -> bool {
1857            match &self.kind {
1858                Common(MessageCommon { is_automatic_forward, .. }) => *is_automatic_forward,
1859                _ => false,
1860            }
1861        }
1862
1863        #[must_use]
1864        pub fn has_protected_content(&self) -> bool {
1865            match &self.kind {
1866                Common(MessageCommon { has_protected_content, .. }) => *has_protected_content,
1867                _ => false,
1868            }
1869        }
1870
1871        /// Common message (text, image, etc)
1872        fn common(&self) -> Option<&MessageCommon> {
1873            match &self.kind {
1874                Common(message) => Some(message),
1875                _ => None,
1876            }
1877        }
1878
1879        // FIXME: add more getters for other types of messages
1880    }
1881}
1882
1883impl Message {
1884    /// Produces a direct link to this message.
1885    ///
1886    /// Note that for private groups the link will only be accessible for group
1887    /// members.
1888    ///
1889    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1890    /// supergroups).
1891    #[must_use]
1892    pub fn url(&self) -> Option<Url> {
1893        Self::url_of(self.chat.id, self.chat.username(), self.id)
1894    }
1895
1896    /// Produces a direct link to a message in a chat.
1897    ///
1898    /// If you have a `Message` object, use [`url`] instead.
1899    /// This function should only be used if you have limited information about
1900    /// the message (chat id, username of the chat, if any and its id).
1901    ///
1902    /// Note that for private groups the link will only be accessible for group
1903    /// members.
1904    ///
1905    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1906    /// supergroups).
1907    ///
1908    /// [`url`]: Message::url
1909    #[track_caller]
1910    #[must_use]
1911    pub fn url_of(
1912        chat_id: ChatId,
1913        chat_username: Option<&str>,
1914        message_id: MessageId,
1915    ) -> Option<Url> {
1916        use BareChatId::*;
1917
1918        // Note: `t.me` links use bare chat ids
1919        let chat_id = match chat_id.to_bare() {
1920            // For private chats (i.e.: DMs) we can't produce "normal" t.me link.
1921            //
1922            // There are "tg://openmessage?user_id={0}&message_id={1}" links, which are
1923            // supposed to open any chat, including private messages, but they
1924            // are only supported by some telegram clients (e.g. Plus Messenger,
1925            // Telegram for Android 4.9+).
1926            User(_) => return None,
1927            // Similarly to user chats, there is no way to create a link to a message in a normal,
1928            // private group.
1929            //
1930            // (public groups are always supergroup which are in turn channels).
1931            Group(_) => return None,
1932            Channel(id) => id,
1933        };
1934
1935        let url = match chat_username {
1936            // If it's public group (i.e. not DM, not private group), we can produce
1937            // "normal" t.me link (accessible to everyone).
1938            Some(username) => format!("https://t.me/{0}/{1}", username, message_id.0),
1939            // For private supergroups and channels we produce "private" t.me/c links. These are
1940            // only accessible to the group members.
1941            None => format!("https://t.me/c/{0}/{1}", chat_id, message_id.0),
1942        };
1943
1944        // UNWRAP:
1945        //
1946        // The `url` produced by formatting is correct since username is
1947        // /[a-zA-Z0-9_]{5,32}/ and chat/message ids are integers.
1948        Some(reqwest::Url::parse(&url).unwrap())
1949    }
1950
1951    /// Produces a direct link to a comment on this post.
1952    ///
1953    /// Note that for private channels the link will only be accessible for
1954    /// channel members.
1955    ///
1956    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1957    /// supergroups).
1958    #[must_use]
1959    pub fn comment_url(&self, comment_id: MessageId) -> Option<Url> {
1960        Self::comment_url_of(self.chat.id, self.chat.username(), self.id, comment_id)
1961    }
1962
1963    /// Produces a direct link to a comment on a post.
1964    ///
1965    /// If you have a `Message` object of the channel post, use [`comment_url`]
1966    /// instead. This function should only be used if you have limited
1967    /// information about the message (channel id, username of the channel,
1968    /// if any, post id and comment id).
1969    ///
1970    /// Note that for private channels the link will only be accessible for
1971    /// channel members.
1972    ///
1973    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
1974    /// supergroups).
1975    ///
1976    /// [`comment_url`]: Message::comment_url
1977    #[must_use]
1978    pub fn comment_url_of(
1979        channel_id: ChatId,
1980        channel_username: Option<&str>,
1981        post_id: MessageId,
1982        comment_id: MessageId,
1983    ) -> Option<Url> {
1984        Self::url_of(channel_id, channel_username, post_id).map(|mut url| {
1985            url.set_query(Some(&format!("comment={}", comment_id.0)));
1986            url
1987        })
1988    }
1989
1990    /// Produces a direct link to this message in a given thread.
1991    ///
1992    /// "Thread" is a group of messages that reply to each other in a tree-like
1993    /// structure. `thread_starter_msg_id` is the id of the first message in
1994    /// the thread, the root of the tree.
1995    ///
1996    /// Note that for private groups the link will only be accessible for group
1997    /// members.
1998    ///
1999    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
2000    /// supergroups).
2001    #[must_use]
2002    pub fn url_in_thread(&self, thread_starter_msg_id: MessageId) -> Option<Url> {
2003        Self::url_in_thread_of(self.chat.id, self.chat.username(), thread_starter_msg_id, self.id)
2004    }
2005
2006    /// Produces a direct link to a message in a given thread.
2007    ///
2008    /// If you have a `Message` object of the channel post, use
2009    /// [`url_in_thread`] instead. This function should only be used if you
2010    /// have limited information about the message (chat id, username of the
2011    /// chat, if any, thread starter id and message id).
2012    ///
2013    /// "Thread" is a group of messages that reply to each other in a tree-like
2014    /// structure. `thread_starter_msg_id` is the id of the first message in
2015    /// the thread, the root of the tree.
2016    ///
2017    /// Note that for private groups the link will only be accessible for group
2018    /// members.
2019    ///
2020    /// Returns `None` for private chats (i.e.: DMs) and private groups (not
2021    /// supergroups).
2022    ///
2023    /// [`url_in_thread`]: Message::url_in_thread
2024    #[must_use]
2025    pub fn url_in_thread_of(
2026        chat_id: ChatId,
2027        chat_username: Option<&str>,
2028        thread_starter_msg_id: MessageId,
2029        message_id: MessageId,
2030    ) -> Option<Url> {
2031        Self::url_of(chat_id, chat_username, message_id).map(|mut url| {
2032            url.set_query(Some(&format!("thread={}", thread_starter_msg_id.0)));
2033            url
2034        })
2035    }
2036
2037    /// Returns message entities that represent text formatting.
2038    ///
2039    /// This function returns `Some(entities)` for **text messages** and
2040    /// `None` for all other kinds of messages (including photos with
2041    /// captions).
2042    ///
2043    /// See also: [`parse_caption_entities`].
2044    ///
2045    /// [`parse_caption_entities`]: Message::parse_caption_entities
2046    #[must_use]
2047    pub fn parse_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
2048        self.text().zip(self.entities()).map(|(t, e)| MessageEntityRef::parse(t, e))
2049    }
2050
2051    /// Returns message entities that represent text formatting.
2052    ///
2053    /// This function returns `Some(entities)` for **media messages** and
2054    /// `None` for all other kinds of messages (including text messages).
2055    ///
2056    /// See also: [`parse_entities`].
2057    ///
2058    /// [`parse_entities`]: Message::parse_entities
2059    #[must_use]
2060    pub fn parse_caption_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
2061        self.caption().zip(self.caption_entities()).map(|(t, e)| MessageEntityRef::parse(t, e))
2062    }
2063
2064    /// Returns all users that are "contained" in this `Message` structure.
2065    ///
2066    /// This might be useful to track information about users.
2067    ///
2068    /// Note that this function may return quite a few users as it scans
2069    /// replies, message entities and more. Also note that this
2070    /// function can return duplicate users.
2071    ///
2072    /// In earlier versions of the `teloixde-core`, this function
2073    /// returned mentioned users in [`chat`] from which the Message was
2074    /// forwarded. E.g. from pinned messages in the chat. This functionality
2075    /// was lost with the TBA 7.3 update.
2076    ///
2077    /// [`chat`]: Self::forward_from_chat
2078    pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
2079        use crate::util::{flatten, mentioned_users_from_entities};
2080
2081        // Lets just hope we didn't forget something here...
2082
2083        self.from
2084            .iter()
2085            .chain(self.via_bot.as_ref())
2086            .chain(flatten(self.reply_to_message().map(Self::mentioned_users_rec)))
2087            .chain(flatten(self.new_chat_members()))
2088            .chain(self.left_chat_member())
2089            .chain(self.forward_from_user())
2090            .chain(flatten(self.game().map(Game::mentioned_users)))
2091            .chain(flatten(self.entities().map(mentioned_users_from_entities)))
2092            .chain(flatten(self.caption_entities().map(mentioned_users_from_entities)))
2093            .chain(flatten(self.poll().map(Poll::mentioned_users)))
2094            .chain(flatten(self.proximity_alert_triggered().map(|a| [&a.traveler, &a.watcher])))
2095            .chain(flatten(self.video_chat_participants_invited().and_then(|i| i.users.as_deref())))
2096    }
2097
2098    /// `Message::mentioned_users` is recursive (due to replies), as such we
2099    /// can't use `->impl Iterator` everywhere, as it would make an infinite
2100    /// type. So we need to box somewhere.
2101    pub(crate) fn mentioned_users_rec(&self) -> Box<dyn Iterator<Item = &User> + Send + Sync + '_> {
2102        Box::new(self.mentioned_users())
2103    }
2104}
2105
2106/// Implemented for syntax sugar, see issue <https://github.com/teloxide/teloxide/issues/1143>
2107impl From<Message> for MessageId {
2108    fn from(message: Message) -> MessageId {
2109        message.id
2110    }
2111}
2112
2113impl From<&Message> for MessageId {
2114    fn from(message: &Message) -> MessageId {
2115        message.id
2116    }
2117}
2118
2119#[cfg(test)]
2120mod tests {
2121    use chrono::DateTime;
2122    use cool_asserts::assert_matches;
2123    use serde_json::from_str;
2124
2125    use crate::types::*;
2126
2127    #[test]
2128    fn de_media_forwarded() {
2129        let json = r#"{
2130          "message_id": 198283,
2131          "from": {
2132            "id": 250918540,
2133            "is_bot": false,
2134            "first_name": "Андрей",
2135            "last_name": "Власов",
2136            "username": "aka_dude",
2137            "language_code": "en"
2138          },
2139          "chat": {
2140            "id": 250918540,
2141            "first_name": "Андрей",
2142            "last_name": "Власов",
2143            "username": "aka_dude",
2144            "type": "private"
2145          },
2146          "date": 1567927221,
2147          "video": {
2148            "duration": 13,
2149            "width": 512,
2150            "height": 640,
2151            "mime_type": "video/mp4",
2152            "thumbnail": {
2153              "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE",
2154              "file_unique_id":"",
2155              "file_size": 10339,
2156              "width": 256,
2157              "height": 320
2158            },
2159            "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE",
2160            "file_unique_id":"",
2161            "file_size": 1381334
2162          }
2163        }"#;
2164        let message = from_str::<Message>(json);
2165        assert!(message.is_ok());
2166    }
2167
2168    #[test]
2169    fn de_shared_chat() {
2170        let json = r#"{
2171            "message_id": 198283,
2172            "chat": {
2173              "id": 250918540,
2174              "first_name": "Андрей",
2175              "last_name": "Власов",
2176              "username": "aka_dude",
2177              "type": "private"
2178            },
2179            "date": 1567927221,
2180            "chat_shared": {
2181                "request_id": 348349,
2182                "chat_id": 384939
2183            }
2184          }"#;
2185        let message = from_str::<Message>(json);
2186        assert!(message.is_ok());
2187        assert_eq!(
2188            message.unwrap(),
2189            Message {
2190                id: MessageId(198283),
2191                thread_id: None,
2192                from: None,
2193                sender_chat: None,
2194                date: chrono::DateTime::from_timestamp(1567927221, 0).unwrap(),
2195                is_topic_message: false,
2196                chat: Chat {
2197                    id: ChatId(250918540),
2198                    kind: ChatKind::Private(ChatPrivate {
2199                        first_name: Some("Андрей".to_string()),
2200                        last_name: Some("Власов".to_string()),
2201                        username: Some("aka_dude".to_string()),
2202                    }),
2203                },
2204                sender_business_bot: None,
2205                kind: MessageKind::ChatShared(MessageChatShared {
2206                    chat_shared: ChatShared {
2207                        request_id: RequestId(348349),
2208                        chat_id: ChatId(384939),
2209                        title: None,
2210                        username: None,
2211                        photo: None,
2212                    }
2213                }),
2214                via_bot: None
2215            }
2216        );
2217    }
2218
2219    #[test]
2220    fn de_media_group_forwarded() {
2221        let json = r#"{
2222          "message_id": 198283,
2223          "from": {
2224            "id": 250918540,
2225            "is_bot": false,
2226            "first_name": "Андрей",
2227            "last_name": "Власов",
2228            "username": "aka_dude",
2229            "language_code": "en"
2230          },
2231          "chat": {
2232            "id": 250918540,
2233            "first_name": "Андрей",
2234            "last_name": "Власов",
2235            "username": "aka_dude",
2236            "type": "private"
2237          },
2238          "date": 1567927221,
2239          "media_group_id": "12543417770506682",
2240          "video": {
2241            "duration": 13,
2242            "width": 512,
2243            "height": 640,
2244            "mime_type": "video/mp4",
2245            "thumbnail": {
2246              "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE",
2247              "file_unique_id":"",
2248              "file_size": 10339,
2249              "width": 256,
2250              "height": 320
2251            },
2252            "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE",
2253            "file_unique_id":"",
2254            "file_size": 1381334
2255          }
2256        }"#;
2257        let message = from_str::<Message>(json);
2258        assert!(message.is_ok());
2259    }
2260
2261    #[test]
2262    fn de_text() {
2263        let json = r#"{
2264          "message_id": 199785,
2265          "from": {
2266           "id": 250918540,
2267           "is_bot": false,
2268           "first_name": "Андрей",
2269           "last_name": "Власов",
2270           "username": "aka_dude",
2271           "language_code": "en"
2272          },
2273          "chat": {
2274           "id": 250918540,
2275           "first_name": "Андрей",
2276           "last_name": "Власов",
2277           "username": "aka_dude",
2278           "type": "private"
2279          },
2280          "date": 1568289890,
2281          "text": "Лол кек 😂"
2282         }"#;
2283        let message = from_str::<Message>(json);
2284        assert!(message.is_ok());
2285    }
2286
2287    #[test]
2288    fn de_sticker() {
2289        let json = r#"{
2290            "message_id": 199787,
2291            "from": {
2292                "id": 250918540,
2293                "is_bot": false,
2294                "first_name": "Андрей",
2295                "last_name": "Власов",
2296                "username": "aka_dude",
2297                "language_code": "en"
2298            },
2299            "chat": {
2300                "id": 250918540,
2301                "first_name": "Андрей",
2302                "last_name": "Власов",
2303                "username": "aka_dude",
2304                "type": "private"
2305            },
2306            "date": 1568290188,
2307            "sticker": {
2308                "width": 512,
2309                "height": 512,
2310                "emoji": "😡",
2311                "set_name": "AdvenTimeAnim",
2312                "is_animated": true,
2313                "is_video": false,
2314                "type": "regular",
2315                "thumbnail": {
2316                    "file_id": "AAMCAgADGQEAARIt0GMwiZ6n4nRbxdpM3pL8vPX6PVAhAAIjAAOw0PgMaabKAcaXKCABAAdtAAMpBA",
2317                    "file_unique_id": "AQADIwADsND4DHI",
2318                    "file_size": 4118,
2319                    "width": 128,
2320                    "height": 128
2321                },
2322                "file_id": "CAACAgIAAxkBAAESLdBjMImep-J0W8XaTN6S_Lz1-j1QIQACIwADsND4DGmmygHGlyggKQQ",
2323                "file_unique_id": "AgADIwADsND4DA",
2324                "file_size": 16639
2325            }
2326        }"#;
2327        from_str::<Message>(json).unwrap();
2328    }
2329
2330    #[test]
2331    fn de_image() {
2332        let json = r#"{
2333          "message_id": 199791,
2334          "from": {
2335           "id": 250918540,
2336           "is_bot": false,
2337           "first_name": "Андрей",
2338           "last_name": "Власов",
2339           "username": "aka_dude",
2340           "language_code": "en"
2341          },
2342          "chat": {
2343           "id": 250918540,
2344           "first_name": "Андрей",
2345           "last_name": "Власов",
2346           "username": "aka_dude",
2347           "type": "private"
2348          },
2349          "date": 1568290622,
2350          "photo": [
2351           {
2352            "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA20AAybcBAABFgQ",
2353            "file_unique_id":"",
2354            "file_size": 18188,
2355            "width": 320,
2356            "height": 239
2357           },
2358           {
2359            "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3gAAyfcBAABFgQ",
2360            "file_unique_id":"",
2361            "file_size": 62123,
2362            "width": 800,
2363            "height": 598
2364           },
2365           {
2366            "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3kAAyTcBAABFgQ",
2367            "file_unique_id":"",
2368            "file_size": 75245,
2369            "width": 962,
2370            "height": 719
2371           }
2372          ]
2373         }"#;
2374        let message = from_str::<Message>(json);
2375        assert!(message.is_ok());
2376    }
2377
2378    /// Regression test for <https://github.com/teloxide/teloxide/issues/419>
2379    #[test]
2380    fn issue_419() {
2381        let json = r#"{
2382            "message_id": 1,
2383            "from": {
2384                "id": 1087968824,
2385                "is_bot": true,
2386                "first_name": "Group",
2387                "username": "GroupAnonymousBot"
2388            },
2389            "author_signature": "TITLE2",
2390            "sender_chat": {
2391                "id": -1001160242915,
2392                "title": "a",
2393                "type": "supergroup"
2394            },
2395            "chat": {
2396                "id": -1001160242915,
2397                "title": "a",
2398                "type": "supergroup"
2399            },
2400            "forward_origin": {
2401                "type": "chat",
2402                "date": 1640359544,
2403                "sender_chat": {
2404                    "id": -1001160242915,
2405                    "title": "a",
2406                    "type": "supergroup"
2407                },
2408                "author_signature": "TITLE"
2409            },
2410            "date": 1640359576,
2411            "text": "text"
2412        }"#;
2413
2414        // Anonymous admin with title "TITLE2" forwards a message from anonymous
2415        // admin with title "TITLE" with text "a", everything is happening in
2416        // the same group.
2417        let message: Message = serde_json::from_str(json).unwrap();
2418
2419        let group = Chat {
2420            id: ChatId(-1001160242915),
2421            kind: ChatKind::Public(ChatPublic {
2422                title: Some("a".to_owned()),
2423                kind: PublicChatKind::Supergroup(PublicChatSupergroup {
2424                    username: None,
2425                    is_forum: false,
2426                }),
2427            }),
2428        };
2429
2430        assert!(message.from.as_ref().unwrap().is_anonymous());
2431        assert_eq!(message.author_signature().unwrap(), "TITLE2");
2432        assert_eq!(message.sender_chat.as_ref().unwrap(), &group);
2433        assert_eq!(&message.chat, &group);
2434        assert_eq!(message.forward_from_chat().unwrap(), &group);
2435        assert_eq!(message.forward_author_signature().unwrap(), "TITLE");
2436        assert!(message.forward_date().is_some());
2437        assert_eq!(message.text().unwrap(), "text");
2438    }
2439
2440    /// Regression test for <https://github.com/teloxide/teloxide/issues/427>
2441    #[test]
2442    fn issue_427() {
2443        let old = ChatId(-599075523);
2444        let new = ChatId(-1001555296434);
2445
2446        // Migration to a supergroup
2447        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}"#;
2448        let message: Message = from_str(json).unwrap();
2449
2450        assert_eq!(message.chat.id, old);
2451        assert_eq!(message.chat_migration(), Some(&ChatMigration::To { chat_id: new }));
2452        assert_eq!(message.migrate_to_chat_id(), Some(&new));
2453
2454        // The user who initialized the migration
2455        assert!(message.from.is_some());
2456
2457        // Migration from a common group
2458        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"}}"#;
2459        let message: Message = from_str(json).unwrap();
2460
2461        assert_eq!(message.chat.id, new);
2462        assert_eq!(message.chat_migration(), Some(&ChatMigration::From { chat_id: old }));
2463        assert_eq!(message.migrate_from_chat_id(), Some(&old));
2464
2465        // Anonymous bot
2466        assert!(message.from.is_some());
2467
2468        // The chat to which the group migrated
2469        assert!(message.sender_chat.is_some());
2470    }
2471
2472    /// Regression test for <https://github.com/teloxide/teloxide/issues/481>
2473    #[test]
2474    fn issue_481() {
2475        let json = r#"
2476{
2477  "message_id": 0,
2478  "date": 0,
2479  "location": {
2480   "latitude": 0.0,
2481   "longitude": 0.0
2482  },
2483  "chat": {
2484   "id": 0,
2485   "first_name": "f",
2486   "type": "private"
2487  },
2488  "venue": {
2489   "location": {
2490    "latitude": 0.0,
2491    "longitude": 0.0,
2492    "live_period": 900
2493   },
2494   "title": "Title",
2495   "address": "Address",
2496   "foursquare_id": "some_foursquare_id"
2497  }
2498 }
2499"#;
2500        let message: Message = from_str(json).unwrap();
2501        assert_eq!(
2502            message.venue().unwrap(),
2503            &Venue {
2504                location: Location {
2505                    longitude: 0.0,
2506                    latitude: 0.0,
2507                    horizontal_accuracy: None,
2508                    live_period: Some(900.into()),
2509                    heading: None,
2510                    proximity_alert_radius: None
2511                },
2512                title: "Title".to_owned(),
2513                address: "Address".to_owned(),
2514                foursquare_id: Some("some_foursquare_id".to_owned()),
2515                foursquare_type: None,
2516                google_place_id: None,
2517                google_place_type: None,
2518            }
2519        )
2520    }
2521
2522    /// Regression test for <https://github.com/teloxide/teloxide/issues/475>
2523    #[test]
2524    fn issue_475() {
2525        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":{}}"#;
2526
2527        let message: Message = serde_json::from_str(json).unwrap();
2528
2529        assert!(matches!(message.kind, MessageKind::VideoChatStarted { .. }));
2530
2531        // FIXME(waffle): it seems like we are losing `sender_chat` in some
2532        // cases inclusing this
2533        // assert!(message.sender_chat().is_some());
2534    }
2535
2536    #[test]
2537    fn parse_caption_entities() {
2538        let json = r#"
2539        {
2540            "message_id": 3460,
2541            "from": {
2542              "id": 27433968,
2543              "is_bot": false,
2544              "first_name": "Crax | rats addict",
2545              "username": "tacocrasco",
2546              "language_code": "en"
2547            },
2548            "chat": {
2549              "id": 27433968,
2550              "first_name": "Crax | rats addict",
2551              "username": "tacocrasco",
2552              "type": "private"
2553            },
2554            "date": 1655671349,
2555            "photo": [
2556              {
2557                "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADcwADJAQ",
2558                "file_unique_id": "AQADabwxGxJmgVF4",
2559                "file_size": 2077,
2560                "width": 90,
2561                "height": 90
2562              },
2563              {
2564                "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADbQADJAQ",
2565                "file_unique_id": "AQADabwxGxJmgVFy",
2566                "file_size": 27640,
2567                "width": 320,
2568                "height": 320
2569              },
2570              {
2571                "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADeAADJAQ",
2572                "file_unique_id": "AQADabwxGxJmgVF9",
2573                "file_size": 99248,
2574                "width": 800,
2575                "height": 800
2576              },
2577              {
2578                "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADeQADJAQ",
2579                "file_unique_id": "AQADabwxGxJmgVF-",
2580                "file_size": 162061,
2581                "width": 1280,
2582                "height": 1280
2583              }
2584            ],
2585            "caption": "www.example.com",
2586            "caption_entities": [
2587              {
2588                "offset": 0,
2589                "length": 15,
2590                "type": "url"
2591              }
2592            ]
2593        }"#;
2594
2595        let message: Message = serde_json::from_str(json).unwrap();
2596        let entities = message.parse_caption_entities();
2597        assert!(entities.is_some());
2598
2599        let entities = entities.unwrap();
2600        assert!(!entities.is_empty());
2601        assert_eq!(entities[0].kind().clone(), MessageEntityKind::Url);
2602    }
2603
2604    #[test]
2605    fn topic_created() {
2606        let json = r#"{
2607            "chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},
2608            "date":1675229139,
2609            "forum_topic_created":{
2610                "icon_color":9367192,
2611                "icon_custom_emoji_id":"5312536423851630001",
2612                "name":"???"
2613            },
2614            "from":{
2615                "first_name":"вафель'",
2616                "id":1253681278,
2617                "is_bot":false,
2618                "language_code":"en",
2619                "username":"wafflelapkin"
2620            },
2621            "is_topic_message":true,
2622            "message_id":4,
2623            "message_thread_id":4
2624        }"#;
2625
2626        let message: Message = serde_json::from_str(json).unwrap();
2627        // https://github.com/teloxide/teloxide/issues/945
2628        assert!(message.from.is_some());
2629    }
2630
2631    #[test]
2632    fn topic_message() {
2633        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"}"#;
2634
2635        let _: Message = serde_json::from_str(json).unwrap();
2636    }
2637
2638    /// Regression test for <https://github.com/teloxide/teloxide/issues/873>
2639    #[test]
2640    fn empty_message() {
2641        let json = r#"{"chat": {"first_name": "FN", "id": 1234567890, "type": "private"}, "date": 0, "message_id": 875400}"#;
2642
2643        let msg: Message = serde_json::from_str(json).unwrap();
2644        assert_matches!(msg.kind, MessageKind::Empty {})
2645    }
2646
2647    #[test]
2648    fn issue_874() {
2649        let json = r#"{
2650            "chat": {
2651                "id": -1001840751935,
2652                "is_forum": true,
2653                "title": "AI",
2654                "type": "supergroup"
2655            },
2656            "date": 1682191229,
2657            "forum_topic_closed": {},
2658            "from": {
2659                "first_name": "Владислав",
2660                "id": 112455916,
2661                "is_bot": false,
2662                "language_code": "en",
2663                "username": "scv977"
2664            },
2665            "message_id": 62
2666        }"#;
2667
2668        let _: Message = serde_json::from_str(json).unwrap();
2669    }
2670
2671    #[test]
2672    fn giveaway() {
2673        let json = r#"{
2674            "message_id": 27,
2675            "sender_chat": {
2676                "id": -1002236736395,
2677                "title": "Test",
2678                "type": "channel"
2679            },
2680            "chat": {
2681                "id": -1002236736395,
2682                "title": "Test",
2683                "type": "channel"
2684            },
2685            "date": 1721162577,
2686            "giveaway": {
2687                "chats": [
2688                    {
2689                        "id": -1002236736395,
2690                        "title": "Test",
2691                        "type": "channel"
2692                    }
2693                ],
2694                "winners_selection_date": 1721162701,
2695                "winner_count": 1,
2696                "has_public_winners": true,
2697                "premium_subscription_month_count": 6
2698            }
2699        }"#;
2700        let message: Message = from_str(json).unwrap();
2701        assert_eq!(
2702            message.giveaway().unwrap(),
2703            &Giveaway {
2704                chats: vec![Chat {
2705                    id: ChatId(-1002236736395),
2706                    kind: ChatKind::Public(ChatPublic {
2707                        title: Some("Test".to_owned()),
2708                        kind: PublicChatKind::Channel(PublicChatChannel { username: None }),
2709                    }),
2710                }],
2711                winners_selection_date: DateTime::from_timestamp(1721162701, 0).unwrap(),
2712                winner_count: 1,
2713                only_new_members: false,
2714                has_public_winners: true,
2715                prize_description: None,
2716                country_codes: None,
2717                prize_star_count: None,
2718                premium_subscription_month_count: Some(6)
2719            }
2720        )
2721    }
2722
2723    #[test]
2724    fn giveaway_created() {
2725        let json = r#"{
2726            "message_id": 27,
2727            "sender_chat": {
2728                "id": -1002236736395,
2729                "title": "Test",
2730                "type": "channel"
2731            },
2732            "chat": {
2733                "id": -1002236736395,
2734                "title": "Test",
2735                "type": "channel"
2736            },
2737            "date": 1721162577,
2738            "giveaway_created": {}
2739        }"#;
2740        let message: Message = from_str(json).unwrap();
2741        assert_eq!(message.giveaway_created().unwrap(), &GiveawayCreated { prize_star_count: None })
2742    }
2743
2744    #[test]
2745    fn giveaway_completed() {
2746        let json = r#"{
2747            "message_id": 27,
2748            "sender_chat": {
2749                "id": -1002236736395,
2750                "title": "Test",
2751                "type": "channel"
2752            },
2753            "chat": {
2754                "id": -1002236736395,
2755                "title": "Test",
2756                "type": "channel"
2757            },
2758            "date": 1721162577,
2759            "giveaway_completed": {
2760                "winner_count": 0,
2761                "unclaimed_prize_count": 1,
2762                "giveaway_message": {
2763                    "message_id": 24,
2764                    "sender_chat": {
2765                        "id": -1002236736395,
2766                        "title": "Test",
2767                        "type": "channel"
2768                    },
2769                    "chat": {
2770                        "id": -1002236736395,
2771                        "title": "Test",
2772                        "type": "channel"
2773                    },
2774                    "date": 1721161230,
2775                    "giveaway": {
2776                        "chats": [
2777                            {
2778                                "id": -1002236736395,
2779                                "title": "Test",
2780                                "type": "channel"
2781                            }
2782                        ],
2783                        "winners_selection_date": 1721162701,
2784                        "winner_count": 1,
2785                        "has_public_winners": true,
2786                        "premium_subscription_month_count": 6
2787                    }
2788                }
2789            }
2790        }"#;
2791        let message: Message = from_str(json).unwrap();
2792        assert_eq!(
2793            message.giveaway_completed().unwrap(),
2794            &GiveawayCompleted {
2795                winner_count: 0,
2796                unclaimed_prize_count: Some(1),
2797                giveaway_message: Some(Box::new(Message {
2798                    id: MessageId(24),
2799                    thread_id: None,
2800                    from: None,
2801                    sender_chat: Some(Chat {
2802                        id: ChatId(-1002236736395),
2803                        kind: ChatKind::Public(ChatPublic {
2804                            title: Some("Test".to_owned()),
2805                            kind: PublicChatKind::Channel(PublicChatChannel { username: None }),
2806                        }),
2807                    }),
2808                    is_topic_message: false,
2809                    date: DateTime::from_timestamp(1721161230, 0).unwrap(),
2810                    chat: Chat {
2811                        id: ChatId(-1002236736395),
2812                        kind: ChatKind::Public(ChatPublic {
2813                            title: Some("Test".to_owned()),
2814                            kind: PublicChatKind::Channel(PublicChatChannel { username: None }),
2815                        }),
2816                    },
2817                    via_bot: None,
2818                    sender_business_bot: None,
2819                    kind: MessageKind::Giveaway(MessageGiveaway {
2820                        giveaway: Giveaway {
2821                            chats: vec![Chat {
2822                                id: ChatId(-1002236736395),
2823                                kind: ChatKind::Public(ChatPublic {
2824                                    title: Some("Test".to_owned()),
2825                                    kind: PublicChatKind::Channel(PublicChatChannel {
2826                                        username: None,
2827                                    }),
2828                                }),
2829                            }],
2830                            winners_selection_date: DateTime::from_timestamp(1721162701, 0)
2831                                .unwrap(),
2832                            winner_count: 1,
2833                            only_new_members: false,
2834                            has_public_winners: true,
2835                            prize_description: None,
2836                            country_codes: None,
2837                            prize_star_count: None,
2838                            premium_subscription_month_count: Some(6)
2839                        }
2840                    })
2841                })),
2842                is_star_giveaway: false,
2843            }
2844        )
2845    }
2846
2847    #[test]
2848    fn giveaway_winners() {
2849        let json = r#"{
2850            "message_id": 28,
2851            "sender_chat": {
2852                "id": -1002236736395,
2853                "title": "Test",
2854                "type": "channel"
2855            },
2856            "chat": {
2857                "id": -1002236736395,
2858                "title": "Test",
2859                "type": "channel"
2860            },
2861            "date": 1721162702,
2862            "reply_to_message": {
2863                "message_id": 27,
2864                "sender_chat": {
2865                    "id": -1002236736395,
2866                    "title": "Test",
2867                    "type": "channel"
2868                },
2869                "chat": {
2870                    "id": -1002236736395,
2871                    "title": "Test",
2872                    "type": "channel"
2873                },
2874                "date": 1721162577,
2875                "giveaway": {
2876                    "chats": [
2877                        {
2878                            "id": -1002236736395,
2879                            "title": "Test",
2880                            "type": "channel"
2881                        }
2882                    ],
2883                    "winners_selection_date": 1721162701,
2884                    "winner_count": 1,
2885                    "has_public_winners": true,
2886                    "premium_subscription_month_count": 6
2887                }
2888            },
2889            "giveaway_winners": {
2890                "chat": {
2891                    "id": -1002236736395,
2892                    "title": "Test",
2893                    "type": "channel"
2894                },
2895                "giveaway_message_id": 27,
2896                "winners_selection_date": 1721162701,
2897                "premium_subscription_month_count": 6,
2898                "winner_count": 1,
2899                "winners": [
2900                    {
2901                        "id": 1459074222,
2902                        "is_bot": false,
2903                        "first_name": "shadowchain",
2904                        "username": "shdwchn10"
2905                    }
2906                ]
2907            }
2908        }"#;
2909        let message: Message = from_str(json).unwrap();
2910        assert_eq!(
2911            message.giveaway_winners().expect("Failed to get GiveawayWinners from Message!"),
2912            &GiveawayWinners {
2913                chat: Chat {
2914                    id: ChatId(-1002236736395),
2915                    kind: ChatKind::Public(ChatPublic {
2916                        title: Some("Test".to_owned()),
2917                        kind: PublicChatKind::Channel(PublicChatChannel { username: None }),
2918                    }),
2919                },
2920                giveaway_message_id: MessageId(27),
2921                winners_selection_date: DateTime::from_timestamp(1721162701, 0).unwrap(),
2922                winner_count: 1,
2923                winners: vec![User {
2924                    id: UserId(1459074222),
2925                    is_bot: false,
2926                    first_name: "shadowchain".to_owned(),
2927                    last_name: None,
2928                    username: Some("shdwchn10".to_owned()),
2929                    language_code: None,
2930                    is_premium: false,
2931                    added_to_attachment_menu: false
2932                }],
2933                additional_chat_count: None,
2934                premium_subscription_month_count: Some(6),
2935                unclaimed_prize_count: None,
2936                only_new_members: false,
2937                was_refunded: false,
2938                prize_star_count: None,
2939                prize_description: None
2940            }
2941        )
2942    }
2943
2944    #[test]
2945    fn paid_message_price_changed() {
2946        let json = r#"{
2947            "message_id": 27,
2948            "sender_chat": {
2949                "id": -1002236736395,
2950                "title": "Test",
2951                "type": "channel"
2952            },
2953            "chat": {
2954                "id": -1002236736395,
2955                "title": "Test",
2956                "type": "channel"
2957            },
2958            "date": 1721162577,
2959            "paid_message_price_changed": {"paid_message_star_count": 1234}
2960        }"#;
2961        let message: Message = from_str(json).unwrap();
2962        assert_eq!(
2963            message.paid_message_price_changed().unwrap(),
2964            &PaidMessagePriceChanged { paid_message_star_count: 1234 }
2965        )
2966    }
2967
2968    #[test]
2969    fn gift_info() {
2970        let json = r#"{
2971            "message_id": 27,
2972            "sender_chat": {
2973                "id": -1002236736395,
2974                "title": "Test",
2975                "type": "channel"
2976            },
2977            "chat": {
2978                "id": -1002236736395,
2979                "title": "Test",
2980                "type": "channel"
2981            },
2982            "date": 1721162577,
2983            "gift": {
2984                "gift": {
2985                    "id": "1234",
2986                    "sticker": {
2987                        "width": 512,
2988                        "height": 512,
2989                        "emoji": "😡",
2990                        "set_name": "AdvenTimeAnim",
2991                        "is_animated": true,
2992                        "is_video": false,
2993                        "type": "regular",
2994                        "thumbnail": {
2995                            "file_id": "AAMCAgADGQEAARIt0GMwiZ6n4nRbxdpM3pL8vPX6PVAhAAIjAAOw0PgMaabKAcaXKCABAAdtAAMpBA",
2996                            "file_unique_id": "AQADIwADsND4DHI",
2997                            "file_size": 4118,
2998                            "width": 128,
2999                            "height": 128
3000                        },
3001                        "file_id": "CAACAgIAAxkBAAESLdBjMImep-J0W8XaTN6S_Lz1-j1QIQACIwADsND4DGmmygHGlyggKQQ",
3002                        "file_unique_id": "AgADIwADsND4DA",
3003                        "file_size": 16639
3004                    },
3005                    "star_count": 10
3006                }
3007            }
3008        }"#;
3009        let message: Message = from_str(json).unwrap();
3010        assert_eq!(message.gift_info().unwrap().gift.id, "1234".into())
3011    }
3012
3013    #[test]
3014    fn unique_gift_info() {
3015        let json = r#"{
3016            "message_id": 27,
3017            "sender_chat": {
3018                "id": -1002236736395,
3019                "title": "Test",
3020                "type": "channel"
3021            },
3022            "chat": {
3023                "id": -1002236736395,
3024                "title": "Test",
3025                "type": "channel"
3026            },
3027            "date": 1721162577,
3028            "unique_gift": {
3029                "gift": {
3030                    "base_name": "name",
3031                    "name": "name",
3032                    "number": 123,
3033                    "model": {
3034                        "name": "name",
3035                        "sticker": {
3036                            "file_id": "CAACAgIAAxUAAWMwcTidRlq7bai-xUkcHQLa6vgJAALZBwACwRieC1FFIeQlHsPdKQQ",
3037                            "file_unique_id": "AgAD2QcAAsEYngs",
3038                            "file_size": 25734,
3039                            "width": 463,
3040                            "height": 512,
3041                            "type": "regular",
3042                            "premium_animation": null,
3043                            "is_animated": false,
3044                            "is_video": false,
3045                            "needs_repainting": false
3046                        },
3047                        "rarity_per_mille": 123
3048                    },
3049                    "symbol": {
3050                        "name": "name",
3051                        "sticker": {
3052                            "file_id": "CAACAgIAAxUAAWMwcTidRlq7bai-xUkcHQLa6vgJAALZBwACwRieC1FFIeQlHsPdKQQ",
3053                            "file_unique_id": "AgAD2QcAAsEYngs",
3054                            "file_size": 25734,
3055                            "width": 463,
3056                            "height": 512,
3057                            "type": "regular",
3058                            "premium_animation": null,
3059                            "is_animated": false,
3060                            "is_video": false,
3061                            "needs_repainting": false
3062                        },
3063                        "rarity_per_mille": 123
3064                    },
3065                    "backdrop": {
3066                        "name": "name",
3067                        "colors": {
3068                            "center_color": 0,
3069                            "edge_color": 0,
3070                            "symbol_color": 0,
3071                            "text_color": 0
3072                        },
3073                        "rarity_per_mille": 123
3074                    }
3075                },
3076                "origin": "resale"
3077            }
3078        }"#;
3079        let message: Message = from_str(json).unwrap();
3080        assert_eq!(message.unique_gift_info().unwrap().origin, UniqueGiftOrigin::Resale);
3081        assert_eq!(message.unique_gift_info().unwrap().gift.name, "name");
3082        assert_eq!(message.unique_gift_info().unwrap().gift.backdrop.rarity_per_mille, 123);
3083    }
3084
3085    #[test]
3086    fn chat_boost_added() {
3087        let json = r#"{
3088            "message_id": 28,
3089            "sender_chat": {
3090                "id": -1002236736395,
3091                "title": "Test",
3092                "type": "channel"
3093            },
3094            "chat": {
3095                "id": -1002236736395,
3096                "title": "Test",
3097                "type": "channel"
3098            },
3099            "date": 1721162702,
3100            "boost_added": {
3101                "boost_count": 4
3102            }
3103        }"#;
3104        let message: Message = from_str(json).unwrap();
3105        assert_eq!(
3106            message.boost_added().expect("Failed to get ChatBoostAdded from Message!"),
3107            &ChatBoostAdded { boost_count: 4 }
3108        )
3109    }
3110
3111    #[test]
3112    fn effect_id() {
3113        let json = r#"{
3114            "message_id": 139,
3115            "from": {
3116                "id": 1459074222,
3117                "is_bot": false,
3118                "first_name": "shadowchain",
3119                "username": "shdwchn10",
3120                "language_code": "en",
3121                "is_premium": true
3122            },
3123            "chat": {
3124                "id": 1459074222,
3125                "first_name": "shadowchain",
3126                "username": "shdwchn10",
3127                "type": "private"
3128            },
3129            "date": 1739038521,
3130            "text": "El Psy Kongroo",
3131            "effect_id": "5123233223429587601"
3132        }"#;
3133        let message: Message = from_str(json).unwrap();
3134        assert_eq!(message.effect_id().unwrap().to_string(), "5123233223429587601")
3135    }
3136
3137    #[test]
3138    fn show_caption_above_media() {
3139        let json = r#"{
3140            "message_id": 140,
3141            "from": {
3142                "id": 1459074222,
3143                "is_bot": false,
3144                "first_name": "shadowchain",
3145                "username": "shdwchn10",
3146                "language_code": "en",
3147                "is_premium": true
3148            },
3149            "chat": {
3150                "id": 1459074222,
3151                "first_name": "shadowchain",
3152                "username": "shdwchn10",
3153                "type": "private"
3154            },
3155            "date": 1739041615,
3156            "photo": [
3157                {
3158                    "file_id": "AgACAgIAAxkBAAOMZ6erTx2-IA9WEXr4NkeUY5AjdvQAArTxMRtfc0FJ1gKSaUEGAfMBAAMCAANzAAM2BA",
3159                    "file_unique_id": "AQADtPExG19zQUl4",
3160                    "file_size": 322,
3161                    "width": 59,
3162                    "height": 90
3163                },
3164                {
3165                    "file_id": "AgACAgIAAxkBAAOMZ6erTx2-IA9WEXr4NkeUY5AjdvQAArTxMRtfc0FJ1gKSaUEGAfMBAAMCAANtAAM2BA",
3166                    "file_unique_id": "AQADtPExG19zQUly",
3167                    "file_size": 358,
3168                    "width": 82,
3169                    "height": 125
3170                }
3171            ],
3172            "caption": "El Psy Kongroo",
3173            "show_caption_above_media": true
3174        }"#;
3175        let message: Message = from_str(json).unwrap();
3176        assert!(message.show_caption_above_media())
3177    }
3178}