grammers_client/client/dialogs.rs
1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8use crate::types::{ChatMap, Dialog, IterBuffer, Message};
9use crate::Client;
10use grammers_mtsender::InvocationError;
11use grammers_session::PackedChat;
12use grammers_tl_types as tl;
13use std::collections::HashMap;
14
15const MAX_LIMIT: usize = 100;
16
17pub type DialogIter = IterBuffer<tl::functions::messages::GetDialogs, Dialog>;
18
19impl DialogIter {
20 fn new(client: &Client) -> Self {
21 // TODO let users tweak all the options from the request
22 Self::from_request(
23 client,
24 MAX_LIMIT,
25 tl::functions::messages::GetDialogs {
26 exclude_pinned: false,
27 folder_id: None,
28 offset_date: 0,
29 offset_id: 0,
30 offset_peer: tl::enums::InputPeer::Empty,
31 limit: 0,
32 hash: 0,
33 },
34 )
35 }
36
37 /// Determines how many dialogs there are in total.
38 ///
39 /// This only performs a network call if `next` has not been called before.
40 pub async fn total(&mut self) -> Result<usize, InvocationError> {
41 if let Some(total) = self.total {
42 return Ok(total);
43 }
44
45 use tl::enums::messages::Dialogs;
46
47 self.request.limit = 1;
48 let total = match self.client.invoke(&self.request).await? {
49 Dialogs::Dialogs(dialogs) => dialogs.dialogs.len(),
50 Dialogs::Slice(dialogs) => dialogs.count as usize,
51 Dialogs::NotModified(dialogs) => dialogs.count as usize,
52 };
53 self.total = Some(total);
54 Ok(total)
55 }
56
57 /// Return the next `Dialog` from the internal buffer, filling the buffer previously if it's
58 /// empty.
59 ///
60 /// Returns `None` if the `limit` is reached or there are no dialogs left.
61 pub async fn next(&mut self) -> Result<Option<Dialog>, InvocationError> {
62 if let Some(result) = self.next_raw() {
63 return result;
64 }
65
66 use tl::enums::messages::Dialogs;
67
68 self.request.limit = self.determine_limit(MAX_LIMIT);
69 let (dialogs, messages, users, chats) = match self.client.invoke(&self.request).await? {
70 Dialogs::Dialogs(d) => {
71 self.last_chunk = true;
72 self.total = Some(d.dialogs.len());
73 (d.dialogs, d.messages, d.users, d.chats)
74 }
75 Dialogs::Slice(d) => {
76 self.last_chunk = d.dialogs.len() < self.request.limit as usize;
77 self.total = Some(d.count as usize);
78 (d.dialogs, d.messages, d.users, d.chats)
79 }
80 Dialogs::NotModified(_) => {
81 panic!("API returned Dialogs::NotModified even though hash = 0")
82 }
83 };
84
85 {
86 let mut state = self.client.0.state.write().unwrap();
87 // Telegram can return peers without hash (e.g. Users with 'min: true')
88 let _ = state.chat_hashes.extend(&users, &chats);
89 }
90
91 let chats = ChatMap::new(users, chats);
92 let mut messages = messages
93 .into_iter()
94 .flat_map(|m| Message::from_raw(&self.client, m, &chats))
95 .map(|m| ((&m.raw.peer_id).into(), m))
96 .collect::<HashMap<_, _>>();
97
98 {
99 let mut state = self.client.0.state.write().unwrap();
100 self.buffer.extend(dialogs.into_iter().map(|dialog| {
101 if let tl::enums::Dialog::Dialog(tl::types::Dialog {
102 peer: tl::enums::Peer::Channel(channel),
103 pts: Some(pts),
104 ..
105 }) = &dialog
106 {
107 state
108 .message_box
109 .try_set_channel_state(channel.channel_id, *pts);
110 }
111 Dialog::new(dialog, &mut messages, &chats)
112 }));
113 }
114
115 // Don't bother updating offsets if this is the last time stuff has to be fetched.
116 if !self.last_chunk && !self.buffer.is_empty() {
117 self.request.exclude_pinned = true;
118 if let Some(last_message) = self
119 .buffer
120 .iter()
121 .rev()
122 .find_map(|dialog| dialog.last_message.as_ref())
123 {
124 self.request.offset_date = last_message.raw.date;
125 self.request.offset_id = last_message.raw.id;
126 }
127 self.request.offset_peer = self.buffer[self.buffer.len() - 1]
128 .chat()
129 .pack()
130 .to_input_peer();
131 }
132
133 Ok(self.pop_item())
134 }
135}
136
137/// Method implementations related to open conversations.
138impl Client {
139 /// Returns a new iterator over the dialogs.
140 ///
141 /// While iterating, the update state for any broadcast channel or megagroup will be set if it was unknown before.
142 /// When the update state is set for these chats, the library can actively check to make sure it's not missing any
143 /// updates from them (as long as the queue limit for updates is larger than zero).
144 ///
145 /// # Examples
146 ///
147 /// ```
148 /// # async fn f(client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
149 /// let mut dialogs = client.iter_dialogs();
150 ///
151 /// while let Some(dialog) = dialogs.next().await? {
152 /// let chat = dialog.chat();
153 /// println!("{} ({})", chat.name(), chat.id());
154 /// }
155 /// # Ok(())
156 /// # }
157 /// ```
158 pub fn iter_dialogs(&self) -> DialogIter {
159 DialogIter::new(self)
160 }
161
162 /// Deletes a dialog, effectively removing it from your list of open conversations.
163 ///
164 /// The dialog is only deleted for yourself.
165 ///
166 /// Deleting a dialog effectively clears the message history and "kicks" you from it.
167 ///
168 /// For groups and channels, this is the same as leaving said chat. This method does **not**
169 /// delete the chat itself (the chat still exists and the other members will remain inside).
170 ///
171 /// # Examples
172 ///
173 /// ```
174 /// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
175 /// // Consider making a backup before, you will lose access to the messages in chat!
176 /// client.delete_dialog(&chat).await?;
177 /// # Ok(())
178 /// # }
179 /// ```
180 pub async fn delete_dialog<C: Into<PackedChat>>(&self, chat: C) -> Result<(), InvocationError> {
181 let chat = chat.into();
182 if let Some(channel) = chat.try_to_input_channel() {
183 self.invoke(&tl::functions::channels::LeaveChannel { channel })
184 .await
185 .map(drop)
186 } else if let Some(chat_id) = chat.try_to_chat_id() {
187 // TODO handle PEER_ID_INVALID and ignore it (happens when trying to delete deactivated chats)
188 self.invoke(&tl::functions::messages::DeleteChatUser {
189 chat_id,
190 user_id: tl::enums::InputUser::UserSelf,
191 revoke_history: false,
192 })
193 .await
194 .map(drop)
195 } else {
196 // TODO only do this if we're not a bot
197 self.invoke(&tl::functions::messages::DeleteHistory {
198 just_clear: false,
199 revoke: false,
200 peer: chat.to_input_peer(),
201 max_id: 0,
202 min_date: None,
203 max_date: None,
204 })
205 .await
206 .map(drop)
207 }
208 }
209
210 /// Mark a chat as read.
211 ///
212 /// If you want to get rid of all the mentions (for example, a voice note that you have not
213 /// listened to yet), you need to also use [`Client::clear_mentions`].
214 ///
215 /// # Examples
216 ///
217 /// ```
218 /// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
219 /// client.mark_as_read(&chat).await?;
220 /// # Ok(())
221 /// # }
222 /// ```
223 pub async fn mark_as_read<C: Into<PackedChat>>(&self, chat: C) -> Result<(), InvocationError> {
224 let chat = chat.into();
225 if let Some(channel) = chat.try_to_input_channel() {
226 self.invoke(&tl::functions::channels::ReadHistory { channel, max_id: 0 })
227 .await
228 .map(drop)
229 } else {
230 self.invoke(&tl::functions::messages::ReadHistory {
231 peer: chat.to_input_peer(),
232 max_id: 0,
233 })
234 .await
235 .map(drop)
236 }
237 }
238
239 /// Clears all pending mentions from a chat, marking them as read.
240 ///
241 /// # Examples
242 ///
243 /// ```
244 /// # async fn f(chat: grammers_client::types::Chat, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
245 /// client.clear_mentions(&chat).await?;
246 /// # Ok(())
247 /// # }
248 /// ```
249 pub async fn clear_mentions<C: Into<PackedChat>>(
250 &self,
251 chat: C,
252 ) -> Result<(), InvocationError> {
253 self.invoke(&tl::functions::messages::ReadMentions {
254 peer: chat.into().to_input_peer(),
255 top_msg_id: None,
256 })
257 .await
258 .map(drop)
259 }
260}