1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
//
// ferogram: async Telegram MTProto client in Rust
// https://github.com/ankit-chaubey/ferogram
//
// Licensed under either the MIT License or the Apache License 2.0.
// See the LICENSE-MIT or LICENSE-APACHE file in this repository:
// https://github.com/ankit-chaubey/ferogram
//
// Feel free to use, modify, and share this code.
// Please keep this notice when redistributing.
use std::collections::VecDeque;
use ferogram_tl_types as tl;
use crate::Client;
use crate::errors::InvocationError;
use crate::peer_ref::PeerRef;
use crate::update;
/// A Telegram dialog (chat, user, channel).
#[derive(Debug, Clone)]
pub struct Dialog {
pub raw: tl::enums::Dialog,
pub message: Option<tl::enums::Message>,
pub entity: Option<tl::enums::User>,
pub chat: Option<tl::enums::Chat>,
}
impl Dialog {
/// The dialog's display title.
pub fn title(&self) -> String {
if let Some(tl::enums::User::User(u)) = &self.entity {
let first = u.first_name.as_deref().unwrap_or("");
let last = u.last_name.as_deref().unwrap_or("");
let name = format!("{first} {last}").trim().to_string();
if !name.is_empty() {
return name;
}
}
if let Some(chat) = &self.chat {
return match chat {
tl::enums::Chat::Chat(c) => c.title.clone(),
tl::enums::Chat::Forbidden(c) => c.title.clone(),
tl::enums::Chat::Channel(c) => c.title.clone(),
tl::enums::Chat::ChannelForbidden(c) => c.title.clone(),
tl::enums::Chat::Empty(_) => "(empty)".into(),
};
}
"(Unknown)".to_string()
}
/// Peer of this dialog.
pub fn peer(&self) -> Option<&tl::enums::Peer> {
match &self.raw {
tl::enums::Dialog::Dialog(d) => Some(&d.peer),
tl::enums::Dialog::Folder(_) => None,
}
}
/// Unread message count.
pub fn unread_count(&self) -> i32 {
match &self.raw {
tl::enums::Dialog::Dialog(d) => d.unread_count,
_ => 0,
}
}
/// ID of the top message.
pub fn top_message(&self) -> i32 {
match &self.raw {
tl::enums::Dialog::Dialog(d) => d.top_message,
_ => 0,
}
}
}
/// Cursor-based iterator over dialogs. Created by [`Client::iter_dialogs`].
pub struct DialogIter {
pub(crate) offset_date: i32,
pub(crate) offset_id: i32,
pub(crate) offset_peer: tl::enums::InputPeer,
pub(crate) done: bool,
pub(crate) buffer: VecDeque<Dialog>,
/// Total dialog count as reported by the first server response.
/// `None` until the first page is fetched.
pub total: Option<i32>,
}
impl DialogIter {
const PAGE_SIZE: i32 = 100;
/// Total number of dialogs as reported by the server on the first page fetch.
///
/// Returns `None` before the first [`next`](Self::next) call, and `None` for
/// accounts with fewer dialogs than `PAGE_SIZE` (where the server returns
/// `messages.Dialogs` instead of `messages.DialogsSlice`).
pub fn total(&self) -> Option<i32> {
self.total
}
/// Fetch the next dialog. Returns `None` when all dialogs have been yielded.
pub async fn next(&mut self, client: &Client) -> Result<Option<Dialog>, InvocationError> {
if let Some(d) = self.buffer.pop_front() {
return Ok(Some(d));
}
if self.done {
return Ok(None);
}
let req = tl::functions::messages::GetDialogs {
exclude_pinned: false,
folder_id: None,
offset_date: self.offset_date,
offset_id: self.offset_id,
offset_peer: self.offset_peer.clone(),
limit: Self::PAGE_SIZE,
hash: 0,
};
let (dialogs, count): (Vec<crate::Dialog>, Option<i32>) =
client.get_dialogs_raw_with_count(req).await?;
// Populate total from the first response (messages.DialogsSlice carries a count).
if self.total.is_none() {
self.total = count;
}
if dialogs.is_empty() || dialogs.len() < Self::PAGE_SIZE as usize {
self.done = true;
}
// Prepare cursor for next page
if let Some(last) = dialogs.last() {
self.offset_date = last
.message
.as_ref()
.map(|m| match m {
tl::enums::Message::Message(x) => x.date,
tl::enums::Message::Service(x) => x.date,
_ => 0,
})
.unwrap_or(0);
self.offset_id = last.top_message();
if let Some(peer) = last.peer() {
self.offset_peer = client.inner.peer_cache.read().await.peer_to_input(peer)?;
}
}
self.buffer.extend(dialogs);
Ok(self.buffer.pop_front())
}
}
/// Cursor-based iterator over message history. Created by [`Client::iter_messages`].
pub struct MessageIter {
pub(crate) unresolved: Option<PeerRef>,
pub(crate) peer: Option<tl::enums::Peer>,
pub(crate) offset_id: i32,
pub(crate) done: bool,
pub(crate) buffer: VecDeque<update::IncomingMessage>,
/// Total message count from the first server response (messages.Slice).
/// `None` until the first page is fetched, `None` for `messages.Messages`
/// (which returns an exact slice with no separate count).
pub total: Option<i32>,
}
impl MessageIter {
const PAGE_SIZE: i32 = 100;
/// Total message count from the first server response.
///
/// Returns `None` before the first [`next`](Self::next) call, or for chats
/// where the server returns an exact (non-slice) response.
pub fn total(&self) -> Option<i32> {
self.total
}
/// Fetch the next message (newest first). Returns `None` when all messages have been yielded.
pub async fn next(
&mut self,
client: &Client,
) -> Result<Option<update::IncomingMessage>, InvocationError> {
if let Some(m) = self.buffer.pop_front() {
return Ok(Some(m));
}
if self.done {
return Ok(None);
}
// Resolve PeerRef on first call, then reuse the cached Peer.
let peer = if let Some(p) = &self.peer {
p.clone()
} else {
let pr = self.unresolved.take().expect("MessageIter: peer not set");
let p = pr.resolve(client).await?;
self.peer = Some(p.clone());
p
};
let input_peer = client.inner.peer_cache.read().await.peer_to_input(&peer)?;
let (page, count): (Vec<crate::update::IncomingMessage>, Option<i32>) = client
.get_messages_with_count(input_peer, Self::PAGE_SIZE, self.offset_id)
.await?;
if self.total.is_none() {
self.total = count;
}
if page.is_empty() || page.len() < Self::PAGE_SIZE as usize {
self.done = true;
}
if let Some(last) = page.last() {
self.offset_id = last.id();
}
self.buffer.extend(page);
Ok(self.buffer.pop_front())
}
}