Custom-bot 开发及使用文档
Custom-bot 介绍:共同铸造,无限成长的自动化传奇
欢迎来到 Custom-bot 的世界! 这不仅是一个功能强大的 Python 异步机器人框架,更是一个开放、协作的平台,邀请每一位。 它并非封闭的终点,而是你我手中蕴含无限可能、等待共同雕琢的璞玉,是我们一起谱写自动化无限成长传奇的起点。
在这里,“自定义”是核心驱动力。 凭借其高度灵活的 架构,你可以轻松打破协议壁垒,让 Bot 通过 WebSocket(正/反向)、HTTP、MQTT 等多种方式联通所需的服务与平台。 而强大的适配器 (Adapter)插件 (Plugin) 系统,则允许你像锻造传奇装备一般,为 Bot 精准注入独特的命令响应、智能的事件处理和强大的业务逻辑,使其成为满足你特定需求的完美工具。 分享你的适配器和插件,让整个社区受益!
“无限成长”是我们共同的愿景与承诺。成本 构建,拥有处理高并发任务的先天优势。 其asyncio模块化设计保证了核心的稳定与高效,更为功能的迭代与扩展铺就了清晰、便捷的道路。 无论是对接新兴的 AI 服务,集成复杂的业务流程,还是扩展到全新的应用场景,这个框架都能作为我们共同的坚实后盾,与社区的集体智慧一同,不断突破自进化
核心特性一览:
协议无关: 通过适配器轻松支持 HTTP, WebSocket 等多种协议,欢迎贡献新的适配器!
功能无限: 插件化设计,自由添加命令、事件处理和业务逻辑,分享你的创意插件!
异步高效: 基于 ,性能卓越,响应迅速。asyncio
配置驱动:山药
易于管理:内置 Web UI (基于 FastAPI)
部署便捷: 提供 Docker 支持,简化部署和运维流程。
开放协作: 鼓励社区贡献代码、分享经验、共同完善框架。
我们相信,每一个由 Custom-bot 驱动的应用,都有潜力成为一个真正的。 更重要的是,通过传奇大家的共同完善,自定义机器人,成为一个更加强大、稳定和易用的自动化基础设施。成长
欢迎加入我们,一起探索、贡献、学习,共同用 Custom-bot 铸造属于我们的自动化传奇!
Custom-bot 文档结构和主要内容框架
请注意,这是一个,你需要根据你的项目实际实现细节来填充和完善具体内容,特别是“安装”、“使用”、“开发文档”下的具体步骤和 API 细节,“常见问题Q&A”和“指南”需要根据用户反馈和项目特点来编写。文档框架
我将把文档分成几个主要部分,并提供每个部分的核心内容。
文档根目录 (例如 或项目根目录的 及子页面)文档/README.md
README.md (项目根目录)
# Custom-bot: 一个灵活、可扩展的 Python 异步机器人框架
[](https://opensource.org/licenses/MIT)
<!-- 在这里添加其他徽章,例如构建状态、代码覆盖率等 -->
**作者:** Xinz/ahhhahh (及贡献者)
**当前版本:** 1.0.0-dev (示例)
Custom-bot 是一个使用 Python 和 asyncio 构建的现代化机器人框架。它旨在提供一个模块化、易于扩展的基础,让开发者可以轻松对接不同的聊天平台和协议,并编写自定义的功能插件。
**核心特性:**
* **异步优先:** 基于 `asyncio`,实现高性能 I/O 处理。
* **多协议支持:** 通过可插拔的适配器 (Adapter) 对接不同协议 (HTTP Webhook, WebSocket, Telegram, QQ OneBot 等)。
* **插件化:** 通过处理器 (Handler/Plugin) 轻松添加自定义命令、事件处理和业务逻辑。
* **Web 管理界面:** 提供图形化界面监控状态、管理配置、查看日志等。
* **定时任务:** 内置定时任务调度器 (APScheduler)。
* **配置驱动:** 使用 YAML 文件进行灵活配置。
* **Docker 支持:** 提供 Dockerfile 和 docker-compose 配置,方便部署。
## 快速开始
**使用 Docker (推荐):**
1. **克隆仓库:** `git clone <your-repository-url> Custom-bot && cd Custom-bot`
2. **配置:** 编辑 `config/config.yaml` 文件 (首次运行会自动生成模板),设置管理员、启用适配器和插件等。
3. **构建并启动:** `docker-compose up --build -d`
4. **访问 Web UI:** `http://<服务器IP>:9090` (默认端口)
**直接运行:**
1. **克隆仓库:** `git clone <your-repository-url> Custom-bot && cd Custom-bot`
2. **创建虚拟环境 (推荐):** `python -m venv .venv && source .venv/bin/activate` (或 Windows 对应命令)
3. **安装依赖:** `pip install -r requirements.txt`
4. **首次运行生成配置:** `python main.py` (然后按 Ctrl+C 停止,编辑 `config/config.yaml`)
5. **启动:** `python main.py`
## 文档导航
* **[安装指南](docs/安装.md):** 详细的安装和环境配置步骤。
* **[使用手册](docs/使用.md):** 如何配置、运行和管理 Custom-bot。
* **[开发文档](docs/开发文档/):** 面向开发者的详细指南。
* **[核心开发](docs/开发文档/核心开发.md):** 框架内部架构和原理。
* **[适配器开发](docs/开发文档/适配器开发.md):** 如何添加新的协议支持。
* **[插件开发](docs/开发文档/插件开发.md):** 如何编写新的功能插件。
* **[远程开发 (VSCode)](docs/开发文档/远程开发.md):** (可选) 使用 VSCode Remote - Containers 进行开发的指南。
* **[常见问题 Q&A](docs/常见问题.md):** 解答使用中可能遇到的问题。
* **[更新日志](CHANGELOG.md):** (推荐单独文件) 记录版本变更历史。
* **[帮助与支持](#帮助与支持):** 获取帮助的途径。
## 贡献
欢迎参与 Custom-bot 的开发!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) (需要创建) 了解贡献指南。
## 帮助与支持
* **提交 Issue:** 如果您遇到 Bug 或有功能建议,请在 [GitHub Issues](<your-repo-url>/issues) 中提出。
* **社区讨论:** (可选) 加入我们的 [讨论区/QQ群/Telegram群](<link-to-community>) 进行交流。
## License
Custom-bot 使用 [MIT License](LICENSE) (需要创建 LICENSE 文件)。
请谨慎使用代码。
docs/安装.md
# Custom-bot 安装指南
本文档提供安装 Custom-bot 的详细步骤和环境要求。
## 环境要求
### 方式一:直接运行 Python 代码
* **Python:** 建议使用 Python 3.8 或更高版本。可以通过 `python --version` 或 `python3 --version` 命令检查。如果未安装,请访问 [Python 官网](https://www.python.org/) 下载并安装。
* **pip:** Python 包管理器,通常随 Python 一起安装。可以通过 `pip --version` 或 `pip3 --version` 检查。
* **虚拟环境 (推荐):** 为了隔离项目依赖,强烈建议使用虚拟环境(如 `venv`)。
* **(可选) Git:** 用于从代码仓库克隆项目。
* **(可选) Node.js & npm/yarn:** 如果需要执行依赖 Node.js 的插件、定时任务脚本,或者进行前端 Web UI 开发,需要安装它们。可以从 [Node.js 官网](https://nodejs.org/) 下载或使用包管理器(如 apt, yum, brew)或 NodeSource 进行安装。
* **(可选) 系统依赖:** 某些 Python 库(尤其涉及 C 扩展的)可能需要系统级的编译工具(如 `build-essential`, `gcc`)或特定的开发库(如 `libffi-dev`)。如果在 `pip install` 过程中遇到编译错误,请根据错误提示安装相应的系统依赖。
### 方式二:使用 Docker (推荐)
* **Docker Engine:** 确保已安装并运行 Docker 服务。版本建议 19.03 或更高。请参考 [Docker 官方文档](https://docs.docker.com/engine/install/) 进行安装。可以通过 `docker --version` 检查。
* **Docker Compose:** 用于管理多容器 Docker 应用。V2 版本(作为 Docker 插件)通常随 Docker Desktop 或 Docker Engine for Linux 一起安装。可以通过 `docker compose version` (注意没有连字符) 检查。如果是旧的 V1 版本 (`docker-compose --version`) 也可以工作,但建议使用 V2。
## 安装步骤
### 1. 获取代码
使用 Git 克隆项目仓库:
```bash
git clone <your-repository-url> Custom-bot
cd Custom-bot
请谨慎使用代码。
(将 '<your-repository- 替换为实际地址)<您的存储库 url>
如果无法使用 Git,可以下载代码压缩包并解压。
2. 准备运行环境
方式一:直接运行
进入项目目录:
cd Custom-bot
请谨慎使用代码。
创建并激活 Python 虚拟环境 (推荐):
python3 -m venv .venv # 创建 .venv 目录
# Linux/macOS 激活:
source .venv/bin/activate
# Windows (cmd):
# .venv\Scripts\activate.bat
# Windows (PowerShell):
# .\.venv\Scripts\Activate.ps1
请谨慎使用代码。重击
(之后所有 和 命令都在虚拟环境中执行)果仁蟒
安装 Python 依赖:
pip install -r requirements.txt
请谨慎使用代码.
如果在安装过程中遇到错误,请根据错误提示解决(例如安装缺失的系统依赖)。
(可选) 安装 Node.js 依赖 (如果需要):
如果项目包含需要 Node.js 依赖的插件或脚本,或者你需要开发前端:
# 假设 webui 目录包含 package.json
cd webui
npm install # 或者 yarn install
cd .. # 返回项目根目录
请谨慎使用代码。
方式二:使用 Docker
无需手动安装依赖: Dockerfile 会处理 Python 依赖。 如果需要 Node.js,需要在 Dockerfile 中添加安装步骤。
确保 Docker 服务正在运行。
3. 初始化配置和目录
方式一:直接运行
首次运行 会自动完成:main.py
python main.py
请谨慎使用代码。
它会检查并创建 , 适配器配置、 、 、 、分贝原木插件, , 'SCR公共脚本 等目录。
如果 文件不存在,它会使用默认模板创建该文件。config/config.yaml
看到提示后,按 停止程序,然后进行下一步的配置。 Ctrl + C 组合键
方式二:使用 Docker
首次运行 'docker-compose up --build 会完成:docker-compose up --build
docker-compose up --build -d # -d 表示后台运行
请谨慎使用代码。
Docker 会构建镜像(包括安装 Python 依赖)。
启动容器时, 会检查通过到宿主机的目录。 如果宿主机上对应的目录不存在,Docker 通常会自动创建(但权限可能需要调整)。main.py卷挂载
同样,如果 在挂载点(即宿主机的 目录)不存在,容器内的启动逻辑会创建它。config/config.yamlconfig/
容器启动后,你可以直接编辑宿主机上的 。 config/config.yaml
命令docker 运行
拉取镜像
docker pull sevenxinz/xinz:tagname
以下是一个,它包含了运行 示例 命令docker 运行自定义机器人 通常需要的和端口映射卷挂载。 你需要根据你的实际情况修改其中的路径和端口。
docker run -d \
--name custom-bot-instance \
-p 9090:9090 \
-p 8765:8765 \
# -p 8088:8088 \ # 如果你需要映射 HTTP Webhook 端口,取消注释并确保端口正确
# --- 在这里添加其他需要映射的适配器端口 ---
-v $(pwd)/config:/app/config \
-v $(pwd)/db:/app/db \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/plugins:/app/plugins \
-v $(pwd)/scripts:/app/scripts \
-v $(pwd)/public:/app/public \
-e TZ=Asia/Shanghai \
--restart unless-stopped \
sevenxinz/xinz:tagname
请谨慎使用代码
命令详解:
docker 运行: 启动一个新容器的命令。
-d: Detached 模式,让容器在后台运行。 如果你想在前台看到日志,可以去掉这个参数。
--name 自定义机器人实例: 给容器指定一个名字,方便后续管理(例如 ,docker stop 自定义机器人实例docker logs 自定义机器人实例)。 你可以改成你喜欢的名字。
- 9090:9090 : 将宿主机的 9090 端口映射到容器的 9090 端口 (Web UI)。 格式是 。 -p <宿主机端口>:<容器端口>
- 8765:8765 : 将宿主机的 8765 端口映射到容器的 8765 端口 (反向 WebSocket)。
- 8088:8088 (注释掉的): 示例,如果你在 'config.yaml 中启用了 HTTP Webhook 适配器并监听在 8088 端口,你需要取消注释这一行(或修改端口号)。config.yaml
-v $(pwd)/config:/app/config: 卷挂载。 将下的 子目录挂载到容器当前宿主机目录配置/app/config 中 目录。
$(pwd): 表示当前工作目录 (运行 命令时所在的目录)。 确保你运行命令时位于 docker 运行自定义机器人 项目的根目录下。
/app/config 中: 这是 中设置的工作目录 Dockerfile 文件/应用程序 下的 目录。 配置
你需要根据你的实际项目结构调整 这部分路径。 $(pwd)/配置 如果你的配置文件放在别处,需要修改。
-v $(pwd)/db:/app/db: 挂载数据库目录,实现数据持久化。
-v $(pwd)/logs:/app/logs: 挂载日志目录,方便在宿主机查看日志。
-v $(pwd)/插件:/app/plugins: 挂载插件目录,允许在宿主机添加/修改插件。
-v $(pwd)/scripts:/app/scripts: 挂载脚本目录,用于定时任务。
-v $(pwd)/public:/app/public: 挂载公开资源目录。
-e TZ=亚洲/上海: 设置容器内的时区环境变量,确保日志时间等正确。 你可以根据需要修改时区。
--restart 除非停止: 设置容器的重启策略。 表示除非手动停止容器,否则容器在退出(例如因为错误或服务器重启)后会自动重启。 其他选项如 除非停止总是 或 。 不
xinz/custom-bot:最新: 指定要使用的 Docker 镜像名称和标签。
使用前的准备:
确保镜像存在:
如果是你自己构建的,确保已经成功运行了 。 docker build -t xinz/custom-bot:latest 的 .
如果是从 Docker Hub 拉取的,运行 。 docker pull xinz/custom-bot:latest
准备宿主机目录: 在你(通常是你的项目根目录)下,确保存在 打算运行 命令的目录docker 运行配置, , , , 'p分贝原木插件, , 'pu脚本公共 这些子目录。 如果不存在,需要先创建 ()。 mkdir config 数据库日志 插件 脚本 public
准备配置文件: 确保 文件存在于宿主机的 config/config.yaml配置 目录下,并且内容是你配置好的。
检查端口占用: 确保你要映射的宿主机端口(9090, 8765 等)没有被其他程序占用。
权限问题: 确保 Docker 守护进程(或者运行 的用户,如果未使用 docker 运行须藤)有权限读取和写入你挂载的宿主机目录(特别是 和 分贝原木)。 如果遇到权限错误,参考之前关于 的解决方案(例如权限错误sudo chown -R <UID>: <GID> ./logs ./db<UID>
运行命令:
将上面提供的 命令复制到你的终端,docker 运行确保你当前位于包含 ,, 等子目录的项目根目录下配置分贝原木,
注意: docker 运行 命令只用于容器。 之后管理容器,你应该使用:首次创建并启动
docker start 自定义机器人实例: 启动已停止的容器。
docker stop 自定义机器人实例: 停止正在运行的容器。
docker restart 自定义机器人实例: 重启容器。
docker logs -f 自定义机器人实例: 查看容器日志。
docker rm custom-bot-instance: 删除已停止的容器(数据卷内容会保留)。
4. 配置框架
打开 文件,根据文件内的注释和你自己的需求进行配置:config/config.yaml
设置 。 kernel.admins 的
启用并配置你需要的 (例如 QQ 适配器的端口、路径、Token)。适配器
启用并配置你需要的 (插件)。 处理器
(可选) 配置 。 scheduled_tasks
(可选) 修改 的端口等设置。对于 Web
重要: 对于 Docker 运行方式,修改配置文件后需要才能生效:重启容器docker-compose restart 自定义机器人。
安装和初步配置完成! 现在可以参考 来运行和管理 Custo使用手册
---
**`docs/使用.md`**
```markdown
# Custom-bot 使用手册
本文档介绍如何配置、运行、管理和与 Custom-bot 进行交互。
## 启动与停止
### 方式一:直接运行
* **启动:**
1. 进入项目根目录 (`Custom-bot/`)。
2. 激活 Python 虚拟环境 (如果使用): `source .venv/bin/activate` (或 Windows 对应命令)。
3. 运行主程序: `python main.py`
4. 程序将在前台运行,控制台会输出日志。
* **停止:** 在运行程序的终端中按 `Ctrl+C`。框架会尝试优雅关闭。
### 方式二:使用 Docker
* **启动:**
1. 进入项目根目录 (`Custom-bot/`)。
2. 运行 Docker Compose: `docker-compose up -d` (`-d` 表示后台运行)。
3. 首次运行或修改了 Dockerfile/requirements.txt 后,使用 `docker-compose up --build -d`。
* **查看状态:** `docker-compose ps` 查看容器是否正在运行 (`running`)。
* **查看日志:** `docker-compose logs -f custom-bot` (`-f` 持续跟踪)。按 `Ctrl+C` 停止跟踪。
* **停止:** `docker-compose down` (停止并移除容器)。
* **重启:** `docker-compose restart custom-bot` (例如修改配置后需要重启)。
## 配置文件 (`config/config.yaml`)
这是控制 Custom-bot 行为的核心文件。详细配置项说明请参考 [安装指南](安装.md) 或文件内的注释。
**关键配置区域:**
* `kernel`: 核心设置,如日志级别、全局管理员。
* `webui`: Web 管理界面开关、地址、端口。
* `database`: 数据库设置 (目前主要是路径)。
* `adapters`: 配置和启用协议适配器。**你需要在这里启用至少一个适配器才能让机器人与外界通信。**
* `handlers`: 配置和启用功能插件。**你需要在这里启用至少一个插件才能让机器人响应命令或事件。**
* `scheduled_tasks`: 配置定时任务。
**修改配置后,务必重启框架 (直接运行则停止后重开,Docker 则 `docker-compose restart`)。**
## Web 管理界面 (Web UI)
如果 `webui.enabled` 设置为 `true`,你可以通过浏览器访问 Web UI。
* **默认地址:** `http://<服务器IP>:9090` (端口在 `config.yaml` 中配置)。
* **功能 (设计目标):**
* **仪表盘:** 查看实时系统状态 (CPU, 内存), BOT 运行信息。
* **网络配置:** 管理适配器,查看连接状态。
* **插件市场 (TODO):** 管理插件的启用/禁用状态(未来可能支持安装/卸载)。
* **文件管理 (高风险):** 浏览指定的项目目录 (需要强安全控制)。
* **终端 (高风险):** Web Shell (需要极高的安全措施)。
* **设置:** 修改部分配置项。
* **日志查看:** 查看不同组件的日志。
* ... 以及其他管理功能。
**(注意:具体可用功能取决于开发进度。高风险功能默认可能未实现或需要显式安全配置。)**
## 通过聊天平台交互
你需要配置并启用至少一个适配器 (例如 `QQAdapter`) 和至少一个处理器插件 (例如 `基础命令`)。
1. **配置适配器:** 在 `config.yaml` 中正确配置适配器,例如 QQ 适配器的监听端口、路径、Access Token (如果需要)。
2. **配置外部客户端:** 配置你的 QQ 客户端 (如 go-cqhttp) 使用反向 WebSocket 连接到 Custom-bot 配置的地址、端口和路径,并设置相同的 Access Token (如果需要)。确保消息格式设置为 `array` 或 `object` (取决于你插件的处理能力,目前示例适配器能接收两者)。
3. **启动外部客户端和 Custom-bot。**
4. **发送命令:** 在 QQ 聊天窗口中发送基础命令插件定义的命令,例如:
* `时间`
* `版本`
* `我的id`
* (在群里) `群id`
* (如果你是管理员) `get system name` (假设存在这个数据)
* (如果你是管理员) `set system name 新名字`
* (如果你是管理员) `del system name`
* (如果你是管理员) `重启`
## 日志文件
日志文件位于项目根目录下的 `logs/` 目录。
* `kernel.log`: 核心运行日志。
* `error.log`: 仅记录错误和严重级别的日志。
* 其他日志文件可能由适配器或插件生成。
可以通过 Web UI 查看日志,或者直接在服务器上使用 `cat`, `tail`, `less` 等命令查看。对于 Docker 用户,也可以使用 `docker-compose logs` 命令。
## 目录结构说明 (用户相关)
当你运行 Custom-bot 时,项目根目录下会包含以下对用户比较重要的目录:
* **`config/`**: 存放核心配置文件 `config.yaml`。**你需要编辑这个文件来配置机器人。**
* **`db/`**: 存放数据库文件 (例如 TinyDB 的 JSON 文件)。**通常不需要手动编辑。**
* **`logs/`**: 存放运行时日志。用于排查问题。
* **`plugins/`**: 存放插件代码。**你需要将下载或编写的插件放入此目录下的二级子目录中。**
* **`scripts/`**: 存放定时任务脚本。**你需要将编写的定时任务脚本放在这里。**
* **`public/`**: 存放可通过 Web 访问的公开静态资源。机器人可以将生成的图片等文件放在这里,然后通过 `http://<服务器IP>:<WebUI端口>/public/文件名` 的链接发送。**注意不要放敏感文件。**
* **`Adapter/`**: (主要面向开发者) 存放协议适配器代码。
* **`core/`**: (主要面向开发者) 框架核心代码。
* **`webui/`**: 存放 Web UI 相关文件。
* `webui/dist/`: 存放前端构建后的静态文件,由 FastAPI 提供服务。**通常不需要手动修改。**
* `webui/src/`: (如果你需要开发前端) 存放前端源代码。
* `main.py`: 程序主入口。
* `web_server.py`: Web 服务器实现。
* `Dockerfile`, `docker-compose.yml`, `.dockerignore`: Docker 相关文件。
* `requirements.txt`: Python 依赖列表。
## 更新框架
1. **备份:** 在更新前,**强烈建议备份**您的 `config/`, `db/`, `plugins/`, `scripts/` 目录,以防更新引入不兼容的更改。
2. **获取新代码:** 使用 `git pull` (如果您使用 Git) 或下载新版本的代码压缩包。
3. **替换代码:** 用新版本的代码覆盖旧的代码文件(除了您需要保留的配置、数据、插件、脚本目录)。注意查看更新日志,了解是否有重大变更。
4. **更新依赖:**
* **直接运行:** 激活虚拟环境后,运行 `pip install -r requirements.txt --upgrade`。
* **Docker:** 运行 `docker-compose build --no-cache custom-bot` 来基于新的 `requirements.txt` 重建镜像。
5. **重启框架:**
* **直接运行:** 停止旧进程,启动新进程 `python main.py`。
* **Docker:** `docker-compose down && docker-compose up -d`。
6. **检查兼容性:** 查看更新日志,确认您的插件、适配器配置或脚本是否需要因框架更新而进行调整。
## 寻求帮助
* 查阅 [常见问题 Q&A](常见问题.md)。
* 在项目的 [GitHub Issues](<your-repo-url>/issues) 中搜索或提问。
* 加入社区讨论 (如果提供)。
请谨慎使用代码。
接下来是面向开发者的文档(核心开发、适配器开发、插件开发)。
4. 核心开发文档 (docs/开发文档/核心开发.md)
# Custom-bot 核心开发文档
**目标读者:** 希望深入理解 Custom-bot 框架内部工作原理、进行框架本身二次开发或排查核心问题的开发者。
## 架构回顾
Custom-bot 采用基于 `asyncio` 的事件驱动和模块化架构。核心组件及其交互关系已在 [使用手册](使用.md) 和项目 `README.md` 中概述。这里重点阐述内部实现细节和开发者需要关注的接口。
## 关键组件实现细节
### Kernel (`core/kernel.py`)
* **配置加载与管理:**
* 使用 `ruamel.yaml` 库加载 `config.yaml`,能较好地保留注释和格式(主要在保存时体现)。
* `_load_config()` 负责读取文件并存入 `self.config` 字典。
* `get_config_value(key_path, default)` 提供安全的点分路径访问配置项的方法,避免因缺少键而抛出 `KeyError`。
* **动态导入与加载 (`_import_class`, `_load_adapters`, `_load_handlers`):**
* `_import_class(class_path, expected_base)` 是核心的动态加载函数:
* 接收 Python 类路径字符串 (如 `Adapter.qq_adapter.QQAdapter`) 和期望的基类 (如 `BaseAdapter`)。
* 使用 `importlib.import_module` 动态导入模块。
* 使用 `getattr` 获取模块中的类对象。
* 进行类型检查,确保获取到的是类 (`isinstance(obj, type)`) 并且是预期基类的子类 (`issubclass(obj, expected_base)`)。
* 处理各种导入和属性错误。
* `_load_adapters()`: 遍历 `config['adapters']`,对 `enabled: true` 的条目,调用 `_import_class` 加载适配器类,实例化,并调用其 `setup()` 方法,最后存入 `self.adapters` 字典。
* `_load_handlers()`:
* 扫描 `plugins/` 下的二级目录中的 `.py` 文件。
* 导入每个模块,并使用 `_parse_plugin_meta` 解析模块 Docstring 中的 `@` 元数据。
* 查找模块中定义的 `BaseHandler` 子类。
* 根据 `@name` 或类名确定 Handler ID。
* 结合 `config['handlers']` 中的配置(`enabled`, `config`),实例化启用的 Handler,调用其 `setup()`,并添加到临时列表。
* **服务类插件 (`@service: true`)**: 如果元数据标记为服务,即使配置中未启用,也会尝试加载并执行其 `setup()`,但**不会**加入到用于消息处理的 `self.handlers` 列表。
* 最后,根据 `priority` 对 `handler_instances` 列表进行**降序排序**,存入 `self.handlers`。
* **生命周期 (`initialize`, `run_forever`, `shutdown`):**
* `initialize`: 编排配置加载、日志设置、数据库/调度器启动、组件加载和 Cron 任务调度的顺序。
* `run_forever`: 启动消息分发器 (`_message_dispatcher`) 和所有适配器的 `start()` 方法(通过 `_start_adapter_safe`),然后进入一个 `while self.is_running:` 循环,主要依靠 `asyncio.sleep` 和健康检查来维持运行,实际工作由后台任务完成。
* `shutdown`: 按安全顺序停止组件:设置 `self.is_running = False` -> 停止调度器 -> 停止所有适配器 (`_stop_adapter_safe`) -> 等待分发器处理完队列 -> 关闭数据库 -> 清理 `wait_input` -> 清理任务引用。
* **消息流 (`queue_message`, `_message_dispatcher`, `_dispatch_single_message`, `send`):**
* `queue_message`: 简单的 `await self.message_queue.put(message)`,带运行状态检查。
* `_message_dispatcher`: 核心的消费者循环,处理 `asyncio.Queue`,调用 `_dispatch_single_message`。包含超时逻辑以在关闭时正常退出。
* `_dispatch_single_message`:
1. 检查消息是否满足 `wait_input`。
2. 如果不满足,按优先级遍历 `self.handlers`。
3. 对每个 Handler 进行平台、权限、规则检查。
4. 匹配成功则创建 `Sender` 对象,调用 `handler.handle()`。
5. 根据返回值决定是否 `break` (停止匹配)。
6. 包含基本的异常捕获和计时。
* `send`: 查找目标 `adapter_id` 对应的实例,调用其 `send()` 方法。
* **定时任务 (`APScheduler`):**
* 在 `initialize` 中启动 `AsyncIOScheduler`。
* `_schedule_plugin_cron_tasks` 在加载 Handler 后,根据 `@cron` 元数据添加 Job,执行 Handler 的 `execute_cron` 方法。
* 提供 `schedule_task`, `cancel_task`, `get_scheduled_tasks` 作为公共 API (供 Web UI 或插件使用),其中 `schedule_task` 通过函数路径字符串动态添加任务。
* **waitInput (`_wait_input_futures`, `register_wait_input`, `_handle_wait_input_timeout`, `satisfy_wait_input`, `clear_wait_input`):** 使用一个字典存储 `wait_key` 到 `(Future, TimerHandle)` 的映射。`register` 时添加记录并设置 `call_later` 定时器。`satisfy` 时找到 Future 设置结果并取消定时器。`_handle_wait_input_timeout` 在超时后设置异常并清理记录。
* **系统方法 (`check_if_admin`, `request_restart`, `install_dependency`, `run_shell_command`):** 提供一些需要内核权限或状态才能执行的操作。**高风险操作必须在内部实现严格的安全检查和沙箱/隔离机制。** 目前示例代码中安全实现是缺失的。
### 消息 (`core/message.py`)
`InternalMessage` 是一个 `dataclass`,方便创建和访问属性。关键在于其字段设计能够承载来自不同平台的信息,并包含框架流转所需的状态和元数据。适配器负责**填充**它,处理器负责**读取**它,`Sender` 基于它创建交互上下文。
### 适配器基类 (`core/adapters.py`)
`BaseAdapter` 定义了适配器的骨架。子类必须实现核心的生命周期 (`setup`, `start`, `stop`) 和双向通信 (`send`, 以及在 `start` 中实现的接收逻辑并调用 `_push_to_kernel`)。`bridge` 属性提供了可选的扩展点。
### 处理器基类 (`core/handlers.py`)
* `BaseHandler` 主要定义了接口和元数据处理逻辑。`_compile_rules` 将 `@rule` 字符串编译为 `re.Pattern` 对象。`check_platform`, `check_rule`, `check_admin` 提供了基础的匹配前检查逻辑。`handle` 和 `run_scheduled_task` 是子类必须或应该实现的核心方法。
* `Sender` 是插件开发的核心交互对象。它通过持有 `kernel`, `message`, `adapter` 的引用,提供了方便且上下文感知的方法,简化了插件与框架底层的交互。
### 数据库 (`core/database.py`)
目前使用 TinyDB 的封装。`_get_table` 方法负责按表名获取(或创建)对应的 JSON 文件和 Table 对象。使用 `asyncio.Lock` 保证应用层对文件的**写操作**是串行的,但读操作可以并发。**注意:这并不是真正的异步 I/O 数据库。** 如果性能或并发写入成为瓶颈,需要替换为真正的异步数据库方案。
## 扩展建议
* **实现配置热重载:** 监控 `config.yaml` 文件变化,当文件被修改时,触发 Kernel 重新加载配置,并根据配置差异动态地启动、停止或重新配置适配器和处理器。这需要仔细处理状态迁移和资源清理。
* **插件依赖管理:** 实现更完善的插件依赖声明和自动安装机制(例如在插件元数据中声明 `requirements.txt` 路径或依赖列表),Kernel 在加载插件时自动检查并安装 Python 依赖。
* **Web UI 功能完善:**
* 实现完整的认证和授权系统。
* 为适配器、插件、定时任务提供增删改查的管理界面,并能安全地修改 `config.yaml`。
* 实现文件管理和 Web 终端的安全版本(例如使用 Docker API 操作容器,限制文件访问范围)。
* 实现插件市场后端逻辑。
* **事件总线:** 引入一个更通用的事件总线(例如使用 `asyncio-dispatch` 或自实现),允许组件之间发布和订阅更细粒度的事件,而不仅仅是消息传递。
* **状态持久化:** 将重要的运行时状态(例如群组监听/回复开关、用户数据等)存储到数据库中,而不是仅存在于内存或配置文件中。
* **测试覆盖:** 为核心代码、适配器和插件编写全面的单元测试和集成测试。
理解 Kernel 的调度逻辑、消息流程以及各组件的职责是进行核心开发的关键。
请谨慎使用代码。
5. 适配器开发文档 (docs/开发文档/适配器开发.md)
# Custom-bot 适配器开发文档
**目标读者:** 希望为 Custom-bot 添加对新聊天平台、协议或服务接口支持的开发者。
## 什么是适配器 (Adapter)?
适配器是 Custom-bot 框架中连接**外部世界**和**框架内核**的桥梁。它的主要职责是:
1. **接收:** 监听或连接到外部服务,接收该服务发送的数据(通常是某种协议格式的消息或事件)。
2. **转换 (入):** 将接收到的外部数据**解析**,并**转换**为框架内部统一的 `InternalMessage` 对象格式。
3. **推送:** 将转换后的 `InternalMessage` 对象**推送**到框架内核 (Kernel) 的消息队列中,等待后续处理。
4. **接收 (出):** 从框架内核接收用于发送的 `InternalMessage` 对象(通常由插件通过 `Sender.reply` 发起)。
5. **转换 (出):** 将 `InternalMessage` 对象**转换**回目标外部服务能够理解的协议格式。
6. **发送:** 通过已建立的连接或调用外部服务的 API,将转换后的数据**发送**出去。
7. **生命周期管理:** 处理自身的初始化 (`setup`)、启动 (`start`) 和停止 (`stop`) 逻辑,包括资源管理(如网络连接、后台任务)。
## 开发步骤
### 1. 创建适配器文件
* 在项目根目录下的 `Adapter/` 目录中创建一个新的 Python 文件。
* 文件名应清晰地反映其支持的协议或平台,并使用下划线命名法(例如 `my_protocol_adapter.py`, `telegram_adapter.py`)。
### 2. 定义适配器类
* 在新建的文件中,导入必要的库(例如协议库、`asyncio`、`logging`)以及框架核心类:
```python
import asyncio
import logging
# 导入你需要的协议库,例如:
# import aiohttp
# import websockets
# from some_sdk import Client
from typing import Any, Dict, Optional
from core.adapters import BaseAdapter
from core.message import InternalMessage
```
* 创建一个新的类,**必须继承**自 `core.adapters.BaseAdapter`。
```python
logger = logging.getLogger(f"适配器.{__name__}") # 使用模块名作为 Logger 名称
class MyProtocolAdapter(BaseAdapter):
# ... 类实现 ...
```
* **(可选但推荐) 添加模块级 Docstring 定义元数据:**
```python
"""
# @name MyProtocol适配器 # 适配器名称 (可选)
# @description 用于对接 MyProtocol 的适配器示例。
# @author Your Name
# @version 0.1.0
# @adapter true # 标记这是一个适配器 (可选)
"""
# ... import 和类定义 ...
```
### 3. 实现 `__init__` 方法
* 构造函数接收 `kernel`, `adapter_id`, `config` 三个参数。
* **必须**调用 `super().__init__(kernel, adapter_id, config)` 来初始化基类。
* 从 `config` 字典中读取并存储此适配器运行所需的配置项(例如 API Key, 服务器地址, 端口, Token 等),并进行必要的校验。
* 初始化适配器内部状态变量(例如连接对象、客户端列表、任务引用等)。
```python
class MyProtocolAdapter(BaseAdapter):
def __init__(self, kernel, adapter_id, config):
super().__init__(kernel, adapter_id, config)
# 读取配置,提供默认值
self.api_key = config.get("api_key")
self.server_url = config.get("server_url", "default.server.com")
self.listen_port = int(config.get("listen_port", 12345)) # 确保类型正确
self.protocol_name = config.get("protocol_name", "my_protocol") # 协议名
# 校验关键配置
if not self.api_key:
self.logger.error("配置错误:缺少 'api_key'。适配器可能无法正常工作。")
# 可以选择抛出异常或设置一个无效状态
# 初始化内部状态
self._client = None # 协议客户端实例
self._listener_task = None # 监听任务
self._is_connected = False # 连接状态标志
self._stop_event = asyncio.Event() # 用于优雅停止后台任务
```
### 4. 实现 `async def setup(self)` 方法
* 在此方法中执行**连接/监听之前**需要完成的异步准备工作。
* 例如:检查 API Key 有效性、加载证书、初始化数据库连接池(如果适配器需要独立数据库)、预加载数据等。
* 如果不需要异步准备,可以直接 `pass` 或只调用 `await super().setup()`。
```python
async def setup(self):
await super().setup()
self.logger.info("正在执行 MyProtocol 适配器的设置...")
# 示例:异步检查配置有效性或预热缓存
# await self.validate_api_key()
self.logger.info("设置完成。")
```
### 5. 实现 `async def start(self)` 方法 (核心 - 接收逻辑)
这是适配器**启动**并开始**接收**外部消息的核心方法。
* **调用基类:** `await super().start()` (虽然基类 `start` 简单,但保持调用是个好习惯)。
* **启动网络通信:**
* **服务器模式 (如反向 WS, HTTP Webhook):** 使用协议库启动监听(例如 `websockets.serve`, `aiohttp.web.TCPSite().start()`)。通常需要将一个连接处理函数(例如 `_connection_handler`)传递给监听器。
* **客户端模式 (如正向 WS, MQTT Client):** 使用协议库发起连接(例如 `websockets.connect`, `paho.mqtt.client.connect_async`)。
* **后台任务:** 网络监听或接收通常是**持续性**的。必须将这些操作放在一个或多个**后台 `asyncio.Task`** 中运行,以避免阻塞 `start()` 方法和整个框架。
```python
self._listener_task = asyncio.create_task(self._run_listener(), name=f"AdapterListener_{self.adapter_id}")
# 将任务存入 Kernel 的管理列表 (如果需要 Kernel 统一管理)
# if self.kernel and hasattr(self.kernel, '_running_tasks'):
# self.kernel._running_tasks[self._listener_task.get_name()] = self._listener_task
```
* **消息接收与处理循环 (在后台任务中):**
* 在后台任务(例如 `_run_listener` 或 `_connection_handler`)中,使用 `async for` 或 `while True: await receive_data()` 循环接收原始数据。
* **处理连接错误和重连 (客户端模式):** 如果是客户端模式,需要在连接断开或失败时实现重连逻辑,通常包含 `try...except` 和 `asyncio.sleep()`。
* **解析数据:** 将收到的原始数据(bytes/str)解析成 Python 对象(例如 JSON -> dict/list)。
* **提取信息:** 从解析后的数据和连接上下文中提取 `user_id`, `group_id`, `msg_id`, `text_content` 等。
* **创建 `InternalMessage`:** 实例化 `InternalMessage`,并填充所有必要字段。**务必**设置 `adapter_id=self.adapter_id` 和 `protocol=self.protocol_name`。
* **推送至 Kernel:** 调用 `await self._push_to_kernel(internal_message)`。
* **设置运行状态:** 在 `start()` 方法成功启动监听或连接任务后,设置 `self.is_running = True`。如果启动失败,应保持 `self.is_running = False`。
```python
async def start(self):
await super().start()
if self.is_running: # 防止重复启动
return
# 示例:启动一个后台监听任务 (服务器模式)
try:
# 假设 _run_server 是一个包含 websockets.serve 和 wait_closed 的方法
self._server_task = asyncio.create_task(self._run_server(), name=f"MyProtoServer_{self.adapter_id}")
# 假设启动成功,设置状态
self.is_running = True
self.logger.info("协议服务器已启动。")
except Exception as e:
self.logger.error(f"启动协议服务器失败: {e}", exc_info=True)
self.is_running = False # 启动失败
async def _run_server(self):
# 示例:使用 websockets 库
try:
async with websockets.serve(self._handle_connection, self.host, self.listen_port):
self.logger.info(f"服务器正在监听 {self.host}:{self.listen_port}")
await asyncio.Future() # 保持运行直到被取消
except OSError as e:
self.logger.error(f"启动监听失败: {e}")
self.is_running = False # 更新状态
except asyncio.CancelledError:
self.logger.info("服务器监听任务被取消。")
finally:
self.is_running = False # 确保退出时状态为 False
async def _handle_connection(self, websocket, path):
# --- 在这里实现接收、解析、转换、推送的逻辑 ---
connection_id = f"{websocket.remote_address[0]}:{websocket.remote_address[1]}"
self.logger.info(f"新连接: {connection_id} (路径: {path})")
try:
async for raw_data in websocket:
# 1. 解析 raw_data
parsed_data = self.parse_my_protocol_data(raw_data)
if not parsed_data: continue
# 2. 提取信息
user_id = parsed_data.get('sender_id')
text = parsed_data.get('text')
# ... 其他信息 ...
# 3. 创建 InternalMessage
msg = InternalMessage(
adapter_id=self.adapter_id, protocol=self.protocol_name,
connection_id=connection_id, user_id=user_id, text_content=text,
parsed_data=parsed_data, raw_data=raw_data
# ... 填充其他字段 ...
)
# 4. 推送到 Kernel
await self._push_to_kernel(msg)
except websockets.exceptions.ConnectionClosed:
self.logger.info(f"连接 {connection_id} 已关闭。")
except Exception as e:
self.logger.error(f"处理连接 {connection_id} 时出错: {e}", exc_info=True)
finally:
# 清理此连接相关的资源 (如果需要)
pass
```
### 6. 实现 `async def stop(self)` 方法 (核心)
负责**优雅地**停止适配器并释放所有资源。
* **调用基类:** `await super().stop()`。
* **设置停止标志:** (如果使用了类似 `_stop_event` 的机制) `self._stop_event.set()`,通知后台任务退出循环。
* **关闭网络连接:**
* **服务器模式:** 调用服务器对象的关闭方法(例如 `self._server.close()`),并等待其完全关闭 (`await self._server.wait_closed()` 或等待关联的任务结束)。主动关闭所有已连接的客户端 (`await client.close()`)。
* **客户端模式:** 调用客户端对象的断开连接方法 (`await self._client.disconnect()` 或 `await self._connection.close()`)。
* **取消后台任务:** 取消在 `start()` 中创建的所有 `asyncio.Task` (`self._listener_task.cancel()`),并使用 `await asyncio.wait_for()` 或 `await asyncio.gather()` 等待它们结束(捕获 `CancelledError`)。
* **清理资源:** 关闭文件句柄、数据库连接(如果适配器自己管理的话)、清除内部状态(如客户端列表 `self._clients.clear()`)。
* **设置运行状态:** 确保最后设置 `self.is_running = False`。
### 7. 实现 `async def send(self, message: InternalMessage)` 方法 (核心 - 发送逻辑)
负责将 Kernel 传来的 `InternalMessage` 发送出去。
* **确定目标:**
* **服务器模式:** 通常需要从 `message.connection_id` 字段获取目标客户端的标识符,并从内部状态(如 `self._clients` 字典)中找到对应的连接对象。如果 `connection_id` 为空或无效,应记录警告或错误,并返回 `False`。
* **客户端模式:** 目标通常是固定的、已连接的服务器。直接使用 `self._client` 或 `self._connection` 对象发送。
* **转换消息格式:**
* 根据目标协议的要求,将 `InternalMessage` 中的信息(主要是 `message.text_content`, `message.attachments`, `message.parsed_data`)转换为协议所需的格式(例如 JSON 字符串、XML、特定结构的 bytes、或者调用 SDK 提供的方法)。
* 处理附件:可能需要将 `attachments` 列表中的 URL 或本地路径转换为协议特定的表示方式(例如 CQ 码、上传文件获取 ID 等)。
* **发送数据:**
* 使用协议库提供的发送方法(例如 `websocket.send()`, `http_client.post()`, `mqtt_client.publish()`)将转换后的数据发送出去。
* 处理发送过程中可能发生的错误(例如连接已断开、网络错误、API 错误),并返回 `False`。
* **返回值:** 发送成功(或至少已尝试发送)返回 `True`,失败返回 `False`。
```python
async def send(self, message: InternalMessage) -> bool:
# 示例:服务器模式
target_conn_id = message.connection_id
if not target_conn_id or target_conn_id not in self._clients:
self.logger.warning(f"发送失败:找不到目标连接 {target_conn_id}")
return False
websocket = self._clients.get(target_conn_id)
if not websocket or not websocket.open:
self.logger.warning(f"发送失败:连接 {target_conn_id} 已关闭或无效")
if target_conn_id in self._clients: del self._clients[target_conn_id] # 清理
return False
# 转换消息为 MyProtocol 格式 (假设是 JSON)
try:
payload = self.convert_to_my_protocol(message)
if payload is None: return False # 无法转换
data_to_send = json.dumps(payload, ensure_ascii=False)
except Exception as convert_err:
self.logger.error(f"转换消息 {message.internal_id} 为协议格式时出错: {convert_err}")
return False
# 发送数据
try:
await websocket.send(data_to_send)
self.logger.debug(f"已向 {target_conn_id} 发送消息 {message.internal_id}")
return True
except websockets.exceptions.ConnectionClosed:
self.logger.warning(f"发送到 {target_conn_id} 失败:连接已关闭")
if target_conn_id in self._clients: del self._clients[target_conn_id]
return False
except Exception as send_err:
self.logger.error(f"向 {target_conn_id} 发送消息时出错: {send_err}", exc_info=True)
return False
```
### 8. (可选) 实现 Bridge 和 get_status
* **Bridge:** 如果协议提供了一些独特的功能(例如获取群成员列表、修改用户资料等),可以通过重写 `@property def bridge(self)` 来暴露一个包含这些异步方法的对象或字典,供插件通过 `sender.bridge` 调用。
* **get_status:** 重写 `def get_status(self)` 方法,返回一个包含更详细运行时状态的字典,例如连接状态、客户端数量、错误计数等,方便 Web UI 展示。
### 9. 配置适配器
最后,在 `config/config.yaml` 的 `adapters` 部分添加该适配器的配置,包括 `enabled: true`, `class:` (指向你的类) 和 `config:` 下的特定参数。
## 适配器开发注意事项
* **完全异步:** 适配器中的所有 I/O 操作(网络、文件读写等)都必须是异步的,使用 `async` 和 `await`。避免任何长时间阻塞事件循环的操作。
* **错误处理:** 对网络错误、协议错误、解析错误、发送错误等进行健壮的处理。客户端适配器应包含自动重连逻辑。
* **资源管理:** 确保在 `stop()` 方法中正确关闭连接、取消任务、释放所有资源,避免内存泄漏或句柄泄漏。
* **日志记录:** 使用 `self.logger` 记录清晰的日志,方便调试和监控。
* **协议细节:** 深入理解你要对接的协议规范或 SDK 文档,确保消息的正确解析和构建。
* **InternalMessage 映射:** 仔细设计外部协议数据与 `InternalMessage` 字段之间的映射关系,尽量保留原始信息,同时提供标准化的字段供插件使用。
通过遵循这些步骤和原则,您可以为 Custom-bot 添加对各种新协议和服务的支持。
请谨慎使用代码。
接下来是最后一部分:插件开发文档。
这是面向开发者的。 插件(处理器/Handler)开发文档
6. 插件开发文档 (docs/开发文档/插件开发.md)
# Custom-bot 插件开发文档
**目标读者:** 希望为 Custom-bot 添加新功能、命令、事件响应或与其他服务集成的开发者。这是扩展 Custom-bot 功能最常用的方式。
## 什么是插件 (Handler)?
插件,在 Custom-bot 框架内部也称为处理器 (Handler),是负责执行具体**业务逻辑**的模块。它们接收由内核 (Kernel) 分发过来的标准化内部消息 (`InternalMessage`),根据预设的规则和条件进行判断,并执行相应的操作,例如:
* 响应用户通过聊天发送的命令(如查询天气、讲笑话)。
* 处理特定类型的事件(如用户加入群聊、收到好友请求)。
* 与外部 API 或服务进行交互(如获取新闻、翻译文本)。
* 操作数据库(如记录用户积分、存储设置)。
* 执行定时任务(如每日签到提醒、定期清理数据)。
* 修改或控制机器人的行为。
## 开发步骤
### 1. 创建插件文件
* **位置规则:** 插件 Python 文件 (`.py`) **必须** 放置在项目根目录下 `plugins/` 文件夹内的 **二级子目录** 中。
* 这个二级子目录通常用于**组织和分类**插件,例如可以按功能命名 (`娱乐`, `管理`, `工具`) 或按作者命名 (`官方插件`, `张三的插件`)。
* **示例:**
* `plugins/娱乐/猜数字.py` (会被加载)
* `plugins/管理/权限检查.py` (会被加载)
* `plugins/官方插件/基础命令.py` (会被加载)
* `plugins/根目录插件.py` (**不会**被加载)
* `plugins/娱乐/utils/helper.py` (**不会**作为插件加载,但可以被同目录插件导入使用)
* **命名:** 文件名建议使用小写和下划线(例如 `weather_query.py`),并能清晰反映插件功能。文件名(去除 `.py`)或稍后定义的类名可以作为配置中的默认 Handler ID。
* **`__init__.py`:** 确保每个二级子目录(例如 `plugins/娱乐/`)下都有一个空的 `__init__.py` 文件,这样 Python 才能将该目录识别为一个包。
### 2. 定义元数据 (模块级 Docstring)
在插件 `.py` 文件的**最顶部**,使用多行字符串 `"""..."""`(模块级 Docstring)来定义插件的元数据。这些元数据使用 `@key value` 的格式,由 Kernel 在加载时解析。
```python
"""
# --- 插件元数据 ---
# @name 天气查询 # [必需] 插件名称,也是 config.yaml 中 handlers 的默认 ID
# @description 查询指定城市的天气信息。 # [推荐] 插件功能描述
# @author 你的名字或昵称 # [推荐] 作者信息
# @version 1.1.0 # [推荐] 插件版本
# @origin 可选的来源 # 例如:社区、官方
# @platform all # [可选] 适用的平台 (all 或空格分隔列表: qqbot_reverse_ws tg_bot)
# @rule ^天气\s+([\u4e00-\u9fa5]+)$ # [必需,除非service] 触发规则(正则),可多个@rule。捕获组会作为参数
# @rule ^weather\s+(\w+)$
# @priority 100 # [可选] 优先级 (数字越大越高, 默认 0)
# @admin false # [可选] 是否仅管理员可触发 (true/false, 默认 false)
# @disable false # [可选] 是否默认禁用 (true/false, 默认 false)
# @cron 0 30 7 * * * # [可选] 定时任务表达式 (6位含秒), 执行 run_scheduled_task
# @service false # [可选] 是否为服务插件 (true/false), 服务插件通常无 rule, 启动时执行 setup
# @public false # [预留] 是否公开发布
# @Copyright (可选) 版权信息
"""
# --- 你的 Python 代码从这里开始 ---
import asyncio
import logging
# ... 其他导入 ...
from core.handlers import BaseHandler, Sender, HandlerMatch, CONTINUE_MATCHING
# ...
请谨慎使用代码。
关键元数据解释:
@name (必需): 插件的易读名称,也是 中 部分默认使用的 ID。 **必须保config.yaml处理器必须保证唯一性。
@description (推荐): 描述插件的功能。
@rule (必需,除非是 的插件): 定义触发插件 @service:true处理 方法的。 可以定义多个正则表达式@rule,只要消息的文本内容 () 匹配其中规则即可触发(还需满足平台和权限条件)。message.text_content任何一个
正则表达式中的 会被提取出来,作为参数传递给 捕获组 (...)处理 方法(可以通过 获取)。 sender.param(索引)
@platform (可选): 限制此插件只在指定平台的消息上触发。 值可以是适配器的 ID (如 ) 或协议名称 (如 `qqbot_reverse_wsonebot_rws),用空格分隔多个。 省略或为 则都
@priority (可选): 整数,决定匹配优先级。 Kernel 会按优先级尝试匹配插件。 如果一个高优先级插件处理了消息并且从高到低没有返回 ('next'),则低优先级的插件即使规则匹配也不会被执行。CONTINUE_MATCHING
@admin (可选): 布尔值。 如果为 ,则只有被配置为管理员的用户(在 `kernel真kernel.admins 的 或插件特定 中)发送的消息匹配 config.admins@rule 时,才会触发 方法。 处理
@disable (可选): 布尔值。 如果为 ,则无论 中如何配置,此插件都**不会真config.yaml不会被加载。 主要用于临时禁用插件而无需修改配置文件。
@cron (可选): 如果提供标准的 6 位 Cron 表达式(含秒),Kernel 会使用 APScheduler 定时调用此插件的 方法。 run_scheduled_task()
@service (可选): 布尔值。 如果为 ,此插件通常没有 `@rul真@rule,主要用于在框架启动时执行一次 方法(例如启动后台任务、注册全局监听器等)。 服务类插件设置 ()不会参与普通的消息处理流程(即 方法不会被消息触发)。 处理
3. 编写插件代码
导入依赖: 导入所需的 Python 库以及框架核心类:
import asyncio import logging import aiohttp # 示例:用于发起 HTTP 请求 from typing import TYPE_CHECKING, Optional # 核心类导入 from core.handlers import BaseHandler, Sender, HandlerMatch, CONTINUE_MATCHING if TYPE_CHECKING: # 类型检查时导入,避免循环 from core.message import InternalMessage
content_copydownload
请谨慎使用代码。蟒
创建处理器类: 创建一个继承自 的类。 类名建议与文件名或 相关。 core.handlers.BaseHandler@name
logger = logging.getLogger(f"插件.{__name__}") # 使用模块名创建 Logger
class WeatherQueryPlugin(BaseHandler):
# ...
请谨慎使用代码。
实现 方法 (可选):async def setup(自身)
在此方法中执行插件初始化时需要的一次性异步操作。
例如:读取插件的特定配置 ()、初始化 API 客户端、加载模型、连接数据库等。self.config
async def setup(self):
await super().setup()
self.api_key = self.config.get("weather_api_key") # 从 config.yaml 读取配置
if not self.api_key:
self.logger.warning("未配置天气 API Key,插件功能可能受限。")
self.http_session = aiohttp.ClientSession() # 创建共享的 HTTP session
self.logger.info("天气查询插件已设置完成。")
请谨慎使用代码。
实现 方法 (核心):async def handle(self, sender: Sender, message: InternalMessage, match: HandlerMatch)
获取参数: 使用 (索引从 1 开始) 获取 正则表达式中匹配到的捕获组内容。sender.param(索引)@rule
city_name = sender.param(1) # 假设第一个捕获组是城市名
if not city_name:
await sender.reply("请输入要查询的城市名称。")
return # 处理完毕,停止匹配
请谨慎使用代码。
执行逻辑: 调用 API、查询数据库、进行计算等。所有 I/O 操作必须使用 。 等待
try:
# 假设有一个异步方法获取天气
weather_info = await self._get_weather(city_name)
if weather_info:
reply_text = f"{city_name}的天气:{weather_info}"
else:
reply_text = f"未能查询到 {city_name} 的天气信息。"
except Exception as e:
self.logger.error(f"查询天气时出错: {e}", exc_info=True)
reply_text = "查询天气时发生错误,请稍后再试。"
请谨慎使用代码。
发送回复: 使用 发送结果给用户。 可以发送文本、图片、或其他类型(取决于适配器支持)。 sender.reply()
await sender.reply(reply_text)
# 示例:发送图片
# await sender.reply({
# 'type': 'image',
# 'path': 'https://example.com/weather.png' # 图片 URL 或本地路径
# })
请谨慎使用代码。
交互式操作 (可选): 使用 可以等待用户的下一步输入,实现多轮对话。sender.waitInput()
调用系统方法 (可选): 通过 , ,sender.db_get()sender.db_set()sender.sleep() 等方法与框架交互。
返回值: 决定是否让其他插件继续处理这条消息。
返回 无 (或不返回): 停止匹配。
返回 CONTINUE_MATCHING (或 ): 继续匹配。 '下一个'
实现 方法 (如果定义了 ):async def run_scheduled_task(个体经营)@cron
在此方法中编写定时执行的逻辑。
可以通过 访问内核实例,例如发送推送消息:self.kernel 内核
async def run_scheduled_task(self):
self.logger.info("执行每日天气推送任务...")
# 假设从数据库获取用户订阅的城市
# subscribed_users = await self.kernel.db.get("weather_subscriptions", "all_users", [])
# for user_id, city in subscribed_users:
# weather_info = await self._get_weather(city)
# if weather_info:
# # 需要知道用户来自哪个平台才能推送
# # 这部分逻辑比较复杂,可能需要额外存储用户信息
# await self.kernel.push_message(
# target_platform="qqbot_reverse_ws", # 需要知道平台
# target_user_id=user_id,
# text_content=f"早上好!{city} 今日天气: {weather_info}"
# )
请谨慎使用代码。
(可选) 实现 方法:async def teardown(自营) 虽然基类没有定义,但你可以在 Kernel 关闭时(如果 Kernel 支持调用插件的 teardown)执行清理操作,例如关闭 。 aiohttp 的客户端会话
导出插件类: 在文件末尾添加(注意:这是 Python 赋值,不是 Node.js 的导出):
# Kernel 会自动查找并加载这个类
# module.exports = YourHandlerClassName # 这是错误的!Python 不需要显式导出
# 只需要定义好类即可
请谨慎使用代码。
(Kernel 的加载逻辑会自动查找文件中定义的 子类)BaseHandler (基础处理程序)
4. 配置插件 (config/config.yaml)
在 部分添加或修改对应插件 ID 的条目。处理器:
设置 。 启用:true
在 下添加插件 或 方法中需要读取的配置项。配置:设置 ()手柄()
5. 测试插件
重启框架:'docker-compdocker-compose restart 自定义机器人 (或 )。 Python main.py
检查日志: 查看 Kernel 日志,确认插件是否成功加载。
触发插件: 通过配置的适配器发送能够匹配插件 的消息。 @rule
观察行为: 查看机器人的回复是否符合预期,检查日志中是否有错误信息。
调试: 使用 添加更多调试日志,或者使用 VSCode Remote - Containers 等工具进行断点调试。logger.debug()
Sender 对象详解
发射机 对象是插件与框架交互的主要接口,在 方法中由 Kernel 传入。 以下是一些常用方法:手柄()
获取信息:
sender.get_msg(): 获取消息文本 ()。 message.text_content
sender.get_msg_id(): 获取平台原始消息 ID。
sender.get_user_id(), : 获取用户信息。 sender.get_user_name()
sender.get_group_id(), : 获取群组信息 (私聊时可能为 None)。 sender.get_group_name()
sender.get_platform(): 获取来源适配器 ID。
sender.get_protocol(): 获取来源协议名称。
sender.param(索引): 获取 正则匹配的第 @rule指数 个捕获组 (从 1 开始)。
sender.msg: 直接访问触发的 对象,获取更完整的信息 (内部消息sender.msg.parsed_data, 等)。 sender.msg.附件
发送消息:
await sender.reply(内容, ...): 发送回复消息。 可以是字符串或包含类型/路径的字典。 可以指定 内容user_id, , 'quote_msggroup_idquote_msg_id。
消息操作:
await sender.delete_message(*msg_ids, wait=0): 请求适配器删除指定 ID 的消息(通常用于撤回机器人自己发的消息)。
交互流程:
await sender.waitInput(超时=30,callback=无): 等待该用户的下一次输入。 返回包含新消息的 对象,发射机没有。 可用于在收到输入时立即处理并决定回调'再')。
等待 sender.again(reply_content): 和返回 ''ag回复()'再' 的语法糖,用于 的回调。 waitInput
权限检查:
await sender.is_admin(): 检查当前用户是否是管理员。
数据库操作:
await sender.db_get(表,键,默认=无)
await sender.db_set(表、键、值)
await sender.db_delete(表,键)
await sender.db_keys(表)
系统功能:
等待 sender.sleep(秒): 异步休眠。
sender.get_time(格式): 获取格式化时间。
sender.config: (只读) 访问 Kernel 加载的全局配置字典。
sender.get_config_value(key_path,默认): 安全地获取嵌套配置项。
等待 sender.inline(text): 模拟当前用户发送一条新消息。
(高风险)'等待sender.kernel.install_等待 sender.kernel.install_dependency(...), '等待 sender.kernel.run_s: 调用 Kernel 提供的受控方法执行高风险操作 (需要 Kernel 实现安全检查)。等待 sender.kernel.run_shell_command(...)
适配器桥 ():发件人.bridge
如果来源适配器实现了 属性,可以通过 桥sender.bridge.your_method() 调用适配器提供的特定功能。
详细的方法签名和用法请参考 中 核心/handlers.py发射机 类的代码和注释。
调试技巧
日志: 在插件代码中使用 , ,logger.debug()logger.info()logger.warning(), 记录关键步骤和变量值。 将 Kernel 的日志级别 (logger.error()config.yaml -> kernel.log_level) 设为 可以看到最详细的输出。 调试
打印(): 在开发初期或简单调试时, 也可以快速输出信息到控制台。 打印()
检查 对象:消息 在 方法开头打印 处理消息 对象本身 () 或其 (print(消息)__dict__打印(message.__dict__)),可以了解适配器传递过来的所有信息。
单元测试: 为插件的核心逻辑编写单元测试,模拟 和 发射机内部消息 对象进行测试。
VSCode 远程 - 容器: 如果使用 Docker,强烈推荐使用 VSCode 的 Remote - Containers 扩展。 它允许你在容器内进行开发和调试,可以直接在插件代码中设置断点、检查变量、单步执行等,极大地提高了调试效率。 参考 文档 (需要创建)。 远程开发 (VSCode)
通过这些步骤和工具,你可以高效地为 Custom-bot 开发各种强大的功能插件。
---
至此,主要的文档框架和内容已经提供。您需要根据项目的实际进展和特性,不断填充、更新和完善这些文档。
请谨慎使用代码
- 感谢你赐予我前进的力量