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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Telegix - Lightweight, Pure JavaScript Telegram Bot API Framework
3
- * @author KazeDevID
3
+ * @author Michael Agam
4
4
  * @license MIT
5
5
  */
6
6
 
@@ -13,7 +13,71 @@ export { session, MemorySessionStore, FileSessionStore } from './lib/session.js'
13
13
  export { Polling } from './lib/polling.js';
14
14
  export { createWebhookCallback } from './lib/webhook.js';
15
15
  export { fmt, Format, escapeHtml, escapeMarkdown, html, markdown } from './lib/format.js';
16
- export { RichMessage, RichMessageBuilder } from './lib/rich.js';
16
+ export { Table, InputRichBlockTable, RichBlockTable } from './lib/table.js';
17
+ export { EphemeralMessageParameters, ReplyParameters, BotCommand } from './lib/ephemeral.js';
18
+ export {
19
+ RichMessage,
20
+ RichMessageBuilder,
21
+ RichMessageButton,
22
+ RichTextButton,
23
+ InputRichMessage,
24
+ InputRichMessageMedia,
25
+ RichMessageMedia,
26
+ InputMediaVoiceNote,
27
+ MediaVoiceNote,
28
+ InputRichBlockButtons,
29
+ RichBlockButtons,
30
+ InputRichBlockExpandableBlockQuotation,
31
+ RichBlockExpandableBlockQuotation,
32
+ InputRichBlockDocument,
33
+ RichBlockDocument,
34
+ InputRichBlockParagraph,
35
+ RichBlockParagraph,
36
+ InputRichBlockSectionHeading,
37
+ RichBlockSectionHeading,
38
+ InputRichBlockPreformatted,
39
+ RichBlockPreformatted,
40
+ InputRichBlockFooter,
41
+ RichBlockFooter,
42
+ InputRichBlockDivider,
43
+ RichBlockDivider,
44
+ InputRichBlockMathematicalExpression,
45
+ RichBlockMathematicalExpression,
46
+ RichBlockMath,
47
+ InputRichBlockAnchor,
48
+ RichBlockAnchor,
49
+ InputRichBlockListItem,
50
+ RichBlockListItem,
51
+ InputRichBlockList,
52
+ RichBlockList,
53
+ InputRichBlockChecklist,
54
+ RichBlockChecklist,
55
+ InputRichBlockBlockQuotation,
56
+ RichBlockBlockQuotation,
57
+ InputRichBlockPullQuotation,
58
+ RichBlockPullQuotation,
59
+ RichBlockPullQuote,
60
+ InputRichBlockCollage,
61
+ RichBlockCollage,
62
+ InputRichBlockSlideshow,
63
+ RichBlockSlideshow,
64
+ InputRichBlockDetails,
65
+ RichBlockDetails,
66
+ InputRichBlockMap,
67
+ RichBlockMap,
68
+ InputRichBlockAnimation,
69
+ RichBlockAnimation,
70
+ InputRichBlockAudio,
71
+ RichBlockAudio,
72
+ InputRichBlockPhoto,
73
+ RichBlockPhoto,
74
+ InputRichBlockVideo,
75
+ RichBlockVideo,
76
+ InputRichBlockVoiceNote,
77
+ RichBlockVoiceNote,
78
+ InputRichBlockThinking,
79
+ RichBlockThinking,
80
+ } from './lib/rich.js';
17
81
  export { Scene, BaseScene, WizardScene, Stage } from './lib/scenes.js';
18
82
  export { I18n } from './lib/i18n.js';
19
83
  export { RateLimiter, rateLimit } from './lib/ratelimit.js';
package/lib/api.js CHANGED
@@ -1,10 +1,12 @@
1
1
  /**
2
- * Telegix - Pure JavaScript Telegram Bot API Client
2
+ * Telegix - Telegram Bot API Client
3
3
  * @module telegix/api
4
4
  */
5
5
 
6
6
  import { TelegramError, NetworkError } from './errors.js';
7
7
  import { streamText as runStreamText } from './stream.js';
