Skip to main content

layer_client/
inline_iter.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// NOTE:
5// The "Layer" project is no longer maintained or supported.
6// Its original purpose for personal SDK/APK experimentation and learning
7// has been fulfilled.
8//
9// Please use Ferogram instead:
10// https://github.com/ankit-chaubey/ferogram
11// Ferogram will receive future updates and development, although progress
12// may be slower.
13//
14// Ferogram is an async Telegram MTProto client library written in Rust.
15// Its implementation follows the behaviour of the official Telegram clients,
16// particularly Telegram Desktop and TDLib, and aims to provide a clean and
17// modern async interface for building Telegram clients and tools.
18
19//! Inline query support: two directions:
20//!
21//! ## Receiving (bot side)
22//! [`InlineQueryIter`] streams live inline queries arriving from users typing
23//! `@yourbot query`.  Backed by the update stream.
24//!
25//! [`InlineResultIter`] lets a *user* account call `messages.GetInlineBotResults`
26//! and iterate the results, with a `.send()` helper to forward a chosen result.
27
28use std::collections::VecDeque;
29
30use layer_tl_types as tl;
31use layer_tl_types::{Cursor, Deserializable};
32use tokio::sync::mpsc;
33
34use crate::update::{InlineQuery, Update};
35use crate::{Client, InvocationError};
36
37// InlineQueryIter (bot side: receive)
38
39/// Async iterator over *incoming* inline queries (bot side).
40/// Created by [`Client::iter_inline_queries`].
41pub struct InlineQueryIter {
42    rx: mpsc::UnboundedReceiver<InlineQuery>,
43}
44
45impl InlineQueryIter {
46    /// Wait for the next inline query. Returns `None` when the stream ends.
47    pub async fn next(&mut self) -> Option<InlineQuery> {
48        self.rx.recv().await
49    }
50}
51
52/// A single result returned by a bot for an inline query.
53/// Obtained from [`InlineResultIter::next`].
54pub struct InlineResult {
55    client: Client,
56    query_id: i64,
57    /// The raw TL result variant.
58    pub raw: tl::enums::BotInlineResult,
59}
60
61impl InlineResult {
62    /// The result ID string.
63    pub fn id(&self) -> &str {
64        match &self.raw {
65            tl::enums::BotInlineResult::BotInlineResult(r) => &r.id,
66            tl::enums::BotInlineResult::BotInlineMediaResult(r) => &r.id,
67        }
68    }
69
70    /// Title, if the result has one.
71    pub fn title(&self) -> Option<&str> {
72        match &self.raw {
73            tl::enums::BotInlineResult::BotInlineResult(r) => r.title.as_deref(),
74            tl::enums::BotInlineResult::BotInlineMediaResult(r) => r.title.as_deref(),
75        }
76    }
77
78    /// Description, if present.
79    pub fn description(&self) -> Option<&str> {
80        match &self.raw {
81            tl::enums::BotInlineResult::BotInlineResult(r) => r.description.as_deref(),
82            tl::enums::BotInlineResult::BotInlineMediaResult(r) => r.description.as_deref(),
83        }
84    }
85
86    /// Send this inline result to the given peer.
87    pub async fn send(&self, peer: tl::enums::Peer) -> Result<(), InvocationError> {
88        let input_peer = {
89            let cache = self.client.inner.peer_cache.read().await;
90            cache.peer_to_input(&peer)
91        };
92        let req = tl::functions::messages::SendInlineBotResult {
93            silent: false,
94            background: false,
95            clear_draft: false,
96            hide_via: false,
97            peer: input_peer,
98            reply_to: None,
99            random_id: crate::random_i64_pub(),
100            query_id: self.query_id,
101            id: self.id().to_string(),
102            schedule_date: None,
103            send_as: None,
104            quick_reply_shortcut: None,
105            allow_paid_stars: None,
106        };
107        self.client.rpc_call_raw_pub(&req).await?;
108        Ok(())
109    }
110}
111
112/// Paginated iterator over results from a bot's inline mode.
113/// Created by [`Client::inline_query`].
114pub struct InlineResultIter {
115    client: Client,
116    request: tl::functions::messages::GetInlineBotResults,
117    buffer: VecDeque<InlineResult>,
118    last_chunk: bool,
119}
120
121impl InlineResultIter {
122    fn new(client: Client, request: tl::functions::messages::GetInlineBotResults) -> Self {
123        Self {
124            client,
125            request,
126            buffer: VecDeque::new(),
127            last_chunk: false,
128        }
129    }
130
131    /// Override the context peer (some bots return different results per chat type).
132    pub fn peer(mut self, peer: tl::enums::InputPeer) -> Self {
133        self.request.peer = peer;
134        self
135    }
136
137    /// Fetch the next result. Returns `None` when all results are consumed.
138    pub async fn next(&mut self) -> Result<Option<InlineResult>, InvocationError> {
139        if let Some(item) = self.buffer.pop_front() {
140            return Ok(Some(item));
141        }
142        if self.last_chunk {
143            return Ok(None);
144        }
145
146        let raw = self.client.rpc_call_raw_pub(&self.request).await?;
147        let mut cur = Cursor::from_slice(&raw);
148        let tl::enums::messages::BotResults::BotResults(r) =
149            tl::enums::messages::BotResults::deserialize(&mut cur)?;
150
151        let query_id = r.query_id;
152        if let Some(offset) = r.next_offset {
153            self.request.offset = offset;
154        } else {
155            self.last_chunk = true;
156        }
157
158        let client = self.client.clone();
159        self.buffer
160            .extend(r.results.into_iter().map(|raw| InlineResult {
161                client: client.clone(),
162                query_id,
163                raw,
164            }));
165
166        Ok(self.buffer.pop_front())
167    }
168}
169
170// Client extensions
171
172impl Client {
173    /// Return an iterator that yields every *incoming* inline query (bot side).
174    pub fn iter_inline_queries(&self) -> InlineQueryIter {
175        let (tx, rx) = mpsc::unbounded_channel();
176        let client = self.clone();
177        tokio::spawn(async move {
178            let mut stream = client.stream_updates();
179            loop {
180                match stream.next().await {
181                    Some(Update::InlineQuery(q)) => {
182                        if tx.send(q).is_err() {
183                            break;
184                        }
185                    }
186                    Some(_) => {}
187                    None => break,
188                }
189            }
190        });
191        InlineQueryIter { rx }
192    }
193
194    /// Query a bot's inline mode and return a paginated [`InlineResultIter`].
195    ///
196    /// Equivalent to typing `@bot_username query` in a Telegram app.
197    ///
198    /// # Example
199    /// ```rust,no_run
200    /// # async fn f(client: layer_client::Client, bot: layer_tl_types::enums::Peer,
201    /// #            dest: layer_tl_types::enums::Peer) -> Result<(), layer_client::InvocationError> {
202    /// let mut iter = client.inline_query(bot, "hello").await?;
203    /// while let Some(r) = iter.next().await? {
204    /// println!("{}", r.title().unwrap_or("(no title)"));
205    /// }
206    /// # Ok(()) }
207    /// ```
208    pub async fn inline_query(
209        &self,
210        bot: tl::enums::Peer,
211        query: &str,
212    ) -> Result<InlineResultIter, InvocationError> {
213        let input_bot = {
214            let cache = self.inner.peer_cache.read().await;
215            match cache.peer_to_input(&bot) {
216                tl::enums::InputPeer::User(u) => {
217                    tl::enums::InputUser::InputUser(tl::types::InputUser {
218                        user_id: u.user_id,
219                        access_hash: u.access_hash,
220                    })
221                }
222                _ => tl::enums::InputUser::Empty,
223            }
224        };
225        let request = tl::functions::messages::GetInlineBotResults {
226            bot: input_bot,
227            peer: tl::enums::InputPeer::Empty,
228            geo_point: None,
229            query: query.to_string(),
230            offset: String::new(),
231        };
232        Ok(InlineResultIter::new(self.clone(), request))
233    }
234}