Files
DanDingNoneBot/danding_bot/plugins/danding_qqpush/README.md

299 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Danding_QqPush 插件
用于通过外部 HTTP API 向 QQ 群定向推送通知的 NoneBot 插件。
## 功能特性
- ✅ 通过 HTTP API 推送消息到指定 QQ 群
- ✅ 自动将文本渲染为图片,避免长文本刷屏
- ✅ 统一通知卡片版式,群聊/私聊使用不同场景徽章
- ✅ 可选 @指定 QQ 用户;省略 `qq` 时直接发送群消息
- ✅ 使用 `#` 符号表示换行
- ✅ 基于 Token 的简单鉴权机制
- ✅ 支持中文文本渲染
- ✅ 自带超级用户预览命令「测试通知图片」,可在群聊/私聊中直接核对卡片版式
## 目录结构
```
danding_qqpush/
├── __init__.py # 插件初始化模块
├── config.py # 配置模块
├── api.py # FastAPI 路由模块
├── text_parser.py # 文本处理模块
├── image_render.py # 通知卡片生成模块
├── article_render.py # 公告 HTML 截图模块
├── preview_image_render.py # 预览命令共用的渲染器实例
├── sender.py # 消息发送模块
├── test_command.py # 超级用户「测试通知图片」预览命令
└── utils.py # 工具函数模块
```
## 通知卡片样式
群聊与私聊的普通图片通知(即未传 `html` 的文本通知)共用同一套「冷灰外衬 + 白色圆角卡片」版式,只通过右上角的场景徽章区分来源:
- 顶部为蓝色品牌头部:左侧固定文案「蛋定助手」,右侧为白底胶囊场景徽章
- 正文使用比纯黑更柔和的墨色,行距不低于 1.6 倍
- 底部为分割线与脚注「本消息由蛋定助手自动生成」
- 正文按宽度自动折行,并做中文避头尾处理
- Emoji 按整体方块宽度测量,避免溢出卡片;Emoji 数据源不可用时自动降级为普通文本绘制,通知仍会正常发出
### 场景徽章
| 场景 (`scene`) | 徽章文案 | 徽章颜色 | 使用位置 |
| -------------- | -------- | -------- | -------- |
| `common` | 通用通知 | 灰色 | 渲染默认值 |
| `group` | 群聊通知 | 品牌蓝 | `POST /danding/qqpush/{token}` 的普通文本通知 |
| `private` | 私聊通知 | 橙色 | `POST /danding/private/{token}` 的图片通知 |
- `scene` 是渲染层内部参数,**不新增配置项、也不新增 API 请求字段**:请求体格式保持不变,场景由插件按接口自动选择(群聊接口传 `group`,私聊接口传 `private`)
- 未知的 `scene` 取值会回退为 `common`
- 传入 `html` 的公告推送使用 Chromium 截图(`article_render.py`),不使用本卡片版式
```python
renderer.render(text) # 通用通知
renderer.render(text, scene="group") # 群聊通知
renderer.render(text, scene="private") # 私聊通知
renderer.render_to_base64(text, title="🏇 赛马开赛", scene="group") # 自定义标题行
renderer.render(text, title=None) # 不绘制标题行
```
`title` 默认沿用旧版固定文案「蛋定助手通知您:」以保持向后兼容;显式传 `None` 或空字符串才隐藏标题行(卡片头部已展示「蛋定助手」品牌,需要更紧凑的版式时可以隐藏)。
## 测试通知图片命令
不想注册 HTTP 接口、也不想占用群聊刷屏时,可以直接用机器人自带的预览命令核对卡片版式。
| 项目 | 说明 |
| ---- | ---- |
| 主命令 | `测试通知图片` |
| 别名 | `通知图片测试`、`测试通知卡片` |
| 权限 | 仅 NoneBot 超级用户(`SUPERUSER`,即 `.env` 的 `SUPERUSERS`) |
| 触发方式 | 群聊直接发送,或与机器人私聊发送;均不需要 @机器人 |
- 群聊中触发时渲染 `scene="group"`(群聊通知徽章),私聊中触发时渲染 `scene="private"`(私聊通知徽章)
- 预览内容是固定示例,覆盖标题行、中文、多行、Emoji 与中文标点;标题行特意写成 `# 会被解析为换行:`,同时演示 `#` 作为换行符的用法
- 命令复用通知卡片渲染器与插件配置(字体、宽度、行距等),不新增配置项,也不改变任何 HTTP 接口
- 渲染是同步阻塞操作,命令会把它放到 `asyncio.to_thread` 执行,避免阻塞事件循环
- 渲染失败时机器人会记录 `[Danding_QqPush] 通知图片测试失败` 日志,并回复「通知图片生成失败,请查看机器人日志后重试。」
### 用法示例
```
(群聊 / 私聊中直接发送)
测试通知图片
```
## 安装与配置
### 1. 确保依赖已安装
```bash
pip install pillow
```
### 2. 配置 Token
在 `.env` 文件或 NoneBot 配置文件中添加:
```env
DANDING_QQPUSH_TOKEN = "your-custom-token-here"
```
如果不配置,默认使用 `danding-8HkL9xQ2`。
### 3. 启动 NoneBot
```bash
nb run
```
插件会自动加载,并在日志中显示注册的 API 路径。
## API 使用说明
### 接口信息
- **方法**: `POST`
- **路径**: `/danding/qqpush/{token}`
- **Content-Type**: `application/json`
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
| -------- | ------ | ---- | ------------------------ |
| token | path | 是 | 配置的 Token |
| group_id | int | 是 | 接收消息的 QQ 群号 |
| qq | int | 否 | 可选的被 @ QQ 号;省略时不 @ 任何人 |
| text | string | 是 | 通知文本(`#` 表示换行) |
### 请求示例
```bash
curl -X POST "http://localhost:8080/danding/qqpush/danding-8HkL9xQ2" \
-H "Content-Type: application/json" \
-d '{
"group_id": 123456789,
"text": "系统告警#数据库连接失败#请立即处理"
}'
```
### Python 请求示例
```python
import requests
url = "http://localhost:8080/danding/qqpush/danding-8HkL9xQ2"
data = {
"group_id": 123456789,
"text": "系统告警#数据库连接失败#请立即处理"
}
response = requests.post(url, json=data)
print(response.json())
```
### 响应示例
**成功响应** (200):
```json
{
"success": true,
"message": "推送成功",
"data": {
"group_id": 123456789,
"qq": null,
"message_id": 12345
}
}
```
**错误响应** (400):
```json
{
"detail": "group_id 不能为空"
}
```
**错误响应** (403):
```json
{
"detail": "Token 验证失败"
}
```
**错误响应** (500):
```json
{
"detail": "Bot 未连接,请检查机器人状态"
}
```
## 配置选项
可以在配置文件中自定义以下选项:
```python
class Config(BaseModel):
Token: str = "danding-8HkL9xQ2"
"""API 访问 Token"""
ImageWidth: int = 800
"""图片宽度(像素)"""
ImageFontSize: int = 24
"""字体大小(像素)"""
ImagePadding: int = 30
"""图片内边距(像素)"""
ImageLineSpacing: float = 1.4
"""行距倍数"""
ImageBgColor: tuple = (252, 252, 252)
"""图片背景颜色 (R, G, B)"""
ImageTextColor: tuple = (0, 0, 0)
"""文本颜色 (R, G, B)"""
MaxTextLength: int = 2000
"""最大文本长度(字符数)"""
FontPaths: list = [...]
"""字体文件路径列表"""
```
与通知卡片相关的渲染约定(沿用上述配置,无需新增配置项):
- `ImageBgColor` 用作卡片填充色;卡片外衬固定为冷灰色,保证浅色卡片轮廓可见
- `ImageTextColor` 传入纯黑 `(0, 0, 0)` 时会自动替换为柔和的 `(31, 35, 41)`,其他颜色原样使用
- `ImageLineSpacing` 低于 `1.6` 时正文按 `1.6` 倍行距渲染
- `ImageWidth`/`ImagePadding`/`FontPaths` 的语义不变,卡片与正文均按它们计算版式
## 文本格式说明
- 使用 `#` 符号表示换行
- 示例:`第一行#第二行#第三行` 会被渲染为三行文本
- 超过最大长度的文本会被自动截断
## 注意事项
1. **安全性**: Token 泄露只影响推送能力,无账号风险,但建议定期更换
2. **Bot 状态**: 确保 Bot 已连接,否则会返回 500 错误
3. **群权限**: 确保 Bot 在目标群中有发送消息的权限
4. **字体支持**: 插件会自动尝试加载系统中的中文字体
## 故障排查
### 问题:启动时显示 "未找到可用的 Bot 实例"
**说明**: 这是正常现象。插件加载时 Bot 可能还未连接,API 调用时会动态获取 Bot 实例。
### 问题:返回 "Bot 未连接"
**解决方案**:
- 检查 NoneBot 是否正常启动
- 检查 OneBot V11 连接状态
- 确认 Bot 已成功连接到 QQ 服务器
### 问题:图片显示乱码
**解决方案**:
- 检查系统是否安装了中文字体
- 在 `FontPaths` 配置中添加正确的字体路径
### 问题:消息发送失败
**解决方案**:
- 检查 Bot 是否在目标群中
- 检查 Bot 是否有发送消息的权限
- 查看日志中的详细错误信息
### 问题:发送「测试通知图片」没有反应
**解决方案**:
- 确认发送者的 QQ 号在 `.env` 的 `SUPERUSERS` 中(该命令仅超级用户可用,其他用户发送不会触发、也不会有提示)
- 确认消息就是命令本身(`测试通知图片` / `通知图片测试` / `测试通知卡片`),命令后跟其他内容不会触发
- 若机器人回复「通知图片生成失败,请查看机器人日志后重试。」,按日志中 `[Danding_QqPush] 通知图片测试失败` 的报错排查字体路径与配置
## 测试
使用提供的测试脚本进行测试:
```bash
python test_qqpush.py
```
修改脚本中的 `group_id` 和 `qq` 为实际值。
插件自带的自动化测试(不需要真实 QQ 连接):
```bash
python -m pytest tests/test_danding_qqpush_test_command.py -q
```