feat(channels): QQ 非消息事件全枚举投递 + systemnotice 补 request/interaction/unknown 类
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 适配器不再整类丢弃:notice/request/meta_event/message_sent/未知 post_type/JSON 解析失败( decode_error ) 一律归一化投递并留痕
- notice 映射补 group_ban/sub_type 全枚举,未知类型走 NoticeUnknown 兜底(fail-safe 只登记、绝不猜、绝不放行)
- 不投影 flag/comment/file/duration;归一化 DEBUG 只记结构(脱敏,变异实测可打红)
- 传输层端到端已覆盖(真实 http+server 上报路径:加好友请求/心跳/未知类型)
- ⚠️ 主人 2026-10-05 决定「先不连 QQ」(风控)⇒ 本片为通用红线(绝不静默丢弃)+ 现成证据链,QQ 侧后续增量工作暂停
This commit is contained in:
AskaEth committed 2026-10-05 13:47:27 +08:00
1 parent ec3f6e810a
commit ba17820040
9 files changed
+1315 -193

No files matched your search

+14 -3
View File
@@ -11,7 +11,9 @@ package qq_obv11
//
// 三种形态的帧都在覆盖范围内,因为「没上报」和「上报了非消息事件」必须能区分:
// - `post_type=message`:真消息(本次排查要找的就是它缺不缺席);
// - `post_type=meta_event`:OneBot 心跳/生命周期,是「连接还活着」的正面证据;
// - `post_type=notice` / `request` / `meta_event` / 未知类型:非消息上报(design/26
// §3.1)——它们现在**一律投递**,这条日志随之覆盖各自的原生类型字段
// (notice_type / request_type / meta_event_type);
// - 空 `post_type`:OneBot 对我们自己 action 的回执(echo 响应)。**有等待者**的回执
// 被 routeActionResponse 消费(如 get_status 探活),没有等待者的散落回执才落这里。
//
@@ -28,8 +30,12 @@ import (
)
// logInboundFrame 为了排障打出本条入站帧的结构。字段刻意只用「有/无、哪一类、哪几个
// id」这类元数据:一次调用足以判断上游是否上报、上报的是哪类事件、群里那句 @ 是否带了
// at 段(以及 @ 的是谁)。
// id」这类元数据:一次调用足以判断上游是否上报、上报的是哪类事件(含 request /
// meta_event / 未知 post_type 的类型字段)、群里那句 @ 是否带了 at 段(以及 @ 的是谁)。
//
// 类型字段按 post_type 各有各的家(notice_type / request_type / meta_event_type),
// 三个都照实打出:只打 post_type 的话,「notice 里到底是 poke 还是 group_ban」在日志里
// 依然看不出来——那正是这次事故里要回答的问题。
func (a *Adapter) logInboundFrame(ev *obEvent) {
if ev == nil {
return
@@ -38,9 +44,14 @@ func (a *Adapter) logInboundFrame(ev *obEvent) {
a.log.Debug("inbound frame",
"post_type", ev.PostType,
"message_type", ev.MessageType,
"notice_type", ev.NoticeType,
"request_type", ev.RequestType,
"meta_event_type", ev.MetaEventType,
"sub_type", ev.SubType,
"self_id", formatID(ev.SelfID),
"user_id", formatID(ev.UserID),
"target_id", formatID(ev.TargetID),
"operator_id", formatID(ev.OperatorID),
"sender_id", senderIDOf(ev),
"group_id", formatID(ev.GroupID),
"segments", segments,