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}