Skip to main content

rustigram_types/
keyboard.rs

1use serde::{Deserialize, Serialize};
2
3use crate::games::CallbackGame;
4use crate::user::ChatAdministratorRights;
5
6use crate::message::WebAppInfo;
7
8/// An inline keyboard attached to a message.
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub struct InlineKeyboardMarkup {
11    /// Array of button rows, each represented by an array of
12    /// [`InlineKeyboardButton`] objects.
13    pub inline_keyboard: Vec<Vec<InlineKeyboardButton>>,
14}
15
16impl InlineKeyboardMarkup {
17    /// Creates an empty inline keyboard.
18    #[must_use]
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Appends a row of buttons.
24    #[must_use]
25    pub fn row(mut self, row: Vec<InlineKeyboardButton>) -> Self {
26        self.inline_keyboard.push(row);
27        self
28    }
29}
30
31/// One button in an inline keyboard.
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct InlineKeyboardButton {
34    /// Label text on the button.
35    pub text: String,
36    /// Custom emoji identifier shown before the button text.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub icon_custom_emoji_id: Option<String>,
39    /// Visual style of the button (`"danger"`, `"success"`, or `"primary"`).
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub style: Option<ButtonStyle>,
42    /// URL to open when the button is pressed.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub url: Option<String>,
45    /// Data to be sent in a callback query (1–64 bytes).
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub callback_data: Option<String>,
48    /// Web App to launch when the button is pressed.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub web_app: Option<WebAppInfo>,
51    /// Defines an authentication button.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub login_url: Option<LoginUrl>,
54    /// Pressing the button prompts the user to select a chat and opens an inline query.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub switch_inline_query: Option<String>,
57    /// Pressing the button opens an inline query in the current chat.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub switch_inline_query_current_chat: Option<String>,
60    /// Prompts the user to select a specific type of chat for an inline query.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub switch_inline_query_chosen_chat: Option<SwitchInlineQueryChosenChat>,
63    /// Describes a button that copies specified text to the clipboard.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub copy_text: Option<CopyTextButton>,
66    /// Description of the game that will be launched when the user presses the button.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub callback_game: Option<CallbackGame>,
69    /// Specify `true` to send a Pay button (invoices only).
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub pay: Option<bool>,
72}
73
74impl InlineKeyboardButton {
75    /// Creates a callback button.
76    #[must_use]
77    pub fn callback(text: impl Into<String>, data: impl Into<String>) -> Self {
78        Self {
79            text: text.into(),
80            callback_data: Some(data.into()),
81            icon_custom_emoji_id: None,
82            style: None,
83            url: None,
84            web_app: None,
85            login_url: None,
86            switch_inline_query: None,
87            switch_inline_query_current_chat: None,
88            switch_inline_query_chosen_chat: None,
89            copy_text: None,
90            callback_game: None,
91            pay: None,
92        }
93    }
94
95    /// Creates a URL button.
96    #[must_use]
97    pub fn url(text: impl Into<String>, url: impl Into<String>) -> Self {
98        Self {
99            text: text.into(),
100            url: Some(url.into()),
101            icon_custom_emoji_id: None,
102            style: None,
103            callback_data: None,
104            web_app: None,
105            login_url: None,
106            switch_inline_query: None,
107            switch_inline_query_current_chat: None,
108            switch_inline_query_chosen_chat: None,
109            copy_text: None,
110            callback_game: None,
111            pay: None,
112        }
113    }
114
115    /// Creates a Web App button.
116    #[must_use]
117    pub fn web_app(text: impl Into<String>, url: impl Into<String>) -> Self {
118        Self {
119            text: text.into(),
120            web_app: Some(WebAppInfo { url: url.into() }),
121            icon_custom_emoji_id: None,
122            style: None,
123            url: None,
124            callback_data: None,
125            login_url: None,
126            switch_inline_query: None,
127            switch_inline_query_current_chat: None,
128            switch_inline_query_chosen_chat: None,
129            copy_text: None,
130            callback_game: None,
131            pay: None,
132        }
133    }
134}
135
136/// The visual style applied to an inline or reply keyboard button.
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum ButtonStyle {
140    /// Red destructive button style.
141    Danger,
142    /// Green positive button style.
143    Success,
144    /// Default blue button style.
145    Primary,
146}
147
148/// Parameters for a Login URL button.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct LoginUrl {
151    /// HTTPS URL to forward the user to.
152    pub url: String,
153    /// New text of the button in forwarded messages.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub forward_text: Option<String>,
156    /// Username of the bot to use for user authorization.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub bot_username: Option<String>,
159    /// `true` to request permission for the bot to send messages to the user.
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub request_write_access: Option<bool>,
162}
163
164/// Parameters for inline query routing to a specific type of chat.
165#[derive(Debug, Clone, Default, Serialize, Deserialize)]
166pub struct SwitchInlineQueryChosenChat {
167    /// Default inline query to insert in the input field.
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub query: Option<String>,
170    /// `true` if private chats with users can be chosen.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub allow_user_chats: Option<bool>,
173    /// `true` if private chats with bots can be chosen.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub allow_bot_chats: Option<bool>,
176    /// `true` if group and supergroup chats can be chosen.
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub allow_group_chats: Option<bool>,
179    /// `true` if channel chats can be chosen.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub allow_channel_chats: Option<bool>,
182}
183
184/// Represents a button that copies text to clipboard.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct CopyTextButton {
187    /// Text to copy (1–256 characters).
188    pub text: String,
189}
190
191/// Custom keyboard shown to the message recipient.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct ReplyKeyboardMarkup {
194    /// Array of button rows.
195    pub keyboard: Vec<Vec<KeyboardButton>>,
196    /// Whether the keyboard is persistent.
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub is_persistent: Option<bool>,
199    /// Requests clients to resize the keyboard vertically.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub resize_keyboard: Option<bool>,
202    /// Requests clients to hide the keyboard after a button is used.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub one_time_keyboard: Option<bool>,
205    /// Placeholder text shown in the input field when the keyboard is active.
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub input_field_placeholder: Option<String>,
208    /// Show keyboard to specific users only.
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub selective: Option<bool>,
211}
212
213/// One button in a reply keyboard.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct KeyboardButton {
216    /// Label text on the button.
217    pub text: String,
218    /// Custom emoji identifier shown before the button text.
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub icon_custom_emoji_id: Option<String>,
221    /// Visual style of the button.
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub style: Option<ButtonStyle>,
224    /// Request to select and share one or more users.
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub request_users: Option<KeyboardButtonRequestUsers>,
227    /// Request to select and share a chat.
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub request_chat: Option<KeyboardButtonRequestChat>,
230    /// Request a managed bot from the user.
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub request_managed_bot: Option<KeyboardButtonRequestManagedBot>,
233    /// Requests the user's phone number.
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub request_contact: Option<bool>,
236    /// Requests the user's current location.
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub request_location: Option<bool>,
239    /// Requests the user to create a poll.
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub request_poll: Option<KeyboardButtonPollType>,
242    /// Web App to launch when the button is pressed.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub web_app: Option<WebAppInfo>,
245}
246
247impl KeyboardButton {
248    /// Creates a simple text button.
249    #[must_use]
250    pub fn text(label: impl Into<String>) -> Self {
251        Self {
252            text: label.into(),
253            icon_custom_emoji_id: None,
254            style: None,
255            request_users: None,
256            request_chat: None,
257            request_managed_bot: None,
258            request_contact: None,
259            request_location: None,
260            request_poll: None,
261            web_app: None,
262        }
263    }
264
265    /// Creates a button that requests the user's phone number.
266    #[must_use]
267    pub fn request_contact(label: impl Into<String>) -> Self {
268        Self {
269            request_contact: Some(true),
270            ..Self::text(label)
271        }
272    }
273
274    /// Creates a button that requests the user's location.
275    #[must_use]
276    pub fn request_location(label: impl Into<String>) -> Self {
277        Self {
278            request_location: Some(true),
279            ..Self::text(label)
280        }
281    }
282}
283
284/// Defines criteria for selecting users via a keyboard button.
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct KeyboardButtonRequestUsers {
287    /// Signed 32-bit identifier of the request.
288    pub request_id: i32,
289    /// `true` to request only bots.
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub user_is_bot: Option<bool>,
292    /// `true` to request only premium users.
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub user_is_premium: Option<bool>,
295    /// Maximum number of users to be selected (1–10, default 1).
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub max_quantity: Option<u8>,
298    /// `true` to request the user's name.
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub request_name: Option<bool>,
301    /// `true` to request the user's username.
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub request_username: Option<bool>,
304    /// `true` to request the user's profile photo.
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub request_photo: Option<bool>,
307}
308
309/// Defines criteria for selecting a chat via a keyboard button.
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct KeyboardButtonRequestChat {
312    /// Signed 32-bit identifier of the request.
313    pub request_id: i32,
314    /// `true` to request a channel chat; `false` for group or supergroup.
315    pub chat_is_channel: bool,
316    /// `true` to request a forum supergroup.
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub chat_is_forum: Option<bool>,
319    /// `true` to request a chat with a username.
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub chat_has_username: Option<bool>,
322    /// `true` to request a chat owned by the user.
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub chat_is_created: Option<bool>,
325    /// Required administrator rights of the user in the chat.
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub user_administrator_rights: Option<ChatAdministratorRights>,
328    /// Required administrator rights of the bot in the chat.
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub bot_administrator_rights: Option<ChatAdministratorRights>,
331    /// `true` to request a chat where the bot is a member.
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub bot_is_member: Option<bool>,
334    /// `true` to request the chat title.
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub request_title: Option<bool>,
337    /// `true` to request the chat username.
338    #[serde(skip_serializing_if = "Option::is_none")]
339    pub request_username: Option<bool>,
340    /// `true` to request the chat photo.
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub request_photo: Option<bool>,
343}
344
345/// Defines parameters for requesting the creation of a managed bot.
346///
347/// Bot API 9.6 — available for bots that have enabled managed bot creation in @BotFather.
348#[derive(Debug, Clone, Default, Serialize, Deserialize)]
349pub struct KeyboardButtonRequestManagedBot {
350    /// Signed 32-bit identifier of the request; must be unique within the message.
351    pub request_id: i32,
352    /// Suggested name for the new bot.
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub suggested_name: Option<String>,
355    /// Suggested username for the new bot.
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub suggested_username: Option<String>,
358}
359
360/// The type of poll requested via a keyboard button.
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct KeyboardButtonPollType {
363    /// `"quiz"`, `"regular"`, or absent (any type).
364    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
365    pub kind: Option<String>,
366}
367
368/// Instructs clients to remove the reply keyboard.
369#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct ReplyKeyboardRemove {
371    /// Must be `true`.
372    pub remove_keyboard: bool,
373    /// Show the remove keyboard to specific users only.
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub selective: Option<bool>,
376}
377
378/// Forces a reply from the user.
379#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct ForceReply {
381    /// Must be `true`.
382    pub force_reply: bool,
383    /// Placeholder text in the input field when the reply is active (1–64 characters).
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub input_field_placeholder: Option<String>,
386    /// Show the force reply to specific users only.
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub selective: Option<bool>,
389}
390
391/// All reply markup variants.
392#[derive(Debug, Clone, Serialize, Deserialize)]
393#[serde(untagged)]
394pub enum ReplyMarkup {
395    /// An inline keyboard attached to the message.
396    InlineKeyboard(InlineKeyboardMarkup),
397    /// A custom reply keyboard shown to the user.
398    ReplyKeyboard(ReplyKeyboardMarkup),
399    /// Removes the reply keyboard.
400    Remove(ReplyKeyboardRemove),
401    /// Forces the user to reply to the message.
402    ForceReply(ForceReply),
403}
404
405/// Menu button configuration.
406#[derive(Debug, Clone, Serialize, Deserialize)]
407#[serde(tag = "type", rename_all = "snake_case")]
408pub enum MenuButton {
409    /// Shows the list of bot commands.
410    Commands,
411    /// Launches a Web App.
412    WebApp {
413        /// Button label text.
414        text: String,
415        /// Web App to launch.
416        web_app: WebAppInfo,
417    },
418    /// No action — uses the default behavior.
419    Default,
420}
421
422/// A keyboard button prepared for use by a Mini App.
423#[derive(Debug, Clone, Default, Serialize, Deserialize)]
424#[non_exhaustive]
425pub struct PreparedKeyboardButton {
426    /// Unique identifier of the prepared button.
427    pub id: String,
428}