teledzik 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -588,9 +588,9 @@ await ctx.sendRichMessage(msg, {
588
588
 
589
589
  ---
590
590
 
591
- ### Universal `editRichMessage` (Omni-Format Support)
591
+ ### Native `editRichMessage` (Preserving 100% Rich UI)
592
592
 
593
- Update existing messages or media captions in-place with **any** content format. It automatically detects Text vs Media (Photo/Video/Audio/Doc) and silently handles Telegram's `message is not modified` without throwing errors:
593
+ Edit a rich message in-place while preserving 100% Native Rich UI layout (tables, headings, cards, collapsibles):
594
594
 
595
595
  ```ts
596
596
  import { Telegraf, RichHTMLBuilder, Markup } from 'teledzik'
@@ -605,23 +605,12 @@ bot.action('btn:update', async (ctx) => {
605
605
  ['VPS-02', '7.2 GB', 'ONLINE 🟢']
606
606
  ], { bordered: true, striped: true, hasHeader: true })
607
607
 
608
+ // Edit in-place with native Rich UI preservation
608
609
  await ctx.editRichMessage(rich, {
609
610
  reply_markup: Markup.inlineKeyboard([
610
611
  [Markup.button.primary('🔄 Refresh', 'btn:update')]
611
612
  ]).reply_markup
612
613
  })
613
-
614
- // 2. Or using an Object
615
- await ctx.editRichMessage({
616
- html: '<b>New Content</b>',
617
- reply_markup: { inline_keyboard: [] }
618
- })
619
-
620
- // 3. Or using Raw HTML String
621
- await ctx.editRichMessage('<b>Updated via raw string</b>')
622
-
623
- // 4. Or specifying a custom messageId
624
- await ctx.editRichMessage(12345, '<b>Edit specific message ID</b>')
625
614
  })
626
615
  ```
627
616
 
package/lib/context.js CHANGED
@@ -1069,39 +1069,21 @@ class Context {
1069
1069
  this.assert(this.chat, 'sendRichMessageDraft');
1070
1070
  return this.telegram.sendRichMessageDraft(this.chat.id, draftId, richMessage, extra);
1071
1071
  }
1072
- async editRichMessage(a, b, c) {
1073
- let targetMsgId;
1074
- let content;
1075
- let extra = {};
1076
- if (typeof a === 'number' ||
1077
- (typeof a === 'string' && !isNaN(Number(a)) && b !== undefined)) {
1078
- targetMsgId = a;
1079
- content = b;
1080
- extra = c || {};
1081
- }
1082
- else {
1083
- targetMsgId =
1084
- this.callbackQuery?.message?.message_id ??
1085
- (this.message && 'message_id' in this.message ? this.message.message_id : undefined) ??
1086
- (this.update && 'edited_message' in this.update && this.update.edited_message
1087
- ? this.update.edited_message.message_id
1088
- : undefined) ??
1089
- this.msgId;
1090
- content = a;
1091
- extra = b || {};
1092
- }
1093
- const chatId = this.chat?.id ??
1094
- (this.callbackQuery?.message && 'chat' in this.callbackQuery.message
1095
- ? this.callbackQuery.message.chat.id
1096
- : undefined);
1097
- const inlineMsgId = this.inlineMessageId ?? extra?.inline_message_id;
1098
- if (!inlineMsgId && (!chatId || targetMsgId == null)) {
1099
- throw new Error('[teledzik] Tidak dapat menemukan chatId atau messageId dari konteks pesan saat ini.');
1100
- }
1101
- if (inlineMsgId && !extra.inline_message_id) {
1102
- extra.inline_message_id = inlineMsgId;
1072
+ /**
1073
+ * Context-aware shorthand for {@link Telegram.editRichMessage}.
1074
+ */
1075
+ editRichMessage(richMessage, extra) {
1076
+ this.assert(this.chat, 'editRichMessage');
1077
+ const messageId = this.callbackQuery?.message?.message_id ||
1078
+ (this.message && 'message_id' in this.message ? this.message.message_id : undefined) ||
1079
+ (this.update && 'edited_message' in this.update && this.update.edited_message
1080
+ ? this.update.edited_message.message_id
1081
+ : undefined) ||
1082
+ this.msgId;
1083
+ if (!messageId) {
1084
+ throw new Error('[teledzik] Tidak dapat menemukan message_id untuk diedit.');
1103
1085
  }
1104
- return this.telegram.editRichMessage(chatId, targetMsgId, content, extra);
1086
+ return this.telegram.editRichMessage(this.chat.id, messageId, richMessage, extra);
1105
1087
  }
1106
1088
  /**
1107
1089
  * Enqueue a Telegram API call for the current chat to prevent 429 Too Many Requests errors.
@@ -1,12 +1,7 @@
1
1
  "use strict";
2
- /**
3
- * Converts Rich HTML strings (containing unsupported Telegram HTML tags like
4
- * <table>, <ul>, <ol>, <li>, <details>, <h1>-<h6>, <aside>, <p>, <hr>, etc.)
5
- * into standard Telegram HTML compatible with parse_mode: 'HTML' (editMessageText/sendMessage).
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.sanitizeRichHtml = void 0;
9
- function convertTableToText(tableHtml) {
4
+ function formatTableToCard(tableHtml) {
10
5
  const rows = [];
11
6
  const rowMatches = tableHtml.match(/<tr[^>]*>([\s\S]*?)<\/tr>/gi) || [];
12
7
  for (const row of rowMatches) {
@@ -21,45 +16,37 @@ function convertTableToText(tableHtml) {
21
16
  }
22
17
  if (rows.length === 0)
23
18
  return '';
24
- // Calculate column widths
25
- const colWidths = [];
26
- rows.forEach((r) => {
27
- r.forEach((c, idx) => {
28
- const textLen = c.replace(/<[^>]+>/g, '').length;
29
- colWidths[idx] = Math.max(colWidths[idx] || 0, textLen);
19
+ // Header vs Row handling (Format sebagai Kartu Elegan)
20
+ const isKeyValue = rows.length > 1 && rows.every((r) => r.length === 2);
21
+ let cardText = '';
22
+ if (isKeyValue) {
23
+ const lines = rows.slice(1).map((r) => `• <b>${r[0]}:</b> ${r[1]}`);
24
+ cardText = `<blockquote>\n${lines.join('\n')}\n</blockquote>\n\n`;
25
+ }
26
+ else {
27
+ const lines = rows.map((r, idx) => {
28
+ if (idx === 0)
29
+ return `<b>${r.join(' │ ')}</b>`;
30
+ return `${r.join(' │ ')}`;
30
31
  });
31
- });
32
- // Format table rows
33
- const formattedRows = rows.map((r, rowIdx) => {
34
- const line = r
35
- .map((c, colIdx) => {
36
- const textLen = c.replace(/<[^>]+>/g, '').length;
37
- const pad = ' '.repeat(Math.max(0, (colWidths[colIdx] || 0) - textLen));
38
- return `${c}${pad}`;
39
- })
40
- .join(' │ ');
41
- if (rowIdx === 0 && rows.length > 1) {
42
- const sep = colWidths.map((w) => '─'.repeat(w)).join('─┼─');
43
- return `${line}\n${sep}`;
44
- }
45
- return line;
46
- });
47
- return `<pre>\n${formattedRows.join('\n')}\n</pre>\n\n`;
32
+ cardText = `<blockquote>\n${lines.join('\n')}\n</blockquote>\n\n`;
33
+ }
34
+ return cardText;
48
35
  }
49
36
  function sanitizeRichHtml(html) {
50
37
  if (!html)
51
38
  return html;
52
39
  let result = html;
53
- // 1. Convert Tables: <table>...</table> -> <pre>formatted table</pre>
54
- result = result.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, content) => convertTableToText(content));
55
- // 2. Convert Details / Expandable: <details><summary>Title</summary>Content</details>
40
+ // 1. Convert Tables to Clean Modern Cards
41
+ result = result.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, content) => formatTableToCard(content));
42
+ // 2. Convert Details to Telegram Native Expandable Blockquotes
56
43
  result = result.replace(/<details[^>]*>\s*<summary[^>]*>(.*?)<\/summary>([\s\S]*?)<\/details>/gi, (_, title, body) => {
57
- return `<b>▶ ${title.trim()}</b>\n<blockquote>${body.trim()}</blockquote>\n\n`;
44
+ return `<blockquote expandable><b>▶ ${title.trim()}</b>\n${body.trim()}</blockquote>\n\n`;
58
45
  });
59
- // 3. Convert Headings
60
- result = result.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `<b>${c.trim().toUpperCase()}</b>\n\n`);
46
+ // 3. Clean Headings
47
+ result = result.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `<b>${c.trim()}</b>\n\n`);
61
48
  result = result.replace(/<h[2-6][^>]*>([\s\S]*?)<\/h[2-6]>/gi, (_, c) => `<b>${c.trim()}</b>\n\n`);
62
- // 4. Convert Task Lists & Unordered/Ordered Lists
49
+ // 4. Task Lists & Bullet Lists
63
50
  result = result.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, listContent) => {
64
51
  return (listContent
65
52
  .replace(/<li[^>]*>\s*<input[^>]*type="checkbox"[^>]*checked[^>]*>\s*([\s\S]*?)<\/li>/gi, '☑ $1\n')
@@ -73,29 +60,24 @@ function sanitizeRichHtml(html) {
73
60
  .replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_match, item) => `${index++}. ${item.trim()}\n`)
74
61
  .trim() + '\n\n');
75
62
  });
76
- // 5. Convert Pull Quotes / Asides
63
+ // 5. Clean Blockquotes & Dividers
77
64
  result = result.replace(/<aside[^>]*>([\s\S]*?)(?:<cite[^>]*>([\s\S]*?)<\/cite>)?<\/aside>/gi, (_, quote, cite) => {
78
65
  return `<blockquote>${quote.trim()}${cite ? `\n— <i>${cite.trim()}</i>` : ''}</blockquote>\n\n`;
79
66
  });
80
- // 6. Convert Special Bot API 10.1 tags
81
67
  result = result.replace(/<tg-thinking[^>]*>([\s\S]*?)<\/tg-thinking>/gi, (_, c) => `<blockquote>💭 <i>${c.trim()}</i></blockquote>\n\n`);
82
68
  result = result.replace(/<tg-math-block[^>]*>([\s\S]*?)<\/tg-math-block>/gi, (_, c) => `<pre><code>${c.trim()}</code></pre>\n\n`);
83
69
  result = result.replace(/<tg-reference[^>]*>([\s\S]*?)<\/tg-reference>/gi, (_, c) => `<i>${c.trim()}</i>`);
84
70
  result = result.replace(/<mark[^>]*>([\s\S]*?)<\/mark>/gi, (_, c) => `<b>[${c.trim()}]</b>`);
85
- // 7. Convert Figures & Captions
86
71
  result = result.replace(/<figure[^>]*>[\s\S]*?<figcaption[^>]*>([\s\S]*?)<\/figcaption><\/figure>/gi, (_, cap) => `\n<i>📷 ${cap.trim()}</i>\n`);
87
- // 8. Convert Paragraphs & Linebreaks
88
72
  result = result.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, c) => `${c.trim()}\n\n`);
89
73
  result = result.replace(/<footer[^>]*>([\s\S]*?)<\/footer>/gi, (_, c) => `<i>${c.trim()}</i>\n`);
90
74
  result = result.replace(/<br\s*\/?>/gi, '\n');
91
- result = result.replace(/<hr\s*\/?>/gi, '──────────\n');
92
- // 9. Strip unsupported media & sub/sup tags but keep text
75
+ result = result.replace(/<hr\s*\/?>/gi, '\n');
76
+ // Strip unsupported tags
93
77
  result = result.replace(/<sub[^>]*>([\s\S]*?)<\/sub>/gi, '$1');
94
78
  result = result.replace(/<sup[^>]*>([\s\S]*?)<\/sup>/gi, '$1');
95
79
  result = result.replace(/<tg-(map|collage|slideshow)[^>]*>([\s\S]*?)<\/tg-\1>/gi, '$2');
96
80
  result = result.replace(/<(img|video|audio|tg-map)[^>]*\/?>/gi, '');
97
- // 10. Clean up extra newlines (> 2 consecutive newlines)
98
- result = result.replace(/\n{3,}/g, '\n\n');
99
- return result.trim();
81
+ return result.replace(/\n{3,}/g, '\n\n').trim();
100
82
  }
101
83
  exports.sanitizeRichHtml = sanitizeRichHtml;
package/lib/telegram.js CHANGED
@@ -1271,26 +1271,13 @@ class Telegram extends client_1.default {
1271
1271
  ...extra,
1272
1272
  });
1273
1273
  }
1274
- async editRichMessage(chatId, messageId, contentOrInline, contentOrExtra, maybeExtra) {
1275
- let inlineMessageId;
1276
- let content;
1277
- let extra = {};
1278
- if (typeof contentOrInline === 'string' &&
1279
- (typeof contentOrExtra === 'object' || typeof contentOrExtra === 'string') &&
1280
- maybeExtra !== undefined) {
1281
- inlineMessageId = contentOrInline;
1282
- content = contentOrExtra;
1283
- extra = maybeExtra || {};
1284
- }
1285
- else {
1286
- content = contentOrInline;
1287
- extra = contentOrExtra || {};
1288
- inlineMessageId = extra?.inline_message_id;
1289
- }
1274
+ /**
1275
+ * Universal in-place message editor with Modern Rich UI formatting.
1276
+ */
1277
+ async editRichMessage(chatId, messageId, content, extra) {
1290
1278
  let htmlText = '';
1291
1279
  let replyMarkup = extra?.reply_markup;
1292
- // 1. Omni-format input extraction
1293
- if (content && typeof content.build === 'function') {
1280
+ if (typeof content?.build === 'function') {
1294
1281
  const built = content.build();
1295
1282
  if (typeof built === 'object' && built !== null) {
1296
1283
  htmlText = built.html || built.text || built.caption || '';
@@ -1303,7 +1290,7 @@ class Telegram extends client_1.default {
1303
1290
  }
1304
1291
  }
1305
1292
  else if (typeof content === 'object' && content !== null) {
1306
- htmlText = content.html || content.text || content.caption || content.markdown || '';
1293
+ htmlText = content.html || content.text || content.caption || '';
1307
1294
  if (content.reply_markup) {
1308
1295
  replyMarkup = replyMarkup || content.reply_markup;
1309
1296
  }
@@ -1311,52 +1298,40 @@ class Telegram extends client_1.default {
1311
1298
  else if (typeof content === 'string') {
1312
1299
  htmlText = content;
1313
1300
  }
1314
- // Sanitize rich HTML formatting (tables, headers, details, etc.)
1301
+ // Format ke Telegram Modern HTML
1315
1302
  htmlText = (0, rich_sanitizer_1.sanitizeRichHtml)(htmlText);
1316
- // 2. Prepare Base Payload
1317
- const isInline = Boolean(inlineMessageId || extra?.inline_message_id);
1318
- const basePayload = {
1303
+ const payload = {
1304
+ chat_id: chatId,
1305
+ message_id: Number(messageId),
1306
+ text: htmlText,
1319
1307
  parse_mode: 'HTML',
1320
1308
  ...extra,
1321
1309
  };
1322
- if (isInline) {
1323
- basePayload.inline_message_id = inlineMessageId || extra.inline_message_id;
1324
- }
1325
- else {
1326
- basePayload.chat_id = chatId;
1327
- basePayload.message_id = messageId != null ? Number(messageId) : undefined;
1328
- }
1329
- if (replyMarkup) {
1330
- basePayload.reply_markup = replyMarkup;
1331
- }
1332
- // 3. Execution with Auto-Detect (Text -> Caption Fallback) and Silent Error Handling
1310
+ if (replyMarkup)
1311
+ payload.reply_markup = replyMarkup;
1333
1312
  try {
1334
- return await this.callApi('editMessageText', {
1335
- ...basePayload,
1336
- text: htmlText,
1337
- });
1313
+ return await this.callApi('editMessageText', payload);
1338
1314
  }
1339
1315
  catch (err) {
1340
1316
  const desc = String(err?.description || err?.message || '');
1341
- // Fallback: If target is a Media message (Photo, Video, Audio, Document, Animation)
1342
- if (desc.includes('no text in the message') ||
1343
- desc.includes('there is no text in the message to edit') ||
1344
- desc.includes('message to edit not found')) {
1317
+ // Fallback jika pesan berupa Media (Foto/Video)
1318
+ if (desc.includes('no text in the message') || desc.includes('message to edit not found')) {
1345
1319
  try {
1346
1320
  return await this.callApi('editMessageCaption', {
1347
- ...basePayload,
1321
+ chat_id: chatId,
1322
+ message_id: Number(messageId),
1348
1323
  caption: htmlText,
1324
+ parse_mode: 'HTML',
1325
+ reply_markup: replyMarkup,
1326
+ ...extra,
1349
1327
  });
1350
1328
  }
1351
1329
  catch (captionErr) {
1352
- const capDesc = String(captionErr?.description || captionErr?.message || '');
1353
- if (capDesc.includes('message is not modified')) {
1330
+ if (String(captionErr?.description).includes('message is not modified'))
1354
1331
  return false;
1355
- }
1356
1332
  throw captionErr;
1357
1333
  }
1358
1334
  }
1359
- // Gracefully handle 'message is not modified'
1360
1335
  if (desc.includes('message is not modified')) {
1361
1336
  return false;
1362
1337
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teledzik",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Modern Telegram Bot Framework",
5
5
  "keywords": [
6
6
  "telekaf",
package/src/context.ts CHANGED
@@ -1407,61 +1407,22 @@ export class Context<U extends Deunionize<tg.Update> = tg.Update> {
1407
1407
  }
1408
1408
 
1409
1409
  /**
1410
- * Universal Context shortcut to edit message with Omni-Format support (RichHTMLBuilder, Object, or String),
1411
- * auto-detection of Text vs Media Caption, and silent 'not modified' error handling.
1412
- */
1413
- editRichMessage(
1414
- content: tg.RichContentInput,
1415
- extra?: tg.EditRichOptions
1416
- ): Promise<any>
1417
- editRichMessage(
1418
- messageId: number | string,
1419
- content: tg.RichContentInput,
1420
- extra?: tg.EditRichOptions
1421
- ): Promise<any>
1422
- async editRichMessage(a: any, b?: any, c?: any): Promise<any> {
1423
- let targetMsgId: number | string | undefined
1424
- let content: any
1425
- let extra: any = {}
1426
-
1427
- if (
1428
- typeof a === 'number' ||
1429
- (typeof a === 'string' && !isNaN(Number(a)) && b !== undefined)
1430
- ) {
1431
- targetMsgId = a
1432
- content = b
1433
- extra = c || {}
1434
- } else {
1435
- targetMsgId =
1436
- this.callbackQuery?.message?.message_id ??
1437
- (this.message && 'message_id' in this.message ? this.message.message_id : undefined) ??
1438
- (this.update && 'edited_message' in this.update && this.update.edited_message
1439
- ? this.update.edited_message.message_id
1440
- : undefined) ??
1441
- this.msgId
1442
- content = a
1443
- extra = b || {}
1410
+ * Context-aware shorthand for {@link Telegram.editRichMessage}.
1411
+ */
1412
+ editRichMessage(richMessage: any, extra?: any): Promise<any> {
1413
+ this.assert(this.chat, 'editRichMessage')
1414
+ const messageId =
1415
+ this.callbackQuery?.message?.message_id ||
1416
+ (this.message && 'message_id' in this.message ? this.message.message_id : undefined) ||
1417
+ (this.update && 'edited_message' in this.update && this.update.edited_message
1418
+ ? this.update.edited_message.message_id
1419
+ : undefined) ||
1420
+ this.msgId
1421
+
1422
+ if (!messageId) {
1423
+ throw new Error('[teledzik] Tidak dapat menemukan message_id untuk diedit.')
1444
1424
  }
1445
-
1446
- const chatId =
1447
- this.chat?.id ??
1448
- (this.callbackQuery?.message && 'chat' in this.callbackQuery.message
1449
- ? this.callbackQuery.message.chat.id
1450
- : undefined)
1451
-
1452
- const inlineMsgId = this.inlineMessageId ?? extra?.inline_message_id
1453
-
1454
- if (!inlineMsgId && (!chatId || targetMsgId == null)) {
1455
- throw new Error(
1456
- '[teledzik] Tidak dapat menemukan chatId atau messageId dari konteks pesan saat ini.'
1457
- )
1458
- }
1459
-
1460
- if (inlineMsgId && !extra.inline_message_id) {
1461
- extra.inline_message_id = inlineMsgId
1462
- }
1463
-
1464
- return this.telegram.editRichMessage(chatId, targetMsgId, content, extra)
1425
+ return this.telegram.editRichMessage(this.chat.id, messageId, richMessage, extra)
1465
1426
  }
1466
1427
 
1467
1428
  /**
@@ -1,13 +1,6 @@
1
- /**
2
- * Converts Rich HTML strings (containing unsupported Telegram HTML tags like
3
- * <table>, <ul>, <ol>, <li>, <details>, <h1>-<h6>, <aside>, <p>, <hr>, etc.)
4
- * into standard Telegram HTML compatible with parse_mode: 'HTML' (editMessageText/sendMessage).
5
- */
6
-
7
- function convertTableToText(tableHtml: string): string {
1
+ function formatTableToCard(tableHtml: string): string {
8
2
  const rows: string[][] = []
9
3
  const rowMatches = tableHtml.match(/<tr[^>]*>([\s\S]*?)<\/tr>/gi) || []
10
-
11
4
  for (const row of rowMatches) {
12
5
  const cells: string[] = []
13
6
  const cellMatches = row.match(/<(th|td)[^>]*>([\s\S]*?)<\/\1>/gi) || []
@@ -17,56 +10,41 @@ function convertTableToText(tableHtml: string): string {
17
10
  }
18
11
  if (cells.length > 0) rows.push(cells)
19
12
  }
20
-
21
13
  if (rows.length === 0) return ''
22
14
 
23
- // Calculate column widths
24
- const colWidths: number[] = []
25
- rows.forEach((r) => {
26
- r.forEach((c, idx) => {
27
- const textLen = c.replace(/<[^>]+>/g, '').length
28
- colWidths[idx] = Math.max(colWidths[idx] || 0, textLen)
15
+ // Header vs Row handling (Format sebagai Kartu Elegan)
16
+ const isKeyValue = rows.length > 1 && rows.every((r) => r.length === 2)
17
+ let cardText = ''
18
+ if (isKeyValue) {
19
+ const lines = rows.slice(1).map((r) => `• <b>${r[0]}:</b> ${r[1]}`)
20
+ cardText = `<blockquote>\n${lines.join('\n')}\n</blockquote>\n\n`
21
+ } else {
22
+ const lines = rows.map((r, idx) => {
23
+ if (idx === 0) return `<b>${r.join(' │ ')}</b>`
24
+ return `${r.join(' │ ')}`
29
25
  })
30
- })
31
-
32
- // Format table rows
33
- const formattedRows = rows.map((r, rowIdx) => {
34
- const line = r
35
- .map((c, colIdx) => {
36
- const textLen = c.replace(/<[^>]+>/g, '').length
37
- const pad = ' '.repeat(Math.max(0, (colWidths[colIdx] || 0) - textLen))
38
- return `${c}${pad}`
39
- })
40
- .join(' │ ')
41
-
42
- if (rowIdx === 0 && rows.length > 1) {
43
- const sep = colWidths.map((w) => '─'.repeat(w)).join('─┼─')
44
- return `${line}\n${sep}`
45
- }
46
- return line
47
- })
48
-
49
- return `<pre>\n${formattedRows.join('\n')}\n</pre>\n\n`
26
+ cardText = `<blockquote>\n${lines.join('\n')}\n</blockquote>\n\n`
27
+ }
28
+ return cardText
50
29
  }
51
30
 
52
31
  export function sanitizeRichHtml(html: string): string {
53
32
  if (!html) return html
54
-
55
33
  let result = html
56
34
 
57
- // 1. Convert Tables: <table>...</table> -> <pre>formatted table</pre>
58
- result = result.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, content) => convertTableToText(content))
35
+ // 1. Convert Tables to Clean Modern Cards
36
+ result = result.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, content) => formatTableToCard(content))
59
37
 
60
- // 2. Convert Details / Expandable: <details><summary>Title</summary>Content</details>
38
+ // 2. Convert Details to Telegram Native Expandable Blockquotes
61
39
  result = result.replace(/<details[^>]*>\s*<summary[^>]*>(.*?)<\/summary>([\s\S]*?)<\/details>/gi, (_, title, body) => {
62
- return `<b>▶ ${title.trim()}</b>\n<blockquote>${body.trim()}</blockquote>\n\n`
40
+ return `<blockquote expandable><b>▶ ${title.trim()}</b>\n${body.trim()}</blockquote>\n\n`
63
41
  })
64
42
 
65
- // 3. Convert Headings
66
- result = result.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `<b>${c.trim().toUpperCase()}</b>\n\n`)
43
+ // 3. Clean Headings
44
+ result = result.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `<b>${c.trim()}</b>\n\n`)
67
45
  result = result.replace(/<h[2-6][^>]*>([\s\S]*?)<\/h[2-6]>/gi, (_, c) => `<b>${c.trim()}</b>\n\n`)
68
46
 
69
- // 4. Convert Task Lists & Unordered/Ordered Lists
47
+ // 4. Task Lists & Bullet Lists
70
48
  result = result.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, listContent) => {
71
49
  return (
72
50
  listContent
@@ -86,34 +64,28 @@ export function sanitizeRichHtml(html: string): string {
86
64
  )
87
65
  })
88
66
 
89
- // 5. Convert Pull Quotes / Asides
67
+ // 5. Clean Blockquotes & Dividers
90
68
  result = result.replace(/<aside[^>]*>([\s\S]*?)(?:<cite[^>]*>([\s\S]*?)<\/cite>)?<\/aside>/gi, (_, quote, cite) => {
91
69
  return `<blockquote>${quote.trim()}${cite ? `\n— <i>${cite.trim()}</i>` : ''}</blockquote>\n\n`
92
70
  })
93
71
 
94
- // 6. Convert Special Bot API 10.1 tags
95
72
  result = result.replace(/<tg-thinking[^>]*>([\s\S]*?)<\/tg-thinking>/gi, (_, c) => `<blockquote>💭 <i>${c.trim()}</i></blockquote>\n\n`)
96
73
  result = result.replace(/<tg-math-block[^>]*>([\s\S]*?)<\/tg-math-block>/gi, (_, c) => `<pre><code>${c.trim()}</code></pre>\n\n`)
97
74
  result = result.replace(/<tg-reference[^>]*>([\s\S]*?)<\/tg-reference>/gi, (_, c) => `<i>${c.trim()}</i>`)
98
75
  result = result.replace(/<mark[^>]*>([\s\S]*?)<\/mark>/gi, (_, c) => `<b>[${c.trim()}]</b>`)
99
76
 
100
- // 7. Convert Figures & Captions
101
77
  result = result.replace(/<figure[^>]*>[\s\S]*?<figcaption[^>]*>([\s\S]*?)<\/figcaption><\/figure>/gi, (_, cap) => `\n<i>📷 ${cap.trim()}</i>\n`)
102
78
 
103
- // 8. Convert Paragraphs & Linebreaks
104
79
  result = result.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, c) => `${c.trim()}\n\n`)
105
80
  result = result.replace(/<footer[^>]*>([\s\S]*?)<\/footer>/gi, (_, c) => `<i>${c.trim()}</i>\n`)
106
81
  result = result.replace(/<br\s*\/?>/gi, '\n')
107
- result = result.replace(/<hr\s*\/?>/gi, '──────────\n')
82
+ result = result.replace(/<hr\s*\/?>/gi, '\n')
108
83
 
109
- // 9. Strip unsupported media & sub/sup tags but keep text
84
+ // Strip unsupported tags
110
85
  result = result.replace(/<sub[^>]*>([\s\S]*?)<\/sub>/gi, '$1')
111
86
  result = result.replace(/<sup[^>]*>([\s\S]*?)<\/sup>/gi, '$1')
112
87
  result = result.replace(/<tg-(map|collage|slideshow)[^>]*>([\s\S]*?)<\/tg-\1>/gi, '$2')
113
88
  result = result.replace(/<(img|video|audio|tg-map)[^>]*\/?>/gi, '')
114
89
 
115
- // 10. Clean up extra newlines (> 2 consecutive newlines)
116
- result = result.replace(/\n{3,}/g, '\n\n')
117
-
118
- return result.trim()
90
+ return result.replace(/\n{3,}/g, '\n\n').trim()
119
91
  }
package/src/telegram.ts CHANGED
@@ -1680,52 +1680,18 @@ export class Telegram extends ApiClient {
1680
1680
  }
1681
1681
 
1682
1682
  /**
1683
- * Universal editRichMessage to update text or media captions in-place with Omni-Format support.
1684
- * Auto-detects Text vs Media Caption and gracefully handles 'message is not modified'.
1683
+ * Universal in-place message editor with Modern Rich UI formatting.
1685
1684
  */
1686
1685
  async editRichMessage(
1687
- chatId: number | string | undefined,
1688
- messageId: number | string | undefined,
1689
- content: any,
1690
- extra?: any
1691
- ): Promise<any>
1692
- async editRichMessage(
1693
- chatId: number | string | undefined,
1694
- messageId: number | string | undefined,
1695
- inlineMessageId: string | undefined,
1686
+ chatId: number | string,
1687
+ messageId: number | string,
1696
1688
  content: any,
1697
1689
  extra?: any
1698
- ): Promise<any>
1699
- async editRichMessage(
1700
- chatId: number | string | undefined,
1701
- messageId: number | string | undefined,
1702
- contentOrInline: any,
1703
- contentOrExtra?: any,
1704
- maybeExtra?: any
1705
1690
  ): Promise<any> {
1706
- let inlineMessageId: string | undefined
1707
- let content: any
1708
- let extra: any = {}
1709
-
1710
- if (
1711
- typeof contentOrInline === 'string' &&
1712
- (typeof contentOrExtra === 'object' || typeof contentOrExtra === 'string') &&
1713
- maybeExtra !== undefined
1714
- ) {
1715
- inlineMessageId = contentOrInline
1716
- content = contentOrExtra
1717
- extra = maybeExtra || {}
1718
- } else {
1719
- content = contentOrInline
1720
- extra = contentOrExtra || {}
1721
- inlineMessageId = extra?.inline_message_id
1722
- }
1723
-
1724
1691
  let htmlText = ''
1725
1692
  let replyMarkup = extra?.reply_markup
1726
1693
 
1727
- // 1. Omni-format input extraction
1728
- if (content && typeof content.build === 'function') {
1694
+ if (typeof content?.build === 'function') {
1729
1695
  const built = content.build()
1730
1696
  if (typeof built === 'object' && built !== null) {
1731
1697
  htmlText = built.html || built.text || built.caption || ''
@@ -1736,7 +1702,7 @@ export class Telegram extends ApiClient {
1736
1702
  htmlText = String(built || '')
1737
1703
  }
1738
1704
  } else if (typeof content === 'object' && content !== null) {
1739
- htmlText = content.html || content.text || content.caption || content.markdown || ''
1705
+ htmlText = content.html || content.text || content.caption || ''
1740
1706
  if (content.reply_markup) {
1741
1707
  replyMarkup = replyMarkup || content.reply_markup
1742
1708
  }
@@ -1744,61 +1710,42 @@ export class Telegram extends ApiClient {
1744
1710
  htmlText = content
1745
1711
  }
1746
1712
 
1747
- // Sanitize rich HTML formatting (tables, headers, details, etc.)
1713
+ // Format ke Telegram Modern HTML
1748
1714
  htmlText = sanitizeRichHtml(htmlText)
1749
1715
 
1750
- // 2. Prepare Base Payload
1751
- const isInline = Boolean(inlineMessageId || extra?.inline_message_id)
1752
- const basePayload: any = {
1716
+ const payload: Record<string, any> = {
1717
+ chat_id: chatId,
1718
+ message_id: Number(messageId),
1719
+ text: htmlText,
1753
1720
  parse_mode: 'HTML',
1754
1721
  ...extra,
1755
1722
  }
1756
1723
 
1757
- if (isInline) {
1758
- basePayload.inline_message_id = inlineMessageId || extra.inline_message_id
1759
- } else {
1760
- basePayload.chat_id = chatId
1761
- basePayload.message_id = messageId != null ? Number(messageId) : undefined
1762
- }
1724
+ if (replyMarkup) payload.reply_markup = replyMarkup
1763
1725
 
1764
- if (replyMarkup) {
1765
- basePayload.reply_markup = replyMarkup
1766
- }
1767
-
1768
- // 3. Execution with Auto-Detect (Text -> Caption Fallback) and Silent Error Handling
1769
1726
  try {
1770
- return await this.callApi('editMessageText' as never, {
1771
- ...basePayload,
1772
- text: htmlText,
1773
- } as never)
1727
+ return await this.callApi('editMessageText' as never, payload as never)
1774
1728
  } catch (err: any) {
1775
1729
  const desc = String(err?.description || err?.message || '')
1776
-
1777
- // Fallback: If target is a Media message (Photo, Video, Audio, Document, Animation)
1778
- if (
1779
- desc.includes('no text in the message') ||
1780
- desc.includes('there is no text in the message to edit') ||
1781
- desc.includes('message to edit not found')
1782
- ) {
1730
+ // Fallback jika pesan berupa Media (Foto/Video)
1731
+ if (desc.includes('no text in the message') || desc.includes('message to edit not found')) {
1783
1732
  try {
1784
1733
  return await this.callApi('editMessageCaption' as never, {
1785
- ...basePayload,
1734
+ chat_id: chatId,
1735
+ message_id: Number(messageId),
1786
1736
  caption: htmlText,
1737
+ parse_mode: 'HTML',
1738
+ reply_markup: replyMarkup,
1739
+ ...extra,
1787
1740
  } as never)
1788
1741
  } catch (captionErr: any) {
1789
- const capDesc = String(captionErr?.description || captionErr?.message || '')
1790
- if (capDesc.includes('message is not modified')) {
1791
- return false
1792
- }
1742
+ if (String(captionErr?.description).includes('message is not modified')) return false
1793
1743
  throw captionErr
1794
1744
  }
1795
1745
  }
1796
-
1797
- // Gracefully handle 'message is not modified'
1798
1746
  if (desc.includes('message is not modified')) {
1799
1747
  return false
1800
1748
  }
1801
-
1802
1749
  throw err
1803
1750
  }
1804
1751
  }
@@ -591,11 +591,9 @@ export declare class Context<U extends Deunionize<tg.Update> = tg.Update> {
591
591
  */
592
592
  sendRichMessageDraft(draftId: number, richMessage: tg.InputRichMessage, extra?: tt.ExtraSendRichMessageDraft): Promise<never>;
593
593
  /**
594
- * Universal Context shortcut to edit message with Omni-Format support (RichHTMLBuilder, Object, or String),
595
- * auto-detection of Text vs Media Caption, and silent 'not modified' error handling.
594
+ * Context-aware shorthand for {@link Telegram.editRichMessage}.
596
595
  */
597
- editRichMessage(content: tg.RichContentInput, extra?: tg.EditRichOptions): Promise<any>;
598
- editRichMessage(messageId: number | string, content: tg.RichContentInput, extra?: tg.EditRichOptions): Promise<any>;
596
+ editRichMessage(richMessage: any, extra?: any): Promise<any>;
599
597
  /**
600
598
  * Enqueue a Telegram API call for the current chat to prevent 429 Too Many Requests errors.
601
599
  */
@@ -1,6 +1 @@
1
- /**
2
- * Converts Rich HTML strings (containing unsupported Telegram HTML tags like
3
- * <table>, <ul>, <ol>, <li>, <details>, <h1>-<h6>, <aside>, <p>, <hr>, etc.)
4
- * into standard Telegram HTML compatible with parse_mode: 'HTML' (editMessageText/sendMessage).
5
- */
6
1
  export declare function sanitizeRichHtml(html: string): string;
@@ -690,10 +690,8 @@ export declare class Telegram extends ApiClient {
690
690
  */
691
691
  sendRichMessageDraft(chatId: number, draftId: number, richMessage: tg.InputRichMessage, extra?: tt.ExtraSendRichMessageDraft): Promise<never>;
692
692
  /**
693
- * Universal editRichMessage to update text or media captions in-place with Omni-Format support.
694
- * Auto-detects Text vs Media Caption and gracefully handles 'message is not modified'.
693
+ * Universal in-place message editor with Modern Rich UI formatting.
695
694
  */
696
- editRichMessage(chatId: number | string | undefined, messageId: number | string | undefined, content: any, extra?: any): Promise<any>;
697
- editRichMessage(chatId: number | string | undefined, messageId: number | string | undefined, inlineMessageId: string | undefined, content: any, extra?: any): Promise<any>;
695
+ editRichMessage(chatId: number | string, messageId: number | string, content: any, extra?: any): Promise<any>;
698
696
  }
699
697
  export default Telegram;