feat: Add NapCat adapter plugin and enhance message handling

- Introduced a built-in NapCat adapter plugin for MVP message forwarding.
- Implemented core functionalities for connecting to NapCat/OneBot v11 WebSocket service.
- Added message serialization capabilities for WebUI chat routes.
- Enhanced the RegisterPluginPayload to include optional adapter declarations.
- Implemented methods for handling external messages and adapter declarations in the PluginRunner.
- Improved the send_service to inherit platform IO route metadata for outgoing messages.
This commit is contained in:
DrSmoothl
2026-03-21 00:18:28 +08:00
parent 75cd50ee0f
commit 85f060621d
14 changed files with 1683 additions and 179 deletions

View File

@@ -4,7 +4,7 @@
提供发送各种类型消息的核心功能。
"""
from typing import Dict, List, Optional, TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Dict, List, Optional
import time
import traceback
@@ -19,6 +19,7 @@ from src.common.data_models.mai_message_data_model import MaiMessage
from src.common.data_models.message_component_data_model import DictComponent, MessageSequence
from src.common.logger import get_logger
from src.config.config import global_config
from src.platform_io.route_key_factory import RouteKeyFactory
if TYPE_CHECKING:
from src.chat.message_receive.message import SessionMessage
@@ -31,6 +32,50 @@ logger = get_logger("send_service")
# =============================================================================
def _inherit_platform_io_route_metadata(target_stream: Any) -> Dict[str, object]:
"""从目标会话上下文继承 Platform IO 路由元数据。
Args:
target_stream: 当前消息要发送到的会话对象。
Returns:
Dict[str, object]: 可安全透传到出站消息 ``additional_config`` 中的
路由辅助字段。
"""
inherited_metadata: Dict[str, object] = {}
context = getattr(target_stream, "context", None)
context_message = getattr(context, "message", None)
if context_message is None:
return inherited_metadata
additional_config = getattr(context_message.message_info, "additional_config", {})
if not isinstance(additional_config, dict):
return inherited_metadata
for key in (*RouteKeyFactory.ACCOUNT_ID_KEYS, *RouteKeyFactory.SCOPE_KEYS):
value = additional_config.get(key)
if value is None:
continue
normalized_value = str(value).strip()
if normalized_value:
inherited_metadata[key] = value
target_group_id = getattr(target_stream, "group_id", None)
if target_group_id is not None:
normalized_group_id = str(target_group_id).strip()
if normalized_group_id:
inherited_metadata["platform_io_target_group_id"] = normalized_group_id
target_user_id = getattr(target_stream, "user_id", None)
if target_user_id is not None:
normalized_user_id = str(target_user_id).strip()
if normalized_user_id:
inherited_metadata["platform_io_target_user_id"] = normalized_user_id
return inherited_metadata
async def _send_to_target(
message_segment: Seg,
stream_id: str,
@@ -42,7 +87,22 @@ async def _send_to_target(
show_log: bool = True,
selected_expressions: Optional[List[int]] = None,
) -> bool:
"""向指定目标发送消息的内部实现"""
"""向指定目标发送消息
Args:
message_segment: 待发送的消息段。
stream_id: 目标会话 ID。
display_message: 用于界面展示的文本内容。
typing: 是否显示输入中状态。
set_reply: 是否在发送时附带引用回复。
reply_message: 被回复的消息对象。
storage_message: 是否将发送结果写入消息存储。
show_log: 是否输出发送日志。
selected_expressions: 可选的表情候选索引列表。
Returns:
bool: 发送成功返回 ``True``,否则返回 ``False``。
"""
try:
if set_reply and not reply_message:
logger.warning("[SendService] 使用引用回复,但未提供回复消息")
@@ -80,7 +140,7 @@ async def _send_to_target(
platform=target_stream.platform,
)
additional_config: dict[str, object] = {}
additional_config: Dict[str, object] = _inherit_platform_io_route_metadata(target_stream)
if selected_expressions is not None:
additional_config["selected_expressions"] = selected_expressions
bot_user_id = get_bot_account(target_stream.platform)