grammers_client/message/reply_markup.rs
1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Contains functions to build reply markups.
10//!
11//! These can only be used by bot accounts when sending
12//! messages through [`InputMessage::reply_markup`].
13//!
14//! Each function returns a concrete builder-like type that
15//! may be further configured via their inherent methods.
16//!
17//! The trait is used to group all types as "something that
18//! may be used as a reply markup".
19//!
20//! [`InputMessage::reply_markup`]: crate::message::InputMessage::reply_markup
21
22use grammers_tl_types as tl;
23
24use super::{Button, Key};
25
26const EMPTY_KEYBOARD_MARKUP: tl::types::ReplyKeyboardMarkup = tl::types::ReplyKeyboardMarkup {
27 resize: false,
28 single_use: false,
29 selective: false,
30 persistent: false,
31 rows: Vec::new(),
32 placeholder: None,
33};
34
35/// Markup to be used as the intended way to reply to the message it is attached to.
36pub struct ReplyMarkup {
37 pub raw: tl::enums::ReplyMarkup,
38}
39
40impl ReplyMarkup {
41 /// Define inline buttons for a message.
42 ///
43 /// These will display right under the message.
44 ///
45 /// You cannot add images to the buttons, but you can use emoji (simply copy-paste them into your
46 /// code, or use the correct escape sequence, or using any other input methods you like).
47 ///
48 /// You will need to provide a matrix of [`Button`], that is, a vector that contains the
49 /// rows from top to bottom, where the rows consist of a vector of buttons from left to right.
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// # async fn f(client: &mut grammers_client::Client, peer: grammers_session::types::PeerRef) -> Result<(), Box<dyn std::error::Error>> {
55 /// use grammers_client::message::{InputMessage, ReplyMarkup, Button};
56 ///
57 /// let artist = "Krewella";
58 /// let markup = ReplyMarkup::from_buttons(&[
59 /// vec![Button::data(format!("Song by {}", artist), b"play")],
60 /// vec![Button::data("Previous", b"prev"), Button::data("Next", b"next")],
61 /// ]);
62 /// client.send_message(peer, InputMessage::new().text("Select song").reply_markup(markup)).await?;
63 /// # Ok(())
64 /// # }
65 /// ```
66 pub fn from_buttons(buttons: &[Vec<Button>]) -> Self {
67 Self {
68 raw: tl::enums::ReplyMarkup::ReplyInlineMarkup(tl::types::ReplyInlineMarkup {
69 rows: buttons
70 .into_iter()
71 .map(|row| {
72 tl::types::KeyboardButtonRow {
73 buttons: row.into_iter().map(|button| button.raw.clone()).collect(),
74 }
75 .into()
76 })
77 .collect(),
78 }),
79 }
80 }
81
82 /// Creates a [`ReplyMarkup::from_buttons`] with a single row.
83 pub fn from_buttons_row(buttons: &[Button]) -> Self {
84 Self {
85 raw: tl::enums::ReplyMarkup::ReplyInlineMarkup(tl::types::ReplyInlineMarkup {
86 rows: vec![tl::enums::KeyboardButtonRow::Row(
87 tl::types::KeyboardButtonRow {
88 buttons: buttons
89 .into_iter()
90 .map(|button| button.raw.clone())
91 .collect(),
92 },
93 )],
94 }),
95 }
96 }
97
98 /// Creates a [`ReplyMarkup::from_buttons`] with a single column.
99 pub fn from_buttons_col(buttons: &[Button]) -> Self {
100 Self {
101 raw: tl::enums::ReplyMarkup::ReplyInlineMarkup(tl::types::ReplyInlineMarkup {
102 rows: buttons
103 .into_iter()
104 .map(|button| {
105 tl::enums::KeyboardButtonRow::Row(tl::types::KeyboardButtonRow {
106 buttons: vec![button.raw.clone()],
107 })
108 })
109 .collect(),
110 }),
111 }
112 }
113
114 /// Define a custom keyboard, replacing the user's own virtual keyboard.
115 ///
116 /// This will be displayed below the input message field for users, and on mobile devices, this
117 /// also hides the virtual keyboard (effectively "replacing" it).
118 ///
119 /// You cannot add images to the buttons, but you can use emoji (simply copy-paste them into your
120 /// code, or use the correct escape sequence, or using any other input methods you like).
121 ///
122 /// You will need to provide a matrix of [`Key`], that is, a vector that contains the
123 /// rows from top to bottom, where the rows consist of a vector of buttons from left to right.
124 ///
125 /// The return type may continue to be configured before being used.
126 ///
127 /// # Examples
128 ///
129 /// ```
130 /// # async fn f(client: &mut grammers_client::Client, peer: grammers_session::types::PeerRef) -> Result<(), Box<dyn std::error::Error>> {
131 /// use grammers_client::message::{InputMessage, ReplyMarkup, Key};
132 ///
133 /// let markup = ReplyMarkup::from_keys(&[
134 /// vec![Key::text("Accept")],
135 /// vec![Key::text("Cancel"), Key::text("Try something else")],
136 /// ]);
137 /// client.send_message(peer, InputMessage::new().text("What do you want to do?").reply_markup(markup)).await?;
138 /// # Ok(())
139 /// # }
140 /// ```
141 pub fn from_keys(keys: &[Vec<Key>]) -> Self {
142 Self {
143 raw: tl::enums::ReplyMarkup::ReplyKeyboardMarkup(tl::types::ReplyKeyboardMarkup {
144 rows: keys
145 .into_iter()
146 .map(|row| {
147 tl::types::KeyboardButtonRow {
148 buttons: row.into_iter().map(|key| key.raw.clone()).collect(),
149 }
150 .into()
151 })
152 .collect(),
153 ..EMPTY_KEYBOARD_MARKUP
154 }),
155 }
156 }
157
158 /// Creates a [`ReplyMarkup::from_keys`] with a single row.
159 pub fn from_keys_row(keys: &[Key]) -> Self {
160 Self {
161 raw: tl::enums::ReplyMarkup::ReplyKeyboardMarkup(tl::types::ReplyKeyboardMarkup {
162 rows: vec![tl::enums::KeyboardButtonRow::Row(
163 tl::types::KeyboardButtonRow {
164 buttons: keys.into_iter().map(|key| key.raw.clone()).collect(),
165 },
166 )],
167 ..EMPTY_KEYBOARD_MARKUP
168 }),
169 }
170 }
171
172 /// Creates a [`ReplyMarkup::from_keys`] with a single column.
173 pub fn from_keys_col(keys: &[Key]) -> Self {
174 Self {
175 raw: tl::enums::ReplyMarkup::ReplyKeyboardMarkup(tl::types::ReplyKeyboardMarkup {
176 rows: keys
177 .into_iter()
178 .map(|key| {
179 tl::enums::KeyboardButtonRow::Row(tl::types::KeyboardButtonRow {
180 buttons: vec![key.raw.clone()],
181 })
182 })
183 .collect(),
184 ..EMPTY_KEYBOARD_MARKUP
185 }),
186 }
187 }
188
189 /// Hide a previously-sent keyboard.
190 ///
191 /// See the return type for further configuration options.
192 ///
193 /// # Examples
194 ///
195 /// ```
196 /// # async fn f(client: &mut grammers_client::Client, peer: grammers_session::types::PeerRef) -> Result<(), Box<dyn std::error::Error>> {
197 /// use grammers_client::message::{InputMessage, ReplyMarkup};
198 ///
199 /// let markup = ReplyMarkup::hide();
200 /// client.send_message(peer, InputMessage::new().text("Bot keyboards removed.").reply_markup(markup)).await?;
201 /// # Ok(())
202 /// # }
203 /// ```
204 pub fn hide() -> Self {
205 Self {
206 raw: tl::enums::ReplyMarkup::ReplyKeyboardHide(tl::types::ReplyKeyboardHide {
207 selective: false,
208 }),
209 }
210 }
211
212 /// "Forces" the user to send a reply.
213 ///
214 /// This will cause the user's application to automatically select the message for replying to it,
215 /// although the user is still able to dismiss the reply and send a normal message.
216 ///
217 /// See the return type for further configuration options.
218 ///
219 /// # Examples
220 ///
221 /// ```
222 /// # async fn f(client: &mut grammers_client::Client, peer: grammers_session::types::PeerRef) -> Result<(), Box<dyn std::error::Error>> {
223 /// use grammers_client::message::{InputMessage, ReplyMarkup};
224 ///
225 /// let markup = ReplyMarkup::force_reply().single_use();
226 /// client.send_message(peer, InputMessage::new().text("Reply me!").reply_markup(markup)).await?;
227 /// # Ok(())
228 /// # }
229 /// ```
230 pub fn force_reply() -> Self {
231 Self {
232 raw: tl::enums::ReplyMarkup::ReplyKeyboardForceReply(
233 tl::types::ReplyKeyboardForceReply {
234 single_use: false,
235 selective: false,
236 placeholder: None,
237 },
238 ),
239 }
240 }
241
242 /// Requests clients to resize the keyboard vertically for optimal fit (e.g., make the
243 /// keyboard smaller if there are just two rows of buttons). Otherwise, the custom keyboard
244 /// is always of the same height as the virtual keyboard.
245 ///
246 /// Only has effect on reply markups that use keys.
247 pub fn fit_size(mut self) -> Self {
248 use grammers_tl_types::enums::ReplyMarkup as RM;
249 match &mut self.raw {
250 RM::ReplyKeyboardMarkup(keyboard) => keyboard.resize = true,
251 RM::ReplyKeyboardHide(_)
252 | RM::ReplyKeyboardForceReply(_)
253 | RM::ReplyInlineMarkup(_) => {}
254 }
255 self
256 }
257
258 /// Requests clients to hide the keyboard as soon as it's been used.
259 ///
260 /// The keyboard will still be available, but clients will automatically display the usual
261 /// letter-keyboard in the conversation – the user can press a special button in the input field to
262 /// see the custom keyboard again.
263 ///
264 /// Only has effect on reply markups that use keys or when forcing the user to reply.
265 pub fn single_use(mut self) -> Self {
266 use grammers_tl_types::enums::ReplyMarkup as RM;
267 match &mut self.raw {
268 RM::ReplyKeyboardForceReply(keyboard) => keyboard.single_use = true,
269 RM::ReplyKeyboardMarkup(keyboard) => keyboard.single_use = true,
270 RM::ReplyKeyboardHide(_) | RM::ReplyInlineMarkup(_) => {}
271 }
272 self
273 }
274
275 /// Force the markup to only apply to specific users.
276 ///
277 /// The selected user will be either the people @-mentioned in the text of the `Message`
278 /// object, or if the bot's message is a reply, the sender of the original message.
279 ///
280 /// Has no effect on markups that consist of inline buttons.
281 pub fn selective(mut self) -> Self {
282 use grammers_tl_types::enums::ReplyMarkup as RM;
283 match &mut self.raw {
284 RM::ReplyKeyboardHide(keyboard) => keyboard.selective = true,
285 RM::ReplyKeyboardForceReply(keyboard) => keyboard.selective = true,
286 RM::ReplyKeyboardMarkup(keyboard) => keyboard.selective = true,
287 RM::ReplyInlineMarkup(_) => {}
288 }
289 self
290 }
291}