feat: 添加带有消息处理和路由功能的NEW_napcat_adapter插件
- 为NEW_napcat_adapter插件实现了核心模块,包括消息处理、事件处理和路由。 - 创建了MessageHandler、MetaEventHandler和NoticeHandler来处理收到的消息和事件。 - 开发了SendHandler,用于向Napcat发送回消息。 引入了StreamRouter来管理多个聊天流,确保消息的顺序和高效处理。 - 增加了对各种消息类型和格式的支持,包括文本、图像和通知。 - 建立了一个用于监控和调试的日志系统。
This commit is contained in:
427
src/plugins/built_in/NEW_napcat_adapter/README.md
Normal file
427
src/plugins/built_in/NEW_napcat_adapter/README.md
Normal file
@@ -0,0 +1,427 @@
|
||||
# NEW_napcat_adapter
|
||||
|
||||
基于 mofox-bus v2.x 的 Napcat 适配器(使用 BaseAdapter 架构)
|
||||
|
||||
## 🏗️ 架构设计
|
||||
|
||||
本插件采用 **BaseAdapter 继承模式** 重写,完全抛弃旧版 maim_message 库,改用 mofox-bus 的 TypedDict 数据结构。
|
||||
|
||||
### 核心组件
|
||||
- **NapcatAdapter**: 继承自 `mofox_bus.AdapterBase`,负责 OneBot 11 协议与 MessageEnvelope 的双向转换
|
||||
- **WebSocketAdapterOptions**: 自动管理 WebSocket 连接,提供 incoming_parser 和 outgoing_encoder
|
||||
- **CoreMessageSink**: 通过 `InProcessCoreSink` 将消息递送到核心系统
|
||||
- **Handlers**: 独立的消息处理器,分为 to_core(接收)和 to_napcat(发送)两个方向
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
NEW_napcat_adapter/
|
||||
├── plugin.py # ✅ 主插件文件(BaseAdapter实现)
|
||||
├── _manifest.json # 插件清单
|
||||
│
|
||||
└── src/
|
||||
├── event_models.py # ✅ OneBot事件类型常量
|
||||
├── common/
|
||||
│ └── core_sink.py # ✅ 全局CoreSink访问点
|
||||
│
|
||||
├── utils/
|
||||
│ ├── utils.py # ⏳ 工具函数(待实现)
|
||||
│ ├── qq_emoji_list.py # ⏳ QQ表情映射(待实现)
|
||||
│ ├── video_handler.py # ⏳ 视频处理(待实现)
|
||||
│ └── message_chunker.py # ⏳ 消息切片(待实现)
|
||||
│
|
||||
├── websocket/
|
||||
│ └── (无需单独实现,使用WebSocketAdapterOptions)
|
||||
│
|
||||
├── database/
|
||||
│ └── database.py # ⏳ 数据库模型(待实现)
|
||||
│
|
||||
└── handlers/
|
||||
├── to_core/ # Napcat → MessageEnvelope 方向
|
||||
│ ├── message_handler.py # ⏳ 消息处理(部分完成)
|
||||
│ ├── notice_handler.py # ⏳ 通知处理(待完成)
|
||||
│ └── meta_event_handler.py # ⏳ 元事件(待完成)
|
||||
│
|
||||
└── to_napcat/ # MessageEnvelope → Napcat API 方向
|
||||
└── send_handler.py # ⏳ 发送处理(部分完成)
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 使用方式
|
||||
|
||||
1. **配置文件**: 在 `config/plugins/NEW_napcat_adapter.toml` 中配置 WebSocket URL 和其他参数
|
||||
2. **启动插件**: 插件自动在系统启动时加载
|
||||
3. **WebSocket连接**: 自动连接到 Napcat OneBot 11 服务器
|
||||
|
||||
## 🔑 核心数据结构
|
||||
|
||||
### MessageEnvelope (mofox-bus v2.x)
|
||||
|
||||
```python
|
||||
from mofox_bus import MessageEnvelope, SegPayload, MessageInfoPayload
|
||||
|
||||
# 创建消息信封
|
||||
envelope: MessageEnvelope = {
|
||||
"direction": "input",
|
||||
"message_info": {
|
||||
"message_type": "group",
|
||||
"message_id": "12345",
|
||||
"self_id": "bot_qq",
|
||||
"user_info": {
|
||||
"user_id": "sender_qq",
|
||||
"user_name": "发送者",
|
||||
"user_displayname": "昵称"
|
||||
},
|
||||
"group_info": {
|
||||
"group_id": "group_id",
|
||||
"group_name": "群名"
|
||||
},
|
||||
"to_me": False
|
||||
},
|
||||
"message_segment": {
|
||||
"type": "seglist",
|
||||
"data": [
|
||||
{"type": "text", "data": "hello"},
|
||||
{"type": "image", "data": "base64_data"}
|
||||
]
|
||||
},
|
||||
"raw_message": "hello[图片]",
|
||||
"platform": "napcat",
|
||||
"message_id": "12345",
|
||||
"timestamp_ms": 1234567890
|
||||
}
|
||||
```
|
||||
|
||||
### BaseAdapter 核心方法
|
||||
|
||||
```python
|
||||
class NapcatAdapter(BaseAdapter):
|
||||
async def from_platform_message(self, message: dict[str, Any]) -> MessageEnvelope | None:
|
||||
"""将 OneBot 11 事件转换为 MessageEnvelope"""
|
||||
# 路由到对应的 Handler
|
||||
|
||||
async def _send_platform_message(self, envelope: MessageEnvelope) -> dict[str, Any]:
|
||||
"""将 MessageEnvelope 转换为 OneBot 11 API 调用"""
|
||||
# 调用 SendHandler 处理
|
||||
```
|
||||
|
||||
## 📝 实现进度
|
||||
|
||||
### ✅ 已完成的核心架构
|
||||
|
||||
1. **BaseAdapter 实现** (plugin.py)
|
||||
- ✅ WebSocket 自动连接管理
|
||||
- ✅ from_platform_message() 事件路由
|
||||
- ✅ _send_platform_message() 消息发送
|
||||
- ✅ API 响应池机制(echo-based request-response)
|
||||
- ✅ CoreSink 集成
|
||||
|
||||
2. **Handler 基础结构**
|
||||
- ✅ MessageHandler 骨架(text、image、at 基本实现)
|
||||
- ✅ NoticeHandler 骨架
|
||||
- ✅ MetaEventHandler 骨架
|
||||
- ✅ SendHandler 骨架(基本类型转换)
|
||||
|
||||
3. **辅助组件**
|
||||
- ✅ event_models.py(事件类型常量)
|
||||
- ✅ core_sink.py(全局 CoreSink 访问)
|
||||
- ✅ 配置 Schema 定义
|
||||
|
||||
### ⏳ 部分完成的功能
|
||||
|
||||
4. **消息类型处理** (MessageHandler)
|
||||
- ✅ 基础消息类型:text, image, at
|
||||
- ❌ 高级消息类型:face, reply, forward, video, json, file, rps, dice, shake
|
||||
|
||||
5. **发送处理** (SendHandler)
|
||||
- ✅ 基础 SegPayload 转换:text, image
|
||||
- ❌ 高级 Seg 类型:emoji, voice, voiceurl, music, videourl, file, command
|
||||
|
||||
### ❌ 待实现的功能
|
||||
|
||||
6. **通知事件处理** (NoticeHandler)
|
||||
- ❌ 戳一戳事件
|
||||
- ❌ 表情回应事件
|
||||
- ❌ 撤回事件
|
||||
- ❌ 禁言事件
|
||||
|
||||
7. **工具函数** (utils.py)
|
||||
- ❌ get_group_info
|
||||
- ❌ get_member_info
|
||||
- ❌ get_image_base64
|
||||
- ❌ get_message_detail
|
||||
- ❌ get_record_detail
|
||||
|
||||
8. **权限系统**
|
||||
- ❌ check_allow_to_chat()
|
||||
- ❌ 群组黑名单/白名单
|
||||
- ❌ 私聊黑名单/白名单
|
||||
- ❌ QQ机器人检测
|
||||
|
||||
9. **其他组件**
|
||||
- ❌ 视频处理器
|
||||
- ❌ 消息切片器
|
||||
- ❌ 数据库模型
|
||||
- ❌ QQ 表情映射表
|
||||
|
||||
## 📋 下一步工作
|
||||
|
||||
### 优先级 1:完善消息处理(参考旧版 recv_handler/message_handler.py)
|
||||
|
||||
1. **完整实现 MessageHandler.handle_raw_message()**
|
||||
- [ ] face(表情)消息段
|
||||
- [ ] reply(回复)消息段
|
||||
- [ ] forward(转发)消息段解析
|
||||
- [ ] video(视频)消息段
|
||||
- [ ] json(JSON卡片)消息段
|
||||
- [ ] file(文件)消息段
|
||||
- [ ] rps/dice/shake(特殊消息)
|
||||
|
||||
2. **实现工具函数**(参考旧版 utils.py)
|
||||
- [ ] `get_group_info()` - 获取群组信息
|
||||
- [ ] `get_member_info()` - 获取成员信息
|
||||
- [ ] `get_image_base64()` - 下载图片并转Base64
|
||||
- [ ] `get_message_detail()` - 获取消息详情
|
||||
- [ ] `get_record_detail()` - 获取语音详情
|
||||
|
||||
3. **实现权限检查**
|
||||
- [ ] `check_allow_to_chat()` - 检查是否允许聊天
|
||||
- [ ] 群组白名单/黑名单逻辑
|
||||
- [ ] 私聊白名单/黑名单逻辑
|
||||
- [ ] QQ机器人检测(ban_qq_bot)
|
||||
|
||||
### 优先级 2:完善发送处理(参考旧版 send_handler.py)
|
||||
|
||||
4. **完整实现 SendHandler._convert_seg_to_onebot()**
|
||||
- [ ] emoji(表情回应)命令
|
||||
- [ ] voice(语音)消息段
|
||||
- [ ] voiceurl(语音URL)消息段
|
||||
- [ ] music(音乐卡片)消息段
|
||||
- [ ] videourl(视频URL)消息段
|
||||
- [ ] file(文件)消息段
|
||||
- [ ] command(命令)消息段
|
||||
|
||||
5. **实现命令处理**
|
||||
- [ ] GROUP_BAN(禁言)
|
||||
- [ ] GROUP_KICK(踢人)
|
||||
- [ ] SEND_POKE(戳一戳)
|
||||
- [ ] DELETE_MSG(撤回消息)
|
||||
- [ ] GROUP_WHOLE_BAN(全员禁言)
|
||||
- [ ] SET_GROUP_CARD(设置群名片)
|
||||
- [ ] SET_GROUP_ADMIN(设置管理员)
|
||||
|
||||
### 优先级 3:补全其他组件(参考旧版对应文件)
|
||||
|
||||
6. **NoticeHandler 实现**
|
||||
- [ ] 戳一戳通知(notify.poke)
|
||||
- [ ] 表情回应通知(notice.group_emoji_like)
|
||||
- [ ] 消息撤回通知(notice.group_recall)
|
||||
- [ ] 禁言通知(notice.group_ban)
|
||||
|
||||
7. **辅助组件**
|
||||
- [ ] `qq_emoji_list.py` - QQ表情ID映射表
|
||||
- [ ] `video_handler.py` - 视频处理(ffmpeg封面提取)
|
||||
- [ ] `message_chunker.py` - 消息分块与重组
|
||||
- [ ] `database.py` - 数据库模型(如有需要)
|
||||
|
||||
### 优先级 4:测试与优化
|
||||
|
||||
8. **功能测试**
|
||||
- [ ] 文本消息收发
|
||||
- [ ] 图片消息收发
|
||||
- [ ] @消息处理
|
||||
- [ ] 表情/语音/视频消息
|
||||
- [ ] 转发消息解析
|
||||
- [ ] 所有命令功能
|
||||
- [ ] 通知事件处理
|
||||
|
||||
9. **性能优化**
|
||||
- [ ] 消息处理并发性能
|
||||
- [ ] API响应池性能
|
||||
- [ ] 内存占用优化
|
||||
|
||||
## 🔍 关键实现细节
|
||||
|
||||
### 1. MessageEnvelope vs 旧版 MessageBase
|
||||
|
||||
**不再使用 Seg dataclass**,全部使用 TypedDict:
|
||||
|
||||
```python
|
||||
# ❌ 旧版(maim_message)
|
||||
from mofox_bus import Seg, MessageBase
|
||||
|
||||
seg = Seg(type="text", data="hello")
|
||||
message = MessageBase(message_info=info, message_segment=seg)
|
||||
|
||||
# ✅ 新版(mofox-bus v2.x)
|
||||
from mofox_bus import SegPayload, MessageEnvelope
|
||||
|
||||
seg_payload: SegPayload = {"type": "text", "data": "hello"}
|
||||
envelope: MessageEnvelope = {
|
||||
"direction": "input",
|
||||
"message_info": {...},
|
||||
"message_segment": seg_payload,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Handler 架构模式
|
||||
|
||||
**接收方向** (to_core):
|
||||
```python
|
||||
class MessageHandler:
|
||||
def __init__(self, adapter: "NapcatAdapter"):
|
||||
self.adapter = adapter
|
||||
|
||||
async def handle_raw_message(self, data: dict[str, Any]) -> MessageEnvelope:
|
||||
# 1. 解析 OneBot 11 数据
|
||||
# 2. 构建 message_info(MessageInfoPayload)
|
||||
# 3. 转换消息段为 SegPayload
|
||||
# 4. 返回完整的 MessageEnvelope
|
||||
```
|
||||
|
||||
**发送方向** (to_napcat):
|
||||
```python
|
||||
class SendHandler:
|
||||
def __init__(self, adapter: "NapcatAdapter"):
|
||||
self.adapter = adapter
|
||||
|
||||
async def handle_message(self, envelope: MessageEnvelope) -> dict[str, Any]:
|
||||
# 1. 从 envelope 提取 message_segment
|
||||
# 2. 递归转换 SegPayload → OneBot 格式
|
||||
# 3. 调用 adapter.send_napcat_api() 发送
|
||||
```
|
||||
|
||||
### 3. API 调用模式(响应池)
|
||||
|
||||
```python
|
||||
# 在 NapcatAdapter 中
|
||||
async def send_napcat_api(self, action: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
# 1. 生成唯一 echo
|
||||
echo = f"{action}_{uuid.uuid4()}"
|
||||
|
||||
# 2. 创建 Future 等待响应
|
||||
future = asyncio.Future()
|
||||
self._response_pool[echo] = future
|
||||
|
||||
# 3. 发送请求(通过 WebSocket)
|
||||
await self._send_request({"action": action, "params": params, "echo": echo})
|
||||
|
||||
# 4. 等待响应(带超时)
|
||||
try:
|
||||
result = await asyncio.wait_for(future, timeout=10.0)
|
||||
return result
|
||||
finally:
|
||||
self._response_pool.pop(echo, None)
|
||||
|
||||
# 响应回来时(在 incoming_parser 中)
|
||||
def _handle_api_response(data: dict[str, Any]):
|
||||
echo = data.get("echo")
|
||||
if echo in adapter._response_pool:
|
||||
adapter._response_pool[echo].set_result(data)
|
||||
```
|
||||
|
||||
### 4. 类型提示技巧
|
||||
|
||||
处理 TypedDict 的严格类型检查:
|
||||
|
||||
```python
|
||||
# 使用 type: ignore 标注(编译时是 TypedDict,运行时是 dict)
|
||||
envelope: MessageEnvelope = {
|
||||
"direction": "input",
|
||||
...
|
||||
} # type: ignore[typeddict-item]
|
||||
|
||||
# 或在函数签名中使用 dict[str, Any]
|
||||
async def from_platform_message(self, message: dict[str, Any]) -> MessageEnvelope | None:
|
||||
...
|
||||
return envelope # type: ignore[return-value]
|
||||
```
|
||||
|
||||
## 🔍 测试检查清单
|
||||
|
||||
- [ ] 文本消息接收/发送
|
||||
- [ ] 图片消息接收/发送
|
||||
- [ ] 语音消息接收/发送
|
||||
- [ ] 视频消息接收/发送
|
||||
- [ ] @消息接收/发送
|
||||
- [ ] 回复消息接收/发送
|
||||
- [ ] 转发消息接收
|
||||
- [ ] JSON消息接收
|
||||
- [ ] 文件消息接收/发送
|
||||
- [ ] 禁言命令
|
||||
- [ ] 踢人命令
|
||||
- [ ] 戳一戳命令
|
||||
- [ ] 表情回应命令
|
||||
- [ ] 通知事件处理
|
||||
- [ ] 元事件处理
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
- **mofox-bus 文档**: 查看 `mofox_bus/types.py` 了解 TypedDict 定义
|
||||
- **BaseAdapter 示例**: 参考 `docs/mofox_bus_demo_adapter.py`
|
||||
- **旧版实现**: `src/plugins/built_in/napcat_adapter_plugin/` (仅参考逻辑)
|
||||
- **OneBot 11 协议**: [OneBot 11 标准](https://github.com/botuniverse/onebot-11)
|
||||
|
||||
## ⚠️ 重要注意事项
|
||||
|
||||
1. **完全抛弃旧版数据结构**
|
||||
- ❌ 不再使用 `Seg` dataclass
|
||||
- ❌ 不再使用 `MessageBase` 类
|
||||
- ✅ 全部使用 `SegPayload`(TypedDict)
|
||||
- ✅ 全部使用 `MessageEnvelope`(TypedDict)
|
||||
|
||||
2. **BaseAdapter 生命周期**
|
||||
- `__init__()` 中初始化同步资源
|
||||
- `start()` 中执行异步初始化(WebSocket连接自动建立)
|
||||
- `stop()` 中清理资源(WebSocket自动断开)
|
||||
|
||||
3. **WebSocketAdapterOptions 自动管理**
|
||||
- 无需手动管理 WebSocket 连接
|
||||
- incoming_parser 自动解析接收数据
|
||||
- outgoing_encoder 自动编码发送数据
|
||||
- 重连机制由基类处理
|
||||
|
||||
4. **CoreSink 依赖注入**
|
||||
- 必须在插件加载后调用 `set_core_sink(sink)`
|
||||
- 通过 `get_core_sink()` 全局访问
|
||||
- 用于将消息递送到核心系统
|
||||
|
||||
5. **类型安全与灵活性平衡**
|
||||
- TypedDict 在编译时提供类型检查
|
||||
- 运行时仍是普通 dict,可灵活操作
|
||||
- 必要时使用 `type: ignore` 抑制误报
|
||||
|
||||
6. **参考旧版但不照搬**
|
||||
- 旧版逻辑流程可参考
|
||||
- 数据结构需完全重写
|
||||
- API调用模式已改变(响应池)
|
||||
|
||||
## 📊 预估工作量
|
||||
|
||||
- ✅ 核心架构: **已完成** (BaseAdapter + Handlers 骨架)
|
||||
- ⏳ 消息处理完善: **4-6 小时** (所有消息类型 + 工具函数)
|
||||
- ⏳ 发送处理完善: **3-4 小时** (所有 Seg 类型 + 命令)
|
||||
- ⏳ 通知事件处理: **2-3 小时** (poke/emoji_like/recall/ban)
|
||||
- ⏳ 测试调试: **2-4 小时** (全流程测试)
|
||||
- **总剩余时间: 11-17 小时**
|
||||
|
||||
## ✅ 完成标准
|
||||
|
||||
当以下条件全部满足时,重写完成:
|
||||
|
||||
1. ✅ BaseAdapter 架构实现完成
|
||||
2. ⏳ 所有 OneBot 11 消息类型支持
|
||||
3. ⏳ 所有发送消息段类型支持
|
||||
4. ⏳ 所有通知事件正确处理
|
||||
5. ⏳ 权限系统集成完成
|
||||
6. ⏳ 与旧版功能完全对等
|
||||
7. ⏳ 所有测试用例通过
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2025-11-23
|
||||
**架构状态**: ✅ 核心架构完成
|
||||
**实现状态**: ⏳ 消息处理部分完成,需完善细节
|
||||
**预计完成**: 根据优先级,核心功能预计 1-2 个工作日
|
||||
16
src/plugins/built_in/NEW_napcat_adapter/__init__.py
Normal file
16
src/plugins/built_in/NEW_napcat_adapter/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="napcat_plugin",
|
||||
description="基于OneBot 11协议的NapCat QQ协议插件,提供完整的QQ机器人API接口,使用现有adapter连接",
|
||||
usage="该插件提供 `napcat_tool` tool。",
|
||||
version="1.0.0",
|
||||
author="Windpicker_owo",
|
||||
license="GPL-v3.0-or-later",
|
||||
repository_url="https://github.com/Windpicker-owo",
|
||||
keywords=["qq", "bot", "napcat", "onebot", "api", "websocket"],
|
||||
categories=["protocol"],
|
||||
extra={
|
||||
"is_built_in": False,
|
||||
},
|
||||
)
|
||||
330
src/plugins/built_in/NEW_napcat_adapter/plugin.py
Normal file
330
src/plugins/built_in/NEW_napcat_adapter/plugin.py
Normal file
@@ -0,0 +1,330 @@
|
||||
"""
|
||||
Napcat 适配器(基于 MoFox-Bus 完全重写版)
|
||||
|
||||
核心流程:
|
||||
1. Napcat WebSocket 连接 → 接收 OneBot 格式消息
|
||||
2. from_platform_message: OneBot dict → MessageEnvelope
|
||||
3. CoreSink → 推送到 MoFox-Bot 核心
|
||||
4. 核心回复 → _send_platform_message: MessageEnvelope → OneBot API 调用
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import uuid
|
||||
from typing import Any, ClassVar, Dict, List, Optional
|
||||
|
||||
import orjson
|
||||
import websockets
|
||||
|
||||
from mofox_bus import CoreMessageSink, MessageEnvelope, WebSocketAdapterOptions
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system import register_plugin
|
||||
from src.plugin_system.base import BaseAdapter, BasePlugin
|
||||
from src.plugin_system.apis import config_api
|
||||
|
||||
from .src.handlers.to_core.message_handler import MessageHandler
|
||||
from .src.handlers.to_core.notice_handler import NoticeHandler
|
||||
from .src.handlers.to_core.meta_event_handler import MetaEventHandler
|
||||
from .src.handlers.to_napcat.send_handler import SendHandler
|
||||
|
||||
logger = get_logger("napcat_adapter")
|
||||
|
||||
|
||||
class NapcatAdapter(BaseAdapter):
|
||||
"""Napcat 适配器 - 完全基于 mofox-bus 架构"""
|
||||
|
||||
adapter_name = "napcat_adapter"
|
||||
adapter_version = "2.0.0"
|
||||
adapter_author = "MoFox Team"
|
||||
adapter_description = "基于 MoFox-Bus 的 Napcat/OneBot 11 适配器"
|
||||
platform = "qq"
|
||||
|
||||
run_in_subprocess = False
|
||||
subprocess_entry = None
|
||||
|
||||
def __init__(self, core_sink: CoreMessageSink, plugin: Optional[BasePlugin] = None):
|
||||
"""初始化 Napcat 适配器"""
|
||||
# 从插件配置读取 WebSocket URL
|
||||
if plugin:
|
||||
mode = config_api.get_plugin_config(plugin.config, "napcat_server.mode", "reverse")
|
||||
host = config_api.get_plugin_config(plugin.config, "napcat_server.host", "localhost")
|
||||
port = config_api.get_plugin_config(plugin.config, "napcat_server.port", 8095)
|
||||
url = config_api.get_plugin_config(plugin.config, "napcat_server.url", "")
|
||||
access_token = config_api.get_plugin_config(plugin.config, "napcat_server.access_token", "")
|
||||
|
||||
if mode == "forward" and url:
|
||||
ws_url = url
|
||||
else:
|
||||
ws_url = f"ws://{host}:{port}"
|
||||
|
||||
headers = {}
|
||||
if access_token:
|
||||
headers["Authorization"] = f"Bearer {access_token}"
|
||||
else:
|
||||
ws_url = "ws://127.0.0.1:8095"
|
||||
headers = {}
|
||||
|
||||
# 配置 WebSocket 传输
|
||||
transport = WebSocketAdapterOptions(
|
||||
url=ws_url,
|
||||
headers=headers if headers else None,
|
||||
incoming_parser=self._parse_napcat_message,
|
||||
outgoing_encoder=self._encode_napcat_response,
|
||||
)
|
||||
|
||||
super().__init__(core_sink, plugin=plugin, transport=transport)
|
||||
|
||||
# 初始化处理器
|
||||
self.message_handler = MessageHandler(self)
|
||||
self.notice_handler = NoticeHandler(self)
|
||||
self.meta_event_handler = MetaEventHandler(self)
|
||||
self.send_handler = SendHandler(self)
|
||||
|
||||
# 响应池:用于存储等待的 API 响应
|
||||
self._response_pool: Dict[str, asyncio.Future] = {}
|
||||
self._response_timeout = 30.0
|
||||
|
||||
# WebSocket 连接(用于发送 API 请求)
|
||||
# 注意:_ws 继承自 BaseAdapter,是 WebSocketLike 协议类型
|
||||
self._napcat_ws = None # 可选的额外连接引用
|
||||
|
||||
async def on_adapter_loaded(self) -> None:
|
||||
"""适配器加载时的初始化"""
|
||||
logger.info("Napcat 适配器正在启动...")
|
||||
|
||||
# 设置处理器配置
|
||||
if self.plugin:
|
||||
self.message_handler.set_plugin_config(self.plugin.config)
|
||||
self.notice_handler.set_plugin_config(self.plugin.config)
|
||||
self.meta_event_handler.set_plugin_config(self.plugin.config)
|
||||
self.send_handler.set_plugin_config(self.plugin.config)
|
||||
|
||||
logger.info("Napcat 适配器已加载")
|
||||
|
||||
async def on_adapter_unloaded(self) -> None:
|
||||
"""适配器卸载时的清理"""
|
||||
logger.info("Napcat 适配器正在关闭...")
|
||||
|
||||
# 清理响应池
|
||||
for future in self._response_pool.values():
|
||||
if not future.done():
|
||||
future.cancel()
|
||||
self._response_pool.clear()
|
||||
|
||||
logger.info("Napcat 适配器已关闭")
|
||||
|
||||
def _parse_napcat_message(self, raw: str | bytes) -> Any:
|
||||
"""解析 Napcat/OneBot 消息"""
|
||||
try:
|
||||
if isinstance(raw, bytes):
|
||||
data = orjson.loads(raw)
|
||||
else:
|
||||
data = orjson.loads(raw)
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.error(f"解析 Napcat 消息失败: {e}")
|
||||
raise
|
||||
|
||||
def _encode_napcat_response(self, envelope: MessageEnvelope) -> bytes:
|
||||
"""编码响应消息为 Napcat 格式(暂未使用,通过 API 调用发送)"""
|
||||
return orjson.dumps(envelope)
|
||||
|
||||
async def from_platform_message(self, raw: Dict[str, Any]) -> MessageEnvelope: # type: ignore[override]
|
||||
"""
|
||||
将 Napcat/OneBot 原始消息转换为 MessageEnvelope
|
||||
|
||||
这是核心转换方法,处理:
|
||||
- message 事件 → 消息
|
||||
- notice 事件 → 通知(戳一戳、表情回复等)
|
||||
- meta_event 事件 → 元事件(心跳、生命周期)
|
||||
- API 响应 → 存入响应池
|
||||
"""
|
||||
post_type = raw.get("post_type")
|
||||
|
||||
# API 响应(没有 post_type,有 echo)
|
||||
if post_type is None and "echo" in raw:
|
||||
echo = raw.get("echo")
|
||||
if echo and echo in self._response_pool:
|
||||
future = self._response_pool[echo]
|
||||
if not future.done():
|
||||
future.set_result(raw)
|
||||
# API 响应不需要转换为 MessageEnvelope,返回空信封
|
||||
return self._create_empty_envelope()
|
||||
|
||||
# 消息事件
|
||||
if post_type == "message":
|
||||
return await self.message_handler.handle_raw_message(raw) # type: ignore[return-value]
|
||||
|
||||
# 通知事件
|
||||
elif post_type == "notice":
|
||||
return await self.notice_handler.handle_notice(raw) # type: ignore[return-value]
|
||||
|
||||
# 元事件
|
||||
elif post_type == "meta_event":
|
||||
return await self.meta_event_handler.handle_meta_event(raw) # type: ignore[return-value]
|
||||
|
||||
# 未知事件类型
|
||||
else:
|
||||
logger.warning(f"未知的事件类型: {post_type}")
|
||||
return self._create_empty_envelope() # type: ignore[return-value]
|
||||
|
||||
async def _send_platform_message(self, envelope: MessageEnvelope) -> None: # type: ignore[override]
|
||||
"""
|
||||
将 MessageEnvelope 转换并发送到 Napcat
|
||||
|
||||
这里不直接通过 WebSocket 发送 envelope,
|
||||
而是调用 Napcat API(send_group_msg, send_private_msg 等)
|
||||
"""
|
||||
await self.send_handler.handle_message(envelope)
|
||||
|
||||
def _create_empty_envelope(self) -> MessageEnvelope: # type: ignore[return]
|
||||
"""创建一个空的消息信封(用于不需要处理的事件)"""
|
||||
import time
|
||||
return {
|
||||
"direction": "incoming",
|
||||
"message_info": {
|
||||
"platform": self.platform,
|
||||
"message_id": str(uuid.uuid4()),
|
||||
"time": time.time(),
|
||||
},
|
||||
"message_segment": {"type": "text", "data": "[系统事件]"},
|
||||
"timestamp_ms": int(time.time() * 1000),
|
||||
}
|
||||
|
||||
async def send_napcat_api(self, action: str, params: Dict[str, Any], timeout: float = 30.0) -> Dict[str, Any]:
|
||||
"""
|
||||
发送 Napcat API 请求并等待响应
|
||||
|
||||
Args:
|
||||
action: API 动作名称(如 send_group_msg)
|
||||
params: API 参数
|
||||
timeout: 超时时间(秒)
|
||||
|
||||
Returns:
|
||||
API 响应数据
|
||||
"""
|
||||
if not self._ws:
|
||||
raise RuntimeError("WebSocket 连接未建立")
|
||||
|
||||
# 生成唯一的 echo ID
|
||||
echo = str(uuid.uuid4())
|
||||
|
||||
# 创建 Future 用于等待响应
|
||||
future = asyncio.Future()
|
||||
self._response_pool[echo] = future
|
||||
|
||||
# 构造请求
|
||||
request = orjson.dumps({
|
||||
"action": action,
|
||||
"params": params,
|
||||
"echo": echo,
|
||||
})
|
||||
|
||||
try:
|
||||
# 发送请求
|
||||
await self._ws.send(request)
|
||||
|
||||
# 等待响应
|
||||
response = await asyncio.wait_for(future, timeout=timeout)
|
||||
return response
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"API 请求超时: {action}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"API 请求失败: {action}, 错误: {e}")
|
||||
raise
|
||||
finally:
|
||||
# 清理响应池
|
||||
self._response_pool.pop(echo, None)
|
||||
|
||||
def get_ws_connection(self):
|
||||
"""获取 WebSocket 连接(用于发送 API 请求)"""
|
||||
if not self._ws:
|
||||
raise RuntimeError("WebSocket 连接未建立")
|
||||
return self._ws
|
||||
|
||||
|
||||
@register_plugin
|
||||
class NapcatAdapterPlugin(BasePlugin):
|
||||
"""Napcat 适配器插件"""
|
||||
|
||||
plugin_name = "napcat_adapter_plugin"
|
||||
enable_plugin = True
|
||||
plugin_version = "2.0.0"
|
||||
plugin_author = "MoFox Team"
|
||||
plugin_description = "Napcat/OneBot 11 适配器(基于 MoFox-Bus 重写)"
|
||||
|
||||
# 配置 Schema
|
||||
config_schema: ClassVar[dict] = {
|
||||
"plugin": {
|
||||
"name": {"type": str, "default": "napcat_adapter_plugin"},
|
||||
"version": {"type": str, "default": "2.0.0"},
|
||||
"enabled": {"type": bool, "default": True},
|
||||
},
|
||||
"napcat_server": {
|
||||
"mode": {
|
||||
"type": str,
|
||||
"default": "reverse",
|
||||
"description": "连接模式:reverse=反向连接(作为服务器), forward=正向连接(作为客户端)",
|
||||
},
|
||||
"host": {"type": str, "default": "localhost"},
|
||||
"port": {"type": int, "default": 8095},
|
||||
"url": {"type": str, "default": "", "description": "正向连接时的完整URL"},
|
||||
"access_token": {"type": str, "default": ""},
|
||||
},
|
||||
"features": {
|
||||
"group_list_type": {"type": str, "default": "blacklist"},
|
||||
"group_list": {"type": list, "default": []},
|
||||
"private_list_type": {"type": str, "default": "blacklist"},
|
||||
"private_list": {"type": list, "default": []},
|
||||
"ban_user_id": {"type": list, "default": []},
|
||||
"ban_qq_bot": {"type": bool, "default": False},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, plugin_dir: str = "", metadata: Any = None):
|
||||
# 如果没有提供参数,创建一个默认的元数据
|
||||
if metadata is None:
|
||||
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
||||
metadata = PluginMetadata(
|
||||
name=self.plugin_name,
|
||||
version=self.plugin_version,
|
||||
author=self.plugin_author,
|
||||
description=self.plugin_description,
|
||||
usage="",
|
||||
dependencies=[],
|
||||
python_dependencies=[],
|
||||
)
|
||||
|
||||
if not plugin_dir:
|
||||
from pathlib import Path
|
||||
plugin_dir = str(Path(__file__).parent)
|
||||
|
||||
super().__init__(plugin_dir, metadata)
|
||||
self._adapter: Optional[NapcatAdapter] = None
|
||||
|
||||
async def on_plugin_loaded(self):
|
||||
"""插件加载时启动适配器"""
|
||||
logger.info("Napcat 适配器插件正在加载...")
|
||||
|
||||
# 获取核心 Sink
|
||||
from src.common.core_sink import get_core_sink
|
||||
core_sink = get_core_sink()
|
||||
|
||||
# 创建并启动适配器
|
||||
self._adapter = NapcatAdapter(core_sink, plugin=self)
|
||||
await self._adapter.start()
|
||||
|
||||
logger.info("Napcat 适配器插件已加载")
|
||||
|
||||
async def on_plugin_unloaded(self):
|
||||
"""插件卸载时停止适配器"""
|
||||
if self._adapter:
|
||||
await self._adapter.stop()
|
||||
logger.info("Napcat 适配器插件已卸载")
|
||||
|
||||
def get_plugin_components(self) -> list:
|
||||
"""返回适配器组件"""
|
||||
return [(NapcatAdapter.get_adapter_info(), NapcatAdapter)]
|
||||
1
src/plugins/built_in/NEW_napcat_adapter/src/__init__.py
Normal file
1
src/plugins/built_in/NEW_napcat_adapter/src/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""工具模块"""
|
||||
310
src/plugins/built_in/NEW_napcat_adapter/src/event_models.py
Normal file
310
src/plugins/built_in/NEW_napcat_adapter/src/event_models.py
Normal file
@@ -0,0 +1,310 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class MetaEventType:
|
||||
lifecycle = "lifecycle" # 生命周期
|
||||
|
||||
class Lifecycle:
|
||||
connect = "connect" # 生命周期 - WebSocket 连接成功
|
||||
|
||||
heartbeat = "heartbeat" # 心跳
|
||||
|
||||
|
||||
class MessageType: # 接受消息大类
|
||||
private = "private" # 私聊消息
|
||||
|
||||
class Private:
|
||||
friend = "friend" # 私聊消息 - 好友
|
||||
group = "group" # 私聊消息 - 群临时
|
||||
group_self = "group_self" # 私聊消息 - 群中自身发送
|
||||
other = "other" # 私聊消息 - 其他
|
||||
|
||||
group = "group" # 群聊消息
|
||||
|
||||
class Group:
|
||||
normal = "normal" # 群聊消息 - 普通
|
||||
anonymous = "anonymous" # 群聊消息 - 匿名消息
|
||||
notice = "notice" # 群聊消息 - 系统提示
|
||||
|
||||
|
||||
class NoticeType: # 通知事件
|
||||
friend_recall = "friend_recall" # 私聊消息撤回
|
||||
group_recall = "group_recall" # 群聊消息撤回
|
||||
notify = "notify"
|
||||
group_ban = "group_ban" # 群禁言
|
||||
group_msg_emoji_like = "group_msg_emoji_like" # 群聊表情回复
|
||||
group_upload = "group_upload" # 群文件上传
|
||||
|
||||
class Notify:
|
||||
poke = "poke" # 戳一戳
|
||||
input_status = "input_status" # 正在输入
|
||||
|
||||
class GroupBan:
|
||||
ban = "ban" # 禁言
|
||||
lift_ban = "lift_ban" # 解除禁言
|
||||
|
||||
|
||||
class RealMessageType: # 实际消息分类
|
||||
text = "text" # 纯文本
|
||||
face = "face" # qq表情
|
||||
image = "image" # 图片
|
||||
record = "record" # 语音
|
||||
video = "video" # 视频
|
||||
at = "at" # @某人
|
||||
rps = "rps" # 猜拳魔法表情
|
||||
dice = "dice" # 骰子
|
||||
shake = "shake" # 私聊窗口抖动(只收)
|
||||
poke = "poke" # 群聊戳一戳
|
||||
share = "share" # 链接分享(json形式)
|
||||
reply = "reply" # 回复消息
|
||||
forward = "forward" # 转发消息
|
||||
node = "node" # 转发消息节点
|
||||
json = "json" # json消息
|
||||
file = "file" # 文件
|
||||
|
||||
|
||||
class MessageSentType:
|
||||
private = "private"
|
||||
|
||||
class Private:
|
||||
friend = "friend"
|
||||
group = "group"
|
||||
|
||||
group = "group"
|
||||
|
||||
class Group:
|
||||
normal = "normal"
|
||||
|
||||
|
||||
class CommandType(Enum):
|
||||
"""命令类型"""
|
||||
|
||||
GROUP_BAN = "set_group_ban" # 禁言用户
|
||||
GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言
|
||||
GROUP_KICK = "set_group_kick" # 踢出群聊
|
||||
SEND_POKE = "send_poke" # 戳一戳
|
||||
DELETE_MSG = "delete_msg" # 撤回消息
|
||||
AI_VOICE_SEND = "ai_voice_send" # AI语音发送
|
||||
SET_EMOJI_LIKE = "set_msg_emoji_like" # 设置表情回应
|
||||
SEND_AT_MESSAGE = "send_at_message" # 发送@消息
|
||||
SEND_LIKE = "send_like" # 发送点赞
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
# 支持的消息格式
|
||||
ACCEPT_FORMAT = [
|
||||
"text",
|
||||
"image",
|
||||
"emoji",
|
||||
"reply",
|
||||
"voice",
|
||||
"command",
|
||||
"voiceurl",
|
||||
"music",
|
||||
"videourl",
|
||||
"file",
|
||||
]
|
||||
|
||||
# 插件名称
|
||||
PLUGIN_NAME = "NEW_napcat_adapter"
|
||||
|
||||
# QQ表情映射表
|
||||
QQ_FACE = {
|
||||
"0": "[表情:惊讶]",
|
||||
"1": "[表情:撇嘴]",
|
||||
"2": "[表情:色]",
|
||||
"3": "[表情:发呆]",
|
||||
"4": "[表情:得意]",
|
||||
"5": "[表情:流泪]",
|
||||
"6": "[表情:害羞]",
|
||||
"7": "[表情:闭嘴]",
|
||||
"8": "[表情:睡]",
|
||||
"9": "[表情:大哭]",
|
||||
"10": "[表情:尴尬]",
|
||||
"11": "[表情:发怒]",
|
||||
"12": "[表情:调皮]",
|
||||
"13": "[表情:呲牙]",
|
||||
"14": "[表情:微笑]",
|
||||
"15": "[表情:难过]",
|
||||
"16": "[表情:酷]",
|
||||
"18": "[表情:抓狂]",
|
||||
"19": "[表情:吐]",
|
||||
"20": "[表情:偷笑]",
|
||||
"21": "[表情:可爱]",
|
||||
"22": "[表情:白眼]",
|
||||
"23": "[表情:傲慢]",
|
||||
"24": "[表情:饥饿]",
|
||||
"25": "[表情:困]",
|
||||
"26": "[表情:惊恐]",
|
||||
"27": "[表情:流汗]",
|
||||
"28": "[表情:憨笑]",
|
||||
"29": "[表情:悠闲]",
|
||||
"30": "[表情:奋斗]",
|
||||
"31": "[表情:咒骂]",
|
||||
"32": "[表情:疑问]",
|
||||
"33": "[表情:嘘]",
|
||||
"34": "[表情:晕]",
|
||||
"35": "[表情:折磨]",
|
||||
"36": "[表情:衰]",
|
||||
"37": "[表情:骷髅]",
|
||||
"38": "[表情:敲打]",
|
||||
"39": "[表情:再见]",
|
||||
"41": "[表情:发抖]",
|
||||
"42": "[表情:爱情]",
|
||||
"43": "[表情:跳跳]",
|
||||
"46": "[表情:猪头]",
|
||||
"49": "[表情:拥抱]",
|
||||
"53": "[表情:蛋糕]",
|
||||
"56": "[表情:刀]",
|
||||
"59": "[表情:便便]",
|
||||
"60": "[表情:咖啡]",
|
||||
"63": "[表情:玫瑰]",
|
||||
"64": "[表情:凋谢]",
|
||||
"66": "[表情:爱心]",
|
||||
"67": "[表情:心碎]",
|
||||
"74": "[表情:太阳]",
|
||||
"75": "[表情:月亮]",
|
||||
"76": "[表情:赞]",
|
||||
"77": "[表情:踩]",
|
||||
"78": "[表情:握手]",
|
||||
"79": "[表情:胜利]",
|
||||
"85": "[表情:飞吻]",
|
||||
"86": "[表情:怄火]",
|
||||
"89": "[表情:西瓜]",
|
||||
"96": "[表情:冷汗]",
|
||||
"97": "[表情:擦汗]",
|
||||
"98": "[表情:抠鼻]",
|
||||
"99": "[表情:鼓掌]",
|
||||
"100": "[表情:糗大了]",
|
||||
"101": "[表情:坏笑]",
|
||||
"102": "[表情:左哼哼]",
|
||||
"103": "[表情:右哼哼]",
|
||||
"104": "[表情:哈欠]",
|
||||
"105": "[表情:鄙视]",
|
||||
"106": "[表情:委屈]",
|
||||
"107": "[表情:快哭了]",
|
||||
"108": "[表情:阴险]",
|
||||
"109": "[表情:左亲亲]",
|
||||
"110": "[表情:吓]",
|
||||
"111": "[表情:可怜]",
|
||||
"112": "[表情:菜刀]",
|
||||
"114": "[表情:篮球]",
|
||||
"116": "[表情:示爱]",
|
||||
"118": "[表情:抱拳]",
|
||||
"119": "[表情:勾引]",
|
||||
"120": "[表情:拳头]",
|
||||
"121": "[表情:差劲]",
|
||||
"123": "[表情:NO]",
|
||||
"124": "[表情:OK]",
|
||||
"125": "[表情:转圈]",
|
||||
"129": "[表情:挥手]",
|
||||
"137": "[表情:鞭炮]",
|
||||
"144": "[表情:喝彩]",
|
||||
"146": "[表情:爆筋]",
|
||||
"147": "[表情:棒棒糖]",
|
||||
"169": "[表情:手枪]",
|
||||
"171": "[表情:茶]",
|
||||
"172": "[表情:眨眼睛]",
|
||||
"173": "[表情:泪奔]",
|
||||
"174": "[表情:无奈]",
|
||||
"175": "[表情:卖萌]",
|
||||
"176": "[表情:小纠结]",
|
||||
"177": "[表情:喷血]",
|
||||
"178": "[表情:斜眼笑]",
|
||||
"179": "[表情:doge]",
|
||||
"181": "[表情:戳一戳]",
|
||||
"182": "[表情:笑哭]",
|
||||
"183": "[表情:我最美]",
|
||||
"185": "[表情:羊驼]",
|
||||
"187": "[表情:幽灵]",
|
||||
"201": "[表情:点赞]",
|
||||
"212": "[表情:托腮]",
|
||||
"262": "[表情:脑阔疼]",
|
||||
"263": "[表情:沧桑]",
|
||||
"264": "[表情:捂脸]",
|
||||
"265": "[表情:辣眼睛]",
|
||||
"266": "[表情:哦哟]",
|
||||
"267": "[表情:头秃]",
|
||||
"268": "[表情:问号脸]",
|
||||
"269": "[表情:暗中观察]",
|
||||
"270": "[表情:emm]",
|
||||
"271": "[表情:吃瓜]",
|
||||
"272": "[表情:呵呵哒]",
|
||||
"273": "[表情:我酸了]",
|
||||
"277": "[表情:滑稽狗头]",
|
||||
"281": "[表情:翻白眼]",
|
||||
"282": "[表情:敬礼]",
|
||||
"283": "[表情:狂笑]",
|
||||
"284": "[表情:面无表情]",
|
||||
"285": "[表情:摸鱼]",
|
||||
"286": "[表情:魔鬼笑]",
|
||||
"287": "[表情:哦]",
|
||||
"289": "[表情:睁眼]",
|
||||
"293": "[表情:摸锦鲤]",
|
||||
"294": "[表情:期待]",
|
||||
"295": "[表情:拿到红包]",
|
||||
"297": "[表情:拜谢]",
|
||||
"298": "[表情:元宝]",
|
||||
"299": "[表情:牛啊]",
|
||||
"300": "[表情:胖三斤]",
|
||||
"302": "[表情:左拜年]",
|
||||
"303": "[表情:右拜年]",
|
||||
"305": "[表情:右亲亲]",
|
||||
"306": "[表情:牛气冲天]",
|
||||
"307": "[表情:喵喵]",
|
||||
"311": "[表情:打call]",
|
||||
"312": "[表情:变形]",
|
||||
"314": "[表情:仔细分析]",
|
||||
"317": "[表情:菜汪]",
|
||||
"318": "[表情:崇拜]",
|
||||
"319": "[表情:比心]",
|
||||
"320": "[表情:庆祝]",
|
||||
"323": "[表情:嫌弃]",
|
||||
"324": "[表情:吃糖]",
|
||||
"325": "[表情:惊吓]",
|
||||
"326": "[表情:生气]",
|
||||
"332": "[表情:举牌牌]",
|
||||
"333": "[表情:烟花]",
|
||||
"334": "[表情:虎虎生威]",
|
||||
"336": "[表情:豹富]",
|
||||
"337": "[表情:花朵脸]",
|
||||
"338": "[表情:我想开了]",
|
||||
"339": "[表情:舔屏]",
|
||||
"341": "[表情:打招呼]",
|
||||
"342": "[表情:酸Q]",
|
||||
"343": "[表情:我方了]",
|
||||
"344": "[表情:大怨种]",
|
||||
"345": "[表情:红包多多]",
|
||||
"346": "[表情:你真棒棒]",
|
||||
"347": "[表情:大展宏兔]",
|
||||
"349": "[表情:坚强]",
|
||||
"350": "[表情:贴贴]",
|
||||
"351": "[表情:敲敲]",
|
||||
"352": "[表情:咦]",
|
||||
"353": "[表情:拜托]",
|
||||
"354": "[表情:尊嘟假嘟]",
|
||||
"355": "[表情:耶]",
|
||||
"356": "[表情:666]",
|
||||
"357": "[表情:裂开]",
|
||||
"392": "[表情:龙年快乐]",
|
||||
"393": "[表情:新年中龙]",
|
||||
"394": "[表情:新年大龙]",
|
||||
"395": "[表情:略略略]",
|
||||
"396": "[表情:龙年快乐]",
|
||||
"424": "[表情:按钮]",
|
||||
}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"MetaEventType",
|
||||
"MessageType",
|
||||
"NoticeType",
|
||||
"RealMessageType",
|
||||
"MessageSentType",
|
||||
"CommandType",
|
||||
"ACCEPT_FORMAT",
|
||||
"PLUGIN_NAME",
|
||||
"QQ_FACE",
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
"""处理器模块"""
|
||||
@@ -0,0 +1 @@
|
||||
"""接收方向处理器"""
|
||||
@@ -0,0 +1,126 @@
|
||||
"""消息处理器 - 将 Napcat OneBot 消息转换为 MessageEnvelope"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.apis import config_api
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...plugin import NapcatAdapter
|
||||
|
||||
logger = get_logger("napcat_adapter.message_handler")
|
||||
|
||||
|
||||
class MessageHandler:
|
||||
"""处理来自 Napcat 的消息事件"""
|
||||
|
||||
def __init__(self, adapter: "NapcatAdapter"):
|
||||
self.adapter = adapter
|
||||
self.plugin_config: Optional[Dict[str, Any]] = None
|
||||
|
||||
def set_plugin_config(self, config: Dict[str, Any]) -> None:
|
||||
"""设置插件配置"""
|
||||
self.plugin_config = config
|
||||
|
||||
async def handle_raw_message(self, raw: Dict[str, Any]):
|
||||
"""
|
||||
处理原始消息并转换为 MessageEnvelope
|
||||
|
||||
Args:
|
||||
raw: OneBot 原始消息数据
|
||||
|
||||
Returns:
|
||||
MessageEnvelope (dict)
|
||||
"""
|
||||
from mofox_bus import MessageEnvelope, SegPayload, MessageInfoPayload, UserInfoPayload, GroupInfoPayload
|
||||
|
||||
message_type = raw.get("message_type")
|
||||
message_id = str(raw.get("message_id", ""))
|
||||
message_time = time.time()
|
||||
|
||||
# 构造用户信息
|
||||
sender_info = raw.get("sender", {})
|
||||
user_info: UserInfoPayload = {
|
||||
"platform": "qq",
|
||||
"user_id": str(sender_info.get("user_id", "")),
|
||||
"user_nickname": sender_info.get("nickname", ""),
|
||||
"user_cardname": sender_info.get("card", ""),
|
||||
"user_avatar": sender_info.get("avatar", ""),
|
||||
}
|
||||
|
||||
# 构造群组信息(如果是群消息)
|
||||
group_info: Optional[GroupInfoPayload] = None
|
||||
if message_type == "group":
|
||||
group_id = raw.get("group_id")
|
||||
if group_id:
|
||||
group_info = {
|
||||
"platform": "qq",
|
||||
"group_id": str(group_id),
|
||||
"group_name": "", # 可以通过 API 获取
|
||||
}
|
||||
|
||||
# 解析消息段
|
||||
message_segments = raw.get("message", [])
|
||||
seg_list: List[SegPayload] = []
|
||||
|
||||
for seg in message_segments:
|
||||
seg_type = seg.get("type", "")
|
||||
seg_data = seg.get("data", {})
|
||||
|
||||
# 转换为 SegPayload
|
||||
if seg_type == "text":
|
||||
seg_list.append({
|
||||
"type": "text",
|
||||
"data": seg_data.get("text", "")
|
||||
})
|
||||
elif seg_type == "image":
|
||||
# 这里需要下载图片并转换为 base64(简化版本)
|
||||
seg_list.append({
|
||||
"type": "image",
|
||||
"data": seg_data.get("url", "") # 实际应该转换为 base64
|
||||
})
|
||||
elif seg_type == "at":
|
||||
seg_list.append({
|
||||
"type": "at",
|
||||
"data": f"{seg_data.get('qq', '')}"
|
||||
})
|
||||
# 其他消息类型...
|
||||
|
||||
# 构造 MessageInfoPayload
|
||||
message_info = {
|
||||
"platform": "qq",
|
||||
"message_id": message_id,
|
||||
"time": message_time,
|
||||
"user_info": user_info,
|
||||
"format_info": {
|
||||
"content_format": ["text", "image"], # 根据实际消息类型设置
|
||||
"accept_format": ["text", "image", "emoji", "voice"],
|
||||
},
|
||||
}
|
||||
|
||||
# 添加群组信息(如果存在)
|
||||
if group_info:
|
||||
message_info["group_info"] = group_info
|
||||
|
||||
# 构造 MessageEnvelope
|
||||
envelope = {
|
||||
"direction": "incoming",
|
||||
"message_info": message_info,
|
||||
"message_segment": {"type": "seglist", "data": seg_list} if len(seg_list) > 1 else (seg_list[0] if seg_list else {"type": "text", "data": ""}),
|
||||
"raw_message": raw.get("raw_message", ""),
|
||||
"platform": "qq",
|
||||
"message_id": message_id,
|
||||
"timestamp_ms": int(message_time * 1000),
|
||||
}
|
||||
|
||||
return envelope
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""元事件处理器"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||
|
||||
from src.common.logger import get_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...plugin import NapcatAdapter
|
||||
|
||||
logger = get_logger("napcat_adapter.meta_event_handler")
|
||||
|
||||
|
||||
class MetaEventHandler:
|
||||
"""处理 Napcat 元事件(心跳、生命周期)"""
|
||||
|
||||
def __init__(self, adapter: "NapcatAdapter"):
|
||||
self.adapter = adapter
|
||||
self.plugin_config: Optional[Dict[str, Any]] = None
|
||||
|
||||
def set_plugin_config(self, config: Dict[str, Any]) -> None:
|
||||
"""设置插件配置"""
|
||||
self.plugin_config = config
|
||||
|
||||
async def handle_meta_event(self, raw: Dict[str, Any]):
|
||||
"""处理元事件"""
|
||||
# 简化版本:返回一个空的 MessageEnvelope
|
||||
import time
|
||||
import uuid
|
||||
|
||||
return {
|
||||
"direction": "incoming",
|
||||
"message_info": {
|
||||
"platform": "qq",
|
||||
"message_id": str(uuid.uuid4()),
|
||||
"time": time.time(),
|
||||
},
|
||||
"message_segment": {"type": "text", "data": "[元事件]"},
|
||||
"timestamp_ms": int(time.time() * 1000),
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
"""通知事件处理器"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||
|
||||
from src.common.logger import get_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...plugin import NapcatAdapter
|
||||
|
||||
logger = get_logger("napcat_adapter.notice_handler")
|
||||
|
||||
|
||||
class NoticeHandler:
|
||||
"""处理 Napcat 通知事件(戳一戳、表情回复等)"""
|
||||
|
||||
def __init__(self, adapter: "NapcatAdapter"):
|
||||
self.adapter = adapter
|
||||
self.plugin_config: Optional[Dict[str, Any]] = None
|
||||
|
||||
def set_plugin_config(self, config: Dict[str, Any]) -> None:
|
||||
"""设置插件配置"""
|
||||
self.plugin_config = config
|
||||
|
||||
async def handle_notice(self, raw: Dict[str, Any]):
|
||||
"""处理通知事件"""
|
||||
# 简化版本:返回一个空的 MessageEnvelope
|
||||
import time
|
||||
import uuid
|
||||
|
||||
return {
|
||||
"direction": "incoming",
|
||||
"message_info": {
|
||||
"platform": "qq",
|
||||
"message_id": str(uuid.uuid4()),
|
||||
"time": time.time(),
|
||||
},
|
||||
"message_segment": {"type": "text", "data": "[通知事件]"},
|
||||
"timestamp_ms": int(time.time() * 1000),
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
"""发送方向处理器"""
|
||||
@@ -0,0 +1,77 @@
|
||||
"""发送处理器 - 将 MessageEnvelope 转换并发送到 Napcat"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||
|
||||
from src.common.logger import get_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...plugin import NapcatAdapter
|
||||
|
||||
logger = get_logger("napcat_adapter.send_handler")
|
||||
|
||||
|
||||
class SendHandler:
|
||||
"""处理向 Napcat 发送消息"""
|
||||
|
||||
def __init__(self, adapter: "NapcatAdapter"):
|
||||
self.adapter = adapter
|
||||
self.plugin_config: Optional[Dict[str, Any]] = None
|
||||
|
||||
def set_plugin_config(self, config: Dict[str, Any]) -> None:
|
||||
"""设置插件配置"""
|
||||
self.plugin_config = config
|
||||
|
||||
async def handle_message(self, envelope) -> None:
|
||||
"""
|
||||
处理发送消息
|
||||
|
||||
将 MessageEnvelope 转换为 OneBot API 调用
|
||||
"""
|
||||
message_info = envelope.get("message_info", {})
|
||||
message_segment = envelope.get("message_segment", {})
|
||||
|
||||
# 获取群组和用户信息
|
||||
group_info = message_info.get("group_info")
|
||||
user_info = message_info.get("user_info")
|
||||
|
||||
# 构造消息内容
|
||||
message = self._convert_seg_to_onebot(message_segment)
|
||||
|
||||
# 发送消息
|
||||
if group_info:
|
||||
# 发送群消息
|
||||
group_id = group_info.get("group_id")
|
||||
if group_id:
|
||||
await self.adapter.send_napcat_api("send_group_msg", {
|
||||
"group_id": int(group_id),
|
||||
"message": message,
|
||||
})
|
||||
elif user_info:
|
||||
# 发送私聊消息
|
||||
user_id = user_info.get("user_id")
|
||||
if user_id:
|
||||
await self.adapter.send_napcat_api("send_private_msg", {
|
||||
"user_id": int(user_id),
|
||||
"message": message,
|
||||
})
|
||||
|
||||
def _convert_seg_to_onebot(self, seg: Dict[str, Any]) -> list:
|
||||
"""将 SegPayload 转换为 OneBot 消息格式"""
|
||||
seg_type = seg.get("type", "")
|
||||
seg_data = seg.get("data", "")
|
||||
|
||||
if seg_type == "text":
|
||||
return [{"type": "text", "data": {"text": seg_data}}]
|
||||
elif seg_type == "image":
|
||||
return [{"type": "image", "data": {"file": f"base64://{seg_data}"}}]
|
||||
elif seg_type == "seglist":
|
||||
# 递归处理列表
|
||||
result = []
|
||||
for sub_seg in seg_data:
|
||||
result.extend(self._convert_seg_to_onebot(sub_seg))
|
||||
return result
|
||||
else:
|
||||
# 默认作为文本
|
||||
return [{"type": "text", "data": {"text": str(seg_data)}}]
|
||||
350
src/plugins/built_in/NEW_napcat_adapter/stream_router.py
Normal file
350
src/plugins/built_in/NEW_napcat_adapter/stream_router.py
Normal file
@@ -0,0 +1,350 @@
|
||||
"""
|
||||
按聊天流分配消费者的消息路由系统
|
||||
|
||||
核心思想:
|
||||
- 为每个活跃的聊天流(stream_id)创建独立的消息队列和消费者协程
|
||||
- 同一聊天流的消息由同一个 worker 处理,保证顺序性
|
||||
- 不同聊天流的消息并发处理,提高吞吐量
|
||||
- 动态管理流的生命周期,自动清理不活跃的流
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Dict, Optional
|
||||
|
||||
from src.common.logger import get_logger
|
||||
|
||||
logger = get_logger("stream_router")
|
||||
|
||||
|
||||
class StreamConsumer:
|
||||
"""单个聊天流的消息消费者
|
||||
|
||||
维护独立的消息队列和处理协程
|
||||
"""
|
||||
|
||||
def __init__(self, stream_id: str, queue_maxsize: int = 100):
|
||||
self.stream_id = stream_id
|
||||
self.queue: asyncio.Queue = asyncio.Queue(maxsize=queue_maxsize)
|
||||
self.worker_task: Optional[asyncio.Task] = None
|
||||
self.last_active_time = time.time()
|
||||
self.is_running = False
|
||||
|
||||
# 性能统计
|
||||
self.stats = {
|
||||
"total_messages": 0,
|
||||
"total_processing_time": 0.0,
|
||||
"queue_overflow_count": 0,
|
||||
}
|
||||
|
||||
async def start(self) -> None:
|
||||
"""启动消费者"""
|
||||
if not self.is_running:
|
||||
self.is_running = True
|
||||
self.worker_task = asyncio.create_task(self._process_loop())
|
||||
logger.debug(f"Stream Consumer 启动: {self.stream_id}")
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""停止消费者"""
|
||||
self.is_running = False
|
||||
if self.worker_task:
|
||||
self.worker_task.cancel()
|
||||
try:
|
||||
await self.worker_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
logger.debug(f"Stream Consumer 停止: {self.stream_id}")
|
||||
|
||||
async def enqueue(self, message: dict) -> None:
|
||||
"""将消息加入队列"""
|
||||
self.last_active_time = time.time()
|
||||
|
||||
try:
|
||||
# 使用 put_nowait 避免阻塞路由器
|
||||
self.queue.put_nowait(message)
|
||||
except asyncio.QueueFull:
|
||||
self.stats["queue_overflow_count"] += 1
|
||||
logger.warning(
|
||||
f"Stream {self.stream_id} 队列已满 "
|
||||
f"({self.queue.qsize()}/{self.queue.maxsize}),"
|
||||
)
|
||||
|
||||
try:
|
||||
self.queue.get_nowait()
|
||||
self.queue.put_nowait(message)
|
||||
logger.debug(f"Stream {self.stream_id} 丢弃最旧消息,添加新消息")
|
||||
except asyncio.QueueEmpty:
|
||||
pass
|
||||
|
||||
async def _process_loop(self) -> None:
|
||||
"""消息处理循环"""
|
||||
# 延迟导入,避免循环依赖
|
||||
from .recv_handler.message_handler import message_handler
|
||||
from .recv_handler.meta_event_handler import meta_event_handler
|
||||
from .recv_handler.notice_handler import notice_handler
|
||||
|
||||
logger.info(f"Stream {self.stream_id} 处理循环启动")
|
||||
|
||||
try:
|
||||
while self.is_running:
|
||||
try:
|
||||
# 等待消息,1秒超时
|
||||
message = await asyncio.wait_for(
|
||||
self.queue.get(),
|
||||
timeout=1.0
|
||||
)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# 处理消息
|
||||
post_type = message.get("post_type")
|
||||
if post_type == "message":
|
||||
await message_handler.handle_raw_message(message)
|
||||
elif post_type == "meta_event":
|
||||
await meta_event_handler.handle_meta_event(message)
|
||||
elif post_type == "notice":
|
||||
await notice_handler.handle_notice(message)
|
||||
else:
|
||||
logger.warning(f"未知的 post_type: {post_type}")
|
||||
|
||||
processing_time = time.time() - start_time
|
||||
|
||||
# 更新统计
|
||||
self.stats["total_messages"] += 1
|
||||
self.stats["total_processing_time"] += processing_time
|
||||
self.last_active_time = time.time()
|
||||
self.queue.task_done()
|
||||
|
||||
# 性能监控(每100条消息输出一次)
|
||||
if self.stats["total_messages"] % 100 == 0:
|
||||
avg_time = self.stats["total_processing_time"] / self.stats["total_messages"]
|
||||
logger.info(
|
||||
f"Stream {self.stream_id[:30]}... 统计: "
|
||||
f"消息数={self.stats['total_messages']}, "
|
||||
f"平均耗时={avg_time:.3f}秒, "
|
||||
f"队列长度={self.queue.qsize()}"
|
||||
)
|
||||
|
||||
# 动态延迟:队列空时短暂休眠
|
||||
if self.queue.qsize() == 0:
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
# 超时是正常的,继续循环
|
||||
continue
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"Stream {self.stream_id} 处理循环被取消")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Stream {self.stream_id} 处理消息时出错: {e}", exc_info=True)
|
||||
# 继续处理下一条消息
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
finally:
|
||||
logger.info(f"Stream {self.stream_id} 处理循环结束")
|
||||
|
||||
def get_stats(self) -> dict:
|
||||
"""获取性能统计"""
|
||||
avg_time = (
|
||||
self.stats["total_processing_time"] / self.stats["total_messages"]
|
||||
if self.stats["total_messages"] > 0
|
||||
else 0
|
||||
)
|
||||
|
||||
return {
|
||||
"stream_id": self.stream_id,
|
||||
"queue_size": self.queue.qsize(),
|
||||
"total_messages": self.stats["total_messages"],
|
||||
"avg_processing_time": avg_time,
|
||||
"queue_overflow_count": self.stats["queue_overflow_count"],
|
||||
"last_active_time": self.last_active_time,
|
||||
}
|
||||
|
||||
|
||||
class StreamRouter:
|
||||
"""流路由器
|
||||
|
||||
负责将消息路由到对应的聊天流队列
|
||||
动态管理聊天流的生命周期
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_streams: int = 500,
|
||||
stream_timeout: int = 600,
|
||||
stream_queue_size: int = 100,
|
||||
cleanup_interval: int = 60,
|
||||
):
|
||||
self.streams: Dict[str, StreamConsumer] = {}
|
||||
self.lock = asyncio.Lock()
|
||||
self.max_streams = max_streams
|
||||
self.stream_timeout = stream_timeout
|
||||
self.stream_queue_size = stream_queue_size
|
||||
self.cleanup_interval = cleanup_interval
|
||||
self.cleanup_task: Optional[asyncio.Task] = None
|
||||
self.is_running = False
|
||||
|
||||
async def start(self) -> None:
|
||||
"""启动路由器"""
|
||||
if not self.is_running:
|
||||
self.is_running = True
|
||||
self.cleanup_task = asyncio.create_task(self._cleanup_loop())
|
||||
logger.info(
|
||||
f"StreamRouter 已启动 - "
|
||||
f"最大流数: {self.max_streams}, "
|
||||
f"超时: {self.stream_timeout}秒, "
|
||||
f"队列大小: {self.stream_queue_size}"
|
||||
)
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""停止路由器"""
|
||||
self.is_running = False
|
||||
|
||||
if self.cleanup_task:
|
||||
self.cleanup_task.cancel()
|
||||
try:
|
||||
await self.cleanup_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
# 停止所有流消费者
|
||||
logger.info(f"正在停止 {len(self.streams)} 个流消费者...")
|
||||
for consumer in self.streams.values():
|
||||
await consumer.stop()
|
||||
|
||||
self.streams.clear()
|
||||
logger.info("StreamRouter 已停止")
|
||||
|
||||
async def route_message(self, message: dict) -> None:
|
||||
"""路由消息到对应的流"""
|
||||
stream_id = self._extract_stream_id(message)
|
||||
|
||||
# 快速路径:流已存在
|
||||
if stream_id in self.streams:
|
||||
await self.streams[stream_id].enqueue(message)
|
||||
return
|
||||
|
||||
# 慢路径:需要创建新流
|
||||
async with self.lock:
|
||||
# 双重检查
|
||||
if stream_id not in self.streams:
|
||||
# 检查流数量限制
|
||||
if len(self.streams) >= self.max_streams:
|
||||
logger.warning(
|
||||
f"达到最大流数量限制 ({self.max_streams}),"
|
||||
f"尝试清理不活跃的流..."
|
||||
)
|
||||
await self._cleanup_inactive_streams()
|
||||
|
||||
# 清理后仍然超限,记录警告但继续创建
|
||||
if len(self.streams) >= self.max_streams:
|
||||
logger.error(
|
||||
f"清理后仍达到最大流数量 ({len(self.streams)}/{self.max_streams})!"
|
||||
)
|
||||
|
||||
# 创建新流
|
||||
consumer = StreamConsumer(stream_id, self.stream_queue_size)
|
||||
self.streams[stream_id] = consumer
|
||||
await consumer.start()
|
||||
logger.info(f"创建新的 Stream Consumer: {stream_id} (总流数: {len(self.streams)})")
|
||||
|
||||
await self.streams[stream_id].enqueue(message)
|
||||
|
||||
def _extract_stream_id(self, message: dict) -> str:
|
||||
"""从消息中提取 stream_id
|
||||
|
||||
返回格式: platform:id:type
|
||||
例如: qq:123456:group 或 qq:789012:private
|
||||
"""
|
||||
post_type = message.get("post_type")
|
||||
|
||||
# 非消息类型,使用默认流(避免创建过多流)
|
||||
if post_type not in ["message", "notice"]:
|
||||
return "system:meta_event"
|
||||
|
||||
# 消息类型
|
||||
if post_type == "message":
|
||||
message_type = message.get("message_type")
|
||||
if message_type == "group":
|
||||
group_id = message.get("group_id")
|
||||
return f"qq:{group_id}:group"
|
||||
elif message_type == "private":
|
||||
user_id = message.get("user_id")
|
||||
return f"qq:{user_id}:private"
|
||||
|
||||
# notice 类型
|
||||
elif post_type == "notice":
|
||||
group_id = message.get("group_id")
|
||||
if group_id:
|
||||
return f"qq:{group_id}:group"
|
||||
user_id = message.get("user_id")
|
||||
if user_id:
|
||||
return f"qq:{user_id}:private"
|
||||
|
||||
# 未知类型,使用通用流
|
||||
return "unknown:unknown"
|
||||
|
||||
async def _cleanup_inactive_streams(self) -> None:
|
||||
"""清理不活跃的流"""
|
||||
current_time = time.time()
|
||||
to_remove = []
|
||||
|
||||
for stream_id, consumer in self.streams.items():
|
||||
if current_time - consumer.last_active_time > self.stream_timeout:
|
||||
to_remove.append(stream_id)
|
||||
|
||||
for stream_id in to_remove:
|
||||
await self.streams[stream_id].stop()
|
||||
del self.streams[stream_id]
|
||||
logger.debug(f"清理不活跃的流: {stream_id}")
|
||||
|
||||
if to_remove:
|
||||
logger.info(
|
||||
f"清理了 {len(to_remove)} 个不活跃的流 "
|
||||
f"(当前活跃流: {len(self.streams)}/{self.max_streams})"
|
||||
)
|
||||
|
||||
async def _cleanup_loop(self) -> None:
|
||||
"""定期清理循环"""
|
||||
logger.info(f"清理循环已启动,间隔: {self.cleanup_interval}秒")
|
||||
try:
|
||||
while self.is_running:
|
||||
await asyncio.sleep(self.cleanup_interval)
|
||||
await self._cleanup_inactive_streams()
|
||||
except asyncio.CancelledError:
|
||||
logger.info("清理循环已停止")
|
||||
|
||||
def get_all_stats(self) -> list[dict]:
|
||||
"""获取所有流的统计信息"""
|
||||
return [consumer.get_stats() for consumer in self.streams.values()]
|
||||
|
||||
def get_summary(self) -> dict:
|
||||
"""获取路由器摘要"""
|
||||
total_messages = sum(c.stats["total_messages"] for c in self.streams.values())
|
||||
total_queue_size = sum(c.queue.qsize() for c in self.streams.values())
|
||||
total_overflows = sum(c.stats["queue_overflow_count"] for c in self.streams.values())
|
||||
|
||||
# 计算平均队列长度
|
||||
avg_queue_size = total_queue_size / len(self.streams) if self.streams else 0
|
||||
|
||||
# 找出最繁忙的流
|
||||
busiest_stream = None
|
||||
if self.streams:
|
||||
busiest_stream = max(
|
||||
self.streams.values(),
|
||||
key=lambda c: c.stats["total_messages"]
|
||||
).stream_id
|
||||
|
||||
return {
|
||||
"total_streams": len(self.streams),
|
||||
"max_streams": self.max_streams,
|
||||
"total_messages_processed": total_messages,
|
||||
"total_queue_size": total_queue_size,
|
||||
"avg_queue_size": avg_queue_size,
|
||||
"total_queue_overflows": total_overflows,
|
||||
"busiest_stream": busiest_stream,
|
||||
}
|
||||
|
||||
|
||||
# 全局路由器实例
|
||||
stream_router = StreamRouter()
|
||||
@@ -236,8 +236,6 @@ class NapcatAdapterPlugin(BasePlugin):
|
||||
def enable_plugin(self) -> bool:
|
||||
"""通过配置文件动态控制插件启用状态"""
|
||||
# 如果已经通过配置加载了状态,使用配置中的值
|
||||
if hasattr(self, "_is_enabled"):
|
||||
return self._is_enabled
|
||||
# 否则使用默认值(禁用状态)
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user