Skip to main content

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}