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 CHANGED
@@ -1,187 +1,285 @@
1
- ### Inline Keyboard Builder
1
+ ![Logo](https://i.ibb.co/BKVnp8dZ/20260202-141042.png) [![npm version](https://img.shields.io/npm/v/telegram-inline-keyboard-builder?style=flat&logo=npm&logoColor=white&color=cb3837)](https://www.npmjs.com/package/telegram-inline-keyboard-builder) [![npm downloads](https://img.shields.io/npm/dw/telegram-inline-keyboard-builder?style=flat&logo=npm&logoColor=white&color=2CA5E0)](https://www.npmjs.com/package/telegram-inline-keyboard-builder) [![license](https://img.shields.io/npm/l/telegram-inline-keyboard-builder?style=flat&color=555555)](LICENSE) ![Telegram](https://img.shields.io/badge/Telegram-Inline%20Keyboard-2CA5E0?style=flat&logo=telegram&logoColor=white)
2
2
 
3
- Small inline button builder for Telegram, designed to be library-agnostic (Telegraf, node-telegram-bot-api, Aiogram, Pyrogram...).
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
- ## πŸš€ Key Features
35
+ > **Warning**: `icon_custom_emoji_id` only works if the bot owner has a Telegram premium subscription.
13
36
 
14
- - Fluent, chainable API
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
- - Core independent of any Telegram library
73
+ ### Results
74
+ ![Example results](https://i.ibb.co/1tgyYQCv/IMG-20260211-054905-332.jpg)
17
75
 
18
- - Easy-to-write adapters (Telegraf, node-telegram-bot-api, Aiogram...)
76
+ ---
77
+ ## other log
19
78
 
20
- - Auto-wrap / control of buttons per row
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
- If the package is published on npm:
29
-
30
- ```bash
93
+ ```bash
31
94
  npm install telegram-inline-keyboard-builder
32
95
  ```
33
- Then in your project:
34
-
96
+ ## importation
35
97
  ```js
36
- import { InlineKeyboardTelegraf } from "telegram-inline-keyboard-builder";
98
+ import { InlineKeyboardBuilder } from "telegram-inline-keyboard-builder";
37
99
  ```
38
100
 
39
- ---
101
+ ## 🧠 Core Concept
40
102
 
41
- ## Quick Concepts
103
+ Telegram inline keyboards follow **one universal schema**.
42
104
 
43
- - **Core**: InlineKeyboardBuilder (placement logic, chainable API). Does not know any Telegram library.
105
+ This builder:
44
106
 
45
- - **Adapter**: object responsible for creating buttons (callback/url/pay/custom) and producing the final structure (reply_markup/Markup depending on the library).
107
+ * generates the keyboard **directly in Telegram format**
46
108
 
47
- - **Facade Builder**: ready-to-use classes that inject an adapter (e.g., InlineKeyboardTelegraf, InlineKeyboardNodeTelegram).
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
- ## πŸ”§ Public Interface (API)
124
+ //Chainable Methods
54
125
 
55
- ### Constructors
56
- ```js
57
- // Core (internal)
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
- new InlineKeyboardBuilder(adapter, buttonsPerRow = 2, autoWrapMaxChars = 0)
135
+ // build
136
+ .build()
60
137
 
61
- // Facade (exposed to users, encapsulates core + adapter)
62
- new InlineKeyboardTelegraf({ buttonsPerRow = 2, autoWrapMaxChars = 0 })
63
- new InlineKeyboardNodeTelegram({ ... })
138
+ const keyboard = builder.build();
64
139
 
65
- Chainable Methods
140
+ // Always returns:
66
141
 
67
- .addCallbackButton(text, callback_data, hide = false)
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
- **Return of build()**: depends on the adapter.
144
+ Fully compliant with Telegram Bot API.
78
145
 
79
- **Telegraf adapte** β†’ Markup.inlineKeyboard(rows) (Markup object)
146
+ ## Usage Example (Telegraf)
80
147
 
81
- **Node‑Telegram adapt** β†’ { reply_markup: { inline_keyboard: rows } }
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
- ## 🧩 Usage Example (Telegraf)
164
+ bot.launch();
165
+
166
+ ```
167
+ ## Usage Example (node-telegram-bot-api)
88
168
 
89
169
  ```js
90
- import { Telegraf } from "telegraf";
91
- import { InlineKeyboardTelegraf } from "telegram-inline-keyboard-builder";
170
+ import TelegramBot from "node-telegram-bot-api";
92
171
 
93
- const bot = new Telegraf(process.env.BOT_TOKEN);
172
+ import { InlineKeyboardBuilder } from "telegram-inline-keyboard-builder";
94
173
 
95
- bot.start(ctx => {
96
- const keyboard = new InlineKeyboardTelegraf({ buttonsPerRow: 2, autoWrapMaxChars: 24 })
97
- .addCallbackButton("βœ… OK", "OK_ACTION")
98
- .addUrlButton("🌍 Site", "https://example.com")
99
- .newRow()
100
- .addCallbackButton("❌ Cancel", "CANCEL_ACTION")
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.launch();
181
+ bot.sendMessage(msg.chat.id, "Hello", keyboard); });
107
182
  ```
108
183
 
109
- ## 🧾 Usage Example (node-telegram-bot-api)
184
+ ## πŸ’³ Payment Buttons
110
185
 
111
- ```js
112
- import TelegramBot from "node-telegram-bot-api";
113
- import { InlineKeyboardNodeTelegram } from "telegram-inline-keyboard-builder";
186
+ ### ⚠️ Telegram limitation
114
187
 
115
- const bot = new TelegramBot(TOKEN, { polling: true });
188
+ > [!WARNING]
189
+ > Payment buttons must only be used with:
190
+ - sendInvoice
191
+ - replyWithInvoice
116
192
 
117
- bot.onText(/\/start/, (msg) => {
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
- ## πŸ› οΈ Writing an Adapter (expected interface)
199
+ Using a visible payment button outside invoices will cause Telegram API errors.
200
+
201
+ ## 🧯 Common Errors
131
202
 
132
- A simple adapter must expose:
203
+ **Telegram API error**
204
+
205
+ Make sure the keyboard object is passed directly:
133
206
 
134
207
  ```js
135
- // create a button
136
- adapter.createCallback(text, data, hide = false)
137
- adapter.createUrl(text, url, hide = false)
138
- adapter.createPay(text, hide = false)
208
+ const keyboard = new InlineKeyboardBuilder(1)
209
+ .addCallbackButton("Setting","show_setting")
210
+ .build()
211
+ // telegraf
212
+ ctx.reply("Text", keyboard);
139
213
 
140
- // build the final keyboard from rows (rows = array of array of buttons)
141
- adapter.buildKeyboard(rows)
214
+ // node telegram bot api
215
+ bot.sendMessage(chatId, "Text", keyboard);
142
216
 
143
- Minimal Example (node-telegram-bot-api)
217
+ // CORRECT βœ…
144
218
 
145
- export class NodeTelegramInlineAdapter {
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
- createUrl(text, url) {
151
- return { text, url };
152
- }
221
+ const keyboard = new InlineKeyboardBuilder(1)
222
+ .addCallbackButton("Setting","show_setting")
223
+ .build()
153
224
 
154
- createPay(text) {
155
- return { text, pay: true };
156
- }
225
+ // telegraf
226
+ ctx.reply("Text", {
227
+ reply_markup: keyboard.reply_makup, // inline keyboard
228
+ parse_mode: "HTML",
229
+ // ...
230
+ });
157
231
 
158
- buildKeyboard(rows) {
159
- return { reply_markup: { inline_keyboard: rows } };
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
- ## 🧯 Common Errors & Debug
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
- **ReferenceError**: Markup is not defined β†’ you are still using Markup in the core; move button creation into the adapter.
243
+ - **V2**: Here we **simply construct an object valid for all types of APIs** without **adapting** it.
174
244
 
175
- **Missing adapter** β†’ the core constructor must receive a valid adapter: new InlineKeyboardBuilder(adapter).
245
+ ## πŸ’œ Support This Project (Crypto)
176
246
 
177
- **Library not installed** (e.g., telegraf missing) β†’ adapter should detect and throw a clear error: npm install telegraf.
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
- ✍️ Contribution
262
+ ### πŸ”Ή Optional QR Codes for quick mobile donation
183
263
 
184
- Everyone are welcome. Open an issue to discuss a feature before implementing major changes.
264
+ **USDT (TRC20)**
265
+ ![USDT TRC20 QR](https://api.qrserver.com/v1/create-qr-code/?data=0x607c1430601989d43c9cd2eed9e516663e0bdd1f&size=150x150)
185
266
 
267
+ **USDC**
268
+ ![USDC QR](https://api.qrserver.com/v1/create-qr-code/?data=0x607c1430601989d43c9CD2eeD9E516663e0BdD1F&size=150x150)
186
269
 
187
- ---
270
+ **Ethereum (ETH)**
271
+ ![ETH QR](https://api.qrserver.com/v1/create-qr-code/?data=0x607c1430601989d43c9CD2eeD9E516663e0BdD1F&size=150x150)
272
+
273
+ **Bitcoin (BTC)**
274
+ ![BTC QR](https://api.qrserver.com/v1/create-qr-code/?data=bc1qmysepz6eerz2mqyx5dd0yy87c3gk6hccwla5x2&size=150x150)
275
+
276
+ **Tron (TRX)**
277
+ ![TRX QR](https://api.qrserver.com/v1/create-qr-code/?data=TE9RiTaDpx7DGZzCMw7qds51nzszKiyeR8&size=150x150)
278
+
279
+ **TON**
280
+ ![TON QR](https://api.qrserver.com/v1/create-qr-code/?data=UQA1NPW4GqgIVa9R6lebN_0v64Q-Sz_nHrmK9LCk-FfdjVOH&size=150x150)
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
- constructor(buttonsPerRow = 2, autoWrapMaxChars = 0) {
4
- this.buttonsPerRow = buttonsPerRow;
5
- this.autoWrapMaxChars = autoWrapMaxChars; // 0 = disabled
6
- this._buttons = []; // flat list of buttons + row markers
7
- }
8
-
9
- // ---------- internal ----------
10
- _pushButton(btn) {
11
- this._buttons.push(btn);
12
- return this;
13
- }
14
-
15
- // ---------- button helpers ----------
16
- addCallbackButton(text, callback_data) {
17
- if (!text || !callback_data) {
18
- throw new Error("Callback button requires text and callback_data");
19
- }
20
-
21
- return this._pushButton({
22
- text,
23
- callback_data,
24
- });
25
- }
26
-
27
- addUrlButton(text, url) {
28
- if (!text || !url) {
29
- throw new Error("URL button requires text and url");
30
- }
31
-
32
- return this._pushButton({
33
- text,
34
- url,
35
- });
36
- }
37
-
38
- addPayButton(text) {
39
- if (!text) {
40
- throw new Error("Pay button requires text");
41
- }
42
-
43
- return this._pushButton({
44
- text,
45
- pay: true,
46
- });
47
- }
48
-
49
- addCustomButton(buttonObject) {
50
- if (!buttonObject || !buttonObject.text) {
51
- throw new Error("Custom button must be a valid InlineKeyboardButton object");
52
- }
53
-
54
- return this._pushButton(buttonObject);
55
- }
56
-
57
- // ---------- layout controls ----------
58
- setButtonsPerRow(n) {
59
- this.buttonsPerRow = Math.max(1, Math.floor(n));
60
- return this;
61
- }
62
-
63
- setAutoWrapMaxChars(n) {
64
- this.autoWrapMaxChars = Math.max(0, Math.floor(n));
65
- return this;
66
- }
67
-
68
- newRow() {
69
- this._buttons.push({ __newRow: true });
70
- return this;
71
- }
72
-
73
- // ---------- config-based API ----------
74
- _addButtonFromConfig(btn) {
75
- const { type, text } = btn;
76
-
77
- if (!type || !text) {
78
- throw new Error("Button must have at least { type, text }");
79
- }
80
-
81
- switch (type) {
82
- case "callback":
83
- if (!btn.data) throw new Error("Callback button requires `data`");
84
- this.addCallbackButton(text, btn.data);
85
- break;
86
-
87
- case "url":
88
- if (!btn.url) throw new Error("URL button requires `url`");
89
- this.addUrlButton(text, btn.url);
90
- break;
91
-
92
- case "pay":
93
- this.addPayButton(text);
94
- break;
95
-
96
- case "custom":
97
- if (!btn.button) throw new Error("Custom button requires `button`");
98
- this.addCustomButton(btn.button);
99
- break;
100
-
101
- default:
102
- throw new Error(`Unknown button type: ${type}`);
103
- }
104
- }
105
-
106
- addButtons(config) {
107
- // Case 1: array of buttons
108
- if (Array.isArray(config)) {
109
- for (const btn of config) {
110
- this._addButtonFromConfig(btn);
111
- }
112
- return this;
113
- }
114
-
115
- // Case 2: grouped config
116
- const { type, buttons } = config;
117
- if (!type || !Array.isArray(buttons)) {
118
- throw new Error("addButtons: invalid config");
119
- }
120
-
121
- for (const btn of buttons) {
122
- this._addButtonFromConfig({ type, ...btn });
123
- }
124
-
125
- return this;
126
- }
127
-
128
- // ---------- layout engine ----------
129
- _layoutButtons() {
130
- const rows = [];
131
- let row = [];
132
- let rowChars = 0;
133
-
134
- const pushRow = () => {
135
- if (row.length > 0) {
136
- rows.push(row);
137
- row = [];
138
- rowChars = 0;
139
- }
140
- };
141
-
142
- for (const b of this._buttons) {
143
- if (b.__newRow) {
144
- pushRow();
145
- continue;
146
- }
147
-
148
- const textLength = String(b.text || "").length;
149
-
150
- if (
151
- this.autoWrapMaxChars > 0 &&
152
- row.length > 0 &&
153
- rowChars + textLength > this.autoWrapMaxChars
154
- ) {
155
- pushRow();
156
- }
157
-
158
- if (row.length >= this.buttonsPerRow) {
159
- pushRow();
160
- }
161
-
162
- row.push(b);
163
- rowChars += textLength;
164
- }
165
-
166
- pushRow();
167
- return rows;
168
- }
169
-
170
- // ---------- final output ----------
171
- build() {
172
- return {
173
- reply_markup: {
174
- inline_keyboard: this._layoutButtons(),
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "telegram-inline-keyboard-builder",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "Universal inline keyboard builder for Telegram APIs",
5
5
  "main": "core/InlineKeyboardBuilder.js",
6
6
  "type": "module",