Fix Telegram edit fallback

This commit is contained in:
jxxghp
2026-06-29 22:48:13 +08:00
parent 4d30dee74c
commit 73d5c95f4e
3 changed files with 140 additions and 26 deletions
+10
View File
@@ -21,6 +21,10 @@ from app.utils.structures import DictUtils
class TelegramModule(_ModuleBase, _MessageBase[Telegram]): class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
"""
Telegram 通知模块,负责模块生命周期、消息解析和通知发送。
"""
def init_module(self) -> None: def init_module(self) -> None:
""" """
初始化模块 初始化模块
@@ -32,6 +36,9 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
@staticmethod @staticmethod
def get_name() -> str: def get_name() -> str:
"""
获取模块名称
"""
return "Telegram" return "Telegram"
@staticmethod @staticmethod
@@ -75,6 +82,9 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
return True, "" return True, ""
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""
获取模块初始化配置项。
"""
pass pass
@staticmethod @staticmethod
+93 -19
View File
@@ -46,10 +46,18 @@ TELEGRAM_PARSE_MODE_ALIASES = {
class RetryException(Exception): class RetryException(Exception):
"""
Telegram 消息发送重试异常。
"""
pass pass
class Telegram: class Telegram:
"""
Telegram 消息客户端,负责发送、编辑、接收和转发 Telegram 消息。
"""
_ds_url = ( _ds_url = (
f"http://127.0.0.1:{settings.PORT}/api/v1/message?token={settings.API_TOKEN}" f"http://127.0.0.1:{settings.PORT}/api/v1/message?token={settings.API_TOKEN}"
) )
@@ -1129,6 +1137,69 @@ class Telegram:
""" """
return "there is no text in the message to edit" in str(err).lower() return "there is no text in the message to edit" in str(err).lower()
@staticmethod
def __is_message_not_modified_error(err: Exception) -> bool:
"""
判断 Telegram 是否因为消息内容未变化而拒绝编辑。
"""
return "message is not modified" in str(err).lower()
@staticmethod
def __is_http_url_content_error(err: Exception) -> bool:
"""
判断 Telegram 是否因为无法获取远端图片 URL 而拒绝编辑。
"""
return "failed to get http url content" in str(err).lower()
def __edit_message_text_or_caption(
self,
chat_id: str,
message_id: int,
text: str,
reply_markup: Optional[InlineKeyboardMarkup] = None,
disable_web_page_preview: Optional[bool] = None,
parse_mode: Optional[str] = None,
) -> bool:
"""
编辑 Telegram 文本消息,原消息无文本时回退为 caption 编辑。
"""
prepared_text = self._prepare_text(text, parse_mode)
edit_text_kwargs: Dict[str, Any] = {
"chat_id": chat_id,
"message_id": message_id,
"text": prepared_text,
"parse_mode": parse_mode,
"reply_markup": reply_markup,
}
if disable_web_page_preview is not None:
edit_text_kwargs["disable_web_page_preview"] = (
disable_web_page_preview
)
try:
self._bot.edit_message_text(**edit_text_kwargs)
except Exception as err:
if self.__is_message_not_modified_error(err):
logger.debug(f"Telegram消息内容未变化,跳过编辑:{str(err)}")
return True
if not self.__is_no_text_edit_error(err):
raise
try:
self._bot.edit_message_caption(
chat_id=chat_id,
message_id=message_id,
caption=prepared_text,
parse_mode=parse_mode,
reply_markup=reply_markup,
)
except Exception as caption_err:
if self.__is_message_not_modified_error(caption_err):
logger.debug(
f"Telegram消息内容未变化,跳过编辑:{str(caption_err)}"
)
return True
raise
return True
def __edit_message( def __edit_message(
self, self,
chat_id: str, chat_id: str,
@@ -1175,31 +1246,34 @@ class Telegram:
) )
else: else:
# 如果没有图片,使用edit_message_text # 如果没有图片,使用edit_message_text
edit_text_kwargs: Dict[str, Any] = { return self.__edit_message_text_or_caption(
"chat_id": chat_id,
"message_id": message_id,
"text": self._prepare_text(text, parse_mode),
"parse_mode": parse_mode,
"reply_markup": reply_markup,
}
if disable_web_page_preview is not None:
edit_text_kwargs["disable_web_page_preview"] = (
disable_web_page_preview
)
try:
self._bot.edit_message_text(**edit_text_kwargs)
except Exception as err:
if not self.__is_no_text_edit_error(err):
raise
self._bot.edit_message_caption(
chat_id=chat_id, chat_id=chat_id,
message_id=message_id, message_id=message_id,
caption=self._prepare_text(text, parse_mode), text=text,
parse_mode=parse_mode,
reply_markup=reply_markup, reply_markup=reply_markup,
disable_web_page_preview=disable_web_page_preview,
parse_mode=parse_mode,
) )
return True return True
except Exception as e: except Exception as e:
if self.__is_message_not_modified_error(e):
logger.debug(f"Telegram消息内容未变化,跳过编辑:{str(e)}")
return True
if image and self.__is_http_url_content_error(e):
logger.warning(
f"Telegram图片编辑失败,降级为文本编辑:{str(e)}"
)
try:
return self.__edit_message_text_or_caption(
chat_id=chat_id,
message_id=message_id,
text=text,
reply_markup=reply_markup,
disable_web_page_preview=disable_web_page_preview,
parse_mode=parse_mode,
)
except Exception as fallback_err:
e = fallback_err
logger.error(f"编辑消息失败:{str(e)}") logger.error(f"编辑消息失败:{str(e)}")
return False return False
+33 -3
View File
@@ -354,8 +354,8 @@ def test_edit_msg_with_html_parse_mode_keeps_html(telegram):
assert edit_kwargs["text"] == "<b>标题</b>\n<blockquote>请选择</blockquote>" assert edit_kwargs["text"] == "<b>标题</b>\n<blockquote>请选择</blockquote>"
def test_edit_msg_keeps_other_edit_errors_failed(telegram): def test_edit_msg_treats_message_not_modified_as_success(telegram):
"""非图片 caption 场景的编辑错误不应被错误标记为成功""" """重复编辑相同内容时应视为成功,避免记录错误日志"""
telegram.bot.edit_message_text.side_effect = Exception( telegram.bot.edit_message_text.side_effect = Exception(
"Bad Request: message is not modified" "Bad Request: message is not modified"
) )
@@ -367,6 +367,36 @@ def test_edit_msg_keeps_other_edit_errors_failed(telegram):
text="测试内容", text="测试内容",
) )
assert result is False assert result is True
telegram.bot.edit_message_text.assert_called_once() telegram.bot.edit_message_text.assert_called_once()
telegram.bot.edit_message_caption.assert_not_called() telegram.bot.edit_message_caption.assert_not_called()
def test_send_msg_edit_with_image_falls_back_to_text_when_image_url_unavailable(telegram):
"""编辑图片消息失败时应去掉图片并降级为文本编辑。"""
telegram.bot.edit_message_media.side_effect = Exception(
"Bad Request: failed to get HTTP URL content"
)
result = telegram.send_msg(
title="测试标题",
text="测试内容",
image="https://example.com/poster.jpg",
buttons=[[{"text": "确认", "callback_data": "confirm"}]],
original_chat_id="1051253579",
original_message_id=110502,
)
assert result == {
"success": True,
"message_id": 110502,
"chat_id": "1051253579",
}
telegram.bot.edit_message_media.assert_called_once()
telegram.bot.edit_message_text.assert_called_once()
edit_kwargs = telegram.bot.edit_message_text.call_args.kwargs
assert edit_kwargs["chat_id"] == "1051253579"
assert edit_kwargs["message_id"] == 110502
assert "测试标题" in edit_kwargs["text"]
assert "测试内容" in edit_kwargs["text"]
assert edit_kwargs["reply_markup"] is not None