grammers_client/types/
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.
8use crate::PeerMap;
9#[cfg(any(feature = "markdown", feature = "html"))]
10use crate::parsers;
11use crate::types::reactions::InputReactions;
12use crate::types::{InputMessage, Media, Photo};
13use crate::{Client, types};
14use crate::{InputMedia, utils};
15use chrono::{DateTime, Utc};
16use grammers_mtsender::InvocationError;
17use grammers_session::defs::{PeerAuth, PeerId, PeerKind, PeerRef};
18use grammers_tl_types as tl;
19use std::fmt;
20use std::sync::Arc;
21
22#[cfg(feature = "fs")]
23use std::{io, path::Path};
24
25/// Represents a Telegram message, which includes text messages, messages with media, and service
26/// messages.
27///
28/// This message should be treated as a snapshot in time, that is, if the message is edited while
29/// using this object, those changes won't alter this structure.
30#[derive(Clone)]
31pub struct Message {
32    pub raw: tl::enums::Message,
33    pub(crate) fetched_in: Option<PeerRef>,
34    pub(crate) client: Client,
35    // When fetching messages or receiving updates, a set of peers will be present. A single
36    // server response contains a lot of peers, and some might be related to deep layers of
37    // a message action for instance. Keeping the entire set like this allows for cheaper clones
38    // and moves, and saves us from worrying about picking out all the peers we care about.
39    pub(crate) peers: Arc<PeerMap>,
40}
41
42impl Message {
43    pub fn from_raw(
44        client: &Client,
45        message: tl::enums::Message,
46        fetched_in: Option<PeerRef>,
47        peers: &Arc<PeerMap>,
48    ) -> Self {
49        Self {
50            raw: message,
51            fetched_in,
52            client: client.clone(),
53            peers: Arc::clone(peers),
54        }
55    }
56
57    pub fn from_raw_short_updates(
58        client: &Client,
59        updates: tl::types::UpdateShortSentMessage,
60        input: InputMessage,
61        peer: PeerRef,
62    ) -> Self {
63        Self {
64            raw: tl::enums::Message::Message(tl::types::Message {
65                out: updates.out,
66                mentioned: false,
67                media_unread: false,
68                silent: input.silent,
69                post: false, // TODO true if sent to broadcast channel
70                from_scheduled: false,
71                legacy: false,
72                edit_hide: false,
73                pinned: false,
74                noforwards: false, // TODO true if channel has noforwads?
75                video_processing_pending: false,
76                paid_suggested_post_stars: false,
77                invert_media: input.invert_media,
78                id: updates.id,
79                from_id: None, // TODO self
80                from_boosts_applied: None,
81                peer_id: peer.id.into(),
82                saved_peer_id: None,
83                fwd_from: None,
84                via_bot_id: None,
85                reply_to: input.reply_to.map(|reply_to_msg_id| {
86                    tl::types::MessageReplyHeader {
87                        reply_to_scheduled: false,
88                        forum_topic: false,
89                        quote: false,
90                        reply_to_msg_id: Some(reply_to_msg_id),
91                        reply_to_peer_id: None,
92                        reply_from: None,
93                        reply_media: None,
94                        reply_to_top_id: None,
95                        quote_text: None,
96                        quote_entities: None,
97                        quote_offset: None,
98                        todo_item_id: None,
99                    }
100                    .into()
101                }),
102                date: updates.date,
103                message: input.text,
104                media: updates.media,
105                reply_markup: input.reply_markup,
106                entities: updates.entities,
107                views: None,
108                forwards: None,
109                replies: None,
110                edit_date: None,
111                post_author: None,
112                grouped_id: None,
113                restriction_reason: None,
114                ttl_period: updates.ttl_period,
115                reactions: None,
116                quick_reply_shortcut_id: None,
117                via_business_bot_id: None,
118                offline: false,
119                effect: None,
120                factcheck: None,
121                report_delivery_until_date: None,
122                paid_message_stars: None,
123                paid_suggested_post_ton: false,
124                suggested_post: None,
125            }),
126            fetched_in: Some(peer),
127            client: client.clone(),
128            peers: PeerMap::empty(),
129        }
130    }
131
132    /// Whether the message is outgoing (i.e. you sent this message to some other peer) or
133    /// incoming (i.e. someone else sent it to you or the peer).
134    pub fn outgoing(&self) -> bool {
135        match &self.raw {
136            tl::enums::Message::Empty(_) => false,
137            tl::enums::Message::Message(message) => message.out,
138            tl::enums::Message::Service(message) => message.out,
139        }
140    }
141
142    /// Whether you were mentioned in this message or not.
143    ///
144    /// This includes @username mentions, text mentions, and messages replying to one of your
145    /// previous messages (even if it contains no mention in the message text).
146    pub fn mentioned(&self) -> bool {
147        match &self.raw {
148            tl::enums::Message::Empty(_) => false,
149            tl::enums::Message::Message(message) => message.mentioned,
150            tl::enums::Message::Service(message) => message.mentioned,
151        }
152    }
153
154    /// Whether you have read the media in this message or not.
155    ///
156    /// Most commonly, these are voice notes that you have not played yet.
157    pub fn media_unread(&self) -> bool {
158        match &self.raw {
159            tl::enums::Message::Empty(_) => false,
160            tl::enums::Message::Message(message) => message.media_unread,
161            tl::enums::Message::Service(message) => message.media_unread,
162        }
163    }
164
165    /// Whether the message should notify people with sound or not.
166    pub fn silent(&self) -> bool {
167        match &self.raw {
168            tl::enums::Message::Empty(_) => false,
169            tl::enums::Message::Message(message) => message.silent,
170            tl::enums::Message::Service(message) => message.silent,
171        }
172    }
173
174    /// Whether this message is a post in a broadcast channel or not.
175    pub fn post(&self) -> bool {
176        match &self.raw {
177            tl::enums::Message::Empty(_) => false,
178            tl::enums::Message::Message(message) => message.post,
179            tl::enums::Message::Service(message) => message.post,
180        }
181    }
182
183    /// Whether this message was originated from a previously-scheduled message or not.
184    pub fn from_scheduled(&self) -> bool {
185        match &self.raw {
186            tl::enums::Message::Empty(_) => false,
187            tl::enums::Message::Message(message) => message.from_scheduled,
188            tl::enums::Message::Service(_) => false,
189        }
190    }
191
192    // `legacy` is not exposed, though it can be if it proves to be useful
193
194    /// Whether the edited mark of this message is edited should be hidden (e.g. in GUI clients)
195    /// or shown.
196    pub fn edit_hide(&self) -> bool {
197        match &self.raw {
198            tl::enums::Message::Empty(_) => false,
199            tl::enums::Message::Message(message) => message.edit_hide,
200            tl::enums::Message::Service(_) => false,
201        }
202    }
203
204    /// Whether this message is currently pinned or not.
205    pub fn pinned(&self) -> bool {
206        match &self.raw {
207            tl::enums::Message::Empty(_) => false,
208            tl::enums::Message::Message(message) => message.pinned,
209            tl::enums::Message::Service(_) => false,
210        }
211    }
212
213    /// The ID of this message.
214    ///
215    /// Message identifiers are counters that start at 1 and grow by 1 for each message produced.
216    ///
217    /// Every channel has its own unique message counter. This counter is the same for all users,
218    /// but unique to each channel.
219    ///
220    /// Every account has another unique message counter which is used for private conversations
221    /// and small group chats. This means different accounts will likely have different message
222    /// identifiers for the same message in a private conversation or small group chat. This also
223    /// implies that the message identifier alone is enough to uniquely identify the message,
224    /// without the need to know the chat ID.
225    ///
226    /// **You cannot use the message ID of User A when running as User B**, unless this message
227    /// belongs to a megagroup or broadcast channel. Beware of this when using methods like
228    /// [`Client::delete_messages`], which **cannot** validate the peer where the message
229    /// should be deleted for those cases.
230    pub fn id(&self) -> i32 {
231        self.raw.id()
232    }
233
234    pub(crate) fn peer_ref(&self) -> PeerRef {
235        utils::peer_from_message(&self.raw)
236            .map(|peer| PeerRef {
237                id: PeerId::from(peer),
238                auth: PeerAuth::default(),
239            })
240            .or(self.fetched_in)
241            .expect("empty messages from updates should contain peer_id")
242    }
243
244    /// The sender of this message, if any.
245    pub fn sender(&self) -> Option<&types::Peer> {
246        let from_id = match &self.raw {
247            tl::enums::Message::Empty(_) => None,
248            tl::enums::Message::Message(message) => message.from_id.clone().map(PeerId::from),
249            tl::enums::Message::Service(message) => message.from_id.clone().map(PeerId::from),
250        };
251        from_id
252            .or({
253                // Incoming messages in private conversations don't include `from_id` since
254                // layer 119, but the sender can only be the peer we're in.
255                let peer_id = self.peer_id();
256                if matches!(peer_id.kind(), PeerKind::User | PeerKind::UserSelf) {
257                    if self.outgoing() {
258                        let user_id = self
259                            .client
260                            .0
261                            .session
262                            .peer(PeerId::self_user())
263                            .unwrap()
264                            .id();
265                        Some(user_id)
266                    } else {
267                        Some(peer_id)
268                    }
269                } else {
270                    None
271                }
272            })
273            .and_then(|from| self.peers.get(from))
274    }
275
276    /// The peer where this message was sent to.
277    ///
278    /// This might be the user you're talking to for private conversations, or the group or
279    /// channel where the message was sent.
280    pub fn peer(&self) -> Result<&types::Peer, PeerRef> {
281        let peer = self.peer_ref();
282        self.peers.get(peer.id).ok_or(peer)
283    }
284
285    pub fn peer_id(&self) -> PeerId {
286        self.peer_ref().id
287    }
288
289    /// If this message was forwarded from a previous message, return the header with information
290    /// about that forward.
291    pub fn forward_header(&self) -> Option<tl::enums::MessageFwdHeader> {
292        match &self.raw {
293            tl::enums::Message::Empty(_) => None,
294            tl::enums::Message::Message(message) => message.fwd_from.clone(),
295            tl::enums::Message::Service(_) => None,
296        }
297    }
298
299    /// If this message was sent @via some inline bot, return the bot's user identifier.
300    pub fn via_bot_id(&self) -> Option<i64> {
301        match &self.raw {
302            tl::enums::Message::Empty(_) => None,
303            tl::enums::Message::Message(message) => message.via_bot_id,
304            tl::enums::Message::Service(_) => None,
305        }
306    }
307
308    /// If this message is replying to a previous message, return the header with information
309    /// about that reply.
310    pub fn reply_header(&self) -> Option<tl::enums::MessageReplyHeader> {
311        match &self.raw {
312            tl::enums::Message::Empty(_) => None,
313            tl::enums::Message::Message(message) => message.reply_to.clone(),
314            tl::enums::Message::Service(message) => message.reply_to.clone(),
315        }
316    }
317
318    pub(crate) fn date_timestamp(&self) -> i32 {
319        match &self.raw {
320            tl::enums::Message::Empty(_) => 0,
321            tl::enums::Message::Message(message) => message.date,
322            tl::enums::Message::Service(message) => message.date,
323        }
324    }
325
326    /// The date when this message was produced.
327    pub fn date(&self) -> DateTime<Utc> {
328        utils::date(self.date_timestamp())
329    }
330
331    /// The message's text.
332    ///
333    /// For service or empty messages, this will be the empty strings.
334    ///
335    /// If the message has media, this text is the caption commonly displayed underneath it.
336    pub fn text(&self) -> &str {
337        match &self.raw {
338            tl::enums::Message::Empty(_) => "",
339            tl::enums::Message::Message(message) => &message.message,
340            tl::enums::Message::Service(_) => "",
341        }
342    }
343
344    fn entities(&self) -> Option<&Vec<tl::enums::MessageEntity>> {
345        match &self.raw {
346            tl::enums::Message::Empty(_) => None,
347            tl::enums::Message::Message(message) => message.entities.as_ref(),
348            tl::enums::Message::Service(_) => None,
349        }
350    }
351
352    /// Like [`text`](Self::text), but with the [`fmt_entities`](Self::fmt_entities)
353    /// applied to produce a markdown string instead.
354    ///
355    /// Some formatting entities automatically added by Telegram, such as bot commands or
356    /// clickable emails, are ignored in the generated string, as those do not need to be
357    /// sent for Telegram to include them in the message.
358    ///
359    /// Formatting entities which cannot be represented in CommonMark without resorting to HTML,
360    /// such as underline, are also ignored.
361    #[cfg(feature = "markdown")]
362    pub fn markdown_text(&self) -> String {
363        if let Some(entities) = self.entities() {
364            parsers::generate_markdown_message(self.text(), entities)
365        } else {
366            self.text().to_owned()
367        }
368    }
369
370    /// Like [`text`](Self::text), but with the [`fmt_entities`](Self::fmt_entities)
371    /// applied to produce a HTML string instead.
372    ///
373    /// Some formatting entities automatically added by Telegram, such as bot commands or
374    /// clickable emails, are ignored in the generated string, as those do not need to be
375    /// sent for Telegram to include them in the message.
376    #[cfg(feature = "html")]
377    pub fn html_text(&self) -> String {
378        if let Some(entities) = self.entities() {
379            parsers::generate_html_message(self.text(), entities)
380        } else {
381            self.text().to_owned()
382        }
383    }
384
385    /// The media displayed by this message, if any.
386    ///
387    /// This not only includes photos or videos, but also contacts, polls, documents, locations
388    /// and many other types.
389    pub fn media(&self) -> Option<types::Media> {
390        let media = match &self.raw {
391            tl::enums::Message::Empty(_) => None,
392            tl::enums::Message::Message(message) => message.media.clone(),
393            tl::enums::Message::Service(_) => None,
394        };
395        media.and_then(Media::from_raw)
396    }
397
398    /// If the message has a reply markup (which can happen for messages produced by bots),
399    /// returns said markup.
400    pub fn reply_markup(&self) -> Option<tl::enums::ReplyMarkup> {
401        match &self.raw {
402            tl::enums::Message::Empty(_) => None,
403            tl::enums::Message::Message(message) => message.reply_markup.clone(),
404            tl::enums::Message::Service(_) => None,
405        }
406    }
407
408    /// The formatting entities used to format this message, such as bold, italic, with their
409    /// offsets and lengths.
410    pub fn fmt_entities(&self) -> Option<&Vec<tl::enums::MessageEntity>> {
411        // TODO correct the offsets and lengths to match the byte offsets
412        self.entities()
413    }
414
415    /// How many views does this message have, when applicable.
416    ///
417    /// The same user account can contribute to increment this counter indefinitedly, however
418    /// there is a server-side cooldown limitting how fast it can happen (several hours).
419    pub fn view_count(&self) -> Option<i32> {
420        match &self.raw {
421            tl::enums::Message::Empty(_) => None,
422            tl::enums::Message::Message(message) => message.views,
423            tl::enums::Message::Service(_) => None,
424        }
425    }
426
427    /// How many times has this message been forwarded, when applicable.
428    pub fn forward_count(&self) -> Option<i32> {
429        match &self.raw {
430            tl::enums::Message::Empty(_) => None,
431            tl::enums::Message::Message(message) => message.forwards,
432            tl::enums::Message::Service(_) => None,
433        }
434    }
435
436    /// How many replies does this message have, when applicable.
437    pub fn reply_count(&self) -> Option<i32> {
438        match &self.raw {
439            tl::enums::Message::Message(tl::types::Message {
440                replies: Some(tl::enums::MessageReplies::Replies(replies)),
441                ..
442            }) => Some(replies.replies),
443            _ => None,
444        }
445    }
446
447    /// React to this message.
448    ///
449    /// # Examples
450    ///
451    /// ```
452    /// # async fn f(message: grammers_client::types::Message, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
453    /// message.react("👍").await?;
454    /// # Ok(())
455    /// # }
456    /// ```
457    ///
458    /// Make animation big & Add to recent
459    ///
460    /// ```
461    /// # async fn f(message: grammers_client::types::Message, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
462    /// use grammers_client::types::InputReactions;
463    ///
464    /// let reactions = InputReactions::emoticon("🤯").big().add_to_recent();
465    ///
466    /// message.react(reactions).await?;
467    /// # Ok(())
468    /// # }
469    /// ```
470    ///
471    /// Remove reactions
472    ///
473    /// ```
474    /// # async fn f(message: grammers_client::types::Message, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
475    /// use grammers_client::types::InputReactions;
476    ///
477    /// message.react(InputReactions::remove()).await?;
478    /// # Ok(())
479    /// # }
480    /// ```
481    pub async fn react<R: Into<InputReactions>>(
482        &self,
483        reactions: R,
484    ) -> Result<(), InvocationError> {
485        self.client
486            .send_reactions(self.peer_ref(), self.id(), reactions)
487            .await?;
488        Ok(())
489    }
490
491    /// How many reactions does this message have, when applicable.
492    pub fn reaction_count(&self) -> Option<i32> {
493        match &self.raw {
494            tl::enums::Message::Message(tl::types::Message {
495                reactions: Some(tl::enums::MessageReactions::Reactions(reactions)),
496                ..
497            }) => {
498                let count = reactions
499                    .results
500                    .iter()
501                    .map(|reaction: &tl::enums::ReactionCount| {
502                        let tl::enums::ReactionCount::Count(reaction) = reaction;
503                        reaction.count
504                    })
505                    .sum();
506                Some(count)
507            }
508            _ => None,
509        }
510    }
511
512    /// The date when this message was last edited.
513    pub fn edit_date(&self) -> Option<DateTime<Utc>> {
514        match &self.raw {
515            tl::enums::Message::Empty(_) => None,
516            tl::enums::Message::Message(message) => message.edit_date.map(utils::date),
517            tl::enums::Message::Service(_) => None,
518        }
519    }
520
521    /// If this message was sent to a channel, return the name used by the author to post it.
522    pub fn post_author(&self) -> Option<&str> {
523        match &self.raw {
524            tl::enums::Message::Empty(_) => None,
525            tl::enums::Message::Message(message) => message.post_author.as_deref(),
526            tl::enums::Message::Service(_) => None,
527        }
528    }
529
530    /// If this message belongs to a group of messages, return the unique identifier for that
531    /// group.
532    ///
533    /// This applies to albums of media, such as multiple photos grouped together.
534    ///
535    /// Note that there may be messages sent in between the messages forming a group.
536    pub fn grouped_id(&self) -> Option<i64> {
537        match &self.raw {
538            tl::enums::Message::Empty(_) => None,
539            tl::enums::Message::Message(message) => message.grouped_id,
540            tl::enums::Message::Service(_) => None,
541        }
542    }
543
544    /// A list of reasons on why this message is restricted.
545    ///
546    /// The message is not restricted if the return value is `None`.
547    pub fn restriction_reason(&self) -> Option<&Vec<tl::enums::RestrictionReason>> {
548        match &self.raw {
549            tl::enums::Message::Empty(_) => None,
550            tl::enums::Message::Message(message) => message.restriction_reason.as_ref(),
551            tl::enums::Message::Service(_) => None,
552        }
553    }
554
555    /// If this message is a service message, return the service action that occured.
556    pub fn action(&self) -> Option<&tl::enums::MessageAction> {
557        match &self.raw {
558            tl::enums::Message::Empty(_) => None,
559            tl::enums::Message::Message(_) => None,
560            tl::enums::Message::Service(message) => Some(&message.action),
561        }
562    }
563
564    /// If this message is replying to another message, return the replied message ID.
565    pub fn reply_to_message_id(&self) -> Option<i32> {
566        match &self.raw {
567            tl::enums::Message::Message(tl::types::Message {
568                reply_to: Some(tl::enums::MessageReplyHeader::Header(header)),
569                ..
570            }) => header.reply_to_msg_id,
571            _ => None,
572        }
573    }
574
575    /// Fetch the message that this message is replying to, or `None` if this message is not a
576    /// reply to a previous message.
577    ///
578    /// Shorthand for `Client::get_reply_to_message`.
579    pub async fn get_reply(&self) -> Result<Option<Self>, InvocationError> {
580        self.client
581            .clone() // TODO don't clone
582            .get_reply_to_message(self)
583            .await
584    }
585
586    /// Respond to this message by sending a new message to the same peer, but without directly
587    /// replying to it.
588    ///
589    /// Shorthand for `Client::send_message`.
590    pub async fn respond<M: Into<InputMessage>>(
591        &self,
592        message: M,
593    ) -> Result<Self, InvocationError> {
594        self.client.send_message(self.peer_ref(), message).await
595    }
596
597    /// Respond to this message by sending a album in the same peer, but without directly
598    /// replying to it.
599    ///
600    /// Shorthand for `Client::send_album`.
601    pub async fn respond_album(
602        &self,
603        medias: Vec<InputMedia>,
604    ) -> Result<Vec<Option<Self>>, InvocationError> {
605        self.client.send_album(self.peer_ref(), medias).await
606    }
607
608    /// Directly reply to this message by sending a new message to the same peer that replies to
609    /// it. This methods overrides the `reply_to` on the `InputMessage` to point to `self`.
610    ///
611    /// Shorthand for `Client::send_message`.
612    pub async fn reply<M: Into<InputMessage>>(&self, message: M) -> Result<Self, InvocationError> {
613        let message = message.into();
614        self.client
615            .send_message(self.peer_ref(), message.reply_to(Some(self.id())))
616            .await
617    }
618
619    /// Directly reply to this message by sending a album to the same peer that replies to
620    /// it. This methods overrides the `reply_to` on the first `InputMedia` to point to `self`.
621    ///
622    /// Shorthand for `Client::send_album`.
623    pub async fn reply_album(
624        &self,
625        mut medias: Vec<InputMedia>,
626    ) -> Result<Vec<Option<Self>>, InvocationError> {
627        medias.first_mut().unwrap().reply_to = Some(self.id());
628        self.client.send_album(self.peer_ref(), medias).await
629    }
630
631    /// Forward this message to another (or the same) peer.
632    ///
633    /// Shorthand for `Client::forward_messages`. If you need to forward multiple messages
634    /// at once, consider using that method instead.
635    pub async fn forward_to<C: Into<PeerRef>>(&self, chat: C) -> Result<Self, InvocationError> {
636        // TODO return `Message`
637        // When forwarding a single message, if it fails, Telegram should respond with RPC error.
638        // If it succeeds we will have the single forwarded message present which we can unwrap.
639        self.client
640            .forward_messages(chat, &[self.id()], self.peer_ref())
641            .await
642            .map(|mut msgs| msgs.pop().unwrap().unwrap())
643    }
644
645    /// Edit this message to change its text or media.
646    ///
647    /// Shorthand for `Client::edit_message`.
648    pub async fn edit<M: Into<InputMessage>>(&self, new_message: M) -> Result<(), InvocationError> {
649        self.client
650            .edit_message(self.peer_ref(), self.id(), new_message)
651            .await
652    }
653
654    /// Delete this message for everyone.
655    ///
656    /// Shorthand for `Client::delete_messages`. If you need to delete multiple messages
657    /// at once, consider using that method instead.
658    pub async fn delete(&self) -> Result<(), InvocationError> {
659        self.client
660            .delete_messages(self.peer_ref(), &[self.id()])
661            .await
662            .map(drop)
663    }
664
665    /// Mark this message and all messages above it as read.
666    ///
667    /// Unlike `Client::mark_as_read`, this method only will mark the conversation as read up to
668    /// this message, not the entire conversation.
669    pub async fn mark_as_read(&self) -> Result<(), InvocationError> {
670        let peer = self.peer_ref();
671        if peer.id.kind() == PeerKind::Channel {
672            self.client
673                .invoke(&tl::functions::channels::ReadHistory {
674                    channel: peer.into(),
675                    max_id: self.id(),
676                })
677                .await
678                .map(drop)
679        } else {
680            self.client
681                .invoke(&tl::functions::messages::ReadHistory {
682                    peer: peer.into(),
683                    max_id: self.id(),
684                })
685                .await
686                .map(drop)
687        }
688    }
689
690    /// Pin this message in the conversation.
691    ///
692    /// Shorthand for `Client::pin_message`.
693    pub async fn pin(&self) -> Result<(), InvocationError> {
694        self.client.pin_message(self.peer_ref(), self.id()).await
695    }
696
697    /// Unpin this message from the conversation.
698    ///
699    /// Shorthand for `Client::unpin_message`.
700    pub async fn unpin(&self) -> Result<(), InvocationError> {
701        self.client.unpin_message(self.peer_ref(), self.id()).await
702    }
703
704    /// Refetch this message, mutating all of its properties in-place.
705    ///
706    /// No changes will be made to the message if it fails to be fetched.
707    ///
708    /// Shorthand for `Client::get_messages_by_id`.
709    pub async fn refetch(&self) -> Result<(), InvocationError> {
710        // When fetching a single message, if it fails, Telegram should respond with RPC error.
711        // If it succeeds we will have the single message present which we can unwrap.
712        self.client
713            .get_messages_by_id(self.peer_ref(), &[self.id()])
714            .await?
715            .pop()
716            .unwrap()
717            .unwrap();
718        todo!("actually mutate self after get_messages_by_id returns `Message`")
719    }
720
721    /// Download the message media in this message if applicable.
722    ///
723    /// Returns `true` if there was media to download, or `false` otherwise.
724    ///
725    /// Shorthand for `Client::download_media`.
726    #[cfg(feature = "fs")]
727    pub async fn download_media<P: AsRef<Path>>(&self, path: P) -> Result<bool, io::Error> {
728        // TODO probably encode failed download in error
729        if let Some(media) = self.media() {
730            self.client.download_media(&media, path).await.map(|_| true)
731        } else {
732            Ok(false)
733        }
734    }
735
736    /// Get photo attached to the message if any.
737    pub fn photo(&self) -> Option<Photo> {
738        if let Media::Photo(photo) = self.media()? {
739            return Some(photo);
740        }
741
742        None
743    }
744}
745
746impl fmt::Debug for Message {
747    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
748        f.debug_struct("Message")
749            .field("id", &self.id())
750            .field("outgoing", &self.outgoing())
751            .field("date", &self.date())
752            .field("text", &self.text())
753            .field("peer", &self.peer())
754            .field("sender", &self.sender())
755            .field("reply_to_message_id", &self.reply_to_message_id())
756            .field("via_bot_id", &self.via_bot_id())
757            .field("media", &self.media())
758            .field("mentioned", &self.mentioned())
759            .field("media_unread", &self.media_unread())
760            .field("silent", &self.silent())
761            .field("post", &self.post())
762            .field("from_scheduled", &self.from_scheduled())
763            .field("edit_hide", &self.edit_hide())
764            .field("pinned", &self.pinned())
765            .field("forward_header", &self.forward_header())
766            .field("reply_header", &self.reply_header())
767            .field("reply_markup", &self.reply_markup())
768            .field("fmt_entities", &self.fmt_entities())
769            .field("view_count", &self.view_count())
770            .field("forward_count", &self.forward_count())
771            .field("reply_count", &self.reply_count())
772            .field("edit_date", &self.edit_date())
773            .field("post_author", &self.post_author())
774            .field("grouped_id", &self.grouped_id())
775            .field("restriction_reason", &self.restriction_reason())
776            .field("action", &self.action())
777            .finish()
778    }
779}