Commit c7863acf867c0f089df7a2fff7e9db5a2007a927
1 parent
fd8d30a9
fix: 授权指令支持引用回复(type 57 appmsg)
症状:"同意 宜创AI全员群" 用引用回复发出后没被识别,pending 没清。 根因:WeChat 引用回复存成 type=57 appmsg XML,真正的回复文本在 <title> 里、被引用的原消息在 <refermsg> 里。parseApprovalCommand 拿到整段 XML 匹配不上 ^(同意|...) 正则。 修复: - parseApprovalCommand 检测 <appmsg>/<refermsg>,提取 <title> 作为 回复文本(指令动词),提取 <refermsg> 作为 quotedContext - handleApproval 目标匹配优先级:显式 target → quotedContext 里出现的 pending displayName(引用哪条授权请求就同意哪个)→ 最近一个 pending 实测:引用回复"同意"(refermsg 含"宜创")→ [AUTH] Approved 宜创AI全员群 → 加白名单 + 自动回复 pending 的 "@小火子 在吗" Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Showing
1 changed file
with
24 additions
and
5 deletions
server/services/wechat/auto-reply.js
| ... | ... | @@ -587,15 +587,25 @@ function escapeRegex(str) { |
| 587 | 587 | */ |
| 588 | 588 | function parseApprovalCommand(content) { |
| 589 | 589 | if (!content) return null; |
| 590 | + let replyText = content; | |
| 591 | + let quotedContext = ''; | |
| 592 | + // 引用回复(type 57 appmsg):真正的回复文本在 <title> 里, | |
| 593 | + // 被引用的原消息内容在 <refermsg> 里(用来判断"同意哪条") | |
| 594 | + if (content.includes('<appmsg') || content.includes('<refermsg>')) { | |
| 595 | + const tm = content.match(/<title>([\s\S]*?)<\/title>/); | |
| 596 | + if (tm) replyText = tm[1]; | |
| 597 | + const rm = content.match(/<refermsg>([\s\S]*?)<\/refermsg>/); | |
| 598 | + if (rm) quotedContext = rm[1]; | |
| 599 | + } | |
| 590 | 600 | // 群消息前缀剥离(owner 私聊一般没有,保险起见) |
| 591 | - let t = content; | |
| 601 | + let t = replyText; | |
| 592 | 602 | const ci = t.indexOf(':\n'); |
| 593 | 603 | if (ci > 0 && ci < 30) t = t.substring(ci + 2); |
| 594 | 604 | t = t.trim(); |
| 595 | 605 | const m = t.match(/^(同意|授权|批准|approve|allow|yes|拒绝|忽略|不同意|deny|no)\s*(.*)$/i); |
| 596 | 606 | if (!m) return null; |
| 597 | 607 | const approve = /同意|授权|批准|approve|allow|yes/i.test(m[1]); |
| 598 | - return { approve, target: (m[2] || '').trim() }; | |
| 608 | + return { approve, target: (m[2] || '').trim(), quotedContext }; | |
| 599 | 609 | } |
| 600 | 610 | |
| 601 | 611 | // ═══════════════════════════════════════════════════════════════ |
| ... | ... | @@ -973,14 +983,17 @@ class AutoReplyBot { |
| 973 | 983 | |
| 974 | 984 | // 处理 owner 的授权指令 |
| 975 | 985 | async handleApproval(cmd) { |
| 976 | - const { approve, target } = cmd; | |
| 986 | + const { approve, target, quotedContext } = cmd; | |
| 977 | 987 | const pendings = this.whitelist.pendingTalkers(); |
| 978 | 988 | if (pendings.length === 0) { |
| 979 | 989 | await this.sendPlainTo(CONFIG.ownerWxid, CONFIG.ownerDisplayName, '当前没有待授权的请求。'); |
| 980 | 990 | return; |
| 981 | 991 | } |
| 982 | 992 | |
| 983 | - // 选定目标:指定了就按 wxid / 昵称 / senderName 匹配,没指定取最近一个 | |
| 993 | + // 选定目标,优先级: | |
| 994 | + // 1. 显式 target("同意 宜创AI全员群")按 wxid/昵称/senderName 匹配 | |
| 995 | + // 2. 引用回复的被引用内容里出现哪个 pending 的 displayName("同意"引用了某条授权请求) | |
| 996 | + // 3. 都没有 → 取最近一个 pending | |
| 984 | 997 | let talker = null; |
| 985 | 998 | if (target) { |
| 986 | 999 | talker = pendings.find(t => { |
| ... | ... | @@ -988,7 +1001,13 @@ class AutoReplyBot { |
| 988 | 1001 | return t === target |
| 989 | 1002 | || p.displayName === target |
| 990 | 1003 | || p.senderName === target |
| 991 | - || (p.displayName && p.displayName.includes(target)); | |
| 1004 | + || (p.displayName && (p.displayName.includes(target) || target.includes(p.displayName))); | |
| 1005 | + }); | |
| 1006 | + } | |
| 1007 | + if (!talker && quotedContext) { | |
| 1008 | + talker = pendings.find(t => { | |
| 1009 | + const dn = this.whitelist.pending[t].displayName; | |
| 1010 | + return dn && quotedContext.includes(dn); | |
| 992 | 1011 | }); |
| 993 | 1012 | } |
| 994 | 1013 | if (!talker) talker = pendings[pendings.length - 1]; | ... | ... |