telegram-inline-keyboard-builder 2.0.0 β 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -113
- package/core/InlineKeyboardBuilder.js +249 -176
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,187 +1,285 @@
|
|
|
1
|
-
|
|
1
|
+
 [](https://www.npmjs.com/package/telegram-inline-keyboard-builder) [](https://www.npmjs.com/package/telegram-inline-keyboard-builder) [](LICENSE) 
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
> Principle: a central logic core handles button creation and placement. Adapters transform the neutral structure into the format expected by the target API.
|
|
3
|
+
# Inline Keyboard Builder (v2) Universal inline keyboard builder for Telegram Bots.
|
|
4
|
+
Produces **pure Telegram Bot API compliant JSON**, usable with **any library** (Telegraf, node-telegram-bot-api, Pyrogram, Aiogram, Puregram, Telebotβ¦).
|
|
6
5
|
|
|
6
|
+
## π₯ New update π₯
|
|
7
|
+
- Added color style for premium Telegram buttons and icons
|
|
8
|
+
- Builder method typing
|
|
7
9
|
|
|
10
|
+
## How does this feature work?
|
|
11
|
+
Simply specify a new parameter to the function to add the URL and class.
|
|
8
12
|
|
|
13
|
+
```js
|
|
14
|
+
addCallbackButton(text, callback_data, options = {})
|
|
15
|
+
addCUrlButton(text, callback_data, options = {})
|
|
16
|
+
```
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
The options must contain at least one of these parameters: either `icon_custom_emoji_id` or `style`
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// Example
|
|
22
|
+
const keyboard = new InlineKeyboardBuilder(1)
|
|
23
|
+
.addCallbackButton("blue button", "click", {
|
|
24
|
+
style: "primary"
|
|
25
|
+
})
|
|
26
|
+
.addCallbackButton("blue button with icon", "click", {
|
|
27
|
+
icon_custom_emoji_id: "4963511421280192936",
|
|
28
|
+
style: "primary"
|
|
29
|
+
})
|
|
30
|
+
.addCallbackButton("Just a icon","click",{
|
|
31
|
+
icon_custom_emoji_id: "4963511421280192936"
|
|
32
|
+
});
|
|
33
|
+
```
|
|
11
34
|
|
|
12
|
-
|
|
35
|
+
> **Warning**: `icon_custom_emoji_id` only works if the bot owner has a Telegram premium subscription.
|
|
13
36
|
|
|
14
|
-
|
|
37
|
+
## Example Usage (telegraf)
|
|
38
|
+
```
|
|
39
|
+
// start command
|
|
40
|
+
bot.start(async ctx => {
|
|
41
|
+
const keyboard = new InlineKeyboardBuilder(1)
|
|
42
|
+
.addCallbackButton("blue", "click", {
|
|
43
|
+
style: "primary"
|
|
44
|
+
})
|
|
45
|
+
.addCallbackButton("blue with icon", "click", {
|
|
46
|
+
icon_custom_emoji_id: "4963511421280192936",
|
|
47
|
+
style: "primary"
|
|
48
|
+
})
|
|
49
|
+
.addCallbackButton("green", "click", {
|
|
50
|
+
style: "success"
|
|
51
|
+
})
|
|
52
|
+
.addCallbackButton("green with icon", "click", {
|
|
53
|
+
icon_custom_emoji_id: "4963511421280192936",
|
|
54
|
+
style: "success"
|
|
55
|
+
})
|
|
56
|
+
.addCallbackButton("red", "danger",{
|
|
57
|
+
style: "danger"
|
|
58
|
+
})
|
|
59
|
+
.addCallbackButton("red with icon", "click", {
|
|
60
|
+
icon_custom_emoji_id: "4963511421280192936",
|
|
61
|
+
style: "danger"
|
|
62
|
+
})
|
|
63
|
+
.addCallbackButton("Just a icon","click",{
|
|
64
|
+
icon_custom_emoji_id: "4963511421280192936"
|
|
65
|
+
})
|
|
66
|
+
await ctx.reply(
|
|
67
|
+
"π New Button style π₯π₯π₯",
|
|
68
|
+
keyboard.build()
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
```
|
|
15
72
|
|
|
16
|
-
|
|
73
|
+
### Results
|
|
74
|
+

|
|
17
75
|
|
|
18
|
-
|
|
76
|
+
---
|
|
77
|
+
## other log
|
|
19
78
|
|
|
20
|
-
|
|
79
|
+
> Version 2 removes adapters and focuses on a single universal output:
|
|
80
|
+
> **valid `inline_keyboard` JSON as expected by Telegram API**.
|
|
21
81
|
|
|
82
|
+
---
|
|
83
|
+
## π Key Features - Fluent & chainable API - Library-agnostic (no adapters, no dependencies)
|
|
22
84
|
|
|
85
|
+
- Produces **pure Telegram inline keyboard JSON**
|
|
86
|
+
- Auto-wrap & row control - Works with **any Telegram framework**
|
|
87
|
+
- Zero abstraction leak
|
|
23
88
|
|
|
24
|
-
---
|
|
89
|
+
---
|
|
25
90
|
|
|
26
91
|
## Installation
|
|
27
92
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```bash
|
|
93
|
+
```bash
|
|
31
94
|
npm install telegram-inline-keyboard-builder
|
|
32
95
|
```
|
|
33
|
-
|
|
34
|
-
|
|
96
|
+
## importation
|
|
35
97
|
```js
|
|
36
|
-
import {
|
|
98
|
+
import { InlineKeyboardBuilder } from "telegram-inline-keyboard-builder";
|
|
37
99
|
```
|
|
38
100
|
|
|
39
|
-
|
|
101
|
+
## π§ Core Concept
|
|
40
102
|
|
|
41
|
-
|
|
103
|
+
Telegram inline keyboards follow **one universal schema**.
|
|
42
104
|
|
|
43
|
-
|
|
105
|
+
This builder:
|
|
44
106
|
|
|
45
|
-
|
|
107
|
+
* generates the keyboard **directly in Telegram format**
|
|
46
108
|
|
|
47
|
-
|
|
109
|
+
* lets you pass the result to **any Telegram library**
|
|
110
|
+
```js
|
|
111
|
+
{ reply_markup: { inline_keyboard: [...] } }
|
|
112
|
+
```
|
|
113
|
+
- **No adapters**.
|
|
114
|
+
- **No wrappers**.
|
|
115
|
+
- **No framework coupling**.
|
|
48
116
|
|
|
117
|
+
## π§ Public API
|
|
49
118
|
|
|
119
|
+
### Constructor
|
|
50
120
|
|
|
51
|
-
|
|
121
|
+
```js
|
|
122
|
+
new InlineKeyboardBuilder({ buttonsPerRow = 2, autoWrapMaxChars = 0 })
|
|
52
123
|
|
|
53
|
-
|
|
124
|
+
//Chainable Methods
|
|
54
125
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
126
|
+
.addCallbackButton(text, callback_data, hide = false)
|
|
127
|
+
.addUrlButton(text, url, hide = false)
|
|
128
|
+
.addPayButton(text, options = {})
|
|
129
|
+
.addCustomButton(buttonObject)
|
|
130
|
+
.addButtons(config)
|
|
131
|
+
.setButtonsPerRow(n)
|
|
132
|
+
.setAutoWrapMaxChars(n)
|
|
133
|
+
.newRow()
|
|
58
134
|
|
|
59
|
-
|
|
135
|
+
// build
|
|
136
|
+
.build()
|
|
60
137
|
|
|
61
|
-
|
|
62
|
-
new InlineKeyboardTelegraf({ buttonsPerRow = 2, autoWrapMaxChars = 0 })
|
|
63
|
-
new InlineKeyboardNodeTelegram({ ... })
|
|
138
|
+
const keyboard = builder.build();
|
|
64
139
|
|
|
65
|
-
|
|
140
|
+
// Always returns:
|
|
66
141
|
|
|
67
|
-
|
|
68
|
-
.addUrlButton(text, url, hide = false)
|
|
69
|
-
.addPayButton(text, options = {})
|
|
70
|
-
.addCustomButton(btnObj)
|
|
71
|
-
.addButtons(config) // array or { type, buttons: [...] }
|
|
72
|
-
.setButtonsPerRow(n)
|
|
73
|
-
.setAutoWrapMaxChars(n)
|
|
74
|
-
.newRow() // forces a new row
|
|
75
|
-
.build() // returns the keyboard formatted by the adapter
|
|
142
|
+
{ reply_markup: { inline_keyboard: [...] } }
|
|
76
143
|
```
|
|
77
|
-
|
|
144
|
+
Fully compliant with Telegram Bot API.
|
|
78
145
|
|
|
79
|
-
|
|
146
|
+
## Usage Example (Telegraf)
|
|
80
147
|
|
|
81
|
-
|
|
148
|
+
```js
|
|
149
|
+
import { Telegraf } from "telegraf";
|
|
82
150
|
|
|
151
|
+
import { InlineKeyboardBuilder } from "telegram-inline-keyboard-builder";
|
|
83
152
|
|
|
153
|
+
const bot = new Telegraf(process.env.BOT_TOKEN);
|
|
84
154
|
|
|
85
|
-
|
|
155
|
+
bot.start(ctx => {
|
|
156
|
+
const keyboard = new InlineKeyboardBuilder({ buttonsPerRow: 2, autoWrapMaxChars: 24 })
|
|
157
|
+
.addCallbackButton("β
OK", "OK_ACTION")
|
|
158
|
+
.addUrlButton("π Website", "https://example.com")
|
|
159
|
+
.newRow()
|
|
160
|
+
.addCallbackButton("β Cancel", "CANCEL_ACTION")
|
|
161
|
+
.build();
|
|
162
|
+
ctx.reply("Welcome π\nChoose an action:", keyboard); });
|
|
86
163
|
|
|
87
|
-
|
|
164
|
+
bot.launch();
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
## Usage Example (node-telegram-bot-api)
|
|
88
168
|
|
|
89
169
|
```js
|
|
90
|
-
import
|
|
91
|
-
import { InlineKeyboardTelegraf } from "telegram-inline-keyboard-builder";
|
|
170
|
+
import TelegramBot from "node-telegram-bot-api";
|
|
92
171
|
|
|
93
|
-
|
|
172
|
+
import { InlineKeyboardBuilder } from "telegram-inline-keyboard-builder";
|
|
94
173
|
|
|
95
|
-
bot
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
.build();
|
|
102
|
-
|
|
103
|
-
ctx.reply("Welcome π\nChoose an action:", keyboard);
|
|
104
|
-
});
|
|
174
|
+
const bot = new TelegramBot(TOKEN, { polling: true });
|
|
175
|
+
bot.onText(/\/start/, msg => {
|
|
176
|
+
const keyboard = new InlineKeyboardBuilder()
|
|
177
|
+
.addCallbackButton("OK", "OK")
|
|
178
|
+
.addUrlButton("Site", "https://example.com")
|
|
179
|
+
.build();
|
|
105
180
|
|
|
106
|
-
bot.
|
|
181
|
+
bot.sendMessage(msg.chat.id, "Hello", keyboard); });
|
|
107
182
|
```
|
|
108
183
|
|
|
109
|
-
##
|
|
184
|
+
## π³ Payment Buttons
|
|
110
185
|
|
|
111
|
-
|
|
112
|
-
import TelegramBot from "node-telegram-bot-api";
|
|
113
|
-
import { InlineKeyboardNodeTelegram } from "telegram-inline-keyboard-builder";
|
|
186
|
+
### β οΈ Telegram limitation
|
|
114
187
|
|
|
115
|
-
|
|
188
|
+
> [!WARNING]
|
|
189
|
+
> Payment buttons must only be used with:
|
|
190
|
+
- sendInvoice
|
|
191
|
+
- replyWithInvoice
|
|
116
192
|
|
|
117
|
-
|
|
118
|
-
const kb = new InlineKeyboardNodeTelegram()
|
|
119
|
-
.addCallbackButton("OK", "OK")
|
|
120
|
-
.addUrlButton("Site", "https://example.com")
|
|
121
|
-
.build();
|
|
122
|
-
|
|
123
|
-
// kb === { reply_markup: { inline_keyboard: [...] } }
|
|
124
|
-
bot.sendMessage(msg.chat.id, "Hello", kb);
|
|
125
|
-
});
|
|
193
|
+
They must be hidden in normal messages.
|
|
126
194
|
|
|
195
|
+
```js
|
|
196
|
+
.addPayButton("Pay now");
|
|
127
197
|
```
|
|
128
|
-
---
|
|
129
198
|
|
|
130
|
-
|
|
199
|
+
Using a visible payment button outside invoices will cause Telegram API errors.
|
|
200
|
+
|
|
201
|
+
## π§― Common Errors
|
|
131
202
|
|
|
132
|
-
|
|
203
|
+
**Telegram API error**
|
|
204
|
+
|
|
205
|
+
Make sure the keyboard object is passed directly:
|
|
133
206
|
|
|
134
207
|
```js
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
208
|
+
const keyboard = new InlineKeyboardBuilder(1)
|
|
209
|
+
.addCallbackButton("Setting","show_setting")
|
|
210
|
+
.build()
|
|
211
|
+
// telegraf
|
|
212
|
+
ctx.reply("Text", keyboard);
|
|
139
213
|
|
|
140
|
-
//
|
|
141
|
-
|
|
214
|
+
// node telegram bot api
|
|
215
|
+
bot.sendMessage(chatId, "Text", keyboard);
|
|
142
216
|
|
|
143
|
-
|
|
217
|
+
// CORRECT β
|
|
144
218
|
|
|
145
|
-
|
|
146
|
-
createCallback(text, data) {
|
|
147
|
-
return { text, callback_data: data };
|
|
148
|
-
}
|
|
219
|
+
// OR if you want to include it in the options
|
|
149
220
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
221
|
+
const keyboard = new InlineKeyboardBuilder(1)
|
|
222
|
+
.addCallbackButton("Setting","show_setting")
|
|
223
|
+
.build()
|
|
153
224
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
225
|
+
// telegraf
|
|
226
|
+
ctx.reply("Text", {
|
|
227
|
+
reply_markup: keyboard.reply_makup, // inline keyboard
|
|
228
|
+
parse_mode: "HTML",
|
|
229
|
+
// ...
|
|
230
|
+
});
|
|
157
231
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
232
|
+
// node telegram bot api
|
|
233
|
+
bot.sendMessage(chatId, "Text", {
|
|
234
|
+
reply_markup: keyboard.reply_makup, // inline keyboard
|
|
235
|
+
parse_mode: "HTML",
|
|
236
|
+
// ...
|
|
237
|
+
);
|
|
162
238
|
```
|
|
163
|
-
|
|
164
|
-
## β
API supported
|
|
165
|
-
- Telegraf
|
|
166
|
-
- Purgram
|
|
167
|
-
- Node-telegram-bot-api
|
|
168
|
-
- Telebot
|
|
169
|
-
---
|
|
239
|
+
## Migration to V2
|
|
170
240
|
|
|
171
|
-
|
|
241
|
+
- **V1**: The inline keyboard builder used **adapters** for each new API, resulting in code that was **unmaintainable** in case of **updates**.
|
|
172
242
|
|
|
173
|
-
**
|
|
243
|
+
- **V2**: Here we **simply construct an object valid for all types of APIs** without **adapting** it.
|
|
174
244
|
|
|
175
|
-
|
|
245
|
+
## π Support This Project (Crypto)
|
|
176
246
|
|
|
177
|
-
|
|
247
|
+
This project is maintained in my free time.
|
|
248
|
+
If it helped you, consider supporting it with a crypto donation β€οΈ
|
|
249
|
+
It helps me maintain and improve the project.
|
|
178
250
|
|
|
251
|
+
You can send donations to the following addresses:
|
|
179
252
|
|
|
180
|
-
|
|
253
|
+
| Crypto | Address |
|
|
254
|
+
|--------|---------|
|
|
255
|
+
| **USDT (TRC20)** | `0x607c1430601989d43c9CD2eeD9E516663e0BdD1F` |
|
|
256
|
+
| **USDC (Polygon/ETH)** | `0x607c1430601989d43c9CD2eeD9E516663e0BdD1F` |
|
|
257
|
+
| **Ethereum (ETH)** | `0x607c1430601989d43c9CD2eeD9E516663e0BdD1F` |
|
|
258
|
+
| **Bitcoin (BTC)** | `bc1qmysepz6eerz2mqyx5dd0yy87c3gk6hccwla5x2` |
|
|
259
|
+
| **Tron (TRX)** | `TE9RiTaDpx7DGZzCMw7qds51nzszKiyeR8` |
|
|
260
|
+
| **TON** | `UQA1NPW4GqgIVa9R6lebN_0v64Q-Sz_nHrmK9LCk-FfdjVOH` |
|
|
181
261
|
|
|
182
|
-
|
|
262
|
+
### πΉ Optional QR Codes for quick mobile donation
|
|
183
263
|
|
|
184
|
-
|
|
264
|
+
**USDT (TRC20)**
|
|
265
|
+

|
|
185
266
|
|
|
267
|
+
**USDC**
|
|
268
|
+

|
|
186
269
|
|
|
187
|
-
|
|
270
|
+
**Ethereum (ETH)**
|
|
271
|
+

|
|
272
|
+
|
|
273
|
+
**Bitcoin (BTC)**
|
|
274
|
+

|
|
275
|
+
|
|
276
|
+
**Tron (TRX)**
|
|
277
|
+

|
|
278
|
+
|
|
279
|
+
**TON**
|
|
280
|
+

|
|
281
|
+
|
|
282
|
+
## βοΈ Contribution
|
|
283
|
+
|
|
284
|
+
Contributions are welcome β€οΈ
|
|
285
|
+
Please open an issue before proposing major changes.
|
|
@@ -1,178 +1,251 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Builder class for creating Telegram inline keyboards with optional
|
|
3
|
+
* custom styles, premium emojis, and automatic layout.
|
|
4
|
+
*/
|
|
2
5
|
export class InlineKeyboardBuilder {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new InlineKeyboardBuilder instance.
|
|
8
|
+
*
|
|
9
|
+
* @param {number} [buttonsPerRow=2] - Number of buttons per row.
|
|
10
|
+
* @param {number} [autoWrapMaxChars=0] - Maximum characters per row before auto-wrapping. 0 = disabled.
|
|
11
|
+
*/
|
|
12
|
+
constructor(buttonsPerRow = 2, autoWrapMaxChars = 0) {
|
|
13
|
+
this.buttonsPerRow = buttonsPerRow;
|
|
14
|
+
this.autoWrapMaxChars = autoWrapMaxChars;
|
|
15
|
+
this._buttons = []; // Flat list of buttons with optional row markers
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ---------- internal ----------
|
|
19
|
+
/**
|
|
20
|
+
* Adds a button to the internal list.
|
|
21
|
+
* @private
|
|
22
|
+
* @param {object} btn - The button object to push.
|
|
23
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
24
|
+
*/
|
|
25
|
+
_pushButton(btn) {
|
|
26
|
+
this._buttons.push(btn);
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------- button helpers ----------
|
|
31
|
+
/**
|
|
32
|
+
* Adds a callback button with optional style or emoji.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} text - Button text.
|
|
35
|
+
* @param {string} callback_data - Data sent in callback query.
|
|
36
|
+
* @param {object} [options] - Optional button options (style, icon_custom_emoji_id).
|
|
37
|
+
* @param {"primary"|"danger"|"success"} [options.style] - Optional button style.
|
|
38
|
+
* @param {string} [options.icon_custom_emoji_id] - Optional premium emoji.You need premium account for use this option
|
|
39
|
+
* @throws {Error} If text or callback_data is missing.
|
|
40
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
41
|
+
*/
|
|
42
|
+
addCallbackButton(text, callback_data, options = {}) {
|
|
43
|
+
if (!text || !callback_data) {
|
|
44
|
+
throw new Error("Callback button requires text and callback_data");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { style, icon_custom_emoji_id } = options;
|
|
48
|
+
|
|
49
|
+
if (style && !["primary", "danger", "success"].includes(style)) {
|
|
50
|
+
throw new Error("Invalid style. Allowed: primary, danger, success");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return this._pushButton({
|
|
54
|
+
text,
|
|
55
|
+
callback_data,
|
|
56
|
+
style,
|
|
57
|
+
icon_custom_emoji_id
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Adds a URL button with optional style or emoji.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} text - Button text.
|
|
65
|
+
* @param {string} url - URL to open.
|
|
66
|
+
* @param {object} [options] - Optional button options (style, icon_custom_emoji_id).
|
|
67
|
+
* @param {"primary"|"danger"|"success"} [options.style] - Optional button style.
|
|
68
|
+
* @param {string} [options.icon_custom_emoji_id] - Optional premium emoji.You need premium account for use this option
|
|
69
|
+
* @throws {Error} If text or URL is missing.
|
|
70
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
71
|
+
*/
|
|
72
|
+
addUrlButton(text, url, options = {}) {
|
|
73
|
+
if (!text || !url) {
|
|
74
|
+
throw new Error("URL button requires text and url");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { style, icon_custom_emoji_id } = options;
|
|
78
|
+
|
|
79
|
+
if (style && !["primary", "danger", "success"].includes(style)) {
|
|
80
|
+
throw new Error("Invalid style. Allowed: primary, danger, success");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return this._pushButton({ text, url, style, icon_custom_emoji_id });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Adds a pay button.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} text - Button text.
|
|
90
|
+
* @throws {Error} If text is missing.
|
|
91
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
92
|
+
*/
|
|
93
|
+
addPayButton(text) {
|
|
94
|
+
if (!text) {
|
|
95
|
+
throw new Error("Pay button requires text");
|
|
96
|
+
}
|
|
97
|
+
return this._pushButton({ text, pay: true });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Adds a fully custom button object.
|
|
102
|
+
*
|
|
103
|
+
* @param {object} buttonObject - Must have at least a `text` property.
|
|
104
|
+
* @throws {Error} If the button object is invalid.
|
|
105
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
106
|
+
*/
|
|
107
|
+
addCustomButton(buttonObject) {
|
|
108
|
+
if (!buttonObject || !buttonObject.text) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"Custom button must be a valid InlineKeyboardButton object"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return this._pushButton(buttonObject);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------- layout controls ----------
|
|
117
|
+
/**
|
|
118
|
+
* Sets the number of buttons per row.
|
|
119
|
+
* @param {number} n - Must be at least 1.
|
|
120
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
121
|
+
*/
|
|
122
|
+
setButtonsPerRow(n) {
|
|
123
|
+
this.buttonsPerRow = Math.max(1, Math.floor(n));
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sets the maximum characters per row before auto-wrapping.
|
|
129
|
+
* @param {number} n - 0 disables auto-wrap.
|
|
130
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
131
|
+
*/
|
|
132
|
+
setAutoWrapMaxChars(n) {
|
|
133
|
+
this.autoWrapMaxChars = Math.max(0, Math.floor(n));
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Forces a new row in the keyboard.
|
|
139
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
140
|
+
*/
|
|
141
|
+
newRow() {
|
|
142
|
+
this._buttons.push({ __newRow: true });
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------- config-based API ----------
|
|
147
|
+
/**
|
|
148
|
+
* Adds a button from a config object.
|
|
149
|
+
* @private
|
|
150
|
+
* @param {object} btn - Button config { type, text, ... }.
|
|
151
|
+
*/
|
|
152
|
+
_addButtonFromConfig(btn) {
|
|
153
|
+
const { type, text } = btn;
|
|
154
|
+
if (!type || !text)
|
|
155
|
+
throw new Error("Button must have at least { type, text }");
|
|
156
|
+
switch (type) {
|
|
157
|
+
case "callback":
|
|
158
|
+
if (!btn.data)
|
|
159
|
+
throw new Error("Callback button requires `data`");
|
|
160
|
+
this.addCallbackButton(text, btn.data);
|
|
161
|
+
break;
|
|
162
|
+
case "url":
|
|
163
|
+
if (!btn.url) throw new Error("URL button requires `url`");
|
|
164
|
+
this.addUrlButton(text, btn.url);
|
|
165
|
+
break;
|
|
166
|
+
case "pay":
|
|
167
|
+
this.addPayButton(text);
|
|
168
|
+
break;
|
|
169
|
+
case "custom":
|
|
170
|
+
if (!btn.button)
|
|
171
|
+
throw new Error("Custom button requires `button`");
|
|
172
|
+
this.addCustomButton(btn.button);
|
|
173
|
+
break;
|
|
174
|
+
default:
|
|
175
|
+
throw new Error(`Unknown button type: ${type}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Adds multiple buttons from config.
|
|
181
|
+
* @param {Array|object} config - Array of button configs or grouped config.
|
|
182
|
+
* @returns {InlineKeyboardBuilder} The instance for chaining.
|
|
183
|
+
*/
|
|
184
|
+
addButtons(config) {
|
|
185
|
+
if (Array.isArray(config)) {
|
|
186
|
+
for (const btn of config) this._addButtonFromConfig(btn);
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
const { type, buttons } = config;
|
|
190
|
+
if (!type || !Array.isArray(buttons))
|
|
191
|
+
throw new Error("addButtons: invalid config");
|
|
192
|
+
for (const btn of buttons) this._addButtonFromConfig({ type, ...btn });
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---------- layout engine ----------
|
|
197
|
+
/**
|
|
198
|
+
* Internal method that lays out buttons into rows based on configuration.
|
|
199
|
+
* @private
|
|
200
|
+
* @returns {Array<Array<object>>} Array of rows with buttons.
|
|
201
|
+
*/
|
|
202
|
+
_layoutButtons() {
|
|
203
|
+
const rows = [];
|
|
204
|
+
let row = [];
|
|
205
|
+
let rowChars = 0;
|
|
206
|
+
|
|
207
|
+
const pushRow = () => {
|
|
208
|
+
if (row.length > 0) {
|
|
209
|
+
rows.push(row);
|
|
210
|
+
row = [];
|
|
211
|
+
rowChars = 0;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
for (const b of this._buttons) {
|
|
216
|
+
if (b.__newRow) {
|
|
217
|
+
pushRow();
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const textLength = String(b.text || "").length;
|
|
221
|
+
if (
|
|
222
|
+
this.autoWrapMaxChars > 0 &&
|
|
223
|
+
row.length > 0 &&
|
|
224
|
+
rowChars + textLength > this.autoWrapMaxChars
|
|
225
|
+
) {
|
|
226
|
+
pushRow();
|
|
227
|
+
}
|
|
228
|
+
if (row.length >= this.buttonsPerRow) {
|
|
229
|
+
pushRow();
|
|
230
|
+
}
|
|
231
|
+
row.push(b);
|
|
232
|
+
rowChars += textLength;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
pushRow();
|
|
236
|
+
return rows;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ---------- final output ----------
|
|
240
|
+
/**
|
|
241
|
+
* Builds the final Telegram reply_markup object.
|
|
242
|
+
* @returns {object} Telegram inline_keyboard reply_markup.
|
|
243
|
+
*/
|
|
244
|
+
build() {
|
|
245
|
+
return {
|
|
246
|
+
reply_markup: {
|
|
247
|
+
inline_keyboard: this._layoutButtons()
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
178
251
|
}
|