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