tgbot/types/definitions/reply/markup/inline_keyboard.rs
1use std::{error::Error, fmt};
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use serde_json::Error as JsonError;
5
6use crate::types::{True, WebAppInfo};
7
8/// Represents an inline keyboard that appears right next to the message it belongs to.
9#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
10pub struct InlineKeyboardMarkup {
11 inline_keyboard: Vec<Vec<InlineKeyboardButton>>,
12}
13
14impl InlineKeyboardMarkup {
15 /// Adds a row to the markup.
16 ///
17 /// # Arguments
18 ///
19 /// * `value` - The row to add.
20 pub fn add_row<T>(mut self, value: T) -> Self
21 where
22 T: IntoIterator<Item = InlineKeyboardButton>,
23 {
24 self.inline_keyboard.push(value.into_iter().collect());
25 self
26 }
27
28 pub(crate) fn serialize(&self) -> Result<String, InlineKeyboardError> {
29 serde_json::to_string(self).map_err(InlineKeyboardError::SerializeMarkup)
30 }
31}
32
33impl<A, B> From<A> for InlineKeyboardMarkup
34where
35 A: IntoIterator<Item = B>,
36 B: IntoIterator<Item = InlineKeyboardButton>,
37{
38 fn from(value: A) -> InlineKeyboardMarkup {
39 Self {
40 inline_keyboard: value.into_iter().map(|x| x.into_iter().collect()).collect(),
41 }
42 }
43}
44
45impl From<InlineKeyboardMarkup> for Vec<Vec<InlineKeyboardButton>> {
46 fn from(value: InlineKeyboardMarkup) -> Self {
47 value.inline_keyboard
48 }
49}
50
51/// Represents a button of an inline keyboard.
52#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
53pub struct InlineKeyboardButton {
54 text: String,
55 #[serde(flatten)]
56 button_type: InlineKeyboardButtonType,
57}
58
59impl InlineKeyboardButton {
60 fn new<T>(text: T, button_type: InlineKeyboardButtonType) -> Self
61 where
62 T: Into<String>,
63 {
64 Self {
65 text: text.into(),
66 button_type,
67 }
68 }
69
70 /// Creates a new `InlineKeyboardButton`.
71 ///
72 /// # Arguments
73 ///
74 /// * `text` - Text of the button.
75 /// * `data` - Data to be sent in a callback query to the bot when button is pressed; 1-64 bytes.
76 pub fn for_callback_data<A, B>(text: A, data: B) -> Self
77 where
78 A: Into<String>,
79 B: Into<String>,
80 {
81 Self::new(text, InlineKeyboardButtonType::CallbackData(data.into()))
82 }
83
84 /// Creates a new `InlineKeyboardButton`.
85 ///
86 /// # Arguments
87 ///
88 /// * `text` - Text of the button.
89 /// * `data` - Data to be sent in a callback query.
90 ///
91 /// Same as [`Self::for_callback_data`], but takes a serializable type.
92 ///
93 /// Data will be serialized using [`serde_json`].
94 pub fn for_callback_data_struct<A, B>(text: A, data: &B) -> Result<Self, InlineKeyboardError>
95 where
96 A: Into<String>,
97 B: Serialize,
98 {
99 let data = serde_json::to_string(data).map_err(InlineKeyboardError::SerializeCallbackData)?;
100 Ok(Self::new(text, InlineKeyboardButtonType::CallbackData(data)))
101 }
102
103 /// Creates a new `InlineKeyboardButton`.
104 ///
105 /// # Arguments
106 ///
107 /// * `text` - Text of the button.
108 ///
109 /// A game will be launched when the user presses the button.
110 ///
111 /// # Notes
112 ///
113 /// This type of button must always be the first button in the first row.
114 pub fn for_callback_game<T>(text: T) -> Self
115 where
116 T: Into<String>,
117 {
118 Self::new(text, InlineKeyboardButtonType::CallbackGame)
119 }
120
121 /// Creates a new `InlineKeyboardButton`.
122 ///
123 /// # Arguments
124 ///
125 /// * `text` - Text of the button.
126 /// * `value` - The text to be copied to the clipboard;
127 /// 1-256 characters.
128 pub fn for_copy_text<A, B>(text: A, value: B) -> Self
129 where
130 A: Into<String>,
131 B: Into<String>,
132 {
133 Self::new(text, InlineKeyboardButtonType::CopyText(value.into()))
134 }
135
136 /// Creates a new `InlineKeyboardButton`.
137 ///
138 /// # Arguments
139 ///
140 /// * `text` - Text of the button.
141 /// * `data` - An HTTPs URL used to automatically authorize the user.
142 ///
143 /// Can be used as a replacement for the [Telegram Login Widget][1].
144 ///
145 /// [1]: https://core.telegram.org/widgets/login
146 pub fn for_login_url<A, B>(text: A, data: B) -> Self
147 where
148 A: Into<String>,
149 B: Into<LoginUrl>,
150 {
151 Self::new(text, InlineKeyboardButtonType::LoginUrl(data.into()))
152 }
153
154 /// Creates a new `InlineKeyboardButton`.
155 ///
156 /// # Arguments
157 ///
158 /// * `text` - Text of the button.
159 ///
160 /// Represents a pay button.
161 ///
162 /// <https://core.telegram.org/bots/payments>
163 ///
164 /// # Notes
165 ///
166 /// This type of button must always be the first button in the
167 /// first row and can only be used in invoice messages.
168 pub fn for_pay<T>(text: T) -> Self
169 where
170 T: Into<String>,
171 {
172 Self::new(text, InlineKeyboardButtonType::Pay)
173 }
174
175 /// Creates a new `InlineKeyboardButton`.
176 ///
177 /// # Arguments
178 ///
179 /// * `text` - Text of the button.
180 /// * `data` - Text of an inline query.
181 ///
182 /// Pressing the button will prompt the user to select one of their chats,
183 /// open that chat and insert the bot‘s username and
184 /// the specified inline query in the input field.
185 ///
186 /// Can be empty, in which case just the bot’s username will be inserted.
187 ///
188 /// # Notes
189 ///
190 /// This offers an easy way for users to start using your bot
191 /// in inline mode when they are currently in a private chat with it.
192 ///
193 /// Especially useful when combined with switch_pm… actions – in this case the user
194 /// will be automatically returned to the chat they switched from,
195 /// skipping the chat selection screen.
196 pub fn for_switch_inline_query<A, B>(text: A, data: B) -> Self
197 where
198 A: Into<String>,
199 B: Into<String>,
200 {
201 Self::new(text, InlineKeyboardButtonType::SwitchInlineQuery(data.into()))
202 }
203
204 /// Creates a new `InlineKeyboardButton`.
205 ///
206 /// # Arguments
207 ///
208 /// * `text` - Text of the button.
209 /// * `data` - Inline query parameters.
210 ///
211 /// Pressing the button will prompt the user to select one of their chats of the specified type,
212 /// open that chat and insert the bot username and the specified inline query in the input field.
213 pub fn for_switch_inline_query_chosen_chat<T>(text: T, data: SwitchInlineQueryChosenChat) -> Self
214 where
215 T: Into<String>,
216 {
217 Self::new(text, InlineKeyboardButtonType::SwitchInlineQueryChosenChat(data))
218 }
219
220 /// Creates a new `InlineKeyboardButton`.
221 ///
222 /// # Arguments
223 ///
224 /// * `text` - Text of the button.
225 /// * `data` - Text of an inline query.
226 ///
227 /// Pressing the button will insert the bot‘s username and
228 /// the specified inline query in the current chat's input field.
229 ///
230 /// Can be empty, in which case only the bot’s username will be inserted
231 /// This offers a quick way for the user to open your bot in
232 /// inline mode in the same chat – good for selecting something from multiple options.
233 pub fn for_switch_inline_query_current_chat<A, B>(text: A, data: B) -> Self
234 where
235 A: Into<String>,
236 B: Into<String>,
237 {
238 Self::new(
239 text,
240 InlineKeyboardButtonType::SwitchInlineQueryCurrentChat(data.into()),
241 )
242 }
243
244 /// Creates a new `InlineKeyboardButton`.
245 ///
246 /// # Arguments
247 ///
248 /// * `text` - Text of the button
249 /// * `data` - HTTP or `tg://` URL to be opened when button is pressed.
250 pub fn for_url<A, B>(text: A, data: B) -> Self
251 where
252 A: Into<String>,
253 B: Into<String>,
254 {
255 Self::new(text, InlineKeyboardButtonType::Url(data.into()))
256 }
257
258 /// Creates a new `InlineKeyboardButton`.
259 ///
260 /// # Arguments
261 ///
262 /// * `text` - Text of the button.
263 /// * `data` - Description of the Web App that will be launched when the user presses the button.
264 pub fn for_web_app<T>(text: T, data: WebAppInfo) -> Self
265 where
266 T: Into<String>,
267 {
268 Self::new(text, InlineKeyboardButtonType::WebApp(data))
269 }
270
271 /// Returns the type of the button.
272 pub fn button_type(&self) -> &InlineKeyboardButtonType {
273 &self.button_type
274 }
275
276 /// Returns the text of the button.
277 pub fn text(&self) -> &str {
278 &self.text
279 }
280}
281
282/// Represents a type of an inline keyboard button.
283#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
284#[serde(rename_all = "snake_case")]
285pub enum InlineKeyboardButtonType {
286 /// Data to be sent in a callback query to the bot when button is pressed; 1-64 bytes.
287 CallbackData(String),
288 /// Description of the game that will be launched when the user presses the button.
289 ///
290 /// # Notes
291 ///
292 /// This type of button must always be the first button in the first row.
293 #[serde(
294 deserialize_with = "RawButtonEmpty::deserialize_value",
295 serialize_with = "RawButtonEmpty::serialize_value"
296 )]
297 CallbackGame,
298 /// Description of the button that copies the specified text to the clipboard.
299 #[serde(
300 deserialize_with = "RawButtonText::deserialize_value",
301 serialize_with = "RawButtonText::serialize_value"
302 )]
303 CopyText(String),
304 /// An HTTP URL used to automatically authorize the user.
305 ///
306 /// Can be used as a replacement for the [Telegram Login Widget][1].
307 ///
308 /// [1]: https://core.telegram.org/widgets/login
309 LoginUrl(LoginUrl),
310 /// Send a Pay button.
311 ///
312 /// # Notes
313 ///
314 /// This type of button must always be the first button in the first row.
315 #[serde(
316 deserialize_with = "RawButtonFlag::deserialize_value",
317 serialize_with = "RawButtonFlag::serialize_value"
318 )]
319 Pay,
320 /// Pressing the button will prompt the user to select one of their chats,
321 /// open that chat and insert the bot‘s username and
322 /// the specified inline query in the input field.
323 ///
324 /// Can be empty, in which case just the bot’s username will be inserted.
325 ///
326 /// # Notes
327 ///
328 /// This offers an easy way for users to start using your bot
329 /// in inline mode when they are currently in a private chat with it.
330 ///
331 /// Especially useful when combined with switch_pm… actions – in this case the user
332 /// will be automatically returned to the chat they switched from,
333 /// skipping the chat selection screen.
334 SwitchInlineQuery(String),
335 /// Pressing the button will prompt the user to select one of their chats of the specified type,
336 /// open that chat and insert the bot username and the specified inline query in the input field.
337 SwitchInlineQueryChosenChat(SwitchInlineQueryChosenChat),
338 /// Pressing the button will insert the bot‘s username and
339 /// the specified inline query in the current chat's input field.
340 ///
341 /// Can be empty, in which case only the bot’s username will be inserted.
342 /// This offers a quick way for the user to open your bot in
343 /// inline mode in the same chat – good for selecting something from multiple options.
344 SwitchInlineQueryCurrentChat(String),
345 /// HTTP or tg:// url to be opened when button is pressed.
346 Url(String),
347 /// Description of the Web App that will be launched when the user presses the button.
348 ///
349 /// The Web App will be able to send an arbitrary message on behalf
350 /// of the user using the method [`crate::types::AnswerWebAppQuery`].
351 /// Available only in private chats between a user and the bot.
352 WebApp(WebAppInfo),
353}
354
355#[derive(Debug, Deserialize, Serialize)]
356struct RawButtonEmpty {}
357
358impl RawButtonEmpty {
359 fn deserialize_value<'de, D>(deserializer: D) -> Result<(), D::Error>
360 where
361 D: Deserializer<'de>,
362 {
363 RawButtonEmpty::deserialize(deserializer).map(|_| ())
364 }
365
366 fn serialize_value<S>(serializer: S) -> Result<S::Ok, S::Error>
367 where
368 S: Serializer,
369 {
370 RawButtonEmpty {}.serialize(serializer)
371 }
372}
373
374#[derive(Deserialize, Serialize)]
375struct RawButtonFlag;
376
377impl RawButtonFlag {
378 fn deserialize_value<'de, D>(deserializer: D) -> Result<(), D::Error>
379 where
380 D: Deserializer<'de>,
381 {
382 True::deserialize(deserializer).map(|_| ())
383 }
384
385 fn serialize_value<S>(serializer: S) -> Result<S::Ok, S::Error>
386 where
387 S: Serializer,
388 {
389 True.serialize(serializer)
390 }
391}
392
393#[derive(Deserialize, Serialize)]
394struct RawButtonText {
395 text: String,
396}
397
398impl RawButtonText {
399 fn deserialize_value<'de, D>(deserializer: D) -> Result<String, D::Error>
400 where
401 D: Deserializer<'de>,
402 {
403 RawButtonText::deserialize(deserializer).map(|RawButtonText { text }| text)
404 }
405
406 fn serialize_value<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
407 where
408 S: Serializer,
409 {
410 RawButtonText {
411 text: String::from(value),
412 }
413 .serialize(serializer)
414 }
415}
416
417/// Represents an error occurred with an inline keyboard.
418#[derive(Debug)]
419pub enum InlineKeyboardError {
420 /// Can not serialize callback data.
421 SerializeCallbackData(JsonError),
422 /// Can not serialize markup.
423 SerializeMarkup(JsonError),
424}
425
426impl Error for InlineKeyboardError {
427 fn source(&self) -> Option<&(dyn Error + 'static)> {
428 use self::InlineKeyboardError::*;
429 match self {
430 SerializeCallbackData(err) => Some(err),
431 SerializeMarkup(err) => Some(err),
432 }
433 }
434}
435
436impl fmt::Display for InlineKeyboardError {
437 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
438 use self::InlineKeyboardError::*;
439 match self {
440 SerializeCallbackData(err) => write!(out, "failed to serialize callback data: {err}"),
441 SerializeMarkup(err) => write!(out, "failed to serialize markup: {err}"),
442 }
443 }
444}
445
446/// Represents a parameter of the inline keyboard button used to automatically authorize a user.
447///
448/// Serves as a great replacement for the
449/// Telegram Login Widget when the user is coming from Telegram.
450#[serde_with::skip_serializing_none]
451#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
452pub struct LoginUrl {
453 url: String,
454 bot_username: Option<String>,
455 forward_text: Option<String>,
456 request_write_access: Option<bool>,
457}
458
459impl LoginUrl {
460 /// Creates a new `LoginUrl`.
461 ///
462 /// # Arguments
463 ///
464 /// * `value` - An HTTP URL to be opened with user authorization data
465 /// added to the query string when the button is pressed.
466 ///
467 /// If the user refuses to provide authorization data,
468 /// the original URL without information about the user will be opened.
469 ///
470 /// The data added is the same as described in [Receiving authorization data][1].
471 ///
472 /// # Notes
473 ///
474 /// You **must** always check the hash of the received data to verify the authentication
475 /// and the integrity of the data as described in [Checking authorization][2].
476 ///
477 /// [1]: https://core.telegram.org/widgets/login#receiving-authorization-data
478 /// [2]: https://core.telegram.org/widgets/login#checking-authorization
479 pub fn new<T>(url: T) -> Self
480 where
481 T: Into<String>,
482 {
483 Self {
484 url: url.into(),
485 bot_username: None,
486 forward_text: None,
487 request_write_access: None,
488 }
489 }
490
491 /// Sets a new username of a bot.
492 ///
493 /// # Arguments
494 ///
495 /// * `value` - The username of the bot, which will be used for user authorization.
496 ///
497 /// See [Setting up a bot][1] for more details.
498 /// If not specified, the current bot username will be assumed.
499 /// The url domain must be the same as the domain linked with the bot.
500 /// See [Linking your domain to the bot][2] for more details.
501 ///
502 /// [1]: https://core.telegram.org/widgets/login#setting-up-a-bot
503 /// [2]: https://core.telegram.org/widgets/login#linking-your-domain-to-the-bot
504 pub fn with_bot_username<T>(mut self, value: T) -> Self
505 where
506 T: Into<String>,
507 {
508 self.bot_username = Some(value.into());
509 self
510 }
511
512 /// Sets a new forward text.
513 ///
514 /// # Arguments
515 ///
516 /// * `value` - New text of the button in forwarded messages.
517 pub fn with_forward_text<T>(mut self, value: T) -> Self
518 where
519 T: Into<String>,
520 {
521 self.forward_text = Some(value.into());
522 self
523 }
524
525 /// Sets a new value for the `request_write_access` flag.
526 ///
527 /// # Arguments
528 ///
529 /// * `value` - Indicates whether to request the permission
530 /// for your bot to send messages to the user.
531 pub fn with_request_write_access(mut self, value: bool) -> Self {
532 self.request_write_access = Some(value);
533 self
534 }
535}
536
537impl<T> From<T> for LoginUrl
538where
539 T: Into<String>,
540{
541 fn from(url: T) -> Self {
542 Self::new(url)
543 }
544}
545
546/// Represents an inline button that switches the current user
547/// to inline mode in a chosen chat, with an optional default inline query.
548#[serde_with::skip_serializing_none]
549#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
550pub struct SwitchInlineQueryChosenChat {
551 query: String,
552 allow_bot_chats: Option<bool>,
553 allow_channel_chats: Option<bool>,
554 allow_group_chats: Option<bool>,
555 allow_user_chats: Option<bool>,
556}
557
558impl SwitchInlineQueryChosenChat {
559 /// Creates a new `SwitchInlineQueryChosenChat`.
560 ///
561 /// # Arguments
562 ///
563 /// * `query` - The default inline query to be inserted in the input field.
564 /// If left empty, only the bot username will be inserted.
565 pub fn new<T>(query: T) -> Self
566 where
567 T: Into<String>,
568 {
569 Self {
570 query: query.into(),
571 allow_bot_chats: None,
572 allow_channel_chats: None,
573 allow_group_chats: None,
574 allow_user_chats: None,
575 }
576 }
577
578 /// Sets a new value for the `allow_bot_chats` flag.
579 ///
580 /// # Arguments
581 ///
582 /// * `value` - Indicates whether private chats with users can be chosen.
583 pub fn with_allow_bot_chats(mut self, value: bool) -> Self {
584 self.allow_bot_chats = Some(value);
585 self
586 }
587
588 /// Sets a new value for the `allow_channel_chats` flag.
589 ///
590 /// # Arguments
591 ///
592 /// * `value` - Indicates whether private chats with bots can be chosen.
593 pub fn with_allow_channel_chats(mut self, value: bool) -> Self {
594 self.allow_channel_chats = Some(value);
595 self
596 }
597
598 /// Sets a new value for the `allow_group_chats` flag.
599 ///
600 /// # Arguments
601 ///
602 /// * `value` - Indicates whether group and supergroup chats can be chosen.
603 pub fn with_allow_group_chats(mut self, value: bool) -> Self {
604 self.allow_group_chats = Some(value);
605 self
606 }
607
608 /// Sets a new value for the `allow_user_chats` flag.
609 ///
610 /// # Arguments
611 ///
612 /// * `value` - Indicates whether channel chats can be chosen.
613 pub fn with_allow_user_chats(mut self, value: bool) -> Self {
614 self.allow_user_chats = Some(value);
615 self
616 }
617}