mirror of
https://github.com/snowy-yang/qqbotsdk.git
synced 2026-10-21 15:54:48 +00:00
15 KiB
15 KiB
更新日志
本项目遵循 Keep a Changelog 格式, 版本号遵循语义化版本。
[Unreleased]
新增
- 旧版(频道侧)OpenAPI 对齐(对照旧版 wiki 全集核对):私信(
create_dms建会话、post_dms_message发信、withdraw_dms_message撤回);身份组增删改与成员授予/收回;频道成员列表/踢出(可拉黑+撤回历史消息)/身份组成员列表/子频道在线成员数;全员与批量成员禁言;子频道用户/身份组权限查改;频道消息频率设置;接口权限列表与授权链接申请;新增audio域(播放控制、上/下麦)与forum域(帖子列表/详情/发帖/删帖)组合进BotApi(AUDIO_STATUS_*、FORUM_FORMAT_*常量随模块导出) - 事件全量对齐:新增
GROUP_MEMBER_ADD/GROUP_MEMBER_REMOVE(共用GroupMemberChange)、GROUP_JOIN_REQUEST(入群申请,含验证问答与自动审批命中信息,仅机器人是群管理员时推送)、SUBSCRIBE_MESSAGE_STATUS(订阅消息授权状态)四个 payload;Intent枚举与 intents.toml 补上此前缺失的GROUP_MEMBER_EVENT (1<<24)分组(默认 false,需订阅请手动开启) - v2 OpenAPI 全量对齐(对照官方 2026-09 新版文档站逐页核对):新增
group域方法集组合进BotApi——群信息/成员列表/成员详情/机器人群内状态、入群申请列表与审批、群黑名单增删查、群成员批量移除、群禁言状态查询、入群自动审批策略(增删改查/执行/白名单,机器人维度全局配置) - v2 消息扩展:
post_c2c_stream_message流式分批发送单聊消息(AI 逐段下发,STREAM_*常量取值);prepare_group_upload/finish_group_upload_part与prepare_c2c_upload/finish_c2c_upload_part大文件分片上传(预上传拿预签名 URL,分片 PUT 后逐片确认,最后携upload_id走原上传接口合并);generate_url_link生成机器人分享链接(callback_data透传) - 频道域补充:
create_channel/update_channel/delete_channel子频道增删改(私域接口)、get_me_guilds机器人加入的频道列表(分页) - 斜线指令管理:新增
panel域方法集组合进BotApi——指令面板(聊天输入框输入/唤起的指令列表)的增删改查与关联对象管理(create_panel/get_panels/get_panel/update_panel/delete_panel/update_panel_targets,支持 c2c/group/channel/dm 四场景,c2c/group 可按指定对象生效);附全局自定义菜单get_menu/update_menu(仅 C2C,send_message类型点击后把/指令填入输入框);SCOPE_*/TARGET_*/ITEM_*等取值常量随模块导出 - Markdown 与内嵌按钮一等支持:
post_group_message/post_c2c_message新增markdown/keyboard参数(自动置 msg_type=2,与 content 互斥);新增respond_interaction()回应按钮回调;Interactionpayload 补齐chat_type/scene/group_openid等官方字段 Event新增event_id属性(推送信封顶层的事件 id,与 op/t/d 平级):非消息事件(GROUP_ADD_ROBOT 等)的被动回复不再需要手动event.raw["id"]- 按钮快捷构造与点击处理:
button()/keyboard()生成内嵌按钮(免手写嵌套 dict);Interaction新增button_data属性(被点按钮的 action.data);examples/group_bot 补/btn卡片与INTERACTION_CREATE回调示例 - handler 后台并发:
@ee.on(..., background=True)把 handler 放进后台任务,不阻塞事件流(同事件内失去先后保证,返回值不回流);run_loop退出经emitter.close()统一取消在飞任务 - OpenAPI 限流与鉴权自愈:
BotApi.request对 429 自动重试(最多 3 次,优先Retry-After,脏值容错解析,退避封顶 30s);401 强制刷新 token 后重试一次,token 被服务端提前作废不再直接抛错 - 群消息全量模式:新增
GROUP_MESSAGE_CREATE事件接收,与GROUP_AT_MESSAGE_CREATE共用GroupAtMessage(需在开放平台开通"接收所有消息"权限,不限 @机器人);GroupAtMessage补齐官方新增的message_type/message_scene/attachments/mentions/ark_data/msg_elements字段 - 连接寿命主动重连:服务端约 1 小时后会无预警断开连接(实测),故连接存活到
_SERVER_LIFETIME - _RECONNECT_MARGIN(默认 60min − 10min = 50min)时由 SDK 主动收尾重连——走正常 RESUME 续传、期间消息由网关补发,而不是被服务端掐断后由看门狗判成异常断开;收帧超时在"静默阈值"与"寿命余量"两个截止里取先到者,静默仍归看门狗处理 - GitHub Actions:push/PR 自动执行
ruff check、pyright与 pytest;文档文件变更时自动把纯文档组装进docs分支(与源码历史无关的孤儿分支),供 Cloudflare Pages 直连 GitHub 仓库部署(生产分支docs,零构建) - 文档站点:基于 docsify(侧边栏、全文搜索、代码复制、mermaid 图渲染、分页),
npx docsify-cli serve本地预览;新增examples/README.md示例索引
变更
- 移除 dishka 依赖,改为内置参数注入:handler 形参直接标注组件类型(
api: BotApi)即注入,不再需要Inject[T]包装;组件改在run_loop显式装配并按类型登记到emitter.services(自定义组件同样登记即可注入)。破坏性变更:Inject、make_container、use_container随之移除,原api: Inject[BotApi]写法改为api: BotApi EventEmitter的queue参数改为可选:不传自动创建队列,EventEmitter()即可直接注册 handler;需多处共享队列时仍可显式传EventQueue(ee.queue取实例)。文档与示例统一改为无参构造api.py拆分为api/包:core.py请求内核(鉴权/限流重试/错误归一)+v2.py(群/单聊与按钮构造)、channel.py(频道消息)、guild.py(频道管理与查询)三个域方法集组合成BotApi;方法名与导入路径不变(from qqbotsdk.api import BotApi, button, keyboard照旧),新增域加模块即可- 心跳归传输层:心跳从
WebsocketProtocol(定时调度任务)迁入WebsocketConnecter(asyncio 任务随连接生灭),断线期间不再往 reply 队列堆积过期心跳,重连也不会先冲出陈旧心跳 - 重连退避只在连接稳定存活(≥60s)后才重置归一,抖动的服务端不再被 1s 间隔反复探测
AccessToken并发安全:换取过程持asyncio.Lock双重检查(并发首调只发一次请求);到期判断改用 monotonic 时钟并预留 30s 提前刷新余量;新增invalidate()供 401 时强制作废- 组件构造签名瘦身:
WebsocketProtocol(config, session, token)移除未使用的queue;WebsocketConnecter改收(config, http, token, session, queue);WebhookConnecter改收(config, queue, handle_event),op=13 应答经注入的回调取得,不再依赖具体EventEmitter;Session合并sequence_id/seq双属性为seq(心跳序列号无历史时发 0 而非 null);移除服务端不会推送的 HEARTBEAT 死代码 handler;ApiError/TokenError的错误响应解析合并为model.error_parts - intents.toml 改为只按大类订阅:配置项从"
[分组]表内逐事件开关"简化为"分组名 = true/false"(如GROUP_AND_C2C_EVENT = true)——网关只接受分组位掩码,组内事件本就无法单独订阅,此前的逐事件开关只是"组内任一为 true 即订阅整组"的装饰。破坏性变更:旧格式(表内逐事件开关)不再兼容,与拼错的分组名、非布尔值一样在启动时直接抛ValueError,而非静默少订阅 - 形参注入缓存化:handler 的签名与类型标注解析结果(此前每条事件都重新跑
inspect.signature/get_type_hints)合并缓存,services命中改为每条事件实时判定——先注册 handler、后登记组件的既有装配顺序不受影响 - 协议帧与业务事件分流,各接入方式配专有协议处理器:
BaseProtocol从emitter.py移入新模块protocol.py,接口由register(emitter)改为async on_frame(payload)(返回需回送的响应体);协议处理器不再注册进 emitter,而是由run_loop构造后注入对应的 connecter(WebsocketConnecter(..., protocol)/WebhookConnecter(config, queue, protocol)),由适配器逐帧调用。破坏性变更:只有 op=0 的业务事件进事件队列交给EventEmitter,HELLO/READY/INVALID_SESSION/op=13 等协议帧在连接器内就地处理——用户 handler 不再收到协议帧;EventEmitter.register_protocol()、EventEmitter.handle()、emitter._protocol/on_payload机制随之删除,协议类不再需要register()注册 handler。附带修掉一处时序耦合:INVALID_SESSION的会话重置此前依赖分发泵在重连前消费掉该事件,现在在 connecterbreak重连前同步完成,不会误 RESUME 已死会话 - 事件分发热路径减负:
emit()先取事件名、无 listener 时不再构造Event(每条未订阅事件/协议帧省一次对象与 dict 拷贝);Event.data改为惰性拷贝(只用.typed/.raw的 handler 不再触发dict(d)) - ws 重连收尾补
await:取消reply/heartbeat任务后gather(..., return_exceptions=True)取回结果,避免 "Task exception was never retrieved" 并确保它们在下一轮重连前真正停下 EventQueue.put_event/put_reply去掉多余的return awaitEventQueue收敛为单向业务事件通道:删除reply_queue与put_reply/get_reply(协议分流后引用它的只剩WebsocketConnecter)。ws 的出站帧(心跳 + 协议应答)改由WebsocketConnecter私有出站缓冲承载、send_helper单任务发送(维持 aiohttp ws 单写者不变量);webhook 的应答直接写 HTTP 响应体。破坏性变更:EventQueue.reply_queue与put_reply/get_reply不再存在- 移除 pyee 依赖:
EventEmitter改为内置的事件名 → handler 列表注册表(on()追加、emit()顺序遍历),不再需要 pyee 的once/new_listener/错误事件机制。注册语义保持不变(同函数重复注册只生效一次、按注册顺序串行执行)。破坏性变更:once/remove_listener等 pyee 装饰性 API 不再提供(SDK 与公开文档本就不涉及) - 热路径去掉重复计算(不改语义):
payloads按类缓存解析计划(不再每次事件重跑dataclasses.fields与联合类型解包,实测 7.5µs→2.5µs;未收录字段照旧忽略);crypto按 AppSecret 缓存派生私钥(验签每请求省约 40µs 的密钥派生) WebsocketConnecter的reply_helper更名为send_helper(它从出站缓冲取帧发送,原名会让人以为还有 reply 队列);Connecter由typing.Protocol改为abc.ABC,与BaseProtocol的接口风格一致- webhook 去重改为摊还清理:过期判定按当前 id 做(O(1),TTL 外重发的同一 id 仍按新事件处理),整表清过期项只在表长到阈值时执行(原先每请求全表扫描,O(n))
移除
Event的字段名映射属性user_id/group_id/content/message_id(含旧版群消息 payload 顶层from_user_id字段的回退):Event收敛为推送信封封装,只保留.raw/.data/.typed/.type/.event_id;事件体字段一律标注 payload dataclass(如msg: GroupAtMessage)获取解析结果,或经.data/.raw自取。破坏性变更:原event.group_id改为 typed payload 的msg.group_openid,event.message_id改为msg.id,以此类推EventEmitter.register_protocol()、EventEmitter.handle()与emitter持有协议层的机制(协议处理器改为由 connecter 持有并逐帧调用,见上文"变更")- apscheduler 依赖(心跳迁入 connecter 后不再需要调度器)
- 音乐插件示例(
plugins/、根目录main.py)已拆分为独立的点歌 bot 项目(含 Markdown 卡片、按钮回调与内置短网址多线程下载代理),不再随 SDK 仓库分发
修复
- INVALID_SESSION 可续传标记失效:op=9 的
d是布尔值(true=可 RESUME),此前参数注入回退把 d 字典化成{}(恒 falsy),任何 INVALID_SESSION 都会清空会话——RESUME 续传永远走不到,断线期间本可回放的消息丢失。现协议帧不再经参数注入,WebsocketProtocol直接读取d的真值,resumable=true正确保留会话(协议帧处理测试覆盖) - 会话重置时序:
INVALID_SESSION的会话清空此前依赖分发泵在重连前消费掉该事件(慢 handler 会推迟它,导致带着已死 session_id 去 RESUME);现在连接器在break重连前同步调用协议处理器完成重置 - token 请求失败时抛出
TokenError(携带 QQ 返回的code/message),不再抛出掩盖真实原因的KeyError: 'access_token' - 修正被动回复
event_id的指引:官方要求取网关帧最外层的 id(Event.event_id),事件体里的裸 UUID(Interaction.id等)不能作event_id,此前文档与 docstring 的说法有误 - 响应
Content-Type非 JSON 时不再抛ContentTypeError(个别接口如回应互动返回 200 text/plain +{}body);JSON 解析统一切换 ujson - 文档站点子页面左侧目录不加载:docsify 进入子目录页面会先请求
<目录>/_sidebar.md、请求失败才回退根目录,而 Cloudflare Pages 对缺失路径回退返回index.html(状态 200),侧栏被当作 Markdown 解析成空白;index.html的 alias 现将docs/、examples/的_sidebar.md请求映射回根目录
0.1.0 - 2026-09-14
首个公开版本:QQ 官方机器人 Python SDK(Python 3.13 / asyncio / aiohttp)。
新增
- 双接入方式:WebSocket 长连接与 Webhook HTTP 回调,由
.env的CONNECTER切换,handler 写法完全一致 - 协议层:HELLO 鉴权(IDENTIFY/RESUME 自动切换)、apscheduler 定时心跳、READY 会话捕获、INVALID_SESSION 处理;断线指数退避重连 + 心跳/静默双看门狗,重连自动 RESUME 续传
- Webhook:Ed25519 验签、op=13 验证请求自动应答、60s TTL 事件去重
- 事件分发:
EventEmitter按 op/t 名路由,payload dataclass 类型化参数、Event封装与 dishka 依赖注入(Inject[T])三种 handler 写法,异常就地隔离 - 事件订阅:
intents.toml声明式控制订阅掩码 - 业务 API(
BotApi):v2 群/单聊消息收发与撤回、富媒体上传、群禁言;频道消息/表态/撤回、公告、精华、日程、频道禁言、频道与成员信息查询;HTTP ≥400 统一抛ApiError - 文档:使用指南、API 参考、架构说明与可运行示例(
examples/) - 测试:纯逻辑单测 + 假网关 WebSocket 端到端 + Webhook 验签/去重端到端