telegix 1.1.2 → 1.1.3

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/lib/rich.js CHANGED
@@ -1,12 +1,1373 @@
1
1
  /**
2
- * Telegix - Rich Message & Draft Builder Suite
3
- * Modern structured rich messages, cards, drafts, and layout blocks for Telegram Bot API.
2
+ * Telegix - Rich Message & Draft Builder Suite (Telegram Bot API 10.3+)
3
+ * Modern structured rich messages, cards, drafts, tables, buttons, and layout blocks.
4
4
  * @module telegix/rich
5
5
  */
6
6
 
7
7
  import { escapeHtml, html } from './format.js';
8
8
  import { Markup } from './markup.js';
9
+ import { Table, InputRichBlockTable, RichBlockTable } from './table.js';
10
+ import { EphemeralMessageParameters } from './ephemeral.js';
9
11
 
12
+ export { Table, InputRichBlockTable, RichBlockTable } from './table.js';
13
+ export { EphemeralMessageParameters } from './ephemeral.js';
14
+
15
+ /**
16
+ * Telegram Bot API 10.3 RichMessageButton class
17
+ * Represents a button in a RichMessage
18
+ */
19
+ export class RichMessageButton {
20
+ /**
21
+ * @param {string} text
22
+ * @param {object} [options]
23
+ */
24
+ constructor(text, options = {}) {
25
+ this.text = String(text);
26
+ this.options = options;
27
+ }
28
+
29
+ /**
30
+ * Convert to Telegram button object
31
+ */
32
+ toJSON() {
33
+ return {
34
+ text: this.text,
35
+ ...this.options,
36
+ };
37
+ }
38
+
39
+ static url(text, url) {
40
+ return new RichMessageButton(text, { url });
41
+ }
42
+
43
+ static callback(text, data) {
44
+ return new RichMessageButton(text, { callback_data: String(data) });
45
+ }
46
+
47
+ static copyText(text, copyText) {
48
+ return new RichMessageButton(text, { copy_text: { text: String(copyText) } });
49
+ }
50
+
51
+ static webApp(text, url) {
52
+ return new RichMessageButton(text, { web_app: { url } });
53
+ }
54
+
55
+ static primary(text, dataOrUrl, options = {}) {
56
+ return new RichMessageButton(text, {
57
+ style: 'primary',
58
+ ...RichMessageButton._resolveDataOrUrl(dataOrUrl),
59
+ ...options,
60
+ });
61
+ }
62
+
63
+ static danger(text, dataOrUrl, options = {}) {
64
+ return new RichMessageButton(text, {
65
+ style: 'danger',
66
+ ...RichMessageButton._resolveDataOrUrl(dataOrUrl),
67
+ ...options,
68
+ });
69
+ }
70
+
71
+ static success(text, dataOrUrl, options = {}) {
72
+ return new RichMessageButton(text, {
73
+ style: 'success',
74
+ ...RichMessageButton._resolveDataOrUrl(dataOrUrl),
75
+ ...options,
76
+ });
77
+ }
78
+
79
+ static colored(text, style, dataOrUrl, options = {}) {
80
+ return new RichMessageButton(text, {
81
+ style,
82
+ ...RichMessageButton._resolveDataOrUrl(dataOrUrl),
83
+ ...options,
84
+ });
85
+ }
86
+
87
+ static disabled(text) {
88
+ return new RichMessageButton(text, { disabled: true });
89
+ }
90
+
91
+ static document(text, documentId) {
92
+ return new RichMessageButton(text, { url: `tg://document?id=${documentId}` });
93
+ }
94
+
95
+ static _resolveDataOrUrl(dataOrUrl) {
96
+ if (!dataOrUrl) return {};
97
+ if (typeof dataOrUrl === 'string') {
98
+ if (/^(https?:\/\/|tg:\/\/)/i.test(dataOrUrl)) {
99
+ return { url: dataOrUrl };
100
+ }
101
+ return { callback_data: dataOrUrl };
102
+ }
103
+ if (typeof dataOrUrl === 'number') {
104
+ return { callback_data: String(dataOrUrl) };
105
+ }
106
+ if (typeof dataOrUrl === 'object') {
107
+ return dataOrUrl;
108
+ }
109
+ return {};
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Telegram Bot API 10.3 RichTextButton class
115
+ * Represents an inline text button / link inside rich text or blocks
116
+ */
117
+ export class RichTextButton {
118
+ /**
119
+ * @param {string} text
120
+ * @param {object} [options]
121
+ */
122
+ constructor(text, options = {}) {
123
+ this.text = String(text);
124
+ this.options = options;
125
+ }
126
+
127
+ toJSON() {
128
+ return {
129
+ type: 'rich_text_button',
130
+ text: this.text,
131
+ ...this.options,
132
+ };
133
+ }
134
+
135
+ toHtml() {
136
+ if (this.options.url) {
137
+ return `<a href="${escapeHtml(this.options.url)}">${escapeHtml(this.text)}</a>`;
138
+ }
139
+ if (this.options.callback_data) {
140
+ return `<b>[${escapeHtml(this.text)}]</b>`;
141
+ }
142
+ return escapeHtml(this.text);
143
+ }
144
+
145
+ static url(text, url) {
146
+ return new RichTextButton(text, { url });
147
+ }
148
+
149
+ static callback(text, data) {
150
+ return new RichTextButton(text, { callback_data: String(data) });
151
+ }
152
+
153
+ static document(text, documentId) {
154
+ return new RichTextButton(text, { url: `tg://document?id=${documentId}` });
155
+ }
156
+
157
+ static user(text, userId) {
158
+ return new RichTextButton(text, { url: `tg://user?id=${userId}` });
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Telegram Bot API 10.3 InputRichBlockButtons class
164
+ * Represents a block containing button rows in a rich message
165
+ */
166
+ export class InputRichBlockButtons {
167
+ /**
168
+ * @param {Array<Array<object>>|Array<object>} [buttons=[]]
169
+ */
170
+ constructor(buttons = []) {
171
+ this.type = 'buttons';
172
+ this.buttons = Array.isArray(buttons) ? buttons : [buttons];
173
+ }
174
+
175
+ addRow(...buttons) {
176
+ this.buttons.push(buttons.flat());
177
+ return this;
178
+ }
179
+
180
+ addButton(button) {
181
+ if (this.buttons.length === 0) {
182
+ this.buttons.push([button]);
183
+ } else {
184
+ this.buttons[this.buttons.length - 1].push(button);
185
+ }
186
+ return this;
187
+ }
188
+
189
+ toJSON() {
190
+ return {
191
+ type: 'buttons',
192
+ buttons: this.buttons.map((row) =>
193
+ Array.isArray(row)
194
+ ? row.map((btn) => (typeof btn?.toJSON === 'function' ? btn.toJSON() : btn))
195
+ : typeof row?.toJSON === 'function'
196
+ ? row.toJSON()
197
+ : row
198
+ ),
199
+ };
200
+ }
201
+
202
+ toHtml() {
203
+ const lines = [];
204
+ for (const row of this.buttons) {
205
+ if (Array.isArray(row)) {
206
+ const rowTexts = row.map((btn) => {
207
+ const text = btn.text || String(btn);
208
+ if (btn.url) {
209
+ return `<a href="${escapeHtml(btn.url)}">${escapeHtml(text)}</a>`;
210
+ }
211
+ return `[${escapeHtml(text)}]`;
212
+ });
213
+ lines.push(rowTexts.join(' '));
214
+ }
215
+ }
216
+ return lines.join('\n');
217
+ }
218
+
219
+ static create(buttons) {
220
+ return new InputRichBlockButtons(buttons);
221
+ }
222
+ }
223
+
224
+ export const RichBlockButtons = InputRichBlockButtons;
225
+
226
+ /**
227
+ * Telegram Bot API 10.3 InputRichBlockExpandableBlockQuotation class
228
+ * Represents a block quotation, which can be expanded or collapsed back
229
+ */
230
+ export class InputRichBlockExpandableBlockQuotation {
231
+ /**
232
+ * @param {string} text
233
+ * @param {object} [options]
234
+ */
235
+ constructor(text, options = {}) {
236
+ this.type = 'expandable_block_quotation';
237
+ this.text = String(text || '');
238
+ this.options = options;
239
+ }
240
+
241
+ toJSON() {
242
+ return {
243
+ type: 'expandable_block_quotation',
244
+ text: this.text,
245
+ ...this.options,
246
+ };
247
+ }
248
+
249
+ toHtml() {
250
+ return `<blockquote expandable>${escapeHtml(this.text)}</blockquote>`;
251
+ }
252
+
253
+ static create(text, options) {
254
+ return new InputRichBlockExpandableBlockQuotation(text, options);
255
+ }
256
+ }
257
+
258
+ export const RichBlockExpandableBlockQuotation = InputRichBlockExpandableBlockQuotation;
259
+
260
+ /**
261
+ * Telegram Bot API 10.3 InputRichBlockDocument class
262
+ * Represents a block containing a file or document link
263
+ */
264
+ export class InputRichBlockDocument {
265
+ /**
266
+ * @param {string|object} document
267
+ * @param {string} [caption='']
268
+ * @param {object} [options={}]
269
+ */
270
+ constructor(document, caption = '', options = {}) {
271
+ this.type = 'document';
272
+ this.document = document;
273
+ this.caption = String(caption || '');
274
+ this.options = options;
275
+ }
276
+
277
+ toJSON() {
278
+ return {
279
+ type: 'document',
280
+ document: this.document,
281
+ caption: this.caption,
282
+ ...this.options,
283
+ };
284
+ }
285
+
286
+ toHtml() {
287
+ if (typeof this.document === 'string' && this.document.startsWith('tg://')) {
288
+ return `<a href="${escapeHtml(this.document)}">📄 ${escapeHtml(this.caption || 'Document')}</a>`;
289
+ }
290
+ return `📄 <b>Document:</b> ${escapeHtml(this.caption || String(this.document))}`;
291
+ }
292
+
293
+ static create(document, caption, options) {
294
+ return new InputRichBlockDocument(document, caption, options);
295
+ }
296
+ }
297
+
298
+ export const RichBlockDocument = InputRichBlockDocument;
299
+
300
+ /**
301
+ * Telegram Bot API 10.2 InputRichBlockParagraph class
302
+ * Represents a standard paragraph of rich text
303
+ */
304
+ export class InputRichBlockParagraph {
305
+ /**
306
+ * @param {string} text
307
+ * @param {object} [options={}]
308
+ */
309
+ constructor(text, options = {}) {
310
+ this.type = 'paragraph';
311
+ this.text = String(text ?? '');
312
+ this.options = options;
313
+ }
314
+
315
+ toJSON() {
316
+ return {
317
+ type: 'paragraph',
318
+ text: this.text,
319
+ ...this.options,
320
+ };
321
+ }
322
+
323
+ toHtml() {
324
+ return `<p>${escapeHtml(this.text)}</p>`;
325
+ }
326
+
327
+ static create(text, options) {
328
+ return new InputRichBlockParagraph(text, options);
329
+ }
330
+ }
331
+ export const RichBlockParagraph = InputRichBlockParagraph;
332
+
333
+ /**
334
+ * Telegram Bot API 10.2 InputRichBlockSectionHeading class
335
+ * Represents a section heading with level 1-6
336
+ */
337
+ export class InputRichBlockSectionHeading {
338
+ /**
339
+ * @param {string} text
340
+ * @param {number} [level=2]
341
+ * @param {object} [options={}]
342
+ */
343
+ constructor(text, level = 2, options = {}) {
344
+ this.type = 'section_heading';
345
+ this.text = String(text ?? '');
346
+ this.level = Math.max(1, Math.min(6, Number(level) || 2));
347
+ this.options = options;
348
+ }
349
+
350
+ toJSON() {
351
+ return {
352
+ type: 'section_heading',
353
+ text: this.text,
354
+ level: this.level,
355
+ ...this.options,
356
+ };
357
+ }
358
+
359
+ toHtml() {
360
+ return `<b>${escapeHtml(this.text)}</b>`;
361
+ }
362
+
363
+ static create(text, level, options) {
364
+ return new InputRichBlockSectionHeading(text, level, options);
365
+ }
366
+ }
367
+ export const RichBlockSectionHeading = InputRichBlockSectionHeading;
368
+
369
+ /**
370
+ * Telegram Bot API 10.2 InputRichBlockPreformatted class
371
+ * Represents preformatted code or text with optional programming language syntax
372
+ */
373
+ export class InputRichBlockPreformatted {
374
+ /**
375
+ * @param {string} text
376
+ * @param {string} [language='']
377
+ * @param {object} [options={}]
378
+ */
379
+ constructor(text, language = '', options = {}) {
380
+ this.type = 'preformatted';
381
+ this.text = String(text ?? '');
382
+ this.language = String(language || '');
383
+ this.options = options;
384
+ }
385
+
386
+ toJSON() {
387
+ return {
388
+ type: 'preformatted',
389
+ text: this.text,
390
+ ...(this.language ? { language: this.language } : {}),
391
+ ...this.options,
392
+ };
393
+ }
394
+
395
+ toHtml() {
396
+ if (this.language) {
397
+ return `<pre><code class="language-${escapeHtml(this.language)}">${escapeHtml(this.text)}</code></pre>`;
398
+ }
399
+ return `<pre>${escapeHtml(this.text)}</pre>`;
400
+ }
401
+
402
+ static create(text, language, options) {
403
+ return new InputRichBlockPreformatted(text, language, options);
404
+ }
405
+ }
406
+ export const RichBlockPreformatted = InputRichBlockPreformatted;
407
+
408
+ /**
409
+ * Telegram Bot API 10.2 InputRichBlockFooter class
410
+ * Represents small footer or timestamp text
411
+ */
412
+ export class InputRichBlockFooter {
413
+ /**
414
+ * @param {string} text
415
+ * @param {object} [options={}]
416
+ */
417
+ constructor(text, options = {}) {
418
+ this.type = 'footer';
419
+ this.text = String(text ?? '');
420
+ this.options = options;
421
+ }
422
+
423
+ toJSON() {
424
+ return {
425
+ type: 'footer',
426
+ text: this.text,
427
+ ...this.options,
428
+ };
429
+ }
430
+
431
+ toHtml() {
432
+ return `<i>${escapeHtml(this.text)}</i>`;
433
+ }
434
+
435
+ static create(text, options) {
436
+ return new InputRichBlockFooter(text, options);
437
+ }
438
+ }
439
+ export const RichBlockFooter = InputRichBlockFooter;
440
+
441
+ /**
442
+ * Telegram Bot API 10.2 InputRichBlockDivider class
443
+ * Represents a horizontal divider / separator
444
+ */
445
+ export class InputRichBlockDivider {
446
+ /**
447
+ * @param {object} [options={}]
448
+ */
449
+ constructor(options = {}) {
450
+ this.type = 'divider';
451
+ this.options = options;
452
+ }
453
+
454
+ toJSON() {
455
+ return {
456
+ type: 'divider',
457
+ ...this.options,
458
+ };
459
+ }
460
+
461
+ toHtml() {
462
+ return '──────────────────────────────';
463
+ }
464
+
465
+ static create(options) {
466
+ return new InputRichBlockDivider(options);
467
+ }
468
+ }
469
+ export const RichBlockDivider = InputRichBlockDivider;
470
+
471
+ /**
472
+ * Telegram Bot API 10.2 InputRichBlockMathematicalExpression class
473
+ * Represents LaTeX or mathematical expression
474
+ */
475
+ export class InputRichBlockMathematicalExpression {
476
+ /**
477
+ * @param {string} expression
478
+ * @param {object} [options={}]
479
+ */
480
+ constructor(expression, options = {}) {
481
+ this.type = 'mathematical_expression';
482
+ this.expression = String(expression ?? '');
483
+ this.options = options;
484
+ }
485
+
486
+ toJSON() {
487
+ return {
488
+ type: 'mathematical_expression',
489
+ expression: this.expression,
490
+ ...this.options,
491
+ };
492
+ }
493
+
494
+ toHtml() {
495
+ return `<tg-math>${escapeHtml(this.expression)}</tg-math>`;
496
+ }
497
+
498
+ static create(expression, options) {
499
+ return new InputRichBlockMathematicalExpression(expression, options);
500
+ }
501
+ }
502
+ export const RichBlockMathematicalExpression = InputRichBlockMathematicalExpression;
503
+ export const RichBlockMath = InputRichBlockMathematicalExpression;
504
+
505
+ /**
506
+ * Telegram Bot API 10.2 InputRichBlockAnchor class
507
+ * Represents an in-message anchor point for internal linking
508
+ */
509
+ export class InputRichBlockAnchor {
510
+ /**
511
+ * @param {string} name
512
+ * @param {string} [text='']
513
+ * @param {object} [options={}]
514
+ */
515
+ constructor(name, text = '', options = {}) {
516
+ this.type = 'anchor';
517
+ this.name = String(name ?? '');
518
+ this.text = String(text ?? '');
519
+ this.options = options;
520
+ }
521
+
522
+ toJSON() {
523
+ return {
524
+ type: 'anchor',
525
+ name: this.name,
526
+ ...(this.text ? { text: this.text } : {}),
527
+ ...this.options,
528
+ };
529
+ }
530
+
531
+ toHtml() {
532
+ return `<a name="${escapeHtml(this.name)}">${escapeHtml(this.text)}</a>`;
533
+ }
534
+
535
+ static create(name, text, options) {
536
+ return new InputRichBlockAnchor(name, text, options);
537
+ }
538
+ }
539
+ export const RichBlockAnchor = InputRichBlockAnchor;
540
+
541
+ /**
542
+ * Telegram Bot API 10.2 InputRichBlockListItem class
543
+ * Represents a single item inside an InputRichBlockList (bullet, numbered, or checklist)
544
+ */
545
+ export class InputRichBlockListItem {
546
+ /**
547
+ * @param {string} text
548
+ * @param {object} [options={}]
549
+ * @param {boolean|null} [options.is_checked] - True for checked checkbox, false for unchecked, null for bullet
550
+ * @param {number|null} [options.number] - Numeric index for numbered lists
551
+ * @param {string} [options.type] - Item type override
552
+ */
553
+ constructor(text, options = {}) {
554
+ this.text = String(text ?? '');
555
+ this.is_checked = options.is_checked ?? options.isChecked ?? null;
556
+ this.number = options.number !== undefined && options.number !== null ? Number(options.number) : null;
557
+ this.type = options.type ?? null;
558
+ this.options = options;
559
+ }
560
+
561
+ /**
562
+ * Check this item (for checklists)
563
+ * @param {boolean} [checked=true]
564
+ * @returns {this}
565
+ */
566
+ check(checked = true) {
567
+ this.is_checked = Boolean(checked);
568
+ return this;
569
+ }
570
+
571
+ /**
572
+ * Uncheck this item
573
+ * @returns {this}
574
+ */
575
+ uncheck() {
576
+ this.is_checked = false;
577
+ return this;
578
+ }
579
+
580
+ toJSON() {
581
+ const res = {
582
+ text: this.text,
583
+ };
584
+ if (this.is_checked !== null && this.is_checked !== undefined) {
585
+ res.is_checked = Boolean(this.is_checked);
586
+ }
587
+ if (this.number !== null && this.number !== undefined) {
588
+ res.number = Number(this.number);
589
+ }
590
+ if (this.type) {
591
+ res.type = this.type;
592
+ }
593
+ return res;
594
+ }
595
+
596
+ toHtml() {
597
+ if (this.is_checked === true) {
598
+ return `☑️ ${escapeHtml(this.text)}`;
599
+ }
600
+ if (this.is_checked === false) {
601
+ return `◻️ ${escapeHtml(this.text)}`;
602
+ }
603
+ if (this.number !== null) {
604
+ return `${this.number}. ${escapeHtml(this.text)}`;
605
+ }
606
+ return `• ${escapeHtml(this.text)}`;
607
+ }
608
+
609
+ static checked(text, options = {}) {
610
+ return new InputRichBlockListItem(text, { is_checked: true, ...options });
611
+ }
612
+
613
+ static unchecked(text, options = {}) {
614
+ return new InputRichBlockListItem(text, { is_checked: false, ...options });
615
+ }
616
+
617
+ static bullet(text, options = {}) {
618
+ return new InputRichBlockListItem(text, options);
619
+ }
620
+
621
+ static numbered(number, text, options = {}) {
622
+ return new InputRichBlockListItem(text, { number, ...options });
623
+ }
624
+ }
625
+ export const RichBlockListItem = InputRichBlockListItem;
626
+
627
+ /**
628
+ * Telegram Bot API 10.2 InputRichBlockList class
629
+ * Represents ordered, unordered, or checklist collections
630
+ */
631
+ export class InputRichBlockList {
632
+ /**
633
+ * @param {Array<InputRichBlockListItem|string|object>} [items=[]]
634
+ * @param {object} [options={}]
635
+ * @param {boolean} [options.ordered=false]
636
+ */
637
+ constructor(items = [], options = {}) {
638
+ this.type = 'list';
639
+ this.ordered = Boolean(options.ordered);
640
+ this.options = options;
641
+ this.items = [];
642
+
643
+ for (let i = 0; i < items.length; i++) {
644
+ const item = items[i];
645
+ if (item instanceof InputRichBlockListItem) {
646
+ this.items.push(item);
647
+ } else if (typeof item === 'object' && item !== null) {
648
+ const itemNumber = this.ordered && item.number === undefined ? i + 1 : item.number;
649
+ this.items.push(new InputRichBlockListItem(item.text ?? item.title ?? '', { number: itemNumber, ...item }));
650
+ } else {
651
+ const itemNumber = this.ordered ? i + 1 : null;
652
+ this.items.push(new InputRichBlockListItem(String(item), { number: itemNumber }));
653
+ }
654
+ }
655
+ }
656
+
657
+ /**
658
+ * Add an item to the list
659
+ * @param {InputRichBlockListItem|string|object} item
660
+ * @returns {this}
661
+ */
662
+ addItem(item) {
663
+ if (item instanceof InputRichBlockListItem) {
664
+ this.items.push(item);
665
+ } else if (typeof item === 'object' && item !== null) {
666
+ const itemNumber = this.ordered && item.number === undefined ? this.items.length + 1 : item.number;
667
+ this.items.push(new InputRichBlockListItem(item.text ?? item.title ?? '', { number: itemNumber, ...item }));
668
+ } else {
669
+ const itemNumber = this.ordered ? this.items.length + 1 : null;
670
+ this.items.push(new InputRichBlockListItem(String(item), { number: itemNumber }));
671
+ }
672
+ return this;
673
+ }
674
+
675
+ toJSON() {
676
+ return {
677
+ type: 'list',
678
+ items: this.items.map((it) => (typeof it.toJSON === 'function' ? it.toJSON() : it)),
679
+ ...(this.ordered ? { ordered: true } : {}),
680
+ ...this.options,
681
+ };
682
+ }
683
+
684
+ toHtml() {
685
+ return this.items.map((it) => (typeof it.toHtml === 'function' ? it.toHtml() : `• ${escapeHtml(it.text || String(it))}`)).join('\n');
686
+ }
687
+
688
+ static create(items, options) {
689
+ return new InputRichBlockList(items, options);
690
+ }
691
+
692
+ static ordered(items, options = {}) {
693
+ return new InputRichBlockList(items, { ordered: true, ...options });
694
+ }
695
+
696
+ static checklist(items, options = {}) {
697
+ const listItems = items.map((it) => {
698
+ if (typeof it === 'object' && it !== null && it.is_checked !== undefined) {
699
+ return it;
700
+ }
701
+ return { text: String(it), is_checked: false };
702
+ });
703
+ return new InputRichBlockList(listItems, options);
704
+ }
705
+ }
706
+ export const RichBlockList = InputRichBlockList;
707
+
708
+ /**
709
+ * Telegram Bot API 10.2 InputRichBlockChecklist
710
+ */
711
+ export class InputRichBlockChecklist extends InputRichBlockList {
712
+ constructor(items = [], options = {}) {
713
+ super(items, { checklist: true, ...options });
714
+ this.type = 'checklist';
715
+ }
716
+
717
+ toJSON() {
718
+ return {
719
+ type: 'checklist',
720
+ items: this.items.map((it) => (typeof it.toJSON === 'function' ? it.toJSON() : it)),
721
+ ...this.options,
722
+ };
723
+ }
724
+
725
+ toHtml() {
726
+ return this.items.map((it) => {
727
+ const isChecked = typeof it === 'object' && it !== null && (it.is_checked || it.checked);
728
+ const mark = isChecked ? '☑️' : '◻️';
729
+ const text = typeof it === 'object' && it !== null ? (it.text || String(it)) : String(it);
730
+ return `${mark} ${escapeHtml(text)}`;
731
+ }).join('\n');
732
+ }
733
+
734
+ static create(items, options) {
735
+ return new InputRichBlockChecklist(items, options);
736
+ }
737
+ }
738
+ export const RichBlockChecklist = InputRichBlockChecklist;
739
+
740
+ /**
741
+ * Telegram Bot API 10.2 InputRichBlockBlockQuotation class
742
+ * Standard block quote block
743
+ */
744
+ export class InputRichBlockBlockQuotation {
745
+ /**
746
+ * @param {string} text
747
+ * @param {object} [options={}]
748
+ */
749
+ constructor(text, options = {}) {
750
+ this.type = 'block_quotation';
751
+ this.text = String(text ?? '');
752
+ this.options = options;
753
+ }
754
+
755
+ toJSON() {
756
+ return {
757
+ type: 'block_quotation',
758
+ text: this.text,
759
+ ...this.options,
760
+ };
761
+ }
762
+
763
+ toHtml() {
764
+ return `<blockquote>${escapeHtml(this.text)}</blockquote>`;
765
+ }
766
+
767
+ static create(text, options) {
768
+ return new InputRichBlockBlockQuotation(text, options);
769
+ }
770
+ }
771
+ export const RichBlockBlockQuotation = InputRichBlockBlockQuotation;
772
+
773
+ /**
774
+ * Telegram Bot API 10.2 InputRichBlockPullQuotation class
775
+ * Pull quote with center emphasis
776
+ */
777
+ export class InputRichBlockPullQuotation {
778
+ /**
779
+ * @param {string} text
780
+ * @param {object} [options={}]
781
+ */
782
+ constructor(text, options = {}) {
783
+ this.type = 'pull_quotation';
784
+ this.text = String(text ?? '');
785
+ this.options = options;
786
+ }
787
+
788
+ toJSON() {
789
+ return {
790
+ type: 'pull_quotation',
791
+ text: this.text,
792
+ ...this.options,
793
+ };
794
+ }
795
+
796
+ toHtml() {
797
+ return `<tg-pullquote>${escapeHtml(this.text)}</tg-pullquote>`;
798
+ }
799
+
800
+ static create(text, options) {
801
+ return new InputRichBlockPullQuotation(text, options);
802
+ }
803
+ }
804
+ export const RichBlockPullQuotation = InputRichBlockPullQuotation;
805
+ export const RichBlockPullQuote = InputRichBlockPullQuotation;
806
+
807
+ /**
808
+ * Telegram Bot API 10.2 InputRichBlockCollage class
809
+ * Represents a photo/media collage block
810
+ */
811
+ export class InputRichBlockCollage {
812
+ /**
813
+ * @param {Array<object|string>} [media=[]]
814
+ * @param {object} [options={}]
815
+ */
816
+ constructor(media = [], options = {}) {
817
+ this.type = 'collage';
818
+ this.media = Array.isArray(media) ? media : [media];
819
+ this.options = options;
820
+ }
821
+
822
+ toJSON() {
823
+ return {
824
+ type: 'collage',
825
+ media: this.media.map((m) => (typeof m?.toJSON === 'function' ? m.toJSON() : m)),
826
+ ...this.options,
827
+ };
828
+ }
829
+
830
+ toHtml() {
831
+ return `🖼️ [Collage: ${this.media.length} media items]`;
832
+ }
833
+
834
+ static create(media, options) {
835
+ return new InputRichBlockCollage(media, options);
836
+ }
837
+ }
838
+ export const RichBlockCollage = InputRichBlockCollage;
839
+
840
+ /**
841
+ * Telegram Bot API 10.2 InputRichBlockSlideshow class
842
+ * Represents a slideshow media block
843
+ */
844
+ export class InputRichBlockSlideshow {
845
+ /**
846
+ * @param {Array<object|string>} [media=[]]
847
+ * @param {object} [options={}]
848
+ */
849
+ constructor(media = [], options = {}) {
850
+ this.type = 'slideshow';
851
+ this.media = Array.isArray(media) ? media : [media];
852
+ this.options = options;
853
+ }
854
+
855
+ toJSON() {
856
+ return {
857
+ type: 'slideshow',
858
+ media: this.media.map((m) => (typeof m?.toJSON === 'function' ? m.toJSON() : m)),
859
+ ...this.options,
860
+ };
861
+ }
862
+
863
+ toHtml() {
864
+ return `🎞️ [Slideshow: ${this.media.length} items]`;
865
+ }
866
+
867
+ static create(media, options) {
868
+ return new InputRichBlockSlideshow(media, options);
869
+ }
870
+ }
871
+ export const RichBlockSlideshow = InputRichBlockSlideshow;
872
+
873
+ /**
874
+ * Telegram Bot API 10.2 InputRichBlockDetails class
875
+ * Represents a collapsible disclosure widget (summary and content)
876
+ */
877
+ export class InputRichBlockDetails {
878
+ /**
879
+ * @param {string} title
880
+ * @param {string|Array<object>} [content='']
881
+ * @param {object} [options={}]
882
+ */
883
+ constructor(title, content = '', options = {}) {
884
+ this.type = 'details';
885
+ this.title = String(title ?? '');
886
+ this.content = content;
887
+ this.is_open = Boolean(options.is_open ?? options.isOpen);
888
+ this.options = options;
889
+ }
890
+
891
+ toJSON() {
892
+ return {
893
+ type: 'details',
894
+ title: this.title,
895
+ content: typeof this.content === 'object' && this.content !== null && typeof this.content.toJSON === 'function'
896
+ ? this.content.toJSON()
897
+ : this.content,
898
+ is_open: this.is_open,
899
+ ...this.options,
900
+ };
901
+ }
902
+
903
+ toHtml() {
904
+ const body = typeof this.content === 'string'
905
+ ? escapeHtml(this.content)
906
+ : Array.isArray(this.content)
907
+ ? this.content.map((c) => (typeof c?.toHtml === 'function' ? c.toHtml() : String(c))).join('\n')
908
+ : '';
909
+ return `<details${this.is_open ? ' open' : ''}><summary>${escapeHtml(this.title)}</summary>${body}</details>`;
910
+ }
911
+
912
+ static create(title, content, options) {
913
+ return new InputRichBlockDetails(title, content, options);
914
+ }
915
+ }
916
+ export const RichBlockDetails = InputRichBlockDetails;
917
+
918
+ /**
919
+ * Telegram Bot API 10.2 InputRichBlockMap class
920
+ * Represents an embedded geographical location card with coordinates
921
+ */
922
+ export class InputRichBlockMap {
923
+ /**
924
+ * @param {number} latitude
925
+ * @param {number} longitude
926
+ * @param {object} [options={}]
927
+ */
928
+ constructor(latitude, longitude, options = {}) {
929
+ this.type = 'map';
930
+ this.latitude = Number(latitude);
931
+ this.longitude = Number(longitude);
932
+ this.title = options.title || '';
933
+ this.options = options;
934
+ }
935
+
936
+ toJSON() {
937
+ return {
938
+ type: 'map',
939
+ latitude: this.latitude,
940
+ longitude: this.longitude,
941
+ ...(this.title ? { title: this.title } : {}),
942
+ ...this.options,
943
+ };
944
+ }
945
+
946
+ toHtml() {
947
+ return `📍 <b>${escapeHtml(this.title || 'Location')}</b> (${this.latitude.toFixed(4)}, ${this.longitude.toFixed(4)})`;
948
+ }
949
+
950
+ static create(latitude, longitude, options) {
951
+ return new InputRichBlockMap(latitude, longitude, options);
952
+ }
953
+ }
954
+ export const RichBlockMap = InputRichBlockMap;
955
+
956
+ /**
957
+ * Telegram Bot API 10.2 InputRichBlockAnimation class
958
+ * Represents an animation or GIF block
959
+ */
960
+ export class InputRichBlockAnimation {
961
+ /**
962
+ * @param {string} animation
963
+ * @param {object} [options={}]
964
+ */
965
+ constructor(animation, options = {}) {
966
+ this.type = 'animation';
967
+ this.animation = animation;
968
+ this.caption = options.caption || '';
969
+ this.options = options;
970
+ }
971
+
972
+ toJSON() {
973
+ return {
974
+ type: 'animation',
975
+ animation: this.animation,
976
+ ...(this.caption ? { caption: this.caption } : {}),
977
+ ...this.options,
978
+ };
979
+ }
980
+
981
+ toHtml() {
982
+ return `🎬 <b>[Animation]</b> ${escapeHtml(this.caption || '')}`;
983
+ }
984
+
985
+ static create(animation, options) {
986
+ return new InputRichBlockAnimation(animation, options);
987
+ }
988
+ }
989
+ export const RichBlockAnimation = InputRichBlockAnimation;
990
+
991
+ /**
992
+ * Telegram Bot API 10.2 InputRichBlockAudio class
993
+ * Represents an audio track block
994
+ */
995
+ export class InputRichBlockAudio {
996
+ /**
997
+ * @param {string} audio
998
+ * @param {object} [options={}]
999
+ */
1000
+ constructor(audio, options = {}) {
1001
+ this.type = 'audio';
1002
+ this.audio = audio;
1003
+ this.title = options.title || '';
1004
+ this.performer = options.performer || '';
1005
+ this.duration = options.duration;
1006
+ this.options = options;
1007
+ }
1008
+
1009
+ toJSON() {
1010
+ return {
1011
+ type: 'audio',
1012
+ audio: this.audio,
1013
+ ...(this.title ? { title: this.title } : {}),
1014
+ ...(this.performer ? { performer: this.performer } : {}),
1015
+ ...(this.duration !== undefined ? { duration: Number(this.duration) } : {}),
1016
+ ...this.options,
1017
+ };
1018
+ }
1019
+
1020
+ toHtml() {
1021
+ const titleStr = this.performer ? `${this.performer} - ${this.title}` : (this.title || 'Audio');
1022
+ return `🎵 <b>${escapeHtml(titleStr)}</b>`;
1023
+ }
1024
+
1025
+ static create(audio, options) {
1026
+ return new InputRichBlockAudio(audio, options);
1027
+ }
1028
+ }
1029
+ export const RichBlockAudio = InputRichBlockAudio;
1030
+
1031
+ /**
1032
+ * Telegram Bot API 10.2 InputRichBlockPhoto class
1033
+ * Represents a photo block
1034
+ */
1035
+ export class InputRichBlockPhoto {
1036
+ /**
1037
+ * @param {string} photo
1038
+ * @param {string} [caption='']
1039
+ * @param {object} [options={}]
1040
+ */
1041
+ constructor(photo, caption = '', options = {}) {
1042
+ this.type = 'photo';
1043
+ this.photo = photo;
1044
+ this.caption = String(caption || options.caption || '');
1045
+ this.options = options;
1046
+ }
1047
+
1048
+ toJSON() {
1049
+ return {
1050
+ type: 'photo',
1051
+ photo: this.photo,
1052
+ ...(this.caption ? { caption: this.caption } : {}),
1053
+ ...this.options,
1054
+ };
1055
+ }
1056
+
1057
+ toHtml() {
1058
+ return `🖼️ <b>[Photo]</b> ${escapeHtml(this.caption || '')}`;
1059
+ }
1060
+
1061
+ static create(photo, caption, options) {
1062
+ return new InputRichBlockPhoto(photo, caption, options);
1063
+ }
1064
+ }
1065
+ export const RichBlockPhoto = InputRichBlockPhoto;
1066
+
1067
+ /**
1068
+ * Telegram Bot API 10.2 InputRichBlockVideo class
1069
+ * Represents a video block
1070
+ */
1071
+ export class InputRichBlockVideo {
1072
+ /**
1073
+ * @param {string} video
1074
+ * @param {object} [options={}]
1075
+ */
1076
+ constructor(video, options = {}) {
1077
+ this.type = 'video';
1078
+ this.video = video;
1079
+ this.caption = options.caption || '';
1080
+ this.duration = options.duration;
1081
+ this.options = options;
1082
+ }
1083
+
1084
+ toJSON() {
1085
+ return {
1086
+ type: 'video',
1087
+ video: this.video,
1088
+ ...(this.caption ? { caption: this.caption } : {}),
1089
+ ...(this.duration !== undefined ? { duration: Number(this.duration) } : {}),
1090
+ ...this.options,
1091
+ };
1092
+ }
1093
+
1094
+ toHtml() {
1095
+ return `🎥 <b>[Video]</b> ${escapeHtml(this.caption || '')}`;
1096
+ }
1097
+
1098
+ static create(video, options) {
1099
+ return new InputRichBlockVideo(video, options);
1100
+ }
1101
+ }
1102
+ export const RichBlockVideo = InputRichBlockVideo;
1103
+
1104
+ /**
1105
+ * Telegram Bot API 10.2 InputRichBlockVoiceNote class
1106
+ * Represents a voice note block
1107
+ */
1108
+ export class InputRichBlockVoiceNote {
1109
+ /**
1110
+ * @param {string} voiceNote
1111
+ * @param {object} [options={}]
1112
+ */
1113
+ constructor(voiceNote, options = {}) {
1114
+ this.type = 'voice_note';
1115
+ this.voice_note = voiceNote;
1116
+ this.caption = options.caption || '';
1117
+ this.duration = options.duration;
1118
+ this.options = options;
1119
+ }
1120
+
1121
+ toJSON() {
1122
+ return {
1123
+ type: 'voice_note',
1124
+ voice_note: this.voice_note,
1125
+ ...(this.caption ? { caption: this.caption } : {}),
1126
+ ...(this.duration !== undefined ? { duration: Number(this.duration) } : {}),
1127
+ ...this.options,
1128
+ };
1129
+ }
1130
+
1131
+ toHtml() {
1132
+ return `🎤 <b>[Voice Note]</b> ${escapeHtml(this.caption || '')}`;
1133
+ }
1134
+
1135
+ static create(voiceNote, options) {
1136
+ return new InputRichBlockVoiceNote(voiceNote, options);
1137
+ }
1138
+ }
1139
+ export const RichBlockVoiceNote = InputRichBlockVoiceNote;
1140
+
1141
+ /**
1142
+ * Telegram Bot API 10.2 InputRichBlockThinking class
1143
+ * Represents an AI thinking / processing indicator block
1144
+ */
1145
+ export class InputRichBlockThinking {
1146
+ /**
1147
+ * @param {string} [text='Thinking...']
1148
+ * @param {object} [options={}]
1149
+ */
1150
+ constructor(text = 'Thinking...', options = {}) {
1151
+ this.type = 'thinking';
1152
+ this.text = String(text ?? 'Thinking...');
1153
+ this.options = options;
1154
+ }
1155
+
1156
+ toJSON() {
1157
+ return {
1158
+ type: 'thinking',
1159
+ text: this.text,
1160
+ ...this.options,
1161
+ };
1162
+ }
1163
+
1164
+ toHtml() {
1165
+ return `<tg-thinking>${escapeHtml(this.text)}</tg-thinking>`;
1166
+ }
1167
+
1168
+ static create(text, options) {
1169
+ return new InputRichBlockThinking(text, options);
1170
+ }
1171
+ }
1172
+ export const RichBlockThinking = InputRichBlockThinking;
1173
+
1174
+ /**
1175
+ * Telegram Bot API 10.2 InputRichMessageMedia class
1176
+ * Represents a media attachment in rich messages
1177
+ */
1178
+ export class InputRichMessageMedia {
1179
+ /**
1180
+ * @param {string} media
1181
+ * @param {string} [type='photo']
1182
+ * @param {object} [options={}]
1183
+ */
1184
+ constructor(media, type = 'photo', options = {}) {
1185
+ if (typeof media === 'object' && media !== null) {
1186
+ this.media = media.media;
1187
+ this.type = media.type || type || 'photo';
1188
+ this.caption = media.caption || options.caption || '';
1189
+ this.parse_mode = media.parse_mode || media.parseMode || options.parse_mode;
1190
+ this.show_caption_above_media = media.show_caption_above_media ?? media.showCaptionAboveMedia ?? options.show_caption_above_media;
1191
+ this.has_spoiler = media.has_spoiler ?? media.hasSpoiler ?? options.has_spoiler;
1192
+ this.width = media.width ?? options.width;
1193
+ this.height = media.height ?? options.height;
1194
+ this.duration = media.duration ?? options.duration;
1195
+ this.performer = media.performer ?? options.performer;
1196
+ this.title = media.title ?? options.title;
1197
+ this.thumbnail = media.thumbnail ?? options.thumbnail;
1198
+ this.options = { ...options, ...media };
1199
+ } else {
1200
+ this.media = media;
1201
+ this.type = type;
1202
+ this.caption = options.caption || '';
1203
+ this.parse_mode = options.parse_mode || options.parseMode;
1204
+ this.show_caption_above_media = options.show_caption_above_media ?? options.showCaptionAboveMedia;
1205
+ this.has_spoiler = options.has_spoiler ?? options.hasSpoiler;
1206
+ this.width = options.width;
1207
+ this.height = options.height;
1208
+ this.duration = options.duration;
1209
+ this.performer = options.performer;
1210
+ this.title = options.title;
1211
+ this.thumbnail = options.thumbnail;
1212
+ this.options = options;
1213
+ }
1214
+ }
1215
+
1216
+ toJSON() {
1217
+ const res = {
1218
+ type: this.type,
1219
+ media: this.media,
1220
+ };
1221
+ if (this.caption) res.caption = this.caption;
1222
+ if (this.parse_mode) res.parse_mode = this.parse_mode;
1223
+ if (this.show_caption_above_media !== undefined) res.show_caption_above_media = Boolean(this.show_caption_above_media);
1224
+ if (this.has_spoiler !== undefined) res.has_spoiler = Boolean(this.has_spoiler);
1225
+ if (this.width !== undefined) res.width = Number(this.width);
1226
+ if (this.height !== undefined) res.height = Number(this.height);
1227
+ if (this.duration !== undefined) res.duration = Number(this.duration);
1228
+ if (this.performer) res.performer = this.performer;
1229
+ if (this.title) res.title = this.title;
1230
+ if (this.thumbnail) res.thumbnail = this.thumbnail;
1231
+ return res;
1232
+ }
1233
+
1234
+ static photo(media, caption = '', options = {}) {
1235
+ return new InputRichMessageMedia(media, 'photo', { caption, ...options });
1236
+ }
1237
+
1238
+ static video(media, caption = '', options = {}) {
1239
+ return new InputRichMessageMedia(media, 'video', { caption, ...options });
1240
+ }
1241
+
1242
+ static animation(media, caption = '', options = {}) {
1243
+ return new InputRichMessageMedia(media, 'animation', { caption, ...options });
1244
+ }
1245
+
1246
+ static audio(media, caption = '', options = {}) {
1247
+ return new InputRichMessageMedia(media, 'audio', { caption, ...options });
1248
+ }
1249
+
1250
+ static document(media, caption = '', options = {}) {
1251
+ return new InputRichMessageMedia(media, 'document', { caption, ...options });
1252
+ }
1253
+
1254
+ static voiceNote(media, caption = '', options = {}) {
1255
+ return new InputRichMessageMedia(media, 'voice_note', { caption, ...options });
1256
+ }
1257
+ }
1258
+ export const RichMessageMedia = InputRichMessageMedia;
1259
+
1260
+ /**
1261
+ * Telegram Bot API 10.2 InputMediaVoiceNote class
1262
+ * Represents a voice note to be sent in media methods
1263
+ */
1264
+ export class InputMediaVoiceNote {
1265
+ /**
1266
+ * @param {string} media
1267
+ * @param {object} [options={}]
1268
+ */
1269
+ constructor(media, options = {}) {
1270
+ this.type = 'voice';
1271
+ this.media = media;
1272
+ this.caption = options.caption || '';
1273
+ this.parse_mode = options.parse_mode || options.parseMode;
1274
+ this.duration = options.duration;
1275
+ this.options = options;
1276
+ }
1277
+
1278
+ toJSON() {
1279
+ const res = {
1280
+ type: this.type,
1281
+ media: this.media,
1282
+ };
1283
+ if (this.caption) res.caption = this.caption;
1284
+ if (this.parse_mode) res.parse_mode = this.parse_mode;
1285
+ if (this.duration !== undefined) res.duration = Number(this.duration);
1286
+ return res;
1287
+ }
1288
+
1289
+ static create(media, options = {}) {
1290
+ return new InputMediaVoiceNote(media, options);
1291
+ }
1292
+ }
1293
+ export const MediaVoiceNote = InputMediaVoiceNote;
1294
+
1295
+ /**
1296
+ * Telegram Bot API 10.2 InputRichMessage class
1297
+ * Root container class for structured rich messages
1298
+ */
1299
+ export class InputRichMessage {
1300
+ /**
1301
+ * @param {object|string} [options={}]
1302
+ */
1303
+ constructor(options = {}) {
1304
+ if (typeof options === 'string') {
1305
+ this.text = options;
1306
+ this.blocks = [];
1307
+ this.media = [];
1308
+ this.is_rtl = false;
1309
+ } else {
1310
+ this.text = options.text || options.html || '';
1311
+ this.blocks = options.blocks ? [...options.blocks] : [];
1312
+ this.media = options.media ? [...options.media] : [];
1313
+ this.is_rtl = Boolean(options.is_rtl ?? options.isRtl);
1314
+ this.draft_id = options.draft_id ?? options.draftId;
1315
+ this.ephemeral_message_parameters = options.ephemeral_message_parameters ?? options.ephemeral;
1316
+ this.reply_markup = options.reply_markup;
1317
+ }
1318
+ }
1319
+
1320
+ /**
1321
+ * Add a block to the rich message
1322
+ * @param {object} block
1323
+ * @returns {this}
1324
+ */
1325
+ addBlock(block) {
1326
+ this.blocks.push(block);
1327
+ return this;
1328
+ }
1329
+
1330
+ /**
1331
+ * Add a media attachment
1332
+ * @param {InputRichMessageMedia|object} mediaItem
1333
+ * @returns {this}
1334
+ */
1335
+ addMedia(mediaItem) {
1336
+ this.media.push(mediaItem);
1337
+ return this;
1338
+ }
1339
+
1340
+ /**
1341
+ * Set right-to-left text direction
1342
+ * @param {boolean} [rtl=true]
1343
+ * @returns {this}
1344
+ */
1345
+ setRtl(rtl = true) {
1346
+ this.is_rtl = Boolean(rtl);
1347
+ return this;
1348
+ }
1349
+
1350
+ toJSON() {
1351
+ return {
1352
+ text: this.text,
1353
+ blocks: this.blocks.map((b) => (typeof b?.toJSON === 'function' ? b.toJSON() : b)),
1354
+ media: this.media.map((m) => (typeof m?.toJSON === 'function' ? m.toJSON() : m)),
1355
+ ...(this.is_rtl ? { is_rtl: true } : {}),
1356
+ ...(this.draft_id !== undefined ? { draft_id: this.draft_id } : {}),
1357
+ ...(this.ephemeral_message_parameters ? { ephemeral_message_parameters: this.ephemeral_message_parameters } : {}),
1358
+ ...(this.reply_markup ? { reply_markup: this.reply_markup } : {}),
1359
+ };
1360
+ }
1361
+
1362
+ static create(options) {
1363
+ return new InputRichMessage(options);
1364
+ }
1365
+ }
1366
+
1367
+
1368
+ /**
1369
+ * Comprehensive Rich Message Builder
1370
+ */
10
1371
  export class RichMessageBuilder {
11
1372
  constructor(initialText = '') {
12
1373
  this.blocks = [];
@@ -15,232 +1376,501 @@ export class RichMessageBuilder {
15
1376
  this._inlineKeyboard = [];
16
1377
  this._draftId = null;
17
1378
  this._ephemeral = null;
18
- this._media = null;
1379
+ this._media = [];
1380
+ this._isRtl = false;
19
1381
  this._extra = {};
20
1382
  }
21
1383
 
22
1384
  /**
23
- * Set parse mode ('HTML', 'MarkdownV2', etc.)
24
- * @param {string} mode
1385
+ * Set parse mode ('HTML', 'MarkdownV2', etc.)
1386
+ * @param {string} mode
1387
+ * @returns {this}
1388
+ */
1389
+ parseMode(mode) {
1390
+ this._parseMode = mode;
1391
+ return this;
1392
+ }
1393
+
1394
+ /**
1395
+ * Set primary text
1396
+ * @param {string} text
1397
+ * @returns {this}
1398
+ */
1399
+ text(text) {
1400
+ this._text = String(text);
1401
+ return this;
1402
+ }
1403
+
1404
+ /**
1405
+ * Add a header block with optional emoji
1406
+ * @param {string} text
1407
+ * @param {string} [emoji]
1408
+ * @returns {this}
1409
+ */
1410
+ header(text, emoji = '') {
1411
+ const formatted = emoji ? `${emoji} ${text}` : text;
1412
+ this.blocks.push({
1413
+ type: 'header',
1414
+ content: formatted,
1415
+ rawHtml: `<b>${escapeHtml(formatted)}</b>`,
1416
+ });
1417
+ return this;
1418
+ }
1419
+
1420
+ /**
1421
+ * Add a paragraph block
1422
+ * @param {string} text
1423
+ * @returns {this}
1424
+ */
1425
+ paragraph(text) {
1426
+ this.blocks.push({
1427
+ type: 'paragraph',
1428
+ content: text,
1429
+ rawHtml: escapeHtml(text),
1430
+ });
1431
+ return this;
1432
+ }
1433
+
1434
+ /**
1435
+ * Add bold text block
1436
+ * @param {string} text
1437
+ * @returns {this}
1438
+ */
1439
+ bold(text) {
1440
+ this.blocks.push({
1441
+ type: 'bold',
1442
+ content: text,
1443
+ rawHtml: `<b>${escapeHtml(text)}</b>`,
1444
+ });
1445
+ return this;
1446
+ }
1447
+
1448
+ /**
1449
+ * Add italic text block
1450
+ * @param {string} text
1451
+ * @returns {this}
1452
+ */
1453
+ italic(text) {
1454
+ this.blocks.push({
1455
+ type: 'italic',
1456
+ content: text,
1457
+ rawHtml: `<i>${escapeHtml(text)}</i>`,
1458
+ });
1459
+ return this;
1460
+ }
1461
+
1462
+ /**
1463
+ * Add underline text block
1464
+ * @param {string} text
1465
+ * @returns {this}
1466
+ */
1467
+ underline(text) {
1468
+ this.blocks.push({
1469
+ type: 'underline',
1470
+ content: text,
1471
+ rawHtml: `<u>${escapeHtml(text)}</u>`,
1472
+ });
1473
+ return this;
1474
+ }
1475
+
1476
+ /**
1477
+ * Add strikethrough text block
1478
+ * @param {string} text
1479
+ * @returns {this}
1480
+ */
1481
+ strikethrough(text) {
1482
+ this.blocks.push({
1483
+ type: 'strikethrough',
1484
+ content: text,
1485
+ rawHtml: `<s>${escapeHtml(text)}</s>`,
1486
+ });
1487
+ return this;
1488
+ }
1489
+
1490
+ /**
1491
+ * Add code block or inline code
1492
+ * @param {string} codeText
1493
+ * @param {string} [language]
1494
+ * @returns {this}
1495
+ */
1496
+ code(codeText, language = '') {
1497
+ const isMultiline = String(codeText).includes('\n') || Boolean(language);
1498
+ this.blocks.push({
1499
+ type: 'code',
1500
+ content: codeText,
1501
+ language,
1502
+ rawHtml: isMultiline
1503
+ ? html.pre(codeText, language)
1504
+ : html.code(codeText),
1505
+ });
1506
+ return this;
1507
+ }
1508
+
1509
+ /**
1510
+ * Add a blockquote block
1511
+ * @param {string} text
1512
+ * @param {boolean} [expandable=false]
1513
+ * @returns {this}
1514
+ */
1515
+ quote(text, expandable = false) {
1516
+ if (expandable) {
1517
+ return this.expandableBlockQuotation(text);
1518
+ }
1519
+ this.blocks.push({
1520
+ type: 'quote',
1521
+ content: text,
1522
+ expandable: false,
1523
+ rawHtml: `<blockquote>${escapeHtml(text)}</blockquote>`,
1524
+ });
1525
+ return this;
1526
+ }
1527
+
1528
+ /**
1529
+ * Add an expandable blockquote (Telegram Bot API 10.3)
1530
+ * @param {string} text
1531
+ * @returns {this}
1532
+ */
1533
+ expandableQuote(text) {
1534
+ return this.expandableBlockQuotation(text);
1535
+ }
1536
+
1537
+ /**
1538
+ * Add a collapsible / expandable blockquote (alias)
1539
+ * @param {string} text
1540
+ * @returns {this}
1541
+ */
1542
+ collapsibleQuote(text) {
1543
+ return this.expandableBlockQuotation(text);
1544
+ }
1545
+
1546
+ /**
1547
+ * Add expandable block quotation (Bot API 10.3 InputRichBlockExpandableBlockQuotation)
1548
+ * @param {string} text
1549
+ * @param {object} [options]
1550
+ * @returns {this}
1551
+ */
1552
+ expandableBlockQuotation(text, options = {}) {
1553
+ const block = new InputRichBlockExpandableBlockQuotation(text, options);
1554
+ this.blocks.push(block);
1555
+ return this;
1556
+ }
1557
+
1558
+ /**
1559
+ * Add spoiler block
1560
+ * @param {string} text
1561
+ * @returns {this}
1562
+ */
1563
+ spoiler(text) {
1564
+ this.blocks.push({
1565
+ type: 'spoiler',
1566
+ content: text,
1567
+ rawHtml: `<span class="tg-spoiler">${escapeHtml(text)}</span>`,
1568
+ });
1569
+ return this;
1570
+ }
1571
+
1572
+ /**
1573
+ * Add formatted link
1574
+ * @param {string} text
1575
+ * @param {string} url
1576
+ * @returns {this}
1577
+ */
1578
+ link(text, url) {
1579
+ this.blocks.push({
1580
+ type: 'link',
1581
+ text,
1582
+ url,
1583
+ rawHtml: `<a href="${escapeHtml(url)}">${escapeHtml(text)}</a>`,
1584
+ });
1585
+ return this;
1586
+ }
1587
+
1588
+ /**
1589
+ * Add user mention
1590
+ * @param {string} text
1591
+ * @param {number|string} userId
1592
+ * @returns {this}
1593
+ */
1594
+ mention(text, userId) {
1595
+ this.blocks.push({
1596
+ type: 'mention',
1597
+ text,
1598
+ userId,
1599
+ rawHtml: `<a href="tg://user?id=${userId}">${escapeHtml(text)}</a>`,
1600
+ });
1601
+ return this;
1602
+ }
1603
+
1604
+ /**
1605
+ * Add document link using tg://document?id= (Bot API 10.3)
1606
+ * @param {string} documentId
1607
+ * @param {string} [text='Document']
1608
+ * @returns {this}
1609
+ */
1610
+ documentLink(documentId, text = 'Document') {
1611
+ return this.link(text, `tg://document?id=${documentId}`);
1612
+ }
1613
+
1614
+ /**
1615
+ * Add bullet list
1616
+ * @param {Array<string>} items
1617
+ * @param {string|object} [bulletOrOptions='•']
1618
+ * @returns {this}
1619
+ */
1620
+ list(items, bulletOrOptions = '•') {
1621
+ if (typeof bulletOrOptions === 'object') {
1622
+ this.blocks.push(new InputRichBlockList(items, bulletOrOptions));
1623
+ return this;
1624
+ }
1625
+ const listItems = Array.isArray(items) ? items : [items];
1626
+ const bullet = typeof bulletOrOptions === 'string' ? bulletOrOptions : '•';
1627
+ const htmlLines = listItems.map((item) => `${bullet} ${escapeHtml(item)}`).join('\n');
1628
+ this.blocks.push(new InputRichBlockList(listItems, { bullet, rawHtml: htmlLines }));
1629
+ return this;
1630
+ }
1631
+
1632
+ /**
1633
+ * Add a checklist block with check states (Bot API 10.2+)
1634
+ * @param {Array<any>} items
1635
+ * @param {object} [options={}]
25
1636
  * @returns {this}
26
1637
  */
27
- parseMode(mode) {
28
- this._parseMode = mode;
1638
+ checklist(items, options = {}) {
1639
+ this.blocks.push(new InputRichBlockChecklist(items, options));
29
1640
  return this;
30
1641
  }
31
1642
 
32
1643
  /**
33
- * Set primary text
1644
+ * Add section heading block (Bot API 10.2+)
34
1645
  * @param {string} text
1646
+ * @param {number} [level=2]
1647
+ * @param {object} [options={}]
35
1648
  * @returns {this}
36
1649
  */
37
- text(text) {
38
- this._text = String(text);
1650
+ sectionHeading(text, level = 2, options = {}) {
1651
+ this.blocks.push(new InputRichBlockSectionHeading(text, level, options));
39
1652
  return this;
40
1653
  }
41
1654
 
42
1655
  /**
43
- * Add a header block with optional emoji
1656
+ * Add heading alias (Bot API 10.2+)
44
1657
  * @param {string} text
45
- * @param {string} [emoji]
1658
+ * @param {number} [level=2]
1659
+ * @param {object} [options={}]
46
1660
  * @returns {this}
47
1661
  */
48
- header(text, emoji = '') {
49
- const formatted = emoji ? `${emoji} ${text}` : text;
50
- this.blocks.push({
51
- type: 'header',
52
- content: formatted,
53
- rawHtml: `<b>${escapeHtml(formatted)}</b>`,
54
- });
55
- return this;
1662
+ heading(text, level = 2, options = {}) {
1663
+ return this.sectionHeading(text, level, options);
56
1664
  }
57
1665
 
58
1666
  /**
59
- * Add a paragraph block
1667
+ * Add preformatted code/text block (Bot API 10.2+)
60
1668
  * @param {string} text
1669
+ * @param {string} [language='']
1670
+ * @param {object} [options={}]
61
1671
  * @returns {this}
62
1672
  */
63
- paragraph(text) {
64
- this.blocks.push({
65
- type: 'paragraph',
66
- content: text,
67
- rawHtml: escapeHtml(text),
68
- });
1673
+ preformatted(text, language = '', options = {}) {
1674
+ this.blocks.push(new InputRichBlockPreformatted(text, language, options));
69
1675
  return this;
70
1676
  }
71
1677
 
72
1678
  /**
73
- * Add bold text block
1679
+ * Add footer block (Bot API 10.2+)
74
1680
  * @param {string} text
1681
+ * @param {object} [options={}]
75
1682
  * @returns {this}
76
1683
  */
77
- bold(text) {
78
- this.blocks.push({
79
- type: 'bold',
80
- content: text,
81
- rawHtml: `<b>${escapeHtml(text)}</b>`,
82
- });
1684
+ footer(text, options = {}) {
1685
+ this.blocks.push(new InputRichBlockFooter(text, options));
83
1686
  return this;
84
1687
  }
85
1688
 
86
1689
  /**
87
- * Add italic text block
1690
+ * Add horizontal divider block (Bot API 10.2+)
1691
+ * @param {object} [options={}]
1692
+ * @returns {this}
1693
+ */
1694
+ divider(options = {}) {
1695
+ this.blocks.push(new InputRichBlockDivider(options));
1696
+ return this;
1697
+ }
1698
+
1699
+ /**
1700
+ * Add mathematical expression / LaTeX block (Bot API 10.2+)
1701
+ * @param {string} expression
1702
+ * @param {object} [options={}]
1703
+ * @returns {this}
1704
+ */
1705
+ math(expression, options = {}) {
1706
+ this.blocks.push(new InputRichBlockMathematicalExpression(expression, options));
1707
+ return this;
1708
+ }
1709
+
1710
+ /**
1711
+ * Add mathematical expression alias (Bot API 10.2+)
1712
+ * @param {string} expression
1713
+ * @param {object} [options={}]
1714
+ * @returns {this}
1715
+ */
1716
+ mathematicalExpression(expression, options = {}) {
1717
+ return this.math(expression, options);
1718
+ }
1719
+
1720
+ /**
1721
+ * Add in-message anchor block (Bot API 10.2+)
1722
+ * @param {string} name
1723
+ * @param {string} [text='']
1724
+ * @param {object} [options={}]
1725
+ * @returns {this}
1726
+ */
1727
+ anchor(name, text = '', options = {}) {
1728
+ this.blocks.push(new InputRichBlockAnchor(name, text, options));
1729
+ return this;
1730
+ }
1731
+
1732
+ /**
1733
+ * Add block quotation block (Bot API 10.2+)
88
1734
  * @param {string} text
1735
+ * @param {object} [options={}]
89
1736
  * @returns {this}
90
1737
  */
91
- italic(text) {
92
- this.blocks.push({
93
- type: 'italic',
94
- content: text,
95
- rawHtml: `<i>${escapeHtml(text)}</i>`,
96
- });
1738
+ blockQuotation(text, options = {}) {
1739
+ this.blocks.push(new InputRichBlockBlockQuotation(text, options));
97
1740
  return this;
98
1741
  }
99
1742
 
100
1743
  /**
101
- * Add underline text block
1744
+ * Add pull quotation block with center emphasis (Bot API 10.2+)
102
1745
  * @param {string} text
1746
+ * @param {object} [options={}]
103
1747
  * @returns {this}
104
1748
  */
105
- underline(text) {
106
- this.blocks.push({
107
- type: 'underline',
108
- content: text,
109
- rawHtml: `<u>${escapeHtml(text)}</u>`,
110
- });
1749
+ pullQuote(text, options = {}) {
1750
+ this.blocks.push(new InputRichBlockPullQuotation(text, options));
111
1751
  return this;
112
1752
  }
113
1753
 
114
1754
  /**
115
- * Add strikethrough text block
1755
+ * Add pull quotation alias (Bot API 10.2+)
116
1756
  * @param {string} text
1757
+ * @param {object} [options={}]
117
1758
  * @returns {this}
118
1759
  */
119
- strikethrough(text) {
120
- this.blocks.push({
121
- type: 'strikethrough',
122
- content: text,
123
- rawHtml: `<s>${escapeHtml(text)}</s>`,
124
- });
1760
+ pullQuotation(text, options = {}) {
1761
+ return this.pullQuote(text, options);
1762
+ }
1763
+
1764
+ /**
1765
+ * Add collage block (Bot API 10.2+)
1766
+ * @param {Array<any>} media
1767
+ * @param {object} [options={}]
1768
+ * @returns {this}
1769
+ */
1770
+ collage(media, options = {}) {
1771
+ this.blocks.push(new InputRichBlockCollage(media, options));
125
1772
  return this;
126
1773
  }
127
1774
 
128
1775
  /**
129
- * Add code block or inline code
130
- * @param {string} codeText
131
- * @param {string} [language]
1776
+ * Add slideshow block (Bot API 10.2+)
1777
+ * @param {Array<any>} media
1778
+ * @param {object} [options={}]
132
1779
  * @returns {this}
133
1780
  */
134
- code(codeText, language = '') {
135
- const isMultiline = String(codeText).includes('\n') || Boolean(language);
136
- this.blocks.push({
137
- type: 'code',
138
- content: codeText,
139
- language,
140
- rawHtml: isMultiline
141
- ? html.pre(codeText, language)
142
- : html.code(codeText),
143
- });
1781
+ slideshow(media, options = {}) {
1782
+ this.blocks.push(new InputRichBlockSlideshow(media, options));
144
1783
  return this;
145
1784
  }
146
1785
 
147
1786
  /**
148
- * Add a blockquote block
149
- * @param {string} text
150
- * @param {boolean} [expandable=false]
1787
+ * Add details / expandable disclosure block (Bot API 10.2+)
1788
+ * @param {string} title
1789
+ * @param {string|Array<any>} [content='']
1790
+ * @param {object} [options={}]
151
1791
  * @returns {this}
152
1792
  */
153
- quote(text, expandable = false) {
154
- this.blocks.push({
155
- type: 'quote',
156
- content: text,
157
- expandable,
158
- rawHtml: expandable
159
- ? `<blockquote expandable>${escapeHtml(text)}</blockquote>`
160
- : `<blockquote>${escapeHtml(text)}</blockquote>`,
161
- });
1793
+ details(title, content = '', options = {}) {
1794
+ this.blocks.push(new InputRichBlockDetails(title, content, options));
162
1795
  return this;
163
1796
  }
164
1797
 
165
1798
  /**
166
- * Add an expandable blockquote
167
- * @param {string} text
1799
+ * Add interactive map block (Bot API 10.2+)
1800
+ * @param {number} latitude
1801
+ * @param {number} longitude
1802
+ * @param {object} [options={}]
168
1803
  * @returns {this}
169
1804
  */
170
- expandableQuote(text) {
171
- return this.quote(text, true);
1805
+ map(latitude, longitude, options = {}) {
1806
+ this.blocks.push(new InputRichBlockMap(latitude, longitude, options));
1807
+ return this;
172
1808
  }
173
1809
 
174
1810
  /**
175
- * Add a collapsible / expandable blockquote (alias)
176
- * @param {string} text
1811
+ * Add animation / GIF block (Bot API 10.2+)
1812
+ * @param {string} animation
1813
+ * @param {object} [options={}]
177
1814
  * @returns {this}
178
1815
  */
179
- collapsibleQuote(text) {
180
- return this.quote(text, true);
1816
+ animation(animation, options = {}) {
1817
+ this.blocks.push(new InputRichBlockAnimation(animation, options));
1818
+ return this;
181
1819
  }
182
1820
 
183
1821
  /**
184
- * Add spoiler block
185
- * @param {string} text
1822
+ * Add audio track block (Bot API 10.2+)
1823
+ * @param {string} audio
1824
+ * @param {object} [options={}]
186
1825
  * @returns {this}
187
1826
  */
188
- spoiler(text) {
189
- this.blocks.push({
190
- type: 'spoiler',
191
- content: text,
192
- rawHtml: `<span class="tg-spoiler">${escapeHtml(text)}</span>`,
193
- });
1827
+ audio(audio, options = {}) {
1828
+ this.blocks.push(new InputRichBlockAudio(audio, options));
194
1829
  return this;
195
1830
  }
196
1831
 
197
1832
  /**
198
- * Add formatted link
199
- * @param {string} text
200
- * @param {string} url
1833
+ * Add photo block (Bot API 10.2+)
1834
+ * @param {string} photo
1835
+ * @param {string} [caption='']
1836
+ * @param {object} [options={}]
201
1837
  * @returns {this}
202
1838
  */
203
- link(text, url) {
204
- this.blocks.push({
205
- type: 'link',
206
- text,
207
- url,
208
- rawHtml: `<a href="${escapeHtml(url)}">${escapeHtml(text)}</a>`,
209
- });
1839
+ photo(photo, caption = '', options = {}) {
1840
+ this.blocks.push(new InputRichBlockPhoto(photo, caption, options));
210
1841
  return this;
211
1842
  }
212
1843
 
213
1844
  /**
214
- * Add user mention
215
- * @param {string} text
216
- * @param {number|string} userId
1845
+ * Add video block (Bot API 10.2+)
1846
+ * @param {string} video
1847
+ * @param {object} [options={}]
217
1848
  * @returns {this}
218
1849
  */
219
- mention(text, userId) {
220
- this.blocks.push({
221
- type: 'mention',
222
- text,
223
- userId,
224
- rawHtml: `<a href="tg://user?id=${userId}">${escapeHtml(text)}</a>`,
225
- });
1850
+ video(video, options = {}) {
1851
+ this.blocks.push(new InputRichBlockVideo(video, options));
226
1852
  return this;
227
1853
  }
228
1854
 
229
1855
  /**
230
- * Add bullet list
231
- * @param {Array<string>} items
232
- * @param {string} [bullet='•']
1856
+ * Add voice note block (Bot API 10.2+)
1857
+ * @param {string} voiceNote
1858
+ * @param {object} [options={}]
233
1859
  * @returns {this}
234
1860
  */
235
- list(items, bullet = '•') {
236
- const listItems = Array.isArray(items) ? items : [items];
237
- const htmlLines = listItems.map((item) => `${bullet} ${escapeHtml(item)}`).join('\n');
238
- this.blocks.push({
239
- type: 'list',
240
- items: listItems,
241
- bullet,
242
- rawHtml: htmlLines,
243
- });
1861
+ voiceNote(voiceNote, options = {}) {
1862
+ this.blocks.push(new InputRichBlockVoiceNote(voiceNote, options));
1863
+ return this;
1864
+ }
1865
+
1866
+ /**
1867
+ * Add AI thinking indicator block (Bot API 10.2+)
1868
+ * @param {string} [text='Thinking...']
1869
+ * @param {object} [options={}]
1870
+ * @returns {this}
1871
+ */
1872
+ thinking(text = 'Thinking...', options = {}) {
1873
+ this.blocks.push(new InputRichBlockThinking(text, options));
244
1874
  return this;
245
1875
  }
246
1876
 
@@ -279,38 +1909,125 @@ export class RichMessageBuilder {
279
1909
  }
280
1910
 
281
1911
  /**
282
- * Add divider line
1912
+ * Add table block (Bot API 10.3 InputRichBlockTable / RichBlockTable)
1913
+ * Supports standard headers, rows, is_bordered, and is_compact mode
1914
+ * @param {Array<string>|Table|InputRichBlockTable|object} headersOrTable
1915
+ * @param {Array<Array<any>>} [rows=[]]
1916
+ * @param {object} [options={}]
283
1917
  * @returns {this}
284
1918
  */
285
- divider() {
286
- this.blocks.push({
287
- type: 'divider',
288
- rawHtml: '───────────────',
289
- });
1919
+ table(headersOrTable, rows = [], options = {}) {
1920
+ let block;
1921
+ if (headersOrTable instanceof InputRichBlockTable) {
1922
+ block = headersOrTable;
1923
+ } else if (headersOrTable instanceof Table) {
1924
+ block = new InputRichBlockTable(headersOrTable.headers, headersOrTable.rows, {
1925
+ is_compact: headersOrTable.is_compact,
1926
+ is_bordered: headersOrTable.is_bordered,
1927
+ is_striped: headersOrTable.is_striped,
1928
+ caption: headersOrTable.caption,
1929
+ alignments: headersOrTable._alignments,
1930
+ title: headersOrTable._title,
1931
+ style: headersOrTable._style,
1932
+ col1Width: headersOrTable.col1Width,
1933
+ ...options,
1934
+ });
1935
+ } else if (headersOrTable && typeof headersOrTable === 'object' && !Array.isArray(headersOrTable)) {
1936
+ block = new InputRichBlockTable(headersOrTable);
1937
+ } else {
1938
+ block = new InputRichBlockTable(headersOrTable, rows, { is_bordered: true, ...options });
1939
+ }
1940
+ this.blocks.push(block);
290
1941
  return this;
291
1942
  }
292
1943
 
293
1944
  /**
294
- * Attach photo or media
295
- * @param {string} url
296
- * @param {string} [caption]
1945
+ * Add a card table block (matching Telegram Bot Card Table UI with rounded container and grid lines)
1946
+ * @param {Array<string>|Table|object} headersOrTable
1947
+ * @param {Array<Array<any>>} [rows=[]]
1948
+ * @param {object} [options={}]
1949
+ * @returns {this}
1950
+ */
1951
+ cardTable(headersOrTable, rows = [], options = {}) {
1952
+ return this.table(headersOrTable, rows, { is_bordered: true, ...options });
1953
+ }
1954
+
1955
+ /**
1956
+ * Add pre-configured system status card table (matching Telegram bot screenshot)
1957
+ * @param {object} [data={}]
1958
+ * @param {object} [options={}]
1959
+ * @returns {this}
1960
+ */
1961
+ systemStatus(data = {}, options = {}) {
1962
+ const table = Table.systemStatus(data, options);
1963
+ return this.table(table);
1964
+ }
1965
+
1966
+ /**
1967
+ * Add pre-configured user profile card table (matching Telegram bot screenshot)
1968
+ * @param {object} [data={}]
1969
+ * @param {object} [options={}]
1970
+ * @returns {this}
1971
+ */
1972
+ userProfile(data = {}, options = {}) {
1973
+ const table = Table.userProfile(data, options);
1974
+ return this.table(table);
1975
+ }
1976
+
1977
+ /**
1978
+ * Add compact table block (Bot API 10.3 is_compact table)
1979
+ * @param {Array<string>} headers
1980
+ * @param {Array<Array<any>>} [rows=[]]
1981
+ * @param {object} [options={}]
1982
+ * @returns {this}
1983
+ */
1984
+ compactTable(headers, rows = [], options = {}) {
1985
+ return this.table(headers, rows, { ...options, is_compact: true });
1986
+ }
1987
+
1988
+ /**
1989
+ * Add document block (Bot API 10.3 InputRichBlockDocument)
1990
+ * @param {string|object} document
1991
+ * @param {string} [caption='']
1992
+ * @param {object} [options={}]
1993
+ * @returns {this}
1994
+ */
1995
+ document(document, caption = '', options = {}) {
1996
+ const block = new InputRichBlockDocument(document, caption, options);
1997
+ this.blocks.push(block);
1998
+ return this;
1999
+ }
2000
+
2001
+ /**
2002
+ * Add buttons block to the rich message (Bot API 10.3 InputRichBlockButtons)
2003
+ * @param {Array<Array<object>>|Array<object>} buttonsMatrix
297
2004
  * @returns {this}
298
2005
  */
299
- photo(url, caption = '') {
300
- this._media = { type: 'photo', url, caption };
2006
+ buttons(buttonsMatrix) {
2007
+ const block = new InputRichBlockButtons(buttonsMatrix);
2008
+ this.blocks.push(block);
301
2009
  return this;
302
2010
  }
303
2011
 
304
2012
  /**
305
- * Set ephemeral parameters (disappearing message)
306
- * @param {number|object} lifetimeSecondsOrParams
2013
+ * Add buttons block (alias)
2014
+ */
2015
+ addButtonsBlock(buttonsMatrix) {
2016
+ return this.buttons(buttonsMatrix);
2017
+ }
2018
+
2019
+ /**
2020
+ * Set ephemeral parameters (Bot API 10.3 EphemeralMessageParameters)
2021
+ * @param {number|object|EphemeralMessageParameters} lifetimeSecondsOrParams
307
2022
  * @returns {this}
308
2023
  */
309
2024
  ephemeral(lifetimeSecondsOrParams = 60) {
310
- if (typeof lifetimeSecondsOrParams === 'number') {
311
- this._ephemeral = { lifetime: lifetimeSecondsOrParams };
312
- } else {
2025
+ if (lifetimeSecondsOrParams instanceof EphemeralMessageParameters) {
313
2026
  this._ephemeral = lifetimeSecondsOrParams;
2027
+ } else if (typeof lifetimeSecondsOrParams === 'number') {
2028
+ this._ephemeral = new EphemeralMessageParameters(lifetimeSecondsOrParams);
2029
+ } else {
2030
+ this._ephemeral = new EphemeralMessageParameters(lifetimeSecondsOrParams);
314
2031
  }
315
2032
  return this;
316
2033
  }
@@ -486,7 +2203,17 @@ export class RichMessageBuilder {
486
2203
  }
487
2204
 
488
2205
  /**
489
- * Compile all rich blocks into standard HTML text for compatibility
2206
+ * Add raw custom block
2207
+ * @param {object} block
2208
+ * @returns {this}
2209
+ */
2210
+ addBlock(block) {
2211
+ this.blocks.push(block);
2212
+ return this;
2213
+ }
2214
+
2215
+ /**
2216
+ * Compile HTML string representation for Telegram HTML mode
490
2217
  * @returns {string}
491
2218
  */
492
2219
  compileHtml() {
@@ -495,7 +2222,9 @@ export class RichMessageBuilder {
495
2222
  parts.push(this._text);
496
2223
  }
497
2224
  for (const block of this.blocks) {
498
- if (block.rawHtml) {
2225
+ if (typeof block?.toHtml === 'function') {
2226
+ parts.push(block.toHtml());
2227
+ } else if (block?.rawHtml) {
499
2228
  parts.push(block.rawHtml);
500
2229
  }
501
2230
  }
@@ -517,24 +2246,33 @@ export class RichMessageBuilder {
517
2246
  const payload = {
518
2247
  text: compiledText || ' ',
519
2248
  parse_mode: this._parseMode,
520
- blocks: this.blocks.map((b) => ({
521
- type: b.type,
522
- content: b.content,
523
- language: b.language,
524
- expandable: b.expandable,
525
- items: b.items,
526
- label: b.label,
527
- value: b.value,
528
- })),
2249
+ blocks: this.blocks.map((b) => {
2250
+ if (typeof b?.toJSON === 'function') {
2251
+ return b.toJSON();
2252
+ }
2253
+ return { ...b };
2254
+ }),
529
2255
  ...this._extra,
530
2256
  };
531
2257
 
2258
+ if (this._media && this._media.length > 0) {
2259
+ payload.media = this._media.map((m) => (typeof m?.toJSON === 'function' ? m.toJSON() : m));
2260
+ }
2261
+
2262
+ if (this._isRtl !== undefined) {
2263
+ payload.is_rtl = Boolean(this._isRtl);
2264
+ }
2265
+
532
2266
  if (replyMarkup) {
533
2267
  payload.reply_markup = replyMarkup;
534
2268
  }
535
2269
 
536
2270
  if (this._ephemeral) {
537
- payload.ephemeral_parameters = this._ephemeral;
2271
+ const ephemeralObj = typeof this._ephemeral.toJSON === 'function'
2272
+ ? this._ephemeral.toJSON()
2273
+ : this._ephemeral;
2274
+ payload.ephemeral_message_parameters = ephemeralObj;
2275
+ payload.ephemeral_parameters = ephemeralObj;
538
2276
  }
539
2277
 
540
2278
  if (this._draftId !== null) {
@@ -544,6 +2282,55 @@ export class RichMessageBuilder {
544
2282
  return payload;
545
2283
  }
546
2284
 
2285
+ /**
2286
+ * Set right-to-left layout mode (Bot API 10.2+)
2287
+ * @param {boolean} [rtl=true]
2288
+ * @returns {this}
2289
+ */
2290
+ isRtl(rtl = true) {
2291
+ this._isRtl = Boolean(rtl);
2292
+ return this;
2293
+ }
2294
+
2295
+ /**
2296
+ * Add media attachments to the rich message (Bot API 10.2+)
2297
+ * @param {...(object|string|Array<object|string>)} items
2298
+ * @returns {this}
2299
+ */
2300
+ media(...items) {
2301
+ for (const item of items.flat()) {
2302
+ this.addMedia(item);
2303
+ }
2304
+ return this;
2305
+ }
2306
+
2307
+ /**
2308
+ * Add a single media attachment
2309
+ * @param {object|string} item
2310
+ * @param {object} [options={}]
2311
+ * @returns {this}
2312
+ */
2313
+ addMedia(item, options = {}) {
2314
+ if (item instanceof InputRichMessageMedia) {
2315
+ this._media.push(item);
2316
+ } else if (typeof item === 'string') {
2317
+ this._media.push(new InputRichMessageMedia(item, options.type || 'photo', options));
2318
+ } else if (typeof item === 'object' && item !== null) {
2319
+ this._media.push(new InputRichMessageMedia(item, item.type || options.type || 'photo', { ...options, ...item }));
2320
+ } else {
2321
+ this._media.push(item);
2322
+ }
2323
+ return this;
2324
+ }
2325
+
2326
+ /**
2327
+ * Export as an InputRichMessage instance
2328
+ * @returns {InputRichMessage}
2329
+ */
2330
+ toInputRichMessage() {
2331
+ return new InputRichMessage(this.compile());
2332
+ }
2333
+
547
2334
  /**
548
2335
  * Build plain structured Rich Message object
549
2336
  * @returns {object}
@@ -618,6 +2405,43 @@ export class RichMessageBuilder {
618
2405
  return new RichMessageBuilder(initialText);
619
2406
  }
620
2407
 
2408
+ /**
2409
+ * Create a rich message pre-populated with a table
2410
+ * @param {Array<string>|Table|InputRichBlockTable} headers
2411
+ * @param {Array<Array<any>>} [rows]
2412
+ * @param {object} [options]
2413
+ * @returns {RichMessageBuilder}
2414
+ */
2415
+ static table(headers, rows = [], options = {}) {
2416
+ const builder = new RichMessageBuilder();
2417
+ builder.table(headers, rows, options);
2418
+ return builder;
2419
+ }
2420
+
2421
+ /**
2422
+ * Create a rich message pre-populated with a compact table
2423
+ * @param {Array<string>} headers
2424
+ * @param {Array<Array<any>>} [rows]
2425
+ * @param {object} [options]
2426
+ * @returns {RichMessageBuilder}
2427
+ */
2428
+ static compactTable(headers, rows = [], options = {}) {
2429
+ const builder = new RichMessageBuilder();
2430
+ builder.compactTable(headers, rows, options);
2431
+ return builder;
2432
+ }
2433
+
2434
+ /**
2435
+ * Create a rich message with buttons block
2436
+ * @param {Array<Array<object>>|Array<object>} buttonsMatrix
2437
+ * @returns {RichMessageBuilder}
2438
+ */
2439
+ static buttons(buttonsMatrix) {
2440
+ const builder = new RichMessageBuilder();
2441
+ builder.buttons(buttonsMatrix);
2442
+ return builder;
2443
+ }
2444
+
621
2445
  /**
622
2446
  * Create a pre-configured interactive Card
623
2447
  * @param {string} title
@@ -651,12 +2475,21 @@ export class RichMessageBuilder {
651
2475
  /**
652
2476
  * Create an ephemeral disappearing message builder
653
2477
  * @param {string} text
654
- * @param {number} [lifetimeSeconds=60]
2478
+ * @param {number|object|EphemeralMessageParameters} [lifetimeSecondsOrParams=60]
655
2479
  * @returns {RichMessageBuilder}
656
2480
  */
657
- static ephemeral(text, lifetimeSeconds = 60) {
2481
+ static ephemeral(text, lifetimeSecondsOrParams = 60) {
658
2482
  const builder = new RichMessageBuilder(text);
659
- return builder.ephemeral(lifetimeSeconds);
2483
+ return builder.ephemeral(lifetimeSecondsOrParams);
2484
+ }
2485
+
2486
+ /**
2487
+ * Helper to format a tg://document?id= link
2488
+ * @param {string} documentId
2489
+ * @param {string} [text='Document']
2490
+ */
2491
+ static documentLink(documentId, text = 'Document') {
2492
+ return `<a href="tg://document?id=${escapeHtml(documentId)}">${escapeHtml(text)}</a>`;
660
2493
  }
661
2494
  }
662
2495