Skip to main content

layer_client/
inline_iter.rs

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