refactor(core): 重构核心模块结构并添加开发文档

将核心模块按功能重新组织为更清晰的结构,包括 managers、handlers 和 utils 目录
添加完整的开发文档,涵盖快速开始、项目结构、核心概念和插件开发指南
更新所有相关模块的导入路径以匹配新的结构
将单例模式实现提取到单独的 singleton.py 文件
This commit is contained in:
2026-01-07 22:51:27 +08:00
parent c708761726
commit 56b1014419
45 changed files with 772 additions and 104 deletions

View File

@@ -64,6 +64,14 @@
--- ---
## 📚 详细开发文档
**想要深入了解框架的工作原理或开发更复杂的插件?**
👉 **[点击这里,查阅完整的开发文档](./docs/index.md)**
---
## 🚀 快速开始 ## 🚀 快速开始
### 1. 环境准备 ### 1. 环境准备

View File

@@ -6,6 +6,7 @@ reconnect_interval = 5
[bot] [bot]
command = ["/"] command = ["/"]
ignore_self_message = true #是否忽略自身消息 ignore_self_message = true #是否忽略自身消息
permission_denied_message = "权限不足,需要 {permission_name} 权限"
[redis] [redis]
host = "114.66.58.203" host = "114.66.58.203"

View File

@@ -1,6 +1,6 @@
from .command_manager import matcher from .managers.command_manager import matcher
from .config_loader import global_config from .config_loader import global_config
from .plugin_manager import PluginDataManager from .managers.plugin_manager import PluginDataManager
from .ws import WS from .ws import WS
__all__ = ["WS", "matcher", "global_config", "PluginDataManager"] __all__ = ["WS", "matcher", "global_config", "PluginDataManager"]

View File

@@ -8,7 +8,7 @@ import json
from typing import Dict, Any from typing import Dict, Any
from .base import BaseAPI from .base import BaseAPI
from models.objects import LoginInfo, VersionInfo, Status from models.objects import LoginInfo, VersionInfo, Status
from core.redis_manager import redis_manager from ..managers.redis_manager import redis_manager
class AccountAPI(BaseAPI): class AccountAPI(BaseAPI):

View File

@@ -8,7 +8,7 @@ import json
from typing import List, Dict, Any from typing import List, Dict, Any
from .base import BaseAPI from .base import BaseAPI
from models.objects import FriendInfo, StrangerInfo from models.objects import FriendInfo, StrangerInfo
from core.redis_manager import redis_manager from ..managers.redis_manager import redis_manager
class FriendAPI(BaseAPI): class FriendAPI(BaseAPI):

View File

@@ -6,10 +6,10 @@
""" """
from typing import List, Dict, Any from typing import List, Dict, Any
import json import json
from core.redis_manager import redis_manager from ..managers.redis_manager import redis_manager
from .base import BaseAPI from .base import BaseAPI
from models.objects import GroupInfo, GroupMemberInfo, GroupHonorInfo from models.objects import GroupInfo, GroupMemberInfo, GroupHonorInfo
from core.logger import logger from ..utils.logger import logger
class GroupAPI(BaseAPI): class GroupAPI(BaseAPI):

3
core/data/admin.json Normal file
View File

@@ -0,0 +1,3 @@
{
"admins": []
}

View File

View File

