teledzik 1.0.2 → 1.0.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.
@@ -1,30 +1,100 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sanitizeRichHtml = void 0;
4
2
  /**
5
- * Converts Rich HTML strings containing tags like <h1>, <h2>, <p>, <hr>
3
+ * Converts Rich HTML strings (containing unsupported Telegram HTML tags like
4
+ * <table>, <ul>, <ol>, <li>, <details>, <h1>-<h6>, <aside>, <p>, <hr>, etc.)
6
5
  * into standard Telegram HTML compatible with parse_mode: 'HTML' (editMessageText/sendMessage).
7
6
  */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.sanitizeRichHtml = void 0;
9
+ function convertTableToText(tableHtml) {
10
+ const rows = [];
11
+ const rowMatches = tableHtml.match(/<tr[^>]*>([\s\S]*?)<\/tr>/gi) || [];
12
+ for (const row of rowMatches) {
13
+ const cells = [];
14
+ const cellMatches = row.match(/<(th|td)[^>]*>([\s\S]*?)<\/\1>/gi) || [];
15
+ for (const cell of cellMatches) {
16
+ const content = cell.replace(/<(th|td)[^>]*>([\s\S]*?)<\/\1>/i, '$2').trim();
17
+ cells.push(content);
18
+ }
19
+ if (cells.length > 0)
20
+ rows.push(cells);
21
+ }
22
+ if (rows.length === 0)
23
+ 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);
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
+ 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`;
48
+ }
8
49
  function sanitizeRichHtml(html) {
9
50
  if (!html)
10
51
  return html;
11
52
  let result = html;
12
- // Convert H1: <h1>Text</h1> -> <b>TEXT</b>
13
- result = result.replace(/<h1[^>]*>(.*?)<\/h1>/gis, (_, content) => `<b>${content.trim().toUpperCase()}</b>\n\n`);
14
- // Convert H2..H6: <h2>Text</h2> -> <b>Text</b>
15
- result = result.replace(/<h[2-6][^>]*>(.*?)<\/h[2-6]>/gis, (_, content) => `<b>${content.trim()}</b>\n\n`);
16
- // Convert Paragraphs: <p>Text</p> -> Text\n\n
17
- result = result.replace(/<p[^>]*>(.*?)<\/p>/gis, (_, content) => `${content.trim()}\n\n`);
18
- // Convert Footer: <footer>Text</footer> -> <i>Text</i>
19
- result = result.replace(/<footer[^>]*>(.*?)<\/footer>/gis, (_, content) => `<i>${content.trim()}</i>\n`);
20
- // Convert Horizontal Rules: <hr/> or <hr> -> ──────────
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>
56
+ 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`;
58
+ });
59
+ // 3. Convert Headings
60
+ result = result.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `<b>${c.trim().toUpperCase()}</b>\n\n`);
61
+ 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
63
+ result = result.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, listContent) => {
64
+ return (listContent
65
+ .replace(/<li[^>]*>\s*<input[^>]*type="checkbox"[^>]*checked[^>]*>\s*([\s\S]*?)<\/li>/gi, '☑ $1\n')
66
+ .replace(/<li[^>]*>\s*<input[^>]*type="checkbox"[^>]*>\s*([\s\S]*?)<\/li>/gi, '☐ $1\n')
67
+ .replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, '• $1\n')
68
+ .trim() + '\n\n');
69
+ });
70
+ result = result.replace(/<ol[^>]*>([\s\S]*?)<\/ol>/gi, (_, listContent) => {
71
+ let index = 1;
72
+ return (listContent
73
+ .replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_match, item) => `${index++}. ${item.trim()}\n`)
74
+ .trim() + '\n\n');
75
+ });
76
+ // 5. Convert Pull Quotes / Asides
77
+ result = result.replace(/<aside[^>]*>([\s\S]*?)(?:<cite[^>]*>([\s\S]*?)<\/cite>)?<\/aside>/gi, (_, quote, cite) => {
78
+ return `<blockquote>${quote.trim()}${cite ? `\n— <i>${cite.trim()}</i>` : ''}</blockquote>\n\n`;
79
+ });
80
+ // 6. Convert Special Bot API 10.1 tags
81
+ result = result.replace(/<tg-thinking[^>]*>([\s\S]*?)<\/tg-thinking>/gi, (_, c) => `<blockquote>💭 <i>${c.trim()}</i></blockquote>\n\n`);
82
+ result = result.replace(/<tg-math-block[^>]*>([\s\S]*?)<\/tg-math-block>/gi, (_, c) => `<pre><code>${c.trim()}</code></pre>\n\n`);
83
+ result = result.replace(/<tg-reference[^>]*>([\s\S]*?)<\/tg-reference>/gi, (_, c) => `<i>${c.trim()}</i>`);
84
+ result = result.replace(/<mark[^>]*>([\s\S]*?)<\/mark>/gi, (_, c) => `<b>[${c.trim()}]</b>`);
85
+ // 7. Convert Figures & Captions
86
+ 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
+ result = result.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, c) => `${c.trim()}\n\n`);
89
+ result = result.replace(/<footer[^>]*>([\s\S]*?)<\/footer>/gi, (_, c) => `<i>${c.trim()}</i>\n`);
90
+ result = result.replace(/<br\s*\/?>/gi, '\n');
21
91
  result = result.replace(/<hr\s*\/?>/gi, '──────────\n');
