1#![allow(clippy::large_enum_variant)]
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use url::Url;
6
7use crate::types::{
8 Animation, Audio, BareChatId, Chat, ChatId, Contact, Dice, Document, ForumTopicClosed,
9 ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden,
10 GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location,
11 MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, PassportData,
12 PhotoSize, Poll, ProximityAlertTriggered, Sticker, SuccessfulPayment, True, User, Venue, Video,
13 VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote,
14 Voice, WebAppData, WriteAccessAllowed,
15};
16
17#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct Message {
22 #[serde(flatten)]
24 pub id: MessageId,
25
26 #[serde(rename = "message_thread_id")]
30 pub thread_id: Option<i32>,
31
32 #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
34 pub date: DateTime<Utc>,
35
36 pub chat: Chat,
38
39 pub via_bot: Option<User>,
41
42 #[serde(flatten)]
43 pub kind: MessageKind,
44}
45
46#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
50#[serde(untagged)]
51pub enum MessageKind {
52 Common(MessageCommon),
53 NewChatMembers(MessageNewChatMembers),
54 LeftChatMember(MessageLeftChatMember),
55 NewChatTitle(MessageNewChatTitle),
56 NewChatPhoto(MessageNewChatPhoto),
57 DeleteChatPhoto(MessageDeleteChatPhoto),
58 GroupChatCreated(MessageGroupChatCreated),
59 SupergroupChatCreated(MessageSupergroupChatCreated),
60 ChannelChatCreated(MessageChannelChatCreated),
61 MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged),
62 Pinned(MessagePinned),
63 Invoice(MessageInvoice),
64 SuccessfulPayment(MessageSuccessfulPayment),
65 ConnectedWebsite(MessageConnectedWebsite),
66 WriteAccessAllowed(MessageWriteAccessAllowed),
67 PassportData(MessagePassportData),
68 Dice(MessageDice),
69 ProximityAlertTriggered(MessageProximityAlertTriggered),
70 ForumTopicCreated(MessageForumTopicCreated),
71 ForumTopicEdited(MessageForumTopicEdited),
72 ForumTopicClosed(MessageForumTopicClosed),
73 ForumTopicReopened(MessageForumTopicReopened),
74 GeneralForumTopicHidden(MessageGeneralForumTopicHidden),
75 GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden),
76 VideoChatScheduled(MessageVideoChatScheduled),
77 VideoChatStarted(MessageVideoChatStarted),
78 VideoChatEnded(MessageVideoChatEnded),
79 VideoChatParticipantsInvited(MessageVideoChatParticipantsInvited),
80 WebAppData(MessageWebAppData),
81}
82
83#[serde_with_macros::skip_serializing_none]
84#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
85pub struct MessageCommon {
86 pub from: Option<User>,
88
89 pub sender_chat: Option<Chat>,
94
95 pub author_signature: Option<String>,
98
99 #[serde(flatten)]
101 pub forward: Option<Forward>,
102
103 pub reply_to_message: Option<Box<Message>>,
107
108 #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")]
110 pub edit_date: Option<DateTime<Utc>>,
111
112 #[serde(flatten)]
113 pub media_kind: MediaKind,
114
115 pub reply_markup: Option<InlineKeyboardMarkup>,
118
119 #[serde(default)]
123 pub is_topic_message: bool,
124
125 #[serde(default)]
128 pub is_automatic_forward: bool,
129
130 #[serde(default)]
132 pub has_protected_content: bool,
133}
134
135#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
136pub struct MessageNewChatMembers {
137 pub new_chat_members: Vec<User>,
141}
142
143#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
144pub struct MessageLeftChatMember {
145 pub left_chat_member: User,
148}
149
150#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
151pub struct MessageNewChatTitle {
152 pub new_chat_title: String,
154}
155
156#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
157pub struct MessageNewChatPhoto {
158 pub new_chat_photo: Vec<PhotoSize>,
160}
161
162#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
163pub struct MessageDeleteChatPhoto {
164 pub delete_chat_photo: True,
166}
167
168#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
169pub struct MessageGroupChatCreated {
170 pub group_chat_created: True,
172}
173
174#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
175pub struct MessageSupergroupChatCreated {
176 pub supergroup_chat_created: True,
182}
183
184#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
185pub struct MessageChannelChatCreated {
186 pub channel_chat_created: True,
192}
193
194#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
195pub struct MessageMessageAutoDeleteTimerChanged {
196 pub message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged,
198}
199
200#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
211#[serde(untagged)]
212pub enum ChatMigration {
213 To {
216 #[serde(rename = "migrate_to_chat_id")]
217 chat_id: ChatId,
218 },
219
220 From {
223 #[serde(rename = "migrate_from_chat_id")]
224 chat_id: ChatId,
225 },
226}
227
228#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
229pub struct MessagePinned {
230 #[serde(rename = "pinned_message")]
234 pub pinned: Box<Message>,
235}
236
237#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
238pub struct MessageInvoice {
239 pub invoice: Invoice,
245}
246
247#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
248pub struct MessageSuccessfulPayment {
249 pub successful_payment: SuccessfulPayment,
254}
255
256#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
257pub struct MessageConnectedWebsite {
258 pub connected_website: String,
263}
264
265#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
266pub struct MessagePassportData {
267 pub passport_data: PassportData,
269}
270
271#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
273pub struct Forward {
274 #[serde(rename = "forward_date")]
276 #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
277 pub date: DateTime<Utc>,
278
279 #[serde(flatten)]
281 pub from: ForwardedFrom,
282
283 #[serde(rename = "forward_signature")]
287 pub signature: Option<String>,
288
289 #[serde(rename = "forward_from_message_id")]
292 pub message_id: Option<i32>,
293}
294
295#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
297pub enum ForwardedFrom {
298 #[serde(rename = "forward_from")]
300 User(User),
301 #[serde(rename = "forward_from_chat")]
304 Chat(Chat),
305 #[serde(rename = "forward_sender_name")]
308 SenderName(String),
309}
310
311#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
312#[serde(untagged)]
313pub enum MediaKind {
314 Animation(MediaAnimation),
323 Audio(MediaAudio),
324 Contact(MediaContact),
325 Document(MediaDocument),
326 Game(MediaGame),
327 Venue(MediaVenue),
328 Location(MediaLocation),
329 Photo(MediaPhoto),
330 Poll(MediaPoll),
331 Sticker(MediaSticker),
332 Text(MediaText),
333 Video(MediaVideo),
334 VideoNote(MediaVideoNote),
335 Voice(MediaVoice),
336 Migration(ChatMigration),
337}
338
339#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
340pub struct MediaAnimation {
341 pub animation: Animation,
345
346 pub caption: Option<String>,
348
349 #[serde(default = "Vec::new")]
352 pub caption_entities: Vec<MessageEntity>,
353
354 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
356 pub has_media_spoiler: bool,
357 }
359
360#[serde_with_macros::skip_serializing_none]
361#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
362pub struct MediaAudio {
363 pub audio: Audio,
365
366 pub caption: Option<String>,
368
369 #[serde(default = "Vec::new")]
372 pub caption_entities: Vec<MessageEntity>,
373
374 pub media_group_id: Option<String>,
377}
378
379#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
380pub struct MediaContact {
381 pub contact: Contact,
383}
384
385#[serde_with_macros::skip_serializing_none]
386#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
387pub struct MediaDocument {
388 pub document: Document,
390
391 pub caption: Option<String>,
393
394 #[serde(default)]
397 pub caption_entities: Vec<MessageEntity>,
398
399 pub media_group_id: Option<String>,
402}
403
404#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
405pub struct MediaGame {
406 pub game: Game,
411}
412
413#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
414pub struct MediaLocation {
415 pub location: Location,
417}
418
419#[serde_with_macros::skip_serializing_none]
420#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
421pub struct MediaPhoto {
422 pub photo: Vec<PhotoSize>,
424
425 pub caption: Option<String>,
427
428 #[serde(default = "Vec::new")]
431 pub caption_entities: Vec<MessageEntity>,
432
433 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
435 pub has_media_spoiler: bool,
436
437 pub media_group_id: Option<String>,
440}
441
442#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
443pub struct MediaPoll {
444 pub poll: Poll,
446}
447
448#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
449pub struct MediaSticker {
450 pub sticker: Sticker,
452}
453
454#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
455pub struct MediaText {
456 pub text: String,
459
460 #[serde(default = "Vec::new")]
463 pub entities: Vec<MessageEntity>,
464}
465
466#[serde_with_macros::skip_serializing_none]
467#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
468pub struct MediaVideo {
469 pub video: Video,
471
472 pub caption: Option<String>,
474
475 #[serde(default = "Vec::new")]
478 pub caption_entities: Vec<MessageEntity>,
479
480 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
482 pub has_media_spoiler: bool,
483
484 pub media_group_id: Option<String>,
487}
488
489#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
490pub struct MediaVideoNote {
491 pub video_note: VideoNote,
495}
496
497#[serde_with_macros::skip_serializing_none]
498#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
499pub struct MediaVoice {
500 pub voice: Voice,
502
503 pub caption: Option<String>,
505
506 #[serde(default = "Vec::new")]
509 pub caption_entities: Vec<MessageEntity>,
510}
511
512#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
513pub struct MediaVenue {
514 pub venue: Venue,
516 }
518
519#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
520pub struct MessageDice {
521 pub dice: Dice,
523}
524
525#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
526pub struct MessageProximityAlertTriggered {
527 pub proximity_alert_triggered: ProximityAlertTriggered,
530}
531
532#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
533pub struct MessageWriteAccessAllowed {
534 pub write_access_allowed: WriteAccessAllowed,
537}
538
539#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
540pub struct MessageForumTopicCreated {
541 pub forum_topic_created: ForumTopicCreated,
543}
544
545#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
546pub struct MessageForumTopicEdited {
547 pub forum_topic_edited: ForumTopicEdited,
549}
550
551#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
552pub struct MessageForumTopicClosed {
553 pub forum_topic_closed: ForumTopicClosed,
555}
556
557#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
558pub struct MessageForumTopicReopened {
559 pub forum_topic_reopened: ForumTopicReopened,
561}
562
563#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
564pub struct MessageGeneralForumTopicHidden {
565 pub general_forum_topic_hidden: GeneralForumTopicHidden,
567}
568
569#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
570pub struct MessageGeneralForumTopicUnhidden {
571 pub general_forum_topic_unhidden: GeneralForumTopicUnhidden,
573}
574
575#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
576pub struct MessageVideoChatScheduled {
577 pub video_chat_scheduled: VideoChatScheduled,
579}
580
581#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
582pub struct MessageVideoChatStarted {
583 pub video_chat_started: VideoChatStarted,
585}
586
587#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
588pub struct MessageVideoChatEnded {
589 pub video_chat_ended: VideoChatEnded,
591}
592
593#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
594pub struct MessageVideoChatParticipantsInvited {
595 pub video_chat_participants_invited: VideoChatParticipantsInvited,
597}
598
599#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
600pub struct MessageWebAppData {
601 pub web_app_data: WebAppData,
603}
604
605mod getters {
606 use chrono::{DateTime, Utc};
607 use std::ops::Deref;
608
609 use crate::types::{
610 self, message::MessageKind::*, Chat, ChatId, ChatMigration, Forward, ForwardedFrom,
611 MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind,
612 MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue, MediaVideo,
613 MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon,
614 MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity,
615 MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers,
616 MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned,
617 MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated,
618 MessageVideoChatParticipantsInvited, PhotoSize, True, User,
619 };
620
621 impl Message {
626 #[must_use]
628 pub fn from(&self) -> Option<&User> {
629 match &self.kind {
630 Common(MessageCommon { from, .. }) => from.as_ref(),
631 _ => None,
632 }
633 }
634
635 #[must_use]
636 pub fn author_signature(&self) -> Option<&str> {
637 match &self.kind {
638 Common(MessageCommon { author_signature, .. }) => author_signature.as_deref(),
639 _ => None,
640 }
641 }
642
643 #[must_use]
644 pub fn sender_chat(&self) -> Option<&Chat> {
645 match &self.kind {
646 Common(MessageCommon { sender_chat, .. }) => sender_chat.as_ref(),
647 _ => None,
648 }
649 }
650
651 #[deprecated(since = "0.4.2", note = "use `.chat.id` field instead")]
652 #[must_use]
653 pub fn chat_id(&self) -> ChatId {
654 self.chat.id
655 }
656
657 #[must_use]
658 pub fn forward(&self) -> Option<&Forward> {
659 self.common().and_then(|m| m.forward.as_ref())
660 }
661
662 #[must_use]
663 pub fn forward_date(&self) -> Option<DateTime<Utc>> {
664 self.forward().map(|f| f.date)
665 }
666
667 #[must_use]
668 pub fn forward_from(&self) -> Option<&ForwardedFrom> {
669 self.forward().map(|f| &f.from)
670 }
671
672 #[must_use]
673 pub fn forward_from_user(&self) -> Option<&User> {
674 self.forward_from().and_then(|from| match from {
675 ForwardedFrom::User(user) => Some(user),
676 _ => None,
677 })
678 }
679
680 #[must_use]
681 pub fn forward_from_chat(&self) -> Option<&Chat> {
682 self.forward_from().and_then(|from| match from {
683 ForwardedFrom::Chat(chat) => Some(chat),
684 _ => None,
685 })
686 }
687
688 #[must_use]
689 pub fn forward_from_sender_name(&self) -> Option<&str> {
690 self.forward_from().and_then(|from| match from {
691 ForwardedFrom::SenderName(sender_name) => Some(&**sender_name),
692 _ => None,
693 })
694 }
695
696 #[must_use]
697 pub fn forward_from_message_id(&self) -> Option<i32> {
698 self.forward().and_then(|f| f.message_id)
699 }
700
701 #[must_use]
702 pub fn forward_signature(&self) -> Option<&str> {
703 self.forward().and_then(|f| f.signature.as_deref())
704 }
705
706 #[must_use]
707 pub fn reply_to_message(&self) -> Option<&Message> {
708 self.common().and_then(|m| m.reply_to_message.as_deref())
709 }
710
711 #[must_use]
712 pub fn edit_date(&self) -> Option<&DateTime<Utc>> {
713 match &self.kind {
714 Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(),
715 _ => None,
716 }
717 }
718
719 #[must_use]
720 pub fn media_group_id(&self) -> Option<&str> {
721 match &self.kind {
722 Common(MessageCommon {
723 media_kind: MediaKind::Video(MediaVideo { media_group_id, .. }),
724 ..
725 })
726 | Common(MessageCommon {
727 media_kind: MediaKind::Photo(MediaPhoto { media_group_id, .. }),
728 ..
729 })
730 | Common(MessageCommon {
731 media_kind: MediaKind::Document(MediaDocument { media_group_id, .. }),
732 ..
733 })
734 | Common(MessageCommon {
735 media_kind: MediaKind::Audio(MediaAudio { media_group_id, .. }),
736 ..
737 }) => media_group_id.as_ref().map(Deref::deref),
738 _ => None,
739 }
740 }
741
742 #[must_use]
743 pub fn text(&self) -> Option<&str> {
744 match &self.kind {
745 Common(MessageCommon {
746 media_kind: MediaKind::Text(MediaText { text, .. }),
747 ..
748 }) => Some(text),
749 _ => None,
750 }
751 }
752
753 #[must_use]
766 pub fn entities(&self) -> Option<&[MessageEntity]> {
767 match &self.kind {
768 Common(MessageCommon {
769 media_kind: MediaKind::Text(MediaText { entities, .. }),
770 ..
771 }) => Some(entities),
772 _ => None,
773 }
774 }
775
776 #[must_use]
789 pub fn caption_entities(&self) -> Option<&[MessageEntity]> {
790 match &self.kind {
791 Common(MessageCommon {
792 media_kind: MediaKind::Animation(MediaAnimation { caption_entities, .. }),
793 ..
794 })
795 | Common(MessageCommon {
796 media_kind: MediaKind::Audio(MediaAudio { caption_entities, .. }),
797 ..
798 })
799 | Common(MessageCommon {
800 media_kind: MediaKind::Document(MediaDocument { caption_entities, .. }),
801 ..
802 })
803 | Common(MessageCommon {
804 media_kind: MediaKind::Photo(MediaPhoto { caption_entities, .. }),
805 ..
806 })
807 | Common(MessageCommon {
808 media_kind: MediaKind::Video(MediaVideo { caption_entities, .. }),
809 ..
810 })
811 | Common(MessageCommon {
812 media_kind: MediaKind::Voice(MediaVoice { caption_entities, .. }),
813 ..
814 }) => Some(caption_entities),
815 _ => None,
816 }
817 }
818
819 #[must_use]
826 pub fn has_media_spoiler(&self) -> bool {
827 self.common()
828 .map(|m| match m.media_kind {
829 MediaKind::Animation(MediaAnimation { has_media_spoiler, .. })
830 | MediaKind::Photo(MediaPhoto { has_media_spoiler, .. })
831 | MediaKind::Video(MediaVideo { has_media_spoiler, .. }) => has_media_spoiler,
832 MediaKind::Audio(_)
833 | MediaKind::Contact(_)
834 | MediaKind::Document(_)
835 | MediaKind::Game(_)
836 | MediaKind::Venue(_)
837 | MediaKind::Location(_)
838 | MediaKind::Poll(_)
839 | MediaKind::Sticker(_)
840 | MediaKind::Text(_)
841 | MediaKind::VideoNote(_)
842 | MediaKind::Voice(_)
843 | MediaKind::Migration(_) => false,
844 })
845 .unwrap_or(false)
846 }
847
848 #[must_use]
849 pub fn audio(&self) -> Option<&types::Audio> {
850 match &self.kind {
851 Common(MessageCommon {
852 media_kind: MediaKind::Audio(MediaAudio { audio, .. }),
853 ..
854 }) => Some(audio),
855 _ => None,
856 }
857 }
858
859 #[must_use]
860 pub fn document(&self) -> Option<&types::Document> {
861 match &self.kind {
862 Common(MessageCommon {
863 media_kind: MediaKind::Document(MediaDocument { document, .. }),
864 ..
865 }) => Some(document),
866 _ => None,
867 }
868 }
869
870 #[must_use]
871 pub fn animation(&self) -> Option<&types::Animation> {
872 match &self.kind {
873 Common(MessageCommon {
874 media_kind: MediaKind::Animation(MediaAnimation { animation, .. }),
875 ..
876 }) => Some(animation),
877 _ => None,
878 }
879 }
880
881 #[must_use]
882 pub fn game(&self) -> Option<&types::Game> {
883 match &self.kind {
884 Common(MessageCommon {
885 media_kind: MediaKind::Game(MediaGame { game, .. }),
886 ..
887 }) => Some(game),
888 _ => None,
889 }
890 }
891
892 #[must_use]
893 pub fn photo(&self) -> Option<&[PhotoSize]> {
894 match &self.kind {
895 Common(MessageCommon {
896 media_kind: MediaKind::Photo(MediaPhoto { photo, .. }),
897 ..
898 }) => Some(photo),
899 _ => None,
900 }
901 }
902
903 #[must_use]
904 pub fn sticker(&self) -> Option<&types::Sticker> {
905 match &self.kind {
906 Common(MessageCommon {
907 media_kind: MediaKind::Sticker(MediaSticker { sticker, .. }),
908 ..
909 }) => Some(sticker),
910 _ => None,
911 }
912 }
913
914 #[must_use]
915 pub fn video(&self) -> Option<&types::Video> {
916 match &self.kind {
917 Common(MessageCommon {
918 media_kind: MediaKind::Video(MediaVideo { video, .. }),
919 ..
920 }) => Some(video),
921 _ => None,
922 }
923 }
924
925 #[must_use]
926 pub fn voice(&self) -> Option<&types::Voice> {
927 match &self.kind {
928 Common(MessageCommon {
929 media_kind: MediaKind::Voice(MediaVoice { voice, .. }),
930 ..
931 }) => Some(voice),
932 _ => None,
933 }
934 }
935
936 #[must_use]
937 pub fn video_note(&self) -> Option<&types::VideoNote> {
938 match &self.kind {
939 Common(MessageCommon {
940 media_kind: MediaKind::VideoNote(MediaVideoNote { video_note, .. }),
941 ..
942 }) => Some(video_note),
943 _ => None,
944 }
945 }
946
947 #[must_use]
948 pub fn caption(&self) -> Option<&str> {
949 match &self.kind {
950 Common(MessageCommon { media_kind, .. }) => match media_kind {
951 MediaKind::Animation(MediaAnimation { caption, .. })
952 | MediaKind::Audio(MediaAudio { caption, .. })
953 | MediaKind::Document(MediaDocument { caption, .. })
954 | MediaKind::Photo(MediaPhoto { caption, .. })
955 | MediaKind::Video(MediaVideo { caption, .. })
956 | MediaKind::Voice(MediaVoice { caption, .. }) => {
957 caption.as_ref().map(Deref::deref)
958 }
959 _ => None,
960 },
961 _ => None,
962 }
963 }
964
965 #[must_use]
966 pub fn contact(&self) -> Option<&types::Contact> {
967 match &self.kind {
968 Common(MessageCommon {
969 media_kind: MediaKind::Contact(MediaContact { contact, .. }),
970 ..
971 }) => Some(contact),
972 _ => None,
973 }
974 }
975
976 #[must_use]
977 pub fn location(&self) -> Option<&types::Location> {
978 match &self.kind {
979 Common(MessageCommon {
980 media_kind: MediaKind::Location(MediaLocation { location, .. }),
981 ..
982 }) => Some(location),
983 _ => None,
984 }
985 }
986
987 #[must_use]
988 pub fn venue(&self) -> Option<&types::Venue> {
989 match &self.kind {
990 Common(MessageCommon {
991 media_kind: MediaKind::Venue(MediaVenue { venue, .. }),
992 ..
993 }) => Some(venue),
994 _ => None,
995 }
996 }
997
998 #[must_use]
999 pub fn poll(&self) -> Option<&types::Poll> {
1000 match &self.kind {
1001 Common(MessageCommon {
1002 media_kind: MediaKind::Poll(MediaPoll { poll, .. }),
1003 ..
1004 }) => Some(poll),
1005 _ => None,
1006 }
1007 }
1008
1009 #[must_use]
1010 pub fn new_chat_members(&self) -> Option<&[User]> {
1011 match &self.kind {
1012 NewChatMembers(MessageNewChatMembers { new_chat_members }) => {
1013 Some(new_chat_members.as_ref())
1014 }
1015 _ => None,
1016 }
1017 }
1018
1019 #[must_use]
1020 pub fn left_chat_member(&self) -> Option<&User> {
1021 match &self.kind {
1022 LeftChatMember(MessageLeftChatMember { left_chat_member }) => {
1023 Some(left_chat_member)
1024 }
1025 _ => None,
1026 }
1027 }
1028
1029 #[must_use]
1030 pub fn new_chat_title(&self) -> Option<&str> {
1031 match &self.kind {
1032 NewChatTitle(MessageNewChatTitle { new_chat_title }) => Some(new_chat_title),
1033 _ => None,
1034 }
1035 }
1036
1037 #[must_use]
1038 pub fn new_chat_photo(&self) -> Option<&[PhotoSize]> {
1039 match &self.kind {
1040 NewChatPhoto(MessageNewChatPhoto { new_chat_photo }) => Some(new_chat_photo),
1041 _ => None,
1042 }
1043 }
1044
1045 #[must_use]
1048 pub fn delete_chat_photo(&self) -> Option<True> {
1049 match &self.kind {
1050 DeleteChatPhoto(MessageDeleteChatPhoto { delete_chat_photo }) => {
1051 Some(*delete_chat_photo)
1052 }
1053 _ => None,
1054 }
1055 }
1056
1057 #[must_use]
1058 pub fn group_chat_created(&self) -> Option<True> {
1059 match &self.kind {
1060 GroupChatCreated(MessageGroupChatCreated { group_chat_created }) => {
1061 Some(*group_chat_created)
1062 }
1063 _ => None,
1064 }
1065 }
1066
1067 #[must_use]
1068 pub fn super_group_chat_created(&self) -> Option<True> {
1069 match &self.kind {
1070 SupergroupChatCreated(MessageSupergroupChatCreated { supergroup_chat_created }) => {
1071 Some(*supergroup_chat_created)
1072 }
1073 _ => None,
1074 }
1075 }
1076
1077 #[must_use]
1078 pub fn channel_chat_created(&self) -> Option<True> {
1079 match &self.kind {
1080 ChannelChatCreated(MessageChannelChatCreated { channel_chat_created }) => {
1081 Some(*channel_chat_created)
1082 }
1083 _ => None,
1084 }
1085 }
1086
1087 #[must_use]
1088 pub fn chat_migration(&self) -> Option<ChatMigration> {
1089 match &self.kind {
1090 Common(MessageCommon {
1091 media_kind: MediaKind::Migration(chat_migration), ..
1092 }) => Some(*chat_migration),
1093 _ => None,
1094 }
1095 }
1096
1097 #[must_use]
1098 pub fn migrate_to_chat_id(&self) -> Option<ChatId> {
1099 match &self.kind {
1100 Common(MessageCommon {
1101 media_kind: MediaKind::Migration(ChatMigration::To { chat_id }),
1102 ..
1103 }) => Some(*chat_id),
1104 _ => None,
1105 }
1106 }
1107
1108 #[must_use]
1109 pub fn migrate_from_chat_id(&self) -> Option<ChatId> {
1110 match &self.kind {
1111 Common(MessageCommon {
1112 media_kind: MediaKind::Migration(ChatMigration::From { chat_id }),
1113 ..
1114 }) => Some(*chat_id),
1115 _ => None,
1116 }
1117 }
1118
1119 #[must_use]
1120 pub fn pinned_message(&self) -> Option<&Message> {
1121 match &self.kind {
1122 Pinned(MessagePinned { pinned }) => Some(pinned),
1123 _ => None,
1124 }
1125 }
1126
1127 #[must_use]
1128 pub fn invoice(&self) -> Option<&types::Invoice> {
1129 match &self.kind {
1130 Invoice(MessageInvoice { invoice }) => Some(invoice),
1131 _ => None,
1132 }
1133 }
1134
1135 #[must_use]
1136 pub fn successful_payment(&self) -> Option<&types::SuccessfulPayment> {
1137 match &self.kind {
1138 SuccessfulPayment(MessageSuccessfulPayment { successful_payment }) => {
1139 Some(successful_payment)
1140 }
1141 _ => None,
1142 }
1143 }
1144
1145 #[must_use]
1146 pub fn connected_website(&self) -> Option<&str> {
1147 match &self.kind {
1148 ConnectedWebsite(MessageConnectedWebsite { connected_website }) => {
1149 Some(connected_website)
1150 }
1151 _ => None,
1152 }
1153 }
1154
1155 #[must_use]
1156 pub fn passport_data(&self) -> Option<&types::PassportData> {
1157 match &self.kind {
1158 PassportData(MessagePassportData { passport_data }) => Some(passport_data),
1159 _ => None,
1160 }
1161 }
1162
1163 #[must_use]
1164 pub fn dice(&self) -> Option<&types::Dice> {
1165 match &self.kind {
1166 Dice(MessageDice { dice }) => Some(dice),
1167 _ => None,
1168 }
1169 }
1170
1171 #[must_use]
1172 pub fn proximity_alert_triggered(&self) -> Option<&types::ProximityAlertTriggered> {
1173 match &self.kind {
1174 ProximityAlertTriggered(MessageProximityAlertTriggered {
1175 proximity_alert_triggered,
1176 }) => Some(proximity_alert_triggered),
1177 _ => None,
1178 }
1179 }
1180
1181 #[must_use]
1182 pub fn video_chat_participants_invited(
1183 &self,
1184 ) -> Option<&types::VideoChatParticipantsInvited> {
1185 match &self.kind {
1186 VideoChatParticipantsInvited(MessageVideoChatParticipantsInvited {
1187 video_chat_participants_invited,
1188 }) => Some(video_chat_participants_invited),
1189 _ => None,
1190 }
1191 }
1192
1193 #[must_use]
1194 pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> {
1195 match &self.kind {
1196 Common(MessageCommon { reply_markup, .. }) => reply_markup.as_ref(),
1197 _ => None,
1198 }
1199 }
1200
1201 #[must_use]
1202 pub fn is_automatic_forward(&self) -> bool {
1203 match &self.kind {
1204 Common(MessageCommon { is_automatic_forward, .. }) => *is_automatic_forward,
1205 _ => false,
1206 }
1207 }
1208
1209 #[must_use]
1210 pub fn has_protected_content(&self) -> bool {
1211 match &self.kind {
1212 Common(MessageCommon { has_protected_content, .. }) => *has_protected_content,
1213 _ => false,
1214 }
1215 }
1216
1217 fn common(&self) -> Option<&MessageCommon> {
1219 match &self.kind {
1220 Common(message) => Some(message),
1221 _ => None,
1222 }
1223 }
1224
1225 }
1227}
1228
1229impl Message {
1230 #[must_use]
1238 pub fn url(&self) -> Option<Url> {
1239 Self::url_of(self.chat.id, self.chat.username(), self.id)
1240 }
1241
1242 #[track_caller]
1256 #[must_use]
1257 pub fn url_of(
1258 chat_id: ChatId,
1259 chat_username: Option<&str>,
1260 message_id: MessageId,
1261 ) -> Option<Url> {
1262 use BareChatId::*;
1263
1264 let chat_id = match chat_id.to_bare() {
1266 User(_) => return None,
1273 Group(_) => return None,
1278 Channel(id) => id,
1279 };
1280
1281 let url = match chat_username {
1282 Some(username) => format!("https://t.me/{0}/{1}", username, message_id.0),
1285 None => format!("https://t.me/c/{0}/{1}", chat_id, message_id.0),
1288 };
1289
1290 Some(url::Url::parse(&url).unwrap())
1295 }
1296
1297 #[must_use]
1305 pub fn comment_url(&self, comment_id: MessageId) -> Option<Url> {
1306 Self::comment_url_of(self.chat.id, self.chat.username(), self.id, comment_id)
1307 }
1308
1309 #[must_use]
1324 pub fn comment_url_of(
1325 channel_id: ChatId,
1326 channel_username: Option<&str>,
1327 post_id: MessageId,
1328 comment_id: MessageId,
1329 ) -> Option<Url> {
1330 Self::url_of(channel_id, channel_username, post_id).map(|mut url| {
1331 url.set_query(Some(&format!("comment={}", comment_id.0)));
1332 url
1333 })
1334 }
1335
1336 #[must_use]
1348 pub fn url_in_thread(&self, thread_starter_msg_id: MessageId) -> Option<Url> {
1349 Self::url_in_thread_of(self.chat.id, self.chat.username(), thread_starter_msg_id, self.id)
1350 }
1351
1352 #[must_use]
1371 pub fn url_in_thread_of(
1372 chat_id: ChatId,
1373 chat_username: Option<&str>,
1374 thread_starter_msg_id: MessageId,
1375 message_id: MessageId,
1376 ) -> Option<Url> {
1377 Self::url_of(chat_id, chat_username, message_id).map(|mut url| {
1378 url.set_query(Some(&format!("thread={}", thread_starter_msg_id.0)));
1379 url
1380 })
1381 }
1382
1383 #[must_use]
1393 pub fn parse_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
1394 self.text().zip(self.entities()).map(|(t, e)| MessageEntityRef::parse(t, e))
1395 }
1396
1397 #[must_use]
1406 pub fn parse_caption_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
1407 self.caption().zip(self.caption_entities()).map(|(t, e)| MessageEntityRef::parse(t, e))
1408 }
1409
1410 pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
1418 use crate::util::{flatten, mentioned_users_from_entities};
1419
1420 self.from()
1423 .into_iter()
1424 .chain(self.via_bot.as_ref())
1425 .chain(self.chat.mentioned_users_rec())
1426 .chain(flatten(self.reply_to_message().map(Self::mentioned_users_rec)))
1427 .chain(flatten(self.new_chat_members()))
1428 .chain(self.left_chat_member())
1429 .chain(self.forward_from_user())
1430 .chain(flatten(self.forward_from_chat().map(Chat::mentioned_users_rec)))
1431 .chain(flatten(self.game().map(Game::mentioned_users)))
1432 .chain(flatten(self.entities().map(mentioned_users_from_entities)))
1433 .chain(flatten(self.caption_entities().map(mentioned_users_from_entities)))
1434 .chain(flatten(self.poll().map(Poll::mentioned_users)))
1435 .chain(flatten(self.proximity_alert_triggered().map(|a| [&a.traveler, &a.watcher])))
1436 .chain(flatten(self.video_chat_participants_invited().and_then(|i| i.users.as_deref())))
1437 }
1438
1439 pub(crate) fn mentioned_users_rec(&self) -> Box<dyn Iterator<Item = &User> + Send + Sync + '_> {
1443 Box::new(self.mentioned_users())
1444 }
1445}
1446
1447#[cfg(test)]
1448mod tests {
1449 use serde_json::from_str;
1450
1451 use crate::types::*;
1452
1453 #[test]
1454 fn de_media_forwarded() {
1455 let json = r#"{
1456 "message_id": 198283,
1457 "from": {
1458 "id": 250918540,
1459 "is_bot": false,
1460 "first_name": "Андрей",
1461 "last_name": "Власов",
1462 "username": "aka_dude",
1463 "language_code": "en"
1464 },
1465 "chat": {
1466 "id": 250918540,
1467 "first_name": "Андрей",
1468 "last_name": "Власов",
1469 "username": "aka_dude",
1470 "type": "private"
1471 },
1472 "date": 1567927221,
1473 "video": {
1474 "duration": 13,
1475 "width": 512,
1476 "height": 640,
1477 "mime_type": "video/mp4",
1478 "thumb": {
1479 "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE",
1480 "file_unique_id":"",
1481 "file_size": 10339,
1482 "width": 256,
1483 "height": 320
1484 },
1485 "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE",
1486 "file_unique_id":"",
1487 "file_size": 1381334
1488 }
1489 }"#;
1490 let message = from_str::<Message>(json);
1491 assert!(message.is_ok());
1492 }
1493
1494 #[test]
1495 fn de_media_group_forwarded() {
1496 let json = r#"{
1497 "message_id": 198283,
1498 "from": {
1499 "id": 250918540,
1500 "is_bot": false,
1501 "first_name": "Андрей",
1502 "last_name": "Власов",
1503 "username": "aka_dude",
1504 "language_code": "en"
1505 },
1506 "chat": {
1507 "id": 250918540,
1508 "first_name": "Андрей",
1509 "last_name": "Власов",
1510 "username": "aka_dude",
1511 "type": "private"
1512 },
1513 "date": 1567927221,
1514 "media_group_id": "12543417770506682",
1515 "video": {
1516 "duration": 13,
1517 "width": 512,
1518 "height": 640,
1519 "mime_type": "video/mp4",
1520 "thumb": {
1521 "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE",
1522 "file_unique_id":"",
1523 "file_size": 10339,
1524 "width": 256,
1525 "height": 320
1526 },
1527 "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE",
1528 "file_unique_id":"",
1529 "file_size": 1381334
1530 }
1531 }"#;
1532 let message = from_str::<Message>(json);
1533 assert!(message.is_ok());
1534 }
1535
1536 #[test]
1537 fn de_text() {
1538 let json = r#"{
1539 "message_id": 199785,
1540 "from": {
1541 "id": 250918540,
1542 "is_bot": false,
1543 "first_name": "Андрей",
1544 "last_name": "Власов",
1545 "username": "aka_dude",
1546 "language_code": "en"
1547 },
1548 "chat": {
1549 "id": 250918540,
1550 "first_name": "Андрей",
1551 "last_name": "Власов",
1552 "username": "aka_dude",
1553 "type": "private"
1554 },
1555 "date": 1568289890,
1556 "text": "Лол кек 😂"
1557 }"#;
1558 let message = from_str::<Message>(json);
1559 assert!(message.is_ok());
1560 }
1561
1562 #[test]
1563 fn de_sticker() {
1564 let json = r#"{
1565 "message_id": 199787,
1566 "from": {
1567 "id": 250918540,
1568 "is_bot": false,
1569 "first_name": "Андрей",
1570 "last_name": "Власов",
1571 "username": "aka_dude",
1572 "language_code": "en"
1573 },
1574 "chat": {
1575 "id": 250918540,
1576 "first_name": "Андрей",
1577 "last_name": "Власов",
1578 "username": "aka_dude",
1579 "type": "private"
1580 },
1581 "date": 1568290188,
1582 "sticker": {
1583 "width": 512,
1584 "height": 512,
1585 "emoji": "😡",
1586 "set_name": "AdvenTimeAnim",
1587 "is_animated": true,
1588 "is_video": false,
1589 "type": "regular",
1590 "thumb": {
1591 "file_id": "AAMCAgADGQEAARIt0GMwiZ6n4nRbxdpM3pL8vPX6PVAhAAIjAAOw0PgMaabKAcaXKCABAAdtAAMpBA",
1592 "file_unique_id": "AQADIwADsND4DHI",
1593 "file_size": 4118,
1594 "width": 128,
1595 "height": 128
1596 },
1597 "file_id": "CAACAgIAAxkBAAESLdBjMImep-J0W8XaTN6S_Lz1-j1QIQACIwADsND4DGmmygHGlyggKQQ",
1598 "file_unique_id": "AgADIwADsND4DA",
1599 "file_size": 16639
1600 }
1601 }"#;
1602 from_str::<Message>(json).unwrap();
1603 }
1604
1605 #[test]
1606 fn de_image() {
1607 let json = r#"{
1608 "message_id": 199791,
1609 "from": {
1610 "id": 250918540,
1611 "is_bot": false,
1612 "first_name": "Андрей",
1613 "last_name": "Власов",
1614 "username": "aka_dude",
1615 "language_code": "en"
1616 },
1617 "chat": {
1618 "id": 250918540,
1619 "first_name": "Андрей",
1620 "last_name": "Власов",
1621 "username": "aka_dude",
1622 "type": "private"
1623 },
1624 "date": 1568290622,
1625 "photo": [
1626 {
1627 "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA20AAybcBAABFgQ",
1628 "file_unique_id":"",
1629 "file_size": 18188,
1630 "width": 320,
1631 "height": 239
1632 },
1633 {
1634 "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3gAAyfcBAABFgQ",
1635 "file_unique_id":"",
1636 "file_size": 62123,
1637 "width": 800,
1638 "height": 598
1639 },
1640 {
1641 "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3kAAyTcBAABFgQ",
1642 "file_unique_id":"",
1643 "file_size": 75245,
1644 "width": 962,
1645 "height": 719
1646 }
1647 ]
1648 }"#;
1649 let message = from_str::<Message>(json);
1650 assert!(message.is_ok());
1651 }
1652
1653 #[test]
1655 fn issue_419() {
1656 let json = r#"{
1657 "message_id": 1,
1658 "from": {
1659 "id": 1087968824,
1660 "is_bot": true,
1661 "first_name": "Group",
1662 "username": "GroupAnonymousBot"
1663 },
1664 "author_signature": "TITLE2",
1665 "sender_chat": {
1666 "id": -1001160242915,
1667 "title": "a",
1668 "type": "supergroup"
1669 },
1670 "chat": {
1671 "id": -1001160242915,
1672 "title": "a",
1673 "type": "supergroup"
1674 },
1675 "date": 1640359576,
1676 "forward_from_chat": {
1677 "id": -1001160242915,
1678 "title": "a",
1679 "type": "supergroup"
1680 },
1681 "forward_signature": "TITLE",
1682 "forward_date": 1640359544,
1683 "text": "text"
1684 }"#;
1685
1686 let message: Message = serde_json::from_str(json).unwrap();
1690
1691 let group = Chat {
1692 id: ChatId(-1001160242915),
1693 kind: ChatKind::Public(ChatPublic {
1694 title: Some("a".to_owned()),
1695 kind: PublicChatKind::Supergroup(PublicChatSupergroup {
1696 username: None,
1697 sticker_set_name: None,
1698 can_set_sticker_set: None,
1699 permissions: None,
1700 slow_mode_delay: None,
1701 linked_chat_id: None,
1702 location: None,
1703 join_by_request: None,
1704 join_to_send_messages: None,
1705 active_usernames: None,
1706 is_forum: false,
1707 }),
1708 description: None,
1709 invite_link: None,
1710 has_protected_content: None,
1711 }),
1712 message_auto_delete_time: None,
1713 photo: None,
1714 pinned_message: None,
1715 has_hidden_members: false,
1716 has_aggressive_anti_spam_enabled: false,
1717 };
1718
1719 assert!(message.from().unwrap().is_anonymous());
1720 assert_eq!(message.author_signature().unwrap(), "TITLE2");
1721 assert_eq!(message.sender_chat().unwrap(), &group);
1722 assert_eq!(&message.chat, &group);
1723 assert_eq!(message.forward_from_chat().unwrap(), &group);
1724 assert_eq!(message.forward_signature().unwrap(), "TITLE");
1725 assert!(message.forward_date().is_some());
1726 assert_eq!(message.text().unwrap(), "text");
1727 }
1728
1729 #[test]
1731 fn issue_427() {
1732 let old = ChatId(-599075523);
1733 let new = ChatId(-1001555296434);
1734
1735 let json = r#"{"chat":{"all_members_are_administrators":false,"id":-599075523,"title":"test","type":"group"},"date":1629404938,"from":{"first_name":"nullptr","id":729497414,"is_bot":false,"language_code":"en","username":"hex0x0000"},"message_id":16,"migrate_to_chat_id":-1001555296434}"#;
1737 let message: Message = from_str(json).unwrap();
1738
1739 assert_eq!(message.chat.id, old);
1740 assert_eq!(message.chat_migration(), Some(ChatMigration::To { chat_id: new }));
1741 assert_eq!(message.migrate_to_chat_id(), Some(new));
1742
1743 assert!(message.from().is_some());
1745
1746 let json = r#"{"chat":{"id":-1001555296434,"title":"test","type":"supergroup"},"date":1629404938,"from":{"first_name":"Group","id":1087968824,"is_bot":true,"username":"GroupAnonymousBot"},"message_id":1,"migrate_from_chat_id":-599075523,"sender_chat":{"id":-1001555296434,"title":"test","type":"supergroup"}}"#;
1748 let message: Message = from_str(json).unwrap();
1749
1750 assert_eq!(message.chat.id, new);
1751 assert_eq!(message.chat_migration(), Some(ChatMigration::From { chat_id: old }));
1752 assert_eq!(message.migrate_from_chat_id(), Some(old));
1753
1754 assert!(message.from().is_some());
1756
1757 assert!(message.sender_chat().is_some());
1759 }
1760
1761 #[test]
1763 fn issue_481() {
1764 let json = r#"
1765{
1766 "message_id": 0,
1767 "date": 0,
1768 "location": {
1769 "latitude": 0.0,
1770 "longitude": 0.0
1771 },
1772 "chat": {
1773 "id": 0,
1774 "first_name": "f",
1775 "type": "private"
1776 },
1777 "venue": {
1778 "location": {
1779 "latitude": 0.0,
1780 "longitude": 0.0
1781 },
1782 "title": "Title",
1783 "address": "Address",
1784 "foursquare_id": "some_foursquare_id"
1785 }
1786 }
1787"#;
1788 let message: Message = from_str(json).unwrap();
1789 assert_eq!(
1790 message.venue().unwrap(),
1791 &Venue {
1792 location: Location {
1793 longitude: 0.0,
1794 latitude: 0.0,
1795 horizontal_accuracy: None,
1796 live_period: None,
1797 heading: None,
1798 proximity_alert_radius: None
1799 },
1800 title: "Title".to_owned(),
1801 address: "Address".to_owned(),
1802 foursquare_id: Some("some_foursquare_id".to_owned()),
1803 foursquare_type: None,
1804 google_place_id: None,
1805 google_place_type: None,
1806 }
1807 )
1808 }
1809
1810 #[test]
1812 fn issue_475() {
1813 let json = r#"{"message_id":198295,"from":{"id":1087968824,"is_bot":true,"first_name":"Group","username":"GroupAnonymousBot"},"sender_chat":{"id":-1001331354980,"title":"C++ Together 2.0","username":"cpptogether","type":"supergroup"},"chat":{"id":-1001331354980,"title":"C++ Together 2.0","username":"cpptogether","type":"supergroup"},"date":1638236631,"video_chat_started":{}}"#;
1814
1815 let message: Message = serde_json::from_str(json).unwrap();
1816
1817 assert!(matches!(message.kind, MessageKind::VideoChatStarted { .. }));
1818
1819 }
1823
1824 #[test]
1825 fn parse_caption_entities() {
1826 let json = r#"
1827 {
1828 "message_id": 3460,
1829 "from": {
1830 "id": 27433968,
1831 "is_bot": false,
1832 "first_name": "Crax | rats addict",
1833 "username": "tacocrasco",
1834 "language_code": "en"
1835 },
1836 "chat": {
1837 "id": 27433968,
1838 "first_name": "Crax | rats addict",
1839 "username": "tacocrasco",
1840 "type": "private"
1841 },
1842 "date": 1655671349,
1843 "photo": [
1844 {
1845 "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADcwADJAQ",
1846 "file_unique_id": "AQADabwxGxJmgVF4",
1847 "file_size": 2077,
1848 "width": 90,
1849 "height": 90
1850 },
1851 {
1852 "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADbQADJAQ",
1853 "file_unique_id": "AQADabwxGxJmgVFy",
1854 "file_size": 27640,
1855 "width": 320,
1856 "height": 320
1857 },
1858 {
1859 "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADeAADJAQ",
1860 "file_unique_id": "AQADabwxGxJmgVF9",
1861 "file_size": 99248,
1862 "width": 800,
1863 "height": 800
1864 },
1865 {
1866 "file_id": "AgACAgQAAxkBAAINhGKvijUVSn2i3980bQIIc1fqWGNCAAJpvDEbEmaBUfuA43fR-BnlAQADAgADeQADJAQ",
1867 "file_unique_id": "AQADabwxGxJmgVF-",
1868 "file_size": 162061,
1869 "width": 1280,
1870 "height": 1280
1871 }
1872 ],
1873 "caption": "www.example.com",
1874 "caption_entities": [
1875 {
1876 "offset": 0,
1877 "length": 15,
1878 "type": "url"
1879 }
1880 ]
1881 }"#;
1882
1883 let message: Message = serde_json::from_str(json).unwrap();
1884 let entities = message.parse_caption_entities();
1885 assert!(entities.is_some());
1886
1887 let entities = entities.unwrap();
1888 assert!(!entities.is_empty());
1889 assert_eq!(entities[0].kind().clone(), MessageEntityKind::Url);
1890 }
1891
1892 #[test]
1893 fn topic_created() {
1894 let json = r#"{
1895 "chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},
1896 "date":1675229139,
1897 "forum_topic_created":{
1898 "icon_color":9367192,
1899 "icon_custom_emoji_id":"5312536423851630001",
1900 "name":"???"
1901 },
1902 "from":{
1903 "first_name":"вафель'",
1904 "id":1253681278,
1905 "is_bot":false,
1906 "language_code":"en",
1907 "username":"wafflelapkin"
1908 },
1909 "is_topic_message":true,
1910 "message_id":4,
1911 "message_thread_id":4
1912 }"#;
1913
1914 let _: Message = serde_json::from_str(json).unwrap();
1915 }
1916
1917 #[test]
1918 fn topic_message() {
1919 let json = r#"{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229140,"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":5,"message_thread_id":4,"reply_to_message":{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229139,"forum_topic_created":{"icon_color":9367192,"icon_custom_emoji_id":"5312536423851630001","name":"???"},"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":4,"message_thread_id":4},"text":"blah"}"#;
1920
1921 let _: Message = serde_json::from_str(json).unwrap();
1922 }
1923}