From ec27d4eb4a85ecd460f54b34040707b950aa7323 Mon Sep 17 00:00:00 2001 From: yibo Date: Thu, 9 Jul 2026 23:30:32 +0800 Subject: [PATCH] feat(wechat-auto-reply): 环节2 读上下文 — 补进 wechat-db-decrypt skill 配方(不写模块) --- md/wechat-auto-reply-requirements.md | 9 ++++++--- wechat_plain/scripts/auto-reply/skill/wechat-db-decrypt-SKILL.md | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 wechat_plain/scripts/auto-reply/skill/wechat-db-decrypt-SKILL.md diff --git a/md/wechat-auto-reply-requirements.md b/md/wechat-auto-reply-requirements.md index 49c4618..dd7594d 100644 --- a/md/wechat-auto-reply-requirements.md +++ b/md/wechat-auto-reply-requirements.md @@ -60,9 +60,12 @@ - 群 @我:群消息 content 前缀 `wxid:\n`,需匹配我的昵称判 @ - 群引用我:看 refermsg 是否指向我的 svrid -### 环节 2 · 读取上下文 -- 从 `message` 表取该会话最近 N 条 + 发送者身份(`rcontact` 昵称/备注)。 -- **二期加分**:回链 lifelog `Person` 库做人物增强("这个人是谁、跟小火子什么关系")。一期不做。 +### 环节 2 · 读取上下文 —— ✅ **不写模块,走 skill 配方(2026-07-09)** +结论:Hermes 是有 shell+SQL 的 agent,已有 `wechat-db-decrypt` skill,环节2 无需新代码。 +- 给 skill 补了「读会话上下文」一节(`~/.hermes/skills/wechat-db-decrypt/SKILL.md`,仓库留档 `wechat_plain/scripts/auto-reply/skill/`):一次拷库查齐 ①发送者身份 `rcontact`(显示名优先级 conRemark>nickname>alias)②最近 N 条 `message` 历史(剥群 `wxid:` 前缀、type→占位符、只喂文本)③引用 refermsg 解析 + 群名;末尾给出喂 env3 的上下文骨架。 +- 真机验证配方查询产出干净上下文(身份+时间正序历史)。 +- 架构澄清:环节 3(生成)= Hermes 这个 LLM 本身;环节 4(策略)= 它的推理;只有环节 4/5 的「审核队列 + viewer 接发送」还需写代码。 +- **二期加分**:回链 lifelog `Person` 库做人物增强。一期不做。 ### 环节 3 · 生成回复 - Hermes(Kimi / 本地 llama-swap):**上下文 + SOUL.md → 回复**。 diff --git a/wechat_plain/scripts/auto-reply/skill/wechat-db-decrypt-SKILL.md b/wechat_plain/scripts/auto-reply/skill/wechat-db-decrypt-SKILL.md new file mode 100644 index 0000000..132db1b --- /dev/null +++ b/wechat_plain/scripts/auto-reply/skill/wechat-db-decrypt-SKILL.md @@ -0,0 +1,146 @@ +--- +name: wechat-db-decrypt +description: 端上解密打开微信 EnMicroMsg.db(SQLCipher),拿聊天/联系人/朋友圈原始数据。纯本地 su,不经 Mac/Frida。含 key 推导 + 多账号选库 + 已验证密钥缓存。 +version: 1.0 +category: communication +--- + +# WeChat DB Decrypt(微信数据库端上解密) + +微信的 `EnMicroMsg.db` 是 **SQLCipher 加密**的。这个 skill 让你在设备内直接算出密钥、拷库、解密只读打开, +拿到 `rcontact`(联系人)、`message`(聊天)、`SnsInfo`(朋友圈)等原始表。**纯 su + sqlcipher,不经 Mac、不用 Frida。** + +## When to Use +- 需要读微信联系人 / 聊天记录 / 朋友圈的**结构化原始数据**(不是截图 OCR) +- 给 lifelog / CRM 灌微信数据、做人物档案、群消息摘要 +- 任何"要查微信数据库"的任务 + +## 密钥推导公式(真机验证) + +``` +key = MD5( IMEI + uin ) 取 hex 前 7 位 +IMEI = "1234567890ABCDEF" # 微信默认值,本机适用 +uin = 从 shared_prefs 读 last_login_uin(当前登录账号) +SQLCipher 参数: cipher_compatibility = 1, cipher_page_size = 1024 +``` + +## ⭐ 已验证密钥缓存(2026-07-08,先查这里,命中就别重算) + +| uin | MicroMsg 目录 | key | 库大小 | 备注 | +|---|---|---|---|---| +| 257966415 | `ec4f423b316d41e9af5e8398155cf105` | `1986554` | ~90MB | **当前主账号**,已实测 720 表 / rcontact 42271 条 | + +> 只要 `last_login_uin` 还是 257966415,直接用 key `1986554`,跳过推导。 +> 换号了才需要走下面的"重新推导"。 + +## 选对账号目录(多账号必看) + +设备上 `MicroMsg/` 下可能有**多个账号目录**,目录名 = `MD5("mm" + uin)`。 +**不要用 `ls | head -1`**——那会按字典序选错账号。正确定位: + +```bash +BASE=/data/user/0/com.tencent.mm +UIN=$(su --mount-master -c "grep -hoE 'last_login_uin\">[0-9-]+' $BASE/shared_prefs/*.xml" | grep -oE '[0-9-]+' | head -1) +DIR=$(printf '%s' "mm${UIN}" | md5sum | cut -c1-32) # = 当前登录账号的目录名 +echo "uin=$UIN dir=$DIR" +``` + +## 完整流程(重新推导 + 打开) + +```bash +BASE=/data/user/0/com.tencent.mm +export PATH=/system/bin:/system/xbin:$PATH + +# 1. 读当前账号 uin,算目录、算 key +UIN=$(su --mount-master -c "grep -hoE 'last_login_uin\">[0-9-]+' $BASE/shared_prefs/*.xml" | grep -oE '[0-9-]+' | head -1) +DIR=$(printf '%s' "mm${UIN}" | md5sum | cut -c1-32) +KEY=$(printf '%s' "1234567890ABCDEF${UIN}" | md5sum | cut -c1-7) +echo "uin=$UIN key=$KEY dir=$DIR" + +# 2. su 拷出该账号的库到可读位置(含 wal/shm,保证最新数据) +su --mount-master -c "D=$BASE/MicroMsg/$DIR; cp \$D/EnMicroMsg.db /sdcard/en.db; \ + [ -f \$D/EnMicroMsg.db-wal ] && cp \$D/EnMicroMsg.db-wal /sdcard/en.db-wal; \ + [ -f \$D/EnMicroMsg.db-shm ] && cp \$D/EnMicroMsg.db-shm /sdcard/en.db-shm; \ + chmod 666 /sdcard/en.db*" + +# 3. sqlcipher 打开验证 + 查数据 +sqlcipher /sdcard/en.db </avatar/` 及独立库 + +--- + +## 读会话上下文(自动回复·环节2:给回复生成备料) + +拿到一条触发事件 `{talker, isGroup, sender(wxid), kind}`(来自 watcher 的 `incoming.jsonl`), +**一次拷库**即可查齐下面三块,拼成喂给 LLM 的上下文。别分三次拷库。 + +### 1. 发送者身份 +```sql +SELECT nickname, conRemark, alias FROM rcontact WHERE username=''; +``` +- 私聊:sender = talker;群聊:sender = 群消息 `wxid:` 前缀那个 +- **显示名优先级**:conRemark(备注) > nickname(昵称) > alias > wxid +- 群名:`SELECT nickname FROM rcontact WHERE username='';`(群的 nickname 就是群名) + +### 2. 最近 N 条会话历史 +```sql +SELECT isSend, type, createTime, content FROM message +WHERE talker='' ORDER BY msgId DESC LIMIT 20; -- 再按时间正序排回来 +``` +解析规则: +- `isSend=1` → 我(小火子)发的;`isSend=0` → 对方发的 +- 群消息 content 形如 `wxid_xxx:\n正文`,剥前缀取「发送者 wxid + 正文」,每个 wxid 再走身份查询解析显示名 +- `type`:`1`=纯文本;`3`=图片;`34`=语音;`43`=视频;`47`=表情;`49`=appmsg(链接/文件/引用/聊天记录);大 type + 内容含「撤回了一条消息 / 拍了拍」= 系统消息(忽略) +- 只把**文本**喂给 LLM;非文本给占位符 `[图片]`/`[语音]`/`[链接:标题]`,别把整坨 XML 塞进去 + +### 3. 引用原文(kind=group_quote_me,或消息含 ``) +type=49 且 appmsg `57`,`` 块内: +- `` / `` = 被引消息作者 wxid +- `` = 作者显示名 +- `` = 被引原文(群里被引消息也可能带 `wxid:` 前缀,同样剥) +把 `` 当作「对方在引用这句话回复你」。(真样例少,遇到先 dump 一条核对字段。) + +### 组装(喂环节3 的上下文骨架) +``` +[群] {群名} ← 群聊才有 +对方:{显示名}({备注或wxid}) +[引用] {被引作者}: {被引原文} ← 有引用才有 +最近对话: + {时间} {对方显示名}: {文本} + {时间} 我: {文本} + ... +本条:{对方显示名}: {正文} +``` + +### 注意 +- **官方号**(username 以 `gh_` 开头)= 公众号推送,不是真人,通常不回(策略层过滤) +- 群成员的「群昵称」(per-group) 在 chatroom 表的 displayname,比 rcontact.nickname 更贴切但要多查一次;MVP 先用 rcontact 的名字够用 + +## 说明 +- key 是**确定性**的:uin 不变 → key 不变。没有"独立 key 文件"存在别处,缓存表就是唯一的省算路径。 +- app 侧同逻辑源码在仓库 `lifelog-recorder/.../vision/WeChatDb.kt`(供交叉参考)。 -- libgit2 0.21.4