Skip to main content

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    /// Bot accounts will receive an error response when using this method, as they do not have dialogs.
141    /// The closest a bot account can get to listing the peers it has interacted with is by querying the
142    /// session cache, but that approach can be misleading, as it often contains both additional peers,
143    /// and peers that have since blocked the bot ("deleted the dialog" with it).
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 peer = dialog.peer();
153    ///     println!("{} ({})", peer.name().unwrap_or_default(), peer.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    /// Bot accounts can use this method to leave a channel or group, but attempting
172    /// to leave the dialog with a user will fail, as bots do not actually have dialogs.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
178    /// // Consider making a backup before, you will lose access to the messages in peer!
179    /// client.delete_dialog(peer).await?;
180    /// # Ok(())
181    /// # }
182    /// ```
183    pub async fn delete_dialog<C: Into<PeerRef>>(&self, peer: C) -> Result<(), InvocationError> {
184        let peer = peer.into();
185        if peer.id.kind() == PeerKind::Channel {
186            self.invoke(&tl::functions::channels::LeaveChannel {
187                channel: peer.into(),
188            })
189            .await
190            .map(drop)
191        } else if peer.id.kind() == PeerKind::Chat {
192            self.invoke(&tl::functions::messages::DeleteChatUser {
193                chat_id: peer.into(),
194                user_id: tl::enums::InputUser::UserSelf,
195                revoke_history: false,
196            })
197            .await
198            .map(drop)
199        } else {
200            self.invoke(&tl::functions::messages::DeleteHistory {
201                just_clear: false,
202                revoke: false,
203                peer: peer.into(),
204                max_id: 0,
205                min_date: None,
206                max_date: None,
207            })
208            .await
209            .map(drop)
210        }
211    }
212
213    /// Mark a peer as read.
214    ///
215    /// If you want to get rid of all the mentions (for example, a voice note that you have not
216    /// listened to yet), you need to also use [`Client::clear_mentions`].
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
222    /// client.mark_as_read(peer).await?;
223    /// # Ok(())
224    /// # }
225    /// ```
226    pub async fn mark_as_read<C: Into<PeerRef>>(&self, peer: C) -> Result<(), InvocationError> {
227        let peer = peer.into();
228        if peer.id.kind() == PeerKind::Channel {
229            self.invoke(&tl::functions::channels::ReadHistory {
230                channel: peer.into(),
231                max_id: 0,
232            })
233            .await
234            .map(drop)
235        } else {
236            self.invoke(&tl::functions::messages::ReadHistory {
237                peer: peer.into(),
238                max_id: 0,
239            })
240            .await
241            .map(drop)
242        }
243    }
244
245    /// Clears all pending mentions from a peer, marking them as read.
246    ///
247    /// # Examples
248    ///
249    /// ```
250    /// # async fn f(peer: grammers_session::types::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
251    /// client.clear_mentions(peer).await?;
252    /// # Ok(())
253    /// # }
254    /// ```
255    pub async fn clear_mentions<C: Into<PeerRef>>(&self, peer: C) -> Result<(), InvocationError> {
256        self.invoke(&tl::functions::messages::ReadMentions {
257            peer: peer.into().into(),
258            top_msg_id: None,
259        })
260        .await
261        .map(drop)
262    }
263}