diff --git a/pyproject.toml b/pyproject.toml index 85dd74e3..ec2dff16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "google-genai>=1.39.1", "jieba>=0.42.1", "json-repair>=0.47.6", - "maim-message", + "maim-message>=0.6.2", "matplotlib>=3.10.3", "msgpack>=1.1.2", "numpy>=2.2.6", diff --git a/src/chat/message_receive/uni_message_sender.py b/src/chat/message_receive/uni_message_sender.py index b0a328e1..8ea7f48d 100644 --- a/src/chat/message_receive/uni_message_sender.py +++ b/src/chat/message_receive/uni_message_sender.py @@ -189,6 +189,7 @@ async def _send_message(message: MessageSending, show_log=True) -> bool: # 如果未开启 API Server,直接跳过 Fallback if not global_config.maim_message.enable_api_server: + logger.debug(f"[API Server Fallback] API Server未开启,跳过fallback") if legacy_exception: raise legacy_exception return False @@ -196,36 +197,59 @@ async def _send_message(message: MessageSending, show_log=True) -> bool: global_api = get_global_api() extra_server = getattr(global_api, "extra_server", None) - if extra_server and extra_server.is_running(): - # Fallback: 使用极其简单的 Platform -> API Key 映射 - # 只有收到过该平台的消息,我们才知道该平台的 API Key,才能回传消息 - platform_map = getattr(global_api, "platform_map", {}) - target_api_key = platform_map.get(platform) + if not extra_server: + logger.warning(f"[API Server Fallback] extra_server不存在") + if legacy_exception: + raise legacy_exception + return False + + if not extra_server.is_running(): + logger.warning(f"[API Server Fallback] extra_server未运行") + if legacy_exception: + raise legacy_exception + return False - if target_api_key: - # 构造 APIMessageBase - from maim_message.message import APIMessageBase, MessageDim + # Fallback: 使用极其简单的 Platform -> API Key 映射 + # 只有收到过该平台的消息,我们才知道该平台的 API Key,才能回传消息 + platform_map = getattr(global_api, "platform_map", {}) + logger.debug(f"[API Server Fallback] platform_map: {platform_map}, 目标平台: '{platform}'") + target_api_key = platform_map.get(platform) - msg_dim = MessageDim(api_key=target_api_key, platform=platform) + if not target_api_key: + logger.warning(f"[API Server Fallback] 未找到平台'{platform}'的API Key映射") + if legacy_exception: + raise legacy_exception + return False - api_message = APIMessageBase( - message_info=message.message_info, - message_segment=message.message_segment, - message_dim=msg_dim, + # 使用 MessageConverter 转换 Legacy MessageBase 到 APIMessageBase + # 发送场景:MaiMBot 发送回复消息给外部用户 + # group_info/user_info 是消息接收者信息,放入 receiver_info + from maim_message import MessageConverter + + api_message = MessageConverter.to_api_send( + message=message, + api_key=target_api_key, + platform=platform, + ) + + # 直接调用 Server 的 send_message 接口,它会自动处理路由 + logger.debug(f"[API Server Fallback] 正在通过extra_server发送消息...") + results = await extra_server.send_message(api_message) + logger.debug(f"[API Server Fallback] 发送结果: {results}") + + # 检查是否有任何连接发送成功 + if any(results.values()): + if show_log: + logger.info( + f"已通过API Server Fallback将消息 '{message_preview}' 发往平台'{platform}' (key: {target_api_key})" ) - - # 直接调用 Server 的 send_message 接口,它会自动处理路由 - results = await extra_server.send_message(api_message) - - # 检查是否有任何连接发送成功 - if any(results.values()): - if show_log: - logger.info( - f"已通过API Server Fallback将消息 '{message_preview}' 发往平台'{platform}' (key: {target_api_key})" - ) - return True - except Exception: - pass + return True + else: + logger.warning(f"[API Server Fallback] 没有连接发送成功, results={results}") + except Exception as e: + logger.error(f"[API Server Fallback] 发生异常: {e}") + import traceback + logger.debug(traceback.format_exc()) # 如果 Fallback 失败,且存在 legacy 异常,则抛出 legacy 异常 if legacy_exception: @@ -234,13 +258,17 @@ async def _send_message(message: MessageSending, show_log=True) -> bool: try: send_result = await get_global_api().send_message(message) - # if send_result: - if show_log: - logger.info(f"已将消息 '{message_preview}' 发往平台'{message.message_info.platform}'") - return True - - # Legacy API 返回 False (发送失败但未报错),尝试 Fallback - # return await send_with_new_api() + if send_result: + if show_log: + logger.info(f"已将消息 '{message_preview}' 发往平台'{message.message_info.platform}'") + return True + else: + # Legacy API 返回 False (发送失败但未报错),尝试 Fallback + fallback_result = await send_with_new_api() + if fallback_result and show_log: + # Fallback成功的日志已在send_with_new_api中打印 + pass + return fallback_result except Exception as legacy_e: # Legacy API 抛出异常,尝试 Fallback diff --git a/src/common/message/api.py b/src/common/message/api.py index f9f6c8f5..3a7193a6 100644 --- a/src/common/message/api.py +++ b/src/common/message/api.py @@ -18,7 +18,7 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method version_int = [int(x) for x in maim_message_version.split(".")] version_compatible = version_int >= [0, 3, 3] # Check for API Server feature (>= 0.6.0) - has_api_server_feature = version_int >= [0, 6, 0] + has_api_server_feature = version_int >= [0, 6, 2] except (importlib.metadata.PackageNotFoundError, ValueError): version_compatible = False has_api_server_feature = False @@ -75,6 +75,7 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method ssl_enabled=use_wss, ssl_certfile=maim_message_config.api_server_cert_file if use_wss else None, ssl_keyfile=maim_message_config.api_server_key_file if use_wss else None, + custom_logger=api_logger # 传入自定义logger ) # 2. Setup Auth Handler @@ -99,32 +100,39 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method global_api.platform_map = {} async def bridge_message_handler(message: APIMessageBase, metadata: dict): - # Bridge message to the main bot logic - # We convert APIMessageBase to dict to be compatible with legacy handlers - # that MainBot (ChatManager) expects. - msg_dict = message.to_dict() + # 使用 MessageConverter 转换 APIMessageBase 到 Legacy MessageBase + # 接收场景:收到从 Adapter 转发的外部消息 + # sender_info 包含消息发送者信息,需要提取到 group_info/user_info + from maim_message import MessageConverter - # Compatibility Layer: Flatten sender_info to top-level user_info/group_info - # Legacy MessageBase expects message_info to have user_info and group_info directly. + legacy_message = MessageConverter.from_api_receive(message) + msg_dict = legacy_message.to_dict() + + # Compatibility Layer: Ensure format_info exists with defaults + # MaiMBot's check_types() accesses format_info.accept_format without None check if "message_info" in msg_dict: msg_info = msg_dict["message_info"] - sender_info = msg_info.get("sender_info") - if sender_info: - # If direct user_info/group_info are missing, populate them from sender_info - if "user_info" not in msg_info and (ui := sender_info.get("user_info")): - msg_info["user_info"] = ui - - if "group_info" not in msg_info and (gi := sender_info.get("group_info")): - msg_info["group_info"] = gi + + if "format_info" not in msg_info or msg_info["format_info"] is None: + msg_info["format_info"] = { + "content_format": ["text", "image", "emoji", "voice"], + "accept_format": [ + "text", "image", "emoji", "reply", "voice", "command", + "voiceurl", "music", "videourl", "file", "imageurl", "forward", "video" + ] + } - # Route Caching Logic: Simply map platform to API Key + # Route Caching Logic: Map platform to API Key (or connection uuid as fallback) # This allows us to send messages back to the correct API client for this platform try: - api_key = metadata.get("api_key") - if api_key: - platform = msg_info.get("platform") - if platform: - global_api.platform_map[platform] = api_key + # Get api_key from metadata, use uuid as fallback if api_key is empty + api_key = metadata.get("api_key") or metadata.get("uuid") or "unknown" + platform = msg_info.get("platform") + api_logger.debug(f"Bridge received: api_key='{api_key}', platform='{platform}'") + + if platform: + global_api.platform_map[platform] = api_key + api_logger.info(f"Updated platform_map: {platform} -> {api_key}") except Exception as e: api_logger.warning(f"Failed to update platform map: {e}") @@ -136,6 +144,27 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method server_config.on_message = bridge_message_handler + # 3.5. Register custom message handlers (bridge to Legacy handlers) + # message_id_echo: handles message ID echo from adapters + # 兼容新旧两个版本的 maim_message: + # - 旧版: handler(payload) + # - 新版: handler(payload, metadata) + async def custom_message_id_echo_handler(payload: dict, metadata: dict = None): + # Bridge to the Legacy custom handler registered in main.py + try: + # The Legacy handler expects the payload format directly + if hasattr(global_api, '_custom_message_handlers'): + handler = global_api._custom_message_handlers.get("message_id_echo") + if handler: + await handler(payload) + api_logger.debug(f"Processed message_id_echo: {payload}") + else: + api_logger.debug(f"No handler for message_id_echo, payload: {payload}") + except Exception as e: + api_logger.warning(f"Failed to process message_id_echo: {e}") + + server_config.register_custom_handler("message_id_echo", custom_message_id_echo_handler) + # 4. Initialize Server extra_server = WebSocketServer(config=server_config) @@ -169,3 +198,4 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method get_logger("maim_message").debug(traceback.format_exc()) return global_api +