From 56b1014419a13f656f16c986efa11a8158276e1d Mon Sep 17 00:00:00 2001 From: K2cr2O1 <2221577113@qq.com> Date: Wed, 7 Jan 2026 22:51:27 +0800 Subject: [PATCH] =?UTF-8?q?refactor(core):=20=E9=87=8D=E6=9E=84=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E6=A8=A1=E5=9D=97=E7=BB=93=E6=9E=84=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=BC=80=E5=8F=91=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将核心模块按功能重新组织为更清晰的结构,包括 managers、handlers 和 utils 目录 添加完整的开发文档,涵盖快速开始、项目结构、核心概念和插件开发指南 更新所有相关模块的导入路径以匹配新的结构 将单例模式实现提取到单独的 singleton.py 文件 --- README.md | 8 ++ config.toml | 1 + core/__init__.py | 4 +- core/api/account.py | 2 +- core/api/friend.py | 2 +- core/api/group.py | 4 +- core/data/admin.json | 3 + {data => core/data}/permissions.json | 0 core/handlers/__init__.py | 0 core/{ => handlers}/event_handler.py | 19 ++-- core/managers/__init__.py | 0 core/{ => managers}/admin_manager.py | 25 ++--- core/{ => managers}/command_manager.py | 4 +- core/{ => managers}/permission_manager.py | 25 ++--- core/{ => managers}/plugin_manager.py | 8 +- core/{ => managers}/redis_manager.py | 4 +- core/utils/__init__.py | 0 core/{ => utils}/exceptions.py | 0 core/{ => utils}/executor.py | 2 +- core/{ => utils}/logger.py | 0 core/utils/singleton.py | 30 ++++++ core/ws.py | 4 +- data/admin.json | 3 - docs/core-concepts/event-flow.md | 64 +++++++++++ docs/core-concepts/singleton-managers.md | 89 +++++++++++++++ docs/deployment.md | 102 ++++++++++++++++++ docs/getting-started.md | 113 ++++++++++++++++++++ docs/index.md | 34 ++++++ docs/plugin-development/command-handling.md | 99 +++++++++++++++++ docs/plugin-development/index.md | 88 +++++++++++++++ docs/project-structure.md | 69 ++++++++++++ main.py | 17 +-- models/events/message.py | 2 +- plugins/admin.py | 4 +- plugins/bili_parser.py | 4 +- plugins/broadcast.py | 6 +- plugins/code_py.py | 6 +- plugins/echo.py | 2 +- plugins/forward_test.py | 2 +- plugins/jrcd.py | 4 +- plugins/sync_async_test_plugin.py | 6 +- plugins/thpic.py | 2 +- pytest.ini | 2 + requirements.txt | 3 + sandbox.Dockerfile | 10 -- 45 files changed, 772 insertions(+), 104 deletions(-) create mode 100644 core/data/admin.json rename {data => core/data}/permissions.json (100%) create mode 100644 core/handlers/__init__.py rename core/{ => handlers}/event_handler.py (85%) create mode 100644 core/managers/__init__.py rename core/{ => managers}/admin_manager.py (91%) rename core/{ => managers}/command_manager.py (97%) rename core/{ => managers}/permission_manager.py (93%) rename core/{ => managers}/plugin_manager.py (95%) rename core/{ => managers}/redis_manager.py (95%) create mode 100644 core/utils/__init__.py rename core/{ => utils}/exceptions.py (100%) rename core/{ => utils}/executor.py (99%) rename core/{ => utils}/logger.py (100%) create mode 100644 core/utils/singleton.py delete mode 100644 data/admin.json create mode 100644 docs/core-concepts/event-flow.md create mode 100644 docs/core-concepts/singleton-managers.md create mode 100644 docs/deployment.md create mode 100644 docs/getting-started.md create mode 100644 docs/index.md create mode 100644 docs/plugin-development/command-handling.md create mode 100644 docs/plugin-development/index.md create mode 100644 docs/project-structure.md create mode 100644 pytest.ini diff --git a/README.md b/README.md index ec20324..4f7c632 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,14 @@ --- +## 📚 详细开发文档 + +**想要深入了解框架的工作原理或开发更复杂的插件?** + +👉 **[点击这里,查阅完整的开发文档](./docs/index.md)** + +--- + ## 🚀 快速开始 ### 1. 环境准备 diff --git a/config.toml b/config.toml index 5955e20..fd8d433 100644 --- a/config.toml +++ b/config.toml @@ -6,6 +6,7 @@ reconnect_interval = 5 [bot] command = ["/"] ignore_self_message = true #是否忽略自身消息 +permission_denied_message = "权限不足,需要 {permission_name} 权限" [redis] host = "114.66.58.203" diff --git a/core/__init__.py b/core/__init__.py index 032d0c6..ad853f6 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,6 +1,6 @@ -from .command_manager import matcher +from .managers.command_manager import matcher from .config_loader import global_config -from .plugin_manager import PluginDataManager +from .managers.plugin_manager import PluginDataManager from .ws import WS __all__ = ["WS", "matcher", "global_config", "PluginDataManager"] diff --git a/core/api/account.py b/core/api/account.py index 07bab8d..7f75507 100644 --- a/core/api/account.py +++ b/core/api/account.py @@ -8,7 +8,7 @@ import json from typing import Dict, Any from .base import BaseAPI from models.objects import LoginInfo, VersionInfo, Status -from core.redis_manager import redis_manager +from ..managers.redis_manager import redis_manager class AccountAPI(BaseAPI): diff --git a/core/api/friend.py b/core/api/friend.py index 823fa06..a3a118b 100644 --- a/core/api/friend.py +++ b/core/api/friend.py @@ -8,7 +8,7 @@ import json from typing import List, Dict, Any from .base import BaseAPI from models.objects import FriendInfo, StrangerInfo -from core.redis_manager import redis_manager +from ..managers.redis_manager import redis_manager class FriendAPI(BaseAPI): diff --git a/core/api/group.py b/core/api/group.py index 2f2b6ac..5714baf 100644 --- a/core/api/group.py +++ b/core/api/group.py @@ -6,10 +6,10 @@ """ from typing import List, Dict, Any import json -from core.redis_manager import redis_manager +from ..managers.redis_manager import redis_manager from .base import BaseAPI from models.objects import GroupInfo, GroupMemberInfo, GroupHonorInfo -from core.logger import logger +from ..utils.logger import logger class GroupAPI(BaseAPI): diff --git a/core/data/admin.json b/core/data/admin.json new file mode 100644 index 0000000..b3c7949 --- /dev/null +++ b/core/data/admin.json @@ -0,0 +1,3 @@ +{ + "admins": [] +} \ No newline at end of file diff --git a/data/permissions.json b/core/data/permissions.json similarity index 100% rename from data/permissions.json rename to core/data/permissions.json diff --git a/core/handlers/__init__.py b/core/handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/event_handler.py b/core/handlers/event_handler.py similarity index 85% rename from core/event_handler.py rename to core/handlers/event_handler.py index 8c384f9..3b1540e 100644 --- a/core/event_handler.py +++ b/core/handlers/event_handler.py @@ -8,10 +8,10 @@ import inspect from abc import ABC, abstractmethod from typing import Any, Callable, Dict, List, Optional, Tuple -from .bot import Bot -from .permission_manager import Permission, permission_manager -from .exceptions import SyncHandlerError -from .executor import run_in_thread_pool +from ..bot import Bot +from ..config_loader import global_config +from ..managers.permission_manager import Permission, permission_manager +from ..utils.executor import run_in_thread_pool class BaseHandler(ABC): @@ -75,8 +75,6 @@ class MessageHandler(BaseHandler): 注册通用消息处理器 """ def decorator(func: Callable) -> Callable: - if not inspect.iscoroutinefunction(func): - raise SyncHandlerError(f"消息处理器 {func.__name__} 必须是异步函数 (async def).") self.message_handlers.append(func) return func return decorator @@ -91,8 +89,6 @@ class MessageHandler(BaseHandler): 注册命令处理器 """ def decorator(func: Callable) -> Callable: - if not inspect.iscoroutinefunction(func): - raise SyncHandlerError(f"命令处理器 {func.__name__} 必须是异步函数 (async def).") for name in names: self.commands[name] = { "func": func, @@ -139,7 +135,8 @@ class MessageHandler(BaseHandler): if not permission_granted and not override_check: permission_name = permission.name if isinstance(permission, Permission) else permission - await bot.send(event, f"权限不足,需要 {permission_name} 权限") + message_template = global_config.bot.get("permission_denied_message", "权限不足,需要 {permission_name} 权限") + await bot.send(event, message_template.format(permission_name=permission_name)) return await self._run_handler( @@ -160,8 +157,6 @@ class NoticeHandler(BaseHandler): 注册通知处理器 """ def decorator(func: Callable) -> Callable: - if not inspect.iscoroutinefunction(func): - raise SyncHandlerError(f"通知处理器 {func.__name__} 必须是异步函数 (async def).") self.handlers.append({"type": notice_type, "func": func}) return func return decorator @@ -184,8 +179,6 @@ class RequestHandler(BaseHandler): 注册请求处理器 """ def decorator(func: Callable) -> Callable: - if not inspect.iscoroutinefunction(func): - raise SyncHandlerError(f"请求处理器 {func.__name__} 必须是异步函数 (async def).") self.handlers.append({"type": request_type, "func": func}) return func return decorator diff --git a/core/managers/__init__.py b/core/managers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/admin_manager.py b/core/managers/admin_manager.py similarity index 91% rename from core/admin_manager.py rename to core/managers/admin_manager.py index 864df52..7e5f0d1 100644 --- a/core/admin_manager.py +++ b/core/managers/admin_manager.py @@ -9,33 +9,25 @@ import json import os from typing import Set -from .logger import logger +from ..utils.logger import logger +from ..utils.singleton import Singleton +from .redis_manager import redis_manager -class AdminManager: +class AdminManager(Singleton): """ 管理员管理器类 负责加载、缓存和管理管理员列表。 使用单例模式,确保全局只有一个实例。 """ - _instance = None _REDIS_KEY = "neobot:admins" # 用于存储管理员集合的 Redis 键 - - def __new__(cls): - """ - 单例模式实现 - """ - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance._initialized = False - return cls._instance - def __init__(self): """ 初始化 AdminManager """ - if getattr(self, "_initialized", False): + super().__init__() + if not self._initialized: return # 管理员数据文件路径 @@ -47,7 +39,6 @@ class AdminManager: ) self._admins: Set[int] = set() - self._initialized = True logger.info("管理员管理器初始化完成") async def initialize(self): @@ -96,7 +87,7 @@ class AdminManager: """ 将内存中的管理员集合同步到 Redis """ - from .redis_manager import redis_manager + from core.managers.redis_manager import redis_manager try: # 首先清空旧的集合 await redis_manager.redis.delete(self._REDIS_KEY) @@ -111,7 +102,7 @@ class AdminManager: """ 检查用户是否为管理员(从 Redis 缓存读取) """ - from .redis_manager import redis_manager + try: return await redis_manager.redis.sismember(self._REDIS_KEY, user_id) except Exception as e: diff --git a/core/command_manager.py b/core/managers/command_manager.py similarity index 97% rename from core/command_manager.py rename to core/managers/command_manager.py index 058cc89..9f6fb19 100644 --- a/core/command_manager.py +++ b/core/managers/command_manager.py @@ -7,8 +7,8 @@ """ from typing import Any, Callable, Dict, Optional, Tuple -from .config_loader import global_config -from .event_handler import MessageHandler, NoticeHandler, RequestHandler +from ..config_loader import global_config +from ..handlers.event_handler import MessageHandler, NoticeHandler, RequestHandler # 从配置中获取命令前缀 diff --git a/core/permission_manager.py b/core/managers/permission_manager.py similarity index 93% rename from core/permission_manager.py rename to core/managers/permission_manager.py index 917e753..c4d2825 100644 --- a/core/permission_manager.py +++ b/core/managers/permission_manager.py @@ -16,8 +16,9 @@ import os from functools import total_ordering from typing import Dict -from .logger import logger -from .admin_manager import admin_manager # 导入 AdminManager +from ..utils.logger import logger +from ..utils.singleton import Singleton +from .admin_manager import admin_manager @total_ordering @@ -73,7 +74,7 @@ _PERMISSIONS: Dict[str, Permission] = { } -class PermissionManager: +class PermissionManager(Singleton): """ 权限管理器类 @@ -81,27 +82,14 @@ class PermissionManager: 使用单例模式,确保全局只有一个权限管理器实例。 """ - _instance = None - - def __new__(cls): - """ - 单例模式实现 - - Returns: - PermissionManager: 全局唯一的权限管理器实例 - """ - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance._initialized = False - return cls._instance - def __init__(self): """ 初始化权限管理器 如果已经初始化过,则直接返回。 """ - if getattr(self, "_initialized", False): + super().__init__() + if not self._initialized: return # 权限数据文件路径 @@ -122,7 +110,6 @@ class PermissionManager: # 加载现有数据 self.load() - self._initialized = True logger.info("权限管理器初始化完成") def load(self) -> None: diff --git a/core/plugin_manager.py b/core/managers/plugin_manager.py similarity index 95% rename from core/plugin_manager.py rename to core/managers/plugin_manager.py index 8b0e7f1..0141b8c 100644 --- a/core/plugin_manager.py +++ b/core/managers/plugin_manager.py @@ -10,10 +10,10 @@ import os import pkgutil import sys -from core.command_manager import matcher -from core.exceptions import SyncHandlerError -from .logger import logger -from .executor import run_in_thread_pool +from .command_manager import matcher +from ..utils.exceptions import SyncHandlerError +from ..utils.logger import logger +from ..utils.executor import run_in_thread_pool def load_all_plugins(): diff --git a/core/redis_manager.py b/core/managers/redis_manager.py similarity index 95% rename from core/redis_manager.py rename to core/managers/redis_manager.py index 7440a22..cdc16cc 100644 --- a/core/redis_manager.py +++ b/core/managers/redis_manager.py @@ -1,6 +1,6 @@ import redis.asyncio as redis -from .config_loader import global_config as config -from .logger import logger +from ..config_loader import global_config as config +from ..utils.logger import logger class RedisManager: """ diff --git a/core/utils/__init__.py b/core/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/exceptions.py b/core/utils/exceptions.py similarity index 100% rename from core/exceptions.py rename to core/utils/exceptions.py diff --git a/core/executor.py b/core/utils/executor.py similarity index 99% rename from core/executor.py rename to core/utils/executor.py index 73599d9..3882a32 100644 --- a/core/executor.py +++ b/core/utils/executor.py @@ -4,7 +4,7 @@ import docker from docker.tls import TLSConfig from typing import Dict, Any, Callable -from core.logger import logger +from core.utils.logger import logger class CodeExecutor: """ diff --git a/core/logger.py b/core/utils/logger.py similarity index 100% rename from core/logger.py rename to core/utils/logger.py diff --git a/core/utils/singleton.py b/core/utils/singleton.py new file mode 100644 index 0000000..db45819 --- /dev/null +++ b/core/utils/singleton.py @@ -0,0 +1,30 @@ +""" +通用单例模式基类 +""" + +class Singleton: + """ + 一个通用的单例基类 + + 任何继承自该类的子类都将自动成为单例。 + 它通过重写 __new__ 方法来确保每个类只有一个实例。 + 同时,它处理了重复初始化的问题,确保 __init__ 方法只在第一次实例化时被调用。 + """ + _instance = None + _initialized = False + + def __new__(cls, *args, **kwargs): + """ + 创建或返回现有的实例 + """ + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + """ + 确保初始化逻辑只执行一次 + """ + if self._initialized: + return + self._initialized = True diff --git a/core/ws.py b/core/ws.py index c9d2b77..2de5244 100644 --- a/core/ws.py +++ b/core/ws.py @@ -22,9 +22,9 @@ import websockets from models import EventFactory from .bot import Bot -from .command_manager import matcher from .config_loader import global_config -from .logger import logger +from .managers.command_manager import matcher +from .utils.logger import logger class WS: diff --git a/data/admin.json b/data/admin.json deleted file mode 100644 index 577c240..0000000 --- a/data/admin.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "admins": [2221577113] -} \ No newline at end of file diff --git a/docs/core-concepts/event-flow.md b/docs/core-concepts/event-flow.md new file mode 100644 index 0000000..5d7275e --- /dev/null +++ b/docs/core-concepts/event-flow.md @@ -0,0 +1,64 @@ +# 核心概念:事件流转 + +在 NEO Bot Framework 中,所有交互都由**事件**驱动。理解一个事件从被接收到最终被处理的完整流程,是掌握框架工作原理的关键。 + +本节将以一个用户发送 `/echo hello` 的群聊消息为例,详细拆解其在框架内部的流转路径。 + +## 事件流转图 + +```mermaid +graph TD + A[OneBot v11 实现端] -- WebSocket Message --> B(core/ws.py); + B -- Raw JSON Data --> C(models/events/factory.py); + C -- Event Object --> D(core/ws.py on_event); + D -- Event Object --> E(core/managers/command_manager.py); + E -- Event & Command Match --> F(core/handlers/event_handler.py); + F -- Matched Handler --> G(plugins/echo.py); + G -- Call API --> H(core/bot.py); + H -- Send Request --> B; + B -- WebSocket Send --> A; +``` + +## 详细步骤 + +### 1. 接收 WebSocket 消息 (`core/ws.py`) + +* 当用户在 QQ 群里发送消息时,OneBot v11 实现端(如 NapCatQQ)会将其打包成一个 JSON 格式的数据,并通过 WebSocket 连接发送给框架。 +* `core/ws.py` 中的 `_listen_loop` 方法持续监听连接,接收到这个原始的 JSON 字符串。 + +### 2. 事件对象实例化 (`models/events/factory.py`) + +* `ws.py` 将接收到的 JSON 数据传递给 `EventFactory.create_event()`。 +* `EventFactory` 会根据 JSON 中的 `post_type` 字段(例如 `"message"`)和 `message_type` 字段(例如 `"group"`),智能地将其解析并实例化为对应的 Python 对象,例如 `GroupMessageEvent`。 +* 这个 `Event` 对象包含了所有事件信息,并且具有清晰的类型提示,方便后续处理。 + +### 3. 事件初步处理与分发 (`core/ws.py`) + +* `ws.py` 的 `on_event` 方法接收到 `Event` 对象后,会做两件重要的事: + 1. **注入 `Bot` 实例**:将 `self.bot` 赋值给 `event.bot`。这使得插件开发者可以在事件处理器中直接通过 `event.reply()` 或 `event.bot.send(...)` 来调用 API。 + 2. **分发事件**:将 `Event` 对象传递给全局的命令管理器 `matcher.handle_event(bot, event)`。 + +### 4. 指令匹配与处理器查找 (`core/managers/command_manager.py`) + +* `CommandManager` (即 `matcher`) 是事件处理的核心中枢。 +* 它的 `handle_event` 方法会首先判断事件类型。对于消息事件,它会将其交给内部的 `MessageHandler`。 +* `MessageHandler` 会检查消息内容是否以已注册的命令前缀(如 `/`)开头。 +* 如果匹配成功(例如 `/echo`),它会从已注册的命令字典中查找对应的处理函数(即在 `echo.py` 中被 `@matcher.command("echo")` 装饰的函数)。 + +### 5. 执行插件逻辑 (`plugins/echo.py`) + +* `MessageHandler` 找到了匹配的处理器后,会调用它,并将 `Event` 对象和解析出的参数(`args`)传递进去。 +* 此时,控制权就完全交给了插件开发者编写的函数,例如 `handle_echo_command(event, args)`。 +* 插件函数可以执行任意逻辑,比如操作数据库、请求外部 API,或者调用 `Bot` 的 API 来回复消息。 + +### 6. API 调用与响应 (`core/bot.py` -> `core/ws.py`) + +* 当插件调用 `event.reply("hello")` 时,实际上是调用了 `core/bot.py` 中封装的 `send` 方法。 +* `Bot` 类会将这个调用转换为一个标准的 OneBot v11 API 请求(例如 `{"action": "send_group_msg", "params": {...}}`)。 +* 这个请求最终通过 `core/ws.py` 的 `call_api` 方法,被序列化为 JSON 字符串,并通过 WebSocket 发送回 OneBot v11 实现端。 + +### 7. 消息发送 + +* OneBot v11 实现端接收到 API 请求后,执行相应的操作——将 "hello" 这条消息发送到原来的 QQ 群。 + +至此,一个完整的事件流转闭环就完成了。理解这个流程后,您就能明白框架是如何将底层的网络通信与高层的插件逻辑解耦,并为开发者提供便捷接口的。 diff --git a/docs/core-concepts/singleton-managers.md b/docs/core-concepts/singleton-managers.md new file mode 100644 index 0000000..41d64b5 --- /dev/null +++ b/docs/core-concepts/singleton-managers.md @@ -0,0 +1,89 @@ +# 核心概念:单例管理器 + +在 `core/managers/` 目录下,存放着一系列全局唯一的**管理器(Managers)**。它们是 NEO Bot Framework 功能的核心实现,负责处理事件、管理权限、加载插件等关键任务。 + +理解这些管理器的职责,有助于您更好地利用框架提供的能力,并进行更高级的开发。 + +## 设计模式:单例 (Singleton) + +框架中所有的管理器都采用了**单例设计模式**。这意味着在整个应用程序的生命周期中,每个管理器类只会存在一个实例。 + +**为什么使用单例?** + +* **全局访问点**: 任何模块(尤其是插件)都可以方便地导入并使用同一个管理器实例,无需手动传递。 +* **状态共享**: 管理器内部维护的状态(如已注册的命令、用户权限列表)是全局共享和一致的。 +* **资源统一管理**: 对于像 Redis 连接这样的资源,单例模式确保了全局只有一个连接池,避免了资源的浪费和冲突。 + +框架在 `core/utils/singleton.py` 中提供了一个 `Singleton` 基类,所有管理器都继承自它,以轻松实现单例模式。 + +## 核心管理器介绍 + +### 1. `CommandManager` (全局实例: `matcher`) + +* **文件**: `core/managers/command_manager.py` +* **全局实例**: `from core.managers.command_manager import matcher` +* **核心职责**: + * **事件处理中枢**: 它是事件流转的核心,负责接收所有类型的事件,并将其分发给相应的底层处理器。 + * **装饰器提供者**: 为插件提供了 `@matcher.command()`, `@matcher.on_notice()` 等一系列装饰器,用于注册事件处理器。 + * **指令匹配**: 内部维护了一个指令注册表,能够根据消息内容匹配到对应的处理函数。 + +`matcher` 是插件开发者最常打交道的管理器。 + +### 2. `PermissionManager` (全局实例: `permission_manager`) + +* **文件**: `core/managers/permission_manager.py` +* **全局实例**: `from core.managers.permission_manager import permission_manager` +* **核心职责**: + * **权限定义与检查**: 定义了 `ADMIN`, `OP`, `USER` 等权限等级,并提供了 `check_permission` 方法来验证用户权限。 + * **数据持久化**: 负责从 `core/data/permissions.json` 文件中加载和保存用户权限设置。 + * **与 `AdminManager` 联动**: 在检查权限时,会自动将机器人管理员(来自 `AdminManager`)识别为最高权限 `ADMIN`。 + +### 3. `AdminManager` (全局实例: `admin_manager`) + +* **文件**: `core/managers/admin_manager.py` +* **全局实例**: `from core.managers.admin_manager import admin_manager` +* **核心职责**: + * **管理员管理**: 提供 `add_admin`, `remove_admin`, `is_admin` 等接口,用于管理机器人的超级管理员列表。 + * **数据同步**: 实现了内存、`core/data/admin.json` 文件以及 Redis 缓存之间的数据同步,确保管理员列表的一致性和高效查询。 + +### 4. `PluginManager` + +* **文件**: `core/managers/plugin_manager.py` +* **核心职责**: + * **插件加载**: 负责扫描 `plugins/` 目录,导入所有合法的插件模块。 + * **元数据提取**: 读取插件文件中定义的 `__plugin_meta__` 字典,用于 `/help` 指令等功能。 + * **热重载支持**: `load_all_plugins` 函数被 `main.py` 中的文件监控服务调用,以实现插件的热重载。 + +此管理器通常在后台工作,开发者较少直接与其交互。 + +### 5. `RedisManager` (全局实例: `redis_manager`) + +* **文件**: `core/managers/redis_manager.py` +* **全局实例**: `from core.managers.redis_manager import redis_manager` +* **核心职责**: + * **连接管理**: 负责初始化和管理与 Redis 服务器的异步连接。 + * **提供实例**: 通过 `redis_manager.redis` 属性,为其他模块提供一个可用的 `redis` 客户端实例。 + +## 如何在插件中使用管理器 + +在您的插件中,只需通过 `import` 语句导入相应管理器的全局实例即可使用。 + +**示例**: 在插件中检查用户是否为管理员。 + +```python +# plugins/my_plugin.py + +from core.managers.command_manager import matcher +from core.managers.permission_manager import permission_manager, ADMIN +from models.events.message import MessageEvent + +@matcher.command("secret") +async def secret_command(event: MessageEvent): + # 使用 permission_manager 检查用户权限 + is_admin = await permission_manager.check_permission(event.user_id, ADMIN) + + if is_admin: + await event.reply("这是一个只有管理员能看到的秘密。") + else: + await event.reply("抱歉,您没有权限执行此命令。") +``` diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..bc5b22d --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,102 @@ +# 部署指南 + +当您的机器人开发完成并准备投入生产环境时,本指南将为您提供部署的最佳实践和建议。 + +## 1. 生产环境配置 + +与开发环境不同,生产环境要求更高的稳定性和安全性。 + +### 创建生产配置文件 + +建议您复制一份 `config.toml` 并重命名为 `config.prod.toml`,专门用于生产环境。 + +**关键修改项**: + +* **数据库与服务地址**: + * 确保 `napcat_ws` 和 `redis` 部分的地址、端口和密码都指向您的生产服务器,而不是本地开发环境。 + + + +## 2. 使用进程守护工具 + +直接在终端中运行 `python main.py` 适用于开发,但在生产环境中,如果终端关闭或程序意外崩溃,机器人就会下线。 + +为了确保机器人能够 7x24 小时稳定运行,您应该使用**进程守护工具**。 + +### 推荐工具 + +* **PM2 (Node.js)**: 尽管是 Node.js 工具,但 PM2 提供了强大的 Python 进程管理功能,包括崩溃自启、日志管理和性能监控。 +* **Supervisor (Python)**: 一个纯 Python 实现的进程控制系统,配置简单,稳定可靠。 +* **Systemd (Linux)**: Linux 系统自带的服务管理器,可以创建系统服务来管理机器人进程。 + +### 使用 PM2 (示例) + +1. **安装 PM2**: + ```bash + npm install -g pm2 + ``` + +2. **创建生态系统文件**: + 在项目根目录创建一个 `ecosystem.config.js` 文件: + + ```javascript + // ecosystem.config.js + module.exports = { + apps: [ + { + name: 'neo-bot', // 应用名称 + script: 'main.py', // 启动脚本 + interpreter: '/path/to/your/venv/bin/python', // 指定虚拟环境的 Python 解释器 + env: { + 'APP_ENV': 'production', // 设置环境变量 + }, + }, + ], + }; + ``` + **注意**: 请务必将 `interpreter` 路径修改为您服务器上虚拟环境的实际路径。 + +3. **启动应用**: + ```bash + pm2 start ecosystem.config.js + ``` + +4. **常用 PM2 命令**: + * `pm2 list`: 查看所有应用状态 + * `pm2 logs neo-bot`: 查看日志 + * `pm2 restart neo-bot`: 重启应用 + * `pm2 stop neo-bot`: 停止应用 + * `pm2 startup`: 设置开机自启 + +## 3. 禁用热重载 + +热重载功能在开发时非常有用,但在生产环境中会带来不必要的性能开销和潜在的不稳定性。 + +在部署前,建议您在 `main.py` 中**注释掉**或移除与 `watchdog` 相关的文件监控代码。 + +**修改 `main.py`**: + +```python +# main.py + +async def main(): + # ... + + # 生产环境中禁用文件监控 + # loop = asyncio.get_running_loop() + # event_handler = PluginReloadHandler(loop) + # observer = Observer() + # if os.path.exists(plugin_path): + # observer.schedule(event_handler, plugin_path, recursive=True) + # observer.start() + # logger.info(f"已启动插件热重载监控: {plugin_path}") + + try: + # ... + finally: + # if observer.is_alive(): + # observer.stop() + # observer.join() +``` + +遵循以上步骤,您就可以将 NEO Bot 机器人稳定、高效地部署在生产服务器上。 diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..3eca810 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,113 @@ +# 快速上手 + +本指南将引导您完成 NEO Bot Framework 的本地开发环境搭建、配置和首次运行。 + +## 1. 环境准备 + +在开始之前,请确保您的开发环境中已安装以下软件: + +* **Python**: 版本要求 `3.12` 或更高。 + * 我们推荐使用官方的 CPython 解释器。 + * 您可以通过在终端运行 `python --version` 来检查您的 Python 版本。 + +* **Git**: 用于克隆项目仓库。 + +* **Redis**: 一个键值对数据库,用于缓存和数据共享。 + * 对于 Windows 用户,可以考虑使用 `memurai` 或通过 WSL2 安装 Redis。 + * 对于 macOS 用户,可以使用 `brew install redis`。 + * 安装后,请确保 Redis 服务正在运行。 + +* **OneBot v11 实现端**: 机器人框架需要连接到一个实现了 OneBot v11 协议的客户端。 + * **推荐**: [NapCatQQ](https://github.com/NapNeko/NapCatQQ) + +## 2. 克隆与安装 + +### 克隆项目 + +打开您的终端,并克隆项目仓库到本地: + +```bash +git clone [项目仓库地址] +cd [项目目录] +``` + +### 创建虚拟环境 (推荐) + +为了保持项目依赖的隔离,强烈建议您创建一个 Python 虚拟环境。 + +```bash +# 创建虚拟环境 +python -m venv venv + +# 激活虚拟环境 +# Windows +.\venv\Scripts\activate +# macOS / Linux +source venv/bin/activate +``` + +### 安装依赖 + +激活虚拟环境后,使用 `pip` 安装所有必需的第三方库: + +```bash +pip install -r requirements.txt +``` + +## 3. 配置 + +项目的核心配置位于根目录下的 `config.toml` 文件中。 + +对于内部开发,该文件通常已预先配置好,可以直接连接到测试服务器。如果您需要连接到自己的环境,请修改以下关键部分: + +```toml +# config.toml + +[napcat_ws] +# 您的 OneBot v11 实现端的 WebSocket 地址 +# 格式通常为 ws://:<端口号> +uri = "ws://127.0.0.1:3001" + +# Access Token (访问令牌),如果您的 OneBot 端设置了 +token = "" + +[redis] +# Redis 服务的连接信息 +host = "127.0.0.1" +port = 6379 +db = 0 +password = "" # 如果您的 Redis 设置了密码 +``` + +## 4. 首次运行 + +完成以上所有步骤后,您就可以启动机器人了。在项目根目录运行: + +```bash +python main.py +``` + +如果一切顺利,您将在控制台看到类似以下的输出: + +``` +2026-01-07 22:42:41.718 | INFO | ... - 管理员管理器初始化完成 +2026-01-07 22:42:41.826 | INFO | ... - 正在从 plugins 加载插件... +2026-01-07 22:42:41.994 | SUCCESS | ... - Redis 连接成功! +... +2026-01-07 22:42:42.618 | SUCCESS | ... - 连接成功! +``` + +看到 `连接成功!` 的日志,即表示您的机器人已成功连接到 OneBot 客户端并准备好接收消息。 + +## 5. 常见问题排查 (FAQ) + +* **Q: 启动时报错 `redis.exceptions.ConnectionError`** + * **A**: 请检查您的 Redis 服务是否已启动,以及 `config.toml` 中的 `host` 和 `port` 是否正确。 + +* **Q: 无法连接到 WebSocket,提示 `ConnectionRefusedError`** + * **A**: 请确认您的 OneBot v11 客户端(如 NapCatQQ)是否正在运行,并检查 `config.toml` 中的 `uri` 地址和端口是否匹配。 + +* **Q: 修改了插件代码但没有生效** + * **A**: 框架默认开启了热重载功能。请检查控制台是否有 `[HotReload]` 相关的日志输出。如果没有,请确认 `watchdog` 库已正确安装。 + +现在,您的开发环境已经准备就绪。接下来,您可以尝试修改一个现有插件或[创建您的第一个插件](./plugin-development/index.md)! diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..508c500 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,34 @@ +# NEO Bot Framework 开发文档 + +欢迎来到 NEO Bot Framework 的官方开发文档。 + +本文档旨在为开发者提供一个清晰、全面的指南,帮助您理解框架的设计理念、核心功能,并快速上手插件开发。 + +## 📖 文档结构 + +本站点的文档分为以下几个主要部分: + +* **基础入门** + * [快速上手](./getting-started.md): 从零开始配置和运行您的第一个机器人实例。 + * [项目结构解析](./project-structure.md): 详细介绍框架的目录和文件结构。 + +* **核心概念** + * [事件流转](./core-concepts/event-flow.md): 深入理解一个事件从接收到处理的完整生命周期。 + * [单例管理器](./core-concepts/singleton-managers.md): 了解框架中核心管理器(如 `CommandManager`, `PermissionManager`)的设计与使用。 + +* **插件开发** + * [基础指南](./plugin-development/index.md): 学习如何创建一个插件,包括元数据定义和热重载工作流。 + * [指令处理](./plugin-development/command-handling.md): 掌握如何使用 `@matcher.command()` 装饰器注册和处理聊天指令。 + +* **部署** + * [部署指南](./deployment.md): 了解如何在生产环境中部署和维护机器人。 + +## 🤝 如何贡献 + +我们欢迎任何形式的贡献,无论是代码提交、文档修正还是功能建议。 + +* **报告问题**: 如果您在使用中遇到任何问题或 Bug,请通过内部渠道提交 Issue。 +* **提交代码**: 请遵循项目的编码规范,并通过 Pull Request 流程提交您的代码。 +* **完善文档**: 如果您发现文档中有任何错误或遗漏,可以直接提出修改建议。 + +我们希望这份文档能让您的开发之旅更加顺畅。如果您有任何疑问,请随时与我们联系。 diff --git a/docs/plugin-development/command-handling.md b/docs/plugin-development/command-handling.md new file mode 100644 index 0000000..fdb2b94 --- /dev/null +++ b/docs/plugin-development/command-handling.md @@ -0,0 +1,99 @@ +# 插件开发:指令处理 + +`@matcher.command()` 是插件开发中使用最频繁的装饰器。本节将深入介绍它的高级用法,帮助您构建功能更强大的指令。 + +## 1. 获取指令参数 + +在很多场景下,指令都需要接收用户提供的参数,例如 `/weather 北京`。框架会自动解析这些参数,并通过函数签名注入到您的处理器中。 + +您只需要在处理函数的参数列表中添加一个名为 `args` 的参数,并指定其类型为 `list[str]`。 + +```python +# plugins/weather.py +from core.managers.command_manager import matcher +from models.events.message import MessageEvent + +@matcher.command("weather") +async def handle_weather_command(event: MessageEvent, args: list[str]): + """ + 处理 /weather 指令 + :param event: 消息事件对象 + :param args: 用户发送的参数列表 (已按空格分割) + """ + if not args: + await event.reply("请输入城市名,例如:/weather 北京") + return + + # args[0] 就是 "北京" + city = args[0] + + # ...后续逻辑... + await event.reply(f"正在查询 {city} 的天气...") +``` + +* 如果用户发送 `/weather 北京`,`args` 将是 `['北京']`。 +* 如果用户发送 `/weather 上海 浦东`,`args` 将是 `['上海', '浦东']`。 +* 如果用户只发送 `/weather`,`args` 将是一个空列表 `[]`。 + +## 2. 设置指令别名 + +同一个功能,用户可能习惯使用不同的指令名称来触发,例如 `天气` 和 `weather`。`@matcher.command()` 允许您为一个处理器设置多个别名。 + +只需在装饰器中传入多个名称即可: + +```python +@matcher.command("weather", "天气") +async def handle_weather_command(event: MessageEvent, args: list[str]): + # ... +``` + +现在,用户发送 `/weather 北京` 或 `/天气 北京` 都可以触发这个函数。 + +## 3. 权限控制 + +某些敏感指令只希望特定权限的用户才能执行,例如 `/reload` (重载插件) 或 `/ban` (禁言用户)。 + +`@matcher.command()` 装饰器提供了一个 `permission` 参数,可以轻松实现权限控制。 + +首先,从 `permission_manager` 导入预设的权限等级: + +```python +from core.managers.permission_manager import ADMIN, OP, USER +``` + +然后,在装饰器中指定所需的权限: + +```python +# plugins/admin_tools.py +from core.managers.command_manager import matcher +from core.managers.permission_manager import ADMIN +from models.events.message import MessageEvent + +__plugin_meta__ = { + "name": "管理工具", + "description": "提供机器人管理功能", + "usage": "/reload - 重载所有插件 (仅管理员)", +} + +@matcher.command("reload", permission=ADMIN) +async def handle_reload_command(event: MessageEvent): + """ + 重载所有插件,仅限管理员使用。 + """ + # 这里的逻辑只有在权限检查通过后才会执行 + await event.reply("正在重载所有插件...") + # ... 执行重载逻辑 ... +``` + +* **工作原理**: 在调用您的处理函数之前,`CommandManager` 会自动调用 `PermissionManager` 来检查用户的权限。 +* **失败响应**: 如果用户权限不足,框架会自动回复一条权限不足的消息(该消息内容可在 `config.toml` 中配置),并且**不会**执行您的处理函数。 + +可用的权限等级: + +* `ADMIN`: 机器人超级管理员。 +* `OP`: 管理员(Operator),权限低于 `ADMIN`。 +* `USER`: 普通用户,默认权限。 + +权限关系是 `ADMIN > OP > USER`。设置 `permission=OP` 意味着 `OP` 和 `ADMIN` 都可以使用该指令。 + +通过组合使用参数处理、别名和权限控制,您可以构建出既灵活又安全的指令来满足各种复杂的需求。 diff --git a/docs/plugin-development/index.md b/docs/plugin-development/index.md new file mode 100644 index 0000000..4677ad5 --- /dev/null +++ b/docs/plugin-development/index.md @@ -0,0 +1,88 @@ +# 插件开发:基础指南 + +在 NEO Bot Framework 中,几乎所有的功能都是通过**插件**来实现的。框架提供了一个强大而简单的插件系统,让您可以专注于功能逻辑的实现。 + +## 插件是什么? + +一个插件本质上就是一个位于 `plugins/` 目录下的独立 Python 文件 (`.py`)。 + +框架会在启动时自动扫描并加载这个目录下的所有文件作为插件。 + +## 🔥 热重载工作流 + +在开始编写插件之前,了解框架的**热重载**机制至关重要,它能极大地提升您的开发效率。 + +1. **启动机器人**: 首先,在您的终端中运行 `python main.py` 并保持其运行状态。 +2. **创建或修改插件**: 在 `plugins/` 目录下创建新的 `.py` 文件,或者修改一个已有的插件文件。 +3. **保存文件**: 当您保存文件时,框架会自动检测到文件变更。 +4. **自动重载**: 控制台会显示 `插件重载完成` 的日志,这意味着您的新代码已经生效,无需重启整个程序。 + +## 创建您的第一个插件 + +让我们来创建一个经典的 "Hello World" 插件。 + +### 1. 创建文件 + +在 `plugins/` 目录下创建一个新文件,命名为 `hello.py`。 + +### 2. 定义插件元数据 (`__plugin_meta__`) + +为了让框架能够识别您的插件信息(例如在 `/help` 命令中显示),您需要在文件顶部定义一个名为 `__plugin_meta__` 的特殊字典。 + +```python +# plugins/hello.py + +__plugin_meta__ = { + "name": "你好世界", + "description": "一个简单的插件,用于回复 'Hello, World!'。", + "usage": "/hello - 发送问候。", +} +``` + +* `name`: 插件的名称。 +* `description`: 插件功能的简短描述。 +* `usage`: 插件的使用方法说明。 + +### 3. 编写处理器 + +现在,让我们来编写一个响应 `/hello` 指令的函数。我们需要从框架中导入 `matcher` 和事件类型。 + +```python +# plugins/hello.py + +from core.managers.command_manager import matcher +from models.events.message import MessageEvent + +__plugin_meta__ = { + "name": "你好世界", + "description": "一个简单的插件,用于回复 'Hello, World!'。", + "usage": "/hello - 发送问候。", +} + +# 使用 @matcher.command 装饰器来注册一个指令 +@matcher.command("hello") +async def handle_hello_command(event: MessageEvent): + """ + 当用户发送 /hello 时,此函数将被调用。 + """ + # 使用 event.reply() 方法可以快速回复消息到来源地 + await event.reply("Hello, World!") +``` + +### 4. 测试插件 + +1. 确保 `python main.py` 正在运行。 +2. 保存 `plugins/hello.py` 文件。您应该会在控制台看到插件重载的日志。 +3. 在任何一个机器人所在的群聊或私聊中,发送 `/hello`。 +4. 机器人应该会回复 `Hello, World!`。 + +恭喜!您已经成功创建并运行了您的第一个插件。 + +## 插件的最佳实践 + +* **保持独立**: 尽量让每个插件文件只负责一项相关的功能。 +* **清晰命名**: 为您的插件文件和处理函数选择清晰、描述性的名称。 +* **善用模型**: 充分利用 `models` 中定义的事件和消息段类型,以获得完整的类型提示和代码补全支持。 +* **异步优先**: 框架是基于 `asyncio` 构建的。对于任何 I/O 密集型操作(如网络请求、文件读写),请务必使用 `async/await` 语法,以避免阻塞事件循环。 + +现在您已经掌握了插件的基础,可以继续学习更高级的主题,例如[如何处理带参数的指令](./command-handling.md)。 diff --git a/docs/project-structure.md b/docs/project-structure.md new file mode 100644 index 0000000..4690b15 --- /dev/null +++ b/docs/project-structure.md @@ -0,0 +1,69 @@ +# 项目结构解析 + +理解 NEO Bot Framework 的项目结构是高效开发的第一步。本节将详细介绍每个主要目录和文件的用途。 + +``` +. +├── core/ # 框架核心代码 +│ ├── api/ # OneBot v11 API 的 Mixin 封装 +│ ├── data/ # 核心模块的数据存储 (admin, permissions) +│ ├── handlers/ # 底层事件处理器 (message, notice, request) +│ ├── managers/ # 核心单例管理器 (command, permission, etc.) +│ ├── utils/ # 通用工具 (logger, singleton, etc.) +│ ├── bot.py # Bot 核心类,提供 API 调用接口 +│ ├── config_loader.py # TOML 配置文件加载器 +│ └── ws.py # WebSocket 底层通信模块 +├── docs/ # 开发文档 +├── html/ # 静态网页文件 (用于 Web 仪表盘等) +├── models/ # 数据模型 (事件, 消息段) +│ ├── events/ # OneBot v11 事件的 Python 对象封装 +│ ├── message.py # 消息段 (MessageSegment) 的定义 +│ └── ... +├── plugins/ # 功能插件目录 +├── venv/ # Python 虚拟环境 (推荐) +├── .gitignore # Git 忽略文件配置 +├── config.toml # 主配置文件 +├── main.py # 项目启动入口 +└── requirements.txt # Python 依赖列表 +``` + +## 顶层目录 + +### `core/` + +这是框架的心脏,包含了所有核心逻辑。**通常情况下,您不需要修改此目录下的代码**,只需了解其工作原理即可。 + +* `api/`: 将 OneBot v11 的 API 按功能(如 `message`, `group`)拆分为多个 `Mixin` 类,最终由 `bot.py` 继承,提供了清晰的 API 结构。 +* `data/`: 存放核心模块所需的数据文件,例如 `admin.json` 和 `permissions.json`。 +* `handlers/`: 定义了最底层的事件处理器,如 `MessageHandler`,负责从 `ws.py` 接收原始事件并进行初步处理和分发。 +* `managers/`: 包含一系列全局单例管理器,是框架功能的核心实现。例如,`CommandManager` 负责指令注册与匹配,`PermissionManager` 负责权限控制。 +* `utils/`: 提供被广泛使用的工具类,如 `logger` (日志)、`singleton` (单例模式基类)。 +* `bot.py`: 定义了 `Bot` 类,这是插件开发者最常与之交互的对象,用于调用所有 OneBot API。 +* `config_loader.py`: 负责解析 `config.toml` 文件,并提供一个全局的 `global_config` 对象。 +* `ws.py`: 实现了与 OneBot v11 实现端的 WebSocket 连接、心跳、重连和消息收发。 + +### `docs/` + +存放项目的所有开发文档。 + +### `html/` + +用于存放未来 Web 仪表盘或其他 Web 功能所需的静态资源(HTML, CSS, JavaScript)。 + +### `models/` + +定义了将 OneBot v11 的 JSON 数据转换为易于使用的 Python 对象。 + +* `events/`: 将所有上报的事件(如 `MessageEvent`, `GroupIncreaseNoticeEvent`)封装为带有类型提示的类。 +* `message.py`: 提供了 `MessageSegment` 类,用于构建复杂的消息内容(如 @某人、发送图片)。 + +### `plugins/` + +这是**插件开发者最关心的目录**。所有机器人的功能都以独立的 `.py` 文件形式存放在这里。框架会自动加载此目录下的所有插件,并支持热重载。 + +## 顶层文件 + +* `.gitignore`: 配置 Git 应忽略的文件和目录,如 `__pycache__`、`venv` 等。 +* `config.toml`: 项目的主配置文件,用于设置机器人、数据库、API 等所有可变参数。 +* `main.py`: 项目的启动入口脚本。它负责初始化日志、加载插件、启动 WebSocket 连接和文件监控(用于热重载)。 +* `requirements.txt`: 列出了项目运行所需的所有 Python 第三方库及其版本。 diff --git a/main.py b/main.py index 0589512..9a27b74 100644 --- a/main.py +++ b/main.py @@ -5,19 +5,24 @@ NEO Bot 主程序入口 """ import asyncio import os +import sys import time +# 将项目根目录添加到 sys.path +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, ROOT_DIR) + from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler # 初始化日志系统,必须在其他 core 模块导入之前执行 -from core.logger import logger +from core.utils.logger import logger -from core.admin_manager import admin_manager +from core.managers.admin_manager import admin_manager from core.ws import WS -from core.plugin_manager import load_all_plugins -from core.redis_manager import redis_manager -from core.executor import run_in_thread_pool +from core.managers.plugin_manager import load_all_plugins +from core.managers.redis_manager import redis_manager +from core.utils.executor import run_in_thread_pool class PluginReloadHandler(FileSystemEventHandler): @@ -111,7 +116,7 @@ async def main(): # 初始化代码执行器 from core.config_loader import global_config as config - from core.executor import initialize_executor + from core.utils.executor import initialize_executor code_executor = initialize_executor(bot, config) bot.bot.code_executor = code_executor # 将执行器实例附加到 bot.bot 对象上 diff --git a/models/events/message.py b/models/events/message.py index febb1d9..530ca62 100644 --- a/models/events/message.py +++ b/models/events/message.py @@ -6,7 +6,7 @@ from dataclasses import dataclass, field from typing import List, Optional -from core.permission_manager import ADMIN, OP, USER +from core.managers.permission_manager import ADMIN, OP, USER from models.message import MessageSegment from models.sender import Sender from .base import OneBotEvent, EventType diff --git a/plugins/admin.py b/plugins/admin.py index 2d4854d..f922db8 100644 --- a/plugins/admin.py +++ b/plugins/admin.py @@ -4,8 +4,8 @@ 提供通过聊天指令动态添加或移除机器人管理员的功能。 """ from core.bot import Bot -from core.command_manager import matcher -from core.admin_manager import admin_manager +from core.managers.command_manager import matcher +from core.managers.admin_manager import admin_manager from models.events.message import MessageEvent __plugin_meta__ = { diff --git a/plugins/bili_parser.py b/plugins/bili_parser.py index ed93b1f..2711d52 100644 --- a/plugins/bili_parser.py +++ b/plugins/bili_parser.py @@ -5,8 +5,8 @@ import requests from bs4 import BeautifulSoup from typing import Optional, Dict, Any -from core.logger import logger -from core.command_manager import matcher +from core.utils.logger import logger +from core.managers.command_manager import matcher from models import MessageEvent, MessageSegment __plugin_meta__ = { diff --git a/plugins/broadcast.py b/plugins/broadcast.py index b150214..530dc09 100644 --- a/plugins/broadcast.py +++ b/plugins/broadcast.py @@ -7,10 +7,10 @@ - 此插件不写入 __plugin_meta__,保持隐藏。 """ import asyncio -from core.command_manager import matcher +from core.managers.command_manager import matcher from models import MessageEvent, PrivateMessageEvent -from core.permission_manager import ADMIN -from core.logger import logger +from core.managers.permission_manager import ADMIN +from core.utils.logger import logger # --- 会话状态管理 --- # 结构: {user_id: asyncio.TimerHandle} diff --git a/plugins/code_py.py b/plugins/code_py.py index 055b034..fe28984 100644 --- a/plugins/code_py.py +++ b/plugins/code_py.py @@ -4,10 +4,10 @@ import textwrap import asyncio from typing import Dict -from core.command_manager import matcher +from core.managers.command_manager import matcher from models import MessageEvent -from core.permission_manager import ADMIN -from core.logger import logger +from core.managers.permission_manager import ADMIN +from core.utils.logger import logger __plugin_meta__ = { "name": "Python 代码执行", diff --git a/plugins/echo.py b/plugins/echo.py index 407510a..de712bb 100644 --- a/plugins/echo.py +++ b/plugins/echo.py @@ -3,7 +3,7 @@ Echo 与交互插件 提供 /echo 和 /赞我 指令。 """ -from core.command_manager import matcher +from core.managers.command_manager import matcher from core.bot import Bot from models import MessageEvent diff --git a/plugins/forward_test.py b/plugins/forward_test.py index 429037f..e52025b 100644 --- a/plugins/forward_test.py +++ b/plugins/forward_test.py @@ -1,7 +1,7 @@ """ 合并转发消息测试插件 """ -from core.command_manager import matcher +from core.managers.command_manager import matcher from core.bot import Bot from models import MessageEvent from models.message import MessageSegment diff --git a/plugins/jrcd.py b/plugins/jrcd.py index f1d4767..fba2399 100644 --- a/plugins/jrcd.py +++ b/plugins/jrcd.py @@ -8,8 +8,8 @@ import random from datetime import datetime from core.bot import Bot -from core.command_manager import matcher -from core.executor import run_in_thread_pool +from core.managers.command_manager import matcher +from core.utils.executor import run_in_thread_pool from models import MessageEvent, MessageSegment __plugin_meta__ = { diff --git a/plugins/sync_async_test_plugin.py b/plugins/sync_async_test_plugin.py index b863959..ffaa1a8 100644 --- a/plugins/sync_async_test_plugin.py +++ b/plugins/sync_async_test_plugin.py @@ -5,10 +5,10 @@ """ import time from typing import Any -from core.command_manager import matcher -from core.executor import run_in_thread_pool +from core.managers.command_manager import matcher +from core.utils.executor import run_in_thread_pool from core.bot import Bot -from core.logger import logger +from core.utils.logger import logger # 插件元数据 __plugin_meta__ = { diff --git a/plugins/thpic.py b/plugins/thpic.py index 1a3dfc8..d6a19db 100644 --- a/plugins/thpic.py +++ b/plugins/thpic.py @@ -6,7 +6,7 @@ thpic 插件 """ from core.bot import Bot -from core.command_manager import matcher +from core.managers.command_manager import matcher from models import MessageEvent, MessageSegment __plugin_meta__ = { diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..a635c5c --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . diff --git a/requirements.txt b/requirements.txt index 81bbec8..0033075 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,6 @@ websockets==15.0.1 win32_setctime==1.2.0 yarg==0.1.10 docker +pytest +pytest-asyncio +pytest-mock diff --git a/sandbox.Dockerfile b/sandbox.Dockerfile index 41f2bc3..2c8d156 100644 --- a/sandbox.Dockerfile +++ b/sandbox.Dockerfile @@ -1,19 +1,9 @@ # 使用一个轻量级的 Python 官方镜像作为基础 FROM python:3.11-slim -# 创建一个低权限的用户来运行代码,增加安全性 -# -S: 创建一个系统用户 (没有 home 目录) -# -u: 指定用户ID -# -g: 指定组ID -RUN groupadd -g 1001 sandbox && useradd -u 1001 -g sandbox -s /bin/sh -r sandbox - # 创建一个工作目录,用于存放和执行用户的代码 WORKDIR /sandbox -# 将目录所有权交给沙箱用户 -RUN chown sandbox:sandbox /sandbox -# 切换到沙箱用户 -USER sandbox # 默认的启动命令是 python,这样容器启动时可以直接执行 .py 文件 CMD ["python"]