22
- // Convert Marks: <mark>Text</mark> -> <b>[Text]</b>
23
- result = result.replace(/<mark[^>]*>(.*?)<\/mark>/gis, (_, content) => `<b>[${content.trim()}]</b>`);
24
- // Strip unsupported sub/sup tags but keep text
25
- result = result.replace(/<sub[^>]*>(.*?)<\/sub>/gis, '$1');
26
- result = result.replace(/<sup[^>]*>(.*?)<\/sup>/gis, '$1');
27
- // Trim extra trailing newlines (> 2 consecutive newlines)
92
+ // 9. Strip unsupported media & sub/sup tags but keep text
93
+ result = result.replace(/<sub[^>]*>([\s\S]*?)<\/sub>/gi, '$1');
94
+ result = result.replace(/<sup[^>]*>([\s\S]*?)<\/sup>/gi, '$1');
95
+ result = result.replace(/<tg-(map|collage|slideshow)[^>]*>([\s\S]*?)<\/tg-\1>/gi, '$2');
96
+ result = result.replace(/<(img|video|audio|tg-map)[^>]*\/?>/gi, '');
97
+ // 10. Clean up extra newlines (> 2 consecutive newlines)
28
98
  result = result.replace(/\n{3,}/g, '\n\n');
29
99
  return result.trim();
30
100
  }