@@ -8,10 +8,10 @@ import inspect
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, List, Optional, Tuple from typing import Any, Callable, Dict, List, Optional, Tuple
from .bot import Bot from ..bot import Bot
from .permission_manager import Permission, permission_manager from ..config_loader import global_config
from .exceptions import SyncHandlerError from ..managers.permission_manager import Permission, permission_manager
from .executor import run_in_thread_pool from ..utils.executor import run_in_thread_pool
class BaseHandler(ABC): class BaseHandler(ABC):
@@ -75,8 +75,6 @@ class MessageHandler(BaseHandler):
注册通用消息处理器 注册通用消息处理器
""" """
def decorator(func: Callable) -> Callable: def decorator(func: Callable) -> Callable:
if not inspect.iscoroutinefunction(func):
raise SyncHandlerError(f"消息处理器 {func.__name__} 必须是异步函数 (async def).")
self.message_handlers.append(func) self.message_handlers.append(func)
return func return func
return decorator return decorator
@@ -91,8 +89,6 @@ class MessageHandler(BaseHandler):
注册命令处理器 注册命令处理器
""" """
def decorator(func: Callable) -> Callable: def decorator(func: Callable) -> Callable:
if not inspect.iscoroutinefunction(func):
raise SyncHandlerError(f"命令处理器 {func.__name__} 必须是异步函数 (async def).")
for name in names: for name in names:
self.commands[name] = { self.commands[name] = {
"func": func, "func": func,
@@ -139,7 +135,8 @@ class MessageHandler(BaseHandler):
if not permission_granted and not override_check: if not permission_granted and not override_check:
permission_name = permission.name if isinstance(permission, Permission) else permission 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 return
await self._run_handler( await self._run_handler(
@@ -160,8 +157,6 @@ class NoticeHandler(BaseHandler):
注册通知处理器 注册通知处理器
""" """
def decorator(func: Callable) -> Callable: 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}) self.handlers.append({"type": notice_type, "func": func})
return func return func
return decorator return decorator
@@ -184,8 +179,6 @@ class RequestHandler(BaseHandler):
注册请求处理器 注册请求处理器
""" """
def decorator(func: Callable) -> Callable: 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}) self.handlers.append({"type": request_type, "func": func})
return func return func
return decorator return decorator

View File

View File

@@ -9,33 +9,25 @@ import json
import os import os
from typing import Set 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 键 _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): def __init__(self):
""" """
初始化 AdminManager 初始化 AdminManager
""" """
if getattr(self, "_initialized", False): super().__init__()
if not self._initialized:
return return
# 管理员数据文件路径 # 管理员数据文件路径
@@ -47,7 +39,6 @@ class AdminManager:
) )
self._admins: Set[int] = set() self._admins: Set[int] = set()
self._initialized = True
logger.info("管理员管理器初始化完成") logger.info("管理员管理器初始化完成")
async def initialize(self): async def initialize(self):
@@ -96,7 +87,7 @@ class AdminManager:
""" """
将内存中的管理员集合同步到 Redis 将内存中的管理员集合同步到 Redis
""" """
from .redis_manager import redis_manager from core.managers.redis_manager import redis_manager
try: try:
# 首先清空旧的集合 # 首先清空旧的集合
await redis_manager.redis.delete(self._REDIS_KEY) await redis_manager.redis.delete(self._REDIS_KEY)
@@ -111,7 +102,7 @@ class AdminManager:
""" """
检查用户是否为管理员 Redis 缓存读取 检查用户是否为管理员 Redis 缓存读取
""" """
from .redis_manager import redis_manager
try: try:
return await redis_manager.redis.sismember(self._REDIS_KEY, user_id) return await redis_manager.redis.sismember(self._REDIS_KEY, user_id)
except Exception as e: except Exception as e:

View File

@@ -7,8 +7,8 @@
""" """
from typing import Any, Callable, Dict, Optional, Tuple from typing import Any, Callable, Dict, Optional, Tuple
from .config_loader import global_config from ..config_loader import global_config
from .event_handler import MessageHandler, NoticeHandler, RequestHandler from ..handlers.event_handler import MessageHandler, NoticeHandler, RequestHandler
# 从配置中获取命令前缀 # 从配置中获取命令前缀

View File

@@ -16,8 +16,9 @@ import os
from functools import total_ordering from functools import total_ordering
from typing import Dict from typing import Dict
from .logger import logger from ..utils.logger import logger
from .admin_manager import admin_manager # 导入 AdminManager from ..utils.singleton import Singleton
from .admin_manager import admin_manager
@total_ordering @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): def __init__(self):
""" """
初始化权限管理器 初始化权限管理器
如果已经初始化过则直接返回 如果已经初始化过则直接返回
""" """
if getattr(self, "_initialized", False): super().__init__()
if not self._initialized:
return return
# 权限数据文件路径 # 权限数据文件路径
@@ -122,7 +110,6 @@ class PermissionManager:
# 加载现有数据 # 加载现有数据
self.load() self.load()
self._initialized = True
logger.info("权限管理器初始化完成") logger.info("权限管理器初始化完成")
def load(self) -> None: def load(self) -> None:

View File

