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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// 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 ferogram_tl_types::{Cursor, Deserializable};
use tokio::sync::mpsc;
use crate::update::{InlineQuery, Update};
use crate::{Client, InvocationError};
// InlineQueryIter (bot side: receive)
/// Backlog capacity for [`InlineQueryIter`]: if the consumer falls this far
/// behind, the producer stops accepting new inline queries and the spawned
/// feed task exits cleanly rather than growing memory without bound.
const INLINE_QUERY_CHANNEL_CAP: usize = 256;
/// Async iterator over *incoming* inline queries (bot side).
/// Created by [`Client::iter_inline_queries`].
pub struct InlineQueryIter {
rx: mpsc::Receiver<InlineQuery>,
}
impl InlineQueryIter {
/// Wait for the next inline query. Returns `None` when the stream ends.
pub async fn next(&mut self) -> Option<InlineQuery> {
self.rx.recv().await
}
}
/// A single result returned by a bot for an inline query.
/// Obtained from [`InlineResultIter::next`].
pub struct InlineResult {
client: Client,
query_id: i64,
/// The raw TL result variant.
pub raw: tl::enums::BotInlineResult,
}
impl InlineResult {
/// The result ID string.
pub fn id(&self) -> &str {
match &self.raw {
tl::enums::BotInlineResult::BotInlineResult(r) => &r.id,
tl::enums::BotInlineResult::BotInlineMediaResult(r) => &r.id,
}
}
/// Title, if the result has one.
pub fn title(&self) -> Option<&str> {
match &self.raw {
tl::enums::BotInlineResult::BotInlineResult(r) => r.title.as_deref(),
tl::enums::BotInlineResult::BotInlineMediaResult(r) => r.title.as_deref(),
}
}
/// Description, if present.
pub fn description(&self) -> Option<&str> {
match &self.raw {
tl::enums::BotInlineResult::BotInlineResult(r) => r.description.as_deref(),
tl::enums::BotInlineResult::BotInlineMediaResult(r) => r.description.as_deref(),
}
}
/// Send this inline result to the given peer.
pub async fn send(&self, peer: tl::enums::Peer) -> Result<(), InvocationError> {
let input_peer = self
.client
.inner
.peer_cache
.read()
.await
.peer_to_input(&peer)?;
let req = tl::functions::messages::SendInlineBotResult {
silent: false,
background: false,
clear_draft: false,
hide_via: false,
peer: input_peer,
reply_to: None,
random_id: crate::random_i64_pub(),
query_id: self.query_id,
id: self.id().to_string(),
schedule_date: None,
send_as: None,
quick_reply_shortcut: None,
allow_paid_stars: None,
};
self.client.rpc_call_raw(&req).await?;
Ok(())
}
}
/// Paginated iterator over results from a bot's inline mode.
/// Created by [`Client::inline_query`].
pub struct InlineResultIter {
client: Client,
request: tl::functions::messages::GetInlineBotResults,
buffer: VecDeque<InlineResult>,
last_chunk: bool,
}
impl InlineResultIter {
fn new(client: Client, request: tl::functions::messages::GetInlineBotResults) -> Self {
Self {
client,
request,
buffer: VecDeque::new(),
last_chunk: false,
}
}
/// Override the context peer (some bots return different results per chat type).
pub fn peer(mut self, peer: tl::enums::InputPeer) -> Self {
self.request.peer = peer;
self
}
/// Fetch the next result. Returns `None` when all results are consumed.
pub async fn next(&mut self) -> Result<Option<InlineResult>, InvocationError> {
if let Some(item) = self.buffer.pop_front() {
return Ok(Some(item));
}
if self.last_chunk {
return Ok(None);
}
let raw: Vec<u8> = self.client.rpc_call_raw(&self.request).await?;
let mut cur = Cursor::from_slice(&raw);
let tl::enums::messages::BotResults::BotResults(r) =
tl::enums::messages::BotResults::deserialize(&mut cur)?;
let query_id = r.query_id;
if let Some(offset) = r.next_offset {
self.request.offset = offset;
} else {
self.last_chunk = true;
}
let client = self.client.clone();
self.buffer
.extend(r.results.into_iter().map(|raw| InlineResult {
client: client.clone(),
query_id,
raw,
}));
Ok(self.buffer.pop_front())
}
}
// Client extensions
impl Client {
/// Return an iterator that yields every *incoming* inline query (bot side).
///
/// The internal channel is bounded to [`INLINE_QUERY_CHANNEL_CAP`] entries.
/// If the consumer stops calling [`InlineQueryIter::next`] and the backlog
/// fills, the feed task exits and the stream ends rather than growing memory
/// without bound.
pub fn iter_inline_queries(&self) -> InlineQueryIter {
let (tx, rx) = mpsc::channel(INLINE_QUERY_CHANNEL_CAP);
let client = self.clone();
tokio::spawn(async move {
let mut stream = client.stream_updates();
loop {
match stream.next().await {
Some(Update::InlineQuery(q)) => {
if tx.send(q).await.is_err() {
break;
}
}
Some(_) => {}
None => break,
}
}
});
InlineQueryIter { rx }
}
/// Query a bot's inline mode and return a paginated [`InlineResultIter`].
///
/// Equivalent to typing `@bot_username query` in a Telegram app.
///
/// # Example
/// ```rust,no_run
/// # async fn f(client: ferogram::Client, bot: ferogram_tl_types::enums::Peer,
/// # dest: ferogram_tl_types::enums::Peer) -> Result<(), ferogram::InvocationError> {
/// let mut iter = client.inline_query(bot, "hello").await?;
/// while let Some(r) = iter.next().await? {
/// println!("{}", r.title().unwrap_or("(no title)"));
/// }
/// # Ok(()) }
/// ```
pub async fn inline_query(
&self,
bot: tl::enums::Peer,
query: &str,
) -> Result<InlineResultIter, InvocationError> {
let input_bot = {
match {
let _g: tokio::sync::RwLockReadGuard<'_, crate::PeerCache> =
self.inner.peer_cache.read().await;
_g
}
.peer_to_input(&bot)?
{
tl::enums::InputPeer::User(u) => {
tl::enums::InputUser::InputUser(tl::types::InputUser {
user_id: u.user_id,
access_hash: u.access_hash,
})
}
_ => tl::enums::InputUser::Empty,
}
};
let request = tl::functions::messages::GetInlineBotResults {
bot: input_bot,
peer: tl::enums::InputPeer::Empty,
geo_point: None,
query: query.to_string(),
offset: String::new(),
};
Ok(InlineResultIter::new(self.clone(), request))
}
}