8
+ import { Table, InputRichBlockTable } from './table.js';
9
+ import { ReplyParameters, BotCommand, EphemeralMessageParameters } from './ephemeral.js';
8
10
  import fs from 'node:fs';
9
11
  import path from 'node:path';
10
12
 
@@ -17,7 +19,15 @@ import path from 'node:path';
17
19
  export function isUploadableFile(value, key = '') {
18
20
  if (!value) return false;
19
21
  if (typeof value === 'string') return false;
20
- if (key === 'link_preview_options' || key === 'reply_parameters' || key === 'reply_markup') {
22
+ if (
23
+ key === 'link_preview_options' ||
24
+ key === 'reply_parameters' ||
25
+ key === 'reply_markup' ||
26
+ key === 'ephemeral_message_parameters' ||
27
+ key === 'ephemeral_parameters' ||
28
+ key === 'blocks' ||
29
+ key === 'media'
30
+ ) {
21
31
  return false;
22
32
  }
23
33
  if (value instanceof Blob || value instanceof Uint8Array || Buffer.isBuffer(value)) return true;
@@ -228,6 +238,57 @@ export function normalizeTelegramPayload(payload) {
228
238
  delete norm.disable_web_page_preview;
229
239
  }
230
240
 
241
+ // 7. Handle reply_parameters (Telegram Bot API 10.2+)
242
+ if (norm.reply_parameters && typeof norm.reply_parameters.toJSON === 'function') {
243
+ norm.reply_parameters = norm.reply_parameters.toJSON();
244
+ }
245
+ if (!norm.reply_parameters) {
246
+ if (norm.ephemeral_message_id !== undefined && norm.reply_to_message_id === undefined) {
247
+ norm.reply_parameters = {
248
+ ephemeral_message_id: Number(norm.ephemeral_message_id),
249
+ ...(norm.allow_sending_without_reply !== undefined ? { allow_sending_without_reply: Boolean(norm.allow_sending_without_reply) } : {}),
250
+ };
251
+ delete norm.ephemeral_message_id;
252
+ delete norm.allow_sending_without_reply;
253
+ } else if (norm.reply_to_message_id !== undefined) {
254
+ norm.reply_parameters = {
255
+ message_id: Number(norm.reply_to_message_id),
256
+ ...(norm.allow_sending_without_reply !== undefined ? { allow_sending_without_reply: Boolean(norm.allow_sending_without_reply) } : {}),
257
+ ...(norm.ephemeral_message_id !== undefined ? { ephemeral_message_id: Number(norm.ephemeral_message_id) } : {}),
258
+ };
259
+ delete norm.reply_to_message_id;
260
+ delete norm.allow_sending_without_reply;
261
+ delete norm.ephemeral_message_id;
262
+ }
263
+ } else if (norm.reply_parameters && typeof norm.reply_parameters === 'object') {
264
+ if (norm.ephemeral_message_id !== undefined && norm.reply_parameters.ephemeral_message_id === undefined) {
265
+ norm.reply_parameters.ephemeral_message_id = Number(norm.ephemeral_message_id);
266
+ delete norm.ephemeral_message_id;
267
+ }
268
+ }
269
+
270
+ // 8. Handle ephemeral_message_parameters (Telegram Bot API 10.2 / 10.3)
271
+ if (norm.ephemeral_message_parameters) {
272
+ if (typeof norm.ephemeral_message_parameters.toJSON === 'function') {
273
+ norm.ephemeral_message_parameters = norm.ephemeral_message_parameters.toJSON();
274
+ } else if (typeof norm.ephemeral_message_parameters === 'number') {
275
+ norm.ephemeral_message_parameters = { lifetime: norm.ephemeral_message_parameters };
276
+ }
277
+ } else if (norm.ephemeral_parameters) {
278
+ if (typeof norm.ephemeral_parameters.toJSON === 'function') {
279
+ norm.ephemeral_message_parameters = norm.ephemeral_parameters.toJSON();
280
+ } else if (typeof norm.ephemeral_parameters === 'number') {
281
+ norm.ephemeral_message_parameters = { lifetime: norm.ephemeral_parameters };
282
+ } else {
283
+ norm.ephemeral_message_parameters = { ...norm.ephemeral_parameters };
284
+ }
285
+ } else if (norm.receiver_user_id !== undefined || norm.callback_query_id !== undefined) {
286
+ norm.ephemeral_message_parameters = {
287
+ ...(norm.receiver_user_id !== undefined ? { receiver_user_id: norm.receiver_user_id } : {}),
288
+ ...(norm.callback_query_id !== undefined ? { callback_query_id: norm.callback_query_id } : {}),
289
+ };
290
+ }
291
+
231
292
  return norm;
232
293
  }
233
294
 
@@ -284,6 +345,12 @@ export class Telegram {
284
345
  hasUpload = true;
285
346
  break;
286
347
  }
348
+ if (key === 'media' && normalizedPayload[key] && typeof normalizedPayload[key] === 'object') {
349
+ if (isUploadableFile(normalizedPayload[key].media, 'media')) {
350
+ hasUpload = true;
351
+ break;
352
+ }
353
+ }
287
354
  }
