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}