From 8fc4cd4c3b5cd3f8ab5217c9264197e9b7d0dbd4 Mon Sep 17 00:00:00 2001
From: Windpicker-owo <3431391539@qq.com>
Date: Wed, 26 Nov 2025 16:40:31 +0800
Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E7=A7=BB=E9=99=A4?=
=?UTF-8?q?=E8=BF=87=E6=97=B6=E7=9A=84napcat=5Fadapter=5Fplugin=E7=BB=84?=
=?UTF-8?q?=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 从napcat_adapter_plugin中删除了stream_router.py、utils.py、video_handler.py、websocket_manager.py和todo.md文件。
- 在napcat_cache.json中为组和成员信息引入了一种新的缓存结构。
- 通过移除未使用的模块和整合功能,简化了插件的架构。
---
.../interest_system/bot_interest_manager.py | 59 +-
src/chat/message_receive/message_handler.py | 4 +
src/plugin_system/__init__.py | 1 +
src/plugin_system/base/base_adapter.py | 2 +-
src/plugin_system/base/base_plugin.py | 42 +-
src/plugin_system/base/component_types.py | 1 +
src/plugin_system/core/component_registry.py | 62 +-
src/plugin_system/core/plugin_manager.py | 11 +-
.../built_in/NEW_napcat_adapter/plugin.py | 105 +-
.../src/handlers/napcat_cache.json | 1 +
.../src/handlers/to_core/message_handler.py | 38 +-
.../built_in/napcat_adapter_plugin/.gitignore | 279 ---
.../built_in/napcat_adapter_plugin/CONSTS.py | 254 ---
.../napcat_adapter_plugin/__init__.py | 16 -
.../napcat_adapter_plugin/event_handlers.py | 1779 ----------------
.../napcat_adapter_plugin/event_types.py | 1844 -----------------
.../built_in/napcat_adapter_plugin/plugin.py | 430 ----
.../napcat_adapter_plugin/pyproject.toml | 47 -
.../napcat_adapter_plugin/src/__init__.py | 32 -
.../napcat_adapter_plugin/src/database.py | 157 --
.../src/message_chunker.py | 280 ---
.../src/mmc_com_layer.py | 61 -
.../src/recv_handler/__init__.py | 92 -
.../src/recv_handler/message_handler.py | 1183 -----------
.../src/recv_handler/message_sending.py | 120 --
.../src/recv_handler/meta_event_handler.py | 65 -
.../src/recv_handler/notice_handler.py | 705 -------
.../src/recv_handler/qq_emoji_list.py | 252 ---
.../src/response_pool.py | 63 -
.../napcat_adapter_plugin/src/send_handler.py | 705 -------
.../src/stream_router.py | 351 ----
.../napcat_adapter_plugin/src/utils.py | 314 ---
.../src/video_handler.py | 179 --
.../src/websocket_manager.py | 163 --
.../built_in/napcat_adapter_plugin/todo.md | 89 -
35 files changed, 162 insertions(+), 9624 deletions(-)
create mode 100644 src/plugins/built_in/NEW_napcat_adapter/src/handlers/napcat_cache.json
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/.gitignore
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/CONSTS.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/__init__.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/event_handlers.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/event_types.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/plugin.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/pyproject.toml
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/__init__.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/database.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/message_chunker.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/mmc_com_layer.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/__init__.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/message_handler.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/message_sending.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/meta_event_handler.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/notice_handler.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/qq_emoji_list.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/response_pool.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/send_handler.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/stream_router.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/utils.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/video_handler.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/src/websocket_manager.py
delete mode 100644 src/plugins/built_in/napcat_adapter_plugin/todo.md
diff --git a/src/chat/interest_system/bot_interest_manager.py b/src/chat/interest_system/bot_interest_manager.py
index ada2d0365..e817426c1 100644
--- a/src/chat/interest_system/bot_interest_manager.py
+++ b/src/chat/interest_system/bot_interest_manager.py
@@ -45,7 +45,6 @@ class BotInterestManager:
"""初始化兴趣标签系统"""
try:
logger.info("机器人兴趣系统开始初始化...")
- logger.info(f"人设ID: {personality_id}, 描述长度: {len(personality_description)}")
# 初始化embedding模型
await self._initialize_embedding_model()
@@ -74,19 +73,14 @@ class BotInterestManager:
async def _initialize_embedding_model(self):
"""初始化embedding模型"""
- logger.info("🔧 正在配置embedding客户端...")
-
# 使用项目配置的embedding模型
from src.config.config import model_config
from src.llm_models.utils_model import LLMRequest
- logger.debug("✅ 成功导入embedding相关模块")
-
# 检查embedding配置是否存在
if not hasattr(model_config.model_task_config, "embedding"):
raise RuntimeError("❌ 未找到embedding模型配置")
- logger.info("📋 找到embedding模型配置")
self.embedding_config = model_config.model_task_config.embedding
if self.embedding_dimension:
@@ -96,36 +90,20 @@ class BotInterestManager:
# 创建LLMRequest实例用于embedding
self.embedding_request = LLMRequest(model_set=self.embedding_config, request_type="interest_embedding")
- logger.info("✅ Embedding请求客户端初始化成功")
- logger.info(f"🔗 客户端类型: {type(self.embedding_request).__name__}")
-
- # 获取第一个embedding模型的ModelInfo
- if hasattr(self.embedding_config, "model_list") and self.embedding_config.model_list:
- first_model_name = self.embedding_config.model_list[0]
- logger.info(f"🎯 使用embedding模型: {first_model_name}")
- else:
- logger.warning("⚠️ 未找到embedding模型列表")
-
- logger.info("✅ Embedding模型初始化完成")
async def _load_or_generate_interests(self, personality_description: str, personality_id: str):
"""加载或生成兴趣标签"""
- logger.info(f"📚 正在为 '{personality_id}' 加载或生成兴趣标签...")
# 首先尝试从数据库加载
- logger.info("尝试从数据库加载兴趣标签...")
loaded_interests = await self._load_interests_from_database(personality_id)
if loaded_interests:
self.current_interests = loaded_interests
- active_count = len(loaded_interests.get_active_tags())
- logger.info(f"成功从数据库加载 {active_count} 个兴趣标签 (版本: {loaded_interests.version})")
+ active_count = len(loaded_interests.get_active_tags())
tags_info = [f" - '{tag.tag_name}' (权重: {tag.weight:.2f})" for tag in loaded_interests.get_active_tags()]
tags_str = "\n".join(tags_info)
- logger.info(f"当前兴趣标签:\n{tags_str}")
# 为加载的标签生成embedding(数据库不存储embedding,启动时动态生成)
- logger.info("🧠 为加载的标签生成embedding向量...")
await self._generate_embeddings_for_tags(loaded_interests)
else:
# 生成新的兴趣标签
@@ -163,7 +141,6 @@ class BotInterestManager:
raise RuntimeError("❌ Embedding客户端未初始化,无法生成兴趣标签")
# 构建提示词
- logger.info("📝 构建LLM提示词...")
prompt = f"""
基于以下机器人人设描述,生成一套合适的兴趣标签:
@@ -218,13 +195,11 @@ class BotInterestManager:
"""
# 调用LLM生成兴趣标签
- logger.info("🤖 正在调用LLM生成兴趣标签...")
response = await self._call_llm_for_interest_generation(prompt)
if not response:
raise RuntimeError("❌ LLM未返回有效响应")
- logger.info("✅ LLM响应成功,开始解析兴趣标签...")
# 使用统一的 JSON 解析工具
interests_data = extract_and_parse_json(response, strict=False)
if not interests_data or not isinstance(interests_data, dict):
@@ -290,7 +265,6 @@ class BotInterestManager:
replyer_config = model_config.model_task_config.replyer
# 调用LLM API
- logger.info("🚀 正在通过LLM API发送请求...")
success, response, reasoning_content, model_name = await llm_api.generate_with_model(
prompt=full_prompt,
model_config=replyer_config,
@@ -300,13 +274,6 @@ class BotInterestManager:
)
if success and response:
- logger.info(f"✅ LLM响应成功,模型: {model_name}, 响应长度: {len(response)} 字符")
- logger.debug(
- f"📄 LLM响应内容: {response[:200]}..." if len(response) > 200 else f"📄 LLM响应内容: {response}"
- )
- if reasoning_content:
- logger.debug(f"🧠 推理内容: {reasoning_content[:100]}...")
-
# 直接返回原始响应,后续使用统一的 JSON 解析工具
return response
else:
@@ -329,11 +296,8 @@ class BotInterestManager:
# 尝试从文件加载缓存
file_cache = await self._load_embedding_cache_from_file(interests.personality_id)
if file_cache:
- logger.info(f"📂 从文件加载 {len(file_cache)} 个embedding缓存")
self.embedding_cache.update(file_cache)
- logger.info(f"🧠 开始为 {total_tags} 个兴趣标签生成embedding向量...")
-
memory_cached_count = 0
file_cached_count = 0
generated_count = 0
@@ -352,8 +316,6 @@ class BotInterestManager:
else:
# 动态生成新的embedding
embedding_text = tag.tag_name
-
- logger.debug(f" [{i}/{total_tags}] 🔄 正在为 '{tag.tag_name}' 动态生成embedding...")
embedding = await self._get_embedding(embedding_text)
if embedding:
@@ -371,18 +333,8 @@ class BotInterestManager:
# 如果有新生成的embedding,保存到文件
if generated_count > 0:
await self._save_embedding_cache_to_file(interests.personality_id)
- logger.info(f"💾 已将 {generated_count} 个新生成的embedding保存到缓存文件")
interests.last_updated = datetime.now()
- logger.info("=" * 50)
- logger.info("✅ Embedding生成完成!")
- logger.info(f"📊 总标签数: {total_tags}")
- logger.info(f"� 文件缓存命中: {file_cached_count}")
- logger.info(f"�💾 内存缓存命中: {memory_cached_count}")
- logger.info(f"🆕 新生成: {generated_count}")
- logger.info(f"❌ 失败: {failed_count}")
- logger.info(f"🗃️ 总缓存大小: {len(self.embedding_cache)}")
- logger.info("=" * 50)
async def _get_embedding(self, text: str) -> list[float]:
"""获取文本的embedding向量"""
@@ -391,11 +343,9 @@ class BotInterestManager:
# 检查缓存
if text in self.embedding_cache:
- logger.debug(f"💾 使用缓存的embedding: '{text[:30]}...'")
return self.embedding_cache[text]
# 使用LLMRequest获取embedding
- logger.debug(f"🔄 正在获取embedding: '{text[:30]}...'")
if not self.embedding_request:
raise RuntimeError("❌ Embedding客户端未初始化")
embedding, model_name = await self.embedding_request.get_embedding(text)
@@ -414,16 +364,12 @@ class BotInterestManager:
)
else:
self.embedding_dimension = current_dim
- logger.info(f"📏 检测到embedding维度: {current_dim}")
elif current_dim != self.embedding_dimension:
logger.warning(
"⚠️ 收到的embedding维度发生变化: 之前=%d, 当前=%d。请确认模型配置是否正确。",
self.embedding_dimension,
current_dim,
)
-
- logger.debug(f"✅ Embedding获取成功,维度: {current_dim}, 模型: {model_name}")
- return embedding
else:
raise RuntimeError(f"❌ 返回的embedding为空: {embedding}")
@@ -435,11 +381,8 @@ class BotInterestManager:
else:
combined_text = message_text
- logger.debug(f"🔄 正在为消息生成embedding,输入长度: {len(combined_text)}")
-
# 生成embedding
embedding = await self._get_embedding(combined_text)
- logger.debug(f"✅ 消息embedding生成成功,维度: {len(embedding)}")
return embedding
async def generate_embeddings_for_texts(
diff --git a/src/chat/message_receive/message_handler.py b/src/chat/message_receive/message_handler.py
index ff40a51a5..362ab40f4 100644
--- a/src/chat/message_receive/message_handler.py
+++ b/src/chat/message_receive/message_handler.py
@@ -258,6 +258,10 @@ class MessageHandler:
group_info = message_info.get("group_info")
user_info = message_info.get("user_info")
+ if not user_info and not group_info:
+ logger.debug("消息缺少用户信息,已跳过处理")
+ return None
+
# 获取或创建聊天流
platform = message_info.get("platform", "unknown")
diff --git a/src/plugin_system/__init__.py b/src/plugin_system/__init__.py
index aaca76746..0bf545580 100644
--- a/src/plugin_system/__init__.py
+++ b/src/plugin_system/__init__.py
@@ -41,6 +41,7 @@ from .base import (
EventHandlerInfo,
EventType,
PluginInfo,
+ AdapterInfo,
# 新增的增强命令系统
PlusCommand,
BaseRouterComponent,
diff --git a/src/plugin_system/base/base_adapter.py b/src/plugin_system/base/base_adapter.py
index 88e65aa22..ac38d250f 100644
--- a/src/plugin_system/base/base_adapter.py
+++ b/src/plugin_system/base/base_adapter.py
@@ -15,7 +15,7 @@ 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.base.base_plugin import BasePlugin
+ from src.plugin_system import BasePlugin, AdapterInfo
from src.common.logger import get_logger
diff --git a/src/plugin_system/base/base_plugin.py b/src/plugin_system/base/base_plugin.py
index ada78e634..f6f492bef 100644
--- a/src/plugin_system/base/base_plugin.py
+++ b/src/plugin_system/base/base_plugin.py
@@ -3,6 +3,7 @@ from abc import abstractmethod
from src.common.logger import get_logger
from src.plugin_system.base.component_types import (
ActionInfo,
+ AdapterInfo,
CommandInfo,
ComponentType,
EventHandlerInfo,
@@ -13,6 +14,7 @@ from src.plugin_system.base.component_types import (
)
from .base_action import BaseAction
+from .base_adapter import BaseAdapter
from .base_command import BaseCommand
from .base_events_handler import BaseEventHandler
from .base_interest_calculator import BaseInterestCalculator
@@ -35,27 +37,26 @@ class BasePlugin(PluginBase):
@classmethod
def _get_component_info_from_class(cls, component_class: type, component_type: ComponentType):
- """从组件类自动生成组件信息
+ """从类获取组件信息
Args:
component_class: 组件类
component_type: 组件类型
-
Returns:
- 对应类型的ComponentInfo对象
+ 对应的ComponentInfo对象
"""
if component_type == ComponentType.COMMAND:
if hasattr(component_class, "get_command_info"):
return component_class.get_command_info()
else:
- logger.warning(f"Command类 {component_class.__name__} 缺少 get_command_info 方法")
+ logger.warning(f"Command组件 {component_class.__name__} 缺少 get_command_info 方法")
return None
elif component_type == ComponentType.ACTION:
if hasattr(component_class, "get_action_info"):
return component_class.get_action_info()
else:
- logger.warning(f"Action类 {component_class.__name__} 缺少 get_action_info 方法")
+ logger.warning(f"Action组件 {component_class.__name__} 缺少 get_action_info 方法")
return None
elif component_type == ComponentType.INTEREST_CALCULATOR:
@@ -63,30 +64,37 @@ class BasePlugin(PluginBase):
return component_class.get_interest_calculator_info()
else:
logger.warning(
- f"InterestCalculator类 {component_class.__name__} 缺少 get_interest_calculator_info 方法"
+ f"InterestCalculator组件 {component_class.__name__} 缺少 get_interest_calculator_info 方法"
)
return None
elif component_type == ComponentType.PLUS_COMMAND:
- # PlusCommand的get_info逻辑可以在这里实现
- logger.warning("PlusCommand的get_info逻辑尚未实现")
+ # PlusCommand组件的get_info方法尚未实现
+ logger.warning("PlusCommand组件的get_info方法尚未实现")
return None
elif component_type == ComponentType.TOOL:
- # Tool的get_info逻辑可以在这里实现
- logger.warning("Tool的get_info逻辑尚未实现")
+ # Tool组件的get_info方法尚未实现
+ logger.warning("Tool组件的get_info方法尚未实现")
return None
elif component_type == ComponentType.EVENT_HANDLER:
- # EventHandler的get_info逻辑可以在这里实现
- logger.warning("EventHandler的get_info逻辑尚未实现")
+ # EventHandler组件的get_info方法尚未实现
+ logger.warning("EventHandler组件的get_info方法尚未实现")
return None
elif component_type == ComponentType.PROMPT:
if hasattr(component_class, "get_prompt_info"):
return component_class.get_prompt_info()
else:
- logger.warning(f"Prompt类 {component_class.__name__} 缺少 get_prompt_info 方法")
+ logger.warning(f"Prompt组件 {component_class.__name__} 缺少 get_prompt_info 方法")
+ return None
+
+ elif component_type == ComponentType.ADAPTER:
+ if hasattr(component_class, "get_adapter_info"):
+ return component_class.get_adapter_info()
+ else:
+ logger.warning(f"Adapter�� {component_class.__name__} ȱ�� get_adapter_info ����")
return None
else:
@@ -95,16 +103,13 @@ class BasePlugin(PluginBase):
@classmethod
def get_component_info(cls, component_class: type, component_type: ComponentType):
- """获取组件信息的通用方法
-
- 这是一个便捷方法,内部调用_get_component_info_from_class
+ """获取组件信息
Args:
component_class: 组件类
component_type: 组件类型
-
Returns:
- 对应类型的ComponentInfo对象
+ 对应的ComponentInfo对象
"""
return cls._get_component_info_from_class(component_class, component_type)
@@ -113,6 +118,7 @@ class BasePlugin(PluginBase):
self,
) -> list[
tuple[ActionInfo, type[BaseAction]]
+ | tuple[AdapterInfo, type[BaseAdapter]]
| tuple[CommandInfo, type[BaseCommand]]
| tuple[PlusCommandInfo, type[PlusCommand]]
| tuple[EventHandlerInfo, type[BaseEventHandler]]
diff --git a/src/plugin_system/base/component_types.py b/src/plugin_system/base/component_types.py
index becbe1b63..b60ac9789 100644
--- a/src/plugin_system/base/component_types.py
+++ b/src/plugin_system/base/component_types.py
@@ -152,6 +152,7 @@ class AdapterInfo:
name: str # 适配器名称
component_type: ComponentType = field(default=ComponentType.ADAPTER, init=False)
+ plugin_name: str = "" # �����������
version: str = "1.0.0" # 适配器版本
platform: str = "unknown" # 平台名称
description: str = "" # 适配器描述
diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py
index d40cb358c..9fc78caf0 100644
--- a/src/plugin_system/core/component_registry.py
+++ b/src/plugin_system/core/component_registry.py
@@ -10,6 +10,7 @@ from fastapi import Depends
from src.common.logger import get_logger
from src.config.config import global_config as bot_config
from src.plugin_system.base.base_action import BaseAction
+from src.plugin_system.base.base_adapter import BaseAdapter
from src.plugin_system.base.base_chatter import BaseChatter
from src.plugin_system.base.base_command import BaseCommand
from src.plugin_system.base.base_events_handler import BaseEventHandler
@@ -19,6 +20,7 @@ from src.plugin_system.base.base_prompt import BasePrompt
from src.plugin_system.base.base_tool import BaseTool
from src.plugin_system.base.component_types import (
ActionInfo,
+ AdapterInfo,
ChatterInfo,
CommandInfo,
ComponentInfo,
@@ -46,6 +48,7 @@ ComponentClassType = (
| type[BaseInterestCalculator]
| type[BasePrompt]
| type[BaseRouterComponent]
+ | type[BaseAdapter]
)
@@ -109,6 +112,21 @@ class ComponentRegistry:
"""启用的chatter名 -> chatter类"""
logger.info("组件注册中心初始化完成")
+ self._interest_calculator_registry: dict[str, type["BaseInterestCalculator"]] = {}
+ """兴趣计算器名 -> 兴趣计算器类"""
+ self._enabled_interest_calculator_registry: dict[str, type["BaseInterestCalculator"]] = {}
+ """启用的兴趣计算器名 -> 兴趣计算器类"""
+
+ self._prompt_registry: dict[str, type["BasePrompt"]] = {}
+ """提示词组件名 -> 提示词组件类"""
+ self._enabled_prompt_registry: dict[str, type["BasePrompt"]] = {}
+ """启用的提示词组件名 -> 提示词组件类"""
+
+ self._adapter_registry: dict[str, type["BaseAdapter"]] = {}
+ """适配器组件名 -> 适配器组件类"""
+ self._enabled_adapter_registry: dict[str, type["BaseAdapter"]] = {}
+ """启用的适配器组件名 -> 适配器组件类"""
+
# == 注册方法 ==
def register_plugin(self, plugin_info: PluginInfo) -> bool:
@@ -204,6 +222,10 @@ class ComponentRegistry:
assert isinstance(component_info, RouterInfo)
assert issubclass(component_class, BaseRouterComponent)
ret = self._register_router_component(component_info, component_class)
+ case ComponentType.ADAPTER:
+ assert isinstance(component_info, AdapterInfo)
+ assert issubclass(component_class, BaseAdapter)
+ ret = self._register_adapter_component(component_info, component_class)
case _:
logger.warning(f"未知组件类型: {component_type}")
ret = False
@@ -335,12 +357,6 @@ class ComponentRegistry:
logger.error(f"注册失败: {calculator_name} 不是有效的InterestCalculator")
return False
- # 创建专门的InterestCalculator注册表(如果还没有)
- if not hasattr(self, "_interest_calculator_registry"):
- self._interest_calculator_registry: dict[str, type["BaseInterestCalculator"]] = {}
- if not hasattr(self, "_enabled_interest_calculator_registry"):
- self._enabled_interest_calculator_registry: dict[str, type["BaseInterestCalculator"]] = {}
-
_assign_plugin_attrs(
interest_calculator_class,
interest_calculator_info.plugin_name,
@@ -365,11 +381,6 @@ class ComponentRegistry:
logger.error(f"Prompt组件 {prompt_class.__name__} 必须指定名称")
return False
- if not hasattr(self, "_prompt_registry"):
- self._prompt_registry: dict[str, type[BasePrompt]] = {}
- if not hasattr(self, "_enabled_prompt_registry"):
- self._enabled_prompt_registry: dict[str, type[BasePrompt]] = {}
-
_assign_plugin_attrs(
prompt_class, prompt_info.plugin_name, self.get_plugin_config(prompt_info.plugin_name) or {}
)
@@ -420,6 +431,29 @@ class ComponentRegistry:
logger.error(f"注册路由组件 '{router_info.name}' 时出错: {e}", exc_info=True)
return False
+ def _register_adapter_component(self, adapter_info: AdapterInfo, adapter_class: type[BaseAdapter]) -> bool:
+ """将Adapter组件注册到Adapter特定注册表"""
+
+ adapter_name = adapter_info.name
+ if not adapter_name:
+ logger.error(f"Adapter组件 {adapter_class.__name__} 必须指定名称")
+ return False
+ if not isinstance(adapter_info, AdapterInfo) or not issubclass(adapter_class, BaseAdapter):
+ logger.error(f"注册失败: {adapter_name} 不是有效的Adapter")
+ return False
+
+ _assign_plugin_attrs(
+ adapter_class, adapter_info.plugin_name, self.get_plugin_config(adapter_info.plugin_name) or {}
+ )
+ if not hasattr(self, "_adapter_registry"):
+ self._adapter_registry: dict[str, type[BaseAdapter]] = {}
+
+ self._adapter_registry[adapter_name] = adapter_class
+
+ if not adapter_info.enabled:
+ logger.warning(f"Adapter {adapter_name} 未启用")
+ return True
+
# === 组件移除相关 ===
async def remove_component(self, component_name: str, component_type: ComponentType, plugin_name: str) -> bool:
@@ -664,6 +698,7 @@ class ComponentRegistry:
| BaseInterestCalculator
| BasePrompt
| BaseRouterComponent
+ | BaseAdapter
]
| None
):
@@ -693,6 +728,7 @@ class ComponentRegistry:
| type[BaseInterestCalculator]
| type[BasePrompt]
| type[BaseRouterComponent]
+ | type[BaseAdapter]
| None,
self._components_classes.get(namespaced_name),
)
@@ -919,6 +955,7 @@ class ComponentRegistry:
chatter_components: int = 0
prompt_components: int = 0
router_components: int = 0
+ adapter_components: int = 0
for component in self._components.values():
if component.component_type == ComponentType.ACTION:
action_components += 1
@@ -936,6 +973,8 @@ class ComponentRegistry:
prompt_components += 1
elif component.component_type == ComponentType.ROUTER:
router_components += 1
+ elif component.component_type == ComponentType.ADAPTER:
+ adapter_components += 1
return {
"action_components": action_components,
"command_components": command_components,
@@ -946,6 +985,7 @@ class ComponentRegistry:
"chatter_components": chatter_components,
"prompt_components": prompt_components,
"router_components": router_components,
+ "adapter_components": adapter_components,
"total_components": len(self._components),
"total_plugins": len(self._plugins),
"components_by_type": {
diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py
index 125fac63e..49f864b45 100644
--- a/src/plugin_system/core/plugin_manager.py
+++ b/src/plugin_system/core/plugin_manager.py
@@ -481,13 +481,14 @@ class PluginManager:
chatter_count = stats.get("chatter_components", 0)
prompt_count = stats.get("prompt_components", 0)
router_count = stats.get("router_components", 0)
+ adapter_count = stats.get("adapter_components", 0)
total_components = stats.get("total_components", 0)
# 📋 显示插件加载总览
if total_registered > 0:
logger.info("🎉 插件系统加载完成!")
logger.info(
- f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, PlusCommand: {plus_command_count}, EventHandler: {event_handler_count}, Chatter: {chatter_count}, Prompt: {prompt_count}, Router: {router_count})"
+ f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, PlusCommand: {plus_command_count}, EventHandler: {event_handler_count}, Chatter: {chatter_count}, Prompt: {prompt_count}, Router: {router_count}, Adapter: {adapter_count})"
)
# 显示详细的插件列表
@@ -531,6 +532,9 @@ class PluginManager:
router_components = [
c for c in plugin_info.components if c.component_type == ComponentType.ROUTER
]
+ adapter_components = [
+ c for c in plugin_info.components if c.component_type == ComponentType.ADAPTER
+ ]
if action_components:
action_details = [format_component(c) for c in action_components]
@@ -560,6 +564,9 @@ class PluginManager:
if router_components:
router_details = [format_component(c) for c in router_components]
logger.info(f" 🌐 Router组件: {', '.join(router_details)}")
+ if adapter_components:
+ adapter_details = [format_component(c) for c in adapter_components]
+ logger.info(f" 🔌 Adapter组件: {', '.join(adapter_details)}")
# 权限节点信息
if plugin_instance := self.loaded_plugins.get(plugin_name):
@@ -698,4 +705,4 @@ class PluginManager:
# 全局插件管理器实例
-plugin_manager = PluginManager()
\ No newline at end of file
+plugin_manager = PluginManager()
diff --git a/src/plugins/built_in/NEW_napcat_adapter/plugin.py b/src/plugins/built_in/NEW_napcat_adapter/plugin.py
index 1eefc990b..be4b8a2dd 100644
--- a/src/plugins/built_in/NEW_napcat_adapter/plugin.py
+++ b/src/plugins/built_in/NEW_napcat_adapter/plugin.py
@@ -19,7 +19,7 @@ import websockets
from mofox_wire import CoreSink, MessageEnvelope, WebSocketAdapterOptions
from src.common.logger import get_logger
-from src.plugin_system import register_plugin
+from src.plugin_system import ConfigField, register_plugin
from src.plugin_system.base import BaseAdapter, BasePlugin
from src.plugin_system.apis import config_api
@@ -43,7 +43,7 @@ class NapcatAdapter(BaseAdapter):
run_in_subprocess = False
- def __init__(self, core_sink: CoreSink, plugin: Optional[BasePlugin] = None):
+ def __init__(self, core_sink: CoreSink, plugin: Optional[BasePlugin] = None, **kwargs):
"""初始化 Napcat 适配器"""
# 从插件配置读取 WebSocket URL
if plugin:
@@ -52,7 +52,6 @@ class NapcatAdapter(BaseAdapter):
access_token = config_api.get_plugin_config(plugin.config, "napcat_server.access_token", "")
ws_url = f"ws://{host}:{port}"
-
headers = {}
if access_token:
headers["Authorization"] = f"Bearer {access_token}"
@@ -62,11 +61,12 @@ class NapcatAdapter(BaseAdapter):
# 配置 WebSocket 传输
transport = WebSocketAdapterOptions(
+ mode="server",
url=ws_url,
headers=headers if headers else None,
)
- super().__init__(core_sink, plugin=plugin, transport=transport)
+ super().__init__(core_sink, plugin=plugin, transport=transport, **kwargs)
# 初始化处理器
self.message_handler = MessageHandler(self)
@@ -178,11 +178,14 @@ class NapcatAdapter(BaseAdapter):
self._response_pool[echo] = future
# 构造请求
- request = orjson.dumps({
- "action": action,
- "params": params,
- "echo": echo,
- })
+ # Napcat expects JSON text frames; orjson.dumps returns bytes so decode to str
+ request = orjson.dumps(
+ {
+ "action": action,
+ "params": params,
+ "echo": echo,
+ }
+ ).decode()
try:
# 发送请求
@@ -214,57 +217,53 @@ class NapcatAdapterPlugin(BasePlugin):
"""Napcat 适配器插件"""
plugin_name = "napcat_adapter_plugin"
+ config_file_name = "config.toml"
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": "1.0.0"},
- "enabled": {"type": bool, "default": True},
- },
- "napcat_server": {
- "host": {"type": str, "default": "localhost"},
- "port": {"type": int, "default": 8095},
- "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},
- },
+ config_section_descriptions: ClassVar = {
+ "plugin": "插件开关",
+ "napcat_server": "Napcat WebSocket 连接设置",
+ "features": "过滤和名单配置",
}
- def __init__(self):
- self._adapter: Optional[NapcatAdapter] = None
-
- async def on_plugin_loaded(self):
- """插件加载时启动适配器"""
- logger.info("Napcat 适配器插件正在加载...")
-
- # 从 CoreSinkManager 获取 InProcessCoreSink
- from src.common.core_sink_manager import get_core_sink_manager
-
- core_sink_manager = get_core_sink_manager()
- core_sink = core_sink_manager.get_in_process_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 适配器插件已卸载")
+ config_schema: ClassVar[dict] = {
+ "plugin": {
+ "enabled": ConfigField(type=bool, default=True, description="是否启用 Napcat 适配器"),
+ "config_version": ConfigField(type=str, default="2.0.0", description="配置文件版本"),
+ },
+ "napcat_server": {
+ "mode": ConfigField(
+ type=str,
+ default="reverse",
+ description="ws 连接模式: reverse/direct",
+ choices=["reverse", "direct"],
+ ),
+ "host": ConfigField(type=str, default="localhost", description="Napcat WebSocket 服务地址"),
+ "port": ConfigField(type=int, default=8095, description="Napcat WebSocket 服务端口"),
+ "access_token": ConfigField(type=str, default="", description="Napcat API 访问令牌(可选)"),
+ },
+ "features": {
+ "group_list_type": ConfigField(
+ type=str,
+ default="blacklist",
+ description="群聊名单模式: blacklist/whitelist",
+ choices=["blacklist", "whitelist"],
+ ),
+ "group_list": ConfigField(type=list, default=[], description="群聊名单;根据名单模式过滤"),
+ "private_list_type": ConfigField(
+ type=str,
+ default="blacklist",
+ description="私聊名单模式: blacklist/whitelist",
+ choices=["blacklist", "whitelist"],
+ ),
+ "private_list": ConfigField(type=list, default=[], description="私聊名单;根据名单模式过滤"),
+ "ban_user_id": ConfigField(type=list, default=[], description="全局封禁的用户 ID 列表"),
+ "ban_qq_bot": ConfigField(type=bool, default=False, description="是否屏蔽其他 QQ 机器人消息"),
+ },
+ }
def get_plugin_components(self) -> list:
"""返回适配器组件"""
diff --git a/src/plugins/built_in/NEW_napcat_adapter/src/handlers/napcat_cache.json b/src/plugins/built_in/NEW_napcat_adapter/src/handlers/napcat_cache.json
new file mode 100644
index 000000000..6e642ef3d
--- /dev/null
+++ b/src/plugins/built_in/NEW_napcat_adapter/src/handlers/napcat_cache.json
@@ -0,0 +1 @@
+{"group_info":{"169850076":{"data":{"group_all_shut":0,"group_remark":"","group_id":169850076,"group_name":"墨狐狐🌟起源之地","member_count":439,"max_member_count":500},"ts":1764145931.0869653},"329577252":{"data":{"group_all_shut":0,"group_remark":"","group_id":329577252,"group_name":"真言的小窝『小可爱聚集地』","member_count":1748,"max_member_count":2000},"ts":1764145933.5440657},"160791652":{"data":{"group_all_shut":0,"group_remark":"","group_id":160791652,"group_name":"一群「往世乐土」","member_count":476,"max_member_count":500},"ts":1764146234.340923}},"group_detail_info":{},"member_info":{"329577252:3307339367":{"data":{"group_id":329577252,"user_id":3307339367,"nickname":"爱丽丝","card":"爱丽丝","sex":"unknown","age":0,"area":"","level":"70","qq_level":11,"join_time":1751672499,"last_sent_time":1764145599,"title_expire_time":0,"unfriendly":false,"card_changeable":true,"is_robot":false,"shut_up_timestamp":0,"role":"admin","title":"不智能ai"},"ts":1764145619.5129182},"169850076:2359337932":{"data":{"group_id":169850076,"user_id":2359337932,"nickname":"不到人","card":"","sex":"unknown","age":0,"area":"","level":"43","qq_level":56,"join_time":1758019556,"last_sent_time":1764145519,"title_expire_time":0,"unfriendly":false,"card_changeable":true,"is_robot":false,"shut_up_timestamp":0,"role":"admin","title":""},"ts":1764145931.3597307}},"stranger_info":{},"self_info":{}}
\ No newline at end of file
diff --git a/src/plugins/built_in/NEW_napcat_adapter/src/handlers/to_core/message_handler.py b/src/plugins/built_in/NEW_napcat_adapter/src/handlers/to_core/message_handler.py
index 0e83e39bb..abe00a10c 100644
--- a/src/plugins/built_in/NEW_napcat_adapter/src/handlers/to_core/message_handler.py
+++ b/src/plugins/built_in/NEW_napcat_adapter/src/handlers/to_core/message_handler.py
@@ -106,11 +106,13 @@ class MessageHandler:
accept_format=ACCEPT_FORMAT,
)
+ msg_builder.seg_list(seg_list)
+
return msg_builder.build()
async def handle_single_segment(
self, segment: dict, raw_message: dict, in_reply: bool = False
- ) -> SegPayload | List[SegPayload] | None:
+ ) -> SegPayload | None:
"""
处理单一消息段并转换为 MessageEnvelope
@@ -203,31 +205,25 @@ class MessageHandler:
sender_info: dict = message_detail.get("sender", {})
sender_nickname: str = sender_info.get("nickname", "")
sender_id = sender_info.get("user_id")
- seg_message: List[SegPayload] = []
if not sender_nickname:
logger.warning("无法获取被引用的人的昵称,返回默认值")
- seg_message.append(
- {
- "type": "text",
- "data": f"[回复<未知用户>:{reply_message}],说:",
- }
- )
+ return {
+ "type": "text",
+ "data": f"[回复<未知用户>:{reply_message}],说:",
+ }
+
else:
if sender_id:
- seg_message.append(
- {
- "type": "text",
- "data": f"[回复<{sender_nickname}({sender_id})>:{reply_message}],说:",
- }
- )
+ return {
+ "type": "text",
+ "data": f"[回复<{sender_nickname}({sender_id})>:{reply_message}],说:",
+ }
else:
- seg_message.append(
- {
- "type": "text",
- "data": f"[回复<{sender_nickname}>:{reply_message}],说:",
- }
- )
- return seg_message
+ return {
+ "type": "text",
+ "data": f"[回复<{sender_nickname}>:{reply_message}],说:",
+ }
+
case "voice":
seg_data = segment.get("url", "")
case _:
diff --git a/src/plugins/built_in/napcat_adapter_plugin/.gitignore b/src/plugins/built_in/napcat_adapter_plugin/.gitignore
deleted file mode 100644
index e4ce10662..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/.gitignore
+++ /dev/null
@@ -1,279 +0,0 @@
-
-log/
-logs/
-out/
-
-.env
-.env.*
-.cursor
-
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-uv.lock
-llm_statistics.txt
-mongodb
-napcat
-run_dev.bat
-elua.confirmed
-# C extensions
-*.so
-/results
-config_backup/
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-cover/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-.pybuilder/
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-# For a library or package, you might want to ignore these files since the code is
-# intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# UV
-# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-#uv.lock
-
-# poetry
-# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
-#poetry.lock
-
-# pdm
-# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
-#pdm.lock
-# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
-# in version control.
-# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
-.pdm.toml
-.pdm-python
-.pdm-build/
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# pytype static type analyzer
-.pytype/
-
-# Cython debug symbols
-cython_debug/
-
-# PyCharm
-# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-# and can be added to the global gitignore or merged into this file. For a more nuclear
-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
-
-# PyPI configuration file
-.pypirc
-
-# rjieba
-rjieba.cache
-
-# .vscode
-!.vscode/settings.json
-
-# direnv
-/.direnv
-
-# JetBrains
-.idea
-*.iml
-*.ipr
-
-# PyEnv
-# If using PyEnv and configured to use a specific Python version locally
-# a .local-version file will be created in the root of the project to specify the version.
-.python-version
-
-OtherRes.txt
-
-/eula.confirmed
-/privacy.confirmed
-
-logs
-
-.ruff_cache
-
-.vscode
-
-/config/*
-config/old/bot_config_20250405_212257.toml
-temp/
-
-# General
-.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-# Windows thumbnail cache files
-Thumbs.db
-Thumbs.db:encryptable
-ehthumbs.db
-ehthumbs_vista.db
-
-# Dump file
-*.stackdump
-
-# Folder config file
-[Dd]esktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-*.cab
-*.msi
-*.msix
-*.msm
-*.msp
-
-# Windows shortcuts
-*.lnk
-
-config.toml
-feature.toml
-config.toml.back
-test
-data/NapcatAdapter.db
-data/NapcatAdapter.db-shm
-data/NapcatAdapter.db-wal
\ No newline at end of file
diff --git a/src/plugins/built_in/napcat_adapter_plugin/CONSTS.py b/src/plugins/built_in/napcat_adapter_plugin/CONSTS.py
deleted file mode 100644
index aac12589d..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/CONSTS.py
+++ /dev/null
@@ -1,254 +0,0 @@
-PLUGIN_NAME = "napcat_adapter"
-
-QQ_FACE: dict = {
- "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": "[表情:按钮]",
- "😊": "[表情:嘿嘿]",
- "😌": "[表情:羞涩]",
- "😚": "[ 表情:亲亲]",
- "😓": "[表情:汗]",
- "😰": "[表情:紧张]",
- "😝": "[表情:吐舌]",
- "😁": "[表情:呲牙]",
- "😜": "[表情:淘气]",
- "☺": "[表情:可爱]",
- "😍": "[表情:花痴]",
- "😔": "[表情:失落]",
- "😄": "[表情:高兴]",
- "😏": "[表情:哼哼]",
- "😒": "[表情:不屑]",
- "😳": "[表情:瞪眼]",
- "😘": "[表情:飞吻]",
- "😭": "[表情:大哭]",
- "😱": "[表情:害怕]",
- "😂": "[表情:激动]",
- "💪": "[表情:肌肉]",
- "👊": "[表情:拳头]",
- "👍": "[表情 :厉害]",
- "👏": "[表情:鼓掌]",
- "👎": "[表情:鄙视]",
- "🙏": "[表情:合十]",
- "👌": "[表情:好的]",
- "👆": "[表情:向上]",
- "👀": "[表情:眼睛]",
- "🍜": "[表情:拉面]",
- "🍧": "[表情:刨冰]",
- "🍞": "[表情:面包]",
- "🍺": "[表情:啤酒]",
- "🍻": "[表情:干杯]",
- "☕": "[表情:咖啡]",
- "🍎": "[表情:苹果]",
- "🍓": "[表情:草莓]",
- "🍉": "[表情:西瓜]",
- "🚬": "[表情:吸烟]",
- "🌹": "[表情:玫瑰]",
- "🎉": "[表情:庆祝]",
- "💝": "[表情:礼物]",
- "💣": "[表情:炸弹]",
- "✨": "[表情:闪光]",
- "💨": "[表情:吹气]",
- "💦": "[表情:水]",
- "🔥": "[表情:火]",
- "💤": "[表情:睡觉]",
- "💩": "[表情:便便]",
- "💉": "[表情:打针]",
- "📫": "[表情:邮箱]",
- "🐎": "[表情:骑马]",
- "👧": "[表情:女孩]",
- "👦": "[表情:男孩]",
- "🐵": "[表情:猴]",
- "🐷": "[表情:猪]",
- "🐮": "[表情:牛]",
- "🐔": "[表情:公鸡]",
- "🐸": "[表情:青蛙]",
- "👻": "[表情:幽灵]",
- "🐛": "[表情:虫]",
- "🐶": "[表情:狗]",
- "🐳": "[表情:鲸鱼]",
- "👢": "[表情:靴子]",
- "☀": "[表情:晴天]",
- "❔": "[表情:问号]",
- "🔫": "[表情:手枪]",
- "💓": "[表情:爱 心]",
- "🏪": "[表情:便利店]",
-}
diff --git a/src/plugins/built_in/napcat_adapter_plugin/__init__.py b/src/plugins/built_in/napcat_adapter_plugin/__init__.py
deleted file mode 100644
index 15e1543e6..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-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/InternetSearchPlugin",
- keywords=["qq", "bot", "napcat", "onebot", "api", "websocket"],
- categories=["protocol"],
- extra={
- "is_built_in": False,
- },
-)
diff --git a/src/plugins/built_in/napcat_adapter_plugin/event_handlers.py b/src/plugins/built_in/napcat_adapter_plugin/event_handlers.py
deleted file mode 100644
index 5855237d2..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/event_handlers.py
+++ /dev/null
@@ -1,1779 +0,0 @@
-from typing import ClassVar
-
-from src.common.logger import get_logger
-from src.plugin_system import BaseEventHandler
-from src.plugin_system.base.base_event import HandlerResult
-
-from .event_types import NapcatEvent
-from .src.send_handler import send_handler
-
-logger = get_logger("napcat_adapter")
-
-
-class SetProfileHandler(BaseEventHandler):
- handler_name: str = "napcat_set_qq_profile_handler"
- handler_description: str = "设置账号信息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.SET_PROFILE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- nickname = params.get("nickname", "")
- personal_note = params.get("personal_note", "")
- sex = params.get("sex", "")
-
- if params.get("raw", ""):
- nickname = raw.get("nickname", "")
- personal_note = raw.get("personal_note", "")
- sex = raw.get("sex", "")
-
- if not nickname:
- logger.error("事件 napcat_set_qq_profile 缺少必要参数: nickname ")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"nickname": nickname, "personal_note": personal_note, "sex": sex}
- response = await send_handler.send_message_to_napcat(action="set_qq_profile", params=payload)
- if response.get("status", "") == "ok":
- if response.get("data", "").get("result", "") == 0:
- return HandlerResult(True, True, response)
- else:
- logger.error(f"事件 napcat_set_qq_profile 请求失败!err={response.get('data', '').get('errMsg', '')}")
- return HandlerResult(False, False, response)
- else:
- logger.error("事件 napcat_set_qq_profile 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetOnlineClientsHandler(BaseEventHandler):
- handler_name: str = "napcat_get_online_clients_handler"
- handler_description: str = "获取当前账号在线客户端列表"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_ONLINE_CLIENTS]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- no_cache = params.get("no_cache", False)
-
- if params.get("raw", ""):
- no_cache = raw.get("no_cache", False)
-
- payload = {"no_cache": no_cache}
- response = await send_handler.send_message_to_napcat(action="get_online_clients", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_online_clients 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetOnlineStatusHandler(BaseEventHandler):
- handler_name: str = "napcat_set_online_status_handler"
- handler_description: str = "设置在线状态"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.SET_ONLINE_STATUS]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- status = params.get("status", "")
- ext_status = params.get("ext_status", "0")
- battery_status = params.get("battery_status", "0")
-
- if params.get("raw", ""):
- status = raw.get("status", "")
- ext_status = raw.get("ext_status", "0")
- battery_status = raw.get("battery_status", "0")
-
- if not status:
- logger.error("事件 napcat_set_online_status 缺少必要参数: status")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"status": status, "ext_status": ext_status, "battery_status": battery_status}
- response = await send_handler.send_message_to_napcat(action="set_online_status", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_online_status 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetFriendsWithCategoryHandler(BaseEventHandler):
- handler_name: str = "napcat_get_friends_with_category_handler"
- handler_description: str = "获取好友分组列表"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_FRIENDS_WITH_CATEGORY]
-
- async def execute(self, params: dict):
- payload = {}
- response = await send_handler.send_message_to_napcat(action="get_friends_with_category", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_friends_with_category 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetAvatarHandler(BaseEventHandler):
- handler_name: str = "napcat_set_qq_avatar_handler"
- handler_description: str = "设置头像"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.SET_AVATAR]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- file = params.get("file", "")
-
- if params.get("raw", ""):
- file = raw.get("file", "")
-
- if not file:
- logger.error("事件 napcat_set_qq_avatar 缺少必要参数: file")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"file": file}
- response = await send_handler.send_message_to_napcat(action="set_qq_avatar", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_qq_avatar 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SendLikeHandler(BaseEventHandler):
- handler_name: str = "napcat_send_like_handler"
- handler_description: str = "点赞"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.SEND_LIKE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
- times = params.get("times", 1)
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
- times = raw.get("times", 1)
-
- if not user_id:
- logger.error("事件 napcat_send_like 缺少必要参数: user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"user_id": str(user_id), "times": times}
- response = await send_handler.send_message_to_napcat(action="send_like", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_send_like 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetFriendAddRequestHandler(BaseEventHandler):
- handler_name: str = "napcat_set_friend_add_request_handler"
- handler_description: str = "处理好友请求"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.SET_FRIEND_ADD_REQUEST]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- flag = params.get("flag", "")
- approve = params.get("approve", True)
- remark = params.get("remark", "")
-
- if params.get("raw", ""):
- flag = raw.get("flag", "")
- approve = raw.get("approve", True)
- remark = raw.get("remark", "")
-
- if not flag or approve is None or remark is None:
- logger.error("事件 napcat_set_friend_add_request 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"flag": flag, "approve": approve, "remark": remark}
- response = await send_handler.send_message_to_napcat(action="set_friend_add_request", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_friend_add_request 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetSelfLongnickHandler(BaseEventHandler):
- handler_name: str = "napcat_set_self_longnick_handler"
- handler_description: str = "设置个性签名"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.SET_SELF_LONGNICK]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- longNick = params.get("longNick", "")
-
- if params.get("raw", ""):
- longNick = raw.get("longNick", "")
-
- if not longNick:
- logger.error("事件 napcat_set_self_longnick 缺少必要参数: longNick")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"longNick": longNick}
- response = await send_handler.send_message_to_napcat(action="set_self_longnick", params=payload)
- if response.get("status", "") == "ok":
- if response.get("data", {}).get("result", "") == 0:
- return HandlerResult(True, True, response)
- else:
- logger.error(
- f"事件 napcat_set_self_longnick 请求失败!err={response.get('data', {}).get('errMsg', '')}"
- )
- return HandlerResult(False, False, response)
- else:
- logger.error("事件 napcat_set_self_longnick 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetLoginInfoHandler(BaseEventHandler):
- handler_name: str = "napcat_get_login_info_handler"
- handler_description: str = "获取登录号信息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_LOGIN_INFO]
-
- async def execute(self, params: dict):
- payload = {}
- response = await send_handler.send_message_to_napcat(action="get_login_info", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_login_info 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetRecentContactHandler(BaseEventHandler):
- handler_name: str = "napcat_get_recent_contact_handler"
- handler_description: str = "最近消息列表"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_RECENT_CONTACT]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- count = params.get("count", 20)
-
- if params.get("raw", ""):
- count = raw.get("count", 20)
-
- payload = {"count": count}
- response = await send_handler.send_message_to_napcat(action="get_recent_contact", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_recent_contact 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetStrangerInfoHandler(BaseEventHandler):
- handler_name: str = "napcat_get_stranger_info_handler"
- handler_description: str = "获取(指定)账号信息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_STRANGER_INFO]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
-
- if not user_id:
- logger.error("事件 napcat_get_stranger_info 缺少必要参数: user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"user_id": str(user_id)}
- response = await send_handler.send_message_to_napcat(action="get_stranger_info", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_stranger_info 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetFriendListHandler(BaseEventHandler):
- handler_name: str = "napcat_get_friend_list_handler"
- handler_description: str = "获取好友列表"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_FRIEND_LIST]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- no_cache = params.get("no_cache", False)
-
- if params.get("raw", ""):
- no_cache = raw.get("no_cache", False)
-
- payload = {"no_cache": no_cache}
- response = await send_handler.send_message_to_napcat(action="get_friend_list", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_friend_list 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetProfileLikeHandler(BaseEventHandler):
- handler_name: str = "napcat_get_profile_like_handler"
- handler_description: str = "获取点赞列表"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_PROFILE_LIKE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
- start = params.get("start", 0)
- count = params.get("count", 10)
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
- start = raw.get("start", 0)
- count = raw.get("count", 10)
-
- payload = {"start": start, "count": count}
- if user_id:
- payload["user_id"] = str(user_id)
-
- response = await send_handler.send_message_to_napcat(action="get_profile_like", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_profile_like 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class DeleteFriendHandler(BaseEventHandler):
- handler_name: str = "napcat_delete_friend_handler"
- handler_description: str = "删除好友"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.DELETE_FRIEND]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
- temp_block = params.get("temp_block", False)
- temp_both_del = params.get("temp_both_del", False)
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
- temp_block = raw.get("temp_block", False)
- temp_both_del = raw.get("temp_both_del", False)
-
- if not user_id or temp_block is None or temp_both_del is None:
- logger.error("事件 napcat_delete_friend 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"user_id": str(user_id), "temp_block": temp_block, "temp_both_del": temp_both_del}
- response = await send_handler.send_message_to_napcat(action="delete_friend", params=payload)
- if response.get("status", "") == "ok":
- if response.get("data", {}).get("result", "") == 0:
- return HandlerResult(True, True, response)
- else:
- logger.error(f"事件 napcat_delete_friend 请求失败!err={response.get('data', {}).get('errMsg', '')}")
- return HandlerResult(False, False, response)
- else:
- logger.error("事件 napcat_delete_friend 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetUserStatusHandler(BaseEventHandler):
- handler_name: str = "napcat_get_user_status_handler"
- handler_description: str = "获取(指定)用户状态"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_USER_STATUS]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
-
- if not user_id:
- logger.error("事件 napcat_get_user_status 缺少必要参数: user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"user_id": str(user_id)}
- response = await send_handler.send_message_to_napcat(action="get_user_status", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_user_status 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetStatusHandler(BaseEventHandler):
- handler_name: str = "napcat_get_status_handler"
- handler_description: str = "获取状态"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_STATUS]
-
- async def execute(self, params: dict):
- payload = {}
- response = await send_handler.send_message_to_napcat(action="get_status", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_status 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetMiniAppArkHandler(BaseEventHandler):
- handler_name: str = "napcat_get_mini_app_ark_handler"
- handler_description: str = "获取小程序卡片"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.GET_MINI_APP_ARK]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- type = params.get("type", "")
- title = params.get("title", "")
- desc = params.get("desc", "")
- picUrl = params.get("picUrl", "")
- jumpUrl = params.get("jumpUrl", "")
- webUrl = params.get("webUrl", "")
- rawArkData = params.get("rawArkData", False)
-
- if params.get("raw", ""):
- type = raw.get("type", "")
- title = raw.get("title", "")
- desc = raw.get("desc", "")
- picUrl = raw.get("picUrl", "")
- jumpUrl = raw.get("jumpUrl", "")
- webUrl = raw.get("webUrl", "")
- rawArkData = raw.get("rawArkData", False)
-
- if not type or not title or not desc or not picUrl or not jumpUrl:
- logger.error("事件 napcat_get_mini_app_ark 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {
- "type": type,
- "title": title,
- "desc": desc,
- "picUrl": picUrl,
- "jumpUrl": jumpUrl,
- "webUrl": webUrl,
- "rawArkData": rawArkData,
- }
- response = await send_handler.send_message_to_napcat(action="get_mini_app_ark", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_mini_app_ark 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetDiyOnlineStatusHandler(BaseEventHandler):
- handler_name: str = "napcat_set_diy_online_status_handler"
- handler_description: str = "设置自定义在线状态"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.ACCOUNT.SET_DIY_ONLINE_STATUS]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- face_id = params.get("face_id", "")
- face_type = params.get("face_type", "0")
- wording = params.get("wording", "")
-
- if params.get("raw", ""):
- face_id = raw.get("face_id", "")
- face_type = raw.get("face_type", "0")
- wording = raw.get("wording", "")
-
- if not face_id:
- logger.error("事件 napcat_set_diy_online_status 缺少必要参数: face_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"face_id": str(face_id), "face_type": str(face_type), "wording": wording}
- response = await send_handler.send_message_to_napcat(action="set_diy_online_status", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_diy_online_status 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-# ===MESSAGE===
-class SendPrivateMsgHandler(BaseEventHandler):
- handler_name: str = "napcat_send_private_msg_handler"
- handler_description: str = "发送私聊消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.SEND_PRIVATE_MSG]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
- message = params.get("message", "")
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
- message = raw.get("message", "")
-
- if not user_id or not message:
- logger.error("事件 napcat_send_private_msg 缺少必要参数: user_id 或 message")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"user_id": str(user_id), "message": message}
- response = await send_handler.send_message_to_napcat(action="send_private_msg", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_send_private_msg 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SendPokeHandler(BaseEventHandler):
- handler_name: str = "napcat_send_poke_handler"
- handler_description: str = "发送戳一戳"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.SEND_POKE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
- group_id = params.get("group_id", None)
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
- group_id = raw.get("group_id", None)
-
- if not user_id:
- logger.error("事件 napcat_send_poke 缺少必要参数: user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"user_id": str(user_id)}
- if group_id is not None:
- payload["group_id"] = str(group_id)
-
- response = await send_handler.send_message_to_napcat(action="send_poke", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_send_poke 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class DeleteMsgHandler(BaseEventHandler):
- handler_name: str = "napcat_delete_msg_handler"
- handler_description: str = "撤回消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.DELETE_MSG]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- message_id = params.get("message_id", "")
-
- if params.get("raw", ""):
- message_id = raw.get("message_id", "")
-
- if not message_id:
- logger.error("事件 napcat_delete_msg 缺少必要参数: message_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"message_id": str(message_id)}
- response = await send_handler.send_message_to_napcat(action="delete_msg", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_delete_msg 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupMsgHistoryHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_msg_history_handler"
- handler_description: str = "获取群历史消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.GET_GROUP_MSG_HISTORY]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- message_seq = params.get("message_seq", 0)
- count = params.get("count", 20)
- reverseOrder = params.get("reverseOrder", False)
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- message_seq = raw.get("message_seq", 0)
- count = raw.get("count", 20)
- reverseOrder = raw.get("reverseOrder", False)
-
- if not group_id:
- logger.error("事件 napcat_get_group_msg_history 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {
- "group_id": str(group_id),
- "message_seq": int(message_seq),
- "count": int(count),
- "reverseOrder": bool(reverseOrder),
- }
- response = await send_handler.send_message_to_napcat(action="get_group_msg_history", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_msg_history 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetMsgHandler(BaseEventHandler):
- handler_name: str = "napcat_get_msg_handler"
- handler_description: str = "获取消息详情"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.GET_MSG]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- message_id = params.get("message_id", "")
-
- if params.get("raw", ""):
- message_id = raw.get("message_id", "")
-
- if not message_id:
- logger.error("事件 napcat_get_msg 缺少必要参数: message_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"message_id": str(message_id)}
- response = await send_handler.send_message_to_napcat(action="get_msg", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_msg 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetForwardMsgHandler(BaseEventHandler):
- handler_name: str = "napcat_get_forward_msg_handler"
- handler_description: str = "获取合并转发消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.GET_FORWARD_MSG]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- message_id = params.get("message_id", "")
-
- if params.get("raw", ""):
- message_id = raw.get("message_id", "")
-
- if not message_id:
- logger.error("事件 napcat_get_forward_msg 缺少必要参数: message_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"message_id": str(message_id)}
- response = await send_handler.send_message_to_napcat(action="get_forward_msg", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_forward_msg 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetMsgEmojiLikeHandler(BaseEventHandler):
- handler_name: str = "napcat_set_msg_emoji_like_handler"
- handler_description: str = "贴表情"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.SET_MSG_EMOJI_LIKE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- message_id = params.get("message_id", "")
- emoji_id = params.get("emoji_id", 0)
- set_flag = params.get("set", True)
-
- if params.get("raw", ""):
- message_id = raw.get("message_id", "")
- emoji_id = raw.get("emoji_id", 0)
- set_flag = raw.get("set", True)
-
- if not message_id or emoji_id is None or set_flag is None:
- logger.error("事件 napcat_set_msg_emoji_like 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"message_id": str(message_id), "emoji_id": int(emoji_id), "set": bool(set_flag)}
- response = await send_handler.send_message_to_napcat(action="set_msg_emoji_like", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_msg_emoji_like 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetFriendMsgHistoryHandler(BaseEventHandler):
- handler_name: str = "napcat_get_friend_msg_history_handler"
- handler_description: str = "获取好友历史消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.GET_FRIEND_MSG_HISTORY]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
- message_seq = params.get("message_seq", 0)
- count = params.get("count", 20)
- reverseOrder = params.get("reverseOrder", False)
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
- message_seq = raw.get("message_seq", 0)
- count = raw.get("count", 20)
- reverseOrder = raw.get("reverseOrder", False)
-
- if not user_id:
- logger.error("事件 napcat_get_friend_msg_history 缺少必要参数: user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {
- "user_id": str(user_id),
- "message_seq": int(message_seq),
- "count": int(count),
- "reverseOrder": bool(reverseOrder),
- }
- response = await send_handler.send_message_to_napcat(action="get_friend_msg_history", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_friend_msg_history 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class FetchEmojiLikeHandler(BaseEventHandler):
- handler_name: str = "napcat_fetch_emoji_like_handler"
- handler_description: str = "获取贴表情详情"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.FETCH_EMOJI_LIKE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- message_id = params.get("message_id", "")
- emoji_id = params.get("emoji_id", "")
- emoji_type = params.get("emoji_type", "")
- count = params.get("count", 20)
-
- if params.get("raw", ""):
- message_id = raw.get("message_id", "")
- emoji_id = raw.get("emoji_id", "")
- emoji_type = raw.get("emoji_type", "")
- count = raw.get("count", 20)
-
- if not message_id or not emoji_id or not emoji_type:
- logger.error("事件 napcat_fetch_emoji_like 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {
- "message_id": str(message_id),
- "emojiId": str(emoji_id),
- "emojiType": str(emoji_type),
- "count": int(count),
- }
- response = await send_handler.send_message_to_napcat(action="fetch_emoji_like", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_fetch_emoji_like 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SendForwardMsgHandler(BaseEventHandler):
- handler_name: str = "napcat_send_forward_msg_handler"
- handler_description: str = "发送合并转发消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.SEND_FORWARD_MSG]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- messages = params.get("messages", {})
- news = params.get("news", {})
- prompt = params.get("prompt", "")
- summary = params.get("summary", "")
- source = params.get("source", "")
- group_id = params.get("group_id", None)
- user_id = params.get("user_id", None)
-
- if params.get("raw", ""):
- messages = raw.get("messages", {})
- news = raw.get("news", {})
- prompt = raw.get("prompt", "")
- summary = raw.get("summary", "")
- source = raw.get("source", "")
- group_id = raw.get("group_id", None)
- user_id = raw.get("user_id", None)
-
- if not messages or not news or not prompt or not summary or not source:
- logger.error("事件 napcat_send_forward_msg 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"messages": messages, "news": news, "prompt": prompt, "summary": summary, "source": source}
- if group_id is not None:
- payload["group_id"] = str(group_id)
- if user_id is not None:
- payload["user_id"] = str(user_id)
-
- response = await send_handler.send_message_to_napcat(action="send_forward_msg", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_send_forward_msg 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SendGroupAiRecordHandler(BaseEventHandler):
- handler_name: str = "napcat_send_group_ai_record_handler"
- handler_description: str = "发送群AI语音"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.MESSAGE.SEND_GROUP_AI_RECORD]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- character = params.get("character", "")
- text = params.get("text", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- character = raw.get("character", "")
- text = raw.get("text", "")
-
- if not group_id or not character or not text:
- logger.error("事件 napcat_send_group_ai_record 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "character": character, "text": text}
- response = await send_handler.send_message_to_napcat(action="send_group_ai_record", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_send_group_ai_record 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-# ===GROUP===
-class GetGroupInfoHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_info_handler"
- handler_description: str = "获取群信息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_INFO]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
-
- if not group_id:
- logger.error("事件 napcat_get_group_info 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- response = await send_handler.send_message_to_napcat(action="get_group_info", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_info 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupAddOptionHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_add_option_handler"
- handler_description: str = "设置群添加选项"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_ADD_OPTION]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- add_type = params.get("add_type", "")
- group_question = params.get("group_question", "")
- group_answer = params.get("group_answer", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- add_type = raw.get("add_type", "")
- group_question = raw.get("group_question", "")
- group_answer = raw.get("group_answer", "")
-
- if not group_id or not add_type:
- logger.error("事件 napcat_set_group_add_option 缺少必要参数: group_id 或 add_type")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "add_type": str(add_type)}
- if group_question:
- payload["group_question"] = group_question
- if group_answer:
- payload["group_answer"] = group_answer
-
- response = await send_handler.send_message_to_napcat(action="set_group_add_option", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_add_option 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupKickMembersHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_kick_members_handler"
- handler_description: str = "批量踢出群成员"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_KICK_MEMBERS]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- user_id = params.get("user_id", [])
- reject_add_request = params.get("reject_add_request", False)
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- user_id = raw.get("user_id", [])
- reject_add_request = raw.get("reject_add_request", False)
-
- if not group_id or not user_id:
- logger.error("事件 napcat_set_group_kick_members 缺少必要参数: group_id 或 user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "user_id": user_id, "reject_add_request": bool(reject_add_request)}
- response = await send_handler.send_message_to_napcat(action="set_group_kick_members", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_kick_members 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupRemarkHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_remark_handler"
- handler_description: str = "设置群备注"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_REMARK]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- remark = params.get("remark", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- remark = raw.get("remark", "")
-
- if not group_id or not remark:
- logger.error("事件 napcat_set_group_remark 缺少必要参数: group_id 或 remark")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "remark": remark}
- response = await send_handler.send_message_to_napcat(action="set_group_remark", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_remark 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupKickHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_kick_handler"
- handler_description: str = "群踢人"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_KICK]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- user_id = params.get("user_id", "")
- reject_add_request = params.get("reject_add_request", False)
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- user_id = raw.get("user_id", "")
- reject_add_request = raw.get("reject_add_request", False)
-
- if not group_id or not user_id:
- logger.error("事件 napcat_set_group_kick 缺少必要参数: group_id 或 user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "user_id": str(user_id), "reject_add_request": bool(reject_add_request)}
- response = await send_handler.send_message_to_napcat(action="set_group_kick", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_kick 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupSystemMsgHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_system_msg_handler"
- handler_description: str = "获取群系统消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_SYSTEM_MSG]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- count = params.get("count", 20)
-
- if params.get("raw", ""):
- count = raw.get("count", 20)
-
- if count is None:
- logger.error("事件 napcat_get_group_system_msg 缺少必要参数: count")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"count": int(count)}
- response = await send_handler.send_message_to_napcat(action="get_group_system_msg", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_system_msg 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupBanHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_ban_handler"
- handler_description: str = "群禁言"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_BAN]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- user_id = params.get("user_id", "")
- duration = params.get("duration", 0)
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- user_id = raw.get("user_id", "")
- duration = raw.get("duration", 0)
-
- if not group_id or not user_id or duration is None:
- logger.error("事件 napcat_set_group_ban 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "user_id": str(user_id), "duration": int(duration)}
- response = await send_handler.send_message_to_napcat(action="set_group_ban", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_ban 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetEssenceMsgListHandler(BaseEventHandler):
- handler_name: str = "napcat_get_essence_msg_list_handler"
- handler_description: str = "获取群精华消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_ESSENCE_MSG_LIST]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
-
- if not group_id:
- logger.error("事件 napcat_get_essence_msg_list 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- response = await send_handler.send_message_to_napcat(action="get_essence_msg_list", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_essence_msg_list 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupWholeBanHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_whole_ban_handler"
- handler_description: str = "全体禁言"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_WHOLE_BAN]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- enable = params.get("enable", True)
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- enable = raw.get("enable", True)
-
- if not group_id or enable is None:
- logger.error("事件 napcat_set_group_whole_ban 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "enable": bool(enable)}
- response = await send_handler.send_message_to_napcat(action="set_group_whole_ban", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_whole_ban 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupPortraitHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_portrait_handler"
- handler_description: str = "设置群头像"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_PORTRAINT]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- file_path = params.get("file", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- file_path = raw.get("file", "")
-
- if not group_id or not file_path:
- logger.error("事件 napcat_set_group_portrait 缺少必要参数: group_id 或 file")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "file": file_path}
- response = await send_handler.send_message_to_napcat(action="set_group_portrait", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_portrait 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupAdminHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_admin_handler"
- handler_description: str = "设置群管理"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_ADMIN]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- user_id = params.get("user_id", "")
- enable = params.get("enable", True)
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- user_id = raw.get("user_id", "")
- enable = raw.get("enable", True)
-
- if not group_id or not user_id or enable is None:
- logger.error("事件 napcat_set_group_admin 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "user_id": str(user_id), "enable": bool(enable)}
- response = await send_handler.send_message_to_napcat(action="set_group_admin", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_admin 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupCardHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_card_handler"
- handler_description: str = "设置群成员名片"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_CARD]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- user_id = params.get("user_id", "")
- card = params.get("card", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- user_id = raw.get("user_id", "")
- card = raw.get("card", "")
-
- if not group_id or not user_id:
- logger.error("事件 napcat_set_group_card 缺少必要参数: group_id 或 user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "user_id": str(user_id)}
- if card:
- payload["card"] = card
-
- response = await send_handler.send_message_to_napcat(action="group_card", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_card 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetEssenceMsgHandler(BaseEventHandler):
- handler_name: str = "napcat_set_essence_msg_handler"
- handler_description: str = "设置群精华消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_ESSENCE_MSG]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- message_id = params.get("message_id", "")
-
- if params.get("raw", ""):
- message_id = raw.get("message_id", "")
-
- if not message_id:
- logger.error("事件 napcat_set_essence_msg 缺少必要参数: message_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"message_id": str(message_id)}
- response = await send_handler.send_message_to_napcat(action="set_essence_msg", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_essence_msg 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupNameHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_name_handler"
- handler_description: str = "设置群名"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_NAME]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- group_name = params.get("group_name", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- group_name = raw.get("group_name", "")
-
- if not group_id or not group_name:
- logger.error("事件 napcat_set_group_name 缺少必要参数: group_id 或 group_name")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "group_name": group_name}
- response = await send_handler.send_message_to_napcat(action="set_group_name", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_name 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class DeleteEssenceMsgHandler(BaseEventHandler):
- handler_name: str = "napcat_delete_essence_msg_handler"
- handler_description: str = "删除群精华消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.DELETE_ESSENCE_MSG]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- message_id = params.get("message_id", "")
-
- if params.get("raw", ""):
- message_id = raw.get("message_id", "")
-
- if not message_id:
- logger.error("事件 napcat_delete_essence_msg 缺少必要参数: message_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"message_id": str(message_id)}
- response = await send_handler.send_message_to_napcat(action="delete_essence_msg", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_delete_essence_msg 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupLeaveHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_leave_handler"
- handler_description: str = "退群"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_LEAVE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
-
- if not group_id:
- logger.error("事件 napcat_set_group_leave 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- response = await send_handler.send_message_to_napcat(action="set_group_leave", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_leave 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SendGroupNoticeHandler(BaseEventHandler):
- handler_name: str = "napcat_send_group_notice_handler"
- handler_description: str = "发送群公告"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SEND_GROUP_NOTICE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- content = params.get("content", "")
- image = params.get("image", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- content = raw.get("content", "")
- image = raw.get("image", "")
-
- if not group_id or not content:
- logger.error("事件 napcat_send_group_notice 缺少必要参数: group_id 或 content")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "content": content}
- if image:
- payload["image"] = image
-
- response = await send_handler.send_message_to_napcat(action="_send_group_notice", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_send_group_notice 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupSpecialTitleHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_special_title_handler"
- handler_description: str = "设置群头衔"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_SPECIAL_TITLE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- user_id = params.get("user_id", "")
- special_title = params.get("special_title", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- user_id = raw.get("user_id", "")
- special_title = raw.get("special_title", "")
-
- if not group_id or not user_id:
- logger.error("事件 napcat_set_group_special_title 缺少必要参数: group_id 或 user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "user_id": str(user_id)}
- if special_title:
- payload["special_title"] = special_title
-
- response = await send_handler.send_message_to_napcat(action="set_group_special_title", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_special_title 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupNoticeHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_notice_handler"
- handler_description: str = "获取群公告"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_NOTICE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
-
- if not group_id:
- logger.error("事件 napcat_get_group_notice 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- response = await send_handler.send_message_to_napcat(action="_get_group_notice", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_notice 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupAddRequestHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_add_request_handler"
- handler_description: str = "处理加群请求"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_ADD_REQUEST]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- flag = params.get("flag", "")
- approve = params.get("approve", True)
- reason = params.get("reason", "")
-
- if params.get("raw", ""):
- flag = raw.get("flag", "")
- approve = raw.get("approve", True)
- reason = raw.get("reason", "")
-
- if not flag or approve is None:
- logger.error("事件 napcat_set_group_add_request 缺少必要参数")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"flag": flag, "approve": bool(approve)}
- if reason:
- payload["reason"] = reason
-
- response = await send_handler.send_message_to_napcat(action="set_group_add_request", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_add_request 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupListHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_list_handler"
- handler_description: str = "获取群列表"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_LIST]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- no_cache = params.get("no_cache", False)
-
- if params.get("raw", ""):
- no_cache = raw.get("no_cache", False)
-
- payload = {"no_cache": bool(no_cache)}
- response = await send_handler.send_message_to_napcat(action="get_group_list", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_list 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class DeleteGroupNoticeHandler(BaseEventHandler):
- handler_name: str = "napcat_del_group_notice_handler"
- handler_description: str = "删除群公告"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.DELETE_GROUP_NOTICE]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- notice_id = params.get("notice_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- notice_id = raw.get("notice_id", "")
-
- if not group_id or not notice_id:
- logger.error("事件 napcat_del_group_notice 缺少必要参数: group_id 或 notice_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "notice_id": notice_id}
- response = await send_handler.send_message_to_napcat(action="_del_group_notice", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_del_group_notice 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupMemberInfoHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_member_info_handler"
- handler_description: str = "获取群成员信息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_MEMBER_INFO]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- user_id = params.get("user_id", "")
- no_cache = params.get("no_cache", False)
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- user_id = raw.get("user_id", "")
- no_cache = raw.get("no_cache", False)
-
- if not group_id or not user_id:
- logger.error("事件 napcat_get_group_member_info 缺少必要参数: group_id 或 user_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "user_id": str(user_id), "no_cache": bool(no_cache)}
- response = await send_handler.send_message_to_napcat(action="get_group_member_info", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_member_info 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupMemberListHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_member_list_handler"
- handler_description: str = "获取群成员列表"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_MEMBER_LIST]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- no_cache = params.get("no_cache", False)
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- no_cache = raw.get("no_cache", False)
-
- if not group_id:
- logger.error("事件 napcat_get_group_member_list 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id), "no_cache": bool(no_cache)}
- response = await send_handler.send_message_to_napcat(action="get_group_member_list", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_member_list 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupHonorInfoHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_honor_info_handler"
- handler_description: str = "获取群荣誉"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_HONOR_INFO]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
- type = params.get("type", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
- type = raw.get("type", "")
-
- if not group_id:
- logger.error("事件 napcat_get_group_honor_info 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- if type:
- payload["type"] = type
-
- response = await send_handler.send_message_to_napcat(action="get_group_honor_info", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_honor_info 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupInfoExHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_info_ex_handler"
- handler_description: str = "获取群信息ex"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_INFO_EX]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
-
- if not group_id:
- logger.error("事件 napcat_get_group_info_ex 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- response = await send_handler.send_message_to_napcat(action="get_group_info_ex", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_info_ex 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupAtAllRemainHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_at_all_remain_handler"
- handler_description: str = "获取群 @全体成员 剩余次数"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_AT_ALL_REMAIN]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
-
- if not group_id:
- logger.error("事件 napcat_get_group_at_all_remain 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- response = await send_handler.send_message_to_napcat(action="get_group_at_all_remain", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_at_all_remain 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupShutListHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_shut_list_handler"
- handler_description: str = "获取群禁言列表"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_SHUT_LIST]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
-
- if not group_id:
- logger.error("事件 napcat_get_group_shut_list 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- response = await send_handler.send_message_to_napcat(action="get_group_shut_list", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_shut_list 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class GetGroupIgnoredNotifiesHandler(BaseEventHandler):
- handler_name: str = "napcat_get_group_ignored_notifies_handler"
- handler_description: str = "获取群过滤系统消息"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.GET_GROUP_IGNORED_NOTIFIES]
-
- async def execute(self, params: dict):
- payload = {}
- response = await send_handler.send_message_to_napcat(action="get_group_ignored_notifies", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_get_group_ignored_notifies 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-class SetGroupSignHandler(BaseEventHandler):
- handler_name: str = "napcat_set_group_sign_handler"
- handler_description: str = "群打卡"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.GROUP.SET_GROUP_SIGN]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- group_id = params.get("group_id", "")
-
- if params.get("raw", ""):
- group_id = raw.get("group_id", "")
-
- if not group_id:
- logger.error("事件 napcat_set_group_sign 缺少必要参数: group_id")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"group_id": str(group_id)}
- response = await send_handler.send_message_to_napcat(action="set_group_sign", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_group_sign 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
-
-
-# ===PERSONAL===
-class SetInputStatusHandler(BaseEventHandler):
- handler_name: str = "napcat_set_input_status_handler"
- handler_description: str = "设置输入状态"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [NapcatEvent.PERSONAL.SET_INPUT_STATUS]
-
- async def execute(self, params: dict):
- raw = params.get("raw", {})
- user_id = params.get("user_id", "")
- event_type = params.get("event_type", 0)
-
- if params.get("raw", ""):
- user_id = raw.get("user_id", "")
- event_type = raw.get("event_type", 0)
-
- if not user_id or event_type is None:
- logger.error("事件 napcat_set_input_status 缺少必要参数: user_id 或 event_type")
- return HandlerResult(False, False, {"status": "error"})
-
- payload = {"user_id": str(user_id), "event_type": int(event_type)}
- response = await send_handler.send_message_to_napcat(action="set_input_status", params=payload)
- if response.get("status", "") == "ok":
- return HandlerResult(True, True, response)
- else:
- logger.error("事件 napcat_set_input_status 请求失败!")
- return HandlerResult(False, False, {"status": "error"})
diff --git a/src/plugins/built_in/napcat_adapter_plugin/event_types.py b/src/plugins/built_in/napcat_adapter_plugin/event_types.py
deleted file mode 100644
index 08ef35598..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/event_types.py
+++ /dev/null
@@ -1,1844 +0,0 @@
-from enum import Enum
-
-
-class NapcatEvent:
- """
- napcat插件事件枚举类
- """
-
- class ON_RECEIVED(Enum):
- """
- 该分类下均为消息接受事件,只能由napcat_plugin触发
- """
-
- TEXT = "napcat_on_received_text"
- """接收到文本消息"""
- FACE = "napcat_on_received_face"
- """接收到表情消息"""
- REPLY = "napcat_on_received_reply"
- """接收到回复消息"""
- IMAGE = "napcat_on_received_image"
- """接收到图像消息"""
- RECORD = "napcat_on_received_record"
- """接收到语音消息"""
- VIDEO = "napcat_on_received_video"
- """接收到视频消息"""
- AT = "napcat_on_received_at"
- """接收到at消息"""
- DICE = "napcat_on_received_dice"
- """接收到骰子消息"""
- SHAKE = "napcat_on_received_shake"
- """接收到屏幕抖动消息"""
- JSON = "napcat_on_received_json"
- """接收到JSON消息"""
- RPS = "napcat_on_received_rps"
- """接收到魔法猜拳消息"""
- FRIEND_INPUT = "napcat_on_friend_input"
- """好友正在输入"""
- EMOJI_LIEK = "napcat_on_received_emoji_like"
- """接收到群聊表情回复"""
-
- class ACCOUNT(Enum):
- """
- 该分类是对账户相关的操作,只能由外部触发,napcat_plugin负责处理
- """
-
- SET_PROFILE = "napcat_set_qq_profile"
- """设置账号信息
-
- Args:
- nickname (Optional[str]): 名称(必须)
- personal_note (Optional[str]): 个性签名
- sex ('0'|'1'|'2'): 性别
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "result": 0,
- "errMsg": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
-
- """
- GET_ONLINE_CLIENTS = "napcat_get_online_clients"
- """获取当前账号在线客户端列表
-
- Args:
- no_cache (Optional[bool]): 是否不使用缓存
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- "string"
- ],
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_ONLINE_STATUS = "napcat_set_online_status"
- """设置在线状态
-
- Args:
- status (Optional[str]): 状态代码(必须)
- ext_status (Optional[str]): 额外状态代码,默认为0
- battery_status (Optional[str]): 电池信息,默认为0
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_FRIENDS_WITH_CATEGORY = "napcat_get_friends_with_category"
- """获取好友分组列表
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- {
- "categoryId": 0,
- "categorySortId": 0,
- "categoryName": "string",
- "categoryMbCount": 0,
- "onlineCount": 0,
- "buddyList": [
- {
- "birthday_year": 0,
- "birthday_month": 0,
- "birthday_day": 0,
- "user_id": 0,
- "age": 0,
- "phone_num": "string",
- "email": "string",
- "category_id": 0,
- "nickname": "string",
- "remark": "string",
- "sex": "string",
- "level": 0
- }
- ]
- }
- ],
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_AVATAR = "napcat_set_qq_avatar"
- """设置头像
-
- Args:
- file (Optional[str]): 文件路径或base64(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SEND_LIKE = "napcat_send_like"
- """点赞
-
- Args:
- user_id (Optional[str|int]): 用户id(必需)
- times (Optional[int]): 点赞次数,默认1
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_FRIEND_ADD_REQUEST = "napcat_set_friend_add_request"
- """处理好友请求
-
- Args:
- flag (Optional[str]): 请求id(必需)
- approve (Optional[bool]): 是否同意(必需)
- remark (Optional[str]): 好友备注(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_SELF_LONGNICK = "napcat_set_self_longnick"
- """设置个性签名
-
- Args:
- longNick (Optional[str]): 内容(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "result": 0,
- "errMsg": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_LOGIN_INFO = "napcat_get_login_info"
- """获取登录号信息
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "user_id": 0,
- "nickname": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_RECENT_CONTACT = "napcat_get_recent_contact"
- """最近消息列表
-
- Args:
- count (Optional[int]): 会话数量
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- {
- "lastestMsg": {
- "self_id": 0,
- "user_id": 0,
- "time": 0,
- "real_seq": "string",
- "message_type": "string",
- "sender": {
- "user_id": 0,
- "nickname": "string",
- "sex": "male",
- "age": 0,
- "card": "string",
- "role": "owner"
- },
- "raw_message": "string",
- "font": 0,
- "sub_type": "string",
- "message": [
- {
- "type": "text",
- "data": {
- "text": "string"
- }
- }
- ],
- "message_format": "string",
- "post_type": "string",
- "group_id": 0
- },
- "peerUin": "string",
- "remark": "string",
- "msgTime": "string",
- "chatType": 0,
- "msgId": "string",
- "sendNickName": "string",
- "sendMemberName": "string",
- "peerName": "string"
- }
- ],
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_STRANGER_INFO = "napcat_get_stranger_info"
- """获取(指定)账号信息
-
- Args:
- user_id (Optional[str|int]): 用户id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "user_id": 0,
- "uid": "string",
- "uin": "string",
- "nickname": "string",
- "age": 0,
- "qid": "string",
- "qqLevel": 0,
- "sex": "string",
- "long_nick": "string",
- "reg_time": 0,
- "is_vip": true,
- "is_years_vip": true,
- "vip_level": 0,
- "remark": "string",
- "status": 0,
- "login_days": 0
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_FRIEND_LIST = "napcat_get_friend_list"
- """获取好友列表
-
- Args:
- no_cache (Optional[bool]): 是否不使用缓存
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- {
- "birthday_year": 0,
- "birthday_month": 0,
- "birthday_day": 0,
- "user_id": 0,
- "age": 0,
- "phone_num": "string",
- "email": "string",
- "category_id": 0,
- "nickname": "string",
- "remark": "string",
- "sex": "string",
- "level": 0
- }
- ],
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_PROFILE_LIKE = "napcat_get_profile_like"
- """获取点赞列表
-
- Args:
- user_id (Optional[str|int]): 用户id,指定用户,不填为获取所有
- start (Optional[int]): 起始值
- count (Optional[int]): 返回数量
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "uid": "string",
- "time": 0,
- "favoriteInfo": {
- "total_count": 0,
- "last_time": 0,
- "today_count": 0,
- "userInfos": [
- {
- "uid": "string",
- "src": 0,
- "latestTime": 0,
- "count": 0,
- "giftCount": 0,
- "customId": 0,
- "lastCharged": 0,
- "bAvailableCnt": 0,
- "bTodayVotedCnt": 0,
- "nick": "string",
- "gender": 0,
- "age": 0,
- "isFriend": true,
- "isvip": true,
- "isSvip": true,
- "uin": 0
- }
- ]
- },
- "voteInfo": {
- "total_count": 0,
- "new_count": 0,
- "new_nearby_count": 0,
- "last_visit_time": 0,
- "userInfos": [
- {
- "uid": "string",
- "src": 0,
- "latestTime": 0,
- "count": 0,
- "giftCount": 0,
- "customId": 0,
- "lastCharged": 0,
- "bAvailableCnt": 0,
- "bTodayVotedCnt": 0,
- "nick": "string",
- "gender": 0,
- "age": 0,
- "isFriend": true,
- "isvip": true,
- "isSvip": true,
- "uin": 0
- }
- ]
- }
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- DELETE_FRIEND = "napcat_delete_friend"
- """删除好友
-
- Args:
- user_id (Optional[str|int]): 用户id(必需)
- temp_block (Optional[bool]): 拉黑(必需)
- temp_both_del (Optional[bool]): 双向删除(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "result": 0,
- "errMsg": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_USER_STATUS = "napcat_get_user_status"
- """获取(指定)用户状态
-
- Args:
- user_id (Optional[str|int]): 用户id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "status": 0,
- "ext_status": 0
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_STATUS = "napcat_get_status"
- """获取状态
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "online": true,
- "good": true,
- "stat": {}
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_MINI_APP_ARK = "napcat_get_mini_app_ark"
- """获取小程序卡片
-
- Args:
- type (Optional[str]): 类型(如bili、weibo,必需)
- title (Optional[str]): 标题(必需)
- desc (Optional[str]): 描述(必需)
- picUrl (Optional[str]): 图片URL(必需)
- jumpUrl (Optional[str]): 跳转URL(必需)
- webUrl (Optional[str]): 网页URL
- rawArkData (Optional[bool]): 是否返回原始ark数据
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "appName": "string",
- "appView": "string",
- "ver": "string",
- "desc": "string",
- "prompt": "string",
- "metaData": {
- "detail_1": {
- "appid": "string",
- "appType": 0,
- "title": "string",
- "desc": "string",
- "icon": "string",
- "preview": "string",
- "url": "string",
- "scene": 0,
- "host": {
- "uin": 0,
- "nick": "string"
- },
- "shareTemplateId": "string",
- "shareTemplateData": {},
- "showLittleTail": "string",
- "gamePoints": "string",
- "gamePointsUrl": "string",
- "shareOrigin": 0
- }
- },
- "config": {
- "type": "string",
- "width": 0,
- "height": 0,
- "forward": 0,
- "autoSize": 0,
- "ctime": 0,
- "token": "string"
- }
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_DIY_ONLINE_STATUS = "napcat_set_diy_online_status"
- """设置自定义在线状态
-
- Args:
- face_id (Optional[str|int]): 表情ID(必需)
- face_type (Optional[str|int]): 表情类型
- wording (Optional[str]):描述文本
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": "string",
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
-
- class MESSAGE(Enum):
- """
- 该分类是对信息相关的操作,只能由外部触发,napcat_plugin负责处理
- """
-
- SEND_PRIVATE_MSG = "napcat_send_private_msg"
- """发送私聊消息
-
- Args:
- user_id (Optional[str|int]): 用户id(必需)
- message (Optional[str]): 消息object(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "message_id": 0
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SEND_POKE = "napcat_send_poke"
- """发送戳一戳
-
- Args:
- group_id (Optional[str|int]): 群号
- user_id (Optional[str|int]): 对方QQ号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- DELETE_MSG = "napcat_delete_msg"
- """撤回消息
-
- Args:
- message_id (Optional[str|int]): 消息id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_MSG_HISTORY = "napcat_get_group_msg_history"
- """获取群历史消息
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- message_seq (Optional[str|int]): 消息序号,0为最新
- count (Optional[int]): 获取数量
- reverseOrder (Optional[bool]): 是否倒序
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "messages": [
- {
- "self_id": 0,
- "user_id": 0,
- "time": 0,
- "message_id": 0,
- "message_seq": 0,
- "real_id": 0,
- "real_seq": "string",
- "message_type": "string",
- "sender": {
- "user_id": 0,
- "nickname": "string",
- "sex": "male",
- "age": 0,
- "card": "string",
- "role": "owner"
- },
- "raw_message": "string",
- "font": 0,
- "sub_type": "string",
- "message": [
- {
- "type": "text",
- "data": {
- "text": "string"
- }
- }
- ],
- "message_format": "string",
- "post_type": "string",
- "group_id": 0
- }
- ]
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_MSG = "napcat_get_msg"
- """获取消息详情
-
- Args:
- message_id (Optional[str|int]): 消息id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "self_id": 0,
- "user_id": 0,
- "time": 0,
- "message_id": 0,
- "message_seq": 0,
- "real_id": 0,
- "real_seq": "string",
- "message_type": "string",
- "sender": {
- "user_id": 0,
- "nickname": "string",
- "sex": "male",
- "age": 0,
- "card": "string",
- "role": "owner"
- },
- "raw_message": "string",
- "font": 0,
- "sub_type": "string",
- "message": [
- {
- "type": "text",
- "data": {
- "text": "string"
- }
- }
- ],
- "message_format": "string",
- "post_type": "string",
- "group_id": 0
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_FORWARD_MSG = "napcat_get_forward_msg"
- """获取合并转发消息
-
- Args:
- message_id (Optional[str|int]): 消息id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "messages": [
- {
- "self_id": 0,
- "user_id": 0,
- "time": 0,
- "message_id": 0,
- "message_seq": 0,
- "real_id": 0,
- "real_seq": "string",
- "message_type": "string",
- "sender": {
- "user_id": 0,
- "nickname": "string",
- "sex": "male",
- "age": 0,
- "card": "string",
- "role": "owner"
- },
- "raw_message": "string",
- "font": 0,
- "sub_type": "string",
- "message": [
- {
- "type": "text",
- "data": {
- "text": "string"
- }
- }
- ],
- "message_format": "string",
- "post_type": "string",
- "group_id": 0
- }
- ]
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_MSG_EMOJI_LIKE = "napcat_set_msg_emoji_like"
- """贴表情
-
- Args:
- message_id (Optional[str|int]): 消息id(必需)
- emoji_id (Optional[int]): 表情id(必需)
- set (Optional[bool]): 是否贴(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "result": 0,
- "errMsg": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_FRIEND_MSG_HISTORY = "napcat_get_friend_msg_history"
- """获取好友历史消息
-
- Args:
- user_id (Optional[str|int]): 用户id(必需)
- message_seq (Optional[str|int]): 消息序号,0为最新
- count (Optional[int]): 获取数量
- reverseOrder (Optional[bool]): 是否倒序
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "messages": [
- {
- "self_id": 0,
- "user_id": 0,
- "time": 0,
- "message_id": 0,
- "message_seq": 0,
- "real_id": 0,
- "real_seq": "string",
- "message_type": "string",
- "sender": {
- "user_id": 0,
- "nickname": "string",
- "sex": "male",
- "age": 0,
- "card": "string",
- "role": "owner"
- },
- "raw_message": "string",
- "font": 0,
- "sub_type": "string",
- "message": [
- {
- "type": "text",
- "data": {
- "text": "string"
- }
- }
- ],
- "message_format": "string",
- "post_type": "string",
- "group_id": 0
- }
- ]
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- FETCH_EMOJI_LIKE = "napcat_fetch_emoji_like"
- """获取贴表情详情
-
- Args:
- message_id (Optional[str|int]): 消息id(必需)
- emojiId (Optional[str]): 表情id(必需)
- emojiType (Optional[str]): 表情类型(必需)
- count (Optional[int]): 返回数量
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "result": 0,
- "errMsg": "string",
- "emojiLikesList": [
- {
- "tinyId": "string",
- "nickName": "string",
- "headUrl": "string"
- }
- ],
- "cookie": "string",
- "isLastPage": true,
- "isFirstPage": true
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SEND_FORWARD_MSG = "napcat_send_forward_msg"
- """发送合并转发消息
-
- Args:
- group_id (Optional[str|int]): 群号
- user_id (Optional[str|int]): 用户id
- messages (Optional[dict]): 一级合并转发消息节点(必需)
- news (Optional[dict]): 原转发消息之外的消息(必需)
- prompt (Optional[str]): 外显(必需)
- summary (Optional[str]): 底下文本(必需)
- source (Optional[str]): 内容(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {},
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SEND_GROUP_AI_RECORD = "napcat_send_group_ai_record"
- """发送群AI语音
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- character (Optional[str]): 角色id(必需)
- text (Optional[str]): 文本(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "message_id": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
-
- class GROUP(Enum):
- """
- 该分类是对群聊相关的操作,只能由外部触发,napcat_plugin负责处理
- """
-
- GET_GROUP_INFO = "napcat_get_group_info"
- """获取群信息
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "group_all_shut": 0,
- "group_remark": "string",
- "group_id": "string",
- "group_name": "string",
- "member_count": 0,
- "max_member_count": 0
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_ADD_OPTION = "napcat_set_group_add_option"
- """设置群添加选项
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- add_type (Optional[str]): 群添加类型(必需)
- group_question (Optional[str]): 群添加问题
- group_answer (Optional[str]): 群添加答案
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_KICK_MEMBERS = "napcat_set_group_kick_members"
- """批量踢出群成员
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- user_id (Optional[List[str|int]]): 用户id列表(必需)
- reject_add_request (Optional[bool]): 是否群拉黑
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_REMARK = "napcat_set_group_remark"
- """设置群备注
-
- Args:
- group_id (Optional[str]): 群号(必需)
- remark (Optional[str]): 备注内容(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_KICK = "napcat_set_group_kick"
- """群踢人
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- user_id (Optional[str|int]): 用户id(必需)
- reject_add_request (Optional[bool]): 是否群拉黑
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_SYSTEM_MSG = "napcat_get_group_system_msg"
- """获取群系统消息
-
- Args:
- count (Optional[int]): 获取数量(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "InvitedRequest": [
- {
- "request_id": 0,
- "invitor_uin": 0,
- "invitor_nick": "string",
- "group_id": 0,
- "message": "string",
- "group_name": "string",
- "checked": true,
- "actor": 0,
- "requester_nick": "string"
- }
- ],
- "join_requests": [
- {
- "request_id": 0,
- "invitor_uin": 0,
- "invitor_nick": "string",
- "group_id": 0,
- "message": "string",
- "group_name": "string",
- "checked": true,
- "actor": 0,
- "requester_nick": "string"
- }
- ]
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_BAN = "napcat_set_group_ban"
- """群禁言
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- user_id (Optional[str|int]): 用户id(必需)
- duration (Optional[int]): 禁言时间:秒(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_ESSENCE_MSG_LIST = "napcat_get_essence_msg_list"
- """获取群精华消息
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- {
- "msg_seq": 0,
- "msg_random": 0,
- "sender_id": 0,
- "sender_nick": "string",
- "operator_id": 0,
- "operator_nick": "string",
- "message_id": 0,
- "operator_time": 0,
- "content": [
- {
- "type": "text",
- "data": {
- "text": "string"
- }
- }
- ]
- }
- ],
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_WHOLE_BAN = "napcat_set_group_whole_ban"
- """全体禁言
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- enable (Optional[bool]): 是否启用(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_PORTRAINT = "napcat_set_group_portrait"
- """设置群头像
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- file (Optional[str]): 文件路径(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "result": 0,
- "errMsg": "success"
- },
- "message": "",
- "wording": "",
- "echo": null
- }
- """
- SET_GROUP_ADMIN = "napcat_set_group_admin"
- """设置群管理
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- user_id (Optional[str|int]): 用户id(必需)
- enable (Optional[bool]): 是否设为群管理(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_CARD = "napcat_group_card"
- """设置群成员名片
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- user_id (Optional[str|int]): 用户id(必需)
- card (Optional[str]): 为空则为取消群名片
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_ESSENCE_MSG = "napcat_set_essence_msg"
- """设置群精华消息
-
- Args:
- message_id (Optional[str|int]): 消息id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "errCode": 0,
- "errMsg": "success",
- "result": {
- "wording": "",
- "digestUin": "0",
- "digestTime": 0,
- "msg": {
- "groupCode": "0",
- "msgSeq": 0,
- "msgRandom": 0,
- "msgContent": [],
- "textSize": "0",
- "picSize": "0",
- "videoSize": "0",
- "senderUin": "0",
- "senderTime": 0,
- "addDigestUin": "0",
- "addDigestTime": 0,
- "startTime": 0,
- "latestMsgSeq": 0,
- "opType": 0
- },
- "errorCode": 0
- }
- },
- "message": "",
- "wording": "",
- "echo": null
- }
- """
- SET_GROUP_NAME = "napcat_set_group_name"
- """设置群名
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- group_name (Optional[str]): 群名(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- DELETE_ESSENCE_MSG = "napcat_delete_essence_msg"
- """删除群精华消息
-
- Args:
- message_id (Optional[str|int]): 消息id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict:{
- "status": "ok",
- "retcode": 0,
- "data": {
- "errCode": 0,
- "errMsg": "success",
- "result": {
- "wording": "",
- "digestUin": "0",
- "digestTime": 0,
- "msg": {
- "groupCode": "0",
- "msgSeq": 0,
- "msgRandom": 0,
- "msgContent": [],
- "textSize": "0",
- "picSize": "0",
- "videoSize": "0",
- "senderUin": "0",
- "senderTime": 0,
- "addDigestUin": "0",
- "addDigestTime": 0,
- "startTime": 0,
- "latestMsgSeq": 0,
- "opType": 0
- },
- "errorCode": 0
- }
- },
- "message": "",
- "wording": "",
- "echo": null
- }
- """
- SET_GROUP_LEAVE = "napcat_set_group_leave"
- """退群
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SEND_GROUP_NOTICE = "napcat_group_notice"
- """发送群公告
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- content (Optional[str]): 公告内容(必需)
- image (Optional[str]): 图片地址
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_SPECIAL_TITLE = "napcat_set_group_special_title"
- """设置群头衔
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- user_id (Optional[str|int]): 用户id(必需)
- special_title (Optional[str]): 为空则取消头衔
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_NOTICE = "napcat_get_group_notice"
- """获取群公告
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- {
- "notice_id": "63491e2f000000004f4d1e677d2b0200",
- "sender_id": 123,
- "publish_time": 1730039119,
- "message": {
- "text": "这是一条神奇的群公告",
- "image": [
- {
- "id": "aJJBbZ6BqyLiaC1kmpvIWGBBkJerEfpRBHX5Brxbaurs",
- "height": "400",
- "width": "400"
- }
- ]
- }
- }
- ],
- "message": "",
- "wording": "",
- "echo": null
- }
- """
- SET_GROUP_ADD_REQUEST = "napcat_set_group_add_request"
- """处理加群请求
-
- Args:
- flag (Optional[str]): 请求id(必需)
- approve (Optional[bool]): 是否同意(必需)
- reason (Optional[str]): 拒绝理由
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": null,
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_LIST = "napcat_get_group_list"
- """获取群列表
-
- Args:
- no_cache (Optional[bool]): 是否不缓存
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- {
- "group_all_shut": 0,
- "group_remark": "string",
- "group_id": "string",
- "group_name": "string",
- "member_count": 0,
- "max_member_count": 0
- }
- ],
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- DELETE_GROUP_NOTICE = "napcat_del_group_notice"
- """删除群公告
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- notice_id (Optional[str]): 公告id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "result": 0,
- "errMsg": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_MEMBER_INFO = "napcat_get_group_member_info"
- """获取群成员信息
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- user_id (Optional[str|int]): 用户id(必需)
- no_cache (Optional[bool]): 是否不缓存
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict:{
- "status": "ok",
- "retcode": 0,
- "data": {
- "group_id": 0,
- "user_id": 0,
- "nickname": "string",
- "card": "string",
- "sex": "string",
- "age": 0,
- "join_time": 0,
- "last_sent_time": 0,
- "level": 0,
- "qq_level": 0,
- "role": "string",
- "title": "string",
- "area": "string",
- "unfriendly": true,
- "title_expire_time": 0,
- "card_changeable": true,
- "shut_up_timestamp": 0,
- "is_robot": true,
- "qage": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_MEMBER_LIST = "napcat_get_group_member_list"
- """获取群成员列表
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- no_cache (Optional[bool]): 是否不缓存
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- {
- "group_id": 0,
- "user_id": 0,
- "nickname": "string",
- "card": "string",
- "sex": "string",
- "age": 0,
- "join_time": 0,
- "last_sent_time": 0,
- "level": 0,
- "qq_level": 0,
- "role": "string",
- "title": "string",
- "area": "string",
- "unfriendly": true,
- "title_expire_time": 0,
- "card_changeable": true,
- "shut_up_timestamp": 0,
- "is_robot": true,
- "qage": "string"
- }
- ],
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_HONOR_INFO = "napcat_get_group_honor_info"
- """获取群荣誉
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- type (Optional[str]): 看详情
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict:{
- "status": "ok",
- "retcode": 0,
- "data": {
- "group_id": "string",
- "current_talkative": {
- "user_id": 0,
- "nickname": "string",
- "avatar": 0,
- "description": "string"
- },
- "talkative_list": [
- {
- "user_id": 0,
- "nickname": "string",
- "avatar": 0,
- "description": "string"
- }
- ],
- "performer_list": [
- {
- "user_id": 0,
- "nickname": "string",
- "avatar": 0,
- "description": "string"
- }
- ],
- "legend_list": [
- {
- "user_id": 0,
- "nickname": "string",
- "avatar": 0,
- "description": "string"
- }
- ],
- "emotion_list": [
- {
- "user_id": 0,
- "nickname": "string",
- "avatar": 0,
- "description": "string"
- }
- ],
- "strong_newbie_list": [
- {
- "user_id": 0,
- "nickname": "string",
- "avatar": 0,
- "description": "string"
- }
- ]
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_INFO_EX = "napcat_get_group_info_ex"
- """获取群信息ex
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "groupCode": "790514019",
- "resultCode": 0,
- "extInfo": {
- "groupInfoExtSeq": 1,
- "reserve": 0,
- "luckyWordId": "0",
- "lightCharNum": 0,
- "luckyWord": "",
- "starId": 0,
- "essentialMsgSwitch": 0,
- "todoSeq": 0,
- "blacklistExpireTime": 0,
- "isLimitGroupRtc": 0,
- "companyId": 0,
- "hasGroupCustomPortrait": 1,
- "bindGuildId": "0",
- "groupOwnerId": {
- "memberUin": "1129317309",
- "memberUid": "u_4_QA-QaFryh-Ocgsv4_8EQ",
- "memberQid": ""
- },
- "essentialMsgPrivilege": 0,
- "msgEventSeq": "0",
- "inviteRobotSwitch": 0,
- "gangUpId": "0",
- "qqMusicMedalSwitch": 0,
- "showPlayTogetherSwitch": 0,
- "groupFlagPro1": "0",
- "groupBindGuildIds": {
- "guildIds": []
- },
- "viewedMsgDisappearTime": "0",
- "groupExtFlameData": {
- "switchState": 0,
- "state": 0,
- "dayNums": [],
- "version": 0,
- "updateTime": "0",
- "isDisplayDayNum": false
- },
- "groupBindGuildSwitch": 0,
- "groupAioBindGuildId": "0",
- "groupExcludeGuildIds": {
- "guildIds": []
- },
- "fullGroupExpansionSwitch": 0,
- "fullGroupExpansionSeq": "0",
- "inviteRobotMemberSwitch": 0,
- "inviteRobotMemberExamine": 0,
- "groupSquareSwitch": 0
- }
- },
- "message": "",
- "wording": "",
- "echo": null
- }
- """
- GET_GROUP_AT_ALL_REMAIN = "napcat_get_group_at_all_remain"
- """获取群 @全体成员 剩余次数
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "can_at_all": true,
- "remain_at_all_count_for_group": 0,
- "remain_at_all_count_for_uin": 0
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_SHUT_LIST = "napcat_get_group_shut_list"
- """获取群禁言列表
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": [
- {
- "uid": "string",
- "qid": "string",
- "uin": "string",
- "nick": "string",
- "remark": "string",
- "cardType": 0,
- "cardName": "string",
- "role": 0,
- "avatarPath": "string",
- "shutUpTime": 0,
- "isDelete": true,
- "isSpecialConcerned": true,
- "isSpecialShield": true,
- "isRobot": true,
- "groupHonor": {},
- "memberRealLevel": 0,
- "memberLevel": 0,
- "globalGroupLevel": 0,
- "globalGroupPoint": 0,
- "memberTitleId": 0,
- "memberSpecialTitle": "string",
- "specialTitleExpireTime": "string",
- "userShowFlag": 0,
- "userShowFlagNew": 0,
- "richFlag": 0,
- "mssVipType": 0,
- "bigClubLevel": 0,
- "bigClubFlag": 0,
- "autoRemark": "string",
- "creditLevel": 0,
- "joinTime": 0,
- "lastSpeakTime": 0,
- "memberFlag": 0,
- "memberFlagExt": 0,
- "memberMobileFlag": 0,
- "memberFlagExt2": 0,
- "isSpecialShielded": true,
- "cardNameId": 0
- }
- ],
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- GET_GROUP_IGNORED_NOTIFIES = "napcat_get_group_ignored_notifies"
- """获取群过滤系统消息
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "InvitedRequest": [
- {
- "request_id": 0,
- "invitor_uin": 0,
- "invitor_nick": "string",
- "group_id": 0,
- "message": "string",
- "group_name": "string",
- "checked": true,
- "actor": 0,
- "requester_nick": "string"
- }
- ],
- "join_requests": [
- {
- "request_id": 0,
- "invitor_uin": 0,
- "invitor_nick": "string",
- "group_id": 0,
- "message": "string",
- "group_name": "string",
- "checked": true,
- "actor": 0,
- "requester_nick": "string"
- }
- ]
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
- SET_GROUP_SIGN = "napcat_set_group_sign"
- """群打卡
-
- Args:
- group_id (Optional[str|int]): 群号(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {}
- """
-
- class FILE(Enum): ...
-
- class PERSONAL(Enum):
- SET_INPUT_STATUS = "napcat_set_input_status"
- """
- 设置输入状态
-
- Args:
- user_id (Optional[str|int]): 用户id(必需)
- event_type (Optional[int]): 输入状态id(必需)
- raw (Optional[dict]): 原始请求体
-
- Returns:
- dict: {
- "status": "ok",
- "retcode": 0,
- "data": {
- "result": 0,
- "errMsg": "string"
- },
- "message": "string",
- "wording": "string",
- "echo": "string"
- }
- """
diff --git a/src/plugins/built_in/napcat_adapter_plugin/plugin.py b/src/plugins/built_in/napcat_adapter_plugin/plugin.py
deleted file mode 100644
index 2437f0162..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/plugin.py
+++ /dev/null
@@ -1,430 +0,0 @@
-import asyncio
-import inspect
-import orjson
-from typing import ClassVar, List
-
-import websockets as Server
-
-from src.common.logger import get_logger
-from src.plugin_system import BaseEventHandler, BasePlugin, ConfigField, EventType, register_plugin
-from src.plugin_system.apis import config_api
-from src.plugin_system.core.event_manager import event_manager
-
-from . import CONSTS, event_handlers, event_types
-from .src.message_chunker import chunker, reassembler
-from .src.mmc_com_layer import mmc_start_com, mmc_stop_com, router
-from .src.recv_handler.message_handler import message_handler
-from .src.recv_handler.message_sending import message_send_instance
-from .src.recv_handler.meta_event_handler import meta_event_handler
-from .src.recv_handler.notice_handler import notice_handler
-from .src.response_pool import check_timeout_response, put_response
-from .src.send_handler import send_handler
-from .src.stream_router import stream_router
-from .src.websocket_manager import websocket_manager
-
-logger = get_logger("napcat_adapter")
-
-# 旧的全局消息队列已被流路由器替代
-# message_queue = asyncio.Queue()
-
-
-def get_classes_in_module(module):
- classes = []
- for _name, member in inspect.getmembers(module):
- if inspect.isclass(member):
- classes.append(member)
- return classes
-
-
-async def message_recv(server_connection: Server.ServerConnection):
- await message_handler.set_server_connection(server_connection)
- asyncio.create_task(notice_handler.set_server_connection(server_connection))
- await send_handler.set_server_connection(server_connection)
- async for raw_message in server_connection:
- # 只在debug模式下记录原始消息
- if logger.level <= 10: # DEBUG level
- logger.debug(f"{raw_message[:1500]}..." if (len(raw_message) > 1500) else raw_message)
- decoded_raw_message: dict = orjson.loads(raw_message)
- try:
- # 首先尝试解析原始消息
- decoded_raw_message: dict = orjson.loads(raw_message)
-
- # 检查是否是切片消息 (来自 MMC)
- if chunker.is_chunk_message(decoded_raw_message):
- logger.debug("接收到切片消息,尝试重组")
- # 尝试重组消息
- reassembled_message = await reassembler.add_chunk(decoded_raw_message)
- if reassembled_message:
- # 重组完成,处理完整消息
- logger.debug("消息重组完成,处理完整消息")
- decoded_raw_message = reassembled_message
- else:
- # 切片尚未完整,继续等待更多切片
- logger.debug("等待更多切片...")
- continue
-
- # 处理完整消息(可能是重组后的,也可能是原本就完整的)
- post_type = decoded_raw_message.get("post_type")
- if post_type in ["meta_event", "message", "notice"]:
- # 使用流路由器路由消息到对应的聊天流
- await stream_router.route_message(decoded_raw_message)
- elif post_type is None:
- await put_response(decoded_raw_message)
-
- except orjson.JSONDecodeError as e:
- logger.error(f"消息解析失败: {e}")
- logger.debug(f"原始消息: {raw_message[:500]}...")
- except Exception as e:
- logger.error(f"处理消息时出错: {e}")
- logger.debug(f"原始消息: {raw_message[:500]}...")
-
-
-# 旧的单消费者消息处理循环已被流路由器替代
-# 现在每个聊天流都有自己的消费者协程
-# async def message_process():
-# """消息处理主循环"""
-# ...
-
-
-async def napcat_server(plugin_config: dict):
- """启动 Napcat WebSocket 连接(支持正向和反向连接)"""
- # 使用插件系统配置API获取配置
- mode = config_api.get_plugin_config(plugin_config, "napcat_server.mode")
- logger.info(f"正在启动 adapter,连接模式: {mode}")
-
- try:
- await websocket_manager.start_connection(message_recv, plugin_config)
- except Exception as e:
- logger.error(f"启动 WebSocket 连接失败: {e}")
- raise
-
-
-async def graceful_shutdown():
- """优雅关闭所有组件"""
- try:
- logger.info("正在关闭adapter...")
-
- # 停止流路由器
- try:
- await stream_router.stop()
- except Exception as e:
- logger.warning(f"停止流路由器时出错: {e}")
-
- # 停止消息重组器的清理任务
- try:
- await reassembler.stop_cleanup_task()
- except Exception as e:
- logger.warning(f"停止消息重组器清理任务时出错: {e}")
-
- # 停止功能管理器文件监控(已迁移到插件系统配置,无需操作)
-
- # 关闭消息处理器(包括消息缓冲器)
- try:
- await message_handler.shutdown()
- except Exception as e:
- logger.warning(f"关闭消息处理器时出错: {e}")
-
- # 关闭 WebSocket 连接
- try:
- await websocket_manager.stop_connection()
- except Exception as e:
- logger.warning(f"关闭WebSocket连接时出错: {e}")
-
- # 关闭 MoFox-Bot 连接
- try:
- await mmc_stop_com()
- except Exception as e:
- logger.warning(f"关闭MoFox-Bot连接时出错: {e}")
-
- # 取消所有剩余任务
- current_task = asyncio.current_task()
- tasks = [t for t in asyncio.all_tasks() if t is not current_task and not t.done()]
-
- if tasks:
- logger.info(f"正在取消 {len(tasks)} 个剩余任务...")
- for task in tasks:
- task.cancel()
-
- # 等待任务取消完成,忽略 CancelledError
- try:
- await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=10)
- except asyncio.TimeoutError:
- logger.warning("部分任务取消超时")
- except Exception as e:
- logger.debug(f"任务取消过程中的异常(可忽略): {e}")
-
- logger.info("Adapter已成功关闭")
-
- except Exception as e:
- logger.error(f"Adapter关闭中出现错误: {e}")
-
-
-class LauchNapcatAdapterHandler(BaseEventHandler):
- """自动启动Adapter"""
-
- handler_name: str = "launch_napcat_adapter_handler"
- handler_description: str = "自动启动napcat adapter"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [EventType.ON_START]
-
- async def execute(self, kwargs):
- # 启动消息重组器的清理任务
- logger.info("启动消息重组器...")
- await reassembler.start_cleanup_task()
-
- # 启动流路由器
- logger.info("启动流路由器...")
- await stream_router.start()
-
- logger.info("开始启动Napcat Adapter")
-
- # 创建单独的异步任务,防止阻塞主线程
- asyncio.create_task(self._start_maibot_connection())
- asyncio.create_task(napcat_server(self.plugin_config))
- # 不再需要 message_process 任务,由流路由器管理消费者
- asyncio.create_task(check_timeout_response())
-
- async def _start_maibot_connection(self):
- """非阻塞方式启动MoFox-Bot连接,等待主服务启动后再连接"""
- # 等待一段时间让MoFox-Bot主服务完全启动
- await asyncio.sleep(5)
-
- max_attempts = 10
- attempt = 0
-
- while attempt < max_attempts:
- try:
- logger.info(f"尝试连接MoFox-Bot (第{attempt + 1}次)")
- await mmc_start_com(self.plugin_config)
- message_send_instance.maibot_router = router
- logger.info("MoFox-Bot router连接已建立")
- return
- except Exception as e:
- attempt += 1
- if attempt >= max_attempts:
- logger.error(f"MoFox-Bot连接失败,已达到最大重试次数: {e}")
- return
- else:
- delay = min(2 + attempt, 10) # 逐渐增加延迟,最大10秒
- logger.warning(f"MoFox-Bot连接失败: {e},{delay}秒后重试")
- await asyncio.sleep(delay)
-
-
-class StopNapcatAdapterHandler(BaseEventHandler):
- """关闭Adapter"""
-
- handler_name: str = "stop_napcat_adapter_handler"
- handler_description: str = "关闭napcat adapter"
- weight: int = 100
- intercept_message: bool = False
- init_subscribe: ClassVar[list] = [EventType.ON_STOP]
-
- async def execute(self, kwargs):
- await graceful_shutdown()
- return
-
-
-@register_plugin
-class NapcatAdapterPlugin(BasePlugin):
- plugin_name = CONSTS.PLUGIN_NAME
- dependencies: ClassVar[List[str]] = [] # 插件依赖列表
- python_dependencies: ClassVar[List[str]] = [] # Python包依赖列表
- config_file_name: str = "config.toml" # 配置文件名
-
- @property
- def enable_plugin(self) -> bool:
- """通过配置文件动态控制插件启用状态"""
- # 如果已经通过配置加载了状态,使用配置中的值
- # 否则使用默认值(禁用状态)
- return False
-
- # 配置节描述
- config_section_descriptions: ClassVar[dict] = {"plugin": "插件基本信息"}
-
- # 配置Schema定义
- config_schema: ClassVar[dict] = {
- "plugin": {
- "name": ConfigField(type=str, default="napcat_adapter_plugin", description="插件名称"),
- "version": ConfigField(type=str, default="1.1.0", description="插件版本"),
- "config_version": ConfigField(type=str, default="1.3.1", description="配置文件版本"),
- "enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
- },
- "inner": {
- "version": ConfigField(type=str, default="0.2.1", description="配置版本号,请勿修改"),
- },
- "nickname": {
- "nickname": ConfigField(type=str, default="", description="昵称配置(目前未使用)"),
- },
- "napcat_server": {
- "mode": ConfigField(
- type=str,
- default="reverse",
- description="连接模式:reverse=反向连接(作为服务器), forward=正向连接(作为客户端)",
- choices=["reverse", "forward"],
- ),
- "host": ConfigField(type=str, default="localhost", description="主机地址"),
- "port": ConfigField(type=int, default=8095, description="端口号"),
- "url": ConfigField(
- type=str,
- default="",
- description="正向连接时的完整WebSocket URL,如 ws://localhost:8080/ws (仅在forward模式下使用)",
- ),
- "access_token": ConfigField(
- type=str, default="", description="WebSocket 连接的访问令牌,用于身份验证(可选)"
- ),
- "heartbeat_interval": ConfigField(type=int, default=30, description="心跳间隔时间(按秒计)"),
- },
- "maibot_server": {
- "platform_name": ConfigField(type=str, default="qq", description="平台名称,用于消息路由"),
- "host": ConfigField(type=str, default="", description="MoFox-Bot服务器地址,留空则使用全局配置"),
- "port": ConfigField(type=int, default=0, description="MoFox-Bot服务器端口,设为0则使用全局配置"),
- },
- "voice": {
- "use_tts": ConfigField(
- type=bool, default=False, description="是否使用tts语音(请确保你配置了tts并有对应的adapter)"
- ),
- },
- "slicing": {
- "max_frame_size": ConfigField(
- type=int, default=64, description="WebSocket帧的最大大小,单位为字节,默认64KB"
- ),
- "delay_ms": ConfigField(type=int, default=10, description="切片发送间隔时间,单位为毫秒"),
- },
- "debug": {
- "level": ConfigField(
- type=str,
- default="INFO",
- description="日志等级(DEBUG, INFO, WARNING, ERROR, CRITICAL)",
- choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
- ),
- },
- "stream_router": {
- "max_streams": ConfigField(type=int, default=500, description="最大并发流数量"),
- "stream_timeout": ConfigField(type=int, default=600, description="流不活跃超时时间(秒),超时后自动清理"),
- "stream_queue_size": ConfigField(type=int, default=100, description="每个流的消息队列大小"),
- "cleanup_interval": ConfigField(type=int, default=60, description="清理不活跃流的间隔时间(秒)"),
- },
- "features": {
- # 权限设置
- "group_list_type": ConfigField(
- type=str,
- default="blacklist",
- description="群聊列表类型:whitelist(白名单)或 blacklist(黑名单)",
- choices=["whitelist", "blacklist"],
- ),
- "group_list": ConfigField(type=list, default=[], description="群聊ID列表"),
- "private_list_type": ConfigField(
- type=str,
- default="blacklist",
- description="私聊列表类型:whitelist(白名单)或 blacklist(黑名单)",
- choices=["whitelist", "blacklist"],
- ),
- "private_list": ConfigField(type=list, default=[], description="用户ID列表"),
- "ban_user_id": ConfigField(
- type=list, default=[], description="全局禁止用户ID列表,这些用户无法在任何地方使用机器人"
- ),
- "ban_qq_bot": ConfigField(type=bool, default=False, description="是否屏蔽QQ官方机器人消息"),
- # 聊天功能设置
- "enable_poke": ConfigField(type=bool, default=True, description="是否启用戳一戳功能"),
- "ignore_non_self_poke": ConfigField(type=bool, default=False, description="是否无视不是针对自己的戳一戳"),
- "poke_debounce_seconds": ConfigField(
- type=int, default=3, description="戳一戳防抖时间(秒),在指定时间内第二次针对机器人的戳一戳将被忽略"
- ),
- "enable_reply_at": ConfigField(type=bool, default=True, description="是否启用引用回复时艾特用户的功能"),
- "reply_at_rate": ConfigField(type=float, default=0.5, description="引用回复时艾特用户的几率 (0.0 ~ 1.0)"),
- "enable_emoji_like": ConfigField(type=bool, default=True, description="是否启用群聊表情回复功能"),
- # 视频处理设置
- "enable_video_analysis": ConfigField(type=bool, default=True, description="是否启用视频识别功能"),
- "max_video_size_mb": ConfigField(type=int, default=100, description="视频文件最大大小限制(MB)"),
- "download_timeout": ConfigField(type=int, default=60, description="视频下载超时时间(秒)"),
- "supported_formats": ConfigField(
- type=list, default=["mp4", "avi", "mov", "mkv", "flv", "wmv", "webm"], description="支持的视频格式"
- ),
- },
- }
-
- # 配置节描述
- config_section_descriptions: ClassVar[dict] = {
- "plugin": "插件基本信息",
- "inner": "内部配置信息(请勿修改)",
- "nickname": "昵称配置(目前未使用)",
- "napcat_server": "Napcat连接的ws服务设置",
- "maibot_server": "连接麦麦的ws服务设置",
- "voice": "发送语音设置",
- "slicing": "WebSocket消息切片设置",
- "debug": "调试设置",
- "stream_router": "流路由器设置(按聊天流分配消费者,提升高并发性能)",
- "features": "功能设置(权限控制、聊天功能、视频处理等)",
- }
-
- def register_events(self):
- # 注册事件
- for e in event_types.NapcatEvent.ON_RECEIVED:
- event_manager.register_event(e, allowed_triggers=[self.plugin_name])
-
- for e in event_types.NapcatEvent.ACCOUNT:
- event_manager.register_event(e, allowed_subscribers=[f"{e.value}_handler"])
-
- for e in event_types.NapcatEvent.GROUP:
- event_manager.register_event(e, allowed_subscribers=[f"{e.value}_handler"])
-
- for e in event_types.NapcatEvent.MESSAGE:
- event_manager.register_event(e, allowed_subscribers=[f"{e.value}_handler"])
-
- def get_plugin_components(self):
- self.register_events()
-
- components = []
- components.append((LauchNapcatAdapterHandler.get_handler_info(), LauchNapcatAdapterHandler))
- components.append((StopNapcatAdapterHandler.get_handler_info(), StopNapcatAdapterHandler))
- components.extend(
- (handler.get_handler_info(), handler)
- for handler in get_classes_in_module(event_handlers)
- if issubclass(handler, BaseEventHandler)
- )
- return components
-
- async def on_plugin_loaded(self):
- # 初始化数据库表
- await self._init_database_tables()
-
- # 设置插件配置
- message_send_instance.set_plugin_config(self.config)
- # 设置chunker的插件配置
- chunker.set_plugin_config(self.config)
- # 设置response_pool的插件配置
- from .src.response_pool import set_plugin_config as set_response_pool_config
-
- set_response_pool_config(self.config)
- # 设置send_handler的插件配置
- send_handler.set_plugin_config(self.config)
- # 设置message_handler的插件配置
- message_handler.set_plugin_config(self.config)
- # 设置notice_handler的插件配置
- notice_handler.set_plugin_config(self.config)
- # 设置meta_event_handler的插件配置
- meta_event_handler.set_plugin_config(self.config)
-
- # 设置流路由器的配置
- stream_router.max_streams = config_api.get_plugin_config(self.config, "stream_router.max_streams", 500)
- stream_router.stream_timeout = config_api.get_plugin_config(self.config, "stream_router.stream_timeout", 600)
- stream_router.stream_queue_size = config_api.get_plugin_config(self.config, "stream_router.stream_queue_size", 100)
- stream_router.cleanup_interval = config_api.get_plugin_config(self.config, "stream_router.cleanup_interval", 60)
-
- # 设置其他handler的插件配置(现在由component_registry在注册时自动设置)
-
- async def _init_database_tables(self):
- """初始化插件所需的数据库表"""
- try:
- from src.common.database.core.engine import get_engine
- from .src.database import NapcatBanRecord
-
- engine = await get_engine()
- async with engine.begin() as conn:
- # 创建 napcat_ban_records 表
- await conn.run_sync(NapcatBanRecord.metadata.create_all)
-
- logger.info("Napcat 插件数据库表初始化成功")
- except Exception as e:
- logger.error(f"Napcat 插件数据库表初始化失败: {e}", exc_info=True)
diff --git a/src/plugins/built_in/napcat_adapter_plugin/pyproject.toml b/src/plugins/built_in/napcat_adapter_plugin/pyproject.toml
deleted file mode 100644
index a45fee969..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/pyproject.toml
+++ /dev/null
@@ -1,47 +0,0 @@
-[project]
-name = "MoFoxBotNapcatAdapter"
-version = "0.4.8"
-description = "A MoFox-Bot adapter for Napcat"
-dependencies = [
- "ruff>=0.12.9",
-]
-
-[tool.ruff]
-
-include = ["*.py"]
-
-# 行长度设置
-line-length = 120
-
-[tool.ruff.lint]
-fixable = ["ALL"]
-unfixable = []
-
-# 启用的规则
-select = [
- "E", # pycodestyle 错误
- "F", # pyflakes
- "B", # flake8-bugbear
-]
-
-ignore = ["E711","E501"]
-
-[tool.ruff.format]
-docstring-code-format = true
-indent-style = "space"
-
-
-# 使用双引号表示字符串
-quote-style = "double"
-
-# 尊重魔法尾随逗号
-# 例如:
-# items = [
-# "apple",
-# "banana",
-# "cherry",
-# ]
-skip-magic-trailing-comma = false
-
-# 自动检测合适的换行符
-line-ending = "auto"
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/__init__.py b/src/plugins/built_in/napcat_adapter_plugin/src/__init__.py
deleted file mode 100644
index aecb7d6c6..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import os
-from enum import Enum
-
-import tomlkit
-
-from src.common.logger import get_logger
-
-logger = get_logger("napcat_adapter")
-
-
-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 = "send_group_ai_record" # 发送群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
-
-
-pyproject_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pyproject.toml")
-toml_data = tomlkit.parse(open(pyproject_path, "r", encoding="utf-8").read())
-project_data = toml_data.get("project", {})
-version = project_data.get("version", "unknown")
-logger.info(f"版本\n\nMoFox-Bot-Napcat-Adapter 版本: {version}\n喜欢的话点个star喵~\n")
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/database.py b/src/plugins/built_in/napcat_adapter_plugin/src/database.py
deleted file mode 100644
index d3cc7e116..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/database.py
+++ /dev/null
@@ -1,157 +0,0 @@
-"""Napcat Adapter 插件数据库层 (基于主程序异步SQLAlchemy API)
-
-本模块替换原先的 sqlmodel + 同步Session 实现:
-1. 复用主项目的异步数据库连接与迁移体系
-2. 提供与旧接口名兼容的方法(update_ban_record/create_ban_record/delete_ban_record)
-3. 新增首选异步方法: update_ban_records / create_or_update / delete_record / get_ban_records
-
-数据语义:
- user_id == 0 表示群全体禁言
-
-注意: 所有方法均为异步, 需要在 async 上下文中调用。
-"""
-from __future__ import annotations
-
-from dataclasses import dataclass
-from typing import List, Optional, Sequence
-
-from sqlalchemy import BigInteger, Column, Index, Integer, UniqueConstraint, select
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from src.common.database.core.models import Base
-from src.common.database.core import get_db_session
-from src.common.logger import get_logger
-
-logger = get_logger("napcat_adapter")
-
-
-class NapcatBanRecord(Base):
- __tablename__ = "napcat_ban_records"
-
- id = Column(Integer, primary_key=True, autoincrement=True)
- group_id = Column(BigInteger, nullable=False, index=True)
- user_id = Column(BigInteger, nullable=False, index=True) # 0 == 全体禁言
- lift_time = Column(BigInteger, nullable=True) # -1 / None 表示未知/永久
-
- __table_args__ = (
- UniqueConstraint("group_id", "user_id", name="uq_napcat_group_user"),
- Index("idx_napcat_ban_group", "group_id"),
- Index("idx_napcat_ban_user", "user_id"),
- )
-
-
-@dataclass
-class BanUser:
- user_id: int
- group_id: int
- lift_time: Optional[int] = -1
-
- def identity(self) -> tuple[int, int]:
- return self.group_id, self.user_id
-
-
-class NapcatDatabase:
- async def _fetch_all(self, session: AsyncSession) -> Sequence[NapcatBanRecord]:
- result = await session.execute(select(NapcatBanRecord))
- return result.scalars().all()
-
- async def get_ban_records(self) -> List[BanUser]:
- async with get_db_session() as session:
- rows = await self._fetch_all(session)
- return [BanUser(group_id=r.group_id, user_id=r.user_id, lift_time=r.lift_time) for r in rows]
-
- async def update_ban_records(self, ban_list: List[BanUser]) -> None:
- target_map = {b.identity(): b for b in ban_list}
- async with get_db_session() as session:
- rows = await self._fetch_all(session)
- existing_map = {(r.group_id, r.user_id): r for r in rows}
-
- changed = 0
- for ident, ban in target_map.items():
- if ident in existing_map:
- row = existing_map[ident]
- if row.lift_time != ban.lift_time:
- row.lift_time = ban.lift_time
- changed += 1
- else:
- session.add(
- NapcatBanRecord(group_id=ban.group_id, user_id=ban.user_id, lift_time=ban.lift_time)
- )
- changed += 1
-
- removed = 0
- for ident, row in existing_map.items():
- if ident not in target_map:
- await session.delete(row)
- removed += 1
-
- logger.debug(
- f"Napcat ban list sync => total_incoming={len(ban_list)} created_or_updated={changed} removed={removed}"
- )
-
- async def create_or_update(self, ban_record: BanUser) -> None:
- async with get_db_session() as session:
- stmt = select(NapcatBanRecord).where(
- NapcatBanRecord.group_id == ban_record.group_id,
- NapcatBanRecord.user_id == ban_record.user_id,
- )
- result = await session.execute(stmt)
- row = result.scalars().first()
- if row:
- if row.lift_time != ban_record.lift_time:
- row.lift_time = ban_record.lift_time
- logger.debug(
- f"更新禁言记录 group={ban_record.group_id} user={ban_record.user_id} lift={ban_record.lift_time}"
- )
- else:
- session.add(
- NapcatBanRecord(
- group_id=ban_record.group_id, user_id=ban_record.user_id, lift_time=ban_record.lift_time
- )
- )
- logger.debug(
- f"创建禁言记录 group={ban_record.group_id} user={ban_record.user_id} lift={ban_record.lift_time}"
- )
-
- async def delete_record(self, ban_record: BanUser) -> None:
- async with get_db_session() as session:
- stmt = select(NapcatBanRecord).where(
- NapcatBanRecord.group_id == ban_record.group_id,
- NapcatBanRecord.user_id == ban_record.user_id,
- )
- result = await session.execute(stmt)
- row = result.scalars().first()
- if row:
- await session.delete(row)
- logger.debug(
- f"删除禁言记录 group={ban_record.group_id} user={ban_record.user_id} lift={row.lift_time}"
- )
- else:
- logger.info(
- f"未找到禁言记录 group={ban_record.group_id} user={ban_record.user_id}"
- )
-
- # 兼容旧命名
- async def update_ban_record(self, ban_list: List[BanUser]) -> None: # old name
- await self.update_ban_records(ban_list)
-
- async def create_ban_record(self, ban_record: BanUser) -> None: # old name
- await self.create_or_update(ban_record)
-
- async def delete_ban_record(self, ban_record: BanUser) -> None: # old name
- await self.delete_record(ban_record)
-
-
-napcat_db = NapcatDatabase()
-
-
-def is_identical(a: BanUser, b: BanUser) -> bool:
- return a.group_id == b.group_id and a.user_id == b.user_id
-
-
-__all__ = [
- "BanUser",
- "NapcatBanRecord",
- "napcat_db",
- "is_identical",
-]
\ No newline at end of file
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/message_chunker.py b/src/plugins/built_in/napcat_adapter_plugin/src/message_chunker.py
deleted file mode 100644
index 86902354f..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/message_chunker.py
+++ /dev/null
@@ -1,280 +0,0 @@
-"""
-消息切片处理模块
-用于在 Ada 发送给 MMC 时进行消息切片,利用 WebSocket 协议的自动重组特性
-仅在 Ada -> MMC 方向进行切片,其他方向(MMC -> Ada,Ada <-> Napcat)不切片
-"""
-
-import asyncio
-import orjson
-import time
-import uuid
-from typing import Any, Dict, List, Optional, Union
-
-from src.common.logger import get_logger
-from src.plugin_system.apis import config_api
-
-logger = get_logger("napcat_adapter")
-
-
-class MessageChunker:
- """消息切片器,用于处理大消息的分片发送"""
-
- def __init__(self):
- self.max_chunk_size = 64 * 1024 # 默认值,将在设置配置时更新
- self.plugin_config = None
-
- def set_plugin_config(self, plugin_config: dict):
- """设置插件配置"""
- self.plugin_config = plugin_config
- if plugin_config:
- max_frame_size = config_api.get_plugin_config(plugin_config, "slicing.max_frame_size", 64)
- self.max_chunk_size = max_frame_size * 1024
-
- def should_chunk_message(self, message: Union[str, Dict[str, Any]]) -> bool:
- """判断消息是否需要切片"""
- try:
- if isinstance(message, dict):
- message_str = orjson.dumps(message, option=orjson.OPT_NON_STR_KEYS).decode('utf-8')
- else:
- message_str = message
- return len(message_str.encode("utf-8")) > self.max_chunk_size
- except Exception as e:
- logger.error(f"检查消息大小时出错: {e}")
- return False
-
- def chunk_message(
- self, message: Union[str, Dict[str, Any]], chunk_id: Optional[str] = None
- ) -> List[Dict[str, Any]]:
- """
- 将消息切片
-
- Args:
- message: 要切片的消息(字符串或字典)
- chunk_id: 切片组ID,如果不提供则自动生成
-
- Returns:
- 切片后的消息字典列表
- """
- try:
- # 统一转换为字符串
- if isinstance(message, dict):
- message_str = orjson.dumps(message, option=orjson.OPT_NON_STR_KEYS).decode('utf-8')
- else:
- message_str = message
-
- if not self.should_chunk_message(message_str):
- # 不需要切片的情况,如果输入是字典则返回字典,如果是字符串则包装成非切片标记的字典
- if isinstance(message, dict):
- return [message]
- else:
- return [{"_original_message": message_str}]
-
- if chunk_id is None:
- chunk_id = str(uuid.uuid4())
-
- message_bytes = message_str.encode("utf-8")
- total_size = len(message_bytes)
-
- # 计算需要多少个切片
- num_chunks = (total_size + self.max_chunk_size - 1) // self.max_chunk_size
-
- chunks = []
- for i in range(num_chunks):
- start_pos = i * self.max_chunk_size
- end_pos = min(start_pos + self.max_chunk_size, total_size)
-
- chunk_data = message_bytes[start_pos:end_pos]
-
- # 构建切片消息
- chunk_message = {
- "__mmc_chunk_info__": {
- "chunk_id": chunk_id,
- "chunk_index": i,
- "total_chunks": num_chunks,
- "chunk_size": len(chunk_data),
- "total_size": total_size,
- "timestamp": time.time(),
- },
- "__mmc_chunk_data__": chunk_data.decode("utf-8", errors="ignore"),
- "__mmc_is_chunked__": True,
- }
-
- chunks.append(chunk_message)
-
- logger.debug(f"消息切片完成: {total_size} bytes -> {num_chunks} chunks (ID: {chunk_id})")
- return chunks
-
- except Exception as e:
- logger.error(f"消息切片时出错: {e}")
- # 出错时返回原消息
- if isinstance(message, dict):
- return [message]
- else:
- return [{"_original_message": message}]
-
- def is_chunk_message(self, message: Union[str, Dict[str, Any]]) -> bool:
- """判断是否是切片消息"""
- try:
- if isinstance(message, str):
- data = orjson.loads(message)
- else:
- data = message
-
- return (
- isinstance(data, dict)
- and "__mmc_chunk_info__" in data
- and "__mmc_chunk_data__" in data
- and "__mmc_is_chunked__" in data
- )
- except (orjson.JSONDecodeError, TypeError):
- return False
-
-
-class MessageReassembler:
- """消息重组器,用于重组接收到的切片消息"""
-
- def __init__(self, timeout: int = 30):
- self.timeout = timeout
- self.chunk_buffers: Dict[str, Dict[str, Any]] = {}
- self._cleanup_task = None
-
- async def start_cleanup_task(self):
- """启动清理任务"""
- if self._cleanup_task is None:
- self._cleanup_task = asyncio.create_task(self._cleanup_expired_chunks())
-
- async def stop_cleanup_task(self):
- """停止清理任务"""
- if self._cleanup_task:
- self._cleanup_task.cancel()
- try:
- await self._cleanup_task
- except asyncio.CancelledError:
- pass
- self._cleanup_task = None
-
- async def _cleanup_expired_chunks(self):
- """清理过期的切片缓冲区"""
- while True:
- try:
- await asyncio.sleep(10) # 每10秒检查一次
- current_time = time.time()
-
- expired_chunks = []
- for chunk_id, buffer_info in self.chunk_buffers.items():
- if current_time - buffer_info["timestamp"] > self.timeout:
- expired_chunks.append(chunk_id)
-
- for chunk_id in expired_chunks:
- logger.warning(f"清理过期的切片缓冲区: {chunk_id}")
- del self.chunk_buffers[chunk_id]
-
- except asyncio.CancelledError:
- break
- except Exception as e:
- logger.error(f"清理过期切片时出错: {e}")
-
- async def add_chunk(self, message: Union[str, Dict[str, Any]]) -> Optional[Dict[str, Any]]:
- """
- 添加切片,如果切片完整则返回重组后的消息
-
- Args:
- message: 切片消息(字符串或字典)
-
- Returns:
- 如果切片完整则返回重组后的原始消息字典,否则返回None
- """
- try:
- # 统一转换为字典
- if isinstance(message, str):
- chunk_data = orjson.loads(message)
- else:
- chunk_data = message
-
- # 检查是否是切片消息
- if not chunker.is_chunk_message(chunk_data):
- # 不是切片消息,直接返回
- if "_original_message" in chunk_data:
- # 这是一个被包装的非切片消息,解包返回
- try:
- return orjson.loads(chunk_data["_original_message"])
- except orjson.JSONDecodeError:
- return {"text_message": chunk_data["_original_message"]}
- else:
- return chunk_data
-
- chunk_info = chunk_data["__mmc_chunk_info__"]
- chunk_content = chunk_data["__mmc_chunk_data__"]
-
- chunk_id = chunk_info["chunk_id"]
- chunk_index = chunk_info["chunk_index"]
- total_chunks = chunk_info["total_chunks"]
- chunk_timestamp = chunk_info.get("timestamp", time.time())
-
- # 初始化缓冲区
- if chunk_id not in self.chunk_buffers:
- self.chunk_buffers[chunk_id] = {
- "chunks": {},
- "total_chunks": total_chunks,
- "received_chunks": 0,
- "timestamp": chunk_timestamp,
- }
-
- buffer = self.chunk_buffers[chunk_id]
-
- # 检查切片是否已经接收过
- if chunk_index in buffer["chunks"]:
- logger.warning(f"重复接收切片: {chunk_id}#{chunk_index}")
- return None
-
- # 添加切片
- buffer["chunks"][chunk_index] = chunk_content
- buffer["received_chunks"] += 1
- buffer["timestamp"] = time.time() # 更新时间戳
-
- logger.debug(f"接收切片: {chunk_id}#{chunk_index} ({buffer['received_chunks']}/{total_chunks})")
-
- # 检查是否接收完整
- if buffer["received_chunks"] == total_chunks:
- # 重组消息
- reassembled_message = ""
- for i in range(total_chunks):
- if i not in buffer["chunks"]:
- logger.error(f"切片 {chunk_id}#{i} 缺失,无法重组")
- return None
- reassembled_message += buffer["chunks"][i]
-
- # 清理缓冲区
- del self.chunk_buffers[chunk_id]
-
- logger.debug(f"消息重组完成: {chunk_id} ({len(reassembled_message)} chars)")
-
- # 尝试反序列化重组后的消息
- try:
- return orjson.loads(reassembled_message)
- except orjson.JSONDecodeError:
- # 如果不能反序列化为JSON,则作为文本消息返回
- return {"text_message": reassembled_message}
-
- return None
-
- except (orjson.JSONDecodeError, KeyError, TypeError) as e:
- logger.error(f"处理切片消息时出错: {e}")
- return None
-
- def get_pending_chunks_info(self) -> Dict[str, Any]:
- """获取待处理切片信息"""
- info = {}
- for chunk_id, buffer in self.chunk_buffers.items():
- info[chunk_id] = {
- "received": buffer["received_chunks"],
- "total": buffer["total_chunks"],
- "progress": f"{buffer['received_chunks']}/{buffer['total_chunks']}",
- "age_seconds": time.time() - buffer["timestamp"],
- }
- return info
-
-
-# 全局实例
-chunker = MessageChunker()
-reassembler = MessageReassembler()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/mmc_com_layer.py b/src/plugins/built_in/napcat_adapter_plugin/src/mmc_com_layer.py
deleted file mode 100644
index 77d9a3939..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/mmc_com_layer.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from mofox_wire import RouteConfig, Router, TargetConfig
-
-from src.common.logger import get_logger
-from src.common.server import get_global_server
-from src.plugin_system.apis import config_api
-
-from .send_handler import send_handler
-
-logger = get_logger("napcat_adapter")
-
-router = None
-
-
-def create_router(plugin_config: dict):
- """创建路由器实例"""
- global router
- platform_name = config_api.get_plugin_config(plugin_config, "maibot_server.platform_name", "qq")
-
- # 优先从插件配置读取 host 和 port,如果不存在则回退到全局配置
- config_host = config_api.get_plugin_config(plugin_config, "maibot_server.host", "")
- config_port = config_api.get_plugin_config(plugin_config, "maibot_server.port", 0)
-
- if config_host and config_port > 0:
- # 使用插件配置
- host = config_host
- port = config_port
- logger.debug(f"初始化MoFox-Bot连接,使用插件配置地址:{host}:{port}")
- else:
- # 回退到全局配置
- server = get_global_server()
- host = server.host
- port = server.port
- logger.debug(f"初始化MoFox-Bot连接,使用全局配置地址:{host}:{port}")
-
- route_config = RouteConfig(
- route_config={
- platform_name: TargetConfig(
- url=f"ws://{host}:{port}/ws",
- token=None,
- )
- }
- )
- router = Router(route_config)
- return router
-
-
-async def mmc_start_com(plugin_config: dict | None = None):
- """启动MoFox-Bot连接"""
- logger.debug("正在连接MoFox-Bot")
- if plugin_config:
- create_router(plugin_config)
-
- if router:
- router.register_class_handler(send_handler.handle_message)
- await router.run()
-
-
-async def mmc_stop_com():
- """停止MoFox-Bot连接"""
- if router:
- await router.stop()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/__init__.py b/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/__init__.py
deleted file mode 100644
index d016a5be4..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/__init__.py
+++ /dev/null
@@ -1,92 +0,0 @@
-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" # 撤回消息
-
- def __str__(self) -> str:
- return self.value
-
-
-ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command", "voiceurl", "music", "videourl", "file"]
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/message_handler.py b/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/message_handler.py
deleted file mode 100644
index b7e7b2c25..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/message_handler.py
+++ /dev/null
@@ -1,1183 +0,0 @@
-import base64
-import orjson
-import time
-import uuid
-from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple
-
-import websockets as Server
-from maim_message import (
- BaseMessageInfo,
- FormatInfo,
- GroupInfo,
- MessageBase,
- Seg,
- TemplateInfo,
- UserInfo,
-)
-
-from src.common.logger import get_logger
-from src.plugin_system.apis import config_api
-from src.plugin_system.core.event_manager import event_manager
-
-from ...CONSTS import PLUGIN_NAME
-from ...event_types import NapcatEvent
-from ..response_pool import get_response
-from ..utils import (
- get_group_info,
- get_image_base64,
- get_member_info,
- get_message_detail,
- get_record_detail,
- get_self_info,
-)
-from ..video_handler import get_video_downloader
-from ..websocket_manager import websocket_manager
-from . import ACCEPT_FORMAT, MessageType, RealMessageType
-from .message_sending import message_send_instance
-from .qq_emoji_list import qq_face
-
-logger = get_logger("napcat_adapter")
-
-
-class MessageHandler:
- def __init__(self):
- self.server_connection: Server.ServerConnection = None
- self.bot_id_list: Dict[int, bool] = {}
- self.plugin_config = None
- # 消息缓冲功能已移除
-
- def set_plugin_config(self, plugin_config: dict):
- """设置插件配置"""
- self.plugin_config = plugin_config
- # 消息缓冲功能已移除
-
- async def shutdown(self):
- """关闭消息处理器,清理资源"""
- # 消息缓冲功能已移除
-
- # 消息缓冲功能已移除
-
- async def set_server_connection(self, server_connection: Server.ServerConnection) -> None:
- """设置Napcat连接"""
- self.server_connection = server_connection
-
- def get_server_connection(self) -> Server.ServerConnection:
- """获取当前的服务器连接"""
- # 优先使用直接设置的连接,否则从 websocket_manager 获取
- if self.server_connection:
- return self.server_connection
- return websocket_manager.get_connection()
-
- async def check_allow_to_chat(
- self,
- user_id: int,
- group_id: Optional[int] = None,
- ignore_bot: Optional[bool] = False,
- ignore_global_list: Optional[bool] = False,
- ) -> bool:
- # sourcery skip: hoist-statement-from-if, merge-else-if-into-elif
- """
- 检查是否允许聊天
- Parameters:
- user_id: int: 用户ID
- group_id: int: 群ID
- ignore_bot: bool: 是否忽略机器人检查
- ignore_global_list: bool: 是否忽略全局黑名单检查
- Returns:
- bool: 是否允许聊天
- """
- logger.debug(f"群聊id: {group_id}, 用户id: {user_id}")
- logger.debug("开始检查聊天白名单/黑名单")
-
- # 使用新的权限管理器检查权限
- if group_id:
- # 检查群聊黑白名单
- group_list_type = config_api.get_plugin_config(self.plugin_config, "features.group_list_type", "blacklist")
- group_list = config_api.get_plugin_config(self.plugin_config, "features.group_list", [])
-
- if group_list_type == "whitelist":
- if group_id not in group_list:
- logger.warning("群聊不在白名单中,消息被丢弃")
- return False
- else: # blacklist
- if group_id in group_list:
- logger.warning("群聊在黑名单中,消息被丢弃")
- return False
- else:
- # 检查私聊黑白名单
- private_list_type = config_api.get_plugin_config(
- self.plugin_config, "features.private_list_type", "blacklist"
- )
- private_list = config_api.get_plugin_config(self.plugin_config, "features.private_list", [])
-
- if private_list_type == "whitelist":
- if user_id not in private_list:
- logger.warning("私聊不在白名单中,消息被丢弃")
- return False
- else: # blacklist
- if user_id in private_list:
- logger.warning("私聊在黑名单中,消息被丢弃")
- return False
-
- # 检查全局禁止名单
- ban_user_id = config_api.get_plugin_config(self.plugin_config, "features.ban_user_id", [])
- if not ignore_global_list and user_id in ban_user_id:
- logger.warning("用户在全局黑名单中,消息被丢弃")
- return False
-
- # 检查QQ官方机器人
- ban_qq_bot = config_api.get_plugin_config(self.plugin_config, "features.ban_qq_bot", False)
- if ban_qq_bot and group_id and not ignore_bot:
- logger.debug("开始判断是否为机器人")
- member_info = await get_member_info(self.get_server_connection(), group_id, user_id)
- if member_info:
- is_bot = member_info.get("is_robot")
- if is_bot is None:
- logger.warning("无法获取用户是否为机器人,默认为不是但是不进行更新")
- else:
- if is_bot:
- logger.warning("QQ官方机器人消息拦截已启用,消息被丢弃,新机器人加入拦截名单")
- self.bot_id_list[user_id] = True
- return False
- else:
- self.bot_id_list[user_id] = False
-
- return True
-
- async def handle_raw_message(self, raw_message: dict) -> None:
- # sourcery skip: low-code-quality, remove-unreachable-code
- """
- 从Napcat接受的原始消息处理
-
- Parameters:
- raw_message: dict: 原始消息
- """
-
- # 添加原始消息调试日志,特别关注message字段
- logger.debug(
- f"收到原始消息: message_type={raw_message.get('message_type')}, message_id={raw_message.get('message_id')}"
- )
- logger.debug(f"原始消息内容: {raw_message.get('message', [])}")
-
- # 检查是否包含@或video消息段
- message_segments = raw_message.get("message", [])
- if message_segments:
- for i, seg in enumerate(message_segments):
- seg_type = seg.get("type")
- if seg_type in ["at", "video"]:
- logger.info(f"检测到 {seg_type.upper()} 消息段 [{i}]: {seg}")
- elif seg_type not in ["text", "face", "image"]:
- logger.warning(f"检测到特殊消息段 [{i}]: type={seg_type}, data={seg.get('data', {})}")
-
- message_type: str = raw_message.get("message_type")
- message_id: int = raw_message.get("message_id")
- # message_time: int = raw_message.get("time")
- message_time: float = time.time() # 应可乐要求,现在是float了
-
- template_info: TemplateInfo = None # 模板信息,暂时为空,等待启用
- if message_type == MessageType.private:
- sub_type = raw_message.get("sub_type")
- if sub_type == MessageType.Private.friend:
- sender_info: dict = raw_message.get("sender")
-
- if not await self.check_allow_to_chat(sender_info.get("user_id"), None):
- return None
-
- # 发送者用户信息
- user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name"),
- user_id=sender_info.get("user_id"),
- user_nickname=sender_info.get("nickname"),
- user_cardname=sender_info.get("card"),
- )
-
- # 不存在群信息
- group_info: GroupInfo = None
- elif sub_type == MessageType.Private.group:
- """
- 本部分暂时不做支持,先放着
- """
- logger.warning("群临时消息类型不支持")
- return None
-
- sender_info: dict = raw_message.get("sender")
-
- # 由于临时会话中,Napcat默认不发送成员昵称,所以需要单独获取
- fetched_member_info: dict = await get_member_info(
- self.get_server_connection(),
- raw_message.get("group_id"),
- sender_info.get("user_id"),
- )
- nickname = fetched_member_info.get("nickname") if fetched_member_info else None
- # 发送者用户信息
- user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name"),
- user_id=sender_info.get("user_id"),
- user_nickname=nickname,
- user_cardname=None,
- )
-
- # -------------------这里需要群信息吗?-------------------
-
- # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有
- fetched_group_info: dict = await get_group_info(
- self.get_server_connection(), raw_message.get("group_id")
- )
- group_name = ""
- if fetched_group_info.get("group_name"):
- group_name = fetched_group_info.get("group_name")
-
- group_info: GroupInfo = GroupInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name"),
- group_id=raw_message.get("group_id"),
- group_name=group_name,
- )
-
- else:
- logger.warning(f"私聊消息类型 {sub_type} 不支持")
- return None
- elif message_type == MessageType.group:
- sub_type = raw_message.get("sub_type")
- if sub_type == MessageType.Group.normal:
- sender_info: dict = raw_message.get("sender")
-
- if not await self.check_allow_to_chat(sender_info.get("user_id"), raw_message.get("group_id")):
- return None
-
- # 发送者用户信息
- user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name"),
- user_id=sender_info.get("user_id"),
- user_nickname=sender_info.get("nickname"),
- user_cardname=sender_info.get("card"),
- )
-
- # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有
- fetched_group_info = await get_group_info(self.get_server_connection(), raw_message.get("group_id"))
- group_name: str = None
- if fetched_group_info:
- group_name = fetched_group_info.get("group_name")
-
- group_info: GroupInfo = GroupInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name"),
- group_id=raw_message.get("group_id"),
- group_name=group_name,
- )
-
- else:
- logger.warning(f"群聊消息类型 {sub_type} 不支持")
- return None
-
- # 处理实际信息
- if not raw_message.get("message"):
- logger.warning("原始消息内容为空")
- return None
-
- # 获取Seg列表
- seg_message: List[Seg] = await self.handle_real_message(raw_message)
- if not seg_message:
- logger.warning("处理后消息内容为空")
- return None
-
- # 动态生成 content_format
- content_formats = sorted(list(set(seg.type for seg in seg_message)))
- logger.debug(f"动态生成 content_format: {content_formats}")
- format_info: FormatInfo = FormatInfo(
- content_format=content_formats,
- accept_format=ACCEPT_FORMAT,
- )
-
- additional_config: dict = {}
- if config_api.get_plugin_config(self.plugin_config, "voice.use_tts"):
- additional_config["allow_tts"] = True
-
- # 消息信息
- message_info: BaseMessageInfo = BaseMessageInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name"),
- message_id=message_id,
- time=message_time,
- user_info=user_info,
- group_info=group_info,
- template_info=template_info,
- format_info=format_info,
- additional_config=additional_config,
- )
-
- # 消息缓冲功能已移除,直接处理消息
-
- logger.debug(f"准备发送消息到MoFox-Bot,消息段数量: {len(seg_message)}")
- for i, seg in enumerate(seg_message):
- logger.debug(f"消息段 {i}: type={seg.type}, data={str(seg.data)[:100]}...")
-
- submit_seg: Seg = Seg(
- type="seglist",
- data=seg_message,
- )
- # MessageBase创建
- message_base: MessageBase = MessageBase(
- message_info=message_info,
- message_segment=submit_seg,
- raw_message=raw_message.get("raw_message"),
- )
-
- logger.debug("发送到Maibot处理信息")
- await message_send_instance.message_send(message_base)
-
- async def handle_real_message(self, raw_message: dict, in_reply: bool = False) -> List[Seg] | None:
- # sourcery skip: low-code-quality
- """
- 处理实际消息
- Parameters:
- real_message: dict: 实际消息
- Returns:
- seg_message: list[Seg]: 处理后的消息段列表
- """
- real_message: list = raw_message.get("message")
- if not real_message:
- return None
- seg_message: List[Seg] = []
- for sub_message in real_message:
- sub_message: dict
- sub_message_type = sub_message.get("type")
-
- # 添加详细的消息类型调试信息
- logger.debug(f"处理消息段: type={sub_message_type}, data={sub_message.get('data', {})}")
-
- # 特别关注 at 和 video 消息的识别
- if sub_message_type == "at":
- logger.debug(f"检测到@消息: {sub_message}")
- elif sub_message_type == "video":
- logger.debug(f"检测到VIDEO消息: {sub_message}")
- elif sub_message_type not in ["text", "face", "image", "record"]:
- logger.warning(f"检测到特殊消息类型: {sub_message_type}, 完整消息: {sub_message}")
-
- match sub_message_type:
- case RealMessageType.text:
- ret_seg = await self.handle_text_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.TEXT, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- else:
- logger.warning("text处理失败")
- case RealMessageType.face:
- ret_seg = await self.handle_face_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.FACE, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- else:
- logger.warning("face处理失败或不支持")
- case RealMessageType.reply:
- if not in_reply:
- ret_seg = await self.handle_reply_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.REPLY, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message += ret_seg
- else:
- logger.warning("reply处理失败")
- case RealMessageType.image:
- logger.debug("开始处理图片消息段")
- ret_seg = await self.handle_image_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.IMAGE, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- logger.debug("图片处理成功,添加到消息段")
- else:
- logger.warning("image处理失败")
- logger.debug("图片消息段处理完成")
- case RealMessageType.record:
- ret_seg = await self.handle_record_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.RECORD, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.clear()
- seg_message.append(ret_seg)
- break # 使得消息只有record消息
- else:
- logger.warning("record处理失败或不支持")
- case RealMessageType.video:
- logger.debug(f"开始处理VIDEO消息段: {sub_message}")
- ret_seg = await self.handle_video_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.VIDEO, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- else:
- logger.warning(f"video处理失败,原始消息: {sub_message}")
- case RealMessageType.at:
- logger.debug(f"开始处理AT消息段: {sub_message}")
- ret_seg = await self.handle_at_message(
- sub_message,
- raw_message.get("self_id"),
- raw_message.get("group_id"),
- )
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.AT, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- else:
- logger.warning(f"at处理失败,原始消息: {sub_message}")
- case RealMessageType.rps:
- ret_seg = await self.handle_rps_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.RPS, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- else:
- logger.warning("rps处理失败")
- case RealMessageType.dice:
- ret_seg = await self.handle_dice_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.DICE, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- else:
- logger.warning("dice处理失败")
- case RealMessageType.shake:
- ret_seg = await self.handle_shake_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.SHAKE, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- else:
- logger.warning("shake处理失败")
- case RealMessageType.share:
- print(
- "\n\n哦哦哦噢噢噢哦哦你收到了一个超级无敌SHARE消息,快速速把你刚刚收到的消息截图发到MoFox-Bot群里!!!!\n\n"
- )
- logger.warning("暂时不支持链接解析")
- case RealMessageType.forward:
- messages = await self._get_forward_message(sub_message)
- if not messages:
- logger.warning("转发消息内容为空或获取失败")
- return None
- ret_seg = await self.handle_forward_message(messages)
- if ret_seg:
- seg_message.append(ret_seg)
- else:
- logger.warning("转发消息处理失败")
- case RealMessageType.node:
- print(
- "\n\n哦哦哦噢噢噢哦哦你收到了一个超级无敌NODE消息,快速速把你刚刚收到的消息截图发到MoFox-Bot群里!!!!\n\n"
- )
- logger.warning("不支持转发消息节点解析")
- case RealMessageType.json:
- ret_seg = await self.handle_json_message(sub_message)
- if ret_seg:
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.JSON, permission_group=PLUGIN_NAME, message_seg=ret_seg
- )
- seg_message.append(ret_seg)
- else:
- logger.warning("json处理失败")
- case RealMessageType.file:
- ret_seg = await self.handle_file_message(sub_message)
- if ret_seg:
- # NapcatEvent doesn't have a FILE event yet, so we won't trigger one for now.
- seg_message.append(ret_seg)
- else:
- logger.warning("file处理失败")
- case _:
- logger.warning(f"未知消息类型: {sub_message_type}")
-
- logger.debug(f"handle_real_message完成,处理了{len(real_message)}个消息段,生成了{len(seg_message)}个seg")
- return seg_message
-
- async def handle_text_message(self, raw_message: dict) -> Seg:
- """
- 处理纯文本信息
- Parameters:
- raw_message: dict: 原始消息
- Returns:
- seg_data: Seg: 处理后的消息段
- """
- message_data: dict = raw_message.get("data")
- plain_text: str = message_data.get("text")
- return Seg(type="text", data=plain_text)
-
- async def handle_face_message(self, raw_message: dict) -> Seg | None:
- """
- 处理表情消息
- Parameters:
- raw_message: dict: 原始消息
- Returns:
- seg_data: Seg: 处理后的消息段
- """
- message_data: dict = raw_message.get("data")
- face_raw_id: str = str(message_data.get("id"))
- if face_raw_id in qq_face:
- face_content: str = qq_face.get(face_raw_id)
- return Seg(type="text", data=face_content)
- else:
- logger.warning(f"不支持的表情:{face_raw_id}")
- return None
-
- async def handle_image_message(self, raw_message: dict) -> Seg | None:
- """
- 处理图片消息与表情包消息
- Parameters:
- raw_message: dict: 原始消息
- Returns:
- seg_data: Seg: 处理后的消息段
- """
- message_data: dict = raw_message.get("data")
- image_sub_type = message_data.get("sub_type")
- try:
- image_base64 = await get_image_base64(message_data.get("url"))
- except Exception as e:
- logger.error(f"图片消息处理失败: {str(e)}")
- return None
- if image_sub_type == 0:
- """这部分认为是图片"""
- return Seg(type="image", data=image_base64)
- elif image_sub_type not in [4, 9]:
- """这部分认为是表情包"""
- return Seg(type="emoji", data=image_base64)
- else:
- logger.warning(f"不支持的图片子类型:{image_sub_type}")
- return None
-
- async def handle_at_message(self, raw_message: dict, self_id: int, group_id: int) -> Seg | None:
- # sourcery skip: use-named-expression
- """
- 处理at消息
- Parameters:
- raw_message: dict: 原始消息
- self_id: int: 机器人QQ号
- group_id: int: 群号
- Returns:
- seg_data: Seg: 处理后的消息段
- """
- message_data: dict = raw_message.get("data")
- if message_data:
- qq_id = message_data.get("qq")
- if str(self_id) == str(qq_id):
- logger.debug("机器人被at")
- self_info: dict = await get_self_info(self.get_server_connection())
- if self_info:
- # 返回包含昵称和用户ID的at格式,便于后续处理
- return Seg(type="at", data=f"{self_info.get('nickname')}:{self_info.get('user_id')}")
- else:
- return None
- else:
- member_info: dict = await get_member_info(
- self.get_server_connection(), group_id=group_id, user_id=qq_id
- )
- if member_info:
- # 返回包含昵称和用户ID的at格式,便于后续处理
- return Seg(type="at", data=f"{member_info.get('nickname')}:{member_info.get('user_id')}")
- else:
- return None
-
- async def handle_record_message(self, raw_message: dict) -> Seg | None:
- """
- 处理语音消息
- Parameters:
- raw_message: dict: 原始消息
- Returns:
- seg_data: Seg: 处理后的消息段
- """
- message_data: dict = raw_message.get("data")
- file: str = message_data.get("file")
- if not file:
- logger.warning("语音消息缺少文件信息")
- return None
- try:
- record_detail = await get_record_detail(self.get_server_connection(), file)
- if not record_detail:
- logger.warning("获取语音消息详情失败")
- return None
- audio_base64: str = record_detail.get("base64")
- except Exception as e:
- logger.error(f"语音消息处理失败: {str(e)}")
- return None
- if not audio_base64:
- logger.error("语音消息处理失败,未获取到音频数据")
- return None
- return Seg(type="voice", data=audio_base64)
-
- async def handle_video_message(self, raw_message: dict) -> Seg | None:
- """
- 处理视频消息
- Parameters:
- raw_message: dict: 原始消息
- Returns:
- seg_data: Seg: 处理后的消息段
- """
- message_data: dict = raw_message.get("data")
-
- # 添加详细的调试信息
- logger.debug(f"视频消息原始数据: {raw_message}")
- logger.debug(f"视频消息数据: {message_data}")
-
- # QQ视频消息可能包含url或filePath字段
- video_url = message_data.get("url")
- file_path = message_data.get("filePath") or message_data.get("file_path")
-
- logger.debug(f"视频URL: {video_url}")
- logger.debug(f"视频文件路径: {file_path}")
-
- # 优先使用本地文件路径,其次使用URL
- video_source = file_path if file_path else video_url
-
- if not video_source:
- logger.warning("视频消息缺少URL或文件路径信息")
- logger.warning(f"完整消息数据: {message_data}")
- return None
-
- try:
- # 检查是否为本地文件路径
- if file_path and Path(file_path).exists():
- logger.debug(f"使用本地视频文件: {file_path}")
- # 直接读取本地文件
- with open(file_path, "rb") as f:
- video_data = f.read()
-
- # 将视频数据编码为base64用于传输
- video_base64 = base64.b64encode(video_data).decode("utf-8")
- logger.debug(f"视频文件大小: {len(video_data) / (1024 * 1024):.2f} MB")
-
- # 返回包含详细信息的字典格式
- return Seg(
- type="video",
- data={
- "base64": video_base64,
- "filename": Path(file_path).name,
- "size_mb": len(video_data) / (1024 * 1024),
- },
- )
-
- elif video_url:
- logger.debug(f"使用视频URL下载: {video_url}")
- # 使用video_handler下载视频
- video_downloader = get_video_downloader()
- download_result = await video_downloader.download_video(video_url)
-
- if not download_result["success"]:
- logger.warning(f"视频下载失败: {download_result.get('error', '未知错误')}")
- logger.warning(f"失败的URL: {video_url}")
- return None
-
- # 将视频数据编码为base64用于传输
- video_base64 = base64.b64encode(download_result["data"]).decode("utf-8")
- logger.debug(f"视频下载成功,大小: {len(download_result['data']) / (1024 * 1024):.2f} MB")
-
- # 返回包含详细信息的字典格式
- return Seg(
- type="video",
- data={
- "base64": video_base64,
- "filename": download_result.get("filename", "video.mp4"),
- "size_mb": len(download_result["data"]) / (1024 * 1024),
- "url": video_url,
- },
- )
-
- else:
- logger.warning("既没有有效的本地文件路径,也没有有效的视频URL")
- return None
-
- except Exception as e:
- logger.error(f"视频消息处理失败: {str(e)}")
- logger.error(f"视频源: {video_source}")
- return None
-
- async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None:
- # sourcery skip: move-assign-in-block, use-named-expression
- """
- 处理回复消息
-
- """
- raw_message_data: dict = raw_message.get("data")
- message_id: int = None
- if raw_message_data:
- message_id = raw_message_data.get("id")
- else:
- return None
- message_detail: dict = await get_message_detail(self.get_server_connection(), message_id)
- if not message_detail:
- logger.warning("获取被引用的消息详情失败")
- return None
- reply_message = await self.handle_real_message(message_detail, in_reply=True)
- if reply_message is None:
- reply_message = [Seg(type="text", data="(获取发言内容失败)")]
- sender_info: dict = message_detail.get("sender")
- sender_nickname: str = sender_info.get("nickname")
- sender_id = sender_info.get("user_id")
- seg_message: List[Seg] = []
- if not sender_nickname:
- logger.warning("无法获取被引用的人的昵称,返回默认值")
- seg_message.append(Seg(type="text", data="[回复 未知用户:"))
- else:
- if sender_id:
- seg_message.append(Seg(type="text", data=f"[回复<{sender_nickname}({sender_id})>:"))
- else:
- seg_message.append(Seg(type="text", data=f"[回复<{sender_nickname}>:"))
- seg_message += reply_message
- seg_message.append(Seg(type="text", data="],说:"))
- return seg_message
-
- async def handle_forward_message(self, message_list: list) -> Seg | None:
- """
- 递归处理转发消息,并按照动态方式确定图片处理方式
- Parameters:
- message_list: list: 转发消息列表
- """
- handled_message, image_count = await self._handle_forward_message(message_list, 0)
- handled_message: Seg
- image_count: int
- if not handled_message:
- return None
-
- processed_message: Seg
- if image_count < 5 and image_count > 0:
- # 处理图片数量小于5的情况,此时解析图片为base64
- logger.debug("图片数量小于5,开始解析图片为base64")
- processed_message = await self._recursive_parse_image_seg(handled_message, True)
- elif image_count > 0:
- logger.debug("图片数量大于等于5,开始解析图片为占位符")
- # 处理图片数量大于等于5的情况,此时解析图片为占位符
- processed_message = await self._recursive_parse_image_seg(handled_message, False)
- else:
- # 处理没有图片的情况,此时直接返回
- logger.debug("没有图片,直接返回")
- processed_message = handled_message
-
- # 添加转发消息提示
- forward_hint = Seg(type="text", data="这是一条转发消息:\n")
- return Seg(type="seglist", data=[forward_hint, processed_message])
-
- async def handle_dice_message(self, raw_message: dict) -> Seg:
- message_data: dict = raw_message.get("data", {})
- res = message_data.get("result", "")
- return Seg(type="text", data=f"[扔了一个骰子,点数是{res}]")
-
- async def handle_shake_message(self, raw_message: dict) -> Seg:
- return Seg(type="text", data="[向你发送了窗口抖动,现在你的屏幕猛烈地震了一下!]")
-
- async def handle_json_message(self, raw_message: dict) -> Seg:
- """
- 处理JSON消息
- Parameters:
- raw_message: dict: 原始消息
- Returns:
- seg_data: Seg: 处理后的消息段
- """
- message_data: dict = raw_message.get("data", {})
- json_data = message_data.get("data", "")
-
- # 检查JSON消息格式
- if not message_data or "data" not in message_data:
- logger.warning("JSON消息格式不正确")
- return Seg(type="json", data=orjson.dumps(message_data).decode('utf-8'))
-
- try:
- # 尝试将json_data解析为Python对象
- nested_data = orjson.loads(json_data)
-
- # 检查是否是机器人自己上传文件的回声
- if self._is_file_upload_echo(nested_data):
- logger.info("检测到机器人发送文件的回声消息,将作为文件消息处理")
- # 从回声消息中提取文件信息
- file_info = self._extract_file_info_from_echo(nested_data)
- if file_info:
- # 构建一个与普通文件消息格式相同的字典
- file_message_dict = {"type": "file", "data": file_info}
- return await self.handle_file_message(file_message_dict)
-
- # 检查是否是QQ小程序分享消息
- if "app" in nested_data and "com.tencent.miniapp" in str(nested_data.get("app", "")):
- logger.debug("检测到QQ小程序分享消息,开始提取信息")
-
- # 提取目标字段
- extracted_info = {}
-
- # 提取 meta.detail_1 中的信息
- meta = nested_data.get("meta", {})
- detail_1 = meta.get("detail_1", {})
-
- if detail_1:
- extracted_info["title"] = detail_1.get("title", "")
- extracted_info["desc"] = detail_1.get("desc", "")
- qqdocurl = detail_1.get("qqdocurl", "")
-
- # 从qqdocurl中提取b23.tv短链接
- if qqdocurl and "b23.tv" in qqdocurl:
- # 查找b23.tv链接的起始位置
- start_pos = qqdocurl.find("https://b23.tv/")
- if start_pos != -1:
- # 提取从https://b23.tv/开始的部分
- b23_part = qqdocurl[start_pos:]
- # 查找第一个?的位置,截取到?之前
- question_pos = b23_part.find("?")
- if question_pos != -1:
- extracted_info["short_url"] = b23_part[:question_pos]
- else:
- extracted_info["short_url"] = b23_part
- else:
- extracted_info["short_url"] = qqdocurl
- else:
- extracted_info["short_url"] = qqdocurl
-
- # 如果成功提取到关键信息,返回格式化的文本
- if extracted_info.get("title") or extracted_info.get("desc") or extracted_info.get("short_url"):
- content_parts = []
-
- if extracted_info.get("title"):
- content_parts.append(f"来源: {extracted_info['title']}")
-
- if extracted_info.get("desc"):
- content_parts.append(f"标题: {extracted_info['desc']}")
-
- if extracted_info.get("short_url"):
- content_parts.append(f"链接: {extracted_info['short_url']}")
-
- formatted_content = "\n".join(content_parts)
- return Seg(
- type="text",
- data=f"这是一条小程序分享消息,可以根据来源,考虑使用对应解析工具\n{formatted_content}",
- )
-
- # 检查是否是音乐分享 (QQ音乐类型)
- if nested_data.get("view") == "music" and "com.tencent.music" in str(nested_data.get("app", "")):
- meta = nested_data.get("meta", {})
- music = meta.get("music", {})
- if music:
- tag = music.get("tag", "未知来源")
- logger.debug(f"检测到【{tag}】音乐分享消息 (music view),开始提取信息")
-
- title = music.get("title", "未知歌曲")
- desc = music.get("desc", "未知艺术家")
- jump_url = music.get("jumpUrl", "")
- preview_url = music.get("preview", "")
-
- artist = "未知艺术家"
- song_title = title
-
- if "网易云音乐" in tag:
- artist = desc
- elif "QQ音乐" in tag:
- if " - " in title:
- parts = title.split(" - ", 1)
- song_title = parts[0]
- artist = parts[1]
- else:
- artist = desc
-
- formatted_content = (
- f"这是一张来自【{tag}】的音乐分享卡片:\n"
- f"歌曲: {song_title}\n"
- f"艺术家: {artist}\n"
- f"跳转链接: {jump_url}\n"
- f"封面图: {preview_url}"
- )
- return Seg(type="text", data=formatted_content)
-
- # 检查是否是新闻/图文分享 (网易云音乐可能伪装成这种)
- elif nested_data.get("view") == "news" and "com.tencent.tuwen" in str(nested_data.get("app", "")):
- meta = nested_data.get("meta", {})
- news = meta.get("news", {})
- if news and "网易云音乐" in news.get("tag", ""):
- tag = news.get("tag")
- logger.debug(f"检测到【{tag}】音乐分享消息 (news view),开始提取信息")
-
- title = news.get("title", "未知歌曲")
- desc = news.get("desc", "未知艺术家")
- jump_url = news.get("jumpUrl", "")
- preview_url = news.get("preview", "")
-
- formatted_content = (
- f"这是一张来自【{tag}】的音乐分享卡片:\n"
- f"标题: {title}\n"
- f"描述: {desc}\n"
- f"跳转链接: {jump_url}\n"
- f"封面图: {preview_url}"
- )
- return Seg(type="text", data=formatted_content)
-
- # 如果没有提取到关键信息,返回None
- return None
-
- except orjson.JSONDecodeError:
- # 如果解析失败,我们假设它不是我们关心的任何一种结构化JSON,
- # 而是普通的文本或者无法解析的格式。
- logger.debug(f"无法将data字段解析为JSON: {json_data}")
- return None
- except Exception as e:
- logger.error(f"处理JSON消息时发生未知错误: {e}")
- return None
-
- async def handle_file_message(self, raw_message: dict) -> Seg | None:
- """
- 处理文件消息
- Parameters:
- raw_message: dict: 原始消息
- Returns:
- seg_data: Seg: 处理后的消息段
- """
- message_data: dict = raw_message.get("data")
- if not message_data:
- logger.warning("文件消息缺少 data 字段")
- return None
-
- # 提取文件信息
- file_name = message_data.get("file")
- file_size = message_data.get("file_size")
- file_id = message_data.get("file_id")
-
- logger.info(f"收到文件消息: name={file_name}, size={file_size}, id={file_id}")
-
- # 将文件信息打包成字典
- file_data = {
- "name": file_name,
- "size": file_size,
- "id": file_id,
- }
-
- return Seg(type="file", data=file_data)
-
- def _is_file_upload_echo(self, nested_data: Any) -> bool:
- """检查一个JSON对象是否是机器人自己上传文件的回声消息"""
- if not isinstance(nested_data, dict):
- return False
-
- # 检查 'app' 和 'meta' 字段是否存在
- if "app" not in nested_data or "meta" not in nested_data:
- return False
-
- # 检查 'app' 字段是否包含 'com.tencent.miniapp'
- if "com.tencent.miniapp" not in str(nested_data.get("app", "")):
- return False
-
- # 检查 'meta' 内部的 'detail_1' 的 'busi_id' 是否为 '1014'
- meta = nested_data.get("meta", {})
- detail_1 = meta.get("detail_1", {})
- if detail_1.get("busi_id") == "1014":
- return True
-
- return False
-
- def _extract_file_info_from_echo(self, nested_data: dict) -> Optional[dict]:
- """从文件上传的回声消息中提取文件信息"""
- try:
- meta = nested_data.get("meta", {})
- detail_1 = meta.get("detail_1", {})
-
- # 文件名在 'desc' 字段
- file_name = detail_1.get("desc")
-
- # 文件大小在 'summary' 字段,格式为 "大小:1.7MB"
- summary = detail_1.get("summary", "")
- file_size_str = summary.replace("大小:", "").strip() # 移除前缀和空格
-
- # QQ API有时返回的大小不标准,这里我们只提取它给的字符串
- # 实际大小已经由Napcat在发送时记录,这里主要是为了保持格式一致
-
- if file_name and file_size_str:
- return {"file": file_name, "file_size": file_size_str, "file_id": None} # file_id在回声中不可用
- except Exception as e:
- logger.error(f"从文件回声中提取信息失败: {e}")
-
- return None
-
- async def handle_rps_message(self, raw_message: dict) -> Seg:
- message_data: dict = raw_message.get("data", {})
- res = message_data.get("result", "")
- if res == "1":
- shape = "布"
- elif res == "2":
- shape = "剪刀"
- else:
- shape = "石头"
- return Seg(type="text", data=f"[发送了一个魔法猜拳表情,结果是:{shape}]")
-
- async def _recursive_parse_image_seg(self, seg_data: Seg, to_image: bool) -> Seg:
- # sourcery skip: merge-else-if-into-elif
- if to_image:
- if seg_data.type == "seglist":
- new_seg_list = []
- for i_seg in seg_data.data:
- parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image)
- new_seg_list.append(parsed_seg)
- return Seg(type="seglist", data=new_seg_list)
- elif seg_data.type == "image":
- image_url = seg_data.data
- try:
- encoded_image = await get_image_base64(image_url)
- except Exception as e:
- logger.error(f"图片处理失败: {str(e)}")
- return Seg(type="text", data="[图片]")
- return Seg(type="image", data=encoded_image)
- elif seg_data.type == "emoji":
- image_url = seg_data.data
- try:
- encoded_image = await get_image_base64(image_url)
- except Exception as e:
- logger.error(f"图片处理失败: {str(e)}")
- return Seg(type="text", data="[表情包]")
- return Seg(type="emoji", data=encoded_image)
- else:
- logger.debug(f"不处理类型: {seg_data.type}")
- return seg_data
- else:
- if seg_data.type == "seglist":
- new_seg_list = []
- for i_seg in seg_data.data:
- parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image)
- new_seg_list.append(parsed_seg)
- return Seg(type="seglist", data=new_seg_list)
- elif seg_data.type == "image":
- return Seg(type="text", data="[图片]")
- elif seg_data.type == "emoji":
- return Seg(type="text", data="[动画表情]")
- else:
- logger.debug(f"不处理类型: {seg_data.type}")
- return seg_data
-
- async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple[Seg, int] | Tuple[None, int]:
- # sourcery skip: low-code-quality
- """
- 递归处理实际转发消息
- Parameters:
- message_list: list: 转发消息列表,首层对应messages字段,后面对应content字段
- layer: int: 当前层级
- Returns:
- seg_data: Seg: 处理后的消息段
- image_count: int: 图片数量
- """
- seg_list: List[Seg] = []
- image_count = 0
- if message_list is None:
- return None, 0
- for sub_message in message_list:
- sub_message: dict
- sender_info: dict = sub_message.get("sender")
- user_nickname: str = sender_info.get("nickname", "QQ用户")
- user_nickname_str = f"【{user_nickname}】:"
- break_seg = Seg(type="text", data="\n")
- message_of_sub_message_list: List[Dict[str, Any]] = sub_message.get("message")
- if not message_of_sub_message_list:
- logger.warning("转发消息内容为空")
- continue
- message_of_sub_message = message_of_sub_message_list[0]
- if message_of_sub_message.get("type") == RealMessageType.forward:
- if layer >= 3:
- full_seg_data = Seg(
- type="text",
- data=("--" * layer) + f"【{user_nickname}】:【转发消息】\n",
- )
- else:
- sub_message_data = message_of_sub_message.get("data")
- if not sub_message_data:
- continue
- contents = sub_message_data.get("content")
- seg_data, count = await self._handle_forward_message(contents, layer + 1)
- image_count += count
- head_tip = Seg(
- type="text",
- data=("--" * layer) + f"【{user_nickname}】: 合并转发消息内容:\n",
- )
- full_seg_data = Seg(type="seglist", data=[head_tip, seg_data])
- seg_list.append(full_seg_data)
- elif message_of_sub_message.get("type") == RealMessageType.text:
- sub_message_data = message_of_sub_message.get("data")
- if not sub_message_data:
- continue
- text_message = sub_message_data.get("text")
- seg_data = Seg(type="text", data=text_message)
- data_list: List[Any] = []
- if layer > 0:
- data_list = [
- Seg(type="text", data=("--" * layer) + user_nickname_str),
- seg_data,
- break_seg,
- ]
- else:
- data_list = [
- Seg(type="text", data=user_nickname_str),
- seg_data,
- break_seg,
- ]
- seg_list.append(Seg(type="seglist", data=data_list))
- elif message_of_sub_message.get("type") == RealMessageType.image:
- image_count += 1
- image_data = message_of_sub_message.get("data")
- sub_type = image_data.get("sub_type")
- image_url = image_data.get("url")
- data_list: List[Any] = []
- if sub_type == 0:
- seg_data = Seg(type="image", data=image_url)
- else:
- seg_data = Seg(type="emoji", data=image_url)
- if layer > 0:
- data_list = [
- Seg(type="text", data=("--" * layer) + user_nickname_str),
- seg_data,
- break_seg,
- ]
- else:
- data_list = [
- Seg(type="text", data=user_nickname_str),
- seg_data,
- break_seg,
- ]
- full_seg_data = Seg(type="seglist", data=data_list)
- seg_list.append(full_seg_data)
- return Seg(type="seglist", data=seg_list), image_count
-
- async def _get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None:
- forward_message_data: Dict = raw_message.get("data")
- if not forward_message_data:
- logger.warning("转发消息内容为空")
- return None
- forward_message_id = forward_message_data.get("id")
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps(
- {
- "action": "get_forward_msg",
- "params": {"message_id": forward_message_id},
- "echo": request_uuid,
- }
- ).decode('utf-8')
- try:
- connection = self.get_server_connection()
- if not connection:
- logger.error("没有可用的 WebSocket 连接")
- return None
- await connection.send(payload)
- response: dict = await get_response(request_uuid)
- except TimeoutError:
- logger.error("获取转发消息超时")
- return None
- except Exception as e:
- logger.error(f"获取转发消息失败: {str(e)}")
- return None
- logger.debug(
- f"转发消息原始格式:{orjson.dumps(response).decode('utf-8')[:80]}..."
- if len(orjson.dumps(response).decode('utf-8')) > 80
- else orjson.dumps(response).decode('utf-8')
- )
- response_data: Dict = response.get("data")
- if not response_data:
- logger.warning("转发消息内容为空或获取失败")
- return None
- return response_data.get("messages")
-
- # 消息缓冲功能已移除
-
-
-message_handler = MessageHandler()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/message_sending.py b/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/message_sending.py
deleted file mode 100644
index b64db620e..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/message_sending.py
+++ /dev/null
@@ -1,120 +0,0 @@
-import asyncio
-
-from maim_message import MessageBase, Router
-
-from src.common.logger import get_logger
-from src.plugin_system.apis import config_api
-
-from ..message_chunker import chunker
-
-logger = get_logger("napcat_adapter")
-
-
-class MessageSending:
- """
- 负责把消息发送到麦麦
- """
-
- maibot_router: Router = None
- plugin_config = None
- _connection_retries = 0
- _max_retries = 3
-
- def __init__(self):
- pass
-
- def set_plugin_config(self, plugin_config: dict):
- """设置插件配置"""
- self.plugin_config = plugin_config
-
- async def _attempt_reconnect(self):
- """尝试重新连接MoFox-Bot router"""
- if self._connection_retries < self._max_retries:
- self._connection_retries += 1
- logger.warning(f"尝试重新连接MoFox-Bot router (第{self._connection_retries}次)")
- try:
- # 重新导入router
- from ..mmc_com_layer import router
-
- self.maibot_router = router
- if self.maibot_router is not None:
- logger.info("MoFox-Bot router重连成功")
- self._connection_retries = 0 # 重置重试计数
- return True
- except Exception as e:
- logger.error(f"重连失败: {e}")
- else:
- logger.error(f"已达到最大重连次数({self._max_retries}),停止重试")
- return False
-
- async def message_send(self, message_base: MessageBase) -> bool:
- """
- 发送消息(Ada -> MMC 方向,需要实现切片)
- Parameters:
- message_base: MessageBase: 消息基类,包含发送目标和消息内容等信息
- """
- try:
- # 检查maibot_router是否已初始化
- if self.maibot_router is None:
- logger.warning("MoFox-Bot router未初始化,尝试重新连接")
- if not await self._attempt_reconnect():
- logger.error("MoFox-Bot router重连失败,无法发送消息")
- logger.error("请检查与MoFox-Bot之间的连接")
- return False
- # 检查是否需要切片发送
- message_dict = message_base.to_dict()
-
- if chunker.should_chunk_message(message_dict):
- logger.info("消息过大,进行切片发送到 MoFox-Bot")
-
- # 切片消息
- chunks = chunker.chunk_message(message_dict)
-
- # 逐个发送切片
- for i, chunk in enumerate(chunks):
- logger.debug(f"发送切片 {i + 1}/{len(chunks)} 到 MoFox-Bot")
-
- # 获取对应的客户端并发送切片
- platform = message_base.message_info.platform
-
- # 再次检查router状态(防止运行时被重置)
- if self.maibot_router is None or not hasattr(self.maibot_router, "clients"):
- logger.warning("MoFox-Bot router连接已断开,尝试重新连接")
- if not await self._attempt_reconnect():
- logger.error("MoFox-Bot router重连失败,切片发送中止")
- return False
-
- if platform not in self.maibot_router.clients:
- logger.error(f"平台 {platform} 未连接")
- return False
-
- client = self.maibot_router.clients[platform]
- send_status = await client.send_message(chunk)
-
- if not send_status:
- logger.error(f"发送切片 {i + 1}/{len(chunks)} 失败")
- return False
-
- # 使用配置中的延迟时间
- if i < len(chunks) - 1 and self.plugin_config:
- delay_ms = config_api.get_plugin_config(self.plugin_config, "slicing.delay_ms", 10)
- delay_seconds = delay_ms / 1000.0
- logger.debug(f"切片发送延迟: {delay_ms}毫秒")
- await asyncio.sleep(delay_seconds)
-
- logger.debug("所有切片发送完成")
- return True
- else:
- # 直接发送小消息
- send_status = await self.maibot_router.send_message(message_base)
- if not send_status:
- raise RuntimeError("可能是路由未正确配置或连接异常")
- return send_status
-
- except Exception as e:
- logger.error(f"发送消息失败: {str(e)}")
- logger.error("请检查与MoFox-Bot之间的连接")
- return False
-
-
-message_send_instance = MessageSending()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/meta_event_handler.py b/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/meta_event_handler.py
deleted file mode 100644
index 12998239c..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/meta_event_handler.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import asyncio
-import time
-
-from src.common.logger import get_logger
-from src.plugin_system.apis import config_api
-
-from . import MetaEventType
-
-logger = get_logger("napcat_adapter")
-
-
-class MetaEventHandler:
- """
- 处理Meta事件
- """
-
- def __init__(self):
- self.interval = 5.0 # 默认值,稍后通过set_plugin_config设置
- self._interval_checking = False
- self.plugin_config = None
-
- def set_plugin_config(self, plugin_config: dict):
- """设置插件配置"""
- self.plugin_config = plugin_config
- # 更新interval值
- self.interval = (
- config_api.get_plugin_config(self.plugin_config, "napcat_server.heartbeat_interval", 5000) / 1000
- )
-
- async def handle_meta_event(self, message: dict) -> None:
- event_type = message.get("meta_event_type")
- if event_type == MetaEventType.lifecycle:
- sub_type = message.get("sub_type")
- if sub_type == MetaEventType.Lifecycle.connect:
- self_id = message.get("self_id")
- self.last_heart_beat = time.time()
- logger.info(f"Bot {self_id} 连接成功")
- # 不在连接时立即启动心跳检查,等第一个心跳包到达后再启动
- elif event_type == MetaEventType.heartbeat:
- if message["status"].get("online") and message["status"].get("good"):
- self_id = message.get("self_id")
- if not self._interval_checking and self_id:
- # 第一次收到心跳包时才启动心跳检查
- asyncio.create_task(self.check_heartbeat(self_id))
- self.last_heart_beat = time.time()
- interval = message.get("interval")
- if interval:
- self.interval = interval / 1000
- else:
- self_id = message.get("self_id")
- logger.warning(f"Bot {self_id} Napcat 端异常!")
-
- async def check_heartbeat(self, id: int) -> None:
- self._interval_checking = True
- while True:
- now_time = time.time()
- if now_time - self.last_heart_beat > self.interval * 2:
- logger.error(f"Bot {id} 可能发生了连接断开,被下线,或者Napcat卡死!")
- break
- else:
- logger.debug("心跳正常")
- await asyncio.sleep(self.interval)
-
-
-meta_event_handler = MetaEventHandler()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/notice_handler.py b/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/notice_handler.py
deleted file mode 100644
index 1f6bf104e..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/notice_handler.py
+++ /dev/null
@@ -1,705 +0,0 @@
-import asyncio
-import orjson
-import time
-from typing import ClassVar, Optional, Tuple
-
-import websockets as Server
-from maim_message import BaseMessageInfo, FormatInfo, GroupInfo, MessageBase, Seg, UserInfo
-
-from src.common.logger import get_logger
-from src.plugin_system.apis import config_api
-
-from ...CONSTS import PLUGIN_NAME, QQ_FACE
-from ..database import BanUser, is_identical, napcat_db
-from ..utils import (
- get_group_info,
- get_member_info,
- get_self_info,
- get_stranger_info,
- read_ban_list,
-)
-from ..websocket_manager import websocket_manager
-from . import ACCEPT_FORMAT, NoticeType
-from .message_handler import message_handler
-from .message_sending import message_send_instance
-
-logger = get_logger("napcat_adapter")
-
-notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=100)
-unsuccessful_notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=3)
-
-
-class NoticeHandler:
- banned_list: ClassVar[list[BanUser]] = [] # 当前仍在禁言中的用户列表
- lifted_list: ClassVar[list[BanUser]] = [] # 已经自然解除禁言
-
- def __init__(self):
- self.server_connection: Server.ServerConnection | None = None
- self.last_poke_time: float = 0.0 # 记录最后一次针对机器人的戳一戳时间
- self.plugin_config = None
-
- def set_plugin_config(self, plugin_config: dict):
- """设置插件配置"""
- self.plugin_config = plugin_config
-
- async def set_server_connection(self, server_connection: Server.ServerConnection) -> None:
- """设置Napcat连接"""
- self.server_connection = server_connection
-
- while self.server_connection.state != Server.State.OPEN:
- await asyncio.sleep(0.5)
- self.banned_list, self.lifted_list = await read_ban_list(self.server_connection)
-
- asyncio.create_task(self.auto_lift_detect())
- asyncio.create_task(self.send_notice())
- asyncio.create_task(self.handle_natural_lift())
-
- def get_server_connection(self) -> Server.ServerConnection:
- """获取当前的服务器连接"""
- # 优先使用直接设置的连接,否则从 websocket_manager 获取
- if self.server_connection:
- return self.server_connection
- return websocket_manager.get_connection()
-
- async def _ban_operation(self, group_id: int, user_id: Optional[int] = None, lift_time: Optional[int] = None) -> None:
- """
- 将用户禁言记录添加到self.banned_list中
- 如果是全体禁言,则user_id为0
- """
- if user_id is None:
- user_id = 0 # 使用0表示全体禁言
- lift_time = -1
- ban_record = BanUser(user_id=user_id, group_id=group_id, lift_time=lift_time)
- for record in list(self.banned_list):
- if is_identical(record, ban_record):
- self.banned_list.remove(record)
- self.banned_list.append(ban_record)
- await napcat_db.create_ban_record(ban_record) # 更新
- return
- self.banned_list.append(ban_record)
- await napcat_db.create_ban_record(ban_record) # 新建
-
- async def _lift_operation(self, group_id: int, user_id: Optional[int] = None) -> None:
- """
- 从self.lifted_group_list中移除已经解除全体禁言的群
- """
- if user_id is None:
- user_id = 0 # 使用0表示全体禁言
- ban_record = BanUser(user_id=user_id, group_id=group_id, lift_time=-1)
- self.lifted_list.append(ban_record)
- # 从被禁言列表里移除对应记录
- for record in list(self.banned_list):
- if is_identical(record, ban_record):
- self.banned_list.remove(record)
- break
- await napcat_db.delete_ban_record(ban_record)
-
- async def handle_notice(self, raw_message: dict) -> None:
- notice_type = raw_message.get("notice_type")
- # message_time: int = raw_message.get("time")
- message_time: float = time.time() # 应可乐要求,现在是float了
-
- self_id = raw_message.get("self_id")
- group_id = raw_message.get("group_id")
- user_id = raw_message.get("user_id")
- target_id = raw_message.get("target_id")
-
- handled_message: Seg = None
- user_info: UserInfo = None
- system_notice: bool = False
-
- match notice_type:
- case NoticeType.friend_recall:
- logger.info("好友撤回一条消息")
- logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}")
- logger.warning("暂时不支持撤回消息处理")
- case NoticeType.group_recall:
- logger.info("群内用户撤回一条消息")
- logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}")
- logger.warning("暂时不支持撤回消息处理")
- case NoticeType.notify:
- sub_type = raw_message.get("sub_type")
- match sub_type:
- case NoticeType.Notify.poke:
- if config_api.get_plugin_config(self.plugin_config, "features.enable_poke", True) and await message_handler.check_allow_to_chat(
- user_id, group_id, False, False
- ):
- logger.debug("处理戳一戳消息")
- handled_message, user_info = await self.handle_poke_notify(raw_message, group_id, user_id)
- else:
- logger.warning("戳一戳消息被禁用,取消戳一戳处理")
- case NoticeType.Notify.input_status:
- from src.plugin_system.core.event_manager import event_manager
-
- from ...event_types import NapcatEvent
-
- event_manager.emit_event(NapcatEvent.ON_RECEIVED.FRIEND_INPUT, permission_group=PLUGIN_NAME)
- case _:
- logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}")
- case NoticeType.group_msg_emoji_like:
- # 该事件转移到 handle_group_emoji_like_notify函数内触发
- if config_api.get_plugin_config(self.plugin_config, "features.enable_emoji_like", True):
- logger.debug("处理群聊表情回复")
- handled_message, user_info = await self.handle_group_emoji_like_notify(raw_message,group_id,user_id)
- else:
- logger.warning("群聊表情回复被禁用,取消群聊表情回复处理")
- case NoticeType.group_ban:
- sub_type = raw_message.get("sub_type")
- match sub_type:
- case NoticeType.GroupBan.ban:
- if not await message_handler.check_allow_to_chat(user_id, group_id, True, False):
- return None
- logger.info("处理群禁言")
- handled_message, user_info = await self.handle_ban_notify(raw_message, group_id)
- system_notice = True
- case NoticeType.GroupBan.lift_ban:
- if not await message_handler.check_allow_to_chat(user_id, group_id, True, False):
- return None
- logger.info("处理解除群禁言")
- handled_message, user_info = await self.handle_lift_ban_notify(raw_message, group_id)
- system_notice = True
- case _:
- logger.warning(f"不支持的group_ban类型: {notice_type}.{sub_type}")
- case NoticeType.group_upload:
- logger.info("群文件上传")
- if user_id == self_id:
- logger.info("检测到机器人自己上传文件,忽略此通知")
- return None
- if not await message_handler.check_allow_to_chat(user_id, group_id, False, False):
- return None
- handled_message, user_info = await self.handle_group_upload_notify(raw_message, group_id, user_id, self_id)
- case _:
- logger.warning(f"不支持的notice类型: {notice_type}")
- return None
- if not handled_message or not user_info:
- logger.warning("notice处理失败或不支持")
- return None
-
- group_info: GroupInfo | None = None
- if group_id:
- fetched_group_info = await get_group_info(self.get_server_connection(), group_id)
- group_name: str | None = None
- if fetched_group_info:
- group_name = fetched_group_info.get("group_name")
- else:
- logger.warning("无法获取notice消息所在群的名称")
- group_info = GroupInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- group_id=group_id,
- group_name=group_name,
- )
-
- # 准备additional_config,包含notice标志
- notice_config = {
- "is_notice": system_notice, # 禁言/解禁是系统通知
- "is_public_notice": False, # 群内notice,非公共
- "target_id": target_id, # 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁
- }
-
- # 根据notice_type设置notice_type字段
- if system_notice:
- sub_type = raw_message.get("sub_type")
- if notice_type == NoticeType.group_ban:
- if sub_type == NoticeType.GroupBan.ban:
- user_id_in_ban = raw_message.get("user_id")
- if user_id_in_ban == 0:
- notice_config["notice_type"] = "group_whole_ban"
- else:
- notice_config["notice_type"] = "group_ban"
- elif sub_type == NoticeType.GroupBan.lift_ban:
- user_id_in_ban = raw_message.get("user_id")
- if user_id_in_ban == 0:
- notice_config["notice_type"] = "group_whole_lift_ban"
- else:
- notice_config["notice_type"] = "group_lift_ban"
- elif notice_type == NoticeType.notify:
- sub_type = raw_message.get("sub_type")
- if sub_type == NoticeType.Notify.poke:
- notice_config["notice_type"] = "poke"
- notice_config["is_notice"] = True # 戳一戳也是notice
- elif notice_type == NoticeType.group_msg_emoji_like:
- notice_config["notice_type"] = "emoji_like"
- notice_config["is_notice"] = True # 表情回复也是notice
- elif notice_type == NoticeType.group_upload:
- notice_config["notice_type"] = "group_upload"
- notice_config["is_notice"] = True # 文件上传也是notice
-
- message_info: BaseMessageInfo = BaseMessageInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- message_id="notice",
- time=message_time,
- user_info=user_info,
- group_info=group_info,
- template_info=None,
- format_info=FormatInfo(
- content_format=["text", "notify"],
- accept_format=ACCEPT_FORMAT,
- ),
- additional_config=notice_config, # 字典而不是JSON字符串
- )
-
- message_base: MessageBase = MessageBase(
- message_info=message_info,
- message_segment=handled_message,
- raw_message=orjson.dumps(raw_message).decode('utf-8'),
- )
-
- if system_notice:
- await self.put_notice(message_base)
- return None
- else:
- logger.debug("发送到Maibot处理通知信息")
- await message_send_instance.message_send(message_base)
- return None
-
- async def handle_poke_notify(
- self, raw_message: dict, group_id: int, user_id: int
- ) -> Tuple[Seg | None, UserInfo | None]:
- # sourcery skip: merge-comparisons, merge-duplicate-blocks, remove-redundant-if, remove-unnecessary-else, swap-if-else-branches
- self_info: dict = await get_self_info(self.get_server_connection())
-
- if not self_info:
- logger.error("自身信息获取失败")
- return None, None
-
- self_id = raw_message.get("self_id")
- target_id = raw_message.get("target_id")
-
- # 防抖检查:如果是针对机器人的戳一戳,检查防抖时间
- if self_id == target_id:
- current_time = time.time()
- debounce_seconds = config_api.get_plugin_config(self.plugin_config, "features.poke_debounce_seconds", 2.0)
-
- if self.last_poke_time > 0:
- time_diff = current_time - self.last_poke_time
- if time_diff < debounce_seconds:
- logger.debug(f"戳一戳防抖:用户 {user_id} 的戳一戳被忽略(距离上次戳一戳 {time_diff:.2f} 秒)")
- return None, None
-
- # 记录这次戳一戳的时间
- self.last_poke_time = current_time
-
- target_name: str = None
- raw_info: list = raw_message.get("raw_info")
-
- if group_id:
- user_qq_info: dict = await get_member_info(self.get_server_connection(), group_id, user_id)
- else:
- user_qq_info: dict = await get_stranger_info(self.get_server_connection(), user_id)
- if user_qq_info:
- user_name = user_qq_info.get("nickname")
- user_cardname = user_qq_info.get("card")
- else:
- user_name = "QQ用户"
- user_cardname = "QQ用户"
- logger.debug("无法获取戳一戳对方的用户昵称")
-
- # 计算Seg
- if self_id == target_id:
- display_name = ""
- target_name = self_info.get("nickname")
-
- elif self_id == user_id:
- # 让ada不发送麦麦戳别人的消息
- return None, None
-
- else:
- # 如果配置为忽略不是针对自己的戳一戳,则直接返回None
- if config_api.get_plugin_config(self.plugin_config, "features.ignore_non_self_poke", False):
- logger.debug("忽略不是针对自己的戳一戳消息")
- return None, None
-
- # 老实说这一步判定没啥意义,毕竟私聊是没有其他人之间的戳一戳,但是感觉可以有这个判定来强限制群聊环境
- if group_id:
- fetched_member_info: dict = await get_member_info(self.get_server_connection(), group_id, target_id)
- if fetched_member_info:
- target_name = fetched_member_info.get("nickname")
- else:
- target_name = "QQ用户"
- logger.debug("无法获取被戳一戳方的用户昵称")
- display_name = user_name
- else:
- return None, None
-
- first_txt: str = "戳了戳"
- second_txt: str = ""
- try:
- first_txt = raw_info[2].get("txt", "戳了戳")
- second_txt = raw_info[4].get("txt", "")
- except Exception as e:
- logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本")
-
- user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- user_id=user_id,
- user_nickname=user_name,
- user_cardname=user_cardname,
- )
-
- seg_data: Seg = Seg(
- type="text",
- data=f"{display_name}{first_txt}{target_name}{second_txt}(这是QQ的一个功能,用于提及某人,但没那么明显)",
- )
- return seg_data, user_info
-
- async def handle_group_emoji_like_notify(self, raw_message: dict, group_id: int, user_id: int):
- if not group_id:
- logger.error("群ID不能为空,无法处理群聊表情回复通知")
- return None, None
-
- user_qq_info: dict = await get_member_info(self.get_server_connection(), group_id, user_id)
- if user_qq_info:
- user_name = user_qq_info.get("nickname")
- user_cardname = user_qq_info.get("card")
- else:
- user_name = "QQ用户"
- user_cardname = "QQ用户"
- logger.debug("无法获取表情回复对方的用户昵称")
-
- from src.plugin_system.core.event_manager import event_manager
-
- from ...event_types import NapcatEvent
-
- target_message = await event_manager.trigger_event(NapcatEvent.MESSAGE.GET_MSG,message_id=raw_message.get("message_id",""))
- target_message_text = target_message.get_message_result().get("data",{}).get("raw_message","")
- if not target_message:
- logger.error("未找到对应消息")
- return None, None
- if len(target_message_text) > 15:
- target_message_text = target_message_text[:15] + "..."
-
- user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- user_id=user_id,
- user_nickname=user_name,
- user_cardname=user_cardname,
- )
-
- like_emoji_id = raw_message.get("likes")[0].get("emoji_id")
- event_manager.emit_event(
- NapcatEvent.ON_RECEIVED.EMOJI_LIEK,
- permission_group=PLUGIN_NAME,
- group_id=group_id,
- user_id=user_id,
- message_id=raw_message.get("message_id",""),
- emoji_id=like_emoji_id
- )
- seg_data = Seg(type="text",data=f"{user_name}使用Emoji表情{QQ_FACE.get(like_emoji_id,"")}回复了你的消息[{target_message_text}]")
- return seg_data, user_info
-
- async def handle_group_upload_notify(self, raw_message: dict, group_id: int, user_id: int, self_id: int):
- if not group_id:
- logger.error("群ID不能为空,无法处理群文件上传通知")
- return None, None
-
- user_qq_info: dict = await get_member_info(self.get_server_connection(), group_id, user_id)
- if user_qq_info:
- user_name = user_qq_info.get("nickname")
- user_cardname = user_qq_info.get("card")
- else:
- user_name = "QQ用户"
- user_cardname = "QQ用户"
- logger.debug("无法获取上传文件的用户昵称")
-
- file_info = raw_message.get("file")
- if not file_info:
- logger.error("群文件上传通知中缺少文件信息")
- return None, None
-
- user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- user_id=user_id,
- user_nickname=user_name,
- user_cardname=user_cardname,
- )
-
- file_name = file_info.get("name", "未知文件")
- file_size = file_info.get("size", 0)
-
- seg_data = Seg(type="text", data=f"{user_name} 上传了文件: {file_name} (大小: {file_size} 字节)")
- return seg_data, user_info
-
- async def handle_ban_notify(self, raw_message: dict, group_id: int) -> Tuple[Seg, UserInfo] | Tuple[None, None]:
- if not group_id:
- logger.error("群ID不能为空,无法处理禁言通知")
- return None, None
-
- # 计算user_info
- operator_id = raw_message.get("operator_id")
- operator_nickname: str = None
- operator_cardname: str = None
-
- member_info: dict = await get_member_info(self.get_server_connection(), group_id, operator_id)
- if member_info:
- operator_nickname = member_info.get("nickname")
- operator_cardname = member_info.get("card")
- else:
- logger.warning("无法获取禁言执行者的昵称,消息可能会无效")
- operator_nickname = "QQ用户"
-
- operator_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- user_id=operator_id,
- user_nickname=operator_nickname,
- user_cardname=operator_cardname,
- )
-
- # 计算Seg
- user_id = raw_message.get("user_id")
- banned_user_info: UserInfo = None
- user_nickname: str = "QQ用户"
- user_cardname: str = None
- sub_type: str = None
-
- duration = raw_message.get("duration")
- if duration is None:
- logger.error("禁言时长不能为空,无法处理禁言通知")
- return None, None
-
- if user_id == 0: # 为全体禁言
- sub_type: str = "whole_ban"
- await self._ban_operation(group_id)
- else: # 为单人禁言
- # 获取被禁言人的信息
- sub_type: str = "ban"
- fetched_member_info: dict = await get_member_info(self.get_server_connection(), group_id, user_id)
- if fetched_member_info:
- user_nickname = fetched_member_info.get("nickname")
- user_cardname = fetched_member_info.get("card")
- banned_user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- user_id=user_id,
- user_nickname=user_nickname,
- user_cardname=user_cardname,
- )
- await self._ban_operation(group_id, user_id, int(time.time() + duration))
-
- seg_data: Seg = Seg(
- type="notify",
- data={
- "sub_type": sub_type,
- "duration": duration,
- "banned_user_info": banned_user_info.to_dict() if banned_user_info else None,
- },
- )
-
- return seg_data, operator_info
-
- async def handle_lift_ban_notify(
- self, raw_message: dict, group_id: int
- ) -> Tuple[Seg, UserInfo] | Tuple[None, None]:
- if not group_id:
- logger.error("群ID不能为空,无法处理解除禁言通知")
- return None, None
-
- # 计算user_info
- operator_id = raw_message.get("operator_id")
- operator_nickname: str = None
- operator_cardname: str = None
-
- member_info: dict = await get_member_info(self.get_server_connection(), group_id, operator_id)
- if member_info:
- operator_nickname = member_info.get("nickname")
- operator_cardname = member_info.get("card")
- else:
- logger.warning("无法获取解除禁言执行者的昵称,消息可能会无效")
- operator_nickname = "QQ用户"
-
- operator_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- user_id=operator_id,
- user_nickname=operator_nickname,
- user_cardname=operator_cardname,
- )
-
- # 计算Seg
- sub_type: str = None
- user_nickname: str = "QQ用户"
- user_cardname: str = None
- lifted_user_info: UserInfo = None
-
- user_id = raw_message.get("user_id")
- if user_id == 0: # 全体禁言解除
- sub_type = "whole_lift_ban"
- await self._lift_operation(group_id)
- else: # 单人禁言解除
- sub_type = "lift_ban"
- # 获取被解除禁言人的信息
- fetched_member_info: dict = await get_member_info(self.get_server_connection(), group_id, user_id)
- if fetched_member_info:
- user_nickname = fetched_member_info.get("nickname")
- user_cardname = fetched_member_info.get("card")
- else:
- logger.warning("无法获取解除禁言消息发送者的昵称,消息可能会无效")
- lifted_user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- user_id=user_id,
- user_nickname=user_nickname,
- user_cardname=user_cardname,
- )
- await self._lift_operation(group_id, user_id)
-
- seg_data: Seg = Seg(
- type="notify",
- data={
- "sub_type": sub_type,
- "lifted_user_info": lifted_user_info.to_dict() if lifted_user_info else None,
- },
- )
- return seg_data, operator_info
-
- @staticmethod
- async def put_notice(message_base: MessageBase) -> None:
- """
- 将处理后的通知消息放入通知队列
- """
- if notice_queue.full() or unsuccessful_notice_queue.full():
- logger.warning("通知队列已满,可能是多次发送失败,消息丢弃")
- else:
- await notice_queue.put(message_base)
-
- async def handle_natural_lift(self) -> None:
- while True:
- if len(self.lifted_list) != 0:
- lift_record = self.lifted_list.pop()
- group_id = lift_record.group_id
- user_id = lift_record.user_id
-
- asyncio.create_task(napcat_db.delete_ban_record(lift_record)) # 从数据库中删除禁言记录
-
- seg_message: Seg = await self.natural_lift(group_id, user_id)
-
- fetched_group_info = await get_group_info(self.get_server_connection(), group_id)
- group_name: str = None
- if fetched_group_info:
- group_name = fetched_group_info.get("group_name")
- else:
- logger.warning("无法获取notice消息所在群的名称")
- group_info = GroupInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- group_id=group_id,
- group_name=group_name,
- )
-
- # 准备notice标志
- notice_config = {
- "is_notice": True,
- "is_public_notice": False,
- "notice_type": "group_lift_ban" if user_id != 0 else "group_whole_lift_ban",
- }
-
- message_info: BaseMessageInfo = BaseMessageInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- message_id="notice",
- time=time.time(),
- user_info=None, # 自然解除禁言没有操作者
- group_info=group_info,
- template_info=None,
- format_info=None,
- additional_config=notice_config, # 字典而不是JSON字符串
- )
-
- message_base: MessageBase = MessageBase(
- message_info=message_info,
- message_segment=seg_message,
- raw_message=orjson.dumps(
- {
- "post_type": "notice",
- "notice_type": "group_ban",
- "sub_type": "lift_ban",
- "group_id": group_id,
- "user_id": user_id,
- "operator_id": None, # 自然解除禁言没有操作者
- }
- ).decode('utf-8'),
- )
-
- await self.put_notice(message_base)
- await asyncio.sleep(0.5) # 确保队列处理间隔
- else:
- await asyncio.sleep(5) # 每5秒检查一次
-
- async def natural_lift(self, group_id: int, user_id: int) -> Seg | None:
- if not group_id:
- logger.error("群ID不能为空,无法处理解除禁言通知")
- return None
-
- if user_id == 0: # 理论上永远不会触发
- return Seg(
- type="notify",
- data={
- "sub_type": "whole_lift_ban",
- "lifted_user_info": None,
- },
- )
-
- user_nickname: str = "QQ用户"
- user_cardname: str = None
- fetched_member_info: dict = await get_member_info(self.get_server_connection(), group_id, user_id)
- if fetched_member_info:
- user_nickname = fetched_member_info.get("nickname")
- user_cardname = fetched_member_info.get("card")
-
- lifted_user_info: UserInfo = UserInfo(
- platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
- user_id=user_id,
- user_nickname=user_nickname,
- user_cardname=user_cardname,
- )
-
- return Seg(
- type="notify",
- data={
- "sub_type": "lift_ban",
- "lifted_user_info": lifted_user_info.to_dict(),
- },
- )
-
- async def auto_lift_detect(self) -> None:
- while True:
- if len(self.banned_list) == 0:
- await asyncio.sleep(5)
- continue
- for ban_record in self.banned_list:
- if ban_record.user_id == 0 or ban_record.lift_time == -1:
- continue
- if ban_record.lift_time <= int(time.time()):
- # 触发自然解除禁言
- logger.debug(f"检测到用户 {ban_record.user_id} 在群 {ban_record.group_id} 的禁言已解除")
- self.lifted_list.append(ban_record)
- self.banned_list.remove(ban_record)
- await asyncio.sleep(5)
-
- @staticmethod
- async def send_notice() -> None:
- """
- 发送通知消息到Napcat
- """
- while True:
- if not unsuccessful_notice_queue.empty():
- to_be_send: MessageBase = await unsuccessful_notice_queue.get()
- try:
- send_status = await message_send_instance.message_send(to_be_send)
- if send_status:
- unsuccessful_notice_queue.task_done()
- else:
- await unsuccessful_notice_queue.put(to_be_send)
- except Exception as e:
- logger.error(f"发送通知消息失败: {str(e)}")
- await unsuccessful_notice_queue.put(to_be_send)
- await asyncio.sleep(1)
- continue
- to_be_send: MessageBase = await notice_queue.get()
- try:
- send_status = await message_send_instance.message_send(to_be_send)
- if send_status:
- notice_queue.task_done()
- else:
- await unsuccessful_notice_queue.put(to_be_send)
- except Exception as e:
- logger.error(f"发送通知消息失败: {str(e)}")
- await unsuccessful_notice_queue.put(to_be_send)
- await asyncio.sleep(1)
-
-
-notice_handler = NoticeHandler()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/qq_emoji_list.py b/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/qq_emoji_list.py
deleted file mode 100644
index 3d1dd191a..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/qq_emoji_list.py
+++ /dev/null
@@ -1,252 +0,0 @@
-qq_face: dict = {
- "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": "[表情:按钮]",
- "😊": "[表情:嘿嘿]",
- "😌": "[表情:羞涩]",
- "😚": "[ 表情:亲亲]",
- "😓": "[表情:汗]",
- "😰": "[表情:紧张]",
- "😝": "[表情:吐舌]",
- "😁": "[表情:呲牙]",
- "😜": "[表情:淘气]",
- "☺": "[表情:可爱]",
- "😍": "[表情:花痴]",
- "😔": "[表情:失落]",
- "😄": "[表情:高兴]",
- "😏": "[表情:哼哼]",
- "😒": "[表情:不屑]",
- "😳": "[表情:瞪眼]",
- "😘": "[表情:飞吻]",
- "😭": "[表情:大哭]",
- "😱": "[表情:害怕]",
- "😂": "[表情:激动]",
- "💪": "[表情:肌肉]",
- "👊": "[表情:拳头]",
- "👍": "[表情 :厉害]",
- "👏": "[表情:鼓掌]",
- "👎": "[表情:鄙视]",
- "🙏": "[表情:合十]",
- "👌": "[表情:好的]",
- "👆": "[表情:向上]",
- "👀": "[表情:眼睛]",
- "🍜": "[表情:拉面]",
- "🍧": "[表情:刨冰]",
- "🍞": "[表情:面包]",
- "🍺": "[表情:啤酒]",
- "🍻": "[表情:干杯]",
- "☕": "[表情:咖啡]",
- "🍎": "[表情:苹果]",
- "🍓": "[表情:草莓]",
- "🍉": "[表情:西瓜]",
- "🚬": "[表情:吸烟]",
- "🌹": "[表情:玫瑰]",
- "🎉": "[表情:庆祝]",
- "💝": "[表情:礼物]",
- "💣": "[表情:炸弹]",
- "✨": "[表情:闪光]",
- "💨": "[表情:吹气]",
- "💦": "[表情:水]",
- "🔥": "[表情:火]",
- "💤": "[表情:睡觉]",
- "💩": "[表情:便便]",
- "💉": "[表情:打针]",
- "📫": "[表情:邮箱]",
- "🐎": "[表情:骑马]",
- "👧": "[表情:女孩]",
- "👦": "[表情:男孩]",
- "🐵": "[表情:猴]",
- "🐷": "[表情:猪]",
- "🐮": "[表情:牛]",
- "🐔": "[表情:公鸡]",
- "🐸": "[表情:青蛙]",
- "👻": "[表情:幽灵]",
- "🐛": "[表情:虫]",
- "🐶": "[表情:狗]",
- "🐳": "[表情:鲸鱼]",
- "👢": "[表情:靴子]",
- "☀": "[表情:晴天]",
- "❔": "[表情:问号]",
- "🔫": "[表情:手枪]",
- "💓": "[表情:爱 心]",
- "🏪": "[表情:便利店]",
-}
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/response_pool.py b/src/plugins/built_in/napcat_adapter_plugin/src/response_pool.py
deleted file mode 100644
index 3458ad6d5..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/response_pool.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import asyncio
-import time
-from typing import Dict
-
-from src.common.logger import get_logger
-from src.plugin_system.apis import config_api
-
-logger = get_logger("napcat_adapter")
-
-response_dict: Dict = {}
-response_time_dict: Dict = {}
-plugin_config = None
-
-
-def set_plugin_config(config: dict):
- """设置插件配置"""
- global plugin_config
- plugin_config = config
-
-
-async def get_response(request_id: str, timeout: int = 10) -> dict:
- response = await asyncio.wait_for(_get_response(request_id), timeout)
- _ = response_time_dict.pop(request_id)
- logger.debug(f"响应信息id: {request_id} 已从响应字典中取出")
- return response
-
-
-async def _get_response(request_id: str) -> dict:
- """
- 内部使用的获取响应函数,主要用于在需要时获取响应
- """
- while request_id not in response_dict:
- await asyncio.sleep(0.2)
- return response_dict.pop(request_id)
-
-
-async def put_response(response: dict):
- echo_id = response.get("echo")
- now_time = time.time()
- response_dict[echo_id] = response
- response_time_dict[echo_id] = now_time
- logger.debug(f"响应信息id: {echo_id} 已存入响应字典")
-
-
-async def check_timeout_response() -> None:
- while True:
- cleaned_message_count: int = 0
- now_time = time.time()
-
- # 获取心跳间隔配置
- heartbeat_interval = 30 # 默认值
- if plugin_config:
- heartbeat_interval = config_api.get_plugin_config(plugin_config, "napcat_server.heartbeat_interval", 30)
-
- for echo_id, response_time in list(response_time_dict.items()):
- if now_time - response_time > heartbeat_interval:
- cleaned_message_count += 1
- response_dict.pop(echo_id)
- response_time_dict.pop(echo_id)
- logger.warning(f"响应消息 {echo_id} 超时,已删除")
- if cleaned_message_count > 0:
- logger.info(f"已删除 {cleaned_message_count} 条超时响应消息")
- await asyncio.sleep(heartbeat_interval)
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/send_handler.py b/src/plugins/built_in/napcat_adapter_plugin/src/send_handler.py
deleted file mode 100644
index 9ec950bc8..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/send_handler.py
+++ /dev/null
@@ -1,705 +0,0 @@
-import orjson
-import random
-import time
-import websockets as Server
-import uuid
-from maim_message import (
- UserInfo,
- GroupInfo,
- Seg,
- BaseMessageInfo,
- MessageBase,
-)
-from typing import Dict, Any, Tuple, Optional
-from src.plugin_system.apis import config_api
-
-from . import CommandType
-from .response_pool import get_response
-from src.common.logger import get_logger
-
-logger = get_logger("napcat_adapter")
-from .utils import get_image_format, convert_image_to_gif
-from .recv_handler.message_sending import message_send_instance
-from .websocket_manager import websocket_manager
-
-
-class SendHandler:
- def __init__(self):
- self.server_connection: Optional[Server.ServerConnection] = None
- self.plugin_config = None
-
- def set_plugin_config(self, plugin_config: dict):
- """设置插件配置"""
- self.plugin_config = plugin_config
-
- async def set_server_connection(self, server_connection: Server.ServerConnection) -> None:
- """设置Napcat连接"""
- self.server_connection = server_connection
-
- def get_server_connection(self) -> Optional[Server.ServerConnection]:
- """获取当前的服务器连接"""
- # 优先使用直接设置的连接,否则从 websocket_manager 获取
- if self.server_connection:
- return self.server_connection
- return websocket_manager.get_connection()
-
- async def handle_message(self, raw_message_base_dict: dict) -> None:
- raw_message_base: MessageBase = MessageBase.from_dict(raw_message_base_dict)
- message_segment: Seg = raw_message_base.message_segment
- logger.info("接收到来自MoFox-Bot的消息,处理中")
- if message_segment.type == "command":
- logger.info("处理命令")
- return await self.send_command(raw_message_base)
- elif message_segment.type == "adapter_command":
- logger.info("处理适配器命令")
- return await self.handle_adapter_command(raw_message_base)
- elif message_segment.type == "adapter_response":
- # adapter_response消息是Napcat发送给Bot的,不应该在这里处理
- # 这个handler只处理Bot发送给Napcat的消息
- logger.info("收到adapter_response消息,此消息应该由Bot端处理,跳过")
- return None
- else:
- logger.info("处理普通消息")
- return await self.send_normal_message(raw_message_base)
-
- async def send_normal_message(self, raw_message_base: MessageBase) -> None:
- """
- 处理普通消息发送
- """
- logger.info("处理普通信息中")
- message_info: BaseMessageInfo = raw_message_base.message_info
- message_segment: Seg = raw_message_base.message_segment
- group_info: Optional[GroupInfo] = message_info.group_info
- user_info: Optional[UserInfo] = message_info.user_info
- target_id: Optional[int] = None
- action: Optional[str] = None
- id_name: Optional[str] = None
- processed_message: list = []
- try:
- if user_info:
- processed_message = await self.handle_seg_recursive(message_segment, user_info)
- except Exception as e:
- logger.error(f"处理消息时发生错误: {e}")
- return
-
- if not processed_message:
- logger.critical("现在暂时不支持解析此回复!")
- return None
-
- if group_info and user_info:
- logger.debug("发送群聊消息")
- target_id = int(group_info.group_id) if group_info.group_id else None
- action = "send_group_msg"
- id_name = "group_id"
- elif user_info:
- logger.debug("发送私聊消息")
- target_id = int(user_info.user_id) if user_info.user_id else None
- action = "send_private_msg"
- id_name = "user_id"
- else:
- logger.error("无法识别的消息类型")
- return
- logger.info("尝试发送到napcat")
- logger.debug(
- f"准备发送到napcat的消息体: action='{action}', {id_name}='{target_id}', message='{processed_message}'"
- )
- response = await self.send_message_to_napcat(
- action,
- {
- id_name: target_id,
- "message": processed_message,
- },
- )
- if response.get("status") == "ok":
- logger.info("消息发送成功")
- qq_message_id = response.get("data", {}).get("message_id")
- await self.message_sent_back(raw_message_base, qq_message_id)
- else:
- logger.warning(f"消息发送失败,napcat返回:{str(response)}")
-
- async def send_command(self, raw_message_base: MessageBase) -> None:
- """
- 处理命令类
- """
- logger.info("处理命令中")
- message_info: BaseMessageInfo = raw_message_base.message_info
- message_segment: Seg = raw_message_base.message_segment
- group_info: Optional[GroupInfo] = message_info.group_info
- seg_data: Dict[str, Any] = message_segment.data if isinstance(message_segment.data, dict) else {}
- command_name: Optional[str] = seg_data.get("name")
- try:
- args = seg_data.get("args", {})
- if not isinstance(args, dict):
- args = {}
-
- match command_name:
- case CommandType.GROUP_BAN.name:
- command, args_dict = self.handle_ban_command(args, group_info)
- case CommandType.GROUP_WHOLE_BAN.name:
- command, args_dict = self.handle_whole_ban_command(args, group_info)
- case CommandType.GROUP_KICK.name:
- command, args_dict = self.handle_kick_command(args, group_info)
- case CommandType.SEND_POKE.name:
- command, args_dict = self.handle_poke_command(args, group_info)
- case CommandType.DELETE_MSG.name:
- command, args_dict = self.delete_msg_command(args)
- case CommandType.AI_VOICE_SEND.name:
- command, args_dict = self.handle_ai_voice_send_command(args, group_info)
- case CommandType.SET_EMOJI_LIKE.name:
- command, args_dict = self.handle_set_emoji_like_command(args)
- case CommandType.SEND_AT_MESSAGE.name:
- command, args_dict = self.handle_at_message_command(args, group_info)
- case CommandType.SEND_LIKE.name:
- command, args_dict = self.handle_send_like_command(args)
- case _:
- logger.error(f"未知命令: {command_name}")
- return
- except Exception as e:
- logger.error(f"处理命令时发生错误: {e}")
- return None
-
- if not command or not args_dict:
- logger.error("命令或参数缺失")
- return None
-
- logger.info(f"准备向 Napcat 发送命令: command='{command}', args_dict='{args_dict}'")
- response = await self.send_message_to_napcat(command, args_dict)
- logger.info(f"收到 Napcat 的命令响应: {response}")
-
- if response.get("status") == "ok":
- logger.info(f"命令 {command_name} 执行成功")
- else:
- logger.warning(f"命令 {command_name} 执行失败,napcat返回:{str(response)}")
-
- async def handle_adapter_command(self, raw_message_base: MessageBase) -> None:
- """
- 处理适配器命令类 - 用于直接向Napcat发送命令并返回结果
- """
- logger.info("处理适配器命令中")
- message_segment: Seg = raw_message_base.message_segment
- seg_data: Dict[str, Any] = message_segment.data if isinstance(message_segment.data, dict) else {}
-
- try:
- action = seg_data.get("action")
- params = seg_data.get("params", {})
- request_id = seg_data.get("request_id")
-
- if not action:
- logger.error("适配器命令缺少action参数")
- await self.send_adapter_command_response(
- raw_message_base, {"status": "error", "message": "缺少action参数"}, request_id
- )
- return
-
- logger.info(f"执行适配器命令: {action}")
-
- # 根据action决定处理方式
- if action == "get_cookies":
- # 对于get_cookies,我们需要一个更长的超时时间
- response = await self.send_message_to_napcat(action, params, timeout=40.0)
- else:
- # 对于其他命令,使用默认超时
- response = await self.send_message_to_napcat(action, params)
-
- # 发送响应回MoFox-Bot
- logger.debug(f"[DEBUG handle_adapter_command] 即将调用send_adapter_command_response, request_id={request_id}")
- await self.send_adapter_command_response(raw_message_base, response, request_id)
- logger.debug("[DEBUG handle_adapter_command] send_adapter_command_response调用完成")
-
- if response.get("status") == "ok":
- logger.info(f"适配器命令 {action} 执行成功")
- else:
- logger.warning(f"适配器命令 {action} 执行失败,napcat返回:{str(response)}")
- # 无论成功失败,都记录下完整的响应内容以供调试
- logger.debug(f"适配器命令 {action} 的完整响应: {response}")
-
- except Exception as e:
- logger.error(f"处理适配器命令时发生错误: {e}")
- error_response = {"status": "error", "message": str(e)}
- await self.send_adapter_command_response(raw_message_base, error_response, seg_data.get("request_id"))
-
- def get_level(self, seg_data: Seg) -> int:
- if seg_data.type == "seglist":
- return 1 + max(self.get_level(seg) for seg in seg_data.data)
- else:
- return 1
-
- async def handle_seg_recursive(self, seg_data: Seg, user_info: UserInfo) -> list:
- payload: list = []
- if seg_data.type == "seglist":
- # level = self.get_level(seg_data) # 给以后可能的多层嵌套做准备,此处不使用
- if not seg_data.data:
- return []
- for seg in seg_data.data:
- payload = await self.process_message_by_type(seg, payload, user_info)
- else:
- payload = await self.process_message_by_type(seg_data, payload, user_info)
- return payload
-
- async def process_message_by_type(self, seg: Seg, payload: list, user_info: UserInfo) -> list:
- # sourcery skip: reintroduce-else, swap-if-else-branches, use-named-expression
- new_payload = payload
- if seg.type == "reply":
- target_id = seg.data
- target_id = str(target_id)
- if target_id == "notice":
- return payload
- logger.info(target_id if isinstance(target_id, str) else "")
- new_payload = self.build_payload(
- payload,
- await self.handle_reply_message(target_id if isinstance(target_id, str) else "", user_info),
- True,
- )
- elif seg.type == "text":
- text = seg.data
- if not text:
- return payload
- new_payload = self.build_payload(
- payload,
- self.handle_text_message(text if isinstance(text, str) else ""),
- False,
- )
- elif seg.type == "face":
- logger.warning("MoFox-Bot 发送了qq原生表情,暂时不支持")
- elif seg.type == "image":
- image = seg.data
- new_payload = self.build_payload(payload, self.handle_image_message(image), False)
- elif seg.type == "emoji":
- emoji = seg.data
- new_payload = self.build_payload(payload, self.handle_emoji_message(emoji), False)
- elif seg.type == "voice":
- voice = seg.data
- new_payload = self.build_payload(payload, self.handle_voice_message(voice), False)
- elif seg.type == "voiceurl":
- voice_url = seg.data
- new_payload = self.build_payload(payload, self.handle_voiceurl_message(voice_url), False)
- elif seg.type == "music":
- song_id = seg.data
- new_payload = self.build_payload(payload, self.handle_music_message(song_id), False)
- elif seg.type == "videourl":
- video_url = seg.data
- new_payload = self.build_payload(payload, self.handle_videourl_message(video_url), False)
- elif seg.type == "file":
- file_path = seg.data
- new_payload = self.build_payload(payload, self.handle_file_message(file_path), False)
- return new_payload
-
- def build_payload(self, payload: list, addon: dict | list, is_reply: bool = False) -> list:
- # sourcery skip: for-append-to-extend, merge-list-append, simplify-generator
- """构建发送的消息体"""
- if is_reply:
- temp_list = []
- if isinstance(addon, list):
- temp_list.extend(addon)
- else:
- temp_list.append(addon)
- for i in payload:
- if i.get("type") == "reply":
- logger.debug("检测到多个回复,使用最新的回复")
- continue
- temp_list.append(i)
- return temp_list
- else:
- if isinstance(addon, list):
- payload.extend(addon)
- else:
- payload.append(addon)
- return payload
-
- async def handle_reply_message(self, id: str, user_info: UserInfo) -> dict | list:
- """处理回复消息"""
- logger.debug(f"开始处理回复消息,消息ID: {id}")
- reply_seg = {"type": "reply", "data": {"id": id}}
-
- # 检查是否启用引用艾特功能
- if not config_api.get_plugin_config(self.plugin_config, "features.enable_reply_at", False):
- logger.info("引用艾特功能未启用,仅发送普通回复")
- return reply_seg
-
- try:
- msg_info_response = await self.send_message_to_napcat("get_msg", {"message_id": id})
- logger.debug(f"获取消息 {id} 的详情响应: {msg_info_response}")
-
- replied_user_id = None
- if msg_info_response and msg_info_response.get("status") == "ok":
- sender_info = msg_info_response.get("data", {}).get("sender")
- if sender_info:
- replied_user_id = sender_info.get("user_id")
-
- # 如果没有获取到被回复者的ID,则直接返回,不进行@
- if not replied_user_id:
- logger.warning(f"无法获取消息 {id} 的发送者信息,跳过 @")
- logger.info(f"最终返回的回复段: {reply_seg}")
- return reply_seg
-
- # 根据概率决定是否艾特用户
- if random.random() < config_api.get_plugin_config(self.plugin_config, "features.reply_at_rate", 0.5):
- at_seg = {"type": "at", "data": {"qq": str(replied_user_id)}}
- # 在艾特后面添加一个空格
- text_seg = {"type": "text", "data": {"text": " "}}
- result_seg = [reply_seg, at_seg, text_seg]
- logger.info(f"最终返回的回复段: {result_seg}")
- return result_seg
-
- except Exception as e:
- logger.error(f"处理引用回复并尝试@时出错: {e}")
- # 出现异常时,只发送普通的回复,避免程序崩溃
- logger.info(f"最终返回的回复段: {reply_seg}")
- return reply_seg
-
- logger.info(f"最终返回的回复段: {reply_seg}")
- return reply_seg
-
- def handle_text_message(self, message: str) -> dict:
- """处理文本消息"""
- return {"type": "text", "data": {"text": message}}
-
- def handle_image_message(self, encoded_image: str) -> dict:
- """处理图片消息"""
- return {
- "type": "image",
- "data": {
- "file": f"base64://{encoded_image}",
- "subtype": 0,
- },
- } # base64 编码的图片
-
- def handle_emoji_message(self, encoded_emoji: str) -> dict:
- """处理表情消息"""
- encoded_image = encoded_emoji
- image_format = get_image_format(encoded_emoji)
- if image_format != "gif":
- encoded_image = convert_image_to_gif(encoded_emoji)
- return {
- "type": "image",
- "data": {
- "file": f"base64://{encoded_image}",
- "subtype": 1,
- "summary": "[动画表情]",
- },
- }
-
- def handle_voice_message(self, encoded_voice: str) -> dict:
- """处理语音消息"""
- use_tts = False
- if self.plugin_config:
- use_tts = config_api.get_plugin_config(self.plugin_config, "voice.use_tts", False)
-
- if not use_tts:
- logger.warning("未启用语音消息处理")
- return {}
- if not encoded_voice:
- return {}
- return {
- "type": "record",
- "data": {"file": f"base64://{encoded_voice}"},
- }
-
- def handle_voiceurl_message(self, voice_url: str) -> dict:
- """处理语音链接消息"""
- return {
- "type": "record",
- "data": {"file": voice_url},
- }
-
- def handle_music_message(self, song_id: str) -> dict:
- """处理音乐消息"""
- return {
- "type": "music",
- "data": {"type": "163", "id": song_id},
- }
-
- def handle_videourl_message(self, video_url: str) -> dict:
- """处理视频链接消息"""
- return {
- "type": "video",
- "data": {"file": video_url},
- }
-
- def handle_file_message(self, file_path: str) -> dict:
- """处理文件消息"""
- return {
- "type": "file",
- "data": {"file": f"file://{file_path}"},
- }
-
- def delete_msg_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
- """处理删除消息命令"""
- return "delete_msg", {"message_id": args["message_id"]}
-
- def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]:
- """处理封禁命令
-
- Args:
- args (Dict[str, Any]): 参数字典
- group_info (GroupInfo): 群聊信息(对应目标群聊)
-
- Returns:
- Tuple[CommandType, Dict[str, Any]]
- """
- duration: int = int(args["duration"])
- user_id: int = int(args["qq_id"])
- group_id: int = int(group_info.group_id)
- if duration < 0:
- raise ValueError("封禁时间必须大于等于0")
- if not user_id or not group_id:
- raise ValueError("封禁命令缺少必要参数")
- if duration > 2592000:
- raise ValueError("封禁时间不能超过30天")
- return (
- CommandType.GROUP_BAN.value,
- {
- "group_id": group_id,
- "user_id": user_id,
- "duration": duration,
- },
- )
-
- def handle_whole_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]:
- """处理全体禁言命令
-
- Args:
- args (Dict[str, Any]): 参数字典
- group_info (GroupInfo): 群聊信息(对应目标群聊)
-
- Returns:
- Tuple[CommandType, Dict[str, Any]]
- """
- enable = args["enable"]
- assert isinstance(enable, bool), "enable参数必须是布尔值"
- group_id: int = int(group_info.group_id)
- if group_id <= 0:
- raise ValueError("群组ID无效")
- return (
- CommandType.GROUP_WHOLE_BAN.value,
- {
- "group_id": group_id,
- "enable": enable,
- },
- )
-
- def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]:
- """处理群成员踢出命令
-
- Args:
- args (Dict[str, Any]): 参数字典
- group_info (GroupInfo): 群聊信息(对应目标群聊)
-
- Returns:
- Tuple[CommandType, Dict[str, Any]]
- """
- user_id: int = int(args["qq_id"])
- group_id: int = int(group_info.group_id)
- if group_id <= 0:
- raise ValueError("群组ID无效")
- if user_id <= 0:
- raise ValueError("用户ID无效")
- return (
- CommandType.GROUP_KICK.value,
- {
- "group_id": group_id,
- "user_id": user_id,
- "reject_add_request": False, # 不拒绝加群请求
- },
- )
-
- def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]:
- """处理戳一戳命令
-
- Args:
- args (Dict[str, Any]): 参数字典
- group_info (GroupInfo): 群聊信息(对应目标群聊)
-
- Returns:
- Tuple[CommandType, Dict[str, Any]]
- """
- user_id: int = int(args["qq_id"])
- if group_info is None:
- group_id = None
- else:
- group_id: int = int(group_info.group_id)
- if group_id <= 0:
- raise ValueError("群组ID无效")
- if user_id <= 0:
- raise ValueError("用户ID无效")
- return (
- CommandType.SEND_POKE.value,
- {
- "group_id": group_id,
- "user_id": user_id,
- },
- )
-
- def handle_set_emoji_like_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
- """处理设置表情回应命令
-
- Args:
- args (Dict[str, Any]): 参数字典
-
-
- Returns:
- Tuple[CommandType, Dict[str, Any]]
- """
- logger.info(f"开始处理表情回应命令, 接收到参数: {args}")
- try:
- message_id = int(args["message_id"])
- emoji_id = int(args["emoji_id"])
- set_like = bool(args["set"])
- except (KeyError, ValueError) as e:
- logger.error(f"处理表情回应命令时发生错误: {e}, 原始参数: {args}")
- raise ValueError(f"缺少必需参数或参数类型错误: {e}")
-
- return (
- CommandType.SET_EMOJI_LIKE.value,
- {"message_id": message_id, "emoji_id": emoji_id, "set": set_like},
- )
-
- def handle_send_like_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
- """
- 处理发送点赞命令的逻辑。
-
- Args:
- args (Dict[str, Any]): 参数字典
-
- Returns:
- Tuple[CommandType, Dict[str, Any]]
- """
- try:
- user_id: int = int(args["qq_id"])
- times: int = int(args["times"])
- except (KeyError, ValueError):
- raise ValueError("缺少必需参数: qq_id 或 times")
-
- return (
- CommandType.SEND_LIKE.value,
- {"user_id": user_id, "times": times},
- )
-
- def handle_ai_voice_send_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]:
- """
- 处理AI语音发送命令的逻辑。
- 并返回 NapCat 兼容的 (action, params) 元组。
- """
- if not group_info or not group_info.group_id:
- raise ValueError("AI语音发送命令必须在群聊上下文中使用")
- if not args:
- raise ValueError("AI语音发送命令缺少参数")
-
- group_id: int = int(group_info.group_id)
- character_id = args.get("character")
- text_content = args.get("text")
-
- if not character_id or not text_content:
- raise ValueError(f"AI语音发送命令参数不完整: character='{character_id}', text='{text_content}'")
-
- return (
- CommandType.AI_VOICE_SEND.value,
- {
- "group_id": group_id,
- "text": text_content,
- "character": character_id,
- },
- )
-
- async def send_message_to_napcat(self, action: str, params: dict, timeout: float = 20.0) -> dict:
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps({"action": action, "params": params, "echo": request_uuid}).decode('utf-8')
-
- # 获取当前连接
- connection = self.get_server_connection()
- if not connection:
- logger.error("没有可用的 Napcat 连接")
- return {"status": "error", "message": "no connection"}
-
- try:
- await connection.send(payload)
- response = await get_response(request_uuid, timeout=timeout) # 使用传入的超时时间
- except TimeoutError:
- logger.error(f"发送消息超时({timeout}秒),未收到响应: action={action}, params={params}")
- return {"status": "error", "message": "timeout"}
- except Exception as e:
- logger.error(f"发送消息失败: {e}")
- return {"status": "error", "message": str(e)}
- return response
-
- async def message_sent_back(self, message_base: MessageBase, qq_message_id: str) -> None:
- # 修改 additional_config,添加 echo 字段
- if message_base.message_info.additional_config is None:
- message_base.message_info.additional_config = {}
-
- message_base.message_info.additional_config["echo"] = True
-
- # 获取原始的 mmc_message_id
- mmc_message_id = message_base.message_info.message_id
-
- # 修改 message_segment 为 notify 类型
- message_base.message_segment = Seg(
- type="notify", data={"sub_type": "echo", "echo": mmc_message_id, "actual_id": qq_message_id}
- )
- await message_send_instance.message_send(message_base)
- logger.debug("已回送消息ID")
- return
-
- async def send_adapter_command_response(
- self, original_message: MessageBase, response_data: dict, request_id: str
- ) -> None:
- """
- 发送适配器命令响应回MoFox-Bot
-
- Args:
- original_message: 原始消息
- response_data: 响应数据
- request_id: 请求ID
- """
- try:
- # 修改 additional_config,添加 echo 字段
- if original_message.message_info.additional_config is None:
- original_message.message_info.additional_config = {}
-
- original_message.message_info.additional_config["echo"] = True
-
- # 修改 message_segment 为 adapter_response 类型
- original_message.message_segment = Seg(
- type="adapter_response",
- data={"request_id": request_id, "response": response_data, "timestamp": int(time.time() * 1000)},
- )
-
- await message_send_instance.message_send(original_message)
-
- except Exception as e:
- logger.error(f"发送适配器命令响应时出错: {e}")
-
- def handle_at_message_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]:
- """处理艾特并发送消息命令
-
- Args:
- args (Dict[str, Any]): 参数字典, 包含 qq_id 和 text
- group_info (GroupInfo): 群聊信息
-
- Returns:
- Tuple[str, Dict[str, Any]]: (action, params)
- """
- at_user_id = args.get("qq_id")
- text = args.get("text")
-
- if not at_user_id or not text:
- raise ValueError("艾特消息命令缺少 qq_id 或 text 参数")
-
- if not group_info:
- raise ValueError("艾特消息命令必须在群聊上下文中使用")
-
- message_payload = [
- {"type": "at", "data": {"qq": str(at_user_id)}},
- {"type": "text", "data": {"text": " " + str(text)}},
- ]
-
- return (
- "send_group_msg",
- {
- "group_id": group_info.group_id,
- "message": message_payload,
- },
- )
-
-
-send_handler = SendHandler()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/stream_router.py b/src/plugins/built_in/napcat_adapter_plugin/src/stream_router.py
deleted file mode 100644
index f8d9fb49f..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/stream_router.py
+++ /dev/null
@@ -1,351 +0,0 @@
-"""
-按聊天流分配消费者的消息路由系统
-
-核心思想:
-- 为每个活跃的聊天流(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}),"
- f"消息被丢弃!溢出次数: {self.stats['queue_overflow_count']}"
- )
- # 可选策略:丢弃最旧的消息
- # 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()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/utils.py b/src/plugins/built_in/napcat_adapter_plugin/src/utils.py
deleted file mode 100644
index b597a60f9..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/utils.py
+++ /dev/null
@@ -1,314 +0,0 @@
-import base64
-import io
-import orjson
-import ssl
-import uuid
-from typing import List, Optional, Tuple, Union
-
-import urllib3
-import websockets as Server
-from PIL import Image
-
-from src.common.logger import get_logger
-
-from .database import BanUser, napcat_db
-from .response_pool import get_response
-
-logger = get_logger("napcat_adapter")
-
-
-class SSLAdapter(urllib3.PoolManager):
- def __init__(self, *args, **kwargs):
- context = ssl.create_default_context()
- context.set_ciphers("DEFAULT@SECLEVEL=1")
- context.minimum_version = ssl.TLSVersion.TLSv1_2
- kwargs["ssl_context"] = context
- super().__init__(*args, **kwargs)
-
-
-async def get_group_info(websocket: Server.ServerConnection, group_id: int) -> dict | None:
- """
- 获取群相关信息
-
- 返回值需要处理可能为空的情况
- """
- logger.debug("获取群聊信息中")
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps({"action": "get_group_info", "params": {"group_id": group_id}, "echo": request_uuid}).decode('utf-8')
- try:
- await websocket.send(payload)
- socket_response: dict = await get_response(request_uuid)
- except TimeoutError:
- logger.error(f"获取群信息超时,群号: {group_id}")
- return None
- except Exception as e:
- logger.error(f"获取群信息失败: {e}")
- return None
- logger.debug(socket_response)
- return socket_response.get("data")
-
-
-async def get_group_detail_info(websocket: Server.ServerConnection, group_id: int) -> dict | None:
- """
- 获取群详细信息
-
- 返回值需要处理可能为空的情况
- """
- logger.debug("获取群详细信息中")
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps({"action": "get_group_detail_info", "params": {"group_id": group_id}, "echo": request_uuid}).decode('utf-8')
- try:
- await websocket.send(payload)
- socket_response: dict = await get_response(request_uuid)
- except TimeoutError:
- logger.error(f"获取群详细信息超时,群号: {group_id}")
- return None
- except Exception as e:
- logger.error(f"获取群详细信息失败: {e}")
- return None
- logger.debug(socket_response)
- return socket_response.get("data")
-
-
-async def get_member_info(websocket: Server.ServerConnection, group_id: int, user_id: int) -> dict | None:
- """
- 获取群成员信息
-
- 返回值需要处理可能为空的情况
- """
- logger.debug("获取群成员信息中")
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps(
- {
- "action": "get_group_member_info",
- "params": {"group_id": group_id, "user_id": user_id, "no_cache": True},
- "echo": request_uuid,
- }
- ).decode('utf-8')
- try:
- await websocket.send(payload)
- socket_response: dict = await get_response(request_uuid)
- except TimeoutError:
- logger.error(f"获取成员信息超时,群号: {group_id}, 用户ID: {user_id}")
- return None
- except Exception as e:
- logger.error(f"获取成员信息失败: {e}")
- return None
- logger.debug(socket_response)
- return socket_response.get("data")
-
-
-async def get_image_base64(url: str) -> str:
- # sourcery skip: raise-specific-error
- """获取图片/表情包的Base64"""
- logger.debug(f"下载图片: {url}")
- http = SSLAdapter()
- try:
- response = http.request("GET", url, timeout=10)
- if response.status != 200:
- raise Exception(f"HTTP Error: {response.status}")
- image_bytes = response.data
- return base64.b64encode(image_bytes).decode("utf-8")
- except Exception as e:
- logger.error(f"图片下载失败: {str(e)}")
- raise
-
-
-def convert_image_to_gif(image_base64: str) -> str:
- # sourcery skip: extract-method
- """
- 将Base64编码的图片转换为GIF格式
- Parameters:
- image_base64: str: Base64编码的图片数据
- Returns:
- str: Base64编码的GIF图片数据
- """
- logger.debug("转换图片为GIF格式")
- try:
- image_bytes = base64.b64decode(image_base64)
- image = Image.open(io.BytesIO(image_bytes))
- output_buffer = io.BytesIO()
- image.save(output_buffer, format="GIF")
- output_buffer.seek(0)
- return base64.b64encode(output_buffer.read()).decode("utf-8")
- except Exception as e:
- logger.error(f"图片转换为GIF失败: {str(e)}")
- return image_base64
-
-
-async def get_self_info(websocket: Server.ServerConnection) -> dict | None:
- """
- 获取自身信息
- Parameters:
- websocket: WebSocket连接对象
- Returns:
- data: dict: 返回的自身信息
- """
- logger.debug("获取自身信息中")
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps({"action": "get_login_info", "params": {}, "echo": request_uuid}).decode('utf-8')
- try:
- await websocket.send(payload)
- response: dict = await get_response(request_uuid)
- except TimeoutError:
- logger.error("获取自身信息超时")
- return None
- except Exception as e:
- logger.error(f"获取自身信息失败: {e}")
- return None
- logger.debug(response)
- return response.get("data")
-
-
-def get_image_format(raw_data: str) -> str:
- """
- 从Base64编码的数据中确定图片的格式。
- Parameters:
- raw_data: str: Base64编码的图片数据。
- Returns:
- format: str: 图片的格式(例如 'jpeg', 'png', 'gif')。
- """
- image_bytes = base64.b64decode(raw_data)
- return Image.open(io.BytesIO(image_bytes)).format.lower()
-
-
-async def get_stranger_info(websocket: Server.ServerConnection, user_id: int) -> dict | None:
- """
- 获取陌生人信息
- Parameters:
- websocket: WebSocket连接对象
- user_id: 用户ID
- Returns:
- dict: 返回的陌生人信息
- """
- logger.debug("获取陌生人信息中")
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps({"action": "get_stranger_info", "params": {"user_id": user_id}, "echo": request_uuid}).decode('utf-8')
- try:
- await websocket.send(payload)
- response: dict = await get_response(request_uuid)
- except TimeoutError:
- logger.error(f"获取陌生人信息超时,用户ID: {user_id}")
- return None
- except Exception as e:
- logger.error(f"获取陌生人信息失败: {e}")
- return None
- logger.debug(response)
- return response.get("data")
-
-
-async def get_message_detail(websocket: Server.ServerConnection, message_id: Union[str, int]) -> dict | None:
- """
- 获取消息详情,可能为空
- Parameters:
- websocket: WebSocket连接对象
- message_id: 消息ID
- Returns:
- dict: 返回的消息详情
- """
- logger.debug("获取消息详情中")
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps({"action": "get_msg", "params": {"message_id": message_id}, "echo": request_uuid}).decode('utf-8')
- try:
- await websocket.send(payload)
- response: dict = await get_response(request_uuid, 30) # 增加超时时间到30秒
- except TimeoutError:
- logger.error(f"获取消息详情超时,消息ID: {message_id}")
- return None
- except Exception as e:
- logger.error(f"获取消息详情失败: {e}")
- return None
- logger.debug(response)
- return response.get("data")
-
-
-async def get_record_detail(
- websocket: Server.ServerConnection, file: str, file_id: Optional[str] = None
-) -> dict | None:
- """
- 获取语音消息内容
- Parameters:
- websocket: WebSocket连接对象
- file: 文件名
- file_id: 文件ID
- Returns:
- dict: 返回的语音消息详情
- """
- logger.debug("获取语音消息详情中")
- request_uuid = str(uuid.uuid4())
- payload = orjson.dumps(
- {
- "action": "get_record",
- "params": {"file": file, "file_id": file_id, "out_format": "wav"},
- "echo": request_uuid,
- }
- ).decode('utf-8')
- try:
- await websocket.send(payload)
- response: dict = await get_response(request_uuid, 30) # 增加超时时间到30秒
- except TimeoutError:
- logger.error(f"获取语音消息详情超时,文件: {file}, 文件ID: {file_id}")
- return None
- except Exception as e:
- logger.error(f"获取语音消息详情失败: {e}")
- return None
- logger.debug(f"{str(response)[:200]}...") # 防止语音的超长base64编码导致日志过长
- return response.get("data")
-
-
-async def read_ban_list(
- websocket: Server.ServerConnection,
-) -> Tuple[List[BanUser], List[BanUser]]:
- """
- 从根目录下的data文件夹中的文件读取禁言列表。
- 同时自动更新已经失效禁言
- Returns:
- Tuple[
- 一个仍在禁言中的用户的BanUser列表,
- 一个已经自然解除禁言的用户的BanUser列表,
- 一个仍在全体禁言中的群的BanUser列表,
- 一个已经自然解除全体禁言的群的BanUser列表,
- ]
- """
- try:
- ban_list = await napcat_db.get_ban_records()
- lifted_list: List[BanUser] = []
- logger.info("已经读取禁言列表")
- # 复制列表以避免迭代中修改原列表问题
- for ban_record in list(ban_list):
- if ban_record.user_id == 0:
- fetched_group_info = await get_group_info(websocket, ban_record.group_id)
- if fetched_group_info is None:
- logger.warning(f"无法获取群信息,群号: {ban_record.group_id},默认禁言解除")
- lifted_list.append(ban_record)
- ban_list.remove(ban_record)
- continue
- group_all_shut: int = fetched_group_info.get("group_all_shut")
- if group_all_shut == 0:
- lifted_list.append(ban_record)
- ban_list.remove(ban_record)
- continue
- else:
- fetched_member_info = await get_member_info(websocket, ban_record.group_id, ban_record.user_id)
- if fetched_member_info is None:
- logger.warning(
- f"无法获取群成员信息,用户ID: {ban_record.user_id}, 群号: {ban_record.group_id},默认禁言解除"
- )
- lifted_list.append(ban_record)
- ban_list.remove(ban_record)
- continue
- lift_ban_time: int = fetched_member_info.get("shut_up_timestamp")
- if lift_ban_time == 0:
- lifted_list.append(ban_record)
- ban_list.remove(ban_record)
- else:
- ban_record.lift_time = lift_ban_time
- await napcat_db.update_ban_record(ban_list)
- return ban_list, lifted_list
- except Exception as e:
- logger.error(f"读取禁言列表失败: {e}")
- return [], []
-
-
-async def save_ban_record(list: List[BanUser]):
- return await napcat_db.update_ban_record(list)
\ No newline at end of file
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/video_handler.py b/src/plugins/built_in/napcat_adapter_plugin/src/video_handler.py
deleted file mode 100644
index aa64d2571..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/video_handler.py
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-视频下载和处理模块
-用于从QQ消息中下载视频并转发给Bot进行分析
-"""
-
-import asyncio
-from pathlib import Path
-from typing import Any, Dict, Optional
-
-import aiohttp
-
-from src.common.logger import get_logger
-
-logger = get_logger("video_handler")
-
-
-class VideoDownloader:
- def __init__(self, max_size_mb: int = 100, download_timeout: int = 60):
- self.max_size_mb = max_size_mb
- self.download_timeout = download_timeout
- self.supported_formats = {".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv", ".webm", ".m4v"}
-
- def is_video_url(self, url: str) -> bool:
- """检查URL是否为视频文件"""
- try:
- # QQ视频URL可能没有扩展名,所以先检查Content-Type
- # 对于QQ视频,我们先假设是视频,稍后通过Content-Type验证
-
- # 检查URL中是否包含视频相关的关键字
- video_keywords = ["video", "mp4", "avi", "mov", "mkv", "flv", "wmv", "webm", "m4v"]
- url_lower = url.lower()
-
- # 如果URL包含视频关键字,认为是视频
- if any(keyword in url_lower for keyword in video_keywords):
- return True
-
- # 检查文件扩展名(传统方法)
- path = Path(url.split("?")[0]) # 移除查询参数
- if path.suffix.lower() in self.supported_formats:
- return True
-
- # 对于QQ等特殊平台,URL可能没有扩展名
- # 我们允许这些URL通过,稍后通过HTTP头Content-Type验证
- qq_domains = ["qpic.cn", "gtimg.cn", "qq.com", "tencent.com"]
- if any(domain in url_lower for domain in qq_domains):
- return True
-
- return False
- except Exception:
- # 如果解析失败,默认允许尝试下载(稍后验证)
- return True
-
- def check_file_size(self, content_length: Optional[str]) -> bool:
- """检查文件大小是否在允许范围内"""
- if content_length is None:
- return True # 无法获取大小时允许下载
-
- try:
- size_bytes = int(content_length)
- size_mb = size_bytes / (1024 * 1024)
- return size_mb <= self.max_size_mb
- except Exception:
- return True
-
- async def download_video(self, url: str, filename: Optional[str] = None) -> Dict[str, Any]:
- """
- 下载视频文件
-
- Args:
- url: 视频URL
- filename: 可选的文件名
-
- Returns:
- dict: 下载结果,包含success、data、filename、error等字段
- """
- try:
- logger.info(f"开始下载视频: {url}")
-
- # 检查URL格式
- if not self.is_video_url(url):
- logger.warning(f"URL格式检查失败: {url}")
- return {"success": False, "error": "不支持的视频格式", "url": url}
-
- async with aiohttp.ClientSession() as session:
- # 先发送HEAD请求检查文件大小
- try:
- async with session.head(url, timeout=aiohttp.ClientTimeout(total=10)) as response:
- if response.status != 200:
- logger.warning(f"HEAD请求失败,状态码: {response.status}")
- else:
- content_length = response.headers.get("Content-Length")
- if not self.check_file_size(content_length):
- return {
- "success": False,
- "error": f"视频文件过大,超过{self.max_size_mb}MB限制",
- "url": url,
- }
- except Exception as e:
- logger.warning(f"HEAD请求失败: {e},继续尝试下载")
-
- # 下载文件
- async with session.get(url, timeout=aiohttp.ClientTimeout(total=self.download_timeout)) as response:
- if response.status != 200:
- return {"success": False, "error": f"下载失败,HTTP状态码: {response.status}", "url": url}
-
- # 检查Content-Type是否为视频
- content_type = response.headers.get("Content-Type", "").lower()
- if content_type:
- # 检查是否为视频类型
- video_mime_types = [
- "video/",
- "application/octet-stream",
- "application/x-msvideo",
- "video/x-msvideo",
- ]
- is_video_content = any(mime in content_type for mime in video_mime_types)
-
- if not is_video_content:
- logger.warning(f"Content-Type不是视频格式: {content_type}")
- # 如果不是明确的视频类型,但可能是QQ的特殊格式,继续尝试
- if "text/" in content_type or "application/json" in content_type:
- return {
- "success": False,
- "error": f"URL返回的不是视频内容,Content-Type: {content_type}",
- "url": url,
- }
-
- # 再次检查Content-Length
- content_length = response.headers.get("Content-Length")
- if not self.check_file_size(content_length):
- return {"success": False, "error": f"视频文件过大,超过{self.max_size_mb}MB限制", "url": url}
-
- # 读取文件内容
- video_data = await response.read()
-
- # 检查实际文件大小
- actual_size_mb = len(video_data) / (1024 * 1024)
- if actual_size_mb > self.max_size_mb:
- return {
- "success": False,
- "error": f"视频文件过大,实际大小: {actual_size_mb:.2f}MB",
- "url": url,
- }
-
- # 确定文件名
- if filename is None:
- filename = Path(url.split("?")[0]).name
- if not filename or "." not in filename:
- filename = "video.mp4"
-
- logger.info(f"视频下载成功: {filename}, 大小: {actual_size_mb:.2f}MB")
-
- return {
- "success": True,
- "data": video_data,
- "filename": filename,
- "size_mb": actual_size_mb,
- "url": url,
- }
-
- except asyncio.TimeoutError:
- return {"success": False, "error": "下载超时", "url": url}
- except Exception as e:
- logger.error(f"下载视频时出错: {e}")
- return {"success": False, "error": str(e), "url": url}
-
-
-# 全局实例
-_video_downloader = None
-
-
-def get_video_downloader(max_size_mb: int = 100, download_timeout: int = 60) -> VideoDownloader:
- """获取视频下载器实例"""
- global _video_downloader
- if _video_downloader is None:
- _video_downloader = VideoDownloader(max_size_mb, download_timeout)
- return _video_downloader
diff --git a/src/plugins/built_in/napcat_adapter_plugin/src/websocket_manager.py b/src/plugins/built_in/napcat_adapter_plugin/src/websocket_manager.py
deleted file mode 100644
index dd248ea82..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/src/websocket_manager.py
+++ /dev/null
@@ -1,163 +0,0 @@
-import asyncio
-from typing import Any, Callable, Optional
-
-import websockets as Server
-
-from src.common.logger import get_logger
-from src.plugin_system.apis import config_api
-
-logger = get_logger("napcat_adapter")
-
-
-class WebSocketManager:
- """WebSocket 连接管理器,支持正向和反向连接"""
-
- def __init__(self):
- self.connection: Optional[Server.ServerConnection] = None
- self.server: Optional[Server.WebSocketServer] = None
- self.is_running = False
- self.reconnect_interval = 5 # 重连间隔(秒)
- self.max_reconnect_attempts = 10 # 最大重连次数
- self.plugin_config = None
-
- async def start_connection(
- self, message_handler: Callable[[Server.ServerConnection], Any], plugin_config: dict
- ) -> None:
- """根据配置启动 WebSocket 连接"""
- self.plugin_config = plugin_config
- mode = config_api.get_plugin_config(plugin_config, "napcat_server.mode")
-
- if mode == "reverse":
- await self._start_reverse_connection(message_handler)
- elif mode == "forward":
- await self._start_forward_connection(message_handler)
- else:
- raise ValueError(f"不支持的连接模式: {mode}")
-
- async def _start_reverse_connection(self, message_handler: Callable[[Server.ServerConnection], Any]) -> None:
- """启动反向连接(作为服务器)"""
- host = config_api.get_plugin_config(self.plugin_config, "napcat_server.host")
- port = config_api.get_plugin_config(self.plugin_config, "napcat_server.port")
-
- logger.info(f"正在启动反向连接模式,监听地址: ws://{host}:{port}")
-
- async def handle_client(websocket, path=None):
- self.connection = websocket
- logger.info(f"Napcat 客户端已连接: {websocket.remote_address}")
- try:
- await message_handler(websocket)
- except Exception as e:
- logger.error(f"处理客户端连接时出错: {e}")
- finally:
- self.connection = None
- logger.info("Napcat 客户端已断开连接")
-
- self.server = await Server.serve(handle_client, host, port, max_size=2**26)
- self.is_running = True
- logger.info(f"反向连接服务器已启动,监听地址: ws://{host}:{port}")
-
- # 保持服务器运行
- await self.server.serve_forever()
-
- async def _start_forward_connection(self, message_handler: Callable[[Server.ServerConnection], Any]) -> None:
- """启动正向连接(作为客户端)"""
- url = self._get_forward_url()
- logger.info(f"正在启动正向连接模式,目标地址: {url}")
-
- reconnect_count = 0
-
- while reconnect_count < self.max_reconnect_attempts:
- try:
- logger.info(f"尝试连接到 Napcat 服务器: {url}")
-
- # 准备连接参数
- connect_kwargs = {"max_size": 2**26}
-
- # 如果配置了访问令牌,添加到请求头
- access_token = config_api.get_plugin_config(self.plugin_config, "napcat_server.access_token")
- if access_token:
- connect_kwargs["additional_headers"] = {"Authorization": f"Bearer {access_token}"}
- logger.info("已添加访问令牌到连接请求头")
-
- async with Server.connect(url, **connect_kwargs) as websocket:
- self.connection = websocket
- self.is_running = True
- reconnect_count = 0 # 重置重连计数
-
- logger.info(f"成功连接到 Napcat 服务器: {url}")
-
- try:
- await message_handler(websocket)
- except Server.exceptions.ConnectionClosed:
- logger.warning("与 Napcat 服务器的连接已断开")
- except Exception as e:
- logger.error(f"处理正向连接时出错: {e}")
- finally:
- self.connection = None
- self.is_running = False
-
- except (
- Server.exceptions.ConnectionClosed,
- Server.exceptions.InvalidMessage,
- OSError,
- ConnectionRefusedError,
- ) as e:
- reconnect_count += 1
- logger.warning(f"连接失败 ({reconnect_count}/{self.max_reconnect_attempts}): {e}")
-
- if reconnect_count < self.max_reconnect_attempts:
- logger.info(f"将在 {self.reconnect_interval} 秒后重试连接...")
- await asyncio.sleep(self.reconnect_interval)
- else:
- logger.error("已达到最大重连次数,停止重连")
- raise
- except Exception as e:
- logger.error(f"正向连接时发生未知错误: {e}")
- raise
-
- def _get_forward_url(self) -> str:
- """获取正向连接的 URL"""
- # 如果配置了完整的 URL,直接使用
- url = config_api.get_plugin_config(self.plugin_config, "napcat_server.url")
- if url:
- return url
-
- # 否则根据 host 和 port 构建 URL
- host = config_api.get_plugin_config(self.plugin_config, "napcat_server.host")
- port = config_api.get_plugin_config(self.plugin_config, "napcat_server.port")
- return f"ws://{host}:{port}"
-
- async def stop_connection(self) -> None:
- """停止 WebSocket 连接"""
- self.is_running = False
-
- if self.connection:
- try:
- await self.connection.close()
- logger.info("WebSocket 连接已关闭")
- except Exception as e:
- logger.error(f"关闭 WebSocket 连接时出错: {e}")
- finally:
- self.connection = None
-
- if self.server:
- try:
- self.server.close()
- await self.server.wait_closed()
- logger.info("WebSocket 服务器已关闭")
- except Exception as e:
- logger.error(f"关闭 WebSocket 服务器时出错: {e}")
- finally:
- self.server = None
-
- def get_connection(self) -> Optional[Server.ServerConnection]:
- """获取当前的 WebSocket 连接"""
- return self.connection
-
- def is_connected(self) -> bool:
- """检查是否已连接"""
- return self.connection is not None and self.is_running
-
-
-# 全局 WebSocket 管理器实例
-websocket_manager = WebSocketManager()
diff --git a/src/plugins/built_in/napcat_adapter_plugin/todo.md b/src/plugins/built_in/napcat_adapter_plugin/todo.md
deleted file mode 100644
index 6eff60839..000000000
--- a/src/plugins/built_in/napcat_adapter_plugin/todo.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# TODO List:
-
-- [x] logger使用主程序的
-- [ ] 使用插件系统的config系统
-- [x] 接收从napcat传递的所有信息
-- [ ] 优化架构,各模块解耦,暴露关键方法用于提供接口
-- [ ] 单独一个模块负责与主程序通信
-- [ ] 使用event系统完善接口api
-
-
----
-Event分为两种,一种是对外输出的event,由napcat插件自主触发并传递参数,另一种是接收外界输入的event,由外部插件触发并向napcat传递参数
-
-
-## 例如,
-
-### 对外输出的event:
-
-napcat_on_received_text -> (message_seg: Seg) 接受到qq的文字消息,会向handler传递一个Seg
-napcat_on_received_face -> (message_seg: Seg) 接受到qq的表情消息,会向handler传递一个Seg
-napcat_on_received_reply -> (message_seg: Seg) 接受到qq的回复消息,会向handler传递一个Seg
-napcat_on_received_image -> (message_seg: Seg) 接受到qq的图片消息,会向handler传递一个Seg
-napcat_on_received_image -> (message_seg: Seg) 接受到qq的图片消息,会向handler传递一个Seg
-napcat_on_received_record -> (message_seg: Seg) 接受到qq的语音消息,会向handler传递一个Seg
-napcat_on_received_rps -> (message_seg: Seg) 接受到qq的猜拳魔法表情,会向handler传递一个Seg
-napcat_on_received_friend_invitation -> (user_id: str) 接受到qq的好友请求,会向handler传递一个user_id
-...
-
-此类event不接受外部插件的触发,只能由napcat插件统一触发。
-
-外部插件需要编写handler并订阅此类事件。
-```python
-from src.plugin_system.core.event_manager import event_manager
-from src.plugin_system.base.base_event import HandlerResult
-
-class MyEventHandler(BaseEventHandler):
- handler_name = "my_handler"
- handler_description = "我的自定义事件处理器"
- weight = 10 # 权重,越大越先执行
- intercept_message = False # 是否拦截消息
- init_subscribe = ["napcat_on_received_text"] # 初始订阅的事件
-
- async def execute(self, params: dict) -> HandlerResult:
- """处理事件"""
- try:
- message = params.get("message_seg")
- print(f"收到消息: {message.data}")
-
- # 业务逻辑处理
- # ...
-
- return HandlerResult(
- success=True,
- continue_process=True, # 是否继续让其他处理器处理
- message="处理成功",
- handler_name=self.handler_name
- )
- except Exception as e:
- return HandlerResult(
- success=False,
- continue_process=True,
- message=f"处理失败: {str(e)}",
- handler_name=self.handler_name
- )
-
-```
-
-### 接收外界输入的event:
-
-napcat_kick_group <- (user_id, group_id) 踢出某个群组中的某个用户
-napcat_mute_user <- (user_id, group_id, time) 禁言某个群组中的某个用户
-napcat_unmute_user <- (user_id, group_id) 取消禁言某个群组中的某个用户
-napcat_mute_group <- (user_id, group_id) 禁言某个群组
-napcat_unmute_group <- (user_id, group_id) 取消禁言某个群组
-napcat_add_friend <- (user_id) 向某个用户发出好友请求
-napcat_accept_friend <- (user_id) 接收某个用户的好友请求
-napcat_reject_friend <- (user_id) 拒绝某个用户的好友请求
-...
-此类事件只由外部插件触发并传递参数,由napcat完成请求任务。
-
-外部插件需要触发此类的event并传递正确的参数。
-
-```python
-from src.plugin_system.core.event_manager import event_manager
-
-# 触发事件
-await event_manager.trigger_event("napcat_accept_friend", user_id = 1234123)
-```
-