fix(telegram): add MarkdownV2 support for strikethrough, spoiler, and blockquotes

- Convert ~~text~~ to ~text~ (MarkdownV2 strikethrough)
- Protect ||text|| from pipe escaping (MarkdownV2 spoiler)
- Preserve > at line start as blockquote instead of escaping it
- Update _strip_mdv2() to strip ~strikethrough~ and ||spoiler|| markers
- Add tests covering new formatting paths and edge cases
This commit is contained in:
llbn
2026-03-20 18:21:24 +01:00
parent 1aa7027be1
commit 02f639e561
2 changed files with 125 additions and 4 deletions

View File

@@ -295,6 +295,95 @@ class TestItalicNewlineBug:
assert "_italic_" in result
# =========================================================================
# format_message - strikethrough
# =========================================================================
class TestFormatMessageStrikethrough:
def test_strikethrough_converted(self, adapter):
result = adapter.format_message("This is ~~deleted~~ text")
assert "~deleted~" in result
assert "~~" not in result
def test_strikethrough_with_special_chars(self, adapter):
result = adapter.format_message("~~hello.world!~~")
assert "~hello\\.world\\!~" in result
def test_strikethrough_in_code_not_converted(self, adapter):
result = adapter.format_message("`~~not struck~~`")
assert "`~~not struck~~`" in result
def test_strikethrough_with_bold(self, adapter):
result = adapter.format_message("**bold** and ~~struck~~")
assert "*bold*" in result
assert "~struck~" in result
# =========================================================================
# format_message - spoiler
# =========================================================================
class TestFormatMessageSpoiler:
def test_spoiler_converted(self, adapter):
result = adapter.format_message("This is ||hidden|| text")
assert "||hidden||" in result
def test_spoiler_with_special_chars(self, adapter):
result = adapter.format_message("||hello.world!||")
assert "||hello\\.world\\!||" in result
def test_spoiler_in_code_not_converted(self, adapter):
result = adapter.format_message("`||not spoiler||`")
assert "`||not spoiler||`" in result
def test_spoiler_pipes_not_escaped(self, adapter):
"""The || delimiters must not be escaped as \\|\\|."""
result = adapter.format_message("||secret||")
assert "\\|\\|" not in result
assert "||secret||" in result
# =========================================================================
# format_message - blockquote
# =========================================================================
class TestFormatMessageBlockquote:
def test_blockquote_converted(self, adapter):
result = adapter.format_message("> This is a quote")
assert "> This is a quote" in result
# > must NOT be escaped
assert "\\>" not in result
def test_blockquote_with_special_chars(self, adapter):
result = adapter.format_message("> Hello (world)!")
assert "> Hello \\(world\\)\\!" in result
assert "\\>" not in result
def test_blockquote_multiline(self, adapter):
text = "> Line one\n> Line two"
result = adapter.format_message(text)
assert "> Line one" in result
assert "> Line two" in result
assert "\\>" not in result
def test_blockquote_in_code_not_converted(self, adapter):
result = adapter.format_message("```\n> not a quote\n```")
assert "> not a quote" in result
def test_nested_blockquote(self, adapter):
result = adapter.format_message(">> Nested quote")
assert ">> Nested quote" in result
assert "\\>" not in result
def test_gt_in_middle_of_line_still_escaped(self, adapter):
"""Only > at line start is a blockquote; mid-line > should be escaped."""
result = adapter.format_message("5 > 3")
assert "\\>" in result
# =========================================================================
# format_message - mixed/complex
# =========================================================================
@@ -393,6 +482,12 @@ class TestStripMdv2:
def test_empty_string(self):
assert _strip_mdv2("") == ""
def test_removes_strikethrough_markers(self):
assert _strip_mdv2("~struck text~") == "struck text"
def test_removes_spoiler_markers(self):
assert _strip_mdv2("||hidden text||") == "hidden text"
@pytest.mark.asyncio
async def test_send_escapes_chunk_indicator_for_markdownv2(adapter):