Skip to main content

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