初始提交:QQ 群入群验证机器人

基于 qqbotsdk-py 的入群验证:进群发按钮验证码、完成前禁言、
超时踢出(11253 时兜底长期禁言)、乱点他人按钮处罚、状态持久化
与重启恢复。38 个单元/集成测试。
This commit is contained in:
snowy-yang committed 2026-10-04 18:44:08 +08:00
commit 755013b9db
17 files changed
+1864

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
# AuthBot — QQ 群入群验证机器人
基于 [qqbotsdk-py](https://pypi.org/project/qqbotsdk-py/)(QQ 官方开放平台 SDK)的群验证机器人:
新成员进群 → 机器人发**数学选择题 + 按钮验证码** → 完成验证前该成员处于**禁言**状态 →
限时内点对按钮立即解禁;**超时未通过则移出群**(若踢人接口未获官方内邀权限,自动降级为
长期禁言 + 通知管理员)。
## 功能一览
- 进群自动验证:`GROUP_MEMBER_ADD` 事件触发,数学题 4 选 1 按钮(题目随机、答错换题、倒计时不重置)
- 验证前禁言:按 `member_openid` 单独禁言(不碰全员禁言),通过后立即解禁
- 超时踢出:默认 10 分钟;踢人失败(业务码 11253)自动兜底为禁言 72 小时 + 群内提示管理员
- 旧题防重放:按钮 data 携带题目 token,换题后旧按钮点击无效
- 反捣乱:非被试者点击他人的验证按钮,30 分钟窗口内累计 3 次即禁言 5 分钟
(次数与时长可配:`FOREIGN_CLICK_LIMIT` / `FOREIGN_CLICK_MUTE_MINUTES`)
- 状态持久化:`data/` 下 JSON 存储,进程重启自动恢复禁言与倒计时(已过期立即处理)
- 轻量管理指令:群里 `@机器人 验证状态` 查看待验证名单与剩余时间;
`@机器人 验证我`(或 `测试验证`)对发起人本人走一遍完整验证流程,方便上线前自测
- 免验信任期(可选):通过后 N 天内退群重进不再验证,默认 0(每次都验)
## 环境要求
- Python **3.12+**(SDK 硬性要求;开发环境为 3.14)
- `qqbotsdk-py >= 0.2.1`(0.2.1 修复了按成员禁言的请求体——旧版平铺写法会被服务端静默忽略)
- QQ 开放平台机器人([q.qq.com](https://q.qq.com) 注册,拿到 AppID / AppSecret)
## 快速开始
```bash
# 1. 安装依赖
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt
# 2. 配置凭据
copy .env.example .env
# 编辑 .env,填入 APPID / APPSECRET
# 3. 启动(WebSocket 方式,开箱即用)
.venv\Scripts\python main.py
```
事件订阅开关已配好在 `intents.toml`:`GROUP_AND_C2C_EVENT`(@消息)、
`GROUP_MEMBER_EVENT`(进退群,**默认关闭的关键开关**)、`INTERACTION`(按钮回调)。
## 平台侧准备(重要)
1. **markdown 消息与按钮(键盘)权限**:在 q.qq.com 开发者后台为机器人申请。
没有该权限验证码发不出去——机器人此时**不会禁言新成员**(防止把人永久锁死),
并在日志输出 CRITICAL/ERROR 提示排查方向。
2. **机器人入群并设为群管理员**:禁言(`restrict_chat_setting`)、移出成员都需要管理员身份。
3. **踢人接口处于官方"内邀接入中"**:未开通的机器人调用返回业务码 11253。
机器人会自动降级:延长禁言(默认 72 小时,可配)并在群里提示管理员手动处理。
4. 群消息接收方式保持默认(@机器人)即可,无需"接收所有消息"权限。
## 配置项(.env)
| 变量 | 默认 | 说明 |
| --- | --- | --- |
| `APPID` / `APPSECRET` | 必填 | 开放平台凭据 |
| `CONNECTER` | `websocket` | `websocket` 或 `webhook` |
| `VERIFY_TIMEOUT_MINUTES` | `10` | 验证限时,超时触发处理 |
| `PENDING_MUTE_HOURS` | `24` | 待验证期间禁言时长 |
| `TIMEOUT_KICK` | `true` | 超时是否尝试移出群 |
| `TIMEOUT_FALLBACK_MUTE_HOURS` | `72` | 踢出失败后的兜底禁言时长 |
| `CAPTCHA_OPTIONS` | `4` | 选项个数(2~5) |
| `VERIFIED_TRUST_DAYS` | `0` | 通过后免验天数(0=每次都验) |
| `FOREIGN_CLICK_LIMIT` | `3` | 他人乱点验证按钮的处罚阈值(30 分钟窗口) |
| `FOREIGN_CLICK_MUTE_MINUTES` | `5` | 乱点处罚的禁言时长(分钟) |
| `DATA_DIR` | `data` | 状态文件目录 |
## 目录结构
```
main.py 入口
authbot/
config.py .env 业务参数读取与校验
captcha.py 验证码生成、按钮编解码、消息模板
state.py pending/verified 持久化 + 超时定时器
handlers.py 事件处理器(进群/按钮/退群/@指令/超时流程)
intents.toml 事件订阅开关
data/ 运行时状态(自动创建)
tests/ pytest 单元测试
```
## 已知限制(官方 API 能力边界)
- 群场景只有 **openid**(`group_openid` / `member_openid`),拿不到真实 QQ 号,
所以提示信息里只能显示 openid 尾号。
- **群主/管理员/机器人无法被禁言**(官方限制,接口会静默返回成功但不生效);
机器人会在回查禁言列表后发现这一点,`验证我` 测试时会明确提示。因此请用
普通成员账号测试禁言效果。
- 禁言接口请求体为嵌套结构 `{"members": [{"op": "add|update|del", ...}]}`
(平铺写法会被服务端静默忽略,实测踩坑;SDK 0.2.1 起已内置正确封装);
写入后机器人会回查确认生效。
- 被动回复有窗口限制:群内同一凭据 5 分钟内最多 5 条;主动消息默认未开通,
超时通知发送失败时仅记录日志(禁言/踢人不受影响)。
- 兜底禁言同样需要机器人是群管理员。
## 测试
```bash
.venv\Scripts\python -m pytest tests/ -v
```
覆盖验证码生成/编解码、状态持久化、定时器触发与取消、重启恢复、配置校验、
处理器装配(不发起任何网络请求)。