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    /// # 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}