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