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    /// Consume into the raw TL type.
95    pub fn into_raw(self) -> tl::enums::KeyboardButton {
96        self.inner
97    }
98}
99
100// ─── InlineKeyboard ───────────────────────────────────────────────────────────
101
102/// Builder for an inline keyboard reply markup.
103///
104/// Each call to [`row`](InlineKeyboard::row) adds a new horizontal row of
105/// buttons. Rows are displayed top-to-bottom.
106///
107/// # Example
108/// ```rust,no_run
109/// use layer_client::keyboard::{InlineKeyboard, Button};
110///
111/// let kb = InlineKeyboard::new()
112///     .row([Button::callback("Option A", b"a"),
113///           Button::callback("Option B", b"b")])
114///     .row([Button::url("More info", "https://example.com")]);
115/// ```
116#[derive(Clone, Default)]
117pub struct InlineKeyboard {
118    rows: Vec<Vec<Button>>,
119}
120
121impl InlineKeyboard {
122    /// Create an empty keyboard. Add rows with [`row`](Self::row).
123    pub fn new() -> Self {
124        Self::default()
125    }
126
127    /// Append a row of buttons.
128    pub fn row(mut self, buttons: impl IntoIterator<Item = Button>) -> Self {
129        self.rows.push(buttons.into_iter().collect());
130        self
131    }
132
133    /// Convert to the `ReplyMarkup` TL type expected by message-sending functions.
134    pub fn into_markup(self) -> tl::enums::ReplyMarkup {
135        let rows = self.rows.into_iter().map(|row| {
136            tl::enums::KeyboardButtonRow::KeyboardButtonRow(
137                tl::types::KeyboardButtonRow {
138                    buttons: row.into_iter().map(Button::into_raw).collect(),
139                },
140            )
141        }).collect();
142
143        tl::enums::ReplyMarkup::ReplyInlineMarkup(
144            tl::types::ReplyInlineMarkup { rows }
145        )
146    }
147}
148
149impl From<InlineKeyboard> for tl::enums::ReplyMarkup {
150    fn from(kb: InlineKeyboard) -> Self {
151        kb.into_markup()
152    }
153}
154
155// ─── ReplyKeyboard ────────────────────────────────────────────────────────────
156
157/// Builder for a reply keyboard (shown below the message input box).
158#[derive(Clone, Default)]
159pub struct ReplyKeyboard {
160    rows:       Vec<Vec<Button>>,
161    resize:     bool,
162    single_use: bool,
163    selective:  bool,
164}
165
166impl ReplyKeyboard {
167    /// Create a new empty reply keyboard.
168    pub fn new() -> Self { Self::default() }
169
170    /// Append a row of text buttons.
171    pub fn row(mut self, buttons: impl IntoIterator<Item = Button>) -> Self {
172        self.rows.push(buttons.into_iter().collect());
173        self
174    }
175
176    /// Resize keyboard to fit its content (recommended).
177    pub fn resize(mut self) -> Self { self.resize = true; self }
178
179    /// Hide keyboard after a single press.
180    pub fn single_use(mut self) -> Self { self.single_use = true; self }
181
182    /// Show keyboard only to mentioned/replied users.
183    pub fn selective(mut self) -> Self { self.selective = true; self }
184
185    /// Convert to `ReplyMarkup`.
186    pub fn into_markup(self) -> tl::enums::ReplyMarkup {
187        let rows = self.rows.into_iter().map(|row| {
188            tl::enums::KeyboardButtonRow::KeyboardButtonRow(
189                tl::types::KeyboardButtonRow {
190                    buttons: row.into_iter().map(Button::into_raw).collect(),
191                },
192            )
193        }).collect();
194
195        tl::enums::ReplyMarkup::ReplyKeyboardMarkup(
196            tl::types::ReplyKeyboardMarkup {
197                resize:     self.resize,
198                single_use: self.single_use,
199                selective:  self.selective,
200                persistent: false,
201                rows,
202                placeholder: None,
203            }
204        )
205    }
206}
207
208impl From<ReplyKeyboard> for tl::enums::ReplyMarkup {
209    fn from(kb: ReplyKeyboard) -> Self {
210        kb.into_markup()
211    }
212}