Skip to main content

layer_client/
keyboard.rs

1//! Inline keyboard builder — create reply markups without raw TL verbosity.
2//!
3//! # Example
4//! ```rust,no_run
5//! use layer_client::keyboard::{InlineKeyboard, Button};
6//!
7//! let kb = InlineKeyboard::new()
8//!     .row([Button::callback("✅ Yes", b"yes"),
9//!           Button::callback("❌ No",  b"no")])
10//!     .row([Button::url("📖 Docs", "https://docs.rs/layer-client")]);
11//!
12//! // Pass to InputMessage:
13//! // let msg = InputMessage::text("Choose:").keyboard(kb);
14//! ```
15
16use layer_tl_types as tl;
17
18// ─── Button ───────────────────────────────────────────────────────────────────
19
20/// A single inline keyboard button.
21#[derive(Clone)]
22pub struct Button {
23    inner: tl::enums::KeyboardButton,
24}
25
26impl Button {
27    /// A button that sends a callback data payload when pressed.
28    pub fn callback(text: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
29        Self {
30            inner: tl::enums::KeyboardButton::Callback(
31                tl::types::KeyboardButtonCallback {
32                    requires_password: false,
33                    text: text.into(),
34                    data: data.into(),
35                    style: None,
36                },
37            ),
38        }
39    }
40
41    /// A button that opens a URL in the browser.
42    pub fn url(text: impl Into<String>, url: impl Into<String>) -> Self {
43        Self {
44            inner: tl::enums::KeyboardButton::Url(
45                tl::types::KeyboardButtonUrl {
46                    text:  text.into(),
47                    url:   url.into(),
48                    style: None,
49                },
50            ),
51        }
52    }
53
54    /// A button that opens a user-profile or bot link in Telegram.
55    pub fn url_auth(text: impl Into<String>, url: impl Into<String>, fwd_text: Option<String>, bot: tl::enums::InputUser) -> Self {
56        Self {
57            inner: tl::enums::KeyboardButton::InputKeyboardButtonUrlAuth(
58                tl::types::InputKeyboardButtonUrlAuth {
59                    request_write_access: false,
60                    text:     text.into(),
61                    fwd_text,
62                    url:      url.into(),
63                    bot,
64                    style:    None,
65                },
66            ),
67        }
68    }
69
70    /// A button that switches to inline mode in the current chat.
71    pub fn switch_inline(text: impl Into<String>, query: impl Into<String>) -> Self {
72        Self {
73            inner: tl::enums::KeyboardButton::SwitchInline(
74                tl::types::KeyboardButtonSwitchInline {
75                    same_peer:   true,
76                    peer_types:  None,
77                    text:        text.into(),
78                    query:       query.into(),
79                    style:       None,
80                },
81            ),
82        }
83    }
84
85    /// A plain text button (for reply keyboards, not inline).
86    pub fn text(label: impl Into<String>) -> Self {
87        Self {
88            inner: tl::enums::KeyboardButton::KeyboardButton(
89                tl::types::KeyboardButton { text: label.into(), style: None },
90            ),
91        }
92    }
93
94    /// A button that switches to inline mode in a different (user-chosen) chat.
95    pub fn switch_elsewhere(text: impl Into<String>, query: impl Into<String>) -> Self {
96        Self {
97            inner: tl::enums::KeyboardButton::SwitchInline(
98                tl::types::KeyboardButtonSwitchInline {
99                    same_peer:  false,
100                    peer_types: None,
101                    text:       text.into(),
102                    query:      query.into(),
103                    style:      None,
104                },
105            ),
106        }
107    }
108
109    /// A button that opens a mini-app WebView.
110    pub fn webview(text: impl Into<String>, url: impl Into<String>) -> Self {
111        Self {
112            inner: tl::enums::KeyboardButton::WebView(
113                tl::types::KeyboardButtonWebView {
114                    text:  text.into(),
115                    url:   url.into(),
116                    style: None,
117                },
118            ),
119        }
120    }
121
122    /// A button that opens a simple WebView (no JS bridge).
123    pub fn simple_webview(text: impl Into<String>, url: impl Into<String>) -> Self {
124        Self {
125            inner: tl::enums::KeyboardButton::SimpleWebView(
126                tl::types::KeyboardButtonSimpleWebView {
127                    text:  text.into(),
128                    url:   url.into(),
129                    style: None,
130                },
131            ),
132        }
133    }
134
135    /// A button that requests the user's phone number (reply keyboards only).
136    pub fn request_phone(text: impl Into<String>) -> Self {
137        Self {
138            inner: tl::enums::KeyboardButton::RequestPhone(
139                tl::types::KeyboardButtonRequestPhone {
140                    text:  text.into(),
141                    style: None,
142                },
143            ),
144        }
145    }
146
147    /// A button that requests the user's location (reply keyboards only).
148    pub fn request_geo(text: impl Into<String>) -> Self {
149        Self {
150            inner: tl::enums::KeyboardButton::RequestGeoLocation(
151                tl::types::KeyboardButtonRequestGeoLocation {
152                    text:  text.into(),
153                    style: None,
154                },
155            ),
156        }
157    }
158
159    /// A button that requests the user to create/share a poll.
160    pub fn request_poll(text: impl Into<String>) -> Self {
161        Self {
162            inner: tl::enums::KeyboardButton::RequestPoll(
163                tl::types::KeyboardButtonRequestPoll {
164                    quiz:  None,
165                    text:  text.into(),
166                    style: None,
167                },
168            ),
169        }
170    }
171
172    /// A button that requests the user to create/share a quiz.
173    pub fn request_quiz(text: impl Into<String>) -> Self {
174        Self {
175            inner: tl::enums::KeyboardButton::RequestPoll(
176                tl::types::KeyboardButtonRequestPoll {
177                    quiz:  Some(true),
178                    text:  text.into(),
179                    style: None,
180                },
181            ),
182        }
183    }
184
185    /// A button that launches a game (bots only).
186    pub fn game(text: impl Into<String>) -> Self {
187        Self {
188            inner: tl::enums::KeyboardButton::Game(
189                tl::types::KeyboardButtonGame {
190                    text:  text.into(),
191                    style: None,
192                },
193            ),
194        }
195    }
196
197    /// A buy button for payments (bots only).
198    pub fn buy(text: impl Into<String>) -> Self {
199        Self {
200            inner: tl::enums::KeyboardButton::Buy(
201                tl::types::KeyboardButtonBuy {
202                    text:  text.into(),
203                    style: None,
204                },
205            ),
206        }
207    }
208
209    /// A copy-to-clipboard button.
210    pub fn copy_text(text: impl Into<String>, copy_text: impl Into<String>) -> Self {
211        Self {
212            inner: tl::enums::KeyboardButton::Copy(
213                tl::types::KeyboardButtonCopy {
214                    text:      text.into(),
215                    copy_text: copy_text.into(),
216                    style:     None,
217                },
218            ),
219        }
220    }
221
222    /// Consume into the raw TL type.
223    pub fn into_raw(self) -> tl::enums::KeyboardButton {
224        self.inner
225    }
226}
227
228// ─── InlineKeyboard ───────────────────────────────────────────────────────────
229
230/// Builder for an inline keyboard reply markup.
231///
232/// Each call to [`row`](InlineKeyboard::row) adds a new horizontal row of
233/// buttons. Rows are displayed top-to-bottom.
234///
235/// # Example
236/// ```rust,no_run
237/// use layer_client::keyboard::{InlineKeyboard, Button};
238///
239/// let kb = InlineKeyboard::new()
240///     .row([Button::callback("Option A", b"a"),
241///           Button::callback("Option B", b"b")])
242///     .row([Button::url("More info", "https://example.com")]);
243/// ```
244#[derive(Clone, Default)]
245pub struct InlineKeyboard {
246    rows: Vec<Vec<Button>>,
247}
248
249impl InlineKeyboard {
250    /// Create an empty keyboard. Add rows with [`row`](Self::row).
251    pub fn new() -> Self {
252        Self::default()
253    }
254
255    /// Append a row of buttons.
256    pub fn row(mut self, buttons: impl IntoIterator<Item = Button>) -> Self {
257        self.rows.push(buttons.into_iter().collect());
258        self
259    }
260
261    /// Convert to the `ReplyMarkup` TL type expected by message-sending functions.
262    pub fn into_markup(self) -> tl::enums::ReplyMarkup {
263        let rows = self.rows.into_iter().map(|row| {
264            tl::enums::KeyboardButtonRow::KeyboardButtonRow(
265                tl::types::KeyboardButtonRow {
266                    buttons: row.into_iter().map(Button::into_raw).collect(),
267                },
268            )
269        }).collect();
270
271        tl::enums::ReplyMarkup::ReplyInlineMarkup(
272            tl::types::ReplyInlineMarkup { rows }
273        )
274    }
275}
276
277impl From<InlineKeyboard> for tl::enums::ReplyMarkup {
278    fn from(kb: InlineKeyboard) -> Self {
279        kb.into_markup()
280    }
281}
282
283// ─── ReplyKeyboard ────────────────────────────────────────────────────────────
284
285/// Builder for a reply keyboard (shown below the message input box).
286#[derive(Clone, Default)]
287pub struct ReplyKeyboard {
288    rows:       Vec<Vec<Button>>,
289    resize:     bool,
290    single_use: bool,
291    selective:  bool,
292}
293
294impl ReplyKeyboard {
295    /// Create a new empty reply keyboard.
296    pub fn new() -> Self { Self::default() }
297
298    /// Append a row of text buttons.
299    pub fn row(mut self, buttons: impl IntoIterator<Item = Button>) -> Self {
300        self.rows.push(buttons.into_iter().collect());
301        self
302    }
303
304    /// Resize keyboard to fit its content (recommended).
305    pub fn resize(mut self) -> Self { self.resize = true; self }
306
307    /// Hide keyboard after a single press.
308    pub fn single_use(mut self) -> Self { self.single_use = true; self }
309
310    /// Show keyboard only to mentioned/replied users.
311    pub fn selective(mut self) -> Self { self.selective = true; self }
312
313    /// Convert to `ReplyMarkup`.
314    pub fn into_markup(self) -> tl::enums::ReplyMarkup {
315        let rows = self.rows.into_iter().map(|row| {
316            tl::enums::KeyboardButtonRow::KeyboardButtonRow(
317                tl::types::KeyboardButtonRow {
318                    buttons: row.into_iter().map(Button::into_raw).collect(),
319                },
320            )
321        }).collect();
322
323        tl::enums::ReplyMarkup::ReplyKeyboardMarkup(
324            tl::types::ReplyKeyboardMarkup {
325                resize:     self.resize,
326                single_use: self.single_use,
327                selective:  self.selective,
328                persistent: false,
329                rows,
330                placeholder: None,
331            }
332        )
333    }
334}
335
336impl From<ReplyKeyboard> for tl::enums::ReplyMarkup {
337    fn from(kb: ReplyKeyboard) -> Self {
338        kb.into_markup()
339    }
340}