teledzik 1.0.6 → 1.0.8

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
@@ -8,21 +8,21 @@
8
8
 
9
9
  <div align="center">
10
10
  <img src="docs/assets/logo.svg" alt="logo" height="90" align="center">
11
- <h1 align="center">telegraf.js</h1>
11
+ <h1 align="center">teledzik</h1>
12
12
 
13
- <p>Modern Telegram Bot API framework for Node.js</p>
13
+ <p>Modern Telegram Bot API framework for Node.js (with Native Rich Messages &amp; Full In-Place Edit)</p>
14
14
 
15
- <a href="https://core.telegram.org/bots/api">
16
- <img src="https://img.shields.io/badge/Bot%20API-v10.1-f36caf.svg?style=flat-square" alt="Bot API Version" />
15
+ <a href="https://www.npmjs.com/package/teledzik">
16
+ <img src="https://img.shields.io/npm/v/teledzik.svg?style=flat-square" alt="npm version" />
17
17
  </a>
18
- <a href="https://packagephobia.com/result?p=telegraf,node-telegram-bot-api">
19
- <img src="https://flat.badgen.net/packagephobia/install/telegraf" alt="install size" />
18
+ <a href="https://core.telegram.org/bots/api">
19
+ <img src="https://img.shields.io/badge/Bot%20API-v10.1+-f36caf.svg?style=flat-square" alt="Bot API Version" />
20
20
  </a>
21
- <a href="https://github.com/telegraf/telegraf">
22
- <img src="https://img.shields.io/github/languages/top/telegraf/telegraf?style=flat-square&logo=github" alt="GitHub top language" />
21
+ <a href="https://packagephobia.com/result?p=teledzik">
22
+ <img src="https://flat.badgen.net/packagephobia/install/teledzik" alt="install size" />
23
23
  </a>
24
- <a href="https://telegram.me/TelegrafJSChat">
25
- <img src="https://img.shields.io/badge/English%20chat-grey?style=flat-square&logo=telegram" alt="English chat" />
24
+ <a href="https://t.me/lorddzik">
25
+ <img src="https://img.shields.io/badge/Telegram-@lorddzik-blue?style=flat-square&logo=telegram" alt="Maintainer Telegram" />
26
26
  </a>
27
27
  </div>
28
28
 
@@ -99,14 +99,16 @@ process.once('SIGTERM', () => bot.stop('SIGTERM'))
99
99
 
100
100
  ---
101
101
 
102
- ### Sending Methods
102
+ ### Sending & Editing Methods
103
103
 
104
104
  | Method | Description |
105
105
  |---|---|
106
106
  | `ctx.sendRichMessage(msg, extra?)` | Send a rich message to the current chat |
107
107
  | `ctx.replyWithRichMessageContent(msg, extra?)` | Send a rich message quoting the current message |
108
+ | `ctx.editRichMessage(msg, extra?)` | In-place edit of message with native Rich UI preservation (v1.0.8+) |
108
109
  | `ctx.sendRichMessageDraft(draftId, msg, extra?)` | Stream a partial draft (private chats only) |
109
- | `ctx.telegram.sendRichMessage(chatId, msg, extra?)` | Explicit call with chat ID |
110
+ | `ctx.telegram.sendRichMessage(chatId, msg, extra?)` | Explicit send with chat ID |
111
+ | `ctx.telegram.editRichMessage(chatId, messageId, msg, extra?)` | Explicit edit with chat ID and message ID |
110
112
  | `ctx.telegram.sendRichMessageDraft(chatId, draftId, msg, extra?)` | Explicit streaming with chat ID |
111
113
 
112
114
  **`extra` options for `sendRichMessage`:**
