
























@@ -140,11 +140,12 @@ export function markdownToTelegramHtml(
140140tableMode: options.tableMode,
141141});
142142const html = renderTelegramHtml(ir);
143+const telegramHtml = preserveSupportedTelegramHtmlTags(html);
143144// Apply file reference wrapping if requested (for chunked rendering)
144145if (options.wrapFileRefs !== false) {
145-return wrapFileReferencesInHtml(html);
146+return wrapFileReferencesInHtml(telegramHtml);
146147}
147-return html;
148+return telegramHtml;
148149}
149150150151/**
@@ -162,9 +163,164 @@ function escapeRegex(str: string): string {
162163163164const AUTO_LINKED_ANCHOR_PATTERN = /<a\s+href="https?:\/\/([^"]+)"[^>]*>\1<\/a>/gi;
164165const HTML_TAG_PATTERN = /(<\/?)([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*?>/gi;
166+const HTML_MODE_TAG_PATTERN = /^<(\/?)([a-zA-Z][a-zA-Z0-9-]*)([^<>]*)>$/;
167+const ESCAPED_HTML_TAG_PATTERN = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)(.*?)>/g;
168+const TELEGRAM_SIMPLE_HTML_TAGS = new Set([
169+"b",
170+"strong",
171+"i",
172+"em",
173+"u",
174+"ins",
175+"s",
176+"strike",
177+"del",
178+"code",
179+"pre",
180+"tg-spoiler",
181+"blockquote",
182+]);
183+const TELEGRAM_ATTR_HTML_TAG_PATTERNS = new Map([
184+["a", /^\s+href="[^"]+"\s*$/],
185+["span", /^\s+class="tg-spoiler"\s*$/],
186+["tg-emoji", /^\s+emoji-id="[^"]+"\s*$/],
187+["tg-time", /^\s+datetime="[^"]+"\s*$/],
188+]);
165189let fileReferencePattern: RegExp | undefined;
166190let orphanedTldPattern: RegExp | undefined;
167191192+function popLastTagName(tags: string[], name: string): boolean {
193+for (let index = tags.length - 1; index >= 0; index -= 1) {
194+if (tags[index] === name) {
195+tags.splice(index, 1);
196+return true;
197+}
198+}
199+return false;
200+}
201+202+function isSupportedTelegramHtmlTag(rawTag: string): boolean {
203+const match = HTML_MODE_TAG_PATTERN.exec(rawTag);
204+if (!match) {
205+return false;
206+}
207+const closing = match[1] === "/";
208+const name = normalizeLowercaseStringOrEmpty(match[2]);
209+const attrs = match[3] ?? "";
210+if (TELEGRAM_SIMPLE_HTML_TAGS.has(name)) {
211+return attrs.trim() === "";
212+}
213+if (closing) {
214+return attrs.trim() === "";
215+}
216+return TELEGRAM_ATTR_HTML_TAG_PATTERNS.get(name)?.test(attrs) ?? false;
217+}
218+219+function preserveTelegramHtmlTag(
220+rawTag: string,
221+openTags: string[],
222+escapeTag: (rawTag: string) => string,
223+): string {
224+const match = HTML_MODE_TAG_PATTERN.exec(rawTag);
225+if (!match || !isSupportedTelegramHtmlTag(rawTag)) {
226+return escapeTag(rawTag);
227+}
228+const closing = match[1] === "/";
229+const tagName = normalizeLowercaseStringOrEmpty(match[2]);
230+if (closing) {
231+return popLastTagName(openTags, tagName) ? rawTag : escapeTag(rawTag);
232+}
233+openTags.push(tagName);
234+return rawTag;
235+}
236+237+function escapeUnsupportedTelegramHtml(text: string): string {
238+let result = "";
239+let index = 0;
240+const openTags: string[] = [];
241+while (index < text.length) {
242+const char = text[index];
243+if (char === "&") {
244+const entityEnd = findTelegramHtmlEntityEnd(text, index);
245+if (entityEnd !== -1) {
246+result += text.slice(index, entityEnd + 1);
247+index = entityEnd + 1;
248+} else {
249+result += "&";
250+index += 1;
251+}
252+continue;
253+}
254+if (char === "<") {
255+const end = text.indexOf(">", index + 1);
256+if (end !== -1) {
257+const rawTag = text.slice(index, end + 1);
258+result += preserveTelegramHtmlTag(rawTag, openTags, escapeHtml);
259+index = end + 1;
260+} else {
261+result += "<";
262+index += 1;
263+}
264+continue;
265+}
266+if (char === ">") {
267+result += ">";
268+index += 1;
269+continue;
270+}
271+result += char;
272+index += 1;
273+}
274+return result;
275+}
276+277+function promoteEscapedSupportedTelegramTags(text: string, openTags: string[]): string {
278+ESCAPED_HTML_TAG_PATTERN.lastIndex = 0;
279+return text.replace(
280+ESCAPED_HTML_TAG_PATTERN,
281+(match, closing: string, name: string, attrs: string) =>
282+preserveTelegramHtmlTag(`<${closing}${name}${attrs}>`, openTags, () => match),
283+);
284+}
285+286+function preserveSupportedTelegramHtmlTags(html: string): string {
287+let codeDepth = 0;
288+let preDepth = 0;
289+let result = "";
290+let lastIndex = 0;
291+const openEscapedTags: string[] = [];
292+293+HTML_TAG_PATTERN.lastIndex = 0;
294+let match: RegExpExecArray | null;
295+while ((match = HTML_TAG_PATTERN.exec(html)) !== null) {
296+const tagStart = match.index;
297+const tagEnd = HTML_TAG_PATTERN.lastIndex;
298+const tagName = normalizeLowercaseStringOrEmpty(match[2]);
299+const isClosing = match[1] === "</";
300+const textBefore = html.slice(lastIndex, tagStart);
301+result +=
302+codeDepth > 0 || preDepth > 0
303+ ? textBefore
304+ : promoteEscapedSupportedTelegramTags(textBefore, openEscapedTags);
305+306+if (tagName === "code") {
307+codeDepth = isClosing ? Math.max(0, codeDepth - 1) : codeDepth + 1;
308+} else if (tagName === "pre") {
309+preDepth = isClosing ? Math.max(0, preDepth - 1) : preDepth + 1;
310+}
311+312+result += html.slice(tagStart, tagEnd);
313+lastIndex = tagEnd;
314+}
315+316+const remainingText = html.slice(lastIndex);
317+result +=
318+codeDepth > 0 || preDepth > 0
319+ ? remainingText
320+ : promoteEscapedSupportedTelegramTags(remainingText, openEscapedTags);
321+return result;
322+}
323+168324function getFileReferencePattern(): RegExp {
169325if (fileReferencePattern) {
170326return fileReferencePattern;
@@ -272,8 +428,7 @@ export function renderTelegramHtmlText(
272428): string {
273429const textMode = options.textMode ?? "markdown";
274430if (textMode === "html") {
275-// For HTML mode, trust caller markup - don't modify
276-return text;
431+return escapeUnsupportedTelegramHtml(text);
277432}
278433// markdownToTelegramHtml already wraps file references by default
279434return markdownToTelegramHtml(text, { tableMode: options.tableMode });
@@ -491,7 +646,7 @@ export function splitTelegramHtmlChunks(html: string, limit: number): string[] {
491646}
492647493648function renderTelegramChunkHtml(ir: MarkdownIR): string {
494-return wrapFileReferencesInHtml(renderTelegramHtml(ir));
649+return wrapFileReferencesInHtml(preserveSupportedTelegramHtmlTags(renderTelegramHtml(ir)));
495650}
496651497652function renderTelegramChunksWithinHtmlLimit(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。