From 58d7be7f0811e0c9bbd3900a23427630e44022c4 Mon Sep 17 00:00:00 2001 From: tcmofashi Date: Fri, 16 Jan 2026 07:21:15 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81maim=5Fmessage=20API?= =?UTF-8?q?=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 3 +- src/config/official_configs.py | 9 ++ src/mmc_com_layer.py | 165 ++++++++++++++++++++++++++++++--- template/template_config.toml | 3 + 4 files changed, 165 insertions(+), 15 deletions(-) diff --git a/main.py b/main.py index 6f824a6..5761593 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ from src.recv_handler.notice_handler import notice_handler from src.recv_handler.message_sending import message_send_instance from src.send_handler.nc_sending import nc_message_sender from src.config import global_config -from src.mmc_com_layer import mmc_start_com, mmc_stop_com, router +from src.mmc_com_layer import mmc_start_com, mmc_stop_com from src.response_pool import put_response, check_timeout_response message_queue = asyncio.Queue() @@ -47,7 +47,6 @@ async def message_process(): async def main(): - message_send_instance.maibot_router = router _ = await asyncio.gather(napcat_server(), mmc_start_com(), message_process(), check_timeout_response()) def check_napcat_server_token(conn, request): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 98b4552..245501b 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -46,6 +46,15 @@ class MaiBotServerConfig(ConfigBase): port: int = 8000 """MaiMCore的端口号""" + enable_api_server: bool = False + """是否启用API-Server模式连接""" + + base_url: str = "" + """API-Server连接地址 (ws://ipp:port/path)""" + + api_key: str = "" + """API Key (仅在enable_api_server为True时使用)""" + @dataclass class ChatConfig(ConfigBase): diff --git a/src/mmc_com_layer.py b/src/mmc_com_layer.py index 012d153..f2a5206 100644 --- a/src/mmc_com_layer.py +++ b/src/mmc_com_layer.py @@ -1,24 +1,163 @@ -from maim_message import Router, RouteConfig, TargetConfig +from maim_message import Router, RouteConfig, TargetConfig, MessageBase from .config import global_config from .logger import logger, custom_logger from .send_handler.main_send_handler import send_handler +from .recv_handler.message_sending import message_send_instance +from maim_message.client import create_client_config, WebSocketClient +from maim_message.message import APIMessageBase +from typing import Dict, Any +import importlib.metadata -route_config = RouteConfig( - route_config={ - global_config.maibot_server.platform_name: TargetConfig( - url=f"ws://{global_config.maibot_server.host}:{global_config.maibot_server.port}/ws", - token=None, +# 检查 maim_message 版本是否支持 MessageConverter (>= 0.6.2) +try: + maim_message_version = importlib.metadata.version("maim_message") + version_int = [int(x) for x in maim_message_version.split(".")] + HAS_MESSAGE_CONVERTER = version_int >= [0, 6, 2] +except (importlib.metadata.PackageNotFoundError, ValueError): + HAS_MESSAGE_CONVERTER = False + +# router = Router(route_config, custom_logger) +# router will be initialized in mmc_start_com +router = None + + +class APIServerWrapper: + """ + Wrapper to make WebSocketClient compatible with legacy Router interface + """ + def __init__(self, client: WebSocketClient): + self.client = client + self.platform = global_config.maibot_server.platform_name + + def register_class_handler(self, handler): + # In API Server mode, we register the on_message callback in config, + # but here we might need to bridge it if the handler structure is different. + # However, WebSocketClient config handles on_message. + # The legacy Router.register_class_handler registers a handler for received messages. + # We need to adapt the callback style. + pass + + async def send_message(self, message: MessageBase) -> bool: + # 使用 MessageConverter 转换 Legacy MessageBase 到 APIMessageBase + # 接收场景:Adapter 收到来自 Napcat 的消息,发送给 MaiMBot + # group_info/user_info 是消息发送者信息,放入 sender_info + from maim_message import MessageConverter + + api_message = MessageConverter.to_api_receive( + message=message, + api_key=global_config.maibot_server.api_key, + platform=message.message_info.platform or self.platform, ) - } -) -router = Router(route_config, custom_logger) + return await self.client.send_message(api_message) + async def send_custom_message(self, platform: str, message_type_name: str, message: Dict) -> bool: + return await self.client.send_custom_message(message_type_name, message) + + async def run(self): + await self.client.start() + await self.client.connect() + + async def stop(self): + await self.client.stop() + +# Global variable to hold the communication object (Router or Wrapper) +router = None + +async def _legacy_message_handler_adapter(message: APIMessageBase, metadata: dict): + # Adapter to call the legacy handler with dict as expected by main_send_handler + # send_handler.handle_message expects a dict. + # We need to convert APIMessageBase back to dict legacy format if possible. + # Or check what handle_message expects. + # main_send_handler.py: handle_message takes raw_message_base_dict: dict + # and does MessageBase.from_dict(raw_message_base_dict). + + # So we need to serialize APIMessageBase to a dict that looks like legacy MessageBase dict. + # This might be tricky if structures diverged. + # Let's try `to_dict()` if available, otherwise construct it. + + # Inspecting APIMessageBase structure from docs: + # APIMessageBase has message_info, message_segment, message_dim. + # Legacy MessageBase has message_info, message_segment. + + # We can try to construct the dict. + data = { + "message_info": { + "id": message.message_info.message_id, + "timestamp": message.message_info.time, + "group_info": {}, # Fill if available + "user_info": {}, # Fill if available + }, + "message_segment": { + "type": message.message_segment.type, + "data": message.message_segment.data + } + } + # Note: This is an approximation. Ideally we should check strict compatibility. + # However, for the adapter -> bot direction (sending to napcat), + # the bot sends messages to adapter? No, Adapter sends to Bot? + # mmc_com_layer seems to be for Adapter talking to MaiBot Core. + # recv_handler/message_sending.py uses this router to send TO MaiBot. + # The `register_class_handler` in `mmc_start_com` suggests MaiBot sends messages TO Adapter? + # Wait, `send_handler.handle_message` seems to be handling messages RECEIVED FROM MaiBot. + # So `router` is bidirectional. + + # If explicit to_dict is needed: + await send_handler.handle_message(data) async def mmc_start_com(): - logger.info("正在连接MaiBot") - router.register_class_handler(send_handler.handle_message) - await router.run() + global router + config = global_config.maibot_server + + if config.enable_api_server and HAS_MESSAGE_CONVERTER: + logger.info("使用 API-Server 模式连接 MaiBot") + + # Create legacy adapter handler + # We need to define the on_message callback here to bridge to send_handler + async def on_message_bridge(message: APIMessageBase, metadata: Dict[str, Any]): + # 使用 MessageConverter 转换 APIMessageBase 到 Legacy MessageBase + # 发送场景:收到来自 MaiMBot 的回复消息,需要发送给 Napcat + # receiver_info 包含消息接收者信息,需要提取到 group_info/user_info + try: + from maim_message import MessageConverter + + legacy_message = MessageConverter.from_api_send(message) + msg_dict = legacy_message.to_dict() + + await send_handler.handle_message(msg_dict) + + except Exception as e: + logger.error(f"消息桥接转换失败: {e}") + import traceback + logger.error(traceback.format_exc()) + + client_config = create_client_config( + url=config.base_url, + api_key=config.api_key, + platform=config.platform_name, + on_message=on_message_bridge + ) + + client = WebSocketClient(client_config) + router = APIServerWrapper(client) + message_send_instance.maibot_router = router + await router.run() + + else: + logger.info("使用 Legacy WebSocket 模式连接 MaiBot") + route_config = RouteConfig( + route_config={ + config.platform_name: TargetConfig( + url=f"ws://{config.host}:{config.port}/ws", + token=None, + ) + } + ) + router = Router(route_config, custom_logger) + router.register_class_handler(send_handler.handle_message) + message_send_instance.maibot_router = router + await router.run() async def mmc_stop_com(): - await router.stop() + if router: + await router.stop() diff --git a/template/template_config.toml b/template/template_config.toml index 63b55ae..d217f8c 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -14,6 +14,9 @@ heartbeat_interval = 30 # 与Napcat设置的心跳相同(按秒计) [maibot_server] # 连接麦麦的ws服务设置 host = "localhost" # 麦麦在.env文件中设置的主机地址,即HOST字段 port = 8000 # 麦麦在.env文件中设置的端口,即PORT字段 +enable_api_server = false # 是否启用API-Server模式连接 +base_url = "" # API-Server连接地址 (ws://ip:port/path),仅在enable_api_server为true时使用 +api_key = "" # API Key (仅在enable_api_server为true时使用) [chat] # 黑白名单功能 group_list_type = "whitelist" # 群组名单类型,可选为:whitelist, blacklist