@@ -645,17 +647,30 @@ bot.on('inline_query', async (ctx) => {
645
647
 
646
648
  ## Exclusive Teledzik Enhancements
647
649
 
648
- ### Auto HTML Sanitizer & Edit Rich Message
650
+ ### Native `editRichMessage` & Auto-Sanitizer (v1.0.8+)
649
651
 
650
- Standard Telegram Bot API's `editMessageText` (`parse_mode: 'HTML'`) fails with `400 CANNOT_PARSE_ENTITIES` when using block HTML tags like `<h1>`, `<h2>`, or `<p>`.
652
+ Standard `editMessageText` (`parse_mode: 'HTML'`) fails or degrades layout when editing complex structured blocks. In `teledzik` v1.0.8+, `ctx.editRichMessage` passes the payload natively via Telegram's `rich_message` parameter:
651
653
 
652
- `teledzik` automatically sanitizes HTML strings on `editMessageText` to convert headers to supported bold text, preventing API errors:
654
+ - **Preserves Full Rich UI**: In-place edits keep native `<table>`, `<h1>`-`<h6>`, collapsible cards (`<details>` / `<blockquote expandable>`), and styled elements without flattening them into plain text.
655
+ - **Smart Key-Value Table Handling**: 2-column tables correctly retain all rows and headers without accidental truncation.
656
+ - **Resilient Media & No-Op Handling**: Automatically falls back to `editMessageCaption` if editing a photo or video message, and silently resolves `message is not modified` instead of throwing unhandled exceptions.
653
657
 
654
658
  ```ts
655
- // Safely edits text containing <h1>/<h2> without throwing 400 CANNOT_PARSE_ENTITIES!
656
- await ctx.editMessageText('<h1>New Title</h1><p>Updated content</p>', { parse_mode: 'HTML' })
659
+ // 1. Edit with RichHTMLBuilder
660
+ const rich = new RichHTMLBuilder()
661
+ .heading(2, 'Order Confirmed')
662
+ .table([
663
+ ['Product', 'Status'],
664
+ ['VPS-01', 'ACTIVE 🟢']
665
+ ])
666
+
667
+ await ctx.editRichMessage(rich, {
668
+ reply_markup: Markup.inlineKeyboard([
669
+ [Markup.button.callback('Back to Menu', 'menu')]
670
+ ]).reply_markup
671
+ })
657
672
 
658
- // Or use the native rich message edit helper:
673
+ // 2. Edit with raw HTML / Rich Object
659
674
  await ctx.editRichMessage({ html: '<h1>Title</h1><p>Body</p>' })
660
675
  ```
661
676
 
@@ -1,15 +1,13 @@
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 = [];
6
+ let hasHeader = false;
11
7
  const rowMatches = tableHtml.match(/<tr[^>]*>([\s\S]*?)<\/tr>/gi) || [];
12
8
  for (const row of rowMatches) {
9
+ if (/<th[^>]*>/i.test(row))
10
+ hasHeader = true;
13
11
  const cells = [];
14
12
  const cellMatches = row.match(/<(th|td)[^>]*>([\s\S]*?)<\/\1>/gi) || [];
15
13
  for (const cell of cellMatches) {
@@ -21,45 +19,38 @@ function convertTableToText(tableHtml) {
21
19
  }
22
20
  if (rows.length === 0)
23
21
  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);
22
+ // Header vs Row handling (Format sebagai Kartu Elegan)
23
+ const isKeyValue = rows.length > 0 && rows.every((r) => r.length === 2);
24
+ let cardText = '';
25
+ if (isKeyValue) {
26
+ const dataRows = hasHeader ? rows.slice(1) : rows;
27
+ const lines = dataRows.map((r) => `• <b>${r[0]}:</b> ${r[1]}`);
28
+ cardText = `<blockquote>\n${lines.join('\n')}\n</blockquote>\n\n`;
29
+ }
30
+ else {
31
+ const lines = rows.map((r, idx) => {
32
+ if (idx === 0 && hasHeader)
33
+ return `<b>${r.join(' │ ')}</b>`;
34
+ return `${r.join(' │ ')}`;
30
35
  });
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`;
36
+ cardText = `<blockquote>\n${lines.join('\n')}\n</blockquote>\n\n`;
37
+ }
38
+ return cardText;
48
39
  }
49
40
  function sanitizeRichHtml(html) {
50
41
  if (!html)
51
42
  return html;
52
43
  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>
44
+ // 1. Convert Tables to Clean Modern Cards
45
+ result = result.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, content) => formatTableToCard(content));
46
+ // 2. Convert Details to Telegram Native Expandable Blockquotes
56
47
  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`;
48
+ return `<blockquote expandable><b>ā–¶ ${title.trim()}</b>\n${body.trim()}</blockquote>\n\n`;
58
49
  });
59
- // 3. Convert Headings
60
- result = result.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `<b>${c.trim().toUpperCase()}</b>\n\n`);
50
+ // 3. Clean Headings
51
+ result = result.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `<b>${c.trim()}</b>\n\n`);
61
52
  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
