diff --git a/lifelog-recorder/HANDOFF.md b/lifelog-recorder/HANDOFF.md index c0d8630..145d02d 100644 --- a/lifelog-recorder/HANDOFF.md +++ b/lifelog-recorder/HANDOFF.md @@ -239,6 +239,27 @@ Tier-0 PhotoFaceWorker (15min, battery-not-low) Tier-1 PhotoVlWorker (1 > 全相册扫描没有 sqlite3 在设备上:dev 改 photos 表用 `adb exec-out run-as com.lemon.lifelog cat databases/lifelog.db{,-wal,-shm}` 拉到 Mac,`sqlite3 'PRAGMA wal_checkpoint(TRUNCATE);'` 合并 WAL 后改,再 `run-as sh -c 'cat > databases/lifelog.db'` 推回 + 删 wal/shm。 +### 微信朋友圈:端上 root 直读(v0.8,2026-06-09,纯本地不经 Mac) + +人物档案「⬇ 下载图片」一键拉该联系人的**头像 + 朋友圈(图 + 文字)**到端上。**全程 app 内 su,不依赖 Mac 导出器**(对比:联系人/聊天仍走 Mac 解密 EnMicroMsg.db→wx_enroll,因为那库 SQLCipher 加密;朋友圈库恰好明文,所以能端上做)。 + +**代码**:`vision/WeChatSnsImporter.kt`(读取核心)+ `PhotologViewModel.downloadWeChatImages()`(落库)+ `PersonProfileScreen` 的「⬇ 下载图片」按钮 + 朋友圈 tab 的 `MomentRow`(渲染)。 + +**微信侧数据(root 才能读,都在 `com.tencent.mm` 私有目录)**: +| 路径 | 内容 | +|---|---| +| `/data/user/0/com.tencent.mm/MicroMsg//SnsMicroMsg.db` | **明文 SQLite**(不加密!)。`SnsInfo` 表:`userName`(发布者 wxid) + `createTime`(秒) + `content`(protobuf BLOB,含正文 + 各图 media id 十进制串 + CDN URL) | +| `/data/user/0/com.tencent.mm/cache//sns///snsb_` | **明文大图 JPEG**(`snst_`=缩略 PNG,`sight_`=视频)。`` 同 MicroMsg 用户目录哈希。**只有在微信里浏览过的朋友圈才有缓存** | + +**读取流程**(`importMomentsFor(ctx, wxid, destDir)`): +1. 一条 `su --mount-master -c`(**两个 root 坑见 §七**:mount namespace + 空 PATH)把 `SnsMicroMsg.db`(+wal/shm) 和所有 `snsb_*` 拷进 app 私有目录,`chown` 给本 app uid。 +2. 系统 `SQLiteDatabase` 只读打开拷贝,`SELECT createTime, content FROM SnsInfo WHERE userName=?`。 +3. **归属**:把缓存文件名里的 media id(`snsb_` 的 ``)当**子串**在该条 `content`(按 ISO-8859-1 字节)里查——命中即这张图属于这条朋友圈。真机实测命中率 100%。 +4. **正文**:`momentText()` 从 content 按字符类切候选串、取中文最多的那段(移植 Mac 端 `extract-wechat-chat-moments.py`)。 +5. 按「条」返回 `MomentData(createTimeMs, text, images)`。 + +**落库 / 展示**:朋友圈图**不进相册**(「照片」tab 只放头像 + 真正人脸认出的合照);图按条存 `filesDir/moments//_.jpg`,文字写 `moments` 表(`createTime` 存 ms)。朋友圈 tab 的 `MomentRow` 按 `createTimeMs` glob 回该条的图,渲染成微信式「正文 + 九宫格 + 时间」。幂等:头像 `getByPath` 去重、`moments.clearUser` + 目录清空重灌、开头 `idsByPathLike("%/moment__%")` 清旧版误导进相册的图。 + --- ## §五 §六 待办(按优先级) @@ -396,6 +417,8 @@ openrouter/nous metadata 超时),对开机服务可接受。 | 全相册照片人脸全 0、`decode failed (face)` 刷屏 | `decodeUri` bounds 预解码处 `decodeStream(inJustDecodeBounds=true)` **按设计返回 null**,被 `?:` 当失败提前 return | `?:` 改判 `openInputStream` 的 null,不是 `decodeStream` 返回值(已修,见 `ImageUtil.decodeUri`) | | 语义搜 `embed 404: no router for requested model` | llama-swap 没配 `embed` 模型 | config.yaml 加一个 `embed` model(`llama-server --embedding` + 中文 GGUF);不配则只用关键词搜 | | VL 进度不动 / 点「立即理解」像没反应 | 单张 VL ~30-40s(含 llama-swap 热加载 vl 模型),且要 battery-not-low | 正常现象;看「场景描述 N/总」进度条;想自动补完插充电(周期 worker 要 charging) | +| **app 内 `su -c` 读不到别 app 的 /data**(root 也报 `No such file or directory`) | app 申请的 Magisk su **默认在本 app 的挂载命名空间**,看不到 `com.tencent.mm` 等别 app 的数据目录(`adb shell` 的 su 在全局 ns 所以能看到,易误判) | `su --mount-master -c "..."`(切全局挂载命名空间)。见 `WeChatSnsImporter.su()` | +| **app 内 `su -c` 脚本里 `ls/cp/find/grep` 全失败**(命令算出空值、静默 fail) | app 经 su 起的 shell **PATH 为空**(`adb shell` 继承了 PATH 所以没事) | 脚本开头 `export PATH=/system/bin:/system/xbin:$PATH`;或用绝对路径。别用 `grep -E '{n}'` 区间,改 glob | ---