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