53
+ // 4. Task Lists & Bullet Lists
63
54
  result = result.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, listContent) => {
64
55
  return (listContent
65
56
  .replace(/<li[^>]*>\s*<input[^>]*type="checkbox"[^>]*checked[^>]*>\s*([\s\S]*?)<\/li>/gi, 'ā˜‘ $1\n')
@@ -73,29 +64,24 @@ function sanitizeRichHtml(html) {
73
64
  .replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_match, item) => `${index++}. ${item.trim()}\n`)
74
65
  .trim() + '\n\n');
75
66
  });
76
- // 5. Convert Pull Quotes / Asides
67
+ // 5. Clean Blockquotes & Dividers
77
68
  result = result.replace(/<aside[^>]*>([\s\S]*?)(?:<cite[^>]*>([\s\S]*?)<\/cite>)?<\/aside>/gi, (_, quote, cite) => {
78
69
  return `<blockquote>${quote.trim()}${cite ? `\n— <i>${cite.trim()}</i>` : ''}</blockquote>\n\n`;
79
70
  });
80
- // 6. Convert Special Bot API 10.1 tags
81
71
  result = result.replace(/<tg-thinking[^>]*>([\s\S]*?)<\/tg-thinking>/gi, (_, c) => `<blockquote>šŸ’­ <i>${c.trim()}</i></blockquote>\n\n`);
82
72
  result = result.replace(/<tg-math-block[^>]*>([\s\S]*?)<\/tg-math-block>/gi, (_, c) => `<pre><code>${c.trim()}</code></pre>\n\n`);
83
73
  result = result.replace(/<tg-reference[^>]*>([\s\S]*?)<\/tg-reference>/gi, (_, c) => `<i>${c.trim()}</i>`);
84
74
  result = result.replace(/<mark[^>]*>([\s\S]*?)<\/mark>/gi, (_, c) => `<b>[${c.trim()}]</b>`);
85
- // 7. Convert Figures & Captions
86
75
  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
76
  result = result.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, c) => `${c.trim()}\n\n`);
89
77
  result = result.replace(/<footer[^>]*>([\s\S]*?)<\/footer>/gi, (_, c) => `<i>${c.trim()}</i>\n`);
90
78
  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
79
+ result = result.replace(/<hr\s*\/?>/gi, '\n');
80
+ // Strip unsupported tags
93
81
  result = result.replace(/<sub[^>]*>([\s\S]*?)<\/sub>/gi, '$1');
94
82
  result = result.replace(/<sup[^>]*>([\s\S]*?)<\/sup>/gi, '$1');
95
83
  result = result.replace(/<tg-(map|collage|slideshow)[^>]*>([\s\S]*?)<\/tg-\1>/gi, '$2');
96
84
  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();
85
+ return result.replace(/\n{3,}/g, '\n\n').trim();
100
86
  }
101
87
  exports.sanitizeRichHtml = sanitizeRichHtml;
package/lib/telegram.js CHANGED
@@ -1272,25 +1272,83 @@ class Telegram extends client_1.default {
1272
1272
  });
1273
1273
  }
