Skip to main content

rustigram_types/
keyboard.rs

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