telegix 1.1.1 → 1.1.2

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.d.ts CHANGED
@@ -92,6 +92,10 @@ export class Markup {
92
92
  text(text: string): object;
93
93
  callback(text: string, data: string): object;
94
94
  url(text: string, url: string): object;
95
+ primary(text: string, dataOrUrl?: string | number | object, options?: object): object;
96
+ danger(text: string, dataOrUrl?: string | number | object, options?: object): object;
97
+ success(text: string, dataOrUrl?: string | number | object, options?: object): object;
98
+ colored(text: string, style: 'primary' | 'danger' | 'success', dataOrUrl?: string | number | object, options?: object): object;
95
99
  webApp(text: string, url: string): object;
96
100
  contactRequest(text: string): object;
97
101
  locationRequest(text: string): object;
@@ -208,6 +212,14 @@ export class Context {
208
212
  replyWithPaidMedia(starCount: number, media: any[], extra?: object): Promise<Message>;
209
213
  replyWithSticker(sticker: any, extra?: object): Promise<Message>;
210
214
  replyWithGame(gameShortName: string, extra?: object): Promise<Message>;
215
+ replyWithLinkPreview(text: string, linkPreviewOptions: LinkPreview | object, extra?: object): Promise<Message>;
216
+ replyWithCollapsibleQuote(text: string, extra?: object): Promise<Message>;
217
+ replyWithExpandableQuote(text: string, extra?: object): Promise<Message>;
218
+ replyWithWebApp(text: string, webAppUrl: string, buttonText?: string, extra?: object): Promise<Message>;
219
+ streamText(textStream: any, options?: object): Promise<any>;
220
+ streamDraft(textStream: any, options?: object): Promise<any>;
221
+ savePreparedInlineMessage(result: object, options?: object): Promise<{ id: string; expiration_date: number }>;
222
+ validateWebAppInitData(initDataStr: string, options?: object): any;
211
223
  createForumTopic(name: string, extra?: object): Promise<any>;
212
224
  editForumTopic(messageThreadId?: number, extra?: object): Promise<boolean>;
213
225
  closeForumTopic(messageThreadId?: number): Promise<boolean>;
@@ -228,6 +240,7 @@ export class Context {
228
240
  sendRichMessageDraft(draft: any, extra?: object): Promise<boolean>;
229
241
  react(emoji: string | any[]): Promise<boolean>;
230
242
  answerCallbackQuery(text?: string, options?: object): Promise<boolean>;
243
+ answerCbQuery(text?: string, options?: object): Promise<boolean>;
231
244
  answerInlineQuery(results: any[], options?: object): Promise<boolean>;
232
245
  editMessageText(text: string, extra?: object): Promise<Message | boolean>;
233
246
  editMessageCaption(caption: string, extra?: object): Promise<Message | boolean>;
@@ -332,6 +345,7 @@ export class RichMessageBuilder {
332
345
  code(codeText: string, language?: string): this;
333
346
  quote(text: string, expandable?: boolean): this;
334
347
  expandableQuote(text: string): this;
348
+ collapsibleQuote(text: string): this;
335
349
  spoiler(text: string): this;
336
350
  link(text: string, url: string): this;
337
351
  mention(text: string, userId: number | string): this;
@@ -347,6 +361,10 @@ export class RichMessageBuilder {
347
361
  row(...buttons: object[]): this;
348
362
  callback(text: string, data: string): this;
349
363
  url(text: string, url: string): this;
364
+ primary(text: string, dataOrUrl?: string | number | object, options?: object): this;
365
+ danger(text: string, dataOrUrl?: string | number | object, options?: object): this;
366
+ success(text: string, dataOrUrl?: string | number | object, options?: object): this;
367
+ colored(text: string, style: 'primary' | 'danger' | 'success', dataOrUrl?: string | number | object, options?: object): this;
350
368
  disabled(text: string): this;
351
369
  webApp(text: string, webAppUrl: string): this;
352
370
  copyText(text: string, textToCopy: string): this;
@@ -402,6 +420,9 @@ export class Telegram {
402
420
  setManagedBotAccessSettings(userId: number, settings?: object, extra?: object): Promise<boolean>;
403
421
  getUserPersonalChatMessages(userId: number, extra?: object): Promise<any>;
404
422
  sendMessageDraft(chatId: number | string, text: string, extra?: object): Promise<boolean>;
423
+ savePreparedInlineMessage(userId: number, result: object, options?: object): Promise<{ id: string; expiration_date: number }>;
424
+ streamText(chatId: number | string, textStream: any, options?: object): Promise<{ message_id?: number; text: string; done: boolean }>;
425
+ streamDraft(chatId: number | string, textStream: any, options?: object): Promise<{ message_id?: number; text: string; done: boolean }>;
405
426
  setWebhook(url: string, extra?: object): Promise<boolean>;
406
427
  deleteWebhook(extra?: object): Promise<boolean>;
407
428
  getWebhookInfo(): Promise<any>;
@@ -481,7 +502,106 @@ export function paginateInlineQuery(ctx: Context, items: any[], formatterFn: (it
481
502
 
482
503
  export function albumMiddleware(options?: { windowMs?: number }): Middleware;
483
504
 
505
+ export interface LinkPreviewOptions {
506
+ is_disabled?: boolean;
507
+ url?: string;
508
+ prefer_small_media?: boolean;
509
+ prefer_large_media?: boolean;
510
+ show_above_text?: boolean;
511
+ }
512
+
513
+ export class LinkPreview {
514
+ constructor(url?: string);
515
+ url(url: string): this;
516
+ disabled(disabled?: boolean): this;
517
+ disable(): this;
518
+ enable(): this;
519
+ smallMedia(small?: boolean): this;
520
+ largeMedia(large?: boolean): this;
521
+ aboveText(above?: boolean): this;
522
+ belowText(): this;
523
+ toJSON(): LinkPreviewOptions;
524
+ static create(url?: string): LinkPreview;
525
+ static disabled(): LinkPreview;
526
+ static small(url?: string): LinkPreview;
527
+ static large(url?: string): LinkPreview;
528
+ static above(url?: string): LinkPreview;
529
+ }
530
+
531
+ export function toTextStream(input: any): AsyncIterable<string>;
532
+ export function streamText(
533
+ telegram: Telegram,
534
+ chatId: number | string,
535
+ textStream: any,
536
+ options?: {
537
+ intervalMs?: number;
538
+ minDeltaChars?: number;
539
+ useDraft?: boolean;
540
+ extra?: object;
541
+ draftId?: number;
542
+ initialMessage?: string;
543
+ onProgress?: (text: string, count: number) => void;
544
+ onError?: (err: Error) => void;
545
+ }
546
+ ): Promise<{ message_id?: number; text: string; done: boolean }>;
547
+
484
548
  export function validateWebAppInitData(initDataStr: string, botToken: string, options?: { maxAgeSeconds?: number }): any;
549
+ export function parseWebAppInitData(initDataStr: string): any;
550
+ export function createMiniAppLaunchUrl(botUsername: string, appShortName: string, startParam?: string): string;
551
+
552
+ export class MiniAppLoadingScreen {
553
+ constructor(options?: {
554
+ title?: string;
555
+ icon?: string;
556
+ lightColor?: string;
557
+ darkColor?: string;
558
+ lightBg?: string;
559
+ darkBg?: string;
560
+ skeleton?: boolean;
561
+ });
562
+ setIcon(icon: string): this;
563
+ setTitle(title: string): this;
564
+ setColors(lightColor: string, darkColor: string): this;
565
+ setSkeleton(enabled?: boolean): this;
566
+ toCSS(): string;
567
+ toHTML(): string;
568
+ }
569
+
570
+ export function generateMiniAppLoadingScreen(options?: any): string;
571
+
572
+ export const MiniApp: {
573
+ isInsideTelegram(): boolean;
574
+ webApp: any;
575
+ fullscreen: {
576
+ request(): boolean;
577
+ exit(): boolean;
578
+ isActive(): boolean;
579
+ onChange(callback: (isFullscreen: boolean) => void): void;
580
+ onFailed(callback: (err: any) => void): void;
581
+ };
582
+ motion: {
583
+ startAccelerometer(options?: { refresh_rate?: number }): boolean;
584
+ stopAccelerometer(): boolean;
585
+ onAccelerometer(callback: (data: { x: number; y: number; z: number }) => void): void;
586
+ startDeviceOrientation(options?: { refresh_rate?: number; need_absolute?: boolean }): boolean;
587
+ stopDeviceOrientation(): boolean;
588
+ onOrientation(callback: (data: { alpha: number; beta: number; gamma: number; absolute: boolean }) => void): void;
589
+ startGyroscope(options?: { refresh_rate?: number }): boolean;
590
+ stopGyroscope(): boolean;
591
+ onGyroscope(callback: (data: { x: number; y: number; z: number }) => void): void;
592
+ };
593
+ homeScreen: {
594
+ addToHomeScreen(): boolean;
595
+ checkStatus(callback: (status: 'unsupported' | 'unknown' | 'added' | 'missed') => void): void;
596
+ };
597
+ sharePreparedMessage(preparedMessageId: string): boolean;
598
+ downloadFile(params: { url: string; file_name: string }): boolean;
599
+ haptics: {
600
+ impact(style?: string): void;
601
+ notification(type?: string): void;
602
+ selection(): void;
603
+ };
604
+ };
485
605
 
486
606
  export function promptMiddleware(): Middleware;
487
607
 
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Telegix - Lightweight Telegram Bot API Framework
2
+ * Telegix - Lightweight, Pure JavaScript Telegram Bot API Framework
3
3
  * @author KazeDevID
4
4
  * @license MIT
5
5
  */
@@ -20,7 +20,16 @@ export { RateLimiter, rateLimit } from './lib/ratelimit.js';
20
20
  export { serializeMessage, serializeUpdate } from './lib/serialize.js';
21
21
  export { InlineQueryResultBuilder, paginateInlineQuery } from './lib/inline.js';
22
22
  export { albumMiddleware } from './lib/album.js';
23
- export { validateWebAppInitData } from './lib/webapp.js';
23
+ export {
24
+ validateWebAppInitData,
25
+ parseWebAppInitData,
26
+ createMiniAppLaunchUrl,
27
+ MiniAppLoadingScreen,
28
+ generateMiniAppLoadingScreen,
29
+ MiniApp,
30
+ } from './lib/webapp.js';
31
+ export { streamText, toTextStream } from './lib/stream.js';
32
+ export { LinkPreview } from './lib/link-preview.js';
24
33
  export { promptMiddleware } from './lib/prompt.js';
25
34
  export { escapeMarkdownV2, mdv2 } from './lib/markdownv2.js';
26
35
  export { inlineDebounceMiddleware } from './lib/inline-debounce.js';
package/lib/api.js CHANGED
@@ -4,20 +4,38 @@
4
4
  */
5
5
 
6
6
  import { TelegramError, NetworkError } from './errors.js';
7
+ import { streamText as runStreamText } from './stream.js';
7
8
  import fs from 'node:fs';
8
9
  import path from 'node:path';
9
10
 
10
11
  /**
11
12
  * Checks if value is a file/stream/buffer that needs multipart/form-data upload
12
13
  * @param {any} value
14
+ * @param {string} [key='']
13
15
  * @returns {boolean}
14
16
  */
15
- export function isUploadableFile(value) {
17
+ export function isUploadableFile(value, key = '') {
16
18
  if (!value) return false;
17
19
  if (typeof value === 'string') return false;
20
+ if (key === 'link_preview_options' || key === 'reply_parameters' || key === 'reply_markup') {
21
+ return false;
22
+ }
18
23
  if (value instanceof Blob || value instanceof Uint8Array || Buffer.isBuffer(value)) return true;
19
- if (typeof value === 'object' && ('source' in value || 'url' in value)) return true;
20
- if (typeof value === 'object' && typeof value.pipe === 'function') return true; // Stream
24
+ if (typeof value === 'object') {
25
+ // If it's a LinkPreview or has link preview options properties, it's not a file
26
+ if (
27
+ value._options !== undefined ||
28
+ value.prefer_small_media !== undefined ||
29
+ value.prefer_large_media !== undefined ||
30
+ value.show_above_text !== undefined ||
31
+ value.is_disabled !== undefined
32
+ ) {
33
+ return false;
34
+ }
35
+ if ('source' in value) return true;
36
+ if (typeof value.pipe === 'function') return true; // Stream
37
+ if ('url' in value && key !== 'link_preview_options') return true;
38
+ }
21
39
  return false;
22
40
  }
23
41
 
@@ -201,6 +219,15 @@ export function normalizeTelegramPayload(payload) {
201
219
  }
202
220
  }
203
221
 
222
+ // 6. Handle link_preview_options and legacy disable_web_page_preview
223
+ if (norm.link_preview_options && typeof norm.link_preview_options.toJSON === 'function') {
224
+ norm.link_preview_options = norm.link_preview_options.toJSON();
225
+ }
226
+ if (norm.disable_web_page_preview !== undefined && !norm.link_preview_options) {
227
+ norm.link_preview_options = { is_disabled: Boolean(norm.disable_web_page_preview) };
228
+ delete norm.disable_web_page_preview;
229
+ }
230
+
204
231
  return norm;
205
232
  }
206
233
 
@@ -253,7 +280,7 @@ export class Telegram {
253
280
 
254
281
  // Check if any payload property is an uploadable file
255
282
  for (const key of Object.keys(normalizedPayload)) {
256
- if (isUploadableFile(normalizedPayload[key])) {
283
+ if (isUploadableFile(normalizedPayload[key], key)) {
257
284
  hasUpload = true;
258
285
  break;
259
286
  }
@@ -269,7 +296,7 @@ export class Telegram {
269
296
  for (const [key, value] of Object.entries(normalizedPayload)) {
270
297
  if (value === undefined || value === null) continue;
271
298
 
272
- if (isUploadableFile(value)) {
299
+ if (isUploadableFile(value, key)) {
273
300
  const { blob, filename } = await normalizeFileSource(value, key);
274
301
  if (typeof blob === 'string') {
275
302
  formData.append(key, blob);
@@ -298,6 +325,14 @@ export class Telegram {
298
325
  }
299
326
  delete cleanPayload.toJSON;
300
327
 
328
+ // Special Telegram API parameters that Telegram requires as JSON-serialized strings
329
+ const jsonStringParams = ['link_preview_options', 'reply_parameters', 'reply_markup'];
330
+ for (const param of jsonStringParams) {
331
+ if (cleanPayload[param] && typeof cleanPayload[param] === 'object') {
332
+ cleanPayload[param] = JSON.stringify(cleanPayload[param]);
333
+ }
334
+ }
335
+
301
336
  requestInit.headers = {
302
337
  'Content-Type': 'application/json',
303
338
  };
@@ -802,6 +837,13 @@ export class Telegram {
802
837
  });
803
838
  }
804
839
 
840
+ /**
841
+ * Alias for answerCallbackQuery
842
+ */
843
+ answerCbQuery(callbackQueryId, extra = {}) {
844
+ return this.answerCallbackQuery(callbackQueryId, extra);
845
+ }
846
+
805
847
  /**
806
848
  * Edit message text
807
849
  */
@@ -1345,20 +1387,6 @@ export class Telegram {
1345
1387
  });
1346
1388
  }
1347
1389
 
1348
- /**
1349
- * Save prepared inline message for mini-app sharing
1350
- * @param {number} userId
1351
- * @param {object} result - InlineQueryResult
1352
- * @param {object} [extra] - allow_user_chats, allow_bot_chats, allow_group_chats, allow_channel_chats
1353
- */
1354
- savePreparedInlineMessage(userId, result, extra = {}) {
1355
- return this.call('savePreparedInlineMessage', {
1356
- user_id: userId,
1357
- result,
1358
- ...extra,
1359
- });
1360
- }
1361
-
1362
1390
  // ==========================================
1363
1391
  // Stickers & Custom Emojis
1364
1392
  // ==========================================
@@ -1837,4 +1865,54 @@ export class Telegram {
1837
1865
  ...extra,
1838
1866
  });
1839
1867
  }
1868
+
1869
+ /**
1870
+ * Save a prepared inline message for a Telegram Mini App user (Bot API 8.0+)
1871
+ * @param {number} userId - Unique identifier of target user who can send the message
1872
+ * @param {object} result - InlineQueryResult object describing the message to be sent
1873
+ * @param {object} [options] - Allowed chats options (allow_user_chats, allow_bot_chats, allow_group_chats, allow_channel_chats)
1874
+ * @returns {Promise<{ id: string, expiration_date: number }>}
1875
+ */
1876
+ savePreparedInlineMessage(userId, result, options = {}) {
1877
+ if (!userId) {
1878
+ throw new Error('Telegram.savePreparedInlineMessage(userId, result) requires a valid userId.');
1879
+ }
1880
+ if (!result) {
1881
+ throw new Error('Telegram.savePreparedInlineMessage(userId, result) requires a valid result object.');
1882
+ }
1883
+
1884
+ const payload = {
1885
+ user_id: userId,
1886
+ result: typeof result.toJSON === 'function' ? result.toJSON() : result,
1887
+ allow_user_chats: options.allow_user_chats ?? true,
1888
+ allow_bot_chats: options.allow_bot_chats ?? true,
1889
+ allow_group_chats: options.allow_group_chats ?? true,
1890
+ allow_channel_chats: options.allow_channel_chats ?? true,
1891
+ ...options,
1892
+ };
1893
+
1894
+ return this.call('savePreparedInlineMessage', payload);
1895
+ }
1896
+
1897
+ /**
1898
+ * Stream text response into a Telegram chat with throttling, token buffering, and rate limit protection
1899
+ * @param {number|string} chatId
1900
+ * @param {AsyncIterable<string>|Iterable<string>|ReadableStream|Array<string>} textStream
1901
+ * @param {object} [options]
1902
+ * @returns {Promise<{ message_id?: number, text: string, done: boolean }>}
1903
+ */
1904
+ streamText(chatId, textStream, options = {}) {
1905
+ return runStreamText(this, chatId, textStream, options);
1906
+ }
1907
+
1908
+ /**
1909
+ * Stream live draft response into a Telegram chat (using Bot API live draft updates)
1910
+ * @param {number|string} chatId
1911
+ * @param {AsyncIterable<string>|Iterable<string>|ReadableStream|Array<string>} textStream
1912
+ * @param {object} [options]
1913
+ * @returns {Promise<{ message_id?: number, text: string, done: boolean }>}
1914
+ */
1915
+ streamDraft(chatId, textStream, options = {}) {
1916
+ return runStreamText(this, chatId, textStream, { ...options, useDraft: true });
1917
+ }
1840
1918
  }
package/lib/context.js CHANGED
@@ -4,6 +4,9 @@
4
4
  */
5
5
 
6
6
  import { serializeMessage, serializeUpdate } from './serialize.js';
7
+ import { escapeHtml } from './format.js';
8
+ import { validateWebAppInitData } from './webapp.js';
9
+ import { LinkPreview } from './link-preview.js';
7
10
 
8
11
  export class Context {
9
12
  /**
@@ -505,6 +508,15 @@ export class Context {
505
508
  });
506
509
  }
507
510
 
511
+ /**
512
+ * Answer callback query (alias for answerCallbackQuery)
513
+ * @param {string} [text]
514
+ * @param {object} [options]
515
+ */
516
+ answerCbQuery(text = '', options = {}) {
517
+ return this.answerCallbackQuery(text, options);
518
+ }
519
+
508
520
  /**
509
521
  * Answer inline query
510
522
  * @param {Array<object>} results
@@ -967,4 +979,103 @@ export class Context {
967
979
  }
968
980
  return this.telegram.setManagedBotAccessSettings(userId, settings);
969
981
  }
982
+
983
+ /**
984
+ * Stream text response to the current chat with rate-limit protection and token buffering
985
+ * @param {AsyncIterable<string>|Iterable<string>|ReadableStream|Array<string>} textStream
986
+ * @param {object} [options]
987
+ * @returns {Promise<{ message_id?: number, text: string, done: boolean }>}
988
+ */
989
+ streamText(textStream, options = {}) {
990
+ return this.telegram.streamText(this._assertChat(), textStream, options);
991
+ }
992
+
993
+ /**
994
+ * Stream live draft response to the current chat
995
+ * @param {AsyncIterable<string>|Iterable<string>|ReadableStream|Array<string>} textStream
996
+ * @param {object} [options]
997
+ * @returns {Promise<{ message_id?: number, text: string, done: boolean }>}
998
+ */
999
+ streamDraft(textStream, options = {}) {
1000
+ return this.telegram.streamDraft(this._assertChat(), textStream, options);
1001
+ }
1002
+
1003
+ /**
1004
+ * Reply with customizable link preview options (Bot API 7.0+)
1005
+ * @param {string} text
1006
+ * @param {LinkPreview|object} linkPreviewOptions
1007
+ * @param {object} [extra]
1008
+ */
1009
+ replyWithLinkPreview(text, linkPreviewOptions, extra = {}) {
1010
+ const previewOptions = typeof linkPreviewOptions?.toJSON === 'function'
1011
+ ? linkPreviewOptions.toJSON()
1012
+ : linkPreviewOptions;
1013
+ return this.reply(text, {
1014
+ link_preview_options: previewOptions,
1015
+ ...extra,
1016
+ });
1017
+ }
1018
+
1019
+ /**
1020
+ * Reply with collapsible quote blockquote (HTML format)
1021
+ * @param {string} text - Quoted text content
1022
+ * @param {object} [extra]
1023
+ */
1024
+ replyWithCollapsibleQuote(text, extra = {}) {
1025
+ const formatted = `<blockquote expandable>${escapeHtml(text)}</blockquote>`;
1026
+ return this.reply(formatted, {
1027
+ parse_mode: 'HTML',
1028
+ ...extra,
1029
+ });
1030
+ }
1031
+
1032
+ /**
1033
+ * Reply with expandable quote blockquote (alias for replyWithCollapsibleQuote)
1034
+ * @param {string} text
1035
+ * @param {object} [extra]
1036
+ */
1037
+ replyWithExpandableQuote(text, extra = {}) {
1038
+ return this.replyWithCollapsibleQuote(text, extra);
1039
+ }
1040
+
1041
+ /**
1042
+ * Save a prepared inline message for the current user (Bot API 8.0+)
1043
+ * @param {object} result - InlineQueryResult object
1044
+ * @param {object} [options]
1045
+ * @returns {Promise<{ id: string, expiration_date: number }>}
1046
+ */
1047
+ savePreparedInlineMessage(result, options = {}) {
1048
+ if (!this.userId) {
1049
+ throw new Error('Telegix Context: savePreparedInlineMessage() requires user context.');
1050
+ }
1051
+ return this.telegram.savePreparedInlineMessage(this.userId, result, options);
1052
+ }
1053
+
1054
+ /**
1055
+ * Validate Web App initData string with the bot token
1056
+ * @param {string} initDataStr
1057
+ * @param {object} [options]
1058
+ * @returns {object|null}
1059
+ */
1060
+ validateWebAppInitData(initDataStr, options = {}) {
1061
+ return validateWebAppInitData(initDataStr, this.telegram.token, options);
1062
+ }
1063
+
1064
+ /**
1065
+ * Reply with a message containing a Telegram Mini App button
1066
+ * @param {string} text
1067
+ * @param {string} webAppUrl
1068
+ * @param {string} [buttonText='Open App']
1069
+ * @param {object} [extra]
1070
+ */
1071
+ replyWithWebApp(text, webAppUrl, buttonText = 'Open App', extra = {}) {
1072
+ return this.reply(text, {
1073
+ reply_markup: {
1074
+ inline_keyboard: [
1075
+ [{ text: buttonText, web_app: { url: webAppUrl } }],
1076
+ ],
1077
+ },
1078
+ ...extra,
1079
+ });
1080
+ }
970
1081
  }
package/lib/format.js CHANGED
@@ -47,6 +47,8 @@ export const html = {
47
47
  customEmoji: (text, customEmojiId) => `<tg-emoji emoji-id="${customEmojiId}">${escapeHtml(text)}</tg-emoji>`,
48
48
  quote: (text) => `<blockquote>${escapeHtml(text)}</blockquote>`,
49
49
  expandableBlockquote: (text) => `<blockquote expandable>${escapeHtml(text)}</blockquote>`,
50
+ expandableQuote: (text) => `<blockquote expandable>${escapeHtml(text)}</blockquote>`,
51
+ collapsibleQuote: (text) => `<blockquote expandable>${escapeHtml(text)}</blockquote>`,
50
52
  };
51
53
 
52
54
  /**
@@ -66,6 +68,8 @@ export const markdown = {
66
68
  customEmoji: (text, customEmojiId) => `![${escapeMarkdown(text)}](tg://emoji?id=${customEmojiId})`,
67
69
  quote: (text) => text.split('\n').map((line) => `>${escapeMarkdown(line)}`).join('\n'),
68
70
  expandableBlockquote: (text) => `**>${escapeMarkdown(text)}||`,
71
+ expandableQuote: (text) => `**>${escapeMarkdown(text)}||`,
72
+ collapsibleQuote: (text) => `**>${escapeMarkdown(text)}||`,
69
73
  };
70
74
 
71
75
  /**
@@ -107,6 +111,8 @@ fmt.mention = html.mention;
107
111
  fmt.customEmoji = html.customEmoji;
108
112
  fmt.quote = html.quote;
109
113
  fmt.expandableBlockquote = html.expandableBlockquote;
114
+ fmt.expandableQuote = html.expandableQuote;
115
+ fmt.collapsibleQuote = html.collapsibleQuote;
110
116
  fmt.escape = escapeHtml;
111
117
  fmt.html = html;
112
118
  fmt.markdown = markdown;