1274
1274
  /**
1275
- * Edit a rich message in-place while preserving 100% Native Rich UI layout (tables, headings, cards).
1276
- * @see https://core.telegram.org/bots/api#editrichmessage
1277
- */
1278
- async editRichMessage(chatId, messageId, richMessage, extra) {
1275
+ * Universal in-place message editor with Modern Rich UI formatting.
1276
+ */
1277
+ async editRichMessage(chatId, messageId, content, extra) {
1278
+ let richPayload = content;
1279
+ let htmlText = '';
1280
+ let replyMarkup = extra?.reply_markup;
1281
+ if (typeof content?.build === 'function') {
1282
+ const built = content.build();
1283
+ richPayload = built;
1284
+ if (typeof built === 'object' && built !== null) {
1285
+ htmlText = built.html || built.text || built.caption || '';
1286
+ if (built.reply_markup) {
1287
+ replyMarkup = replyMarkup || built.reply_markup;
1288
+ }
1289
+ }
1290
+ else {
1291
+ htmlText = String(built || '');
1292
+ }
1293
+ }
1294
+ else if (typeof content === 'object' && content !== null) {
1295
+ richPayload = content;
1296
+ htmlText = content.html || content.text || content.caption || '';
1297
+ if (content.reply_markup) {
1298
+ replyMarkup = replyMarkup || content.reply_markup;
1299
+ }
1300
+ }
1301
+ else if (typeof content === 'string') {
1302
+ htmlText = content;
1303
+ richPayload = content.startsWith('{') || content.includes('<') ? content : { text: content };
1304
+ }
1279
1305
  const payload = {
1280
1306
  chat_id: chatId,
1281
1307
  message_id: Number(messageId),
1282
- rich_message: typeof richMessage?.build === 'function' ? richMessage.build() : richMessage,
1308
+ rich_message: richPayload,
1283
1309
  ...extra,
1284
1310
  };
1311
+ if (replyMarkup)
1312
+ payload.reply_markup = replyMarkup;
1285
1313
  try {
1286
- return await this.callApi('editRichMessage', payload);
1314
+ return await this.callApi('editMessageText', payload);
1287
1315
  }
1288
1316
  catch (err) {
1289
1317
  const desc = String(err?.description || err?.message || '');
1290
- // Silent ignore jika konten pesan tidak mengalami perubahan
1318
+ // Ignore if message is unchanged
1291
1319
  if (desc.includes('message is not modified')) {
1292
1320
  return false;
1293
1321
  }
1322
+ const fallbackHtml = (0, rich_sanitizer_1.sanitizeRichHtml)(htmlText);
1323
+ // Fallback jika pesan berupa Media (Foto/Video)
1324
+ if (desc.includes('no text in the message') || desc.includes('message to edit not found')) {
1325
+ try {
1326
+ return await this.callApi('editMessageCaption', {
1327
+ chat_id: chatId,
1328
+ message_id: Number(messageId),
1329
+ caption: fallbackHtml,
1330
+ parse_mode: 'HTML',
1331
+ reply_markup: replyMarkup,
1332
+ ...extra,
1333
+ });
1334
+ }
1335
+ catch (captionErr) {
1336
+ if (String(captionErr?.description).includes('message is not modified'))
1337
+ return false;
1338
+ throw captionErr;
1339
+ }
1340
+ }
1341
+ // Fallback untuk Bot API versi lama tanpa parameter rich_message pada editMessageText
1342
+ if (desc.includes('rich_message') || desc.includes('not supported') || desc.includes('wrong type')) {
1343
+ return await this.callApi('editMessageText', {
1344
+ chat_id: chatId,
1345
+ message_id: Number(messageId),
1346
+ text: fallbackHtml,
1347
+ parse_mode: 'HTML',
1348
+ reply_markup: replyMarkup,
1349
+ ...extra,
1350
+ });
1351
+ }
1294
1352
  throw err;
1295
1353
  }
1296
1354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teledzik",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Modern Telegram Bot Framework",
5
5
  "keywords": [
6
6
  "telekaf",
@@ -1,14 +1,9 @@
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[][] = []
3
+ let hasHeader = false
9
4
  const rowMatches = tableHtml.match(/<tr[^>]*>([\s\S]*?)<\/tr>/gi) || []
10
-
11
5
  for (const row of rowMatches) {
6
+ if (/<th[^>]*>/i.test(row)) hasHeader = true
12
7
  const cells: string[] = []
13
8
  const cellMatches = row.match(/<(th|td)[^>]*>([\s\S]*?)<\/\1>/gi) || []
14
9
  for (const cell of cellMatches) {
@@ -17,56 +12,42 @@ function convertTableToText(tableHtml: string): string {
17
12
  }
18
13
  if (cells.length > 0) rows.push(cells)
19
14
  }
20
-
21
15
  if (rows.length === 0) return ''
22
16
 
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)
17
+ // Header vs Row handling (Format sebagai Kartu Elegan)
18
+ const isKeyValue = rows.length > 0 && rows.every((r) => r.length === 2)
19
+ let cardText = ''
20
+ if (isKeyValue) {
21
+ const dataRows = hasHeader ? rows.slice(1) : rows
22
+ const lines = dataRows.map((r) => `• <b>${r[0]}:</b> ${r[1]}`)
23
+ cardText = `<blockquote>\n${lines.join('\n')}\n</blockquote>\n\n`
24
+ } else {
25
+ const lines = rows.map((r, idx) => {
26
+ if (idx === 0 && hasHeader) return `<b>${r.join(' │ ')}</b>`
27
+ return `${r.join(' │ ')}`
29
28
  })
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`
29
+ cardText = `<blockquote>\n${lines.join('\n')}\n</blockquote>\n\n`
30
+ }
31
+ return cardText
50
32
  }
51
33
 
52
34
  export function sanitizeRichHtml(html: string): string {
53
35
  if (!html) return html
54
-
55
36
  let result = html
56
37
 
57
- // 1. Convert Tables: <table>...</table> -> <pre>formatted table</pre>
58
- result = result.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, content) => convertTableToText(content))
38
+ // 1. Convert Tables to Clean Modern Cards
39
+ result = result.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, content) => formatTableToCard(content))
59
40
 
60
- // 2. Convert Details / Expandable: <details><summary>Title</summary>Content</details>
41
+ // 2. Convert Details to Telegram Native Expandable Blockquotes
61
42
  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`
43
+ return `<blockquote expandable><b>ā–¶ ${title.trim()}</b>\n${body.trim()}</blockquote>\n\n`
63
44
  })
64
45
 
65
- // 3. Convert Headings
66
- 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`)
67
48
  result = result.replace(/<h[2-6][^>]*>([\s\S]*?)<\/h[2-6]>/gi, (_, c) => `<b>${c.trim()}</b>\n\n`)
68
49
 
69
- // 4. Convert Task Lists & Unordered/Ordered Lists
50
+ // 4. Task Lists & Bullet Lists
70
51
  result = result.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, listContent) => {
71
52
  return (
72
53
  listContent
@@ -86,34 +67,28 @@ export function sanitizeRichHtml(html: string): string {
86
67
  )
87
68
  })
88
69
 
89
- // 5. Convert Pull Quotes / Asides
70
+ // 5. Clean Blockquotes & Dividers
90
71
  result = result.replace(/<aside[^>]*>([\s\S]*?)(?:<cite[^>]*>([\s\S]*?)<\/cite>)?<\/aside>/gi, (_, quote, cite) => {
91
72
  return `<blockquote>${quote.trim()}${cite ? `\n— <i>${cite.trim()}</i>` : ''}</blockquote>\n\n`
92
73
  })
93
74
 
94
- // 6. Convert Special Bot API 10.1 tags
95
75
  result = result.replace(/<tg-thinking[^>]*>([\s\S]*?)<\/tg-thinking>/gi, (_, c) => `<blockquote>šŸ’­ <i>${c.trim()}</i></blockquote>\n\n`)
96
76
  result = result.replace(/<tg-math-block[^>]*>([\s\S]*?)<\/tg-math-block>/gi, (_, c) => `<pre><code>${c.trim()}</code></pre>\n\n`)
97
77
  result = result.replace(/<tg-reference[^>]*>([\s\S]*?)<\/tg-reference>/gi, (_, c) => `<i>${c.trim()}</i>`)
98
78
  result = result.replace(/<mark[^>]*>([\s\S]*?)<\/mark>/gi, (_, c) => `<b>[${c.trim()}]</b>`)
99
79
 
100
- // 7. Convert Figures & Captions
101
80
  result = result.replace(/<figure[^>]*>[\s\S]*?<figcaption[^>]*>([\s\S]*?)<\/figcaption><\/figure>/gi, (_, cap) => `\n<i>šŸ“· ${cap.trim()}</i>\n`)
102
81
 
103
- // 8. Convert Paragraphs & Linebreaks
104
82
  result = result.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, c) => `${c.trim()}\n\n`)
105
83
  result = result.replace(/<footer[^>]*>([\s\S]*?)<\/footer>/gi, (_, c) => `<i>${c.trim()}</i>\n`)
106
84
  result = result.replace(/<br\s*\/?>/gi, '\n')
107
- result = result.replace(/<hr\s*\/?>/gi, '──────────\n')
85
+ result = result.replace(/<hr\s*\/?>/gi, '\n')
108
86
 
109
- // 9. Strip unsupported media & sub/sup tags but keep text
87
+ // Strip unsupported tags
110
88
  result = result.replace(/<sub[^>]*>([\s\S]*?)<\/sub>/gi, '$1')
111
89
  result = result.replace(/<sup[^>]*>([\s\S]*?)<\/sup>/gi, '$1')
112
90
  result = result.replace(/<tg-(map|collage|slideshow)[^>]*>([\s\S]*?)<\/tg-\1>/gi, '$2')
113
91
  result = result.replace(/<(img|video|audio|tg-map)[^>]*\/?>/gi, '')
114
92
 
115
- // 10. Clean up extra newlines (> 2 consecutive newlines)
116
- result = result.replace(/\n{3,}/g, '\n\n')
117
-
118
- return result.trim()
93
+ return result.replace(/\n{3,}/g, '\n\n').trim()
119
94
  }
package/src/telegram.ts CHANGED
@@ -1680,29 +1680,85 @@ export class Telegram extends ApiClient {
1680
1680
  }
1681
1681
 
1682
1682
  /**
1683
- * Edit a rich message in-place while preserving 100% Native Rich UI layout (tables, headings, cards).
1684
- * @see https://core.telegram.org/bots/api#editrichmessage
1683
+ * Universal in-place message editor with Modern Rich UI formatting.
1685
1684
  */
1686
1685
  async editRichMessage(
1687
1686
  chatId: number | string,
1688
1687
  messageId: number | string,
1689
- richMessage: any,
1688
+ content: any,
1690
1689
  extra?: any
1691
1690
  ): Promise<any> {
1691
+ let richPayload: any = content
1692
+ let htmlText = ''
1693
+ let replyMarkup = extra?.reply_markup
1694
+
1695
+ if (typeof content?.build === 'function') {
1696
+ const built = content.build()
1697
+ richPayload = built
1698
+ if (typeof built === 'object' && built !== null) {
1699
+ htmlText = built.html || built.text || built.caption || ''
1700
+ if (built.reply_markup) {
1701
+ replyMarkup = replyMarkup || built.reply_markup
1702
+ }
1703
+ } else {
1704
+ htmlText = String(built || '')
1705
+ }
1706
+ } else if (typeof content === 'object' && content !== null) {
1707
+ richPayload = content
1708
+ htmlText = content.html || content.text || content.caption || ''
1709
+ if (content.reply_markup) {
1710
+ replyMarkup = replyMarkup || content.reply_markup
1711
+ }
1712
+ } else if (typeof content === 'string') {
1713
+ htmlText = content
1714
+ richPayload = content.startsWith('{') || content.includes('<') ? content : { text: content }
1715
+ }
1716
+
1692
1717
  const payload: Record<string, any> = {
1693
1718
  chat_id: chatId,
1694
1719
  message_id: Number(messageId),
1695
- rich_message: typeof richMessage?.build === 'function' ? richMessage.build() : richMessage,
1720
+ rich_message: richPayload,
1696
1721
  ...extra,
1697
1722
  }
1723
+
1724
+ if (replyMarkup) payload.reply_markup = replyMarkup
1725
+
1698
1726
  try {
1699
- return await this.callApi('editRichMessage' as never, payload as never)
1727
+ return await this.callApi('editMessageText' as never, payload as never)
1700
1728
  } catch (err: any) {
1701
1729
  const desc = String(err?.description || err?.message || '')
1702
- // Silent ignore jika konten pesan tidak mengalami perubahan
1730
+ // Ignore if message is unchanged
1703
1731
  if (desc.includes('message is not modified')) {
1704
1732
  return false
1705
1733
  }
1734
+ const fallbackHtml = sanitizeRichHtml(htmlText)
1735
+ // Fallback jika pesan berupa Media (Foto/Video)
1736
+ if (desc.includes('no text in the message') || desc.includes('message to edit not found')) {
1737
+ try {
1738
+ return await this.callApi('editMessageCaption' as never, {
1739
+ chat_id: chatId,
1740
+ message_id: Number(messageId),
1741
+ caption: fallbackHtml,
1742
+ parse_mode: 'HTML',
1743
+ reply_markup: replyMarkup,
1744
+ ...extra,
1745
+ } as never)
1746
+ } catch (captionErr: any) {
1747
+ if (String(captionErr?.description).includes('message is not modified')) return false
1748
+ throw captionErr
1749
+ }
1750
+ }
1751
+ // Fallback untuk Bot API versi lama tanpa parameter rich_message pada editMessageText
1752
+ if (desc.includes('rich_message') || desc.includes('not supported') || desc.includes('wrong type')) {
1753
+ return await this.callApi('editMessageText' as never, {
1754
+ chat_id: chatId,
1755
+ message_id: Number(messageId),
1756
+ text: fallbackHtml,
1757
+ parse_mode: 'HTML',
1758
+ reply_markup: replyMarkup,
1759
+ ...extra,
1760
+ } as never)
1761
+ }
1706
1762
  throw err
1707
1763
  }
1708
1764
  }
@@ -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,9 +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
- * Edit a rich message in-place while preserving 100% Native Rich UI layout (tables, headings, cards).
694
- * @see https://core.telegram.org/bots/api#editrichmessage
693
+ * Universal in-place message editor with Modern Rich UI formatting.
695
694
  */
696
- editRichMessage(chatId: number | string, messageId: number | string, richMessage: any, extra?: any): Promise<any>;
695
+ editRichMessage(chatId: number | string, messageId: number | string, content: any, extra?: any): Promise<any>;
697
696
  }
698
697
  export default Telegram;