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