fix(telegram): address code review issues from cherry-pick merge

- Fix critical plain-text fallback that was sending raw HTML tags to
  users: keep raw markdown available for the fallback path
- Extract TELEGRAM_HTML_MAX_LEN (4096) constant to replace hardcoded
  magic number and document the difference from TELEGRAM_MAX_MESSAGE_LEN
- Add fallback to _send_text for extra HTML chunks when HTML parse fails
- Add missing @pytest.mark.asyncio decorator on
  test_send_delta_stream_end_html_expansion_does_not_overflow
This commit is contained in:
chengyongru 2026-04-20 11:59:28 +08:00 committed by chengyongru
parent c7057cb3bf
commit 3454efcd98
2 changed files with 23 additions and 10 deletions

View File

@ -26,6 +26,11 @@ from nanobot.security.network import validate_url_target
from nanobot.utils.helpers import split_message from nanobot.utils.helpers import split_message
TELEGRAM_MAX_MESSAGE_LEN = 4000 # Telegram message character limit TELEGRAM_MAX_MESSAGE_LEN = 4000 # Telegram message character limit
# Telegram's actual API limit is 4096; we split raw markdown at 4000 as a
# safety margin for mid-stream edits (plain text). For _stream_end, we
# convert to HTML first and then split at the true 4096-char boundary so
# the final rendered message never overflows.
TELEGRAM_HTML_MAX_LEN = 4096
TELEGRAM_REPLY_CONTEXT_MAX_LEN = TELEGRAM_MAX_MESSAGE_LEN # Max length for reply context in user message TELEGRAM_REPLY_CONTEXT_MAX_LEN = TELEGRAM_MAX_MESSAGE_LEN # Max length for reply context in user message
@ -564,12 +569,13 @@ class TelegramChannel(BaseChannel):
thread_kwargs = {} thread_kwargs = {}
if message_thread_id := meta.get("message_thread_id"): if message_thread_id := meta.get("message_thread_id"):
thread_kwargs["message_thread_id"] = message_thread_id thread_kwargs["message_thread_id"] = message_thread_id
html = _markdown_to_telegram_html(buf.text) raw_text = buf.text
if len(html) <= 4096: html = _markdown_to_telegram_html(raw_text)
if len(html) <= TELEGRAM_HTML_MAX_LEN:
primary_html = html primary_html = html
extra_html_chunks = [] extra_html_chunks = []
else: else:
html_chunks = split_message(html, 4096) html_chunks = split_message(html, TELEGRAM_HTML_MAX_LEN)
primary_html = html_chunks[0] primary_html = html_chunks[0]
extra_html_chunks = html_chunks[1:] extra_html_chunks = html_chunks[1:]
try: try:
@ -587,11 +593,13 @@ class TelegramChannel(BaseChannel):
self._stream_bufs.pop(chat_id, None) self._stream_bufs.pop(chat_id, None)
return return
logger.debug("Final stream edit failed (HTML), trying plain: {}", e) logger.debug("Final stream edit failed (HTML), trying plain: {}", e)
# Fall back to raw markdown (not HTML) so users don't see raw tags.
primary_plain = split_message(raw_text, TELEGRAM_MAX_MESSAGE_LEN)[0] if len(raw_text) > TELEGRAM_MAX_MESSAGE_LEN else raw_text
try: try:
await self._call_with_retry( await self._call_with_retry(
self._app.bot.edit_message_text, self._app.bot.edit_message_text,
chat_id=int_chat_id, message_id=buf.message_id, chat_id=int_chat_id, message_id=buf.message_id,
text=primary_html, text=primary_plain,
) )
except Exception as e2: except Exception as e2:
if self._is_not_modified_error(e2): if self._is_not_modified_error(e2):
@ -600,12 +608,16 @@ class TelegramChannel(BaseChannel):
logger.warning("Final stream edit failed: {}", e2) logger.warning("Final stream edit failed: {}", e2)
raise # Let ChannelManager handle retry raise # Let ChannelManager handle retry
for extra_html_chunk in extra_html_chunks: for extra_html_chunk in extra_html_chunks:
await self._call_with_retry( try:
self._app.bot.send_message, await self._call_with_retry(
chat_id=int_chat_id, text=extra_html_chunk, self._app.bot.send_message,
parse_mode="HTML", chat_id=int_chat_id, text=extra_html_chunk,
**thread_kwargs, parse_mode="HTML",
) **thread_kwargs,
)
except Exception:
# Fall back to _send_text which handles HTML→plain gracefully.
await self._send_text(int_chat_id, extra_html_chunk)
self._stream_bufs.pop(chat_id, None) self._stream_bufs.pop(chat_id, None)
return return

View File

@ -497,6 +497,7 @@ async def test_send_delta_stream_end_splits_oversized_reply() -> None:
assert "123" not in channel._stream_bufs assert "123" not in channel._stream_bufs
@pytest.mark.asyncio
async def test_send_delta_stream_end_html_expansion_does_not_overflow() -> None: async def test_send_delta_stream_end_html_expansion_does_not_overflow() -> None:
"""Markdown that expands when converted to HTML is still split correctly. """Markdown that expands when converted to HTML is still split correctly.