1use std::fmt;
10#[cfg(feature = "fs")]
11use std::path::Path;
12
13use chrono::{DateTime, Utc};
14use grammers_mtsender::InvocationError;
15use grammers_session::types::{PeerId, PeerKind, PeerRef};
16use grammers_tl_types as tl;
17
18use super::{InputMessage, InputReactions};
19use crate::Client;
20use crate::media::{InputMedia, Media, Photo};
21#[cfg(any(feature = "markdown", feature = "html"))]
22use crate::parsers;
23use crate::peer::{Peer, PeerMap};
24use crate::utils;
25
26#[derive(Clone)]
32pub struct Message {
33 pub raw: tl::enums::Message,
34 pub(crate) fetched_in: Option<PeerRef>,
35 pub(crate) client: Client,
36 pub(crate) peers: PeerMap,
41}
42
43impl Message {
44 pub fn from_raw(
45 client: &Client,
46 message: tl::enums::Message,
47 fetched_in: Option<PeerRef>,
48 peers: PeerMap,
49 ) -> Self {
50 Self {
51 raw: message,
52 fetched_in,
53 client: client.clone(),
54 peers,
55 }
56 }
57
58 pub fn from_raw_short_updates(
59 client: &Client,
60 updates: tl::types::UpdateShortSentMessage,
61 input: InputMessage,
62 peer: PeerRef,
63 ) -> Self {
64 Self {
65 raw: tl::enums::Message::Message(tl::types::Message {
66 out: updates.out,
67 mentioned: false,
68 media_unread: false,
69 silent: input.silent,
70 post: false, from_scheduled: false,
72 legacy: false,
73 edit_hide: false,
74 pinned: false,
75 noforwards: false, video_processing_pending: false,
77 paid_suggested_post_stars: false,
78 invert_media: input.invert_media,
79 id: updates.id,
80 from_id: None, from_boosts_applied: None,
82 from_rank: None,
83 peer_id: peer.id.into(),
84 saved_peer_id: None,
85 fwd_from: None,
86 via_bot_id: None,
87 reply_to: input.reply_to.map(|reply_to_msg_id| {
88 tl::types::MessageReplyHeader {
89 reply_to_scheduled: false,
90 forum_topic: false,
91 quote: false,
92 reply_to_ephemeral: false,
93 reply_to_msg_id: Some(reply_to_msg_id),
94 reply_to_peer_id: None,
95 reply_from: None,
96 reply_media: None,
97 reply_to_top_id: None,
98 quote_text: None,
99 quote_entities: None,
100 quote_offset: None,
101 todo_item_id: None,
102 poll_option: None,
103 }
104 .into()
105 }),
106 date: updates.date,
107 message: input.text,
108 media: updates.media,
109 reply_markup: input.reply_markup,
110 entities: updates.entities,
111 views: None,
112 forwards: None,
113 replies: None,
114 edit_date: None,
115 post_author: None,
116 grouped_id: None,
117 restriction_reason: None,
118 ttl_period: updates.ttl_period,
119 reactions: None,
120 quick_reply_shortcut_id: None,
121 via_business_bot_id: None,
122 guestchat_via_from: None,
123 offline: false,
124 effect: None,
125 factcheck: None,
126 report_delivery_until_date: None,
127 paid_message_stars: None,
128 paid_suggested_post_ton: false,
129 suggested_post: None,
130 schedule_repeat_period: None,
131 summary_from_language: None,
132 rich_message: None,
133 }),
134 fetched_in: Some(peer),
135 client: client.clone(),
136 peers: client.empty_peer_map(),
137 }
138 }
139
140 pub fn outgoing(&self) -> bool {
143 match &self.raw {
144 tl::enums::Message::Empty(_) => false,
145 tl::enums::Message::Message(message) => message.out,
146 tl::enums::Message::Service(message) => message.out,
147 }
148 }
149
150 pub fn mentioned(&self) -> bool {
155 match &self.raw {
156 tl::enums::Message::Empty(_) => false,
157 tl::enums::Message::Message(message) => message.mentioned,
158 tl::enums::Message::Service(message) => message.mentioned,
159 }
160 }
161
162 pub fn media_unread(&self) -> bool {
166 match &self.raw {
167 tl::enums::Message::Empty(_) => false,
168 tl::enums::Message::Message(message) => message.media_unread,
169 tl::enums::Message::Service(message) => message.media_unread,
170 }
171 }
172
173 pub fn silent(&self) -> bool {
175 match &self.raw {
176 tl::enums::Message::Empty(_) => false,
177 tl::enums::Message::Message(message) => message.silent,
178 tl::enums::Message::Service(message) => message.silent,
179 }
180 }
181
182 pub fn post(&self) -> bool {
184 match &self.raw {
185 tl::enums::Message::Empty(_) => false,
186 tl::enums::Message::Message(message) => message.post,
187 tl::enums::Message::Service(message) => message.post,
188 }
189 }
190
191 pub fn from_scheduled(&self) -> bool {
193 match &self.raw {
194 tl::enums::Message::Empty(_) => false,
195 tl::enums::Message::Message(message) => message.from_scheduled,
196 tl::enums::Message::Service(_) => false,
197 }
198 }
199
200 pub fn edit_hide(&self) -> bool {
205 match &self.raw {
206 tl::enums::Message::Empty(_) => false,
207 tl::enums::Message::Message(message) => message.edit_hide,
208 tl::enums::Message::Service(_) => false,
209 }
210 }
211
212 pub fn pinned(&self) -> bool {
214 match &self.raw {
215 tl::enums::Message::Empty(_) => false,
216 tl::enums::Message::Message(message) => message.pinned,
217 tl::enums::Message::Service(_) => false,
218 }
219 }
220
221 pub fn id(&self) -> i32 {
239 self.raw.id()
240 }
241
242 pub fn peer_id(&self) -> PeerId {
244 utils::peer_from_message(&self.raw)
245 .map(PeerId::from)
246 .or_else(|| self.fetched_in.map(|peer| peer.id))
247 .expect("empty messages from updates should contain peer_id")
248 }
249
250 pub async fn peer_ref(
252 &self,
253 ) -> Result<Option<PeerRef>, Box<dyn std::error::Error + Send + Sync>> {
254 match self.fetched_in {
255 Some(peer) => Ok(Some(peer)),
256 None => self.peers.get_ref(self.peer_id()).await,
257 }
258 }
259
260 pub fn peer(&self) -> Option<&Peer> {
265 self.peers.get(self.peer_id())
266 }
267
268 pub fn sender_id(&self) -> Option<PeerId> {
270 let from_id = match &self.raw {
271 tl::enums::Message::Empty(_) => None,
272 tl::enums::Message::Message(message) => message.from_id.clone().map(PeerId::from),
273 tl::enums::Message::Service(message) => message.from_id.clone().map(PeerId::from),
274 };
275 from_id.or_else(|| {
276 let peer_id = self.peer_id();
279 if matches!(peer_id.kind(), PeerKind::User) {
280 if self.outgoing() {
281 Some(PeerId::self_user())
282 } else {
283 Some(peer_id)
284 }
285 } else {
286 None
287 }
288 })
289 }
290
291 pub async fn sender_ref(
293 &self,
294 ) -> Result<Option<PeerRef>, Box<dyn std::error::Error + Send + Sync>> {
295 match self.sender_id() {
296 None => Ok(None),
297 Some(x) => self.peers.get_ref(x).await,
298 }
299 }
300
301 pub fn sender(&self) -> Option<&Peer> {
303 self.sender_id().and_then(|id| self.peers.get(id))
304 }
305
306 pub fn forward_header(&self) -> Option<tl::enums::MessageFwdHeader> {
309 match &self.raw {
310 tl::enums::Message::Empty(_) => None,
311 tl::enums::Message::Message(message) => message.fwd_from.clone(),
312 tl::enums::Message::Service(_) => None,
313 }
314 }
315
316 pub fn via_bot_id(&self) -> Option<i64> {
318 match &self.raw {
319 tl::enums::Message::Empty(_) => None,
320 tl::enums::Message::Message(message) => message.via_bot_id,
321 tl::enums::Message::Service(_) => None,
322 }
323 }
324
325 pub fn reply_header(&self) -> Option<tl::enums::MessageReplyHeader> {
328 match &self.raw {
329 tl::enums::Message::Empty(_) => None,
330 tl::enums::Message::Message(message) => message.reply_to.clone(),
331 tl::enums::Message::Service(message) => message.reply_to.clone(),
332 }
333 }
334
335 pub(crate) fn date_timestamp(&self) -> i32 {
336 match &self.raw {
337 tl::enums::Message::Empty(_) => 0,
338 tl::enums::Message::Message(message) => message.date,
339 tl::enums::Message::Service(message) => message.date,
340 }
341 }
342
343 pub fn date(&self) -> DateTime<Utc> {
345 utils::date(self.date_timestamp())
346 }
347
348 pub fn text(&self) -> &str {
354 match &self.raw {
355 tl::enums::Message::Empty(_) => "",
356 tl::enums::Message::Message(message) => &message.message,
357 tl::enums::Message::Service(_) => "",
358 }
359 }
360
361 fn entities(&self) -> Option<&Vec<tl::enums::MessageEntity>> {
362 match &self.raw {
363 tl::enums::Message::Empty(_) => None,
364 tl::enums::Message::Message(message) => message.entities.as_ref(),
365 tl::enums::Message::Service(_) => None,
366 }
367 }
368
369 #[cfg(feature = "markdown")]
379 pub fn markdown_text(&self) -> String {
380 if let Some(entities) = self.entities() {
381 parsers::generate_markdown_message(self.text(), entities)
382 } else {
383 self.text().to_owned()
384 }
385 }
386
387 #[cfg(feature = "html")]
394 pub fn html_text(&self) -> String {
395 if let Some(entities) = self.entities() {
396 parsers::generate_html_message(self.text(), entities)
397 } else {
398 self.text().to_owned()
399 }
400 }
401
402 pub fn media(&self) -> Option<Media> {
407 let media = match &self.raw {
408 tl::enums::Message::Empty(_) => None,
409 tl::enums::Message::Message(message) => message.media.clone(),
410 tl::enums::Message::Service(_) => None,
411 };
412 media.and_then(Media::from_raw)
413 }
414
415 pub fn reply_markup(&self) -> Option<tl::enums::ReplyMarkup> {
418 match &self.raw {
419 tl::enums::Message::Empty(_) => None,
420 tl::enums::Message::Message(message) => message.reply_markup.clone(),
421 tl::enums::Message::Service(_) => None,
422 }
423 }
424
425 pub fn fmt_entities(&self) -> Option<&Vec<tl::enums::MessageEntity>> {
428 self.entities()
430 }
431
432 pub fn view_count(&self) -> Option<i32> {
437 match &self.raw {
438 tl::enums::Message::Empty(_) => None,
439 tl::enums::Message::Message(message) => message.views,
440 tl::enums::Message::Service(_) => None,
441 }
442 }
443
444 pub fn forward_count(&self) -> Option<i32> {
446 match &self.raw {
447 tl::enums::Message::Empty(_) => None,
448 tl::enums::Message::Message(message) => message.forwards,
449 tl::enums::Message::Service(_) => None,
450 }
451 }
452
453 pub fn reply_count(&self) -> Option<i32> {
455 match &self.raw {
456 tl::enums::Message::Message(tl::types::Message {
457 replies: Some(tl::enums::MessageReplies::Replies(replies)),
458 ..
459 }) => Some(replies.replies),
460 _ => None,
461 }
462 }
463
464 pub async fn react<R: Into<InputReactions>>(
499 &self,
500 reactions: R,
501 ) -> Result<(), InvocationError> {
502 self.client
503 .send_reactions(
504 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
505 self.id(),
506 reactions,
507 )
508 .await?;
509 Ok(())
510 }
511
512 pub fn reaction_count(&self) -> Option<i32> {
514 match &self.raw {
515 tl::enums::Message::Message(tl::types::Message {
516 reactions: Some(tl::enums::MessageReactions::Reactions(reactions)),
517 ..
518 }) => {
519 let count = reactions
520 .results
521 .iter()
522 .map(|reaction: &tl::enums::ReactionCount| {
523 let tl::enums::ReactionCount::Count(reaction) = reaction;
524 reaction.count
525 })
526 .sum();
527 Some(count)
528 }
529 _ => None,
530 }
531 }
532
533 pub fn edit_date(&self) -> Option<DateTime<Utc>> {
535 match &self.raw {
536 tl::enums::Message::Empty(_) => None,
537 tl::enums::Message::Message(message) => message.edit_date.map(utils::date),
538 tl::enums::Message::Service(_) => None,
539 }
540 }
541
542 pub fn post_author(&self) -> Option<&str> {
544 match &self.raw {
545 tl::enums::Message::Empty(_) => None,
546 tl::enums::Message::Message(message) => message.post_author.as_deref(),
547 tl::enums::Message::Service(_) => None,
548 }
549 }
550
551 pub fn grouped_id(&self) -> Option<i64> {
558 match &self.raw {
559 tl::enums::Message::Empty(_) => None,
560 tl::enums::Message::Message(message) => message.grouped_id,
561 tl::enums::Message::Service(_) => None,
562 }
563 }
564
565 pub fn restriction_reason(&self) -> Option<&Vec<tl::enums::RestrictionReason>> {
569 match &self.raw {
570 tl::enums::Message::Empty(_) => None,
571 tl::enums::Message::Message(message) => message.restriction_reason.as_ref(),
572 tl::enums::Message::Service(_) => None,
573 }
574 }
575
576 pub fn action(&self) -> Option<&tl::enums::MessageAction> {
578 match &self.raw {
579 tl::enums::Message::Empty(_) => None,
580 tl::enums::Message::Message(_) => None,
581 tl::enums::Message::Service(message) => Some(&message.action),
582 }
583 }
584
585 pub fn reply_to_message_id(&self) -> Option<i32> {
587 match &self.raw {
588 tl::enums::Message::Message(tl::types::Message {
589 reply_to: Some(tl::enums::MessageReplyHeader::Header(header)),
590 ..
591 }) => header.reply_to_msg_id,
592 _ => None,
593 }
594 }
595
596 pub async fn get_reply(&self) -> Result<Option<Self>, InvocationError> {
601 self.client
602 .clone() .get_reply_to_message(self)
604 .await
605 }
606
607 pub async fn respond<M: Into<InputMessage>>(
612 &self,
613 message: M,
614 ) -> Result<Self, InvocationError> {
615 self.client
616 .send_message(
617 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
618 message,
619 )
620 .await
621 }
622
623 pub async fn respond_album(
628 &self,
629 medias: Vec<InputMedia>,
630 ) -> Result<Vec<Option<Self>>, InvocationError> {
631 self.client
632 .send_album(
633 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
634 medias,
635 )
636 .await
637 }
638
639 pub async fn reply<M: Into<InputMessage>>(&self, message: M) -> Result<Self, InvocationError> {
644 let message = message.into();
645 self.client
646 .send_message(
647 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
648 message.reply_to(Some(self.id())),
649 )
650 .await
651 }
652
653 pub async fn reply_album(
658 &self,
659 mut medias: Vec<InputMedia>,
660 ) -> Result<Vec<Option<Self>>, InvocationError> {
661 medias.first_mut().unwrap().reply_to = Some(self.id());
662 self.client
663 .send_album(
664 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
665 medias,
666 )
667 .await
668 }
669
670 pub async fn forward_to<C: Into<PeerRef>>(&self, chat: C) -> Result<Self, InvocationError> {
675 self.client
679 .forward_messages(
680 chat,
681 &[self.id()],
682 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
683 )
684 .await
685 .map(|mut msgs| msgs.pop().unwrap().unwrap())
686 }
687
688 pub async fn edit<M: Into<InputMessage>>(&self, new_message: M) -> Result<(), InvocationError> {
692 self.client
693 .edit_message(
694 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
695 self.id(),
696 new_message,
697 )
698 .await
699 }
700
701 pub async fn delete(&self) -> Result<(), InvocationError> {
706 self.client
707 .delete_messages(
708 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
709 &[self.id()],
710 )
711 .await
712 .map(drop)
713 }
714
715 pub async fn mark_as_read(&self) -> Result<(), InvocationError> {
720 let peer = self.peer_ref().await?.ok_or(InvocationError::Dropped)?;
721 if peer.id.kind() == PeerKind::Channel {
722 self.client
723 .invoke(&tl::functions::channels::ReadHistory {
724 channel: peer.into(),
725 max_id: self.id(),
726 })
727 .await
728 .map(drop)
729 } else {
730 self.client
731 .invoke(&tl::functions::messages::ReadHistory {
732 peer: peer.into(),
733 max_id: self.id(),
734 })
735 .await
736 .map(drop)
737 }
738 }
739
740 pub async fn pin(&self) -> Result<(), InvocationError> {
744 self.client
745 .pin_message(
746 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
747 self.id(),
748 )
749 .await
750 }
751
752 pub async fn unpin(&self) -> Result<(), InvocationError> {
756 self.client
757 .unpin_message(
758 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
759 self.id(),
760 )
761 .await
762 }
763
764 pub async fn refetch(&self) -> Result<(), InvocationError> {
770 self.client
773 .get_messages_by_id(
774 self.peer_ref().await?.ok_or(InvocationError::Dropped)?,
775 &[self.id()],
776 )
777 .await?
778 .pop()
779 .unwrap()
780 .unwrap();
781 todo!("actually mutate self after get_messages_by_id returns `Message`")
782 }
783
784 #[cfg(feature = "fs")]
790 pub async fn download_media<P: AsRef<Path>>(&self, path: P) -> Result<bool, InvocationError> {
791 if let Some(media) = self.media() {
793 self.client.download_media(&media, path).await.map(|_| true)
794 } else {
795 Ok(false)
796 }
797 }
798
799 pub fn photo(&self) -> Option<Photo> {
801 if let Media::Photo(photo) = self.media()? {
802 return Some(photo);
803 }
804
805 None
806 }
807}
808
809impl fmt::Debug for Message {
810 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
811 f.debug_struct("Message")
812 .field("id", &self.id())
813 .field("outgoing", &self.outgoing())
814 .field("date", &self.date())
815 .field("text", &self.text())
816 .field("peer", &self.peer())
817 .field("sender", &self.sender())
818 .field("reply_to_message_id", &self.reply_to_message_id())
819 .field("via_bot_id", &self.via_bot_id())
820 .field("media", &self.media())
821 .field("mentioned", &self.mentioned())
822 .field("media_unread", &self.media_unread())
823 .field("silent", &self.silent())
824 .field("post", &self.post())
825 .field("from_scheduled", &self.from_scheduled())
826 .field("edit_hide", &self.edit_hide())
827 .field("pinned", &self.pinned())
828 .field("forward_header", &self.forward_header())
829 .field("reply_header", &self.reply_header())
830 .field("reply_markup", &self.reply_markup())
831 .field("fmt_entities", &self.fmt_entities())
832 .field("view_count", &self.view_count())
833 .field("forward_count", &self.forward_count())
834 .field("reply_count", &self.reply_count())
835 .field("edit_date", &self.edit_date())
836 .field("post_author", &self.post_author())
837 .field("grouped_id", &self.grouped_id())
838 .field("restriction_reason", &self.restriction_reason())
839 .field("action", &self.action())
840 .finish()
841 }
842}