grammers_client/client/
bots.rs

1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8use crate::Client;
9use crate::client::messages::parse_mention_entities;
10use crate::utils::generate_random_id;
11use crate::{InputMessage, types::IterBuffer};
12use grammers_mtsender::InvocationError;
13use grammers_session::defs::PeerRef;
14use grammers_tl_types as tl;
15
16const MAX_LIMIT: usize = 50;
17
18pub struct InlineResult {
19    client: Client,
20    query_id: i64,
21    pub raw: tl::enums::BotInlineResult,
22}
23
24pub type InlineResultIter = IterBuffer<tl::functions::messages::GetInlineBotResults, InlineResult>;
25
26impl InlineResult {
27    /// Send this inline result to the specified peer.
28    // TODO return the produced message
29    pub async fn send<C: Into<PeerRef>>(&self, peer: C) -> Result<(), InvocationError> {
30        self.client
31            .invoke(&tl::functions::messages::SendInlineBotResult {
32                silent: false,
33                background: false,
34                clear_draft: false,
35                hide_via: false,
36                peer: peer.into().into(),
37                reply_to: None,
38                random_id: generate_random_id(),
39                query_id: self.query_id,
40                id: self.id().to_string(),
41                schedule_date: None,
42                send_as: None,
43                quick_reply_shortcut: None,
44                allow_paid_stars: None,
45            })
46            .await
47            .map(drop)
48    }
49
50    /// The ID for this result.
51    pub fn id(&self) -> &str {
52        use tl::enums::BotInlineResult::*;
53
54        match &self.raw {
55            Result(r) => &r.id,
56            BotInlineMediaResult(r) => &r.id,
57        }
58    }
59
60    /// The title for this result, if any.
61    pub fn title(&self) -> Option<&String> {
62        use tl::enums::BotInlineResult::*;
63
64        match &self.raw {
65            Result(r) => r.title.as_ref(),
66            BotInlineMediaResult(r) => r.title.as_ref(),
67        }
68    }
69}
70
71impl InlineResultIter {
72    fn new(client: &Client, bot: PeerRef, query: &str) -> Self {
73        Self::from_request(
74            client,
75            MAX_LIMIT,
76            tl::functions::messages::GetInlineBotResults {
77                bot: bot.into(),
78                peer: tl::enums::InputPeer::Empty,
79                geo_point: None,
80                query: query.to_string(),
81                offset: String::new(),
82            },
83        )
84    }
85
86    /// Indicate the bot the peer where this inline query will be sent to.
87    ///
88    /// Some bots use this information to return different results depending on the type of the
89    /// peer, and some even "need" it to give useful results.
90    pub fn peer<C: Into<PeerRef>>(mut self, peer: C) -> Self {
91        self.request.peer = peer.into().into();
92        self
93    }
94
95    /// Return the next `InlineResult` from the internal buffer, filling the buffer previously if
96    /// it's empty.
97    ///
98    /// Returns `None` if the `limit` is reached or there are no results left.
99    pub async fn next(&mut self) -> Result<Option<InlineResult>, InvocationError> {
100        if let Some(result) = self.next_raw() {
101            return result;
102        }
103
104        let tl::enums::messages::BotResults::Results(tl::types::messages::BotResults {
105            query_id,
106            next_offset,
107            results,
108            ..
109        }) = self.client.invoke(&self.request).await?;
110
111        if let Some(offset) = next_offset {
112            self.request.offset = offset;
113        } else {
114            self.last_chunk = true;
115        }
116
117        let client = self.client.clone();
118        self.buffer
119            .extend(results.into_iter().map(|r| InlineResult {
120                client: client.clone(),
121                query_id,
122                raw: r,
123            }));
124
125        Ok(self.pop_item())
126    }
127}
128
129/// Method implementations related to dealing with bots.
130impl Client {
131    /// Perform an inline query to the specified bot.
132    ///
133    /// The query text may be empty.
134    ///
135    /// The return value is used like any other async iterator, by repeatedly calling `next`.
136    ///
137    /// Executing the query will fail if the input peer does not actually represent a bot account
138    /// supporting inline mode.
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// # async fn f(bot: grammers_session::defs::PeerRef, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
144    /// // This is equivalent to writing `@bot inline query` in a Telegram app.
145    /// let mut inline_results = client.inline_query(bot, "inline query");
146    ///
147    /// while let Some(result) = inline_results.next().await? {
148    ///     println!("{}", result.title().unwrap());
149    /// }
150    /// # Ok(())
151    /// # }
152    /// ```
153    pub fn inline_query<C: Into<PeerRef>>(&self, bot: C, query: &str) -> InlineResultIter {
154        InlineResultIter::new(self, bot.into(), query)
155    }
156
157    /// Edits an inline message sent by a bot.
158    ///
159    /// Similar to [`Client::send_message`], advanced formatting can be achieved with the
160    /// options offered by [`InputMessage`].
161    ///
162    /// [`InputMessage`]: crate::InputMessage
163    pub async fn edit_inline_message<M: Into<InputMessage>>(
164        &self,
165        message_id: tl::enums::InputBotInlineMessageId,
166        input_message: M,
167    ) -> Result<bool, InvocationError> {
168        let message: InputMessage = input_message.into();
169        let entities = parse_mention_entities(self, message.entities);
170        if message.media.as_ref().is_some_and(|media| {
171            !matches!(
172                media,
173                tl::enums::InputMedia::PhotoExternal(_)
174                    | tl::enums::InputMedia::DocumentExternal(_),
175            )
176        }) {
177            let dc_id = message_id.dc_id();
178            self.invoke_in_dc(
179                dc_id,
180                &tl::functions::messages::EditInlineBotMessage {
181                    id: message_id,
182                    message: Some(message.text),
183                    media: message.media,
184                    entities,
185                    no_webpage: !message.link_preview,
186                    reply_markup: message.reply_markup,
187                    invert_media: message.invert_media,
188                },
189            )
190            .await
191        } else {
192            self.invoke(&tl::functions::messages::EditInlineBotMessage {
193                id: message_id,
194                message: Some(message.text),
195                media: message.media,
196                entities,
197                no_webpage: !message.link_preview,
198                reply_markup: message.reply_markup,
199                invert_media: message.invert_media,
200            })
201            .await
202        }
203    }
204}