288
355
 
289
356
  let requestInit = {
@@ -296,7 +363,17 @@ export class Telegram {
296
363
  for (const [key, value] of Object.entries(normalizedPayload)) {
297
364
  if (value === undefined || value === null) continue;
298
365
 
299
- if (isUploadableFile(value, key)) {
366
+ if (key === 'media' && typeof value === 'object' && value.media && isUploadableFile(value.media, 'media')) {
367
+ const { blob, filename } = await normalizeFileSource(value.media, 'media');
368
+ const attachName = `media_file_${Date.now()}`;
369
+ if (typeof blob === 'string') {
370
+ formData.append(attachName, blob);
371
+ } else {
372
+ formData.append(attachName, blob, filename || 'file');
373
+ }
374
+ const mediaObj = { ...value, media: `attach://${attachName}` };
375
+ formData.append('media', JSON.stringify(mediaObj));
376
+ } else if (isUploadableFile(value, key)) {
300
377
  const { blob, filename } = await normalizeFileSource(value, key);
301
378
  if (typeof blob === 'string') {
302
379
  formData.append(key, blob);
@@ -463,6 +540,22 @@ export class Telegram {
463
540
  return this.call('sendPhoto', { chat_id: chatId, photo, ...extra });
464
541
  }
465
542
 
543
+ /**
544
+ * Send a live photo (photo with short video - Bot API 10.2+)
545
+ * @param {number|string} chatId
546
+ * @param {any} photo - Photo file_id, URL, or input file
547
+ * @param {any} video - Video loop file_id, URL, or input file (max 10s, 10MB)
548
+ * @param {object} [extra] - Extra parameters (receiver_user_id, callback_query_id, caption, etc.)
549
+ */
550
+ sendLivePhoto(chatId, photo, video, extra = {}) {
551
+ return this.call('sendLivePhoto', {
552
+ chat_id: chatId,
553
+ photo,
554
+ video,
555
+ ...extra,
556
+ });
557
+ }
558
+
466
559
  /**
467
560
  * Send audio
468
561
  */
@@ -967,7 +1060,10 @@ export class Telegram {
967
1060
  * Bot commands and metadata
968
1061
  */
969
1062
  setMyCommands(commands, extra = {}) {
970
- return this.call('setMyCommands', { commands, ...extra });
1063
+ const serialized = Array.isArray(commands)
1064
+ ? commands.map((cmd) => (typeof cmd?.toJSON === 'function' ? cmd.toJSON() : cmd))
1065
+ : commands;
1066
+ return this.call('setMyCommands', { commands: serialized, ...extra });
971
1067
  }
972
1068
 
973
1069
  deleteMyCommands(extra = {}) {
@@ -1703,9 +1799,9 @@ export class Telegram {
1703
1799
  }
1704
1800
 
1705
1801
  /**
1706
- * Send an ephemeral message
1802
+ * Send an ephemeral message (Bot API 10.3 EphemeralMessageParameters)
1707
1803
  * @param {number|string} chatId
1708
- * @param {string} text
1804
+ * @param {string|object} text
1709
1805
  * @param {object|number} [ephemeralParameters={}]
1710
1806
  * @param {object} [extra={}]
1711
1807
  */
@@ -1713,14 +1809,28 @@ export class Telegram {
1713
1809
  let params = ephemeralParameters;
1714
1810
  if (typeof ephemeralParameters === 'number') {
1715
1811
  params = { lifetime: ephemeralParameters };
1812
+ } else if (ephemeralParameters && typeof ephemeralParameters.toJSON === 'function') {
1813
+ params = ephemeralParameters.toJSON();
1716
1814
  }
1717
1815
  const payload = {
1718
1816
  chat_id: chatId,
1719
- text,
1720
- ...(params && typeof params === 'object' ? { ephemeral_parameters: params } : {}),
1817
+ text: typeof text === 'string' ? text : (text?.text || String(text)),
1818
+ ...(params && typeof params === 'object'
1819
+ ? {
1820
+ ephemeral_message_parameters: params,
1821
+ ephemeral_parameters: params,
1822
+ }
1823
+ : {}),
1721
1824
  ...extra,
1722
1825
  };
1723
1826
 
1827
+ if (text && typeof text === 'object' && (text.blocks || typeof text.compile === 'function')) {
1828
+ const compiled = typeof text.compile === 'function' ? text.compile() : text;
1829
+ payload.text = compiled.text;
1830
+ if (compiled.blocks) payload.blocks = compiled.blocks;
1831
+ if (compiled.reply_markup && !payload.reply_markup) payload.reply_markup = compiled.reply_markup;
1832
+ }
1833
+
1724
1834
  const autoDeleteSeconds =
1725
1835
  params?.autoDeleteSeconds ||
1726
1836
  params?.lifetime ||
@@ -1734,7 +1844,7 @@ export class Telegram {
1734
1844
  err.description?.includes('Unknown method') ||
1735
1845
  err.description?.includes('Bad Request')
1736
1846
  ) {
1737
- const msg = await this.sendMessage(chatId, text, extra);
1847
+ const msg = await this.sendMessage(chatId, payload.text, extra);
1738
1848
  if (autoDeleteSeconds && msg?.message_id) {
1739
1849
  setTimeout(() => {
1740
1850
  this.deleteMessage(chatId, msg.message_id).catch(() => {});
@@ -1747,23 +1857,159 @@ export class Telegram {
1747
1857
  }
1748
1858
 
1749
1859
  /**
1750
- * Edit ephemeral message text
1860
+ * Send a formatted table to a chat (HTML Table or Native Bot API 10.3 Card)
1861
+ * @param {number|string} chatId
1862
+ * @param {Array<string>|Table|InputRichBlockTable} headersOrTable
1863
+ * @param {Array<Array<any>>} [rows=[]]
1864
+ * @param {object} [options={}]
1865
+ */
1866
+ sendTable(chatId, headersOrTable, rows = [], options = {}) {
1867
+ if (options.rich === true || options.card === true || options.style === 'card' || options.native === true) {
1868
+ return this.sendRichTable(chatId, headersOrTable, rows, options);
1869
+ }
1870
+ let tableHtml;
1871
+ if (headersOrTable instanceof Table || (headersOrTable && typeof headersOrTable.toHtml === 'function')) {
1872
+ tableHtml = headersOrTable.toHtml(options);
1873
+ } else {
1874
+ tableHtml = Table.html(headersOrTable, rows, options);
1875
+ }
1876
+ return this.sendMessage(chatId, tableHtml, {
1877
+ parse_mode: 'HTML',
1878
+ ...options,
1879
+ });
1880
+ }
1881
+
1882
+ /**
1883
+ * Send a native Bot API 10.3 Rich Message Table (renders modern card table UI)
1884
+ * @param {number|string} chatId
1885
+ * @param {Array<string>|Table|InputRichBlockTable|object} headersOrTable
1886
+ * @param {Array<Array<any>>} [rows=[]]
1887
+ * @param {object} [options={}]
1888
+ */
1889
+ sendRichTable(chatId, headersOrTable, rows = [], options = {}) {
1890
+ let block;
1891
+ if (headersOrTable instanceof InputRichBlockTable) {
1892
+ block = headersOrTable;
1893
+ } else if (headersOrTable instanceof Table) {
1894
+ block = new InputRichBlockTable(headersOrTable.headers, headersOrTable.rows, {
1895
+ is_compact: headersOrTable.is_compact,
1896
+ is_bordered: headersOrTable.is_bordered,
1897
+ is_striped: headersOrTable.is_striped,
1898
+ caption: headersOrTable.caption,
1899
+ alignments: headersOrTable._alignments,
1900
+ title: headersOrTable._title,
1901
+ col1Width: headersOrTable.col1Width,
1902
+ ...options,
1903
+ });
1904
+ } else if (headersOrTable && typeof headersOrTable === 'object' && !Array.isArray(headersOrTable)) {
1905
+ block = new InputRichBlockTable(headersOrTable);
1906
+ } else {
1907
+ block = new InputRichBlockTable(headersOrTable, rows, { is_bordered: true, ...options });
1908
+ }
1909
+
1910
+ const titleText = options.title || block.title || '';
1911
+ const caption = options.caption || block.caption || '';
1912
+ const headerPrefix = titleText ? `<b>${titleText}</b>\n\n` : '';
1913
+ const footerSuffix = caption ? `\n\n<i>${caption}</i>` : '';
1914
+
1915
+ return this.sendRichMessage(chatId, {
1916
+ text: `${headerPrefix}${block.toHtml()}${footerSuffix}`,
1917
+ parse_mode: 'HTML',
1918
+ blocks: [block.toJSON()],
1919
+ }, options);
1920
+ }
1921
+
1922
+ /**
1923
+ * Send a Card Table to a chat (as Rich Block or styled SVG Card Image)
1924
+ * @param {number|string} chatId
1925
+ * @param {Array<string>|Table} headersOrTable
1926
+ * @param {Array<Array<any>>} [rows=[]]
1927
+ * @param {object} [options={}]
1928
+ */
1929
+ sendTableCard(chatId, headersOrTable, rows = [], options = {}) {
1930
+ if (options.asImage || options.photo || options.image) {
1931
+ const table = headersOrTable instanceof Table
1932
+ ? headersOrTable
1933
+ : new Table(headersOrTable, rows, options);
1934
+ const svgBuffer = table.toBuffer(options);
1935
+ return this.sendPhoto(chatId, {
1936
+ source: svgBuffer,
1937
+ filename: 'table-card.svg',
1938
+ }, {
1939
+ caption: options.caption || options.title || '',
1940
+ parse_mode: options.parse_mode || 'HTML',
1941
+ ...options,
1942
+ });
1943
+ }
1944
+
1945
+ return this.sendRichTable(chatId, headersOrTable, rows, { is_bordered: true, ...options });
1946
+ }
1947
+
1948
+ /**
1949
+ * Send multiple stacked card tables (e.g. SYSTEM and PROFILE cards)
1950
+ * @param {number|string} chatId
1951
+ * @param {Array<Table|object>} tables
1952
+ * @param {object} [options={}]
1953
+ */
1954
+ sendCardTables(chatId, tables, options = {}) {
1955
+ if (options.asImage || options.photo || options.image) {
1956
+ const svg = Table.multiCardSvg(tables, options);
1957
+ const buffer = Buffer.from(svg, 'utf-8');
1958
+ return this.sendPhoto(chatId, {
1959
+ source: buffer,
1960
+ filename: 'tables-cards.svg',
1961
+ }, {
1962
+ caption: options.caption || '',
1963
+ parse_mode: options.parse_mode || 'HTML',
1964
+ ...options,
1965
+ });
1966
+ }
1967
+
1968
+ const list = Array.isArray(tables) ? tables : [tables];
1969
+ const blocks = list.map((t) => {
1970
+ const tableInst = t instanceof Table ? t : (t instanceof InputRichBlockTable ? t : new Table(t));
1971
+ return tableInst.toRichBlock ? tableInst.toRichBlock() : tableInst.toJSON();
1972
+ });
1973
+
1974
+ const htmlParts = list.map((t) => {
1975
+ if (typeof t?.toHtml === 'function') return t.toHtml();
1976
+ return new Table(t).toHtml();
1977
+ });
1978
+
1979
+ return this.sendRichMessage(chatId, {
1980
+ text: htmlParts.join('\n\n'),
1981
+ parse_mode: 'HTML',
1982
+ blocks,
1983
+ }, options);
1984
+ }
1985
+
1986
+ /**
1987
+ * Edit ephemeral message text (Bot API 10.3 supports rich_message)
1751
1988
  * @param {number|string} chatId
1752
1989
  * @param {number} messageId
1753
- * @param {string} text
1990
+ * @param {string|object} text
1754
1991
  * @param {object} [extra]
1755
1992
  */
1756
1993
  editEphemeralMessageText(chatId, messageId, text, extra = {}) {
1757
- return this.call('editEphemeralMessageText', {
1994
+ const payload = {
1758
1995
  chat_id: chatId,
1759
1996
  message_id: messageId,
1760
- text,
1761
1997
  ...extra,
1762
- });
1998
+ };
1999
+ if (extra.rich_message) {
2000
+ payload.rich_message = typeof extra.rich_message.toJSON === 'function'
2001
+ ? extra.rich_message.toJSON()
2002
+ : extra.rich_message;
2003
+ } else if (text && typeof text === 'object' && (text.blocks || typeof text.compile === 'function' || typeof text.toJSON === 'function')) {
2004
+ payload.rich_message = typeof text.compile === 'function' ? text.compile() : (typeof text.toJSON === 'function' ? text.toJSON() : text);
2005
+ } else {
2006
+ payload.text = String(text);
2007
+ }
2008
+ return this.call('editEphemeralMessageText', payload);
1763
2009
  }
1764
2010
 
1765
2011
  /**
1766
- * Edit ephemeral message media
2012
+ * Edit ephemeral message media (Bot API 10.3 supports file upload)
1767
2013
  * @param {number|string} chatId
1768
2014
  * @param {number} messageId
1769
2015
  * @param {object} media
@@ -1779,19 +2025,23 @@ export class Telegram {
1779
2025
  }
1780
2026
 
1781
2027
  /**
1782
- * Edit ephemeral message caption
2028
+ * Edit ephemeral message caption (Bot API 10.3 supports show_caption_above_media)
1783
2029
  * @param {number|string} chatId
1784
2030
  * @param {number} messageId
1785
2031
  * @param {string} caption
1786
2032
  * @param {object} [extra]
1787
2033
  */
1788
2034
  editEphemeralMessageCaption(chatId, messageId, caption, extra = {}) {
1789
- return this.call('editEphemeralMessageCaption', {
2035
+ const payload = {
1790
2036
  chat_id: chatId,
1791
2037
  message_id: messageId,
1792
2038
  caption,
1793
2039
  ...extra,
1794
- });
2040
+ };
2041
+ if (extra.show_caption_above_media !== undefined) {
2042
+ payload.show_caption_above_media = Boolean(extra.show_caption_above_media);
2043
+ }
2044
+ return this.call('editEphemeralMessageCaption', payload);
1795
2045
  }
1796
2046
 
1797
2047
  /**
@@ -1916,3 +2166,5 @@ export class Telegram {
1916
2166
  return runStreamText(this, chatId, textStream, { ...options, useDraft: true });
1917
2167
  }
1918
2168
  }
2169
+
2170
+ export { ReplyParameters, BotCommand, EphemeralMessageParameters };