Files
AskaEth d532eb3fcd
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(session,qq-obv11): 修「判主好了、会话车道没跟上」(QQ 私聊没镜像进主会话)
真因(agent-221 用三条独立证据推翻了「SenderID 为空」的假设):ev.UserID 有值;
owner.yaml 的 qq 条目是 10:37:57 才加的(此前那几条判陌生人属**正确的 fail-closed**);
而热重载成功后仍不对 —— 因为 internal/channel/session/manager.go 的 GetOrCreate
对**已绑定的渠道直接返回缓存会话**:判主门槛用新 actor 会过(所以她会回话),
但**车道用的是缓存的旧会话**(Owner=false ⇒ 不镜像进 Web 主会话、MemWritable=false、
落盘 actor_role 永不更新)。

修:缓存命中但 ActorRole 与本次解析不一致 ⇒ rebindLocked 刷新车道 + 同步落盘
(updateActorRoleSQL);persist 的 INSERT OR IGNORE 后读回落盘车道、不一致就地纠正
(覆盖上一轮进程留下的旧行);落盘失败只 WARN(不因记账失败丢主人消息);
**身份变化不搬家、不换 session id**;车道只来自 ResolveActor 的结构化白名单判定。

QQ 适配器(防御加固,非本次真机 bug 的修复):senderIDOf 兜底链 顶层 user_id →
sender.user_id;并用完整论证钉住「**群聊绝不能用 ChannelID 兜底**」(否则群里任何人
可顶群号冒充主人);顺手体检:自己发的消息(post_type=message_sent)与 meta_event
已被 handlePayload 过滤。为守 ≤500 行把 id/字符串小工具拆到 ids.go。

测试:session/ownerlane_test + qq/ownerlane_test 新增用例(含「群号等于主人号时
陌生人不得借群号冒充」「群聊发送者 id 缺失时 SenderID 必须为空而非群号」「白名单
热重载后车道立刻刷新且 session id 不变」「白名单移除后立即退回,不是单向提权」
「跨进程继承的旧行被就地纠正」)。
变异 5 条(去 sender.user_id 兜底 / 群聊用群号当 SenderID / 命中却判 unknown /
还原缓存直返 / 去掉旧行纠正),每条先确认可编译、贴红行、还原复绿(md5 逐字节校验)。
2026-10-05 10:53:05 +08:00

56 lines
1.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package qq_obv11
// 本文件承载 OneBot 事件里 id 的解析与几个轻量字符串工具,从 message.go 拆出
// (AGENTS §10 文件 ≤500 行)。判主用的 SenderID 解析规则就在这里:见 senderIDOf。
import (
"fmt"
"strconv"
)
// senderIDOf 解析本条消息的发送者 id(判主用的那个字段)。OneBot v11 规定消息事件
// 在**顶层**带 user_id(obEvent.UserID),但实现之间有出入:有的只在 sender.user_id
// 里给。过去的实现只读顶层,顶层缺失/为 0 时 SenderID 就成了空串,而
// owner.IsOwner 对空 accountID 一律返回 false(fail-closed)——号码明明在白名单里
// 也永远判不成主人。这里给出兜底链:顶层 user_id → sender.user_id,两者都缺才是空。
//
// 群聊同样走这条链(发送者就是 user_id):群号**不是**发送者,绝不能作为它的兜底,
// 否则群里任何人都能顶着群号去撞白名单。见 toUnified 的 group/private 两个分支。
func senderIDOf(ev *obEvent) string {
if ev == nil {
return ""
}
if id := formatID(ev.UserID); id != "" {
return id
}
if ev.Sender != nil {
return formatID(ev.Sender.UserID)
}
return ""
}
// formatID 把 OneBot 的数字 id 归一成字符串;0(字段缺失)归一成空串,让
// 「没给」与「给了 0」在下游是同一种情况。
func formatID(id int64) string {
if id == 0 {
return ""
}
return strconv.FormatInt(id, 10)
}
func parseInt64(s string) (int64, error) {
if s == "" {
return 0, fmt.Errorf("empty id")
}
return strconv.ParseInt(s, 10, 64)
}
func firstNonEmpty(ss ...string) string {
for _, s := range ss {
if s != "" {
return s
}
}
return ""
}