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}