Commit Graph
21 Commits
Author SHA1 Message Date
snowy-yang 32bd7c13b5 fix: CI 去掉 setup-uv 的 enable-cache,部署工作流更名
首次 CI 实跑两条工作流均失败且匿名读不到日志:检测工作流 26s 即败、
落在早期步骤,优先怀疑 enable-cache 触发的 Actions 缓存服务问题
(Gitea 对该 API 的支持不完善,post 步骤常因此失败)。先摘除该选项
再观察;部署工作流顺带改显示名并借文件变更重新触发。
2026-09-24 20:25:50 +08:00
snowy-yang f45299f92c docs: README/CHANGELOG 同步——方式三改写、CI 与文档站部署说明
README 方式三示例从 event.user_id/group_id/content/message_id 改为
typed payload + 组件注入组合;开发节补 CI 与 Cloudflare Pages 部署
说明;CHANGELOG Unreleased 记录 Event 兼容属性移除(破坏性)、
Gitea Actions CI 与文档站部署三项。
2026-09-24 20:18:46 +08:00
snowy-yang 35e501aabb feat: Gitea Actions——ruff/pyright 检测与 Cloudflare Pages 文档自动部署
- .gitea/workflows/ci.yml:push(main)/PR/手动触发,setup-uv 装依赖
  后运行与本地一致的 uv run ruff check src tests examples + uv run
  pyright;故意未加 ruff format --check(存量 5 文件格式漂移会打红)
- .gitea/workflows/deploy-docs.yml:main 的文档文件变更时自动部署,
  经 CLOUDFLARE_API_TOKEN/CLOUDFLARE_ACCOUNT_ID secrets 鉴权
- scripts/deploy_docs.sh:组装纯文档 dist/(不含源码,排除
  __pycache__)后 wrangler Direct Upload 上传——CF Pages 的 Git 直连
  只支持 GitHub/GitLab,Gitea 仓库只能走 Direct Upload;新版
  wrangler 的 pages deploy 不再自动建项目,脚本先幂等 create

站点 https://qqbotsdk-docs.pages.dev 已上线。.wrangler/ 缓存与
dist/ 产物入 .gitignore。
2026-09-24 20:18:39 +08:00
snowy-yang 7e7dcbaa94 refactor!: Event 收敛为推送信封封装,删除字段名兼容属性
删除 Event 的 user_id/group_id/content/message_id 四个属性:它们是
"兼容群(openid)与频道两套字段名"的映射层,其中 user_id 还带着
旧版群消息 payload 顶层 from_user_id 的回退(现行格式发言人标识在
author 里,该回退永远轮不到)。Event 只保留信封语义:
.raw/.data/.typed/.type/.event_id,事件体字段一律标注 payload
dataclass(如 msg: GroupAtMessage)获取解析结果。

破坏性变更:event.group_id → msg.group_openid、event.message_id →
msg.id,以此类推;被动回复凭据分工不变——消息事件用事件体 d.id
(msg_id),其余事件用信封顶层 event_id。

迁移:examples/group_bot 入群欢迎改标注 GroupAddRobot(已确认
MusicBot 未使用被删属性);GUIDE/API 同步删除对应属性行;测试
重写为信封语义断言 + typed 解析断言。

107 测试全绿,ruff/pyright 干净。
2026-09-24 20:18:29 +08:00
snowy-yang 32d9438d31 refactor: 拆分 receive_helper——心跳联动收进 _Heartbeat,等待与单帧路由各自成方法(圈复杂度 16→7) 2026-09-23 21:09:22 +08:00
snowy-yang 15e1f5e303 feat: OpenAPI 全量对齐——对照新旧两版官方文档补齐全部接口与事件
- 新增 panel 域:指令面板(斜线指令)增删改查/关联对象 + /v2/menu 全局自定义菜单
- 新增 group 域:群信息/成员/机器人群内状态、入群申请列表与审批、群黑名单、
  群禁言状态、入群自动审批策略(增删改查/执行/白名单)
- v2 补充:流式单聊消息、群/单聊大文件分片上传(预上传+分片确认)、分享链接
- guild 补充:子频道增删改、机器人频道列表、身份组 CRUD 与成员授予/收回、
  成员列表/踢出、全员与批量禁言、接口权限、消息频率设置
- channel 补充:子频道用户/身份组权限、频道私信(建会话/发信/撤回)
- 新增 audio 域(播放控制、上/下麦)与 forum 域(帖子增删查)
- 事件补齐 GROUP_MEMBER_ADD/REMOVE、GROUP_JOIN_REQUEST、SUBSCRIBE_MESSAGE_STATUS;
  Intent 与 intents.toml 补 GROUP_MEMBER_EVENT (1<<24) 分组(默认 false)
2026-09-23 17:46:37 +08:00
snowy-yang 13a1d971be feat: 连接寿命到点前主动重连(服务端约 1 小时会无预警断开)
实测 QQ 服务端在一个连接建立约 1 小时后会自己断开。此前只能等它断:
要么被看门狗判成静默超时、要么等读写失败,都算"异常断开",观感与恢复
路径都不理想。现改为临到期前主动收尾重连——是正常断开 + RESUME 续传,
期间消息由网关补发。

