From 363a56ab879acdd845fe7157c7a8b406b6aae471 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Feb 2026 01:03:50 +0100 Subject: [PATCH] refactor(telegram): streamline file-ref wrapping and hoist regexes --- src/telegram/format.test.ts | 12 ++++ src/telegram/format.ts | 136 +++++++++++++----------------------- 2 files changed, 60 insertions(+), 88 deletions(-) diff --git a/src/telegram/format.test.ts b/src/telegram/format.test.ts index 48e95343750..6b0e1944f70 100644 --- a/src/telegram/format.test.ts +++ b/src/telegram/format.test.ts @@ -95,6 +95,18 @@ describe("markdownToTelegramHtml", () => { expect(res).toBe('bold'); }); + it("wraps punctuated file references in code tags", () => { + const res = markdownToTelegramHtml("See README.md. Also (backup.sh)."); + expect(res).toContain("README.md."); + expect(res).toContain("(backup.sh)."); + }); + + it("keeps .co domains as links", () => { + const res = markdownToTelegramHtml("Visit t.co and openclaw.co"); + expect(res).toContain('t.co'); + expect(res).toContain('openclaw.co'); + }); + it("renders spoiler tags", () => { const res = markdownToTelegramHtml("the answer is ||42||"); expect(res).toBe("the answer is 42"); diff --git a/src/telegram/format.ts b/src/telegram/format.ts index dae60ff1d96..f919a917f9f 100644 --- a/src/telegram/format.ts +++ b/src/telegram/format.ts @@ -139,22 +139,52 @@ function escapeRegex(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -export function wrapFileReferencesInHtml(html: string): string { - // Build regex pattern for all tracked extensions (escape metacharacters for safety) - const extensionsPattern = Array.from(FILE_EXTENSIONS_WITH_TLD).map(escapeRegex).join("|"); +const FILE_EXTENSIONS_PATTERN = Array.from(FILE_EXTENSIONS_WITH_TLD).map(escapeRegex).join("|"); +const AUTO_LINKED_ANCHOR_PATTERN = /]*>\1<\/a>/gi; +const FILE_REFERENCE_PATTERN = new RegExp( + `(^|[^a-zA-Z0-9_\\-/])([a-zA-Z0-9_.\\-./]+\\.(?:${FILE_EXTENSIONS_PATTERN}))(?=$|[^a-zA-Z0-9_\\-/])`, + "gi", +); +const ORPHANED_TLD_PATTERN = new RegExp( + `([^a-zA-Z0-9]|^)([A-Za-z]\\.(?:${FILE_EXTENSIONS_PATTERN}))(?=[^a-zA-Z0-9/]|$)`, + "g", +); +const HTML_TAG_PATTERN = /(<\/?)([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*?>/gi; +function wrapStandaloneFileRef(match: string, prefix: string, filename: string): string { + if (filename.startsWith("//")) { + return match; + } + if (/https?:\/\/$/i.test(prefix)) { + return match; + } + return `${prefix}${escapeHtml(filename)}`; +} + +function wrapSegmentFileRefs( + text: string, + codeDepth: number, + preDepth: number, + anchorDepth: number, +): string { + if (!text || codeDepth > 0 || preDepth > 0 || anchorDepth > 0) { + return text; + } + const wrappedStandalone = text.replace(FILE_REFERENCE_PATTERN, wrapStandaloneFileRef); + return wrappedStandalone.replace(ORPHANED_TLD_PATTERN, (match, prefix: string, tld: string) => + prefix === ">" ? match : `${prefix}${escapeHtml(tld)}`, + ); +} + +export function wrapFileReferencesInHtml(html: string): string { // Safety-net: de-linkify auto-generated anchors where href="http://