fix(telegram): keep link targets literal during Markdown rendering

This commit is contained in:
Rudy Celekli committed 2026-10-11 09:06:38 -04:00
1 parent 5f54fc5beb
commit 939b2f151f
3 files changed
+74 -7

No files matched your search

+20 -7
View File
@@ -104,14 +104,27 @@ def _render_line(line: str) -> str:
def _render_inline(text: str) -> str:
links = {}
source = text
def emphasis(value: str) -> str:
value = _BOLD_RE.sub(r"<b>\1</b>", value)
value = _BOLD_ALT_RE.sub(r"<b>\1</b>", value)
value = _STRIKE_RE.sub(r"<s>\1</s>", value)
value = _ITALIC_RE.sub(r"<i>\1</i>", value)
return _ITALIC_ALT_RE.sub(r"<i>\1</i>", value)
def link(m: re.Match) -> str:
url = m.group(2).replace('"', "&quot;")
return f'<a href="{url}">{m.group(1)}</a>'
token = f"\x01link{len(links)}\x01"
while token in source:
token += "\x01"
links[token] = f'<a href="{url}">{emphasis(m.group(1))}</a>'
return token
text = _LINK_RE.sub(link, text)
text = _BOLD_RE.sub(r"<b>\1</b>", text)
text = _BOLD_ALT_RE.sub(r"<b>\1</b>", text)
text = _STRIKE_RE.sub(r"<s>\1</s>", text)
text = _ITALIC_RE.sub(r"<i>\1</i>", text)
text = _ITALIC_ALT_RE.sub(r"<i>\1</i>", text)
# URLs are not Markdown source. Park the anchor while surrounding prose
# is formatted, then restore it with the separately formatted label.
text = emphasis(_LINK_RE.sub(link, text))
for token, rendered in links.items():
text = text.replace(token, rendered)
return text
@@ -0,0 +1,19 @@
# ADR 027: fix(telegram): keep link targets literal during Markdown rendering
Status: Accepted for this candidate.
## Context
The Telegram renderer creates anchor tags before running emphasis substitutions over the complete HTML. URL path segments such as __archive__ become bold tags inside href, corrupting the destination and Telegram parse mode.
## Decision
Park rendered links during the emphasis passes, apply formatting to labels, and restore links afterward. Use collision-safe placeholders so original text is not mistaken for a parked link.
## Consequences
Link destinations remain escaped literal URLs; label, surrounding emphasis, and inline code formatting continue to work.
## Validation
The focused regression fails on the unmodified base and passes with this change. tests/test_telegram_markdown_link_targets.py tests/test_telegram_markdown_fences.py passed locally; changed Python files passed CI-equivalent Ruff F checks. These are local native-contract checks using controlled doubles, not live provider or channel acceptance.
@@ -0,0 +1,35 @@
"""Markdown link targets are literal URLs, not emphasis source."""
import pytest
from channel.telegram.telegram_markdown import to_telegram_html
@pytest.mark.parametrize("url", [
"https://example.invalid/__archive__/report",
"https://example.invalid/a*b*c",
"https://example.invalid/~~draft~~",
"https://example.invalid/api?first=1&path=__value__",
])
def test_link_url_is_not_rewritten_as_inline_markup(url):
expected = url.replace("&", "&amp;")
assert to_telegram_html(f"[report]({url})") == f'<a href="{expected}">report</a>'
def test_link_label_and_surrounding_emphasis_keep_formatting():
assert to_telegram_html("**[Read *this*](https://example.invalid/__docs__)**") == (
'<b><a href="https://example.invalid/__docs__">Read <i>this</i></a></b>'
)
def test_inline_code_in_link_label_still_restores():
assert to_telegram_html("[`__name__`](https://example.invalid/__docs__)") == (
'<a href="https://example.invalid/__docs__"><code>__name__</code></a>'
)
def test_multiple_links_and_literal_placeholder_text_are_preserved():
source = "\x01link0\x01 [one](https://example.invalid/__a__) and [two](https://example.invalid/__b__)"
assert to_telegram_html(source) == (
'\x01link0\x01 <a href="https://example.invalid/__a__">one</a> and '
'<a href="https://example.invalid/__b__">two</a>'
)