alina sireneva
690948b8b1
All checks were successful
Build and deploy typedoc / build (push) Successful in 5m15s
Co-authored-by: Kamilla 'ova <me@kamillaova.dev> Co-authored-by: Alina Chebakova <chebakov05@gmail.com> Co-authored-by: Kravets <57632712+kravetsone@users.noreply.github.com> Co-authored-by: starkow <hello@starkow.dev> Co-authored-by: sireneva <150665887+sireneva@users.noreply.github.com>
301 lines
9 KiB
Markdown
Executable file
301 lines
9 KiB
Markdown
Executable file
# 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.
|
|
|
|
<v-img
|
|
src="https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd"
|
|
width="280"
|
|
caption="Example of a reply keyboard"
|
|
/>
|
|
|
|
```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.
|
|
|
|
<v-img
|
|
src="https://core.telegram.org/file/811140217/1/NkRCCLeQZVc/17a804837802700ea4"
|
|
width="280"
|
|
caption="Example of an inline keyboard"
|
|
/>
|
|
|
|
```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.
|
|
|
|
<!-- Full example TODO LINK -->
|
|
|
|
### 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.
|