diff --git a/app/modules/slack/__init__.py b/app/modules/slack/__init__.py index 4f6d3cff..69a5602c 100644 --- a/app/modules/slack/__init__.py +++ b/app/modules/slack/__init__.py @@ -193,10 +193,15 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]): if not client_config: return None try: - msg_json: dict = json.loads(body) + msg_json = json.loads(body) + while isinstance(msg_json, str): + msg_json = json.loads(msg_json) except Exception as err: logger.debug(f"解析Slack消息失败:{str(err)}") return None + if not isinstance(msg_json, dict): + logger.debug(f"Slack消息格式无效:{type(msg_json)}") + return None if msg_json: images = None if msg_json.get("type") == "message": diff --git a/app/modules/telegram/__init__.py b/app/modules/telegram/__init__.py index 547ebc08..050260bf 100644 --- a/app/modules/telegram/__init__.py +++ b/app/modules/telegram/__init__.py @@ -131,11 +131,21 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]): return None client: Telegram = self.get_instance(client_config.name) try: - message: dict = json.loads(body) + message = json.loads(body) + while isinstance(message, str): + message = json.loads(message) except Exception as err: logger.debug(f"解析Telegram消息失败:{str(err)}") return None + if not isinstance(message, dict): + logger.debug(f"Telegram消息格式无效:{type(message)}") + return None + + # 兼容某些转发链路使用 Telegram Update 外壳 + if "message" in message and isinstance(message.get("message"), dict): + message = message.get("message") + if message: # 处理按钮回调 if "callback_query" in message: diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index 7161996a..1e1375cc 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -1,4 +1,5 @@ import asyncio +import json import re import threading import time @@ -113,7 +114,11 @@ class Telegram: if self._should_process_message(message): # 启动持续发送正在输入状态 self._start_typing_task(message.chat.id) - RequestUtils(timeout=15).post_res(self._ds_url, json=message.json) + payload = self._serialize_update_payload(message) + if not payload: + logger.warn("Telegram消息序列化失败,跳过转发") + return + RequestUtils(timeout=15).post_res(self._ds_url, json=payload) @_bot.callback_query_handler(func=lambda call: True) def callback_query(call): @@ -208,6 +213,23 @@ class Telegram: logger.error(f"下载Telegram文件失败: {e}") return None + @staticmethod + def _serialize_update_payload(message: Any) -> Optional[dict]: + """ + 将 Telegram Message 对象稳定序列化为 dict,避免 requests 的 json 参数再次包一层字符串。 + """ + try: + if hasattr(message, "to_dict"): + payload = message.to_dict() + else: + payload = getattr(message, "json", None) or message + if isinstance(payload, str): + payload = json.loads(payload) + return payload if isinstance(payload, dict) else None + except Exception as e: + logger.error(f"序列化Telegram消息失败: {e}") + return None + def _update_user_chat_mapping(self, userid: int, chat_id: int) -> None: """ 更新用户与聊天的映射关系 diff --git a/tests/test_agent_image_support.py b/tests/test_agent_image_support.py index d52e0d08..c1948cc6 100644 --- a/tests/test_agent_image_support.py +++ b/tests/test_agent_image_support.py @@ -1,4 +1,5 @@ import base64 +import json import unittest from types import SimpleNamespace from unittest.mock import Mock, patch @@ -7,6 +8,7 @@ from app.agent.tools.impl.send_message import SendMessageInput from app.chain.message import MessageChain from app.core.config import settings from app.modules.slack import SlackModule +from app.modules.telegram.telegram import Telegram from app.modules.telegram import TelegramModule from app.schemas import CommingMessage from app.schemas.types import MessageChannel @@ -26,6 +28,51 @@ class AgentImageSupportTest(unittest.TestCase): ["tg://file_id/large", "tg://file_id/doc-image"], ) + def test_telegram_message_parser_accepts_double_encoded_body(self): + module = TelegramModule() + body = json.dumps( + json.dumps( + { + "message": { + "from": {"id": 10001, "username": "tester"}, + "chat": {"id": 10001, "type": "private"}, + "photo": [{"file_id": "small"}, {"file_id": "large"}], + } + } + ) + ) + + with patch.object( + module, + "get_config", + return_value=SimpleNamespace(name="telegram-test", config={}), + ), patch.object( + module, + "get_instance", + return_value=SimpleNamespace(bot_username=None), + ): + message = module.message_parser( + source="telegram-test", body=body, form={}, args={} + ) + + self.assertIsNotNone(message) + self.assertEqual(message.images, ["tg://file_id/large"]) + + def test_telegram_forward_payload_uses_dict_not_json_string(self): + payload = Telegram._serialize_update_payload( + SimpleNamespace( + to_dict=lambda: { + "text": "hi", + "photo": [{"file_id": "image-1"}], + } + ) + ) + + self.assertEqual( + payload, + {"text": "hi", "photo": [{"file_id": "image-1"}]}, + ) + def test_process_allows_image_only_message(self): chain = MessageChain() message = CommingMessage(