package/lib/telegram.js CHANGED
@@ -105,6 +105,9 @@ class Telegram extends client_1.default {
105
105
  */
106
106
  sendMessage(chatId, text, extra) {
107
107
  const t = format_1.FmtString.normalise(text);
108
+ if (extra?.parse_mode === 'HTML' && typeof t.text === 'string') {
109
+ t.text = (0, rich_sanitizer_1.sanitizeRichHtml)(t.text);
110
+ }
108
111
  return this.callApi('sendMessage', { chat_id: chatId, ...extra, ...t });
109
112
  }
110
113
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teledzik",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Modern Telegram Bot Framework",
5
5
  "keywords": [
6
6
  "telekaf",
@@ -1,35 +1,118 @@
1
1
  /**
2
- * Converts Rich HTML strings containing tags like <h1>, <h2>, <p>, <hr>
2
+ * Converts Rich HTML strings (containing unsupported Telegram HTML tags like
3
+ * <table>, <ul>, <ol>, <li>, <details>, <h1>-<h6>, <aside>, <p>, <hr>, etc.)
3
4
  * into standard Telegram HTML compatible with parse_mode: 'HTML' (editMessageText/sendMessage).
4
5
  */
6
+
7
+ function convertTableToText(tableHtml: string): string {
8
+ const rows: string[][] = []
9
+ const rowMatches = tableHtml.match(/<tr[^>]*>([\s\S]*?)<\/tr>/gi) || []
10
+
11
+ for (const row of rowMatches) {
12
+ const cells: string[] = []
13
+ const cellMatches = row.match(/<(th|td)[^>]*>([\s\S]*?)<\/\1>/gi) || []
14
+ for (const cell of cellMatches) {
15
+ const content = cell.replace(/<(th|td)[^>]*>([\s\S]*?)<\/\1>/i, '$2').trim()
16
+ cells.push(content)
17
+ }
18
+ if (cells.length > 0) rows.push(cells)
19
+ }
20
+
21
+ if (rows.length === 0) return ''
22
+
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)
29
+ })
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`
50
+ }
51
+
5
52
  export function sanitizeRichHtml(html: string): string {
6
53
  if (!html) return html
7
54
 
8
55
  let result = html
9
56
 
10
- // Convert H1: <h1>Text</h1> -> <b>TEXT</b>
11
- result = result.replace(/<h1[^>]*>(.*?)<\/h1>/gis, (_, content) => `<b>${content.trim().toUpperCase()}</b>\n\n`)
57
+ // 1. Convert Tables: <table>...</table> -> <pre>formatted table</pre>
58
+ result = result.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, content) => convertTableToText(content))
12
59
 
13
- // Convert H2..H6: <h2>Text</h2> -> <b>Text</b>
14
- result = result.replace(/<h[2-6][^>]*>(.*?)<\/h[2-6]>/gis, (_, content) => `<b>${content.trim()}</b>\n\n`)
60
+ // 2. Convert Details / Expandable: <details><summary>Title</summary>Content</details>
61
+ 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`
63
+ })
15
64
 
16
- // Convert Paragraphs: <p>Text</p> -> Text\n\n
17
- result = result.replace(/<p[^>]*>(.*?)<\/p>/gis, (_, content) => `${content.trim()}\n\n`)
65
+ // 3. Convert Headings
66
+ result = result.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `<b>${c.trim().toUpperCase()}</b>\n\n`)
67
+ result = result.replace(/<h[2-6][^>]*>([\s\S]*?)<\/h[2-6]>/gi, (_, c) => `<b>${c.trim()}</b>\n\n`)
18
68
 
19
- // Convert Footer: <footer>Text</footer> -> <i>Text</i>
20
- result = result.replace(/<footer[^>]*>(.*?)<\/footer>/gis, (_, content) => `<i>${content.trim()}</i>\n`)
69
+ // 4. Convert Task Lists & Unordered/Ordered Lists
70
+ result = result.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, listContent) => {
71
+ return (
72
+ listContent
73
+ .replace(/<li[^>]*>\s*<input[^>]*type="checkbox"[^>]*checked[^>]*>\s*([\s\S]*?)<\/li>/gi, '☑ $1\n')
74
+ .replace(/<li[^>]*>\s*<input[^>]*type="checkbox"[^>]*>\s*([\s\S]*?)<\/li>/gi, '☐ $1\n')
75
+ .replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, '• $1\n')
76
+ .trim() + '\n\n'
77
+ )
78
+ })
21
79
 
22
- // Convert Horizontal Rules: <hr/> or <hr> -> ──────────
23
- result = result.replace(/<hr\s*\/?>/gi, '──────────\n')
80
+ result = result.replace(/<ol[^>]*>([\s\S]*?)<\/ol>/gi, (_: string, listContent: string) => {
81
+ let index = 1
82
+ return (
83
+ listContent
84
+ .replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_match: string, item: string) => `${index++}. ${item.trim()}\n`)
85
+ .trim() + '\n\n'
86
+ )
87
+ })
88
+
89
+ // 5. Convert Pull Quotes / Asides
90
+ result = result.replace(/<aside[^>]*>([\s\S]*?)(?:<cite[^>]*>([\s\S]*?)<\/cite>)?<\/aside>/gi, (_, quote, cite) => {
91
+ return `<blockquote>${quote.trim()}${cite ? `\n— <i>${cite.trim()}</i>` : ''}</blockquote>\n\n`
92
+ })
24
93
 
