From 43b3a0ac66ae81172fa1f905c47e266107c03510 Mon Sep 17 00:00:00 2001 From: llbn <46884939+llbn@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:32:45 +0100 Subject: [PATCH] fix(telegram): escape backslashes and backticks inside code entities for MarkdownV2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Escape \ → \\ inside inline code and fenced code blocks - Escape ` → \` inside fenced code block bodies (not delimiters) - Add regression tests for code entity backslash handling --- gateway/platforms/telegram.py | 20 ++++++++++++++++++-- tests/gateway/test_telegram_format.py | 25 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index fe869f18e..dd4632e13 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -787,14 +787,30 @@ class TelegramAdapter(BasePlatformAdapter): text = content # 1) Protect fenced code blocks (``` ... ```) + # Per MarkdownV2 spec, \ and ` inside pre/code must be escaped. + def _protect_fenced(m): + raw = m.group(0) + # Split off opening ``` (with optional language) and closing ``` + open_end = raw.index('\n') + 1 if '\n' in raw[3:] else 3 + opening = raw[:open_end] + body_and_close = raw[open_end:] + body = body_and_close[:-3] + body = body.replace('\\', '\\\\').replace('`', '\\`') + return _ph(opening + body + '```') + text = re.sub( r'(```(?:[^\n]*\n)?[\s\S]*?```)', - lambda m: _ph(m.group(0)), + _protect_fenced, text, ) # 2) Protect inline code (`...`) - text = re.sub(r'(`[^`]+`)', lambda m: _ph(m.group(0)), text) + # Escape \ inside inline code per MarkdownV2 spec. + text = re.sub( + r'(`[^`]+`)', + lambda m: _ph(m.group(0).replace('\\', '\\\\')), + text, + ) # 3) Convert markdown links – escape the display text; inside the URL # only ')' and '\' need escaping per the MarkdownV2 spec. diff --git a/tests/gateway/test_telegram_format.py b/tests/gateway/test_telegram_format.py index 19e56198b..bc840da90 100644 --- a/tests/gateway/test_telegram_format.py +++ b/tests/gateway/test_telegram_format.py @@ -146,6 +146,31 @@ class TestFormatMessageCodeBlocks: # "text" between blocks should be present assert "text" in result + def test_inline_code_backslashes_escaped(self, adapter): + r"""Backslashes in inline code must be escaped for MarkdownV2.""" + text = r"Check `C:\ProgramData\VMware\` path" + result = adapter.format_message(text) + assert r"`C:\\ProgramData\\VMware\\`" in result + + def test_fenced_code_block_backslashes_escaped(self, adapter): + r"""Backslashes in fenced code blocks must be escaped for MarkdownV2.""" + text = "```\npath = r'C:\\Users\\test'\n```" + result = adapter.format_message(text) + assert r"C:\\Users\\test" in result + + def test_fenced_code_block_backticks_escaped(self, adapter): + r"""Backticks inside fenced code blocks must be escaped for MarkdownV2.""" + text = "```\necho `hostname`\n```" + result = adapter.format_message(text) + assert r"echo \`hostname\`" in result + + def test_inline_code_no_double_escape(self, adapter): + r"""Already-escaped backslashes should not be quadruple-escaped.""" + text = r"Use `\\server\share`" + result = adapter.format_message(text) + # \\ in input → \\\\ in output (each \ escaped once) + assert r"`\\\\server\\share`" in result + # ========================================================================= # format_message - bold and italic