# Keyboards You probably already know what a keyboard is, and if you don't, check the [Bots documentation](https://core.telegram.org/bots#keyboards) by Telegram. ## Sending a keyboard When developing bots, a common feature that many developers use is sending custom keyboards to their users - be it inline or reply. In both cases, this is done by providing `replyMarkup` parameter when using `sendText` or similar methods. It accepts plain JavaScript object, but you can also use builder functions from `BotKeyboard` namespace. In mtcute, buttons are represented as a two-dimensional array. ## Reply keyboards Reply keyboard is a keyboard that is shown under user's writebar. When user taps on some button, a message is sent containing this button's text. ```ts await tg.sendText('username', 'Awesome keyboard!', { replyMarkup: BotKeyboard.reply([ [BotKeyboard.text('First button')], [BotKeyboard.text('Second button')], ]) }) ``` You can only use the following button types with reply keyboards: | Name | Type | Notes | | ------------------- | ---------------------------- | ----------------------- | | Text-only | `BotKeyboard.text` | | | Request contact | `BotKeyboard.requestContact` | only for private chats. | | Request geolocation | `BotKeyboard.requestGeo` | only for private chats. | | Request poll | `BotKeyboard.requestPoll` | only for private chats. | Using any other will result in an error by Telegram. You can also instruct the client to hide a previously sent reply keyboard: ```ts await tg.sendText('username', 'No more keyboard :p', { replyMarkup: BotKeyboard.hideReply() }) ``` Or, ask the user to reply to this message with custom text: ```ts await tg.sendText('username', 'What is your name?', { replyMarkup: BotKeyboard.forceReply() }) ``` ## Inline keyboards Inline keyboard is a keyboard that is shown under the message. When user taps on some button, a client does some action that particular button instructs it to do. ```ts await tg.sendText('username', 'Awesome keyboard!', { replyMarkup: BotKeyboard.inline([ [BotKeyboard.callback('First button', 'btn:1')], [BotKeyboard.callback('Second button', 'btn:2')], ]) }) ``` You can only use the following button types with inline keyboards: | Name | Type | Notes | | -------------- | -------------------------- | ----------------------------------------------------------------------------------- | | Callback | `BotKeyboard.callback` | When clicked a callback query will be sent to the bot. | | URL | `BotKeyboard.url` | When clicked the client will open the given URL. | | Switch inline | `BotKeyboard.switchInline` | When clicked the client will open an inline query to this bot with the given query. | | "Play game" | `BotKeyboard.game` | Must be the first one, must be used with `InputMedia.game` as the media. | | "Pay" | `BotKeyboard.pay` | Must be the first one, must be used with `InputMedia.invoice` as the media. | | Seamless login | `BotKeyboard.urlAuth` | [Learn more](https://corefork.telegram.org/constructor/inputKeyboardButtonUrlAuth) | | WebView | `BotKeyboard.webView` | [Learn more](https://corefork.telegram.org/api/bots/webapps) | | Open user | `BotKeyboard.userProfile` | When clicked the client will open the given user's profile | | Request peer | `BotKeyboard.requestPeer` | When clicked the client will ask the user to choose a peer and will send a message with [`ActionPeerChosen`](https://ref.mtcute.dev/interfaces/_mtcute_core.index.ActionPeerChosen.html) | Using any other will result in an error by Telegram. ## Keyboard builder Sometimes 2D array is a bit too low-level, and thus mtcute provides an easy-to-use builder for the keyboards. Once created using `BotKeyboard.builder()`, you can `push` buttons there, and then get it either `asInline` or `asReply`: ```ts const markup = BotKeyboard.builder() .push(BotKeyboard.text('Button 1')) .push(BotKeyboard.text('Button 2')) .asReply() // Result: // [ Button 1 ] // [ Button 2 ] ``` You can also push a button conditionally, or even use a function: ```ts const markup = BotKeyboard.builder() .push(BotKeyboard.text('Button 1')) .push(isAdmin && BotKeyboard.text('Button 2')) .push(() => BotKeyboard.text('Button 3')) .asReply() // Result: // [ Button 1 ] // [ Button 2 ] (only if admin) // [ Button 3 ] ``` When `push`-ing multiple buttons at once, they will be wrapped after a certain number of buttons added (default: 3): ```ts const markup = BotKeyboard.builder() .push( BotKeyboard.text('Button 1'), BotKeyboard.text('Button 2'), BotKeyboard.text('Button 3'), BotKeyboard.text('Button 4'), ) .asReply() // Result: // [ Button 1 ] [ Button 2 ] [ Button 3 ] // [ Button 4 ] ``` Or, you can add entire rows at once without them getting wrapped (and even populate them from a function!): ```ts const markup = BotKeyboard.builder() .row( BotKeyboard.text('1'), BotKeyboard.text('2'), BotKeyboard.text('3'), BotKeyboard.text('4'), ) .row((row) => { for (let i = 5; i <= 8; i++ ) { row.push(BotKeyboard.text(`${i}`)) } }) .asReply() // Result: // [ 1 ] [ 2 ] [ 3 ] [ 4 ] // [ 5 ] [ 6 ] [ 7 ] [ 8 ] ``` ## Callback data builders Writing, parsing and checking callback data manually gets tiring quite fast. Luckily, mtcute provides a tool that does the heavy stuff for you, called Callback data builder. ### Creating a builder Consider a simple bot that has some posts to display to user, and the user can switch between them using inline buttons. First, let's declare a builder for the button: ```ts const PostButton = new CallbackDataBuilder('post', 'id', 'action') ``` Here, `post` is the *prefix*, which will be prepended to all callback data strings generated by this builder to disambiguate. Make sure to use something unique! `id` and `action` are *fields* which will be parsed/serialized to the callback data string in that particular order. Only include important stuff there, since callback data is limited to 64 characters! ::: tip Callback data builders are meant to be static, so it is best to declare them in a separate file and import from other files. ::: ### Creating buttons Now that we have the builder, we can use `.build` method to add buttons to the messages: ```ts await msg.answerText('...', { replyMarkup: BotKeyboard.inline([ [ BotKeyboard.callback( 'Post title', PostButton.build({ id: 1, action: 'view' }) ) ] ]) }) ``` The above code will produce the following callback data in that button: ``` post:1:view ``` ### Handling clicks Our button is currently rather useless, since we haven't registered a handler for it just yet. We can use `.filter` method of our builder to create a filter to suit our needs: ```ts dp.onCallbackQuery(PostButton.filter({ action: 'view' }), async (upd) => { const post = await getPostById(upd.match.id) if (!post) { await upd.answer({ text: 'Not found!' }) return } await upd.editMessage({ text: post.text }) }) ``` `.filter` not only handles parsing and checking, but also provides `.match` extension field that contains the parsed data, and you can use it inside your code. ## Using a keyboard When using mtcute as a client, you may want to use some keyboard that was attached to some message. ```ts dp.onNewMessage(async (msg: Message) => { const markup = msg.markup switch (markup.type) { // see below } }) ``` If type is `hide_reply`, there is (obviously) nothing to do except to hide the current reply keyboard from the UI (if applicable). If type is `force_reply`, just send a message in reply to this message: ```ts await msg.replyText('Some text') ``` If type is `reply` or `inline`, then there are some buttons available. You can find the one you need, and then act accordingly: ```ts const buttons = markup.buttons const buttonINeed = BotKeyboard.findButton(buttons, 'Button text') switch (buttonINeed._) { // see below } ``` `buttonINeed` will be a plain TL object of type [KeyboardButton](https://corefork.telegram.org/type/KeyboardButton). ### Emulating a click See [Telegram docs](https://core.telegram.org/api/bots/buttons#pressing-buttons) on this topic.