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
|
|
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
|
|
13
|
-
result = result.replace(/<
|
|
14
|
-
// Convert
|
|
15
|
-
result = result.replace(/<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// Convert
|
|
19
|
-
result = result.replace(/<
|
|
20
|
-
|
|
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
|
-
//
|
|
23
|
-
result = result.replace(/<
|
|
24
|
-
|
|
25
|
-
result = result.replace(/<
|
|
26
|
-
result = result.replace(/<
|
|
27
|
-
//
|
|
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,35 +1,118 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Converts Rich HTML strings containing
|
|
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
|
|
11
|
-
result = result.replace(/<
|
|
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
|
|
14
|
-
result = result.replace(/<
|
|
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
|
|
17
|
-
result = result.replace(/<
|
|
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
|
|
20
|
-
result = result.replace(/<
|
|
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
|
-
|
|
23
|
-
|
|
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
|
|
26
|
-
result = result.replace(/<
|
|
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[^>]*>(
|
|
30
|
-
result = result.replace(/<sup[^>]*>(
|
|
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
|
-
//
|
|
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
|
|
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;
|