自己在 QQ 上跟 AI 聊天,其实不难——难的是把那些坑趟平。这份源码就是趟完坑的版本,直接能用。
先说清楚它是什么:收到 QQ 消息 → 拼上下文 → 调用任意 OpenAI 兼容接口 → 把回复发回 QQ。 它不是带工具能力的 Agent,读不了你电脑上的文件、跑不了命令,就是个聊天桥。这一点先讲明白,免得预期错位。
两种接入方式
| 对比项 | 官方机器人 | NapCat + OneBot |
|---|---|---|
| 账号 | 独立机器人账号,带「机器人」标识 | 你的小号 |
| 封号风险 | 无 | 有 |
| 公网 IP | 不需要 | 不需要 |
| 群里回话 | 只在 @ 机器人时 | 默认 @ 时,可关 |
| 门槛 | 注册开发者、创建机器人 | 扫码即用 |
推荐官方。网上很多教程默认你要有公网服务器配 Webhook,其实官方支持 WebSocket 长连接, 官方文档原话是「不依赖公网服务器部署,适合个人单机使用」。也就是说一台普通 Windows 电脑就能跑。
零依赖是怎么做到的
整个项目不需要 pip install 任何东西,Python 3.10+ 自带的标准库就够。
唯一的障碍是官方网关走 WebSocket,而 Python 标准库没有 WS 客户端。常见做法是装 botpy, 但它会拖进 aiohttp 一串依赖。这里的做法是手写了一个约 200 行的 RFC6455 客户端 ws_client.py: 握手校验 Sec-WebSocket-Accept、掩码帧收发、ping/pong 自动应答、分片重组。
好处不只是省依赖——你完全知道它做了什么。坏处是自己要测得够狠,所以自测里单独验了 125 字节边界、 126 和 127 两种扩展长度、以及 90KB 大帧的收发往返。
快速开始
1. 开通官方机器人
- 登录 QQ 开放平台(q.qq.com)注册并完成主体认证,个人可以。
- 「创建机器人」,填头像、昵称、简介。
- 进机器人管理页 →「开发设置」,记下
AppID和AppSecret。 - 同一页的「事件订阅与回调地址」,连接方式选 WebSocket。
- 「内部体验号码」加上你自己的 QQ 号——这一栏最多 20 个号,不受服务范围限制,
等于绕过审核先自用,比等服务范围审批快得多。
2. 配置
copy .env.example .env
编辑 .env,至少填五项:
LLM_BASE_URL=https://api.deepseek.com/v1
LLM_API_KEY=sk-你的key
LLM_MODEL=deepseek-flash
QQ_APP_ID=你的AppID
QQ_CLIENT_SECRET=你的AppSecret
模型端只要兼容 OpenAI 的 /chat/completions 就行,DeepSeek、各类中转、本地 Ollama 都可以。
3. 启动
python qq_official.py
看到这两行就成了:
鉴权成功,机器人「你的机器人名」,session=...
网关已连接 wss://api.sgroup.qq.com/websocket/,心跳 41s
然后给机器人发条私聊。
4. 锁定白名单
第一轮白名单是空的,谁找到这个机器人都能用你的额度。跑起来后日志会打印:
首次见到 private 会话 openid=ABC123DEF... (你的昵称)—— 想锁定就把这个 id 填进 ALLOW_PRIVATE
把这个 openid 填进 .env 的 ALLOW_PRIVATE,群消息填 ALLOW_GROUPS,重启生效。
注意 openid 不是 QQ 号,是机器人和用户之间的映射,每个机器人都不一样,只能从日志里拿。 这也是为什么白名单不能预先填——装好先用一轮,把日志里的值抄进去。
三个实际踩过的坑
这部分是本文最值钱的地方,都是真撞过并定位了原因的。
坑一:群里 @ 它不回,但私聊正常
不是代码问题。日志里能看到 GROUP_ADD_ROBOT,说明机器人确实进群了,但之后一条消息事件都没有。
原因:群消息权限不是「拉进群」就自动有的。必须由群主用最新版手机 QQ 把机器人 移除再重新添加,添加过程中会弹「设置权限」,点进去勾上接收消息。单纯拉进群不算授权。
补充两个小细节:手打的 @机器人名 只是一段普通文本,平台不会当成真 @,必须从成员列表里点选; 只发一个空 @ 不带文字也收不到,脚本会当作没内容忽略。
坑二:msg_id + msg_seq 会被去重
官方的被动回复机制是带 msg_id 回复,同一条消息有次数上限(单聊 4 次、群聊 5 次),而且 相同的 msg_id + msg_seq 重复发送会被去重,报错 40054005。
这意味着:如果你先发一条「收到,正在思考…」占位,再发正式回复,两者都从 msg_seq=1 开始的话, 第二条会被直接丢掉。正确做法是同一条消息的所有回复连续递增 msg_seq——占位吃 1,正文从 2 开始。
源码里 send_reply() 返回下一个可用序号,就是这个原因。
坑三:推理模型会返回空内容,而且不报错
换成 deepseek-flash 之后踩到的。这个模型会把 max_tokens 预算先花在 reasoning_content(思考过程)上, 再输出 content。实测 max_tokens=16 时,16 个 token 全被思考吃光,返回:
{
"content": "",
"reasoning_content": "我们需要回答中文。用户问……",
"finish_reason": "length"
}
HTTP 200,没有报错,但内容是空的。 如果代码不判断这点,就会把它当成「模型没响应」。
源码里的处理:默认预算提到 2000,并且第一次拿到空内容时把预算翻倍重试一次,日志里带上 finish_reason 和当时的预算,方便判断是不是又被思考吃光了。
顺便说成本:这类模型的思考 token 一样计费。实测一句「在吗」,输出 7 个 token,思考用了 114 个—— 费用大头在思考上。
顺带一个判断中转端点的技巧
市面上的 API 中转很多,模型名往往只是标签。想知道背后到底是谁,看两个地方:
一、看 usage 字段。 DeepSeek 原生只会吐 prompt_cache_hit_tokens / prompt_cache_miss_tokens。 如果同时出现 cache_read_input_tokens / cache_creation_input_tokens(这是 Anthropic 的命名), 说明网关在混多套字段,大概率不是单一上游。
二、看报错体。 如果错误里出现 account(s) cooling down this model 这类字样, 说明它背后是账号池轮换——同一个模型名可能轮到不同账号甚至不同上游,那模型标签就完全不可信了。
最后提醒一句:模型自报身份毫无参考价值。 实测 DeepSeek 官方 deepseek-reasoner 被问 「你是不是 DeepSeek」时,回答「我是 OpenAI 开发的 ChatGPT」。模型没有可靠的自我认知, 答案是顺着上下文编的——你自己试试,前面对话说它是某某模型,它多半就顺着认了。
其他能力
- 上下文记忆:默认记最近 8 轮,存
memory.json,重启不丢;/reset清空当前会话 - 引用解析:群里引用某条消息跟它说话时,会把被引用原文一起喂给模型
- Markdown 转纯文本:QQ 不渲染 Markdown,链接保留成
文字 (url)形式,QQ 里可点 - 长回复分段:按换行边界切,段间隔 300ms 防触发频率限制
- CQ 码转义:文本里的
[CQ:转成全角冒号,防止模型被诱导输出 CQ 码去 @全体成员 - 单实例锁:重复启动会被拒绝并退出,避免两个进程同时连网关、同一条消息各回一遍
下载
压缩包内含全部源码、配置模板、MIT 许可与致谢说明(NOTICE.md),解压即用。
# 解压后
copy .env.example .env
# 编辑 .env 填 AppID / AppSecret / 模型 key
python qq_official.py
# 想先确认环境没问题,跑自测(不碰真 QQ、不需要真 AppID)
python test_official.py
免责与合规
- QQ 机器人服务由腾讯提供,本项目与其无隶属关系。使用前请阅读 QQ 开放平台运营规范与《QQ 用户协议》。
- NapCat 方案属于非官方协议,存在账号风控风险,建议只用小号,且不要用于群发、自动加好友等行为。
- 本项目采用 MIT 许可开源。其中 QQ 侧文本处理部分移植自开源项目
Derpyu520/qq-bridge(MIT), 已在 NOTICE.md 中保留原作者版权声明。
- 代码不收集、不上传任何数据,
memory.json与日志都只存在本地。
源码下载:qq-bridge-1.0.0.zip(约 35KB,MIT 许可,解压即用)






暂无评论内容