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