这是一个适用于 早柚核心 (gsuid_core) 的 QQ 官方机器人适配器插件。它把 QQ 开放平台 的官方机器人接进早柚核心:通过 WebSocket 长连接或 Webhook 回调收消息,转成早柚协议交给核心处理,再把核心下发的消息转成 QQ 官方接口调用发出去。
- 🔌 两种接入方式:
WebSocket(插件主动连官方网关,最省事,默认)与Webhook(插件自己开 HTTP 端口等官方回调,需要核心公网可达,所有机器人共用一个端口)都支持,每个机器人单独选。 - 🤖 多机器人: Bot 管理页里加几个就是几个官方机器人,一个插件实例能同时挂多个 AppID,共用一条核心连接。
- 📱 扫码绑定: 用手机 QQ 扫码授权,自动写入 AppID / Secret 并拉取机器人资料,不用手抄密钥。
- ♻️ 保存即生效: 改完启用状态或接入方式点保存,插件自己重载运行时,不用重启核心。
- 📝 Markdown 图片: MD 里的图片会先换成公网链接(核心图床优先,失败回落 QQ 素材管理)
- 🔘 按钮: 核心下发按钮时按官方 keyboard 结构带上去。
- ↩️ 引用与撤回: 入站解析被引用消息的正文与图片;出站支持引用回复、主动撤回,并按
echo回传消息 ID。 - 🛡️ 禁言 / 解禁: 支持核心下发的
excute_ban_user,秒数为 0 即解禁。 - 📡 元事件上报: 进群、退群、机器人被拉进群 / 被踢出群都转成核心的 meta 事件。
- 🔄 断线重连: 间隔可配(毫秒),0 表示断开立刻重连;连续快速失败会退避到 1 秒,连上并稳定 5 秒才清零失败计数。
- 📮 主动消息队列: 被动回复窗口(5 分钟)之外的发送自动转成主动消息,群聊和私聊各走一条独立车道,按可配置间隔放行限速(间隔填
0则不限速,立刻放行);订阅推送和其他主动推送共用同一条队列,先到先发。 - 🧩 可选独立进程: 打开后适配器跑在单独进程里,通过核心的
/ws/{bot_id}接入。
-
把插件目录放进早柚核心的
gsuid_core/gsuid_core/plugins/下,目录名保持QQBotUID:cd gsuid_core/gsuid_core/pluginsgit clone https://github.com/xiowo/QQBotUID.git QQBotUID
-
重启核心,插件会在
on_core_start阶段读取 Bot 列表并启动。 -
网页控制台 → 插件页面 → Bot 管理 → 扫码添加,或手动填 AppID / Secret。
-
网页控制台 → 插件配置 → QQBotUID配置,确认
启用 QQBotUID已打开。
配置分三个地方,互不重叠:
| 位置 | 管什么 |
|---|---|
| 插件页面 → Bot 管理 | 机器人列表:启用状态、接入方式、扫码添加、群 / 频道二维码 |
| 插件配置 → QQBotUID配置 | 全局开关、独立进程模式、Webhook 监听参数 |
| 插件配置 → 高级配置 | 接口地址、客户端名称、主动消息队列 |
〔运行控制〕
| 配置项 | 说明 | 默认值 |
|---|---|---|
| 启用 QQBotUID | 总开关,关掉后所有机器人都不会启动 | true |
| 启用 Webhook | Webhook 总开关,只有打开时 Webhook 模式的机器人才会监听端口 | false |
| 忽略其他机器人消息 | 发送者是机器人的消息直接丢弃,不交给核心处理 | true |
| 重连间隔 (ms) | 断线后等多少毫秒重连,0 表示断开立刻重连 |
0 |
| 获取群名称 | 开启后调 /v2/groups/{group_openid}/info 查群名并缓存 24 小时;关闭则群名统一用 群聊-xxxxxxxx 兜底,不请求接口、也不建缓存表 |
true |
〔Webhook〕
| 配置项 | 说明 | 默认值 |
|---|---|---|
| Webhook 监听地址 | 监听哪个网卡,0.0.0.0 表示全部;核心在 Docker 里时留 0.0.0.0 并映射端口 |
0.0.0.0 |
| Webhook 监听端口 | 所有 Webhook 机器人共用这一个端口,按回调路径里的 AppID 分发 | 8766 |
| Webhook 路径 | 官方后台填的回调路径,插件会在后面追加每个机器人的 AppID | /qqbotuid/webhook |
〔接口地址〕
| 配置项 | 说明 | 默认值 |
|---|---|---|
| QQ API Base | 官方开放平台接口地址,一般不用改 | https://api.bot.qq.com |
| QQ Token Base | 获取 access_token 的地址,一般不用改 |
https://api.bot.qq.com |
〔连接细节〕
| 配置项 | 说明 | 默认值 |
|---|---|---|
| 固定 Gateway URL | 留空时由 /gateway/bot 自动获取;只在自建网关时才需要填 |
空 |
| 客户端名称 | 写进官方 WebSocket User-Agent 的客户端名 | QQBotUID |
〔主动消息队列〕
被动消息只能在收到消息后 5 分钟内带 msg_id 回复。超过窗口的发送(订阅推送、定时任务等)只能走主动消息,官方对这类消息有频率限制,所以统一排队。
群聊和私聊是两条独立车道,互不阻塞;同一车道内按提交顺序放行,相邻两条之间至少隔下面的毫秒数。
| 配置项 | 说明 | 默认值 |
|---|---|---|
| 私聊主动间隔 (ms) | 私聊车道两条主动消息之间的放行间隔;填 0 立刻放行 |
110 |
| 群聊主动间隔 (ms) | 群聊车道两条主动消息之间的放行间隔;频道消息也走这条车道;填 0 立刻放行 |
1010 |
每条记录对应一个官方机器人,可以随时新增 / 删除 / 单独禁用。
| 项 | 说明 | 默认值 |
|---|---|---|
| 启用 | 单独开关,关掉只停这一个 | true |
| 接入方式 | WebSocket 或 Webhook,改完保存就重载运行时 |
WebSocket |
| AppID | 开放平台的应用 ID,扫码绑定会自动写入 | 空 |
| Secret | 应用密钥,列表里只显示「已设置」,不回显明文 | 空 |
| 群聊添加 / 频道添加 | 详情页里的两个二维码,扫码把机器人拉进群或频道 | — |
机器人昵称、头像、
uin、bot_id在扫码绑定或首次拉取时从官方接口取一次,之后存在data/QQBotUID/bots.json。
插件从收到的事件里推「哪个群有哪些人」,只存 openid。不用开开关,也不用手动建表——启动时自己补。
| 表 | 一行是什么 | 什么时候写 |
|---|---|---|
QQBotUID_User |
一个群里的一个 openid | 收到消息 / 入群事件加,退群事件删;内存攒 5 秒批量落库 |
QQBotUID_GroupInfo |
一个群:群名、成员数、更新时间 | 收到群消息时只读缓存,没命中就丢后台查一次,24 小时内不重复请求 |
QQBotUID_User 只有 bot_id、group_openid、user_openid 三列(外加自增 id)。私聊的 group_openid 是空串,频道存 channel_id。
| 事件 | 动作 |
|---|---|
群消息(GROUP_AT_MESSAGE_CREATE 等) |
加这个人——能说话就一定在群里 |
GROUP_MEMBER_ADD |
加这个人 |
GROUP_MEMBER_REMOVE |
删这个人 |
| 私聊消息 | 加到 group_openid 为空的那一组 |
同一个人重复出现不会产生重复行;同一批里「退了又进」以最后一次为准。
QQBotUID_GroupInfo 的列是 bot_id、group_openid、group_name、member_num、updated_at。群名和成员数来自 /v2/groups/{group_openid}/info,拉取失败留空串并按 24 小时冷却,不会冲掉已经拿到的名字。
控制帧沿插件与核心之间那条 ws 走:核心下发一帧 MessageSend,插件查完把结果塞进 recall_message_id 回执的 id 字段返回,不经过 HTTP、不占端口。
import json
from gsuid_core.models import Message
receipts = await bot.send(
Message(
type="qqbotuid_users_request",
data={"request_id": "req-1", "bot_id": "102030405", "group_openid": "1A790910D2..."},
),
wait_recall=True,
)
payload = json.loads(receipts[0]) if receipts else {}帧 type |
data 参数 |
回执内容 |
|---|---|---|
qqbotuid_users_request |
request_id、bot_id、group_openid |
group_openid + count + user_ids |
qqbotuid_groups_request |
request_id、bot_id、keyword、limit(默认 200) |
rows,按 updated_at 倒序 |
group_openid 不传是全部群(同一个 openid 可能重复出现),传空串是只看私聊。
回执长这样,request_id 原样带回,用来对账:
{
"request_id": "req-1",
"ok": true,
"group_openid": "1A790910D2...",
"count": 2,
"user_ids": ["9E5C1F3A4B2D...", "B41D77C0E19A..."]
}几个必须注意的点:
wait_recall=True不能省。 不带它核心不会登记等待,receipts永远是None。request_id必填。 缺了会回{"ok": false, "error": "missing request_id"},不会去查库。- 等待上限 10 秒(核心
RECALL_WAIT_TIMEOUT),超时receipts为空列表。查库是本地 SQLite,正常毫秒级。 - 别把这条连接的回执能力打坏。 核心对「连续 3 次整轮零回执」的连接会关掉 recall,之后撤回和查询都拿不到回执。插件这边每帧都会回执(查库异常也转成
ok: false),但如果你在别处乱发wait_recall请求,要留意这个计数。
默认适配器跑在核心进程里。打开独立进程模式后,核心会在启动时自动拉起一个独立的 daemon 进程来承载适配器,关闭核心时一并关掉——插件崩了不会带走核心。
插件配置 → QQBotUID配置 → 〔独立进程模式〕,把 启用独立进程 打开,重启核心即可。核心会自动拉起 daemon,不用手动跑。
daemon 意外退出时核心会自动重启它(2 秒起指数退避,最长 30 秒;稳定跑满 60 秒后退避归零)。如果检测到已有别的 daemon 在跑(data/QQBotUID/daemon.lock 有 30 秒内的心跳、且里面记的 pid 确实活着),核心会停止自动拉起并打 error 日志,避免两个进程抢同一个 bot。反过来,daemon 被强杀留下的残锁不会被误判——pid 已经不存在就直接忽略。
同机部署不用配任何东西:核心默认把 127.0.0.1 列在 TRUSTED_IPS 里。跨机才需要填 核心地址,并且要在核心那边配好 WS_TOKEN。
| 项 | 同进程 | 独立进程 |
|---|---|---|
| 崩溃隔离 | 和核心共享进程 | 独立进程,崩了不影响核心 |
| 核心图床 | 可用 | 可用,含 CNBUpload 这类插件接管的上传通道 |
| 改配置 | 保存即生效 | 保存后 daemon 3 秒内自己重载;图床配置单独一路,只重建客户端不重启 Bot |
| 部署 | 只跑核心 | 核心自动托管 daemon,仍是一个启动入口 |
不用额外配 systemd / nssm:daemon 由核心托管,核心退出时会先 terminate,8 秒内没退就 kill。想让它脱离核心单独跑(比如核心和 daemon 分机器部署),也可以直接执行 python daemon.py,这时关掉核心里的 启用独立进程 免得重复拉起。
在 QQBotUID配置 页的〔独立进程模式〕分组:
| 配置项 | 说明 | 默认值 |
|---|---|---|
| 启用独立进程 | 打开后核心拉起 daemon 承载适配器,关掉即回到同进程 | false |
| 核心地址 | daemon 连核心用的地址,同机填 127.0.0.1 |
127.0.0.1 |
| 核心端口 | 要和核心配置里的 PORT 一致 |
8765 |
| 核心 WS Token | 核心配置里的 WS_TOKEN,同机留空即可 |
空 |
A: 机器人选了 Webhook,但 QQBotUID配置 里的 启用 Webhook 总开关没打开。两者都要开,Webhook 才会真正监听端口。
A:
- 官方要求 MD 里的图片要么是公网可访问的 URL,要么是素材管理返回的
qqbot-media://引用;本地路径和内联 base64 都不行。 - 检查是否开了「启用将图片转链接发送」,或者核心有没有可用的图床客户端;两样都没有时会回落 QQ 素材管理。
- 素材管理有 10MB 单张上限、每个机器人 100 张配额,超限的图会被跳过(日志里会写明原因);插件发完 10 秒会删掉刚上传的素材释放配额,已发出的消息不受影响。
A: 能,Bot 管理页里加几条就是几个。Webhook 模式下所有机器人共用 Webhook 监听端口 这一个端口,靠回调路径里的 AppID 区分,不用多开端口;同一个 AppID 不要重复添加。
A:
| 文件 | 内容 |
|---|---|
data/QQBotUID/config.json |
全局开关与 Webhook 参数 |
data/QQBotUID/advanced_config.json |
接口地址、客户端名称、主动队列间隔 |
data/QQBotUID/bots.json |
机器人清单,含 AppSecret,别外传 |
A: 插件收到消息时会记下 msg_id,5 分钟内的回复带上它,算被动消息,不消耗主动额度。超过窗口的会去掉 msg_id 走主动队列——订阅推送就是典型,它带的是注册订阅时的旧 msg_id,必然过期,所以固定走主动,按间隔排队发出。
MIT License