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}