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