

























@@ -165,6 +165,11 @@ const AUTO_LINKED_ANCHOR_PATTERN = /<a\s+href="https?:\/\/([^"]+)"[^>]*>\1<\/a>/
165165const HTML_TAG_PATTERN = /(<\/?)([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*?>/gi;
166166const HTML_MODE_TAG_PATTERN = /^<(\/?)([a-zA-Z][a-zA-Z0-9-]*)([^<>]*)>$/;
167167const ESCAPED_HTML_TAG_PATTERN = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)(.*?)>/g;
168+const TELEGRAM_HTML_ANCHOR_PATTERN =
169+/<a\b[^>]*\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a\s*>/gi;
170+const TELEGRAM_HTML_BREAK_PATTERN = /<br\s*\/?>/gi;
171+const TELEGRAM_HTML_ENTITY_PATTERN = /&(#x[0-9A-Fa-f]+|#\d+|amp|lt|gt|quot|apos);/g;
172+const TELEGRAM_HTML_TAG_PATTERN = /<[^>]*>/g;
168173const TELEGRAM_SIMPLE_HTML_TAGS = new Set([
169174"b",
170175"strong",
@@ -274,6 +279,88 @@ function escapeUnsupportedTelegramHtml(text: string): string {
274279return result;
275280}
276281282+function decodeTelegramHtmlEntity(entity: string, fallback: string): string {
283+if (entity.startsWith("#x") || entity.startsWith("#X")) {
284+const codePoint = Number.parseInt(entity.slice(2), 16);
285+return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff
286+ ? String.fromCodePoint(codePoint)
287+ : fallback;
288+}
289+if (entity.startsWith("#")) {
290+const codePoint = Number.parseInt(entity.slice(1), 10);
291+return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff
292+ ? String.fromCodePoint(codePoint)
293+ : fallback;
294+}
295+switch (entity) {
296+case "amp":
297+return "&";
298+case "lt":
299+return "<";
300+case "gt":
301+return ">";
302+case "quot":
303+return '"';
304+case "apos":
305+return "'";
306+default:
307+return fallback;
308+}
309+}
310+311+function decodeTelegramHtmlEntities(text: string): string {
312+return text.replace(TELEGRAM_HTML_ENTITY_PATTERN, (match, entity: string) =>
313+decodeTelegramHtmlEntity(entity, match),
314+);
315+}
316+317+function stripTelegramHtmlForPlainText(html: string): string {
318+return decodeTelegramHtmlEntities(
319+html.replace(TELEGRAM_HTML_BREAK_PATTERN, "\n").replace(TELEGRAM_HTML_TAG_PATTERN, ""),
320+);
321+}
322+323+function encodePlainTextForTelegramHtmlStrip(text: string): string {
324+return text.replace(/[&<>]/g, (char) => {
325+switch (char) {
326+case "&":
327+return "&";
328+case "<":
329+return "<";
330+case ">":
331+return ">";
332+default:
333+return char;
334+}
335+});
336+}
337+338+export function telegramHtmlToPlainTextFallback(html: string): string {
339+TELEGRAM_HTML_ANCHOR_PATTERN.lastIndex = 0;
340+const withPlainLinks = html.replace(
341+TELEGRAM_HTML_ANCHOR_PATTERN,
342+(
343+_match: string,
344+doubleQuotedHref: string | undefined,
345+singleQuotedHref: string | undefined,
346+unquotedHref: string | undefined,
347+labelHtml: string,
348+) => {
349+const href = decodeTelegramHtmlEntities(
350+doubleQuotedHref ?? singleQuotedHref ?? unquotedHref ?? "",
351+).trim();
352+const label = stripTelegramHtmlForPlainText(labelHtml).trim();
353+if (!href) {
354+return encodePlainTextForTelegramHtmlStrip(label);
355+}
356+return encodePlainTextForTelegramHtmlStrip(
357+!label || label === href ? href : `${label} (${href})`,
358+);
359+},
360+);
361+return stripTelegramHtmlForPlainText(withPlainLinks);
362+}
363+277364function promoteEscapedSupportedTelegramTags(text: string, openTags: string[]): string {
278365ESCAPED_HTML_TAG_PATTERN.lastIndex = 0;
279366return text.replace(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。