fix: 死锁修复、管理员权限集中管控、群聊频率控制、表情映射修复

thinker.go: 移除所有 defer t.muUnlock() 持锁跨阻塞调用的模式,消除8处死锁点
- performThink: defer→立即解锁,LLM调用不再持锁
- lightThinkLoop: defer在for循环内→第2次迭代自死锁
- resetSilenceTimer: defer持锁调performThink
- UpdatePresence: defer持锁调time.Sleep+performThink
- storeThought: defer+panic→锁泄露; 移除extractProactiveMessage嵌套锁

is_admin三层防御:
- synthesizer: 系统提示词注入管理员/非管理员身份标签
- iot_provider: 非管理员直接拒绝IoT操作
- plugin-manager: ToolDefinition.AdminOnly自动拦截,集中管控

群聊优化:
- group_ambient: 强化审查指令,【不发送】自审查标签
- 群聊间隔4s→3s,最多2条/轮
- 工具失败也推跟进消息,避免沉默

平台桥接:
- 日志文件名使用适配器唯一标识符(ConfigName)
- QQ表情映射替换为官方116条目数据
- CQ表情保留名称/ID
This commit is contained in:
2026-06-28 20:22:09 +08:00
parent 236dbfcc92
commit 44048b333a
14 changed files with 491 additions and 72 deletions
@@ -805,6 +805,141 @@ var cqSimplifyMap = map[string]string{
"video": "[视频]",
"file": "[文件]",
}
// qqFaceName maps QQ face/emoji IDs to their Chinese names.
// Source: Official QQ Bot API Emoji List (https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html)
var qqFaceName = map[string]string{
"4": "得意",
"5": "流泪",
"8": "睡",
"9": "大哭",
"10": "尴尬",
"12": "调皮",
"14": "微笑",
"16": "酷",
"21": "可爱",
"23": "傲慢",
"24": "饥饿",
"25": "困",
"26": "惊恐",
"27": "流汗",
"28": "憨笑",
"29": "悠闲",
"30": "奋斗",
"32": "疑问",
"33": "嘘",
"34": "晕",
"38": "敲打",
"39": "再见",
"41": "发抖",
"42": "爱情",
"43": "跳跳",
"49": "拥抱",
"53": "蛋糕",
"60": "咖啡",
"63": "玫瑰",
"66": "爱心",
"74": "太阳",
"75": "月亮",
"76": "赞",
"78": "握手",
"79": "胜利",
"85": "飞吻",
"89": "西瓜",
"96": "冷汗",
"97": "擦汗",
"98": "抠鼻",
"99": "鼓掌",
"100": "糗大了",
"101": "坏笑",
"102": "左哼哼",
"103": "右哼哼",
"104": "哈欠",
"106": "委屈",
"109": "左亲亲",
"111": "可怜",
"116": "示爱",
"118": "抱拳",
"120": "拳头",
"122": "爱你",
"123": "NO",
"124": "OK",
"125": "转圈",
"129": "挥手",
"144": "喝彩",
"147": "棒棒糖",
"171": "茶",
"173": "泪奔",
"174": "无奈",
"175": "卖萌",
"176": "小纠结",
"179": "doge",
"180": "惊喜",
"181": "骚扰",
"182": "笑哭",
"183": "我最美",
"201": "点赞",
"203": "托脸",
"212": "托腮",
"214": "啵啵",
"219": "蹭一蹭",
"222": "抱抱",
"227": "拍手",
"232": "佛系",
"240": "喷脸",
"243": "甩头",
"246": "加油抱抱",
"262": "脑阔疼",
"264": "捂脸",
"265": "辣眼睛",
"266": "哦哟",
"267": "头秃",
"268": "问号脸",
"269": "暗中观察",
"270": "emm",
"271": "吃瓜",
"272": "呵呵哒",
"273": "我酸了",
"277": "汪汪",
"278": "汗",
"281": "无眼笑",
"282": "敬礼",
"284": "面无表情",
"285": "摸鱼",
"287": "哦",
"289": "睁眼",
"290": "敲开心",
"293": "摸锦鲤",
"294": "期待",
"297": "拜谢",
"298": "元宝",
"299": "牛啊",
"305": "右亲亲",
"306": "牛气冲天",
"307": "喵喵",
"314": "仔细分析",
"315": "加油",
"318": "崇拜",
"319": "比心",
"320": "庆祝",
"322": "拒绝",
"324": "吃糖",
"326": "生气",
}
// resolveFaceName returns the display name for a face CQ code.
// Uses name field if present (NapCat array format), otherwise looks up the face ID in qqFaceName map.
func resolveFaceName(match string) string {
if name := extractCQParam(match, "name"); name != "" {
return name
}
if id := extractCQParam(match, "id"); id != "" {
if name, ok := qqFaceName[id]; ok {
return name
}
return id
}
return ""
}
// simplifyCQCodes replaces [CQ:type,...] codes with human-readable labels.
func simplifyCQCodes(s string) string {
@@ -826,6 +961,13 @@ func simplifyCQCodes(s string) string {
}
return "[卡片消息]"
}
if typ == "face" {
faceName := resolveFaceName(match)
if faceName != "" {
return "[表情 " + faceName + "]"
}
return "[表情]"
}
if label, ok := cqSimplifyMap[typ]; ok {
return label
}
@@ -253,7 +253,7 @@ func (h *BridgeHandler) sendProactive(w http.ResponseWriter, r *http.Request) {
if msgType == "private" {
chID = req.UserID
}
h.logFn(req.Platform, chID, "Cyrene", fullMsg, true)
h.logFn(adapterName, chID, "Cyrene", fullMsg, true)
}
}
if sendErr != nil {
@@ -29,13 +29,9 @@ func (h *LogHandler) handleLogs(w http.ResponseWriter, r *http.Request) {
return
}
// Resolve platform type from config name (e.g. "obv11-home" → "obv11").
// Use the name directly as the log key. Each config has its own log file
// named by its unique identifier (e.g., "obv11-main.log").
platform := name
if h.store != nil {
if cfg, err := h.store.Get(name); err == nil && cfg.Platform != "" {
platform = cfg.Platform
}
}
limit := 100
if l := r.URL.Query().Get("limit"); l != "" {