mirror of https://github.com/Mai-with-u/MaiBot.git
Merge pull request #1485 from tcmofashi/dev
feat: 完整支持maim_message API mode,经过与nc ada联调(其他开发未验证)pull/1489/head
commit
19eee8358f
|
|
@ -12,7 +12,7 @@ dependencies = [
|
||||||
"google-genai>=1.39.1",
|
"google-genai>=1.39.1",
|
||||||
"jieba>=0.42.1",
|
"jieba>=0.42.1",
|
||||||
"json-repair>=0.47.6",
|
"json-repair>=0.47.6",
|
||||||
"maim-message",
|
"maim-message>=0.6.2",
|
||||||
"matplotlib>=3.10.3",
|
"matplotlib>=3.10.3",
|
||||||
"msgpack>=1.1.2",
|
"msgpack>=1.1.2",
|
||||||
"numpy>=2.2.6",
|
"numpy>=2.2.6",
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,7 @@ async def _send_message(message: MessageSending, show_log=True) -> bool:
|
||||||
|
|
||||||
# 如果未开启 API Server,直接跳过 Fallback
|
# 如果未开启 API Server,直接跳过 Fallback
|
||||||
if not global_config.maim_message.enable_api_server:
|
if not global_config.maim_message.enable_api_server:
|
||||||
|
logger.debug(f"[API Server Fallback] API Server未开启,跳过fallback")
|
||||||
if legacy_exception:
|
if legacy_exception:
|
||||||
raise legacy_exception
|
raise legacy_exception
|
||||||
return False
|
return False
|
||||||
|
|
@ -196,36 +197,59 @@ async def _send_message(message: MessageSending, show_log=True) -> bool:
|
||||||
global_api = get_global_api()
|
global_api = get_global_api()
|
||||||
extra_server = getattr(global_api, "extra_server", None)
|
extra_server = getattr(global_api, "extra_server", None)
|
||||||
|
|
||||||
if extra_server and extra_server.is_running():
|
if not extra_server:
|
||||||
# Fallback: 使用极其简单的 Platform -> API Key 映射
|
logger.warning(f"[API Server Fallback] extra_server不存在")
|
||||||
# 只有收到过该平台的消息,我们才知道该平台的 API Key,才能回传消息
|
if legacy_exception:
|
||||||
platform_map = getattr(global_api, "platform_map", {})
|
raise legacy_exception
|
||||||
target_api_key = platform_map.get(platform)
|
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:
|
# Fallback: 使用极其简单的 Platform -> API Key 映射
|
||||||
# 构造 APIMessageBase
|
# 只有收到过该平台的消息,我们才知道该平台的 API Key,才能回传消息
|
||||||
from maim_message.message import APIMessageBase, MessageDim
|
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(
|
# 使用 MessageConverter 转换 Legacy MessageBase 到 APIMessageBase
|
||||||
message_info=message.message_info,
|
# 发送场景:MaiMBot 发送回复消息给外部用户
|
||||||
message_segment=message.message_segment,
|
# group_info/user_info 是消息接收者信息,放入 receiver_info
|
||||||
message_dim=msg_dim,
|
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})"
|
||||||
)
|
)
|
||||||
|
return True
|
||||||
# 直接调用 Server 的 send_message 接口,它会自动处理路由
|
else:
|
||||||
results = await extra_server.send_message(api_message)
|
logger.warning(f"[API Server Fallback] 没有连接发送成功, results={results}")
|
||||||
|
except Exception as e:
|
||||||
# 检查是否有任何连接发送成功
|
logger.error(f"[API Server Fallback] 发生异常: {e}")
|
||||||
if any(results.values()):
|
import traceback
|
||||||
if show_log:
|
logger.debug(traceback.format_exc())
|
||||||
logger.info(
|
|
||||||
f"已通过API Server Fallback将消息 '{message_preview}' 发往平台'{platform}' (key: {target_api_key})"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 如果 Fallback 失败,且存在 legacy 异常,则抛出 legacy 异常
|
# 如果 Fallback 失败,且存在 legacy 异常,则抛出 legacy 异常
|
||||||
if legacy_exception:
|
if legacy_exception:
|
||||||
|
|
@ -234,13 +258,17 @@ async def _send_message(message: MessageSending, show_log=True) -> bool:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
send_result = await get_global_api().send_message(message)
|
send_result = await get_global_api().send_message(message)
|
||||||
# if send_result:
|
if send_result:
|
||||||
if show_log:
|
if show_log:
|
||||||
logger.info(f"已将消息 '{message_preview}' 发往平台'{message.message_info.platform}'")
|
logger.info(f"已将消息 '{message_preview}' 发往平台'{message.message_info.platform}'")
|
||||||
return True
|
return True
|
||||||
|
else:
|
||||||
# Legacy API 返回 False (发送失败但未报错),尝试 Fallback
|
# Legacy API 返回 False (发送失败但未报错),尝试 Fallback
|
||||||
# return await send_with_new_api()
|
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:
|
except Exception as legacy_e:
|
||||||
# Legacy API 抛出异常,尝试 Fallback
|
# Legacy API 抛出异常,尝试 Fallback
|
||||||
|
|
|
||||||
|
|
@ -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_int = [int(x) for x in maim_message_version.split(".")]
|
||||||
version_compatible = version_int >= [0, 3, 3]
|
version_compatible = version_int >= [0, 3, 3]
|
||||||
# Check for API Server feature (>= 0.6.0)
|
# 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):
|
except (importlib.metadata.PackageNotFoundError, ValueError):
|
||||||
version_compatible = False
|
version_compatible = False
|
||||||
has_api_server_feature = False
|
has_api_server_feature = False
|
||||||
|
|
@ -75,6 +75,7 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method
|
||||||
ssl_enabled=use_wss,
|
ssl_enabled=use_wss,
|
||||||
ssl_certfile=maim_message_config.api_server_cert_file if use_wss else None,
|
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,
|
ssl_keyfile=maim_message_config.api_server_key_file if use_wss else None,
|
||||||
|
custom_logger=api_logger # 传入自定义logger
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Setup Auth Handler
|
# 2. Setup Auth Handler
|
||||||
|
|
@ -99,32 +100,39 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method
|
||||||
global_api.platform_map = {}
|
global_api.platform_map = {}
|
||||||
|
|
||||||
async def bridge_message_handler(message: APIMessageBase, metadata: dict):
|
async def bridge_message_handler(message: APIMessageBase, metadata: dict):
|
||||||
# Bridge message to the main bot logic
|
# 使用 MessageConverter 转换 APIMessageBase 到 Legacy MessageBase
|
||||||
# We convert APIMessageBase to dict to be compatible with legacy handlers
|
# 接收场景:收到从 Adapter 转发的外部消息
|
||||||
# that MainBot (ChatManager) expects.
|
# sender_info 包含消息发送者信息,需要提取到 group_info/user_info
|
||||||
msg_dict = message.to_dict()
|
from maim_message import MessageConverter
|
||||||
|
|
||||||
# Compatibility Layer: Flatten sender_info to top-level user_info/group_info
|
legacy_message = MessageConverter.from_api_receive(message)
|
||||||
# Legacy MessageBase expects message_info to have user_info and group_info directly.
|
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:
|
if "message_info" in msg_dict:
|
||||||
msg_info = msg_dict["message_info"]
|
msg_info = msg_dict["message_info"]
|
||||||
sender_info = msg_info.get("sender_info")
|
|
||||||
if sender_info:
|
if "format_info" not in msg_info or msg_info["format_info"] is None:
|
||||||
# If direct user_info/group_info are missing, populate them from sender_info
|
msg_info["format_info"] = {
|
||||||
if "user_info" not in msg_info and (ui := sender_info.get("user_info")):
|
"content_format": ["text", "image", "emoji", "voice"],
|
||||||
msg_info["user_info"] = ui
|
"accept_format": [
|
||||||
|
"text", "image", "emoji", "reply", "voice", "command",
|
||||||
if "group_info" not in msg_info and (gi := sender_info.get("group_info")):
|
"voiceurl", "music", "videourl", "file", "imageurl", "forward", "video"
|
||||||
msg_info["group_info"] = gi
|
]
|
||||||
|
}
|
||||||
|
|
||||||
# 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
|
# This allows us to send messages back to the correct API client for this platform
|
||||||
try:
|
try:
|
||||||
api_key = metadata.get("api_key")
|
# Get api_key from metadata, use uuid as fallback if api_key is empty
|
||||||
if api_key:
|
api_key = metadata.get("api_key") or metadata.get("uuid") or "unknown"
|
||||||
platform = msg_info.get("platform")
|
platform = msg_info.get("platform")
|
||||||
if platform:
|
api_logger.debug(f"Bridge received: api_key='{api_key}', platform='{platform}'")
|
||||||
global_api.platform_map[platform] = api_key
|
|
||||||
|
if platform:
|
||||||
|
global_api.platform_map[platform] = api_key
|
||||||
|
api_logger.info(f"Updated platform_map: {platform} -> {api_key}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
api_logger.warning(f"Failed to update platform map: {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
|
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
|
# 4. Initialize Server
|
||||||
extra_server = WebSocketServer(config=server_config)
|
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())
|
get_logger("maim_message").debug(traceback.format_exc())
|
||||||
|
|
||||||
return global_api
|
return global_api
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue