tgbot/types/callback/mod.rs
1use std::{error::Error, fmt};
2
3use serde::{Deserialize, Serialize, de::DeserializeOwned};
4use serde_json::Error as JsonError;
5
6use crate::{
7 api::{Method, Payload},
8 types::{Integer, MaybeInaccessibleMessage, User},
9};
10
11#[cfg(test)]
12mod tests;
13
14/// Represents an incoming callback query from a callback button in an inline keyboard.
15///
16/// If the button that originated the query was attached to a message sent by the bot,
17/// the field message will be present.
18///
19/// If the button was attached to a message sent via the bot (in inline mode),
20/// the field `inline_message_id` will be present.
21///
22/// Exactly one of the fields data or `game_short_name` will be present.
23#[serde_with::skip_serializing_none]
24#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
25pub struct CallbackQuery {
26 /// Sender of the query.
27 pub from: User,
28 /// Unique identifier of the query.
29 pub id: String,
30 /// Global identifier, uniquely corresponding
31 /// to the chat to which the message with the
32 /// callback button was sent.
33 ///
34 /// Useful for high scores in games.
35 pub chat_instance: Option<String>,
36 /// Data associated with the callback button.
37 ///
38 /// Be aware that a bad client can send arbitrary data in this field.
39 pub data: Option<String>,
40 /// Short name of a Game to be returned,
41 /// serves as the unique identifier for the game.
42 pub game_short_name: Option<String>,
43 /// Identifier of the message sent via the bot
44 /// in inline mode, that originated the query.
45 pub inline_message_id: Option<String>,
46 /// Message with the callback button that originated the query.
47 ///
48 /// Note that message content and message date
49 /// will not be available if the message is too old.
50 pub message: Option<MaybeInaccessibleMessage>,
51}
52
53impl CallbackQuery {
54 /// Creates a new `CallbackQuery`.
55 ///
56 /// # Arguments
57 ///
58 /// * `id` - Unique identifier of the query.
59 /// * `from` - Sender of the query.
60 pub fn new<T>(id: T, from: User) -> Self
61 where
62 T: Into<String>,
63 {
64 Self {
65 from,
66 id: id.into(),
67 chat_instance: None,
68 data: None,
69 game_short_name: None,
70 inline_message_id: None,
71 message: None,
72 }
73 }
74
75 /// Parses callback data using [`serde_json`].
76 pub fn parse_data<T: DeserializeOwned>(&self) -> Result<Option<T>, CallbackQueryError> {
77 if let Some(ref data) = self.data {
78 serde_json::from_str(data)
79 .map(Some)
80 .map_err(CallbackQueryError::ParseJsonData)
81 } else {
82 Ok(None)
83 }
84 }
85
86 /// Sets a new chat instance.
87 ///
88 /// # Arguments
89 ///
90 /// * `value` - Global identifier, uniquely corresponding to the chat.
91 pub fn with_chat_instance<T>(mut self, value: T) -> Self
92 where
93 T: Into<String>,
94 {
95 self.chat_instance = Some(value.into());
96 self
97 }
98
99 /// Sets a new data for callback button.
100 ///
101 /// # Arguments
102 ///
103 /// * `value` - Data associated with the callback button.
104 pub fn with_data<T>(mut self, value: T) -> Self
105 where
106 T: Into<String>,
107 {
108 self.data = Some(value.into());
109 self
110 }
111
112 /// Sets a new short name for a game.
113 ///
114 /// # Arguments
115 ///
116 /// * `value` - Short name of the game.
117 pub fn with_game_short_name<T>(mut self, value: T) -> Self
118 where
119 T: Into<String>,
120 {
121 self.game_short_name = Some(value.into());
122 self
123 }
124
125 /// Sets a new inline message ID.
126 ///
127 /// # Arguments
128 ///
129 /// * `value` - Identifier of the message sent via the bot in inline mode.
130 pub fn with_inline_message_id<T>(mut self, value: T) -> Self
131 where
132 T: Into<String>,
133 {
134 self.inline_message_id = Some(value.into());
135 self
136 }
137
138 /// Sets a new message.
139 ///
140 /// # Arguments
141 ///
142 /// * `value` - Message with the callback button that originated the query.
143 pub fn with_message(mut self, value: MaybeInaccessibleMessage) -> Self {
144 self.message = Some(value);
145 self
146 }
147}
148
149/// Represents an error that can occur while parsing data from a callback query.
150#[derive(Debug)]
151pub enum CallbackQueryError {
152 /// Failed to parse JSON data
153 ParseJsonData(JsonError),
154}
155
156impl Error for CallbackQueryError {
157 fn source(&self) -> Option<&(dyn Error + 'static)> {
158 match self {
159 Self::ParseJsonData(err) => Some(err),
160 }
161 }
162}
163
164impl fmt::Display for CallbackQueryError {
165 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
166 match self {
167 Self::ParseJsonData(err) => write!(out, "failed to parse callback query data: {}", err),
168 }
169 }
170}
171
172/// Sends an answer to a callback query sent from an inline keyboard.
173///
174/// The answer will be displayed to the user as a notification at the top of the chat screen or as an alert.
175///
176/// Alternatively, the user can be redirected to the specified Game URL.
177///
178/// For this option to work, you must first create a game for your bot via Bot Father and accept the terms.
179///
180/// Otherwise, you may use links like `t.me/your_bot?start=XXX` that open your bot with a parameter.
181#[serde_with::skip_serializing_none]
182#[derive(Clone, Debug, Serialize)]
183pub struct AnswerCallbackQuery {
184 callback_query_id: String,
185 cache_time: Option<Integer>,
186 show_alert: Option<bool>,
187 text: Option<String>,
188 url: Option<String>,
189}
190
191impl AnswerCallbackQuery {
192 /// Creates a new `AnswerCallbackQuery`.
193 ///
194 /// # Arguments
195 ///
196 /// * `callback_query_id` - Unique identifier of the query to be answered.
197 pub fn new<T>(callback_query_id: T) -> Self
198 where
199 T: Into<String>,
200 {
201 Self {
202 callback_query_id: callback_query_id.into(),
203 cache_time: None,
204 show_alert: None,
205 text: None,
206 url: None,
207 }
208 }
209
210 /// Sets a new cache time.
211 ///
212 /// # Arguments
213 ///
214 /// * `value` - The maximum amount of time in seconds that the result
215 /// of the callback query may be cached client-side;
216 /// telegram apps will support caching starting in version 3.14;
217 /// default - 0.
218 pub fn with_cache_time(mut self, value: Integer) -> Self {
219 self.cache_time = Some(value);
220 self
221 }
222
223 /// Sets a new value for the `short_alert` flag.
224 ///
225 /// # Arguments
226 ///
227 /// * `value` - An alert will be shown by the client instead
228 /// of a notification at the top of the chat screen;
229 /// default - `false`.
230 pub fn with_show_alert(mut self, value: bool) -> Self {
231 self.show_alert = Some(value);
232 self
233 }
234
235 /// Sets a new text.
236 ///
237 /// # Arguments
238 ///
239 /// * `value` - Text of the notification;
240 /// if not specified, nothing will be shown to the user;
241 /// 0-200 characters.
242 pub fn with_text<T>(mut self, value: T) -> Self
243 where
244 T: Into<String>,
245 {
246 self.text = Some(value.into());
247 self
248 }
249
250 /// Sets a new URL.
251 ///
252 /// # Arguments
253 ///
254 /// * `value` - URL that will be opened by the user's client.
255 ///
256 /// If you have created a game and accepted the conditions via Bot Father,
257 /// specify the URL that opens your game – note that this will only work
258 /// if the query comes from a callback game button.
259 ///
260 /// Otherwise, you may use links like `t.me/your_bot?start=XXX`
261 /// that open your bot with a parameter.
262 pub fn with_url<T>(mut self, value: T) -> Self
263 where
264 T: Into<String>,
265 {
266 self.url = Some(value.into());
267 self
268 }
269}
270
271impl Method for AnswerCallbackQuery {
272 type Response = bool;
273
274 fn into_payload(self) -> Payload {
275 Payload::json("answerCallbackQuery", self)
276 }
277}