实现:receive_helper 记录寿命截止点(_SERVER_LIFETIME - _RECONNECT_MARGIN,
默认 60min - 10min = 50min),收帧超时取"静默阈值"与"寿命余量"两个截止里
的先到者——静默过久仍抛 TimeoutError 交给看门狗,寿命到点则 return 正常
结束本轮连接,run() 见存活超过 _STABLE_SECONDS 便立刻重连(沿用 b37a53f
的"健康断开零等待"路径),RESUME 带上 session_id 续传。

_connect 的存活秒数天然 >= 50min > _STABLE_SECONDS,主动重连必被判定为
健康断开,无需额外标志位。

测试:新增 test_proactive_reconnect_before_server_lifetime(真实 ws 端到端,
把寿命阈值调到 0.5s 让这一刻立即发生);已验证把寿命阈值关掉后该测试会
超时失败,不是空转到通过。另给 tests/test_reconnect.py 补 CHANGELOG 条目
(b37a53f 漏记),并同步 connecter 模块 docstring / ARCHITECTURE / GUIDE。

90 测试全绿,ruff check/format + pyright 干净。
2026-09-21 18:28:53 +08:00
snowy-yang b37a53f6e0 feat: 重连不再等待并精简连接日志,失败才记断开与退避
- 连接曾稳定存活(>= _STABLE_SECONDS)而被服务端要求重连时立刻重连、
  不等待也不记日志:这期间事件由网关按 RESUME 补发,等待只是徒增盲区
- 仅在连接未能稳定存活(握手失败/看门狗超时/限流/抖动)时才记
  "WebSocket 已断开"并按指数退避重试
- 去掉常规的「WebSocket 已连接/已断开」info,只保留「发送 RESUME」等
  有信息量的行,消除每小时一次的日志噪音
- 新增 tests/test_reconnect.py:健康断开零等待零日志、失败退避翻倍、
  限流 60s、抖动不重置退避
2026-09-21 18:25:16 +08:00
snowy-yang 1552c5207b refactor: 工程化收尾——去 pyee、预计算解析计划与派生密钥、命名与接口一致化
以「语义可读、逻辑清晰、减少心智负担」为目标,不追求极致速度:

- **移除 pyee 依赖**:EventEmitter 改为内置的 `事件名 → handler 列表` 注册表
  (on() 追加、emit() 顺序遍历)。SDK 用不到 pyee 的 once/new_listener/错误
  事件机制,去掉后少一个依赖、分发语义一眼可读。注册语义不变(同函数重复
  注册只生效一次),并补测试锁定「重复注册只生效一次」「按注册顺序执行」。
  破坏性:once/remove_listener 等装饰性 API 不再提供。

- **去掉热路径上的重复计算**(纯缓存,不改语义):
  · payloads 按类缓存解析计划(字段名 + 是否嵌套 Author),不再每次事件重跑
    dataclasses.fields 与联合类型解包;未收录字段照旧忽略(新增回归测试,
    线上 payload 会带 author.union_openid 这类字段)。实测 7.5µs → 2.5µs。
  · crypto 按 AppSecret 缓存派生私钥(每个 webhook 请求都要验签,派生一次
    约 40µs);公钥派生只差 3µs,不再多缓存一层。

- **命名与接口一致性**:
  · WebsocketConnecter.reply_helper → send_helper(它从出站缓冲取帧发送,
    原名会让人误以为还有 reply 队列;model.py 的对应注释同步)
  · Connecter 由 typing.Protocol 改为 abc.ABC,与 BaseProtocol 风格统一

- **webhook 去重改摊还清理**:过期判定按当前 id 做(O(1),且 TTL 外重发的
  同一 id 仍按新事件处理——原先的写法在阈值清理下会误判为重复,已由测试
  抓出并修正),整表清过期项只在表长到阈值时执行,不再每请求 O(n) 扫描。

测试 85 全绿(新增未知字段/解析计划缓存/注册语义/去重摊还与 TTL 过期),
ruff check/format + pyright 干净。文档 ARCHITECTURE/API/CHANGELOG 同步。
2026-09-20 22:45:34 +08:00
snowy-yang 1a7111973b docs: 修正 README 架构图与 API 模块表(协议分流后残留) 2026-09-20 22:16:05 +08:00
snowy-yang 4d19db24e1 refactor!: EventQueue 收敛为单向业务事件通道,出站缓冲归 ws 适配器
协议分流后 reply_queue 只剩 WebsocketConnecter 一个使用者(emitter 不再
回流应答、webhook 完全不用),它已不是"连接层与分发层之间的通道",而是
ws 私有的出站缓冲,却仍挂在共享 EventQueue 上——queue.py 的模块注释
"连接层与分发层之间唯一的跨任务通道"对 reply_queue 不再成立。

删除 EventQueue.reply_queue 与 put_reply/get_reply,EventQueue 只承载
业务事件(op=0,单向)。ws 的出站帧改由 WebsocketConnecter 私有
_outbound 队列承载,heartbeat_helper 与协议应答生产者投递、reply_helper
单任务发送——维持 aiohttp ws 单写者不变量,生产者也不会被发送阻塞。

