grammers_client/client/messages.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
9//! Methods related to sending messages.
10
11use std::collections::HashMap;
12
13use chrono::{DateTime, FixedOffset};
14use grammers_mtsender::InvocationError;
15use grammers_session::types::{PeerAuth, PeerId, PeerKind, PeerRef};
16use grammers_tl_types::{self as tl, enums::InputPeer};
17use log::{Level, log_enabled, warn};
18
19use super::{Client, IterBuffer};
20use crate::media::{InputMedia, Media};
21use crate::message::{InputMessage, InputReactions, Message};
22use crate::utils::{generate_random_id, generate_random_ids};
23
24async fn map_random_ids_to_messages(
25 client: &Client,
26 fetched_in: PeerRef,
27 random_ids: &[i64],
28 updates: tl::enums::Updates,
29) -> Result<Vec<Option<Message>>, Box<dyn std::error::Error + Send + Sync>> {
30 match updates {
31 tl::enums::Updates::Updates(tl::types::Updates {
32 updates,
33 users,
34 chats,
35 date: _,
36 seq: _,
37 }) => {
38 let peers = client.build_peer_map(users, chats).await;
39
40 let rnd_to_id = updates
41 .iter()
42 .filter_map(|update| match update {
43 tl::enums::Update::MessageId(u) => Some((u.random_id, u.id)),
44 _ => None,
45 })
46 .collect::<HashMap<_, _>>();
47
48 // TODO ideally this would use the same UpdateIter mechanism to make sure we don't
49 // accidentally miss variants
50 let mut id_to_msg = updates
51 .into_iter()
52 .filter_map(|update| match update {
53 tl::enums::Update::NewMessage(tl::types::UpdateNewMessage {
54 message, ..
55 }) => Some(message),
56 tl::enums::Update::NewChannelMessage(tl::types::UpdateNewChannelMessage {
57 message,
58 ..
59 }) => Some(message),
60 tl::enums::Update::NewScheduledMessage(
61 tl::types::UpdateNewScheduledMessage { message, .. },
62 ) => Some(message),
63 _ => None,
64 })
65 .map(|message| {
66 Message::from_raw(client, message, Some(fetched_in.clone()), peers.handle())
67 })
68 .map(|message| (message.id(), message))
69 .collect::<HashMap<_, _>>();
70
71 Ok(random_ids
72 .iter()
73 .map(|rnd| {
74 rnd_to_id
75 .get(rnd)
76 .and_then(|id| id_to_msg.remove(id))
77 .or_else(|| {
78 if id_to_msg.len() == 1 {
79 // If there's no random_id to map from, in the common case a single message
80 // should've been produced regardless, so try to recover by returning that.
81 id_to_msg.drain().next().map(|(_, m)| m)
82 } else {
83 None
84 }
85 })
86 })
87 .collect())
88 }
89 _ => panic!("API returned something other than Updates so messages can't be mapped"),
90 }
91}
92
93pub(crate) async fn parse_mention_entities(
94 client: &Client,
95 mut entities: Vec<tl::enums::MessageEntity>,
96) -> Option<Vec<tl::enums::MessageEntity>> {
97 if entities.is_empty() {
98 return None;
99 }
100
101 if entities
102 .iter()
103 .any(|e| matches!(e, tl::enums::MessageEntity::MentionName(_)))
104 {
105 for entity in entities.iter_mut() {
106 if let tl::enums::MessageEntity::MentionName(mention_name) = entity {
107 *entity = tl::types::InputMessageEntityMentionName {
108 offset: mention_name.offset,
109 length: mention_name.length,
110 user_id: tl::enums::InputUser::User(tl::types::InputUser {
111 user_id: mention_name.user_id,
112 access_hash: client
113 .0
114 .session
115 .peer(PeerId::user_unchecked(mention_name.user_id))
116 .await
117 .unwrap_or_default()
118 .and_then(|peer| peer.auth())
119 .unwrap_or_default()
120 .hash(),
121 }),
122 }
123 .into()
124 }
125 }
126 }
127
128 Some(entities)
129}
130
131const MAX_LIMIT: usize = 100;
132
133/// Build a reply-to a plain message by its ID, leaving all other fields unset.
134fn input_reply_to(reply_to_msg_id: i32) -> tl::enums::InputReplyTo {
135 tl::types::InputReplyToMessage {
136 reply_to_msg_id,
137 top_msg_id: None,
138 reply_to_peer_id: None,
139 quote_text: None,
140 quote_entities: None,
141 quote_offset: None,
142 monoforum_peer_id: None,
143 todo_item_id: None,
144 poll_option: None,
145 }
146 .into()
147}
148
149/// Flatten a `messages.Messages` response into its messages, users and chats.
150///
151/// Panics on `NotModified`, which is only returned when a non-zero `hash` was sent.
152fn unpack_messages(
153 res: tl::enums::messages::Messages,
154) -> (
155 Vec<tl::enums::Message>,
156 Vec<tl::enums::User>,
157 Vec<tl::enums::Chat>,
158) {
159 use tl::enums::messages::Messages;
160
161 match res {
162 Messages::Messages(m) => (m.messages, m.users, m.chats),
163 Messages::Slice(m) => (m.messages, m.users, m.chats),
164 Messages::ChannelMessages(m) => (m.messages, m.users, m.chats),
165 Messages::NotModified(_) => {
166 panic!("API returned Messages::NotModified even though GetMessages was used")
167 }
168 }
169}
170
171impl<R: tl::RemoteCall<Return = tl::enums::messages::Messages>> IterBuffer<R, Message> {
172 /// Fetches the total unless cached.
173 ///
174 /// The `request.limit` should be set to the right value before calling this method.
175 async fn get_total(&mut self) -> Result<usize, InvocationError> {
176 if let Some(total) = self.total {
177 return Ok(total);
178 }
179
180 use tl::enums::messages::Messages;
181
182 let total = match self.client.invoke(&self.request).await? {
183 Messages::Messages(messages) => messages.messages.len(),
184 Messages::Slice(messages) => messages.count as usize,
185 Messages::ChannelMessages(messages) => messages.count as usize,
186 Messages::NotModified(messages) => messages.count as usize,
187 };
188 self.total = Some(total);
189 Ok(total)
190 }
191
192 /// Performs the network call, fills the buffer, and returns the `offset_rate` if any.
193 ///
194 /// The `request.limit` should be set to the right value before calling this method.
195 async fn fill_buffer(
196 &mut self,
197 limit: i32,
198 reverse: bool,
199 peer: Option<PeerRef>,
200 ) -> Result<Option<i32>, InvocationError> {
201 use tl::enums::messages::Messages;
202
203 let (mut messages, users, chats, rate) = match self.client.invoke(&self.request).await? {
204 Messages::Messages(m) => {
205 self.last_chunk = true;
206 self.total = Some(m.messages.len());
207 (m.messages, m.users, m.chats, None)
208 }
209 Messages::Slice(m) => {
210 // Can't rely on `count(messages) < limit` as the stop condition.
211 // See https://t.me/tdlibchat/57257 for discussion. As an example:
212 // offset_id 132002, limit 4 => we get msg 131999 & 131998
213 // offset_id 132002, limit 3 => we get msg 131999
214 // offset_id 132002, limit 2 => we get msg 132000
215 // offset_id 132002, limit 1 => we get msg 132001
216 //
217 // When iterating newest-to-oldest, the highest fetched message ID is
218 // lower than or equal to the limit, there can't be more messages after
219 // (highest ID - limit), because the absolute lowest message ID is 1.
220 self.last_chunk =
221 m.messages.is_empty() || (!reverse && m.messages[0].id() <= limit);
222
223 self.total = Some(m.count as usize);
224
225 (m.messages, m.users, m.chats, m.next_rate)
226 }
227 Messages::ChannelMessages(m) => {
228 self.last_chunk =
229 m.messages.is_empty() || (!reverse && m.messages[0].id() <= limit);
230 self.total = Some(m.count as usize);
231 (m.messages, m.users, m.chats, None)
232 }
233 Messages::NotModified(_) => {
234 panic!("API returned Messages::NotModified even though hash = 0")
235 }
236 };
237
238 if reverse {
239 messages.reverse();
240 }
241
242 let peers = self.client.build_peer_map(users, chats).await;
243
244 let client = self.client.clone();
245 self.buffer.extend(
246 messages
247 .into_iter()
248 .map(|message| Message::from_raw(&client, message, peer, peers.handle())),
249 );
250
251 Ok(rate)
252 }
253}
254
255/// Iterator returned by [`Client::iter_messages`].
256pub struct MessageIter {
257 inner: IterBuffer<tl::functions::messages::GetHistory, Message>,
258 reverse: bool,
259}
260
261impl MessageIter {
262 fn new(client: &Client, peer: PeerRef) -> Self {
263 let inner = IterBuffer::from_request(
264 client,
265 MAX_LIMIT,
266 tl::functions::messages::GetHistory {
267 peer: peer.into(),
268 offset_id: 0,
269 offset_date: 0,
270 add_offset: 0,
271 limit: 0,
272 max_id: 0,
273 min_id: 0,
274 hash: 0,
275 },
276 );
277
278 Self {
279 inner,
280 reverse: false,
281 }
282 }
283
284 /// Changes the starting message ID (exclusive).
285 pub fn offset_id(mut self, offset: i32) -> Self {
286 self.inner.request.offset_id = offset;
287 self
288 }
289
290 /// Changes the starting message date.
291 pub fn offset_date(mut self, offset: i32) -> Self {
292 self.inner.request.offset_date = offset;
293 self
294 }
295
296 /// Changes the order to oldest-to-newest. (default is newest-to-oldest)
297 pub fn reverse(mut self, reverse: bool) -> Self {
298 self.reverse = reverse;
299 self
300 }
301
302 pub fn limit(mut self, limit: usize) -> Self {
303 self.inner = self.inner.limit(limit);
304 self
305 }
306
307 /// Determines the total number of messages in the chat.
308 ///
309 /// This only performs a network call if `next` has not been called before.
310 pub async fn total(&mut self) -> Result<usize, InvocationError> {
311 self.inner.request.limit = 1;
312 self.inner.get_total().await
313 }
314
315 /// Returns the next `Message` from the internal buffer, filling the buffer previously if it's
316 /// empty.
317 ///
318 /// Returns `None` if the `limit` is reached or there are no messages left.
319 pub async fn next(&mut self) -> Result<Option<Message>, InvocationError> {
320 if let Some(result) = self.inner.next_raw() {
321 return result;
322 }
323
324 self.inner.request.limit = self.inner.determine_limit(MAX_LIMIT);
325 let request = &mut self.inner.request;
326 if self.reverse {
327 request.add_offset = -request.limit;
328 request.min_id = request.offset_id;
329 if request.offset_id == 0 && request.offset_date == 0 {
330 // With no explicit offset, start from the oldest available page.
331 request.offset_id = 1;
332 request.add_offset = -request.limit + 1; // +1 to include the first message (ID=1)
333 }
334 }
335 self.inner
336 .fill_buffer(
337 self.inner.request.limit,
338 self.reverse,
339 Some(self.inner.request.peer.clone().into()),
340 )
341 .await?;
342
343 // Don't bother updating offsets if this is the last time stuff has to be fetched.
344 if !self.inner.last_chunk && !self.inner.buffer.is_empty() {
345 let last = &self.inner.buffer[self.inner.buffer.len() - 1];
346 self.inner.request.offset_id = last.id();
347 self.inner.request.offset_date = last.date_timestamp();
348 }
349
350 Ok(self.inner.pop_item())
351 }
352}
353
354/// Iterator returned by [`Client::search_messages`].
355pub type SearchIter = IterBuffer<tl::functions::messages::Search, Message>;
356
357impl SearchIter {
358 fn new(client: &Client, peer: PeerRef) -> Self {
359 // TODO let users tweak all the options from the request
360 Self::from_request(
361 client,
362 MAX_LIMIT,
363 tl::functions::messages::Search {
364 peer: peer.into(),
365 q: String::new(),
366 from_id: None,
367 saved_peer_id: None,
368 saved_reaction: None,
369 top_msg_id: None,
370 filter: tl::enums::MessagesFilter::InputMessagesFilterEmpty,
371 min_date: 0,
372 max_date: 0,
373 offset_id: 0,
374 add_offset: 0,
375 limit: 0,
376 max_id: 0,
377 min_id: 0,
378 hash: 0,
379 },
380 )
381 }
382
383 /// Changes the message identifier upper bound.
384 pub fn offset_id(mut self, offset: i32) -> Self {
385 self.request.offset_id = offset;
386 self
387 }
388
389 /// Changes the query of the search. Telegram servers perform a somewhat fuzzy search over
390 /// this query (so a word in singular may also return messages with the word in plural, for
391 /// example).
392 pub fn query(mut self, query: &str) -> Self {
393 self.request.q = query.to_string();
394 self
395 }
396
397 /// Restricts results to messages sent by the logged-in user
398 pub fn sent_by_self(mut self) -> Self {
399 self.request.from_id = Some(InputPeer::PeerSelf);
400 self
401 }
402
403 /// Returns only messages with date bigger than date_time.
404 ///
405 /// ```
406 /// use chrono::DateTime;
407 ///
408 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
409 /// // Search messages sent after Jan 1st, 2021
410 /// let min_date = DateTime::parse_from_rfc3339("2021-01-01T00:00:00-00:00").unwrap();
411 ///
412 /// let mut messages = client.search_messages(peer).min_date(&min_date);
413 ///
414 /// # Ok(())
415 /// # }
416 /// ```
417 pub fn min_date(mut self, date_time: &DateTime<FixedOffset>) -> Self {
418 self.request.min_date = date_time.timestamp() as i32;
419 self
420 }
421
422 /// Returns only messages with date smaller than date_time
423 ///
424 /// ```
425 /// use chrono::DateTime;
426 ///
427 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
428 /// // Search messages sent before Dec, 25th 2022
429 /// let max_date = DateTime::parse_from_rfc3339("2022-12-25T00:00:00-00:00").unwrap();
430 ///
431 /// let mut messages = client.search_messages(peer).max_date(&max_date);
432 ///
433 /// # Ok(())
434 /// # }
435 /// ```
436 pub fn max_date(mut self, date_time: &DateTime<FixedOffset>) -> Self {
437 self.request.max_date = date_time.timestamp() as i32;
438 self
439 }
440
441 /// Changes the media filter. Only messages with this type of media will be fetched.
442 pub fn filter(mut self, filter: tl::enums::MessagesFilter) -> Self {
443 self.request.filter = filter;
444 self
445 }
446
447 /// Determines how many messages there are in total.
448 ///
449 /// This only performs a network call if `next` has not been called before.
450 pub async fn total(&mut self) -> Result<usize, InvocationError> {
451 // Unlike most requests, a limit of 0 actually returns 0 and not a default amount
452 // (as of layer 120).
453 self.request.limit = 0;
454 self.get_total().await
455 }
456
457 /// Return the next `Message` from the internal buffer, filling the buffer previously if it's
458 /// empty.
459 ///
460 /// Returns `None` if the `limit` is reached or there are no messages left.
461 pub async fn next(&mut self) -> Result<Option<Message>, InvocationError> {
462 if let Some(result) = self.next_raw() {
463 return result;
464 }
465
466 self.request.limit = self.determine_limit(MAX_LIMIT);
467 self.fill_buffer(
468 self.request.limit,
469 false,
470 Some(self.request.peer.clone().into()),
471 )
472 .await?;
473
474 // Don't bother updating offsets if this is the last time stuff has to be fetched.
475 if !self.last_chunk && !self.buffer.is_empty() {
476 let last = &self.buffer[self.buffer.len() - 1];
477 self.request.offset_id = last.id();
478 self.request.max_date = last.date_timestamp();
479 }
480
481 Ok(self.pop_item())
482 }
483}
484
485/// Iterator returned by [`Client::search_all_messages`].
486pub type GlobalSearchIter = IterBuffer<tl::functions::messages::SearchGlobal, Message>;
487
488impl GlobalSearchIter {
489 fn new(client: &Client) -> Self {
490 // TODO let users tweak all the options from the request
491 Self::from_request(
492 client,
493 MAX_LIMIT,
494 tl::functions::messages::SearchGlobal {
495 folder_id: None,
496 q: String::new(),
497 filter: tl::enums::MessagesFilter::InputMessagesFilterEmpty,
498 min_date: 0,
499 max_date: 0,
500 offset_rate: 0,
501 offset_peer: tl::enums::InputPeer::Empty,
502 offset_id: 0,
503 limit: 0,
504 broadcasts_only: false,
505 groups_only: false,
506 users_only: false,
507 },
508 )
509 }
510
511 /// Changes the message identifier upper bound.
512 pub fn offset_id(mut self, offset: i32) -> Self {
513 self.request.offset_id = offset;
514 self
515 }
516
517 /// Changes the query of the search. Telegram servers perform a somewhat fuzzy search over
518 /// this query (so a word in singular may also return messages with the word in plural, for
519 /// example).
520 pub fn query(mut self, query: &str) -> Self {
521 self.request.q = query.to_string();
522 self
523 }
524
525 /// Changes the media filter. Only messages with this type of media will be fetched.
526 pub fn filter(mut self, filter: tl::enums::MessagesFilter) -> Self {
527 self.request.filter = filter;
528 self
529 }
530
531 /// Determines how many messages there are in total.
532 ///
533 /// This only performs a network call if `next` has not been called before.
534 pub async fn total(&mut self) -> Result<usize, InvocationError> {
535 self.request.limit = 1;
536 self.get_total().await
537 }
538
539 /// Return the next `Message` from the internal buffer, filling the buffer previously if it's
540 /// empty.
541 ///
542 /// Returns `None` if the `limit` is reached or there are no messages left.
543 pub async fn next(&mut self) -> Result<Option<Message>, InvocationError> {
544 if let Some(result) = self.next_raw() {
545 return result;
546 }
547
548 self.request.limit = self.determine_limit(MAX_LIMIT);
549 let offset_rate = self.fill_buffer(self.request.limit, false, None).await?;
550
551 // Don't bother updating offsets if this is the last time stuff has to be fetched.
552 if !self.last_chunk && !self.buffer.is_empty() {
553 let last = &self.buffer[self.buffer.len() - 1];
554 self.request.offset_rate = offset_rate.unwrap_or(0);
555 self.request.offset_peer = last
556 .peer_ref()
557 .await?
558 .map(|peer| peer.into())
559 .unwrap_or(tl::enums::InputPeer::Empty);
560 self.request.offset_id = last.id();
561 }
562
563 Ok(self.pop_item())
564 }
565}
566
567/// Method implementations related to sending, modifying or getting messages.
568impl Client {
569 /// Sends a message to the desired peer.
570 ///
571 /// This method can also be used to send media such as photos, videos, documents, polls, etc.
572 ///
573 /// If you want to send a local file as media, you will need to use
574 /// [`Client::upload_file`] first.
575 ///
576 /// Refer to [`InputMessage`] to learn more formatting options, such as using markdown or
577 /// adding buttons under your message (if you're logged in as a bot).
578 ///
579 /// See also: [`Message::respond`], [`Message::reply`].
580 ///
581 /// # Examples
582 ///
583 /// ```
584 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
585 /// client.send_message(peer, "Boring text message :-(").await?;
586 ///
587 /// use grammers_client::message::InputMessage;
588 ///
589 /// client.send_message(peer, InputMessage::new().text("Sneaky message").silent(true)).await?;
590 /// # Ok(())
591 /// # }
592 /// ```
593 pub async fn send_message<C: Into<PeerRef>, M: Into<InputMessage>>(
594 &self,
595 peer: C,
596 message: M,
597 ) -> Result<Message, InvocationError> {
598 let peer = peer.into();
599 let message = message.into();
600 let random_id = generate_random_id();
601 let entities = parse_mention_entities(self, message.entities.clone()).await;
602 let updates = if let Some(media) = message.media.clone() {
603 self.invoke(&tl::functions::messages::SendMedia {
604 silent: message.silent,
605 background: message.background,
606 clear_draft: message.clear_draft,
607 peer: peer.into(),
608 reply_to: message.reply_to.map(input_reply_to),
609 media,
610 message: message.text.clone(),
611 random_id,
612 reply_markup: message.reply_markup.clone(),
613 entities,
614 schedule_date: message.schedule_date,
615 schedule_repeat_period: None,
616 send_as: None,
617 noforwards: false,
618 update_stickersets_order: false,
619 invert_media: message.invert_media,
620 quick_reply_shortcut: None,
621 effect: None,
622 allow_paid_floodskip: false,
623 allow_paid_stars: None,
624 suggested_post: None,
625 })
626 .await
627 } else {
628 self.invoke(&tl::functions::messages::SendMessage {
629 no_webpage: !message.link_preview,
630 silent: message.silent,
631 background: message.background,
632 clear_draft: message.clear_draft,
633 peer: peer.into(),
634 reply_to: message.reply_to.map(input_reply_to),
635 message: message.text.clone(),
636 random_id,
637 reply_markup: message.reply_markup.clone(),
638 entities,
639 schedule_date: message.schedule_date,
640 schedule_repeat_period: None,
641 send_as: None,
642 noforwards: false,
643 update_stickersets_order: false,
644 invert_media: message.invert_media,
645 quick_reply_shortcut: None,
646 effect: None,
647 allow_paid_floodskip: false,
648 allow_paid_stars: None,
649 suggested_post: None,
650 rich_message: None,
651 })
652 .await
653 }?;
654
655 Ok(match updates {
656 tl::enums::Updates::UpdateShortSentMessage(updates) => {
657 let peer = if peer.id.bare_id().is_none() {
658 // from_raw_short_updates needs the peer ID
659 self.0.session.peer_ref(peer.id).await?.unwrap()
660 } else {
661 peer
662 };
663
664 Message::from_raw_short_updates(self, updates, message, peer)
665 }
666 updates => {
667 let updates_debug = if log_enabled!(Level::Warn) {
668 Some(updates.clone())
669 } else {
670 None
671 };
672
673 match map_random_ids_to_messages(self, peer, &[random_id], updates)
674 .await?
675 .pop()
676 .flatten()
677 {
678 Some(message) => message,
679 None => {
680 if let Some(updates) = updates_debug {
681 warn!(
682 "failed to find just-sent message in response updates; please report this:"
683 );
684 warn!("{:#?}", updates);
685 }
686 Message::from_raw(
687 self,
688 tl::enums::Message::Empty(tl::types::MessageEmpty {
689 id: 0,
690 peer_id: Some(peer.id.into()),
691 }),
692 Some(peer),
693 self.empty_peer_map(),
694 )
695 }
696 }
697 }
698 })
699 }
700
701 /// Sends a album to the desired peer.
702 ///
703 /// This method can also be used to send a bunch of media such as photos, videos, documents, polls, etc.
704 ///
705 /// If you want to send a local file as media, you will need to use
706 /// [`Client::upload_file`] first.
707 ///
708 /// Refer to [`InputMedia`] to learn more formatting options, such as using markdown.
709 ///
710 /// See also: [`Message::respond_album`], [`Message::reply_album`].
711 ///
712 /// # Examples
713 ///
714 /// ```
715 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
716 /// use grammers_client::media::InputMedia;
717 ///
718 /// client.send_album(peer, vec![InputMedia::new().caption("A album").photo_url("https://example.com/cat.jpg")]).await?;
719 /// # Ok(())
720 /// # }
721 /// ```
722 pub async fn send_album<C: Into<PeerRef>>(
723 &self,
724 peer: C,
725 mut medias: Vec<InputMedia>,
726 ) -> Result<Vec<Option<Message>>, InvocationError> {
727 let peer = peer.into();
728 let random_ids = generate_random_ids(medias.len());
729
730 // Upload external files
731 for media in medias.iter_mut() {
732 let raw_media = media.media.clone().unwrap();
733
734 if matches!(
735 raw_media,
736 tl::enums::InputMedia::UploadedPhoto(_)
737 | tl::enums::InputMedia::PhotoExternal(_)
738 | tl::enums::InputMedia::UploadedDocument(_)
739 | tl::enums::InputMedia::DocumentExternal(_)
740 ) {
741 let uploaded = self
742 .invoke(&tl::functions::messages::UploadMedia {
743 business_connection_id: None,
744 peer: peer.into(),
745 media: raw_media,
746 })
747 .await?;
748 media.media = Some(
749 Media::from_raw(uploaded)
750 .unwrap()
751 .to_raw_input_media()
752 .unwrap(),
753 );
754 }
755 }
756
757 let first_media_reply = medias.first().unwrap().reply_to;
758
759 let mut multi_media = Vec::with_capacity(medias.len());
760 for (input_media, random_id) in medias.into_iter().zip(random_ids.iter()) {
761 let entities = parse_mention_entities(self, input_media.entities).await;
762 let raw_media = input_media.media.unwrap();
763 multi_media.push(tl::enums::InputSingleMedia::Media(
764 tl::types::InputSingleMedia {
765 media: raw_media,
766 random_id: *random_id,
767 message: input_media.caption,
768 entities,
769 },
770 ))
771 }
772
773 let updates = self
774 .invoke(&tl::functions::messages::SendMultiMedia {
775 silent: false,
776 background: false,
777 clear_draft: false,
778 peer: peer.into(),
779 reply_to: first_media_reply.map(input_reply_to),
780 schedule_date: None,
781 multi_media,
782 send_as: None,
783 noforwards: false,
784 update_stickersets_order: false,
785 invert_media: false,
786 quick_reply_shortcut: None,
787 effect: None,
788 allow_paid_floodskip: false,
789 allow_paid_stars: None,
790 })
791 .await?;
792
793 Ok(map_random_ids_to_messages(self, peer, &random_ids, updates).await?)
794 }
795
796 /// Edits an existing message.
797 ///
798 /// Similar to [`Client::send_message`], advanced formatting can be achieved with the
799 /// options offered by [`InputMessage`].
800 ///
801 /// See also: [`Message::edit`].
802 ///
803 /// # Examples
804 ///
805 /// ```
806 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
807 /// let old_message_id = 123;
808 /// client.edit_message(peer, old_message_id, "New text message").await?;
809 /// # Ok(())
810 /// # }
811 /// ```
812 pub async fn edit_message<C: Into<PeerRef>, M: Into<InputMessage>>(
813 &self,
814 peer: C,
815 message_id: i32,
816 new_message: M,
817 ) -> Result<(), InvocationError> {
818 let new_message = new_message.into();
819 let entities = parse_mention_entities(self, new_message.entities).await;
820 self.invoke(&tl::functions::messages::EditMessage {
821 no_webpage: !new_message.link_preview,
822 invert_media: new_message.invert_media,
823 peer: peer.into().into(),
824 id: message_id,
825 message: Some(new_message.text),
826 media: new_message.media,
827 reply_markup: new_message.reply_markup,
828 entities,
829 schedule_date: new_message.schedule_date,
830 schedule_repeat_period: None,
831 quick_reply_shortcut_id: None,
832 rich_message: None,
833 })
834 .await?;
835
836 Ok(())
837 }
838
839 /// Deletes up to 100 messages in a peer.
840 ///
841 /// <div class="warning">
842 ///
843 /// When deleting messages from small group peers or private conversations, this
844 /// method cannot validate that the provided message IDs actually belong to the input peer due
845 /// to the way Telegram's API works. Make sure to pass correct [`Message::id`]'s.
846 ///
847 /// </div>
848 ///
849 /// The messages are deleted for both ends.
850 ///
851 /// The amount of deleted messages is returned (it might be less than the amount of input
852 /// message IDs if some of them were already missing). It is not possible to find out which
853 /// messages were actually deleted, but if the request succeeds, none of the specified message
854 /// IDs will appear in the message history from that point on.
855 ///
856 /// See also: [`Message::delete`].
857 ///
858 /// # Examples
859 ///
860 /// ```
861 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
862 /// let message_ids = [123, 456, 789];
863 ///
864 /// // Careful, these messages will be gone after the method succeeds!
865 /// client.delete_messages(peer, &message_ids).await?;
866 /// # Ok(())
867 /// # }
868 /// ```
869 pub async fn delete_messages<C: Into<PeerRef>>(
870 &self,
871 peer: C,
872 message_ids: &[i32],
873 ) -> Result<usize, InvocationError> {
874 let peer = peer.into();
875 let tl::enums::messages::AffectedMessages::Messages(affected) =
876 if peer.id.kind() == PeerKind::Channel {
877 self.invoke(&tl::functions::channels::DeleteMessages {
878 channel: peer.into(),
879 id: message_ids.to_vec(),
880 })
881 .await
882 } else {
883 self.invoke(&tl::functions::messages::DeleteMessages {
884 revoke: true,
885 id: message_ids.to_vec(),
886 })
887 .await
888 }?;
889
890 Ok(affected.pts_count as usize)
891 }
892
893 /// Forwards up to 100 messages from `source` into `destination`.
894 ///
895 /// For consistency with other methods, the peer upon which this request acts comes first
896 /// (destination), and then the source peer.
897 ///
898 /// Returns the new forwarded messages in a list. Those messages that could not be forwarded
899 /// will be `None`. The length of the resulting list is the same as the length of the input
900 /// message IDs, and the indices from the list of IDs map to the indices in the result so
901 /// you can find which messages were forwarded and which message they became.
902 ///
903 /// See also: [`Message::forward_to`].
904 ///
905 /// # Examples
906 ///
907 /// ```
908 /// # async fn f(destination: grammers_session::types::PeerRef, source: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
909 /// let message_ids = [123, 456, 789];
910 ///
911 /// let messages = client.forward_messages(destination, &message_ids, source).await?;
912 /// let fwd_count = messages.into_iter().filter(Option::is_some).count();
913 /// println!("Forwarded {} out of {} messages!", fwd_count, message_ids.len());
914 /// # Ok(())
915 /// # }
916 /// ```
917 pub async fn forward_messages<C: Into<PeerRef>, S: Into<PeerRef>>(
918 &self,
919 destination: C,
920 message_ids: &[i32],
921 source: S,
922 ) -> Result<Vec<Option<Message>>, InvocationError> {
923 // TODO let user customize more options
924 let peer = destination.into();
925 let request = tl::functions::messages::ForwardMessages {
926 silent: false,
927 background: false,
928 with_my_score: false,
929 drop_author: false,
930 drop_media_captions: false,
931 from_peer: source.into().into(),
932 id: message_ids.to_vec(),
933 random_id: generate_random_ids(message_ids.len()),
934 to_peer: peer.into(),
935 top_msg_id: None,
936 reply_to: None,
937 schedule_date: None,
938 schedule_repeat_period: None,
939 send_as: None,
940 noforwards: false,
941 quick_reply_shortcut: None,
942 allow_paid_floodskip: false,
943 effect: None,
944 video_timestamp: None,
945 allow_paid_stars: None,
946 suggested_post: None,
947 };
948 let result = self.invoke(&request).await?;
949 Ok(map_random_ids_to_messages(self, peer.into(), &request.random_id, result).await?)
950 }
951
952 /// Gets the [`Message`] to which the input message is replying to.
953 ///
954 /// See also: [`Message::get_reply`].
955 ///
956 /// # Examples
957 ///
958 /// ```
959 /// # async fn f(message: grammers_client::message::Message, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
960 /// if let Some(reply) = client.get_reply_to_message(&message).await? {
961 /// println!("The reply said: {}", reply.text());
962 /// }
963 /// # Ok(())
964 /// # }
965 /// ```
966 pub async fn get_reply_to_message(
967 &self,
968 message: &Message,
969 ) -> Result<Option<Message>, InvocationError> {
970 /// Helper method to fetch a single message by its input message.
971 async fn get_message(
972 client: &Client,
973 peer: PeerRef,
974 id: tl::enums::InputMessage,
975 ) -> Result<(tl::enums::messages::Messages, bool), InvocationError> {
976 if peer.id.kind() == PeerKind::Channel {
977 client
978 .invoke(&tl::functions::channels::GetMessages {
979 id: vec![id],
980 channel: peer.into(),
981 })
982 .await
983 .map(|res| (res, false))
984 } else {
985 client
986 .invoke(&tl::functions::messages::GetMessages { id: vec![id] })
987 .await
988 .map(|res| (res, true))
989 }
990 }
991
992 // TODO shouldn't this method take in a message id anyway?
993 let peer_id = message.peer_id();
994 let peer = match peer_id.kind() {
995 PeerKind::User | PeerKind::Chat => PeerRef {
996 id: peer_id,
997 auth: PeerAuth::default(), // unused, so no need to bother fetching it
998 },
999 PeerKind::Channel => message.peer_ref().await?.ok_or(InvocationError::Dropped)?,
1000 };
1001 let reply_to_message_id = match message.reply_to_message_id() {
1002 Some(id) => id,
1003 None => return Ok(None),
1004 };
1005
1006 let input_id =
1007 tl::enums::InputMessage::ReplyTo(tl::types::InputMessageReplyTo { id: message.id() });
1008
1009 let (res, filter_req) = match get_message(self, peer, input_id).await {
1010 Ok(tup) => tup,
1011 Err(_) => {
1012 let input_id = tl::enums::InputMessage::Id(tl::types::InputMessageId {
1013 id: reply_to_message_id,
1014 });
1015 get_message(self, peer, input_id).await?
1016 }
1017 };
1018
1019 let (messages, users, chats) = unpack_messages(res);
1020
1021 let peers = self.build_peer_map(users, chats).await;
1022 Ok(messages
1023 .into_iter()
1024 .map(|m| Message::from_raw(self, m, Some(peer.into()), peers.handle()))
1025 .next()
1026 .filter(|m| !filter_req || m.peer_id() == message.peer_id()))
1027 }
1028
1029 /// Iterate over the message history of a peer, from most recent to oldest.
1030 ///
1031 /// # Examples
1032 ///
1033 /// ```
1034 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1035 /// // Note we're setting a reasonable limit, or we'd print out ALL the messages in peer!
1036 /// let mut messages = client.iter_messages(peer).limit(100);
1037 ///
1038 /// while let Some(message) = messages.next().await? {
1039 /// println!("{}", message.text());
1040 /// }
1041 /// # Ok(())
1042 /// # }
1043 /// ```
1044 pub fn iter_messages<C: Into<PeerRef>>(&self, peer: C) -> MessageIter {
1045 MessageIter::new(self, peer.into())
1046 }
1047
1048 /// Iterate over the messages that match certain search criteria.
1049 ///
1050 /// This allows you to search by text within a peer or filter by media among other things.
1051 ///
1052 /// # Examples
1053 ///
1054 /// ```
1055 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1056 /// // Let's print all the people who think grammers is cool.
1057 /// let mut messages = client.search_messages(peer).query("grammers is cool");
1058 ///
1059 /// while let Some(message) = messages.next().await? {
1060 /// let sender = message.sender().unwrap();
1061 /// println!("{}", sender.name().unwrap_or(&sender.id().to_string()));
1062 /// }
1063 /// # Ok(())
1064 /// # }
1065 /// ```
1066 pub fn search_messages<C: Into<PeerRef>>(&self, peer: C) -> SearchIter {
1067 SearchIter::new(self, peer.into())
1068 }
1069
1070 /// Iterate over the messages that match certain search criteria, without being restricted to
1071 /// searching in a specific peer. The downside is that this global search supports less filters.
1072 ///
1073 /// This allows you to search by text within a peer or filter by media among other things.
1074 ///
1075 /// # Examples
1076 ///
1077 /// ```
1078 /// # async fn f(client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1079 /// // Let's print all the peers were people think grammers is cool.
1080 /// let mut messages = client.search_all_messages().query("grammers is cool");
1081 ///
1082 /// while let Some(message) = messages.next().await? {
1083 /// println!("{}", message.peer().unwrap().name().unwrap_or(&message.peer().unwrap().id().to_string()));
1084 /// }
1085 /// # Ok(())
1086 /// # }
1087 /// ```
1088 pub fn search_all_messages(&self) -> GlobalSearchIter {
1089 GlobalSearchIter::new(self)
1090 }
1091
1092 /// Get up to 100 messages using their ID.
1093 ///
1094 /// Returns the new retrieved messages in a list. Those messages that could not be retrieved
1095 /// or do not belong to the input peer will be `None`. The length of the resulting list is the
1096 /// same as the length of the input message IDs, and the indices from the list of IDs map to
1097 /// the indices in the result so you can map them into the new list.
1098 ///
1099 /// # Examples
1100 ///
1101 /// ```
1102 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1103 /// let message_ids = [123, 456, 789];
1104 ///
1105 /// let messages = client.get_messages_by_id(peer, &message_ids).await?;
1106 /// let count = messages.into_iter().filter(Option::is_some).count();
1107 /// println!("{} out of {} messages were deleted!", message_ids.len() - count, message_ids.len());
1108 /// # Ok(())
1109 /// # }
1110 /// ```
1111 pub async fn get_messages_by_id<C: Into<PeerRef>>(
1112 &self,
1113 peer: C,
1114 message_ids: &[i32],
1115 ) -> Result<Vec<Option<Message>>, InvocationError> {
1116 let peer = peer.into();
1117 let id = message_ids
1118 .iter()
1119 .map(|&id| tl::enums::InputMessage::Id(tl::types::InputMessageId { id }))
1120 .collect();
1121
1122 let result = if peer.id.kind() == PeerKind::Channel {
1123 self.invoke(&tl::functions::channels::GetMessages {
1124 channel: peer.into(),
1125 id,
1126 })
1127 .await
1128 } else {
1129 self.invoke(&tl::functions::messages::GetMessages { id })
1130 .await
1131 }?;
1132
1133 let (messages, users, chats) = unpack_messages(result);
1134
1135 let peers = self.build_peer_map(users, chats).await;
1136 let mut map = messages
1137 .into_iter()
1138 .map(|m| Message::from_raw(self, m, Some(peer.into()), peers.handle()))
1139 .filter(|m| m.peer_id() == peer.id)
1140 .map(|m| (m.id(), m))
1141 .collect::<HashMap<_, _>>();
1142
1143 Ok(message_ids.iter().map(|id| map.remove(id)).collect())
1144 }
1145
1146 /// Get the latest pin from a peer.
1147 ///
1148 /// # Examples
1149 ///
1150 /// ```
1151 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1152 /// if let Some(message) = client.get_pinned_message(peer).await? {
1153 /// println!("There is a message pinned: {}", message.text());
1154 /// } else {
1155 /// println!("There are no messages pinned");
1156 /// }
1157 /// # Ok(())
1158 /// # }
1159 /// ```
1160 pub async fn get_pinned_message<C: Into<PeerRef>>(
1161 &self,
1162 peer: C,
1163 ) -> Result<Option<Message>, InvocationError> {
1164 let peer = peer.into();
1165 // TODO return types::Message and print its text in the example
1166 let id = vec![tl::enums::InputMessage::Pinned];
1167
1168 let result = if peer.id.kind() == PeerKind::Channel {
1169 self.invoke(&tl::functions::channels::GetMessages {
1170 channel: peer.into(),
1171 id,
1172 })
1173 .await
1174 } else {
1175 self.invoke(&tl::functions::messages::GetMessages { id })
1176 .await
1177 }?;
1178
1179 let (messages, users, chats) = unpack_messages(result);
1180
1181 let peers = self.build_peer_map(users, chats).await;
1182 Ok(messages
1183 .into_iter()
1184 .map(|m| Message::from_raw(self, m, Some(peer.into()), peers.handle()))
1185 .find(|m| m.peer_id() == peer.id))
1186 }
1187
1188 /// Pin a message in the peer. This will not notify any users.
1189 ///
1190 /// # Examples
1191 ///
1192 /// ```
1193 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1194 /// let message_id = 123;
1195 /// client.pin_message(peer, message_id).await?;
1196 /// # Ok(())
1197 /// # }
1198 /// ```
1199 // TODO return produced Option<service message>
1200 pub async fn pin_message<C: Into<PeerRef>>(
1201 &self,
1202 peer: C,
1203 message_id: i32,
1204 ) -> Result<(), InvocationError> {
1205 self.update_pinned(peer.into(), message_id, true).await
1206 }
1207
1208 /// Unpin a message from the peer.
1209 ///
1210 /// # Examples
1211 ///
1212 /// ```
1213 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1214 /// let message_id = 123;
1215 /// client.unpin_message(peer, message_id).await?;
1216 /// # Ok(())
1217 /// # }
1218 /// ```
1219 pub async fn unpin_message<C: Into<PeerRef>>(
1220 &self,
1221 peer: C,
1222 message_id: i32,
1223 ) -> Result<(), InvocationError> {
1224 self.update_pinned(peer.into(), message_id, false).await
1225 }
1226
1227 async fn update_pinned(
1228 &self,
1229 peer: PeerRef,
1230 id: i32,
1231 pin: bool,
1232 ) -> Result<(), InvocationError> {
1233 self.invoke(&tl::functions::messages::UpdatePinnedMessage {
1234 silent: true,
1235 unpin: !pin,
1236 pm_oneside: false,
1237 peer: peer.into(),
1238 id,
1239 })
1240 .await
1241 .map(drop)
1242 }
1243
1244 /// Unpin all currently-pinned messages from the peer.
1245 ///
1246 /// # Examples
1247 ///
1248 /// ```
1249 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1250 /// client.unpin_all_messages(peer).await?;
1251 /// # Ok(())
1252 /// # }
1253 /// ```
1254 pub async fn unpin_all_messages<C: Into<PeerRef>>(
1255 &self,
1256 peer: C,
1257 ) -> Result<(), InvocationError> {
1258 self.invoke(&tl::functions::messages::UnpinAllMessages {
1259 peer: peer.into().into(),
1260 top_msg_id: None,
1261 saved_peer_id: None,
1262 })
1263 .await?;
1264 Ok(())
1265 }
1266
1267 /// Send reaction.
1268 ///
1269 /// # Examples
1270 ///
1271 /// Via emoticon
1272 ///
1273 /// ```
1274 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1275 /// let message_id = 123;
1276 ///
1277 /// client.send_reactions(peer, message_id, "👍").await?;
1278 /// # Ok(())
1279 /// # }
1280 /// ```
1281 ///
1282 /// Make animation big & Add to recent
1283 ///
1284 /// ```
1285 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1286 /// use grammers_client::message::InputReactions;
1287 ///
1288 /// let message_id = 123;
1289 /// let reactions = InputReactions::emoticon("🤯").big().add_to_recent();
1290 ///
1291 /// client.send_reactions(peer, message_id, reactions).await?;
1292 /// # Ok(())
1293 /// # }
1294 /// ```
1295 ///
1296 /// Remove reactions
1297 ///
1298 /// ```
1299 /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
1300 /// use grammers_client::message::InputReactions;
1301 ///
1302 /// let message_id = 123;
1303 ///
1304 /// client.send_reactions(peer, message_id, InputReactions::remove()).await?;
1305 /// # Ok(())
1306 /// # }
1307 /// ```
1308 pub async fn send_reactions<C: Into<PeerRef>, R: Into<InputReactions>>(
1309 &self,
1310 peer: C,
1311 message_id: i32,
1312 reactions: R,
1313 ) -> Result<(), InvocationError> {
1314 let reactions = reactions.into();
1315
1316 self.invoke(&tl::functions::messages::SendReaction {
1317 big: reactions.big,
1318 add_to_recent: reactions.add_to_recent,
1319 peer: peer.into().into(),
1320 msg_id: message_id,
1321 reaction: Some(reactions.reactions),
1322 })
1323 .await?;
1324
1325 Ok(())
1326 }
1327}