25
- // Convert Marks: <mark>Text</mark> -> <b>[Text]</b>
26
- result = result.replace(/<mark[^>]*>(.*?)<\/mark>/gis, (_, content) => `<b>[${content.trim()}]</b>`)
94
+ // 6. Convert Special Bot API 10.1 tags
95
+ result = result.replace(/<tg-thinking[^>]*>([\s\S]*?)<\/tg-thinking>/gi, (_, c) => `<blockquote>💭 <i>${c.trim()}</i></blockquote>\n\n`)
96
+ result = result.replace(/<tg-math-block[^>]*>([\s\S]*?)<\/tg-math-block>/gi, (_, c) => `<pre><code>${c.trim()}</code></pre>\n\n`)
97
+ result = result.replace(/<tg-reference[^>]*>([\s\S]*?)<\/tg-reference>/gi, (_, c) => `<i>${c.trim()}</i>`)
98
+ result = result.replace(/<mark[^>]*>([\s\S]*?)<\/mark>/gi, (_, c) => `<b>[${c.trim()}]</b>`)
99
+
100
+ // 7. Convert Figures & Captions
101
+ result = result.replace(/<figure[^>]*>[\s\S]*?<figcaption[^>]*>([\s\S]*?)<\/figcaption><\/figure>/gi, (_, cap) => `\n<i>📷 ${cap.trim()}</i>\n`)
102
+
103
+ // 8. Convert Paragraphs & Linebreaks
104
+ result = result.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, c) => `${c.trim()}\n\n`)
105
+ result = result.replace(/<footer[^>]*>([\s\S]*?)<\/footer>/gi, (_, c) => `<i>${c.trim()}</i>\n`)
106
+ result = result.replace(/<br\s*\/?>/gi, '\n')
107
+ result = result.replace(/<hr\s*\/?>/gi, '──────────\n')
27
108
 
28
- // Strip unsupported sub/sup tags but keep text
29
- result = result.replace(/<sub[^>]*>(.*?)<\/sub>/gis, '$1')
30
- result = result.replace(/<sup[^>]*>(.*?)<\/sup>/gis, '$1')
109
+ // 9. Strip unsupported media & sub/sup tags but keep text
110
+ result = result.replace(/<sub[^>]*>([\s\S]*?)<\/sub>/gi, '$1')
111
+ result = result.replace(/<sup[^>]*>([\s\S]*?)<\/sup>/gi, '$1')
112
+ result = result.replace(/<tg-(map|collage|slideshow)[^>]*>([\s\S]*?)<\/tg-\1>/gi, '$2')
113
+ result = result.replace(/<(img|video|audio|tg-map)[^>]*\/?>/gi, '')
31
114
 
32
- // Trim extra trailing newlines (> 2 consecutive newlines)
115
+ // 10. Clean up extra newlines (> 2 consecutive newlines)
33
116
  result = result.replace(/\n{3,}/g, '\n\n')
34
117
 
35
118
  return result.trim()
package/src/telegram.ts CHANGED
@@ -138,6 +138,9 @@ export class Telegram extends ApiClient {
138
138
  extra?: tt.ExtraReplyMessage
139
139
  ) {
140
140
  const t = FmtString.normalise(text)
141
+ if (extra?.parse_mode === 'HTML' && typeof t.text === 'string') {
142
+ t.text = sanitizeRichHtml(t.text)
143
+ }
141
144
  return this.callApi('sendMessage', { chat_id: chatId, ...extra, ...t })
142
145
  }
143
146
 
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Converts Rich HTML strings containing tags like <h1>, <h2>, <p>, <hr>
2
+ * Converts Rich HTML strings (containing unsupported Telegram HTML tags like
3
+ * <table>, <ul>, <ol>, <li>, <details>, <h1>-<h6>, <aside>, <p>, <hr>, etc.)
3
4
  * into standard Telegram HTML compatible with parse_mode: 'HTML' (editMessageText/sendMessage).
4
5
  */
5
6
  export declare function sanitizeRichHtml(html: string): string;