破坏性变更:EventQueue.reply_queue / put_reply / get_reply 不再存在
(这两个方法本就只由 SDK 内部使用,非用户面)。

文档同步 queue.py 注释、ARCHITECTURE(图与心跳/应答路径)、API 的
EventQueue 说明、CHANGELOG;测试 80 全绿,ruff/pyright 干净。
2026-09-20 22:02:07 +08:00
snowy-yang 7accbd1ead refactor!: 协议帧与业务事件分流,各接入方式配专有协议处理器
BaseProtocol 从 emitter.py 移入新模块 protocol.py,接口由 register(emitter)
改为 async on_frame(payload)(返回需回送的响应体);协议处理器不再注册进
emitter,而由 run_loop 注入对应的 connecter,由适配器逐帧调用。

破坏性变更:只有 op=0 的业务事件进事件队列交给 EventEmitter 分发,
HELLO/READY/INVALID_SESSION/op=13 等协议帧在连接器内就地处理——用户 handler
不再收到协议帧(此前 HELLO/INVALID_SESSION 等会路由到 handler)。
EventEmitter.register_protocol()/handle() 与 emitter 持有协议层的机制删除,
协议类不再用 register() 注册 handler。

附带修掉一处时序耦合:INVALID_SESSION 的会话重置此前依赖分发泵在重连前
消费掉该事件(慢 handler 会推迟它,导致带着已死 session_id 去 RESUME),
现在连接器在 break 重连前同步完成重置。

同时做的事件流优化:
- emit() 先取事件名、无 listener 时不构造 Event(未订阅事件省一次对象与拷贝)
- Event.data 改为惰性拷贝(只用 .typed/.raw 的 handler 不再触发 dict(d))
- ws 重连收尾 await 被取消的 reply/heartbeat 任务,避免异常无人取用
- EventQueue.put_event/put_reply 去掉多余的 return await

测试 80 全绿(新增协议帧处理测试组、ws 集成断言协议帧不触达 handler),
ruff/pyright 干净。文档 ARCHITECTURE/API/GUIDE/README/CHANGELOG 同步。
2026-09-20 21:57:57 +08:00
snowy-yang 71cbfdaf5d feat!: intents 只按大类订阅、群消息全量模式与事件流优化
破坏性变更:intents.toml 订阅粒度收敛到 Intents 大类(分组)——
配置从「[分组] 表内逐事件开关」改为「分组名 = true/false」。网关只接受
分组位掩码,组内事件本就无法单独订阅,此前的逐事件开关仅是「组内任一
为 true 即订阅整组」的装饰。旧格式不再兼容,与拼错的分组名、非布尔值
一样在启动时直接抛 ValueError,而非静默少订阅。

新增:
- GROUP_MESSAGE_CREATE 事件接收(群消息全量模式,与 GROUP_AT_MESSAGE_CREATE
  共用 GroupAtMessage);GroupAtMessage 补齐 message_type/message_scene/
  attachments/mentions/ark_data/msg_elements 字段

优化:
- 形参注入缓存化:合并缓存 handler 签名与类型标注解析(此前每条事件都重跑
  inspect.signature/get_type_hints),services 命中改为每条事件实时判定,
  保证「先注册 handler、后登记组件」的既有装配顺序不受影响
- EventQueue.put_event/put_reply 去掉多余的 return await
- model.NoneType 联合成员顺序调整(None 置于末尾)

文档同步 GUIDE/API/README/CHANGELOG 与示例;测试 76 全绿,ruff/pyright 干净。
2026-09-20 21:39:25 +08:00
snowy-yang 3237558128 feat!: api 拆包、心跳迁入传输层、限流重试与后台 handler 2026-09-20 21:26:55 +08:00
snowy-yang 750ed81d96 docs: 基于 docsify 搭建文档站点 2026-09-16 21:42:43 +08:00
snowy-yang 097bc96d8b feat: 按钮快捷构造、event_id 语义修正与 TokenError 2026-09-16 21:42:33 +08:00
snowy-yang cff0c142c2 refactor!: 移除 dishka,改为内置裸标注参数注入
- handler 形参直接标注组件类型(api: BotApi)即注入,解析为 emitter 内同步查表(payload dataclass / Event / services 登记类型 / 回退原始 dict)
2026-09-16 19:18:06 +08:00
snowy-yang 953d44f62e feat: Markdown 与内嵌按钮一等支持
- post_group_message/post_c2c_message 新增 markdown/keyboard 参数,自动置 msg_type=2(与 content 互斥)
2026-09-16 19:17:05 +08:00
snowy-yang 7a1b8ac691 docs: 补全使用指南、API 参考、可运行示例与更新日志 2026-09-14 21:05:50 +08:00
snowy-yang 2ee0184fc1 chore: 移除 botpy 参考文件的 gitignore 规则 2026-09-11 20:35:19 +08:00
snowy-yang 0c364bf803 qqbotsdk: QQ 官方机器人 SDK(协议层/DI/业务 API/测试/文档) 2026-09-11 19:49:17 +08:00