@@ -10,10 +10,10 @@ import os
import pkgutil import pkgutil
import sys import sys
from core.command_manager import matcher from .command_manager import matcher
from core.exceptions import SyncHandlerError from ..utils.exceptions import SyncHandlerError
from .logger import logger from ..utils.logger import logger
from .executor import run_in_thread_pool from ..utils.executor import run_in_thread_pool
def load_all_plugins(): def load_all_plugins():

View File

@@ -1,6 +1,6 @@
import redis.asyncio as redis import redis.asyncio as redis
from .config_loader import global_config as config from ..config_loader import global_config as config
from .logger import logger from ..utils.logger import logger
class RedisManager: class RedisManager:
""" """

0
core/utils/__init__.py Normal file
View File

View File

@@ -4,7 +4,7 @@ import docker
from docker.tls import TLSConfig from docker.tls import TLSConfig
from typing import Dict, Any, Callable from typing import Dict, Any, Callable
from core.logger import logger from core.utils.logger import logger
class CodeExecutor: class CodeExecutor:
""" """

30
core/utils/singleton.py Normal file
View File

@@ -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

View File

@@ -22,9 +22,9 @@ import websockets
from models import EventFactory from models import EventFactory
from .bot import Bot from .bot import Bot
from .command_manager import matcher
from .config_loader import global_config from .config_loader import global_config
from .logger import logger from .managers.command_manager import matcher
from .utils.logger import logger
class WS: class WS:

View File

@@ -1,3 +0,0 @@
{
"admins": [2221577113]
}

View File

@@ -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 群。
至此,一个完整的事件流转闭环就完成了。理解这个流程后,您就能明白框架是如何将底层的网络通信与高层的插件逻辑解耦,并为开发者提供便捷接口的。

View File

@@ -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("抱歉,您没有权限执行此命令。")
```

102
docs/deployment.md Normal file
View File

@@ -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 机器人稳定、高效地部署在生产服务器上。

113
docs/getting-started.md Normal file
View File

@@ -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://<IP地址>:<端口号>
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)

34
docs/index.md Normal file
View File

@@ -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 流程提交您的代码。
* **完善文档**: 如果您发现文档中有任何错误或遗漏,可以直接提出修改建议。
我们希望这份文档能让您的开发之旅更加顺畅。如果您有任何疑问,请随时与我们联系。

View File

@@ -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` 都可以使用该指令。
通过组合使用参数处理、别名和权限控制,您可以构建出既灵活又安全的指令来满足各种复杂的需求。

View File

@@ -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)。

69
docs/project-structure.md Normal file
View File

@@ -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 第三方库及其版本。

17
main.py
View File

@@ -5,19 +5,24 @@ NEO Bot 主程序入口
""" """
import asyncio import asyncio
import os import os
import sys
import time 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.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
# 初始化日志系统,必须在其他 core 模块导入之前执行 # 初始化日志系统,必须在其他 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.ws import WS
from core.plugin_manager import load_all_plugins from core.managers.plugin_manager import load_all_plugins
from core.redis_manager import redis_manager from core.managers.redis_manager import redis_manager
from core.executor import run_in_thread_pool from core.utils.executor import run_in_thread_pool
class PluginReloadHandler(FileSystemEventHandler): class PluginReloadHandler(FileSystemEventHandler):
@@ -111,7 +116,7 @@ async def main():
# 初始化代码执行器 # 初始化代码执行器
from core.config_loader import global_config as config 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) code_executor = initialize_executor(bot, config)
bot.bot.code_executor = code_executor # 将执行器实例附加到 bot.bot 对象上 bot.bot.code_executor = code_executor # 将执行器实例附加到 bot.bot 对象上

View File

@@ -6,7 +6,7 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List, Optional 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.message import MessageSegment
from models.sender import Sender from models.sender import Sender
from .base import OneBotEvent, EventType from .base import OneBotEvent, EventType

View File

@@ -4,8 +4,8 @@
提供通过聊天指令动态添加或移除机器人管理员的功能。 提供通过聊天指令动态添加或移除机器人管理员的功能。
""" """
from core.bot import Bot from core.bot import Bot
from core.command_manager import matcher from core.managers.command_manager import matcher
from core.admin_manager import admin_manager from core.managers.admin_manager import admin_manager
from models.events.message import MessageEvent from models.events.message import MessageEvent
__plugin_meta__ = { __plugin_meta__ = {

View File

@@ -5,8 +5,8 @@ import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from core.logger import logger from core.utils.logger import logger
from core.command_manager import matcher from core.managers.command_manager import matcher
from models import MessageEvent, MessageSegment from models import MessageEvent, MessageSegment
__plugin_meta__ = { __plugin_meta__ = {

View File

@@ -7,10 +7,10 @@
- 此插件不写入 __plugin_meta__保持隐藏。 - 此插件不写入 __plugin_meta__保持隐藏。
""" """
import asyncio import asyncio
from core.command_manager import matcher from core.managers.command_manager import matcher
from models import MessageEvent, PrivateMessageEvent from models import MessageEvent, PrivateMessageEvent
from core.permission_manager import ADMIN from core.managers.permission_manager import ADMIN
from core.logger import logger from core.utils.logger import logger
# --- 会话状态管理 --- # --- 会话状态管理 ---
# 结构: {user_id: asyncio.TimerHandle} # 结构: {user_id: asyncio.TimerHandle}

View File

@@ -4,10 +4,10 @@ import textwrap
import asyncio import asyncio
from typing import Dict from typing import Dict
from core.command_manager import matcher from core.managers.command_manager import matcher
from models import MessageEvent from models import MessageEvent
from core.permission_manager import ADMIN from core.managers.permission_manager import ADMIN
from core.logger import logger from core.utils.logger import logger
__plugin_meta__ = { __plugin_meta__ = {
"name": "Python 代码执行", "name": "Python 代码执行",

View File

@@ -3,7 +3,7 @@ Echo 与交互插件
提供 /echo 和 /赞我 指令。 提供 /echo 和 /赞我 指令。
""" """
from core.command_manager import matcher from core.managers.command_manager import matcher
from core.bot import Bot from core.bot import Bot
from models import MessageEvent from models import MessageEvent

View File

@@ -1,7 +1,7 @@
""" """
合并转发消息测试插件 合并转发消息测试插件
""" """
from core.command_manager import matcher from core.managers.command_manager import matcher
from core.bot import Bot from core.bot import Bot
from models import MessageEvent from models import MessageEvent
from models.message import MessageSegment from models.message import MessageSegment

View File

@@ -8,8 +8,8 @@ import random
from datetime import datetime from datetime import datetime
from core.bot import Bot from core.bot import Bot
from core.command_manager import matcher from core.managers.command_manager import matcher
from core.executor import run_in_thread_pool from core.utils.executor import run_in_thread_pool
from models import MessageEvent, MessageSegment from models import MessageEvent, MessageSegment
__plugin_meta__ = { __plugin_meta__ = {

View File

@@ -5,10 +5,10 @@
""" """
import time import time
from typing import Any from typing import Any
from core.command_manager import matcher from core.managers.command_manager import matcher
from core.executor import run_in_thread_pool from core.utils.executor import run_in_thread_pool
from core.bot import Bot from core.bot import Bot
from core.logger import logger from core.utils.logger import logger
# 插件元数据 # 插件元数据
__plugin_meta__ = { __plugin_meta__ = {

View File

@@ -6,7 +6,7 @@ thpic 插件
""" """
from core.bot import Bot from core.bot import Bot
from core.command_manager import matcher from core.managers.command_manager import matcher
from models import MessageEvent, MessageSegment from models import MessageEvent, MessageSegment
__plugin_meta__ = { __plugin_meta__ = {

2
pytest.ini Normal file
View File

@@ -0,0 +1,2 @@
[pytest]
pythonpath = .

View File

@@ -20,3 +20,6 @@ websockets==15.0.1
win32_setctime==1.2.0 win32_setctime==1.2.0
yarg==0.1.10 yarg==0.1.10
docker docker
pytest
pytest-asyncio
pytest-mock

View File

@@ -1,19 +1,9 @@
# 使用一个轻量级的 Python 官方镜像作为基础 # 使用一个轻量级的 Python 官方镜像作为基础
FROM python:3.11-slim 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 WORKDIR /sandbox
# 将目录所有权交给沙箱用户
RUN chown sandbox:sandbox /sandbox
# 切换到沙箱用户
USER sandbox
# 默认的启动命令是 python这样容器启动时可以直接执行 .py 文件 # 默认的启动命令是 python这样容器启动时可以直接执行 .py 文件
CMD ["python"] CMD ["python"]