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