Skip to content
xiowoPublic

About

一个适用于早柚的QQBot适配器

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

🦊 适用于早柚核心的QQ官方机器人适配器 (QQBotUID)

这是一个适用于 早柚核心 (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} 接入。

🛠️ 安装说明

  1. 把插件目录放进早柚核心的 gsuid_core/gsuid_core/plugins/ 下,目录名保持 QQBotUID:

    cd gsuid_core/gsuid_core/plugins
    git clone https://github.com/xiowo/QQBotUID.git QQBotUID
  2. 重启核心,插件会在 on_core_start 阶段读取 Bot 列表并启动。

  3. 网页控制台 → 插件页面 → Bot 管理 → 扫码添加,或手动填 AppID / Secret。

  4. 网页控制台 → 插件配置 → QQBotUID配置,确认 启用 QQBotUID 已打开。

📝 配置指南

配置分三个地方,互不重叠:

位置 管什么
插件页面 → Bot 管理 机器人列表:启用状态、接入方式、扫码添加、群 / 频道二维码
插件配置 → QQBotUID配置 全局开关、独立进程模式、Webhook 监听参数
插件配置 → 高级配置 接口地址、客户端名称、主动消息队列

页签:QQBotUID配置

〔运行控制〕

配置项 说明 默认值
启用 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

Bot 管理页(每条记录)

每条记录对应一个官方机器人,可以随时新增 / 删除 / 单独禁用。

项 说明 默认值
启用 单独开关,关掉只停这一个 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,同机留空即可 空

❓ 常见问题 (FAQ)

Q1: 日志提示「是 Webhook 模式但未启用『启用 Webhook』,已跳过监听」?

A: 机器人选了 Webhook,但 QQBotUID配置 里的 启用 Webhook 总开关没打开。两者都要开,Webhook 才会真正监听端口。

Q2: Markdown 里的图片显示不出来?

A:

  1. 官方要求 MD 里的图片要么是公网可访问的 URL,要么是素材管理返回的 qqbot-media:// 引用;本地路径和内联 base64 都不行。
  2. 检查是否开了「启用将图片转链接发送」,或者核心有没有可用的图床客户端;两样都没有时会回落 QQ 素材管理。
  3. 素材管理有 10MB 单张上限、每个机器人 100 张配额,超限的图会被跳过(日志里会写明原因);插件发完 10 秒会删掉刚上传的素材释放配额,已发出的消息不受影响。

Q3: 能接多个机器人吗?

A: 能,Bot 管理页里加几条就是几个。Webhook 模式下所有机器人共用 Webhook 监听端口 这一个端口,靠回调路径里的 AppID 区分,不用多开端口;同一个 AppID 不要重复添加。

Q4: 配置文件在哪?

A:

文件 内容
data/QQBotUID/config.json 全局开关与 Webhook 参数
data/QQBotUID/advanced_config.json 接口地址、客户端名称、主动队列间隔
data/QQBotUID/bots.json 机器人清单,含 AppSecret,别外传

Q5: 回复走主动消息还是被动消息?

A: 插件收到消息时会记下 msg_id,5 分钟内的回复带上它,算被动消息,不消耗主动额度。超过窗口的会去掉 msg_id 走主动队列——订阅推送就是典型,它带的是注册订阅时的旧 msg_id,必然过期,所以固定走主动,按间隔排队发出。

📄 License

MIT License

About

一个适用于早柚的QQBot适配器

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages