Skip to main content

grammers_client/update/
inline_query.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 std::fmt;
10
11use grammers_mtsender::InvocationError;
12use grammers_session::types::{PeerId, PeerRef};
13use grammers_session::updates::State;
14use grammers_tl_types as tl;
15
16use crate::message::InputMessage;
17use crate::peer::{Peer, PeerMap, User};
18use crate::{Client, utils};
19
20/// Update that bots receive when a user performs an inline query such as `@bot query`.
21#[derive(Clone)]
22pub struct InlineQuery {
23    pub raw: tl::enums::Update,
24    pub state: State,
25    pub(crate) client: Client,
26    pub(crate) peers: PeerMap,
27}
28
29/// An inline query answer builder.
30pub struct Answer {
31    request: tl::functions::messages::SetInlineBotResults,
32    client: Client,
33}
34
35impl InlineQuery {
36    fn update(&self) -> &tl::types::UpdateBotInlineQuery {
37        match &self.raw {
38            tl::enums::Update::BotInlineQuery(update) => update,
39            _ => unreachable!(),
40        }
41    }
42
43    /// The [`Self::sender`]'s identifier.
44    pub fn sender_id(&self) -> PeerId {
45        PeerId::user_unchecked(self.update().user_id)
46    }
47
48    /// Cached reference to the [`Self::sender`], if it is in cache.
49    pub async fn sender_ref(
50        &self,
51    ) -> Result<Option<PeerRef>, Box<dyn std::error::Error + Send + Sync>> {
52        self.peers.get_ref(self.sender_id()).await
53    }
54
55    /// User that sent the query, if it is in cache.
56    pub fn sender(&self) -> Option<&User> {
57        match self.peers.get(self.sender_id()) {
58            Some(Peer::User(user)) => Some(user),
59            None => None,
60            _ => unreachable!(),
61        }
62    }
63
64    /// The text of the inline query.
65    pub fn text(&self) -> &str {
66        self.update().query.as_str()
67    }
68
69    /// The offset of the inline query.
70    pub fn offset(&self) -> &str {
71        self.update().offset.as_str()
72    }
73
74    /// Answer the inline query.
75    // TODO: add example
76    pub fn answer<T>(&self, results: impl IntoIterator<Item = T>) -> Answer
77    where
78        T: Into<tl::enums::InputBotInlineResult>,
79    {
80        Answer {
81            request: tl::functions::messages::SetInlineBotResults {
82                gallery: false,
83                private: false,
84                query_id: self.update().query_id,
85                results: results.into_iter().map(Into::into).collect(),
86                cache_time: 0,
87                next_offset: None,
88                switch_pm: None,
89                switch_webview: None,
90            },
91            client: self.client.clone(),
92        }
93    }
94
95    /// Type of the peer from which the inline query was sent.
96    pub fn peer_type(&self) -> Option<tl::enums::InlineQueryPeerType> {
97        self.update().peer_type.clone()
98    }
99
100    /// Query ID
101    pub fn query_id(&self) -> i64 {
102        self.update().query_id
103    }
104}
105
106impl Answer {
107    /// If set, the results will show as a gallery (grid).
108    pub fn gallery(mut self) -> Self {
109        self.request.gallery = true;
110        self
111    }
112
113    /// If set, the results will be cached by the user's client (private) rather than by Telgram
114    /// (not private).
115    pub fn private(mut self) -> Self {
116        self.request.private = true;
117        self
118    }
119
120    /// For how long this result should be cached on the user's client. Defaults to 0 for no
121    /// cache.
122    pub fn cache_time(mut self, cache_time: i32) -> Self {
123        self.request.cache_time = cache_time;
124        self
125    }
126
127    /// The offset the client will send when the user scrolls the results and it repeats the
128    /// request.
129    pub fn next_offset(mut self, next_offset: impl Into<String>) -> Self {
130        self.request.next_offset = Some(next_offset.into());
131        self
132    }
133
134    /// If set, this text will be shown in the results to allow the user to switch to private
135    /// messages.
136    pub fn switch_pm(mut self, text: impl Into<String>, start_param: impl Into<String>) -> Self {
137        self.request.switch_pm = Some(tl::enums::InlineBotSwitchPm::Pm(
138            tl::types::InlineBotSwitchPm {
139                text: text.into(),
140                start_param: start_param.into(),
141            },
142        ));
143        self
144    }
145
146    /// Answers the inline query with the given results.
147    pub async fn send(self) -> Result<(), InvocationError> {
148        self.client.invoke(&self.request).await?;
149        Ok(())
150    }
151}
152
153/// One of the possible answers to an [`InlineQuery`].
154///
155/// Article answers let you show an option that when clicked will cause the user to send a text message.
156#[derive(Debug)]
157pub struct Article {
158    pub raw: tl::types::InputBotInlineResult,
159}
160
161impl Article {
162    /// Creates an inline result with the given title.
163    ///
164    /// If selected by the user that made the inline query, the input message will be sent by them.
165    pub fn new<S: Into<String>, M: Into<InputMessage>>(title: S, input_message: M) -> Self {
166        let message = input_message.into();
167        Self {
168            raw: tl::types::InputBotInlineResult {
169                id: utils::generate_random_id().to_string(),
170                r#type: "article".into(),
171                title: Some(title.into()),
172                description: None,
173                url: None,
174                thumb: None,
175                content: None,
176                // TODO: also allow other types of messages than text
177                send_message: tl::enums::InputBotInlineMessage::Text(
178                    tl::types::InputBotInlineMessageText {
179                        no_webpage: !message.link_preview,
180                        invert_media: message.invert_media,
181                        message: message.text,
182                        entities: Some(message.entities),
183                        reply_markup: message.reply_markup,
184                    },
185                ),
186            },
187        }
188    }
189
190    /// Unique identifier of the result.
191    ///
192    /// By default, a random string will be used.
193    pub fn id(mut self, result_id: impl Into<String>) -> Self {
194        self.raw.id = result_id.into();
195        self
196    }
197
198    /// Short description of the result.
199    pub fn description(mut self, description: impl Into<String>) -> Self {
200        self.raw.description = Some(description.into());
201        self
202    }
203
204    /// URL of the result.
205    pub fn url(mut self, url: impl Into<String>) -> Self {
206        self.raw.url = Some(url.into());
207        self
208    }
209
210    /// URL of the thumbnail for the result.
211    ///
212    /// Must point to a suitable JPEG image.
213    pub fn thumb_url(mut self, thumb_url: impl Into<String>) -> Self {
214        self.raw.thumb = Some(tl::enums::InputWebDocument::Document(
215            tl::types::InputWebDocument {
216                url: thumb_url.into(),
217                size: 0,
218                mime_type: "image/jpeg".into(),
219                attributes: vec![],
220            },
221        ));
222        self
223    }
224}
225
226impl From<Article> for tl::enums::InputBotInlineResult {
227    fn from(article: Article) -> Self {
228        tl::enums::InputBotInlineResult::Result(article.raw)
229    }
230}
231
232impl fmt::Debug for InlineQuery {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("InlineQuery")
235            .field("text", &self.text())
236            .field("peer_type", &self.peer_type())
237            .field("sender", &self.sender())
238            .field("query_id", &self.query_id())
239            .finish()
240    }
241}