feat: 添加带有消息处理和路由功能的NEW_napcat_adapter插件

- 为NEW_napcat_adapter插件实现了核心模块,包括消息处理、事件处理和路由。
- 创建了MessageHandler、MetaEventHandler和NoticeHandler来处理收到的消息和事件。
- 开发了SendHandler,用于向Napcat发送回消息。
引入了StreamRouter来管理多个聊天流,确保消息的顺序和高效处理。
- 增加了对各种消息类型和格式的支持,包括文本、图像和通知。
- 建立了一个用于监控和调试的日志系统。
This commit is contained in:
Windpicker-owo
2025-11-24 13:24:55 +08:00
parent b08c70dfa6
commit 36fce6ca98
28 changed files with 3041 additions and 824 deletions

View File

@@ -7,130 +7,152 @@ Adapter 管理器
from __future__ import annotations
import asyncio
import subprocess
import sys
from pathlib import Path
import importlib
import multiprocessing as mp
from typing import TYPE_CHECKING, Dict, Optional
if TYPE_CHECKING:
from src.plugin_system.base.base_adapter import BaseAdapter
from mofox_bus import ProcessCoreSinkServer
from src.common.core_sink import get_core_sink
from src.common.logger import get_logger
logger = get_logger("adapter_manager")
class AdapterProcess:
"""适配器子进程包装器"""
def __init__(
self,
adapter_name: str,
entry_path: Path,
python_executable: Optional[str] = None,
):
self.adapter_name = adapter_name
self.entry_path = entry_path
self.python_executable = python_executable or sys.executable
self.process: Optional[subprocess.Popen] = None
self._monitor_task: Optional[asyncio.Task] = None
def _load_class(module_name: str, class_name: str):
module = importlib.import_module(module_name)
return getattr(module, class_name)
def _adapter_process_entry(
adapter_path: tuple[str, str],
plugin_info: dict | None,
incoming_queue: mp.Queue,
outgoing_queue: mp.Queue,
):
import asyncio
import contextlib
from mofox_bus import ProcessCoreSink
async def _run() -> None:
adapter_cls = _load_class(*adapter_path)
plugin_instance = None
if plugin_info:
plugin_cls = _load_class(plugin_info["module"], plugin_info["class"])
plugin_instance = plugin_cls(plugin_info["plugin_dir"], plugin_info["metadata"])
core_sink = ProcessCoreSink(to_core_queue=incoming_queue, from_core_queue=outgoing_queue)
adapter = adapter_cls(core_sink, plugin=plugin_instance)
await adapter.start()
try:
while not getattr(core_sink, "_closed", False):
await asyncio.sleep(0.2)
finally:
with contextlib.suppress(Exception):
await adapter.stop()
with contextlib.suppress(Exception):
await core_sink.close()
asyncio.run(_run())
class AdapterProcess:
"""适配器子进程包装器,负责适配器子进程的启动和生命周期管理"""
def __init__(self, adapter: "BaseAdapter", core_sink) -> None:
self.adapter = adapter
self.adapter_name = adapter.adapter_name
self.process: mp.Process | None = None
self._ctx = mp.get_context("spawn")
self._incoming_queue: mp.Queue = self._ctx.Queue()
self._outgoing_queue: mp.Queue = self._ctx.Queue()
self._bridge: ProcessCoreSinkServer | None = None
self._core_sink = core_sink
self._adapter_path: tuple[str, str] = (adapter.__class__.__module__, adapter.__class__.__name__)
self._plugin_info = self._extract_plugin_info(adapter)
self._outgoing_handler = None
@staticmethod
def _extract_plugin_info(adapter: "BaseAdapter") -> dict | None:
plugin = getattr(adapter, "plugin", None)
if plugin is None:
return None
return {
"module": plugin.__class__.__module__,
"class": plugin.__class__.__name__,
"plugin_dir": getattr(plugin, "plugin_dir", ""),
"metadata": getattr(plugin, "plugin_meta", None),
}
def _make_outgoing_handler(self):
async def _handler(envelope):
if self._bridge:
await self._bridge.push_outgoing(envelope)
return _handler
async def start(self) -> bool:
"""启动适配器子进程"""
try:
logger.info(f"启动适配器子进程: {self.adapter_name}")
logger.debug(f"Python: {self.python_executable}")
logger.debug(f"Entry: {self.entry_path}")
# 启动子进程
self.process = subprocess.Popen(
[self.python_executable, str(self.entry_path)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
self._bridge = ProcessCoreSinkServer(
incoming_queue=self._incoming_queue,
outgoing_queue=self._outgoing_queue,
core_handler=self._core_sink.send,
name=self.adapter_name,
)
# 启动监控任务
self._monitor_task = asyncio.create_task(self._monitor_process())
logger.info(f"适配器 {self.adapter_name} 子进程已启动 (PID: {self.process.pid})")
self._bridge.start()
if hasattr(self._core_sink, "set_outgoing_handler"):
self._outgoing_handler = self._make_outgoing_handler()
try:
self._core_sink.set_outgoing_handler(self._outgoing_handler)
except Exception:
logger.exception("Failed to register outgoing bridge for %s", self.adapter_name)
self.process = self._ctx.Process(
target=_adapter_process_entry,
args=(self._adapter_path, self._plugin_info, self._incoming_queue, self._outgoing_queue),
name=f"{self.adapter_name}-proc",
)
self.process.start()
logger.info(f"启动适配器子进程 {self.adapter_name} (PID: {self.process.pid})")
return True
except Exception as e:
logger.error(f"启动适配器 {self.adapter_name} 子进程失败: {e}", exc_info=True)
logger.error(f"启动适配器子进程 {self.adapter_name} 失败: {e}", exc_info=True)
return False
async def stop(self) -> None:
"""停止适配器子进程"""
if not self.process:
return
logger.info(f"停止适配器子进程: {self.adapter_name} (PID: {self.process.pid})")
try:
# 取消监控任务
if self._monitor_task and not self._monitor_task.done():
self._monitor_task.cancel()
remover = getattr(self._core_sink, "remove_outgoing_handler", None)
if callable(remover) and self._outgoing_handler:
try:
await self._monitor_task
except asyncio.CancelledError:
pass
# 终止进程
self.process.terminate()
# 等待进程退出最多等待5秒
try:
await asyncio.wait_for(
asyncio.to_thread(self.process.wait),
timeout=5.0
)
except asyncio.TimeoutError:
logger.warning(f"适配器 {self.adapter_name} 未能在5秒内退出强制终止")
self.process.kill()
await asyncio.to_thread(self.process.wait)
logger.info(f"适配器 {self.adapter_name} 子进程已停止")
remover(self._outgoing_handler)
except Exception:
logger.exception(f"移除 {self.adapter_name} 的 outgoing bridge 失败")
if self._bridge:
await self._bridge.close()
if self.process.is_alive():
self.process.join(timeout=5.0)
if self.process.is_alive():
logger.warning(f"适配器 {self.adapter_name} 未能及时停止,强制终止中")
self.process.terminate()
self.process.join()
except Exception as e:
logger.error(f"停止适配器 {self.adapter_name} 子进程时出错: {e}", exc_info=True)
logger.error(f"停止适配器子进程 {self.adapter_name} 时发生错误: {e}", exc_info=True)
finally:
self.process = None
async def _monitor_process(self) -> None:
"""监控子进程状态"""
if not self.process:
return
try:
# 在后台线程中等待进程退出
return_code = await asyncio.to_thread(self.process.wait)
if return_code != 0:
logger.error(
f"适配器 {self.adapter_name} 子进程异常退出 (返回码: {return_code})"
)
# 读取 stderr 输出
if self.process.stderr:
stderr = self.process.stderr.read()
if stderr:
logger.error(f"错误输出:\n{stderr}")
else:
logger.info(f"适配器 {self.adapter_name} 子进程正常退出")
except asyncio.CancelledError:
pass
except Exception as e:
logger.error(f"监控适配器 {self.adapter_name} 子进程时出错: {e}", exc_info=True)
def is_running(self) -> bool:
"""检查进程是否正在运行"""
"""适配器是否正在运行"""
if not self.process:
return False
return self.process.poll() is None
return self.process.is_alive()
class AdapterManager:
"""适配器管理器"""
@@ -176,20 +198,17 @@ class AdapterManager:
else:
return await self._start_adapter_in_process(adapter)
async def _start_adapter_subprocess(self, adapter: BaseAdapter) -> bool:
"""在子进程中启动适配器"""
adapter_name = adapter.adapter_name
# 获取子进程入口脚本
entry_path = adapter.get_subprocess_entry_path()
if not entry_path:
logger.error(
f"适配器 {adapter_name} 配置为子进程运行,但未提供有效的入口脚本"
)
async def _start_adapter_subprocess(self, adapter: BaseAdapter) -> bool:
"""启动适配器子进程"""
adapter_name = adapter.adapter_name
try:
core_sink = get_core_sink()
except Exception as e:
logger.error(f"无法获取 core_sink启动适配器子进程 {adapter_name} 失败: {e}", exc_info=True)
return False
# 创建并启动子进程
adapter_process = AdapterProcess(adapter_name, entry_path)
adapter_process = AdapterProcess(adapter, core_sink)
success = await adapter_process.start()
if success: