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