feat(channels): QQ 非消息事件全枚举投递 + systemnotice 补 request/interaction/unknown 类
- 适配器不再整类丢弃: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:
1 parent
ec3f6e810a
commit
ba17820040
9 files changed
+1315
-193
No files matched your search
@@ -123,11 +123,18 @@ func TestOwnerLaneRejectsAnonymousWhitelistEntry(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelfSentAndNonMessageFramesNotDispatched 覆盖第三种事件:自己发出的消息
|
||||
// (OneBot v11 的 post_type=message_sent)与任何非 message 上报都不得进流水线。
|
||||
// message_sent 的 user_id 是**对端**:一旦被当成入站,机器人会把自己刚发出的消息
|
||||
// 当对方的来信再回一遍(回声环),判主/会话键也会跟着乱。
|
||||
func TestSelfSentAndNonMessageFramesNotDispatched(t *testing.T) {
|
||||
// TestSelfSentAndNonMessageFramesDeliveredButNeverChat 覆盖主人 2026-10-05 的裁决与
|
||||
// 它**必须同时保住的那条安全性质**(design/26 §3.1 / §5.5):
|
||||
//
|
||||
// - 自己发出的消息回声(post_type=message_sent)、元事件与非消息上报现在**要投递**
|
||||
// (「绝不整类丢弃」);
|
||||
// - 但它们必须带着 ContentType=ContentAction 投递,并在归一化里被判为**可忽略**——
|
||||
// 于是桥接层的传入前审查把它们**只登记**,绝不进聊天路径。否则 message_sent 的
|
||||
// 对端会被当成来信再答一遍(回声环),判主/会话键也会跟着乱。
|
||||
//
|
||||
// 换句话说:防回声环的手段从「适配器丢弃」换成了「结构化标注 + 审查层仅登记」,
|
||||
// 这既保住了不回自己,又让「上游推过什么」留得下痕。
|
||||
func TestSelfSentAndNonMessageFramesDeliveredButNeverChat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
frames := []struct {
|
||||
@@ -150,15 +157,32 @@ func TestSelfSentAndNonMessageFramesNotDispatched(t *testing.T) {
|
||||
for _, f := range frames {
|
||||
f := f
|
||||
t.Run(f.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
a := NewAdapter(Config{})
|
||||
var delivered []adapter.UnifiedMessage
|
||||
// handlePayload → dispatch 是同步的,因此无需 sleep 即可断言「没投递」。
|
||||
// handlePayload → dispatch 是同步的,因此无需 sleep 即可断言。
|
||||
a.SetMessageHandler(func(_ context.Context, m adapter.UnifiedMessage) {
|
||||
delivered = append(delivered, m)
|
||||
})
|
||||
a.handlePayload(context.Background(), []byte(f.raw))
|
||||
if len(delivered) != 0 {
|
||||
t.Fatalf("%s 被投递进流水线: %+v", f.name, delivered)
|
||||
if len(delivered) != 1 {
|
||||
t.Fatalf("%s 投递条数 = %d, want 1(非消息上报不许被整类丢弃)", f.name, len(delivered))
|
||||
}
|
||||
msg := delivered[0]
|
||||
if msg.ContentType != adapter.ContentAction {
|
||||
t.Fatalf("%s 未带结构化系统事件标记:%q", f.name, msg.ContentType)
|
||||
}
|
||||
// 回声的会话键必须是**对端**:拿 user_id(她自己)当键会建出一个
|
||||
// 「她与自己」的私聊,把她刚发的话再答一遍。
|
||||
notice, ok := a.NormalizeSystemNotice(msg)
|
||||
if !ok {
|
||||
t.Fatalf("%s 无法归一化", f.name)
|
||||
}
|
||||
if !notice.Ignorable {
|
||||
t.Fatalf("%s 未判为可忽略(会进聊天路径 ⇒ 回声环):%+v", f.name, notice)
|
||||
}
|
||||
if notice.Kind != adapter.NoticeUnknown && notice.SafeText != "" && msg.Content != "" {
|
||||
t.Fatalf("%s 的平台原文进了正文:%q", f.name, msg.Content)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user