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::messages::parse_mention_entities;
9use crate::utils::generate_random_id;
10use crate::Client;
11use crate::{types::IterBuffer, InputMessage};
12pub use grammers_mtsender::{AuthorizationError, InvocationError};
13use grammers_session::PackedChat;
14use grammers_tl_types as tl;
15
16const MAX_LIMIT: usize = 50;
17
18pub struct InlineResult {
19    client: Client,
20    query_id: i64,
21    result: 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 chat.
28    // TODO return the produced message
29    pub async fn send<C: Into<PackedChat>>(&self, chat: 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: chat.into().to_input_peer(),
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            })
45            .await
46            .map(drop)
47    }
48
49    /// The ID for this result.
50    pub fn id(&self) -> &str {
51        use tl::enums::BotInlineResult::*;
52
53        match &self.result {
54            Result(r) => &r.id,
55            BotInlineMediaResult(r) => &r.id,
56        }
57    }
58
59    /// The title for this result, if any.
60    pub fn title(&self) -> Option<&String> {
61        use tl::enums::BotInlineResult::*;
62
63        match &self.result {
64            Result(r) => r.title.as_ref(),
65            BotInlineMediaResult(r) => r.title.as_ref(),
66        }
67    }
68}
69
70impl InlineResultIter {
71    fn new(client: &Client, bot: PackedChat, query: &str) -> Self {
72        Self::from_request(
73            client,
74            MAX_LIMIT,
75            tl::functions::messages::GetInlineBotResults {
76                bot: bot.to_input_user_lossy(),
77                peer: tl::enums::InputPeer::Empty,
78                geo_point: None,
79                query: query.to_string(),
80                offset: String::new(),
81            },
82        )
83    }
84
85    /// Indicate the bot the chat where this inline query will be sent to.
86    ///
87    /// Some bots use this information to return different results depending on the type of the
88    /// chat, and some even "need" it to give useful results.
89    pub fn chat<C: Into<PackedChat>>(mut self, chat: C) -> Self {
90        self.request.peer = chat.into().to_input_peer();
91        self
92    }
93
94    /// Return the next `InlineResult` from the internal buffer, filling the buffer previously if
95    /// it's empty.
96    ///
97    /// Returns `None` if the `limit` is reached or there are no results left.
98    pub async fn next(&mut self) -> Result<Option<InlineResult>, InvocationError> {
99        if let Some(result) = self.next_raw() {
100            return result;
101        }
102
103        let tl::enums::messages::BotResults::Results(tl::types::messages::BotResults {
104            query_id,
105            next_offset,
106            results,
107            ..
108        }) = self.client.invoke(&self.request).await?;
109
110        if let Some(offset) = next_offset {
111            self.request.offset = offset;
112        } else {
113            self.last_chunk = true;
114        }
115
116        let client = self.client.clone();
117        self.buffer
118            .extend(results.into_iter().map(|r| InlineResult {
119                client: client.clone(),
120                query_id,
121                result: r,
122            }));
123
124        Ok(self.pop_item())
125    }
126}
127
128/// Method implementations related to dealing with bots.
129impl Client {
130    /// Perform an inline query to the specified bot.
131    ///
132    /// The query text may be empty.
133    ///
134    /// The return value is used like any other async iterator, by repeatedly calling `next`.
135    ///
136    /// Executing the query will fail if the input chat does not actually represent a bot account
137    /// supporting inline mode.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// # async fn f(bot: grammers_client::types::User, client: grammers_client::Client) -> Result<(), Box<dyn std::error::Error>> {
143    /// // This is equivalent to writing `@bot inline query` in a Telegram app.
144    /// let mut inline_results = client.inline_query(&bot, "inline query");
145    ///
146    /// while let Some(result) = inline_results.next().await? {
147    ///     println!("{}", result.title().unwrap());
148    /// }
149    /// # Ok(())
150    /// # }
151    /// ```
152    pub fn inline_query<C: Into<PackedChat>>(&self, bot: C, query: &str) -> InlineResultIter {
153        InlineResultIter::new(self, bot.into(), query)
154    }
155
156    pub async fn edit_inline_message<M: Into<InputMessage>>(
157        &self,
158        message_id: tl::enums::InputBotInlineMessageId,
159        input_message: M,
160    ) -> Result<bool, InvocationError> {
161        let message: InputMessage = input_message.into();
162        let entities = parse_mention_entities(self, message.entities);
163        let dc_id = message_id.dc_id();
164        let result = self
165            .invoke_in_dc(
166                &tl::functions::messages::EditInlineBotMessage {
167                    id: message_id,
168                    message: Some(message.text),
169                    media: message.media,
170                    entities,
171                    no_webpage: !message.link_preview,
172                    reply_markup: message.reply_markup,
173                    invert_media: message.invert_media,
174                },
175                dc_id,
176            )
177            .await?;
178        Ok(result)
179    }
180}