Files
Mofox-Core/src/plugin_system/base/base_adapter.py
Windpicker-owo 8fc4cd4c3b 重构:移除过时的napcat_adapter_plugin组件
- 从napcat_adapter_plugin中删除了stream_router.py、utils.py、video_handler.py、websocket_manager.py和todo.md文件。
- 在napcat_cache.json中为组和成员信息引入了一种新的缓存结构。
- 通过移除未使用的模块和整合功能,简化了插件的架构。
2025-11-26 16:40:31 +08:00

286 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
插件系统 Adapter 基类
提供插件化的适配器支持,包装 mofox_wire.AdapterBase
添加插件生命周期、配置管理、自动启动等特性。
"""
from __future__ import annotations
import asyncio
from abc import ABC, abstractmethod
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, Optional
from mofox_wire import AdapterBase as MoFoxAdapterBase, CoreSink, MessageEnvelope, ProcessCoreSink
if TYPE_CHECKING:
from src.plugin_system import BasePlugin, AdapterInfo
from src.common.logger import get_logger
logger = get_logger("plugin.adapter")
class BaseAdapter(MoFoxAdapterBase, ABC):
"""
插件系统的 Adapter 基类
相比 mofox_wire.AdapterBase增加了以下特性
1. 插件生命周期管理 (on_adapter_loaded, on_adapter_unloaded)
2. 配置管理集成
3. 自动重连与健康检查
4. 子进程启动支持
"""
# 适配器元数据
adapter_name: str = "unknown_adapter"
adapter_version: str = "0.0.1"
adapter_author: str = "Unknown"
adapter_description: str = "No description"
# 是否在子进程中运行
run_in_subprocess: bool = True
# 子进程启动脚本路径(相对于插件目录)
subprocess_entry: Optional[str] = None
def __init__(
self,
core_sink: CoreSink,
plugin: Optional[BasePlugin] = None,
**kwargs
):
"""
Args:
core_sink: 核心消息接收器
plugin: 所属插件实例(可选)
**kwargs: 传递给 AdapterBase 的其他参数
"""
super().__init__(core_sink, **kwargs)
self.plugin = plugin
self._config: Dict[str, Any] = {}
self._health_check_task: Optional[asyncio.Task] = None
self._running = False
# 标记是否在子进程中运行
self._is_subprocess = False
@classmethod
def from_process_queues(
cls,
to_core_queue,
from_core_queue,
plugin: Optional["BasePlugin"] = None,
**kwargs: Any,
) -> "BaseAdapter":
"""
子进程入口便捷构造:使用 multiprocessing.Queue 与核心建立 ProcessCoreSink 通讯。
Args:
to_core_queue: 发往核心的 multiprocessing.Queue
from_core_queue: 核心回传的 multiprocessing.Queue
plugin: 可选插件实例
**kwargs: 透传给适配器构造函数
"""
sink = ProcessCoreSink(to_core_queue=to_core_queue, from_core_queue=from_core_queue)
return cls(core_sink=sink, plugin=plugin, **kwargs)
@property
def config(self) -> Dict[str, Any]:
"""获取适配器配置"""
if self.plugin and hasattr(self.plugin, "config"):
return self.plugin.config
return self._config
@config.setter
def config(self, value: Dict[str, Any]) -> None:
"""设置适配器配置"""
self._config = value
def get_config(self, key: str, default: Any = None) -> Any:
"""获取适配器配置,优先使用插件配置,其次使用内部配置。"""
current = self.config or {}
for part in key.split("."):
if isinstance(current, dict) and part in current:
current = current[part]
else:
return default
return current
async def start(self) -> None:
"""启动适配器"""
logger.info(f"启动适配器: {self.adapter_name} v{self.adapter_version}")
# 调用生命周期钩子
await self.on_adapter_loaded()
# 调用父类启动
await super().start()
# 启动健康检查
if self.config.get("enable_health_check", False):
self._health_check_task = asyncio.create_task(self._health_check_loop())
self._running = True
logger.info(f"适配器 {self.adapter_name} 启动成功")
async def stop(self) -> None:
"""停止适配器"""
logger.info(f"停止适配器: {self.adapter_name}")
self._running = False
# 停止健康检查
if self._health_check_task and not self._health_check_task.done():
self._health_check_task.cancel()
try:
await self._health_check_task
except asyncio.CancelledError:
pass
# 调用父类停止
await super().stop()
# 调用生命周期钩子
await self.on_adapter_unloaded()
logger.info(f"适配器 {self.adapter_name} 已停止")
async def on_adapter_loaded(self) -> None:
"""
适配器加载时的钩子
子类可重写以执行初始化逻辑
"""
pass
async def on_adapter_unloaded(self) -> None:
"""
适配器卸载时的钩子
子类可重写以执行清理逻辑
"""
pass
async def _health_check_loop(self) -> None:
"""健康检查循环"""
interval = self.config.get("health_check_interval", 30)
while self._running:
try:
await asyncio.sleep(interval)
# 执行健康检查
is_healthy = await self.health_check()
if not is_healthy:
logger.warning(f"适配器 {self.adapter_name} 健康检查失败,尝试重连...")
await self.reconnect()
except asyncio.CancelledError:
break
except Exception as e:
logger.error(f"适配器 {self.adapter_name} 健康检查异常: {e}", exc_info=True)
async def health_check(self) -> bool:
"""
健康检查
子类可重写以实现自定义检查逻辑
Returns:
bool: 是否健康
"""
# 默认检查 WebSocket 连接状态
if self._ws and not self._ws.closed:
return True
return False
async def reconnect(self) -> None:
"""
重新连接
子类可重写以实现自定义重连逻辑
"""
try:
await self.stop()
await asyncio.sleep(2) # 等待一段时间再重连
await self.start()
except Exception as e:
logger.error(f"适配器 {self.adapter_name} 重连失败: {e}", exc_info=True)
def get_subprocess_entry_path(self) -> Optional[Path]:
"""
获取子进程启动脚本的完整路径
Returns:
Path | None: 脚本路径,如果不存在则返回 None
"""
if not self.subprocess_entry:
return None
if not self.plugin:
return None
# 获取插件目录
plugin_dir = Path(self.plugin.__file__).parent
entry_path = plugin_dir / self.subprocess_entry
if entry_path.exists():
return entry_path
logger.warning(f"子进程入口脚本不存在: {entry_path}")
return None
@classmethod
def get_adapter_info(cls) -> "AdapterInfo":
"""获取适配器的信息
Returns:
AdapterInfo: 适配器组件信息
"""
from src.plugin_system.base.component_types import AdapterInfo
return AdapterInfo(
name=getattr(cls, "adapter_name", cls.__name__.lower().replace("adapter", "")),
version=getattr(cls, "adapter_version", "1.0.0"),
platform=getattr(cls, "platform", "unknown"),
description=getattr(cls, "adapter_description", ""),
enabled=True,
run_in_subprocess=getattr(cls, "run_in_subprocess", False),
subprocess_entry=getattr(cls, "subprocess_entry", None),
)
@abstractmethod
async def from_platform_message(self, raw: Any) -> MessageEnvelope:
"""
将平台原始消息转换为 MessageEnvelope
子类必须实现此方法
Args:
raw: 平台原始消息
Returns:
MessageEnvelope: 统一的消息信封
"""
raise NotImplementedError
async def _send_platform_message(self, envelope: MessageEnvelope) -> None:
"""
发送消息到平台
如果使用了 WebSocketAdapterOptions 或 HttpAdapterOptions
此方法会自动处理。否则子类需要重写此方法。
Args:
envelope: 要发送的消息信封
"""
# 如果配置了自动传输,调用父类方法
if self._transport_config:
await super()._send_platform_message(envelope)
else:
raise NotImplementedError(
f"适配器 {self.adapter_name} 未配置自动传输,必须重写 _send_platform_message 方法"
)
__all__ = ["BaseAdapter"]