Skip to main content

grammers_client/message/
message.rs

1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use 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/// Represents a Telegram message, which includes text messages, messages with media, and service
27/// messages.
28///
29/// This message should be treated as a snapshot in time, that is, if the message is edited while
30/// using this object, those changes won't alter this structure.
31#[derive(Clone)]
32pub struct Message {
33    pub raw: tl::enums::Message,
34    pub(crate) fetched_in: Option<PeerRef>,
35    pub(crate) client: Client,
36    // When fetching messages or receiving updates, a set of peers will be present. A single
37    // server response contains a lot of peers, and some might be related to deep layers of
38    // a message action for instance. Keeping the entire set like this allows for cheaper clones
39    // and moves, and saves us from worrying about picking out all the peers we care about.
40    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, // TODO true if sent to broadcast channel
71                from_scheduled: false,
72                legacy: false,
73                edit_hide: false,
74                pinned: false,
75                noforwards: false, // TODO true if channel has noforwads?
76                video_processing_pending: false,
77                paid_suggested_post_stars: false,
78                invert_media: input.invert_media,
79                id: updates.id,
80                from_id: None, // TODO self
81                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    /// Whether the message is outgoing (i.e. you sent this message to some other peer) or
141    /// incoming (i.e. someone else sent it to you or the peer).
142    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    /// Whether you were mentioned in this message or not.
151    ///
152    /// This includes @username mentions, text mentions, and messages replying to one of your
153    /// previous messages (even if it contains no mention in the message text).
154    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    /// Whether you have read the media in this message or not.
163    ///
164    /// Most commonly, these are voice notes that you have not played yet.
165    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    /// Whether the message should notify people with sound or not.
174    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    /// Whether this message is a post in a broadcast channel or not.
183    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    /// Whether this message was originated from a previously-scheduled message or not.
192    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    // `legacy` is not exposed, though it can be if it proves to be useful
201
202    /// Whether the edited mark of this message is edited should be hidden (e.g. in GUI clients)
203    /// or shown.
204    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    /// Whether this message is currently pinned or not.
213    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    /// The ID of this message.
222    ///
223    /// Message identifiers are counters that start at 1 and grow by 1 for each message produced.
224    ///
225    /// Every channel has its own unique message counter. This counter is the same for all users,
226    /// but unique to each channel.
227    ///
228    /// Every account has another unique message counter which is used for private conversations
229    /// and small group chats. This means different accounts will likely have different message
230    /// identifiers for the same message in a private conversation or small group chat. This also
231    /// implies that the message identifier alone is enough to uniquely identify the message,
232    /// without the need to know the chat ID.
233    ///
234    /// **You cannot use the message ID of User A when running as User B**, unless this message
235    /// belongs to a megagroup or broadcast channel. Beware of this when using methods like
236    /// [`Client::delete_messages`], which **cannot** validate the peer where the message
237    /// should be deleted for those cases.
238    pub fn id(&self) -> i32 {
239        self.raw.id()
240    }
241
242    /// The [`Self::peer`]'s identifier.
243    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    /// Cached reference to the [`Self::peer`], if it is in cache.
251    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    /// The peer where this message was sent to.
261    ///
262    /// This might be the user you're talking to for private conversations, or the group or
263    /// channel where the message was sent.
264    pub fn peer(&self) -> Option<&Peer> {
265        self.peers.get(self.peer_id())
266    }
267
268    /// The [`Self::sender`]'s identifier, if there is a sender.
269    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            // Incoming messages in private conversations don't include `from_id` since
277            // layer 119, but the sender can only be the peer we're in.
278            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    /// Cached reference to the [`Self::sender`], if there is a sender and they are in cache.
292    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    /// The sender of this message, if there is a sender **and** the sender is in cache.
302    pub fn sender(&self) -> Option<&Peer> {
303        self.sender_id().and_then(|id| self.peers.get(id))
304    }
305
306    /// If this message was forwarded from a previous message, return the header with information
307    /// about that forward.
308    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    /// If this message was sent @via some inline bot, return the bot's user identifier.
317    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    /// If this message is replying to a previous message, return the header with information
326    /// about that reply.
327    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    /// The date when this message was produced.
344    pub fn date(&self) -> DateTime<Utc> {
345        utils::date(self.date_timestamp())
346    }
347
348    /// The message's text.
349    ///
350    /// For service or empty messages, this will be the empty strings.
351    ///
352    /// If the message has media, this text is the caption commonly displayed underneath it.
353    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    /// Like [`text`](Self::text), but with the [`fmt_entities`](Self::fmt_entities)
370    /// applied to produce a markdown string instead.
371    ///
372    /// Some formatting entities automatically added by Telegram, such as bot commands or
373    /// clickable emails, are ignored in the generated string, as those do not need to be
374    /// sent for Telegram to include them in the message.
375    ///
376    /// Formatting entities which cannot be represented in CommonMark without resorting to HTML,
377    /// such as underline, are also ignored.
378    #[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    /// Like [`text`](Self::text), but with the [`fmt_entities`](Self::fmt_entities)
388    /// applied to produce a HTML string instead.
389    ///
390    /// Some formatting entities automatically added by Telegram, such as bot commands or
391    /// clickable emails, are ignored in the generated string, as those do not need to be
392    /// sent for Telegram to include them in the message.
393    #[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    /// The media displayed by this message, if any.
403    ///
404    /// This not only includes photos or videos, but also contacts, polls, documents, locations
405    /// and many other types.
406    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    /// If the message has a reply markup (which can happen for messages produced by bots),
416    /// returns said markup.
417    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    /// The formatting entities used to format this message, such as bold, italic, with their
426    /// offsets and lengths.
427    pub fn fmt_entities(&self) -> Option<&Vec<tl::enums::MessageEntity>> {
428        // TODO correct the offsets and lengths to match the byte offsets
429        self.entities()
430    }
431
432    /// How many views does this message have, when applicable.
433    ///
434    /// The same user account can contribute to increment this counter indefinitedly, however
435    /// there is a server-side cooldown limitting how fast it can happen (several hours).
436    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    /// How many times has this message been forwarded, when applicable.
445    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    /// How many replies does this message have, when applicable.
454    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    /// React to this message.
465    ///
466    /// # Examples
467    ///
468    /// ```
469    /// # async fn f(message: grammers_client::message::Message, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
470    /// message.react("👍").await?;
471    /// # Ok(())
472    /// # }
473    /// ```
474    ///
475    /// Make animation big & Add to recent
476    ///
477    /// ```
478    /// # async fn f(message: grammers_client::message::Message, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
479    /// use grammers_client::message::InputReactions;
480    ///
481    /// let reactions = InputReactions::emoticon("🤯").big().add_to_recent();
482    ///
483    /// message.react(reactions).await?;
484    /// # Ok(())
485    /// # }
486    /// ```
487    ///
488    /// Remove reactions
489    ///
490    /// ```
491    /// # async fn f(message: grammers_client::message::Message, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
492    /// use grammers_client::message::InputReactions;
493    ///
494    /// message.react(InputReactions::remove()).await?;
495    /// # Ok(())
496    /// # }
497    /// ```
498    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    /// How many reactions does this message have, when applicable.
513    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    /// The date when this message was last edited.
534    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    /// If this message was sent to a channel, return the name used by the author to post it.
543    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    /// If this message belongs to a group of messages, return the unique identifier for that
552    /// group.
553    ///
554    /// This applies to albums of media, such as multiple photos grouped together.
555    ///
556    /// Note that there may be messages sent in between the messages forming a group.
557    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    /// A list of reasons on why this message is restricted.
566    ///
567    /// The message is not restricted if the return value is `None`.
568    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    /// If this message is a service message, return the service action that occured.
577    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    /// If this message is replying to another message, return the replied message ID.
586    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    /// Fetch the message that this message is replying to, or `None` if this message is not a
597    /// reply to a previous message.
598    ///
599    /// Shorthand for `Client::get_reply_to_message`.
600    pub async fn get_reply(&self) -> Result<Option<Self>, InvocationError> {
601        self.client
602            .clone() // TODO don't clone
603            .get_reply_to_message(self)
604            .await
605    }
606
607    /// Respond to this message by sending a new message to the same peer, but without directly
608    /// replying to it.
609    ///
610    /// Shorthand for `Client::send_message`.
611    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    /// Respond to this message by sending a album in the same peer, but without directly
624    /// replying to it.
625    ///
626    /// Shorthand for `Client::send_album`.
627    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    /// Directly reply to this message by sending a new message to the same peer that replies to
640    /// it. This methods overrides the `reply_to` on the `InputMessage` to point to `self`.
641    ///
642    /// Shorthand for `Client::send_message`.
643    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    /// Directly reply to this message by sending a album to the same peer that replies to
654    /// it. This methods overrides the `reply_to` on the first `InputMedia` to point to `self`.
655    ///
656    /// Shorthand for `Client::send_album`.
657    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    /// Forward this message to another (or the same) peer.
671    ///
672    /// Shorthand for `Client::forward_messages`. If you need to forward multiple messages
673    /// at once, consider using that method instead.
674    pub async fn forward_to<C: Into<PeerRef>>(&self, chat: C) -> Result<Self, InvocationError> {
675        // TODO return `Message`
676        // When forwarding a single message, if it fails, Telegram should respond with RPC error.
677        // If it succeeds we will have the single forwarded message present which we can unwrap.
678        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    /// Edit this message to change its text or media.
689    ///
690    /// Shorthand for `Client::edit_message`.
691    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    /// Delete this message for everyone.
702    ///
703    /// Shorthand for `Client::delete_messages`. If you need to delete multiple messages
704    /// at once, consider using that method instead.
705    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    /// Mark this message and all messages above it as read.
716    ///
717    /// Unlike `Client::mark_as_read`, this method only will mark the conversation as read up to
718    /// this message, not the entire conversation.
719    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    /// Pin this message in the conversation.
741    ///
742    /// Shorthand for `Client::pin_message`.
743    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    /// Unpin this message from the conversation.
753    ///
754    /// Shorthand for `Client::unpin_message`.
755    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    /// Refetch this message, mutating all of its properties in-place.
765    ///
766    /// No changes will be made to the message if it fails to be fetched.
767    ///
768    /// Shorthand for `Client::get_messages_by_id`.
769    pub async fn refetch(&self) -> Result<(), InvocationError> {
770        // When fetching a single message, if it fails, Telegram should respond with RPC error.
771        // If it succeeds we will have the single message present which we can unwrap.
772        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    /// Download the message media in this message if applicable.
785    ///
786    /// Returns `true` if there was media to download, or `false` otherwise.
787    ///
788    /// Shorthand for `Client::download_media`.
789    #[cfg(feature = "fs")]
790    pub async fn download_media<P: AsRef<Path>>(&self, path: P) -> Result<bool, InvocationError> {
791        // TODO probably encode failed download in error
792        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    /// Get photo attached to the message if any.
800    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}