Commit 1dfe5938647d3071afc137d95246d1bb6cff37a2
1 parent
d38d9a22
docs: update all WeChat auto-reply docs for SQLCipher + Frida refactor
- HERMES_WECHAT_ARCHITECTURE.md: v2.0 → v3.0, rewrite for Node.js plugin - README.md: update architecture diagram, file list, API endpoint, env vars - server/services/wechat/README.md: add sqlcipher tracker + frida sender - SKILL.md: add Frida daemon and SQLCipher tracker usage - wechat-auto-reply-status.md: mark SQLCipher + Frida as verified, update plans Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Showing
5 changed files
with
415 additions
and
304 deletions
md/wechat-auto-reply-status.md
| 1 | # WeChat 自动回复项目 — 完整状态报告 | 1 | # WeChat 自动回复项目 — 完整状态报告 |
| 2 | 2 | ||
| 3 | -> 日期:2026-05-22 | 3 | +> 日期:2026-05-23 |
| 4 | > 设备:Pixel 10 Pro Fold | 4 | > 设备:Pixel 10 Pro Fold |
| 5 | > 微信版本:8.0.72 | 5 | > 微信版本:8.0.72 |
| 6 | > 环境:Termux + LSPosed (Zygisk) + Frida Server 17.9.10 | 6 | > 环境:Termux + LSPosed (Zygisk) + Frida Server 17.9.10 |
| @@ -40,20 +40,31 @@ adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser 'filehelper' - | @@ -40,20 +40,31 @@ adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser 'filehelper' - | ||
| 40 | 40 | ||
| 41 | ## 3. 已尝试但不可行的方案 | 41 | ## 3. 已尝试但不可行的方案 |
| 42 | 42 | ||
| 43 | -### 3.1 直接查询 SQLite 数据库(失败) | 43 | +### 3.1 直接查询 SQLite 数据库(成功 — 已验证) |
| 44 | 44 | ||
| 45 | -**方案**:通过 `sqlite3` 命令行直接查询 `EnMicroMsg.db` 的 message 表。 | 45 | +**方案**:通过 `sqlcipher` CLI 直接查询 `EnMicroMsg.db` 的 message 表。 |
| 46 | 46 | ||
| 47 | -**结果**:❌ 完全失败 | 47 | +**结果**:✅ 已验证,可直接解密并查询 |
| 48 | 48 | ||
| 49 | -**原因**: | ||
| 50 | -- 数据库使用 SQLCipher 加密,需要正确的 key 才能打开 | ||
| 51 | -- 通过 Frida hook `sqlite3_key`(offset 0x2687d8)捕获到 5 个 key | ||
| 52 | -- 确认 EnMicroMsg.db 使用的 key 为 `e03ce81` | ||
| 53 | -- 但使用 sqlcipher CLI 尝试 11 种不同配置(不同 KDF 迭代次数、HMAC 设置、page size),全部返回 "file is not a database" | ||
| 54 | -- 通过 Frida 直接调用 `libWCDB.so` 的 `sqlite3_key`/`sqlite3_key_v2` 后执行 `PRAGMA schema_version`,同样返回错误码 26 | 49 | +**关键参数**: |
| 50 | +- SQLCipher key: `e03ce81` | ||
| 51 | +- `PRAGMA cipher_compatibility = 1`(WCDB 兼容模式) | ||
| 52 | +- `PRAGMA cipher_page_size = 1024` | ||
| 53 | +- 无需复制 WAL(sqlcipher 可直接打开运行中的 DB,但可能遇到 `database is locked`) | ||
| 55 | 54 | ||
| 56 | -**结论**:捕获的 key 可能并非真正用于解密,或者 SQLCipher 参数(salt、KDF 算法、加密页大小等)与标准配置完全不同。 | 55 | +**使用方式**(在 auto-reply.js 中通过 `AndroidWechatTrackerSQLCipher`): |
| 56 | +```bash | ||
| 57 | +sqlcipher /data/data/com.tencent.mm/MicroMsg/.../EnMicroMsg.db <<EOF | ||
| 58 | +.headers off | ||
| 59 | +.mode line | ||
| 60 | +PRAGMA key="e03ce81"; | ||
| 61 | +PRAGMA cipher_compatibility=1; | ||
| 62 | +PRAGMA cipher_page_size=1024; | ||
| 63 | +SELECT msgId, type, isSend, talker, content, createTime FROM message WHERE msgId > ${lastId} AND isSend = 0 AND type IN (1, 10002) ORDER BY msgId ASC; | ||
| 64 | +EOF | ||
| 65 | +``` | ||
| 66 | + | ||
| 67 | +**结论**:SQLCipher 解密成功,已成为主消息检测方案(替代 LSPosed 日志 tail)。 | ||
| 57 | 68 | ||
| 58 | --- | 69 | --- |
| 59 | 70 | ||
| @@ -202,43 +213,37 @@ adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser 'filehelper' - | @@ -202,43 +213,37 @@ adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser 'filehelper' - | ||
| 202 | 213 | ||
| 203 | | 功能 | 状态 | 备注 | | 214 | | 功能 | 状态 | 备注 | |
| 204 | |------|------|------| | 215 | |------|------|------| |
| 205 | -| 发送消息 | ✅ 可用 | LSPosed broadcast | | ||
| 206 | -| 消息追踪 | ❌ 阻塞 | 所有已知 hook 点均失败 | | ||
| 207 | -| 数据库解密 | ❌ 阻塞 | Key 正确但参数不匹配 | | ||
| 208 | -| 自动回复 daemon | ⏳ 待联调 | 发送端可用,但缺少输入源 | | ||
| 209 | - | ||
| 210 | -**核心瓶颈**:消息追踪。没有可靠的消息来源,auto-reply.js 无法判断何时回复、回复给谁。 | 216 | +| 发送消息 | ✅ 可用 | LSPosed broadcast(备用) | |
| 217 | +| 发送消息(Frida) | ✅ 可用 | Frida daemon 队列注入(主推) | | ||
| 218 | +| 消息追踪 | ✅ 可用 | SQLCipher DB 轮询(主推) | | ||
| 219 | +| 数据库解密 | ✅ 可用 | sqlcipher CLI + WCDB 兼容参数 | | ||
| 220 | +| 自动回复 daemon | ✅ 可用 | auto-reply.js 主流程已跑通 | | ||
| 221 | +| 息屏唤醒 | ✅ 可用 | notification-list 兜底 + 自动亮屏解锁 | | ||
| 222 | + | ||
| 223 | +**当前主推方案**:Node.js plugin (`auto-reply.js`) | ||
| 224 | +- 消息检测:`AndroidWechatTrackerSQLCipher`(sqlcipher 轮询) | ||
| 225 | +- 回复生成:`callHermesApi()` → `hermes-agent` OpenAI-compatible API | ||
| 226 | +- 消息发送:`sendTextMessageFrida()`(Frida daemon 队列) | ||
| 227 | +- 息屏兜底:`pollNotifications()`(通知监听 → 唤醒微信) | ||
| 211 | 228 | ||
| 212 | --- | 229 | --- |
| 213 | 230 | ||
| 214 | ## 6. 下一步计划 | 231 | ## 6. 下一步计划 |
| 215 | 232 | ||
| 216 | -### 方案 A:LSPosed 模块扩展(推荐优先尝试) | ||
| 217 | - | ||
| 218 | -1. 在 Android Studio 中重新编译 LSPosed 模块(含 WCDB insert hook) | ||
| 219 | -2. 安装新模块,重启 WeChat | ||
| 220 | -3. 发送测试消息,检查 `/sdcard/hermes/wechat_msgs.jsonl` 是否有数据写入 | ||
| 221 | -4. 如果有数据,修改 auto-reply.js 读取该文件实现消息追踪 | ||
| 222 | - | ||
| 223 | -### 方案 B:Frida :push 进程 hook | ||
| 224 | - | ||
| 225 | -1. 对 `:push` 进程(PID 18531)运行 Frida,hook WCDB insert | ||
| 226 | -2. 发送消息到该设备,观察 push 进程是否有数据库操作 | ||
| 227 | -3. 如果成功,考虑将 hook 逻辑转为 LSPosed 模块(持久化) | ||
| 228 | - | ||
| 229 | -### 方案 C:AccessibilityService | 233 | +### 已完成(当前主流程) |
| 230 | 234 | ||
| 231 | -1. 开发一个 Android AccessibilityService | ||
| 232 | -2. 监听微信通知栏或消息列表的文本变化 | ||
| 233 | -3. 将捕获的消息写入文件供 Termux 读取 | ||
| 234 | -4. 需要用户手动授权无障碍权限 | 235 | +1. **SQLCipher DB 轮询** — `AndroidWechatTrackerSQLCipher` 已实现并验证,替代 LSPosed 日志 tail |
| 236 | +2. **Frida Daemon 发送** — `sendTextMessageFrida` 已实现并验证,替代 am broadcast(绕过 Android 10+ 后台限制) | ||
| 237 | +3. **hermes-agent API** — `auto-reply.js` 已切换到 OpenAI-compatible `/v1/chat/completions` | ||
| 238 | +4. **息屏自动唤醒** — 通知监听 + `wakeWechat()` 点亮/解锁/启动微信流程已验证 | ||
| 235 | 239 | ||
| 236 | -### 方案 D:妥协方案 — 基于通知的简化版 | 240 | +### 待优化 |
| 237 | 241 | ||
| 238 | -1. 使用 `dumpsys notification` 定期轮询 | ||
| 239 | -2. 仅检测"有新消息"事件(不获取具体内容) | ||
| 240 | -3. 收到通知后,通过 ContentProvider 或数据库查询尝试获取完整内容 | ||
| 241 | -4. 如仍无法获取,可回退到"收到消息后固定回复"模式 | 242 | +1. **息屏发送稳定性** — 当前息屏时 Frida daemon 可能因微信进程被冻结而失效,需要进一步验证 |
| 243 | +2. **群聊 @识别精度** — 依赖昵称匹配,考虑加入 wxid 匹配 | ||
| 244 | +3. **Frida daemon 持久化** — 目前需要手动注入,考虑 Termux:Boot 自动注入 | ||
| 245 | +4. **消息去重** — 息屏唤醒后 tracker 可能捕获到已处理过的消息,需优化 state 管理 | ||
| 246 | +5. **本地 LLM 部署** — 减少 API 延迟和网络依赖 | ||
| 242 | 247 | ||
| 243 | --- | 248 | --- |
| 244 | 249 |
server/services/wechat/README.md
| @@ -119,8 +119,11 @@ hermes / adb shell content → 模块 Provider 进程 → 发送广播 | @@ -119,8 +119,11 @@ hermes / adb shell content → 模块 Provider 进程 → 发送广播 | ||
| 119 | |------|------|--------| | 119 | |------|------|--------| |
| 120 | | `wechat-sender.js` | 发送消息(adb) | Mac | | 120 | | `wechat-sender.js` | 发送消息(adb) | Mac | |
| 121 | | `wechat-tracker.js` | 跟踪消息(adb) | Mac | | 121 | | `wechat-tracker.js` | 跟踪消息(adb) | Mac | |
| 122 | -| `android-wechat-sender.js` | 发送消息(su) | Pixel | | ||
| 123 | -| `android-wechat-tracker.js` | 跟踪消息(su) | Pixel | | 122 | +| `android-wechat-tracker-sqlcipher.js` | **主推** 跟踪消息(sqlcipher 轮询 DB) | Pixel | |
| 123 | +| `android-wechat-sender-frida.js` | **主推** 发送消息(Frida daemon 队列) | Pixel | | ||
| 124 | +| `android-wechat-sender.js` | 备用发送消息(am broadcast / content insert) | Pixel | | ||
| 125 | +| `android-wechat-tracker.js` | 备用跟踪消息(tail -f LSPosed 日志) | Pixel | | ||
| 126 | +| `auto-reply.js` | Pixel 自动回复 plugin(hermes-agent API) | Pixel | | ||
| 124 | | `index.js` | 统一入口,自动检测环境 | 通用 | | 127 | | `index.js` | 统一入口,自动检测环境 | 通用 | |
| 125 | | `test-phone.js` | 手机端一键测试 | Pixel | | 128 | | `test-phone.js` | 手机端一键测试 | Pixel | |
| 126 | | `deploy-to-phone.sh` | 推送到手机 | Mac | | 129 | | `deploy-to-phone.sh` | 推送到手机 | Mac | |
server/services/wechat/SKILL.md
| @@ -14,23 +14,38 @@ | @@ -14,23 +14,38 @@ | ||
| 14 | 14 | ||
| 15 | ``` | 15 | ``` |
| 16 | server/services/wechat/ | 16 | server/services/wechat/ |
| 17 | -├── android-wechat-sender.js # 消息发送 | ||
| 18 | -├── android-wechat-tracker.js # 消息跟踪 | ||
| 19 | -└── wechat-reader.js # 数据库读取(已有) | 17 | +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪(sqlcipher 轮询 DB) |
| 18 | +├── android-wechat-sender-frida.js # **主推** 消息发送(Frida daemon 队列) | ||
| 19 | +├── android-wechat-sender.js # 备用发送(am broadcast / content insert) | ||
| 20 | +├── android-wechat-tracker.js # 备用跟踪(tail -f LSPosed 日志) | ||
| 21 | +├── auto-reply.js # 自动回复主入口 | ||
| 22 | +└── wechat-reader.js # 数据库读取 | ||
| 20 | ``` | 23 | ``` |
| 21 | 24 | ||
| 22 | ## 发送消息 | 25 | ## 发送消息 |
| 23 | 26 | ||
| 24 | -### 单条发送(推荐) | 27 | +### 方式1:Frida Daemon(推荐) |
| 28 | + | ||
| 29 | +需要 frida-server 运行且 frida_daemon.js 已注入微信进程: | ||
| 30 | + | ||
| 31 | +```js | ||
| 32 | +const { sendTextMessageFrida } = require('./android-wechat-sender-frida'); | ||
| 33 | + | ||
| 34 | +const r = await sendTextMessageFrida('wxid_xxxxxx', '你好,来自 hermes'); | ||
| 35 | +console.log(r); // { ok: true } | ||
| 36 | +// 写入 /data/local/tmp/frida_daemon/queue.jsonl,daemon 异步消费 | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +### 方式2:ContentProvider / Broadcast(备用) | ||
| 25 | 40 | ||
| 26 | ```js | 41 | ```js |
| 27 | const { sendTextMessage, sendTextMessageBroadcast } = require('./android-wechat-sender'); | 42 | const { sendTextMessage, sendTextMessageBroadcast } = require('./android-wechat-sender'); |
| 28 | 43 | ||
| 29 | -// 方式1:通过 ContentProvider(与 Mac 端 API 一致) | ||
| 30 | -const r1 = await sendTextMessage('filehelper', '你好,来自 hermes'); | 44 | +// ContentProvider(与 Mac 端 API 一致) |
| 45 | +const r1 = await sendTextMessage('filehelper', '你好'); | ||
| 31 | console.log(r1); // { ok: true, uri: 'content://.../send/0' } | 46 | console.log(r1); // { ok: true, uri: 'content://.../send/0' } |
| 32 | 47 | ||
| 33 | -// 方式2:直接广播(更可靠,绕过 Provider 进程) | 48 | +// 直接广播(绕过 Provider 进程) |
| 34 | const r2 = await sendTextMessageBroadcast('wxid_xxxxxx', '测试消息'); | 49 | const r2 = await sendTextMessageBroadcast('wxid_xxxxxx', '测试消息'); |
| 35 | console.log(r2); // { ok: true } | 50 | console.log(r2); // { ok: true } |
| 36 | ``` | 51 | ``` |
| @@ -56,37 +71,41 @@ console.log(s); // { status: 'pending' } | { status: 'true' } | { status: 'false | @@ -56,37 +71,41 @@ console.log(s); // { status: 'pending' } | { status: 'true' } | { status: 'false | ||
| 56 | 71 | ||
| 57 | ## 跟踪消息 | 72 | ## 跟踪消息 |
| 58 | 73 | ||
| 59 | -### 轮询模式(轻量,推荐) | 74 | +### 方式1:SQLCipher 轮询(推荐) |
| 75 | + | ||
| 76 | +直接查询微信加密 DB,不依赖 LSPosed hook: | ||
| 60 | 77 | ||
| 61 | ```js | 78 | ```js |
| 62 | -const { AndroidWechatTracker } = require('./android-wechat-tracker'); | ||
| 63 | -const tracker = new AndroidWechatTracker(); | 79 | +const { AndroidWechatTrackerSQLCipher } = require('./android-wechat-tracker-sqlcipher'); |
| 80 | +const tracker = new AndroidWechatTrackerSQLCipher(); | ||
| 64 | 81 | ||
| 65 | tracker.on('message', msg => { | 82 | tracker.on('message', msg => { |
| 66 | console.log(`[${msg.type.toUpperCase()}] ${msg.talker}: ${msg.content}`); | 83 | console.log(`[${msg.type.toUpperCase()}] ${msg.talker}: ${msg.content}`); |
| 67 | }); | 84 | }); |
| 68 | 85 | ||
| 69 | -tracker.on('sendResult', result => { | ||
| 70 | - console.log('发送结果:', result.success); | ||
| 71 | -}); | 86 | +tracker.on('error', err => console.error('Tracker error:', err.message)); |
| 72 | 87 | ||
| 73 | -tracker.startPolling(3000); // 每 3 秒轮询 | 88 | +await tracker.init(); // 读取当前最大 msgId |
| 89 | +tracker.startPolling(3000); // 每 3 秒轮询 | ||
| 74 | // tracker.stopPolling(); | 90 | // tracker.stopPolling(); |
| 75 | ``` | 91 | ``` |
| 76 | 92 | ||
| 77 | -### 流式模式(更实时) | 93 | +### 方式2:LSPosed 日志 tail(备用) |
| 78 | 94 | ||
| 79 | ```js | 95 | ```js |
| 96 | +const { AndroidWechatTracker } = require('./android-wechat-tracker'); | ||
| 80 | const tracker = new AndroidWechatTracker(); | 97 | const tracker = new AndroidWechatTracker(); |
| 98 | + | ||
| 81 | tracker.on('message', msg => console.log(msg)); | 99 | tracker.on('message', msg => console.log(msg)); |
| 82 | -tracker.startStream(); // tail -f 日志 | ||
| 83 | -// tracker.stopStream(); | 100 | +tracker.startPolling(3000); |
| 101 | +// tracker.startStream(); // tail -f 模式(更实时) | ||
| 102 | +// tracker.stopPolling(); | ||
| 84 | ``` | 103 | ``` |
| 85 | 104 | ||
| 86 | ### 一次性读取最近消息 | 105 | ### 一次性读取最近消息 |
| 87 | 106 | ||
| 88 | ```js | 107 | ```js |
| 89 | -const msgs = await tracker.fetchRecent(20); | 108 | +const msgs = await tracker.fetchRecent(20); // 仅 AndroidWechatTracker 支持 |
| 90 | ``` | 109 | ``` |
| 91 | 110 | ||
| 92 | ## 消息格式 | 111 | ## 消息格式 |
wechat_plain/scripts/local-testkit/docs/HERMES_WECHAT_ARCHITECTURE.md
| 1 | # Hermes WeChat 本地闭环架构方案 | 1 | # Hermes WeChat 本地闭环架构方案 |
| 2 | 2 | ||
| 3 | -> 版本: 2.0 (HTTP API 模式) | ||
| 4 | -> 日期: 2026-05-22 | 3 | +> 版本: 3.0 (Node.js Plugin + SQLCipher + Frida) |
| 4 | +> 日期: 2026-05-23 | ||
| 5 | > 目标设备: Pixel 10 Fold (Android) | 5 | > 目标设备: Pixel 10 Fold (Android) |
| 6 | > 运行环境: Termux + Ubuntu proot-distro | 6 | > 运行环境: Termux + Ubuntu proot-distro |
| 7 | > 微信版本: 8.0.72 | 7 | > 微信版本: 8.0.72 |
| @@ -13,15 +13,17 @@ | @@ -13,15 +13,17 @@ | ||
| 13 | 在 Pixel 手机本地实现微信全自动闭环: | 13 | 在 Pixel 手机本地实现微信全自动闭环: |
| 14 | 14 | ||
| 15 | ``` | 15 | ``` |
| 16 | -收到消息 → 读取内容 → HTTP 调用 hermes → 生成回复 → 发送回复 | 16 | +收到消息 → 读取内容 → HTTP 调用 hermes-agent → 生成回复 → 发送回复 |
| 17 | ``` | 17 | ``` |
| 18 | 18 | ||
| 19 | **不依赖 Mac 远程 adb**,所有组件运行在 Android 本地。 | 19 | **不依赖 Mac 远程 adb**,所有组件运行在 Android 本地。 |
| 20 | 20 | ||
| 21 | **核心设计原则**: | 21 | **核心设计原则**: |
| 22 | -- **daemon** 是常驻后台进程,只负责数据读写和消息收发 | ||
| 23 | -- **hermes** (lemonPCRM) 是独立的 AI 服务,负责 LLM 推理和回复生成 | ||
| 24 | -- 两者通过 HTTP API (`/api/master/chat`) 通信 | 22 | +- **auto-reply.js** (Node.js plugin) 是常驻后台进程,负责消息检测、AI 调用、回复发送 |
| 23 | +- **hermes-agent** (lemonPCRM wrapper) 是独立的 OpenAI-compatible AI 服务,负责 LLM 推理 | ||
| 24 | +- 两者通过 HTTP API (`POST /v1/chat/completions`) 通信 | ||
| 25 | +- **SQLCipher DB 轮询** 替代 LSPosed 日志 tail,更稳定可靠 | ||
| 26 | +- **Frida Daemon** 替代 am broadcast,绕过 Android 10+ 后台限制 | ||
| 25 | 27 | ||
| 26 | --- | 28 | --- |
| 27 | 29 | ||
| @@ -34,35 +36,35 @@ | @@ -34,35 +36,35 @@ | ||
| 34 | │ ┌───────────────────────────────────────────────────────────────────────┐ │ | 36 | │ ┌───────────────────────────────────────────────────────────────────────┐ │ |
| 35 | │ │ Ubuntu proot-distro 容器 │ │ | 37 | │ │ Ubuntu proot-distro 容器 │ │ |
| 36 | │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ | 38 | │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ |
| 37 | -│ │ │ hermes (lemonPCRM) │ │ │ | ||
| 38 | -│ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────┐ │ │ │ | ||
| 39 | -│ │ │ │ Node.js │ │ Master │ │ Claude │ │ │ │ | ||
| 40 | -│ │ │ │ Express │ │ Session │ │ CLI │ │ │ │ | ||
| 41 | -│ │ │ │ :3010 │ │ Manager │ │ Protocol │ │ │ │ | ||
| 42 | -│ │ │ └──────────────┘ └──────────┘ └──────────┘ │ │ │ | 39 | +│ │ │ hermes-agent (:8642) │ │ │ |
| 40 | +│ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────────────────────┐ │ │ │ | ||
| 41 | +│ │ │ │ Node.js │ │ OpenAI │ │ Claude CLI / Agents │ │ │ │ | ||
| 42 | +│ │ │ │ Express │ │ compat │ │ (wrapped via lemonPCRM) │ │ │ │ | ||
| 43 | +│ │ │ └──────────────┘ └──────────┘ └──────────────────────────┘ │ │ │ | ||
| 43 | │ │ │ ↑ │ │ │ | 44 | │ │ │ ↑ │ │ │ |
| 44 | -│ │ │ │ POST /api/master/chat (SSE) │ │ │ | ||
| 45 | -│ │ │ │ {message, sessionId} │ │ │ | 45 | +│ │ │ │ POST /v1/chat/completions │ │ │ |
| 46 | +│ │ │ │ {model, messages, stream} │ │ │ | ||
| 46 | │ │ └─────────┼────────────────────────────────────────────────────────┘ │ │ | 47 | │ │ └─────────┼────────────────────────────────────────────────────────┘ │ │ |
| 47 | │ │ │ │ │ | 48 | │ │ │ │ │ |
| 48 | │ │ ┌─────────┴─────────────────────────────────────────────────────────┐ │ │ | 49 | │ │ ┌─────────┴─────────────────────────────────────────────────────────┐ │ │ |
| 49 | -│ │ │ daemon (Python) │ │ │ | 50 | +│ │ │ auto-reply.js (Node.js) │ │ │ |
| 50 | │ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ | 51 | │ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ |
| 51 | -│ │ │ │ DB 轮询/ │→ │ 新消息 │→ │ HTTP │→ │ am broadcast │ │ │ │ | ||
| 52 | -│ │ │ │ 通知触发 │ │ 检测 │ │ hermes │ │ 发送回复 │ │ │ │ | 52 | +│ │ │ │ SQLCipher │→ │ 新消息 │→ │ HTTP │→ │ Frida Queue │ │ │ │ |
| 53 | +│ │ │ │ DB 轮询 │ │ 检测 │ │ hermes- │ │ /tmp/frida_ │ │ │ │ | ||
| 54 | +│ │ │ │ │ │ │ │ agent │ │ daemon/ │ │ │ │ | ||
| 53 | │ │ │ └──────────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │ │ | 55 | │ │ │ └──────────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │ │ |
| 54 | │ │ │ ↑ │ │ │ │ | 56 | │ │ │ ↑ │ │ │ │ |
| 55 | │ │ │ │ │ │ │ │ | 57 | │ │ │ │ │ │ │ │ |
| 56 | │ │ │ ┌──────────────┐ ┌──────────┴─────┐ │ │ │ | 58 | │ │ │ ┌──────────────┐ ┌──────────┴─────┐ │ │ │ |
| 57 | -│ │ │ │ sqlcipher │ │ 回复内容 │ │ │ │ | ||
| 58 | -│ │ │ │ 解密读取 │ │ (SSE result) │ │ │ │ | 59 | +│ │ │ │ sqlcipher │ │ Frida Daemon │ │ │ │ |
| 60 | +│ │ │ │ 直接查询 │ │ (注入微信进程) │ │ │ │ | ||
| 59 | │ │ │ └──────────────┘ └────────────────┘ │ │ │ | 61 | │ │ │ └──────────────┘ └────────────────┘ │ │ │ |
| 60 | │ │ └──────────────────────────────────────────────────────────────────┘ │ │ | 62 | │ │ └──────────────────────────────────────────────────────────────────┘ │ │ |
| 61 | │ │ ↑ │ │ | 63 | │ │ ↑ │ │ |
| 62 | │ │ │ │ │ | 64 | │ │ │ │ │ |
| 63 | │ │ ┌──────────────┐ ┌──────────┴──┐│ | 65 | │ │ ┌──────────────┐ ┌──────────┴──┐│ |
| 64 | -│ │ │ su -c cp DB │ │ LSPosed ││ | ||
| 65 | -│ │ │ + WAL + SHM │ │ WeChatHook ││ | 66 | +│ │ │ EnMicroMsg.db│ │ com.tencent ││ |
| 67 | +│ │ │ (SQLCipher) │ │ .mm 主进程 ││ | ||
| 66 | │ │ └──────────────┘ └─────────────┘│ | 68 | │ │ └──────────────┘ └─────────────┘│ |
| 67 | │ └───────────────────────────────────────────────────────────────────────────┘ │ | 69 | │ └───────────────────────────────────────────────────────────────────────────┘ │ |
| 68 | │ ↑ │ │ | 70 | │ ↑ │ │ |
| @@ -70,14 +72,18 @@ | @@ -70,14 +72,18 @@ | ||
| 70 | │ ┌───────────────────────────────────────────────────────────────────────┐ │ | 72 | │ ┌───────────────────────────────────────────────────────────────────────┐ │ |
| 71 | │ │ Termux 环境 (与 proot 共享 home) │ │ | 73 | │ │ Termux 环境 (与 proot 共享 home) │ │ |
| 72 | │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ | 74 | │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ |
| 73 | -│ │ │ 通知监听线程 (辅助通道) │ │ │ | 75 | +│ │ │ 通知监听线程(息屏兜底) │ │ │ |
| 74 | │ │ │ termux-notification-list 每 3 秒轮询 │ │ │ | 76 | │ │ │ termux-notification-list 每 3 秒轮询 │ │ │ |
| 75 | -│ │ │ 发现微信通知 → 触发紧急 DB 轮询 │ │ │ | 77 | +│ │ │ 发现微信通知 → 点亮屏幕 → 解锁 → 启动微信 → tracker 恢复工作 │ │ │ |
| 76 | │ │ └─────────────────────────────────────────────────────────────────┘ │ │ | 78 | │ │ └─────────────────────────────────────────────────────────────────┘ │ │ |
| 77 | │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ | 79 | │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ |
| 78 | │ │ │ Termux:Boot 开机自启 │ │ │ | 80 | │ │ │ Termux:Boot 开机自启 │ │ │ |
| 79 | │ │ │ ~/.termux/boot/start-hermes.sh │ │ │ | 81 | │ │ │ ~/.termux/boot/start-hermes.sh │ │ │ |
| 80 | │ │ └─────────────────────────────────────────────────────────────────┘ │ │ | 82 | │ │ └─────────────────────────────────────────────────────────────────┘ │ │ |
| 83 | +│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ | ||
| 84 | +│ │ │ Frida Server (可选,用于注入 daemon) │ │ │ | ||
| 85 | +│ │ │ /data/local/tmp/frida-server │ │ │ | ||
| 86 | +│ │ └─────────────────────────────────────────────────────────────────┘ │ │ | ||
| 81 | │ └───────────────────────────────────────────────────────────────────────┘ │ | 87 | │ └───────────────────────────────────────────────────────────────────────┘ │ |
| 82 | │ ↑ │ │ | 88 | │ ↑ │ │ |
| 83 | │ │ ▼ │ | 89 | │ │ ▼ │ |
| @@ -98,75 +104,75 @@ | @@ -98,75 +104,75 @@ | ||
| 98 | ### 3.1 完整消息处理流程 | 104 | ### 3.1 完整消息处理流程 |
| 99 | 105 | ||
| 100 | ``` | 106 | ``` |
| 101 | -1. 通知触发(或定时轮询) | 107 | +1. 息屏状态:通知监听线程发现微信通知 |
| 102 | │ | 108 | │ |
| 103 | ▼ | 109 | ▼ |
| 104 | -2. daemon 复制 EnMicroMsg.db + WAL + SHM 到 /sdcard/hermes/dbs/ | 110 | +2. 点亮屏幕 → 解除锁屏 → 启动微信到前台(约 3-5 秒) |
| 105 | │ | 111 | │ |
| 106 | ▼ | 112 | ▼ |
| 107 | -3. daemon 用 sqlcipher 解密 DB | 113 | +3. auto-reply.js 的 SQLCipher tracker 轮询 EnMicroMsg.db |
| 108 | │ | 114 | │ |
| 109 | ▼ | 115 | ▼ |
| 110 | -4. daemon 查询 message 表:SELECT * WHERE msgId > last_msg_id | 116 | +4. 查询 message 表:SELECT ... WHERE msgId > last_msg_id AND isSend = 0 |
| 111 | │ | 117 | │ |
| 112 | ▼ | 118 | ▼ |
| 113 | -5. daemon 提取消息内容 + 最近 5 条上下文 | 119 | +5. 提取消息内容(单聊 / 群聊@ / 群聊引用) |
| 114 | │ | 120 | │ |
| 115 | ▼ | 121 | ▼ |
| 116 | -6. daemon 构造 prompt,HTTP POST 到 hermes /api/master/chat | 122 | +6. 构造 OpenAI messages 数组,HTTP POST 到 hermes-agent /v1/chat/completions |
| 117 | │ { | 123 | │ { |
| 118 | - │ "message": "收到消息(来自 xxx):...\n上下文: ...", | ||
| 119 | - │ "sessionId": "wx-xxx" | 124 | + │ "model": "hermes-agent", |
| 125 | + │ "messages": [{"role": "system", ...}, {"role": "user", ...}], | ||
| 126 | + │ "stream": false | ||
| 120 | │ } | 127 | │ } |
| 121 | │ | 128 | │ |
| 122 | ▼ | 129 | ▼ |
| 123 | -7. hermes (lemonPCRM) 调用 Claude CLI 处理消息,返回 SSE 流 | ||
| 124 | - │ events: session → progress → agent_output → result | 130 | +7. hermes-agent (lemonPCRM) 调用 Claude CLI 处理消息,返回 JSON |
| 131 | + │ {"choices": [{"message": {"content": "回复内容"}}]} | ||
| 125 | │ | 132 | │ |
| 126 | ▼ | 133 | ▼ |
| 127 | -8. daemon 解析 SSE,提取 result.output 作为回复文本 | 134 | +8. auto-reply.js 提取 content 作为回复文本 |
| 128 | │ | 135 | │ |
| 129 | ▼ | 136 | ▼ |
| 130 | -9. daemon 执行 am broadcast 发送回复到微信 | 137 | +9. 写入 /data/local/tmp/frida_daemon/queue.jsonl |
| 131 | │ | 138 | │ |
| 132 | ▼ | 139 | ▼ |
| 133 | -10. LSPosed 模块拦截广播,调用微信内部 API 发送消息 | 140 | +10. Frida Daemon(注入微信进程)读取队列,调用微信内部 API 发送消息 |
| 134 | ``` | 141 | ``` |
| 135 | 142 | ||
| 136 | ### 3.2 Session 管理 | 143 | ### 3.2 Session 管理 |
| 137 | 144 | ||
| 138 | -每个微信联系人 (`talker`) 对应一个独立的 hermes session: | 145 | +每个微信联系人 (`talker`) 对应一个独立的 hermes-agent session: |
| 139 | 146 | ||
| 140 | -| talker | sessionId | 作用 | | ||
| 141 | -|--------|-----------|------| | 147 | +| talker | sessionId(X-Hermes-Session-Id) | 作用 | |
| 148 | +|--------|--------------------------------|------| | ||
| 142 | | `filehelper` | `wx-filehelper` | 文件传输助手 | | 149 | | `filehelper` | `wx-filehelper` | 文件传输助手 | |
| 143 | | `wxid_abc123` | `wx-wxid_abc123` | 个人联系人 | | 150 | | `wxid_abc123` | `wx-wxid_abc123` | 个人联系人 | |
| 144 | | `123456789@chatroom` | `wx-123456789@chatroom` | 群聊 | | 151 | | `123456789@chatroom` | `wx-123456789@chatroom` | 群聊 | |
| 145 | 152 | ||
| 146 | **好处**: | 153 | **好处**: |
| 147 | -- hermes 自动维护每个对话的上下文记忆 | 154 | +- hermes-agent 自动维护每个对话的上下文记忆 |
| 148 | - 不同联系人的对话互不干扰 | 155 | - 不同联系人的对话互不干扰 |
| 149 | -- session 10 分钟空闲后自动清理 | ||
| 150 | 156 | ||
| 151 | --- | 157 | --- |
| 152 | 158 | ||
| 153 | ## 4. 消息监听双通道 | 159 | ## 4. 消息监听双通道 |
| 154 | 160 | ||
| 155 | -### 4.1 通道 A: DB 轮询(主推) | 161 | +### 4.1 通道 A: SQLCipher DB 轮询(主推) |
| 156 | 162 | ||
| 157 | -**原理**: 定期复制微信加密 DB → sqlcipher 解密 → 查询 message 表 → 检测 msgId 变化 | 163 | +**原理**: sqlcipher CLI 直接查询微信加密 DB 的 message 表,检测 msgId 变化。 |
| 158 | 164 | ||
| 159 | **流程**: | 165 | **流程**: |
| 160 | -```python | ||
| 161 | -while True: | ||
| 162 | - su -c "cp /data/data/com.tencent.mm/MicroMsg/<hash>/EnMicroMsg.db /sdcard/hermes/dbs/" | ||
| 163 | - su -c "cp /data/data/com.tencent.mm/MicroMsg/<hash>/EnMicroMsg.db-wal /sdcard/hermes/dbs/" | ||
| 164 | - | ||
| 165 | - sqlcipher EnMicroMsg.db "PRAGMA key='e03ce81'; ..." | ||
| 166 | - | ||
| 167 | - SELECT * FROM message WHERE msgId > last_msg_id ORDER BY msgId | ||
| 168 | - UPDATE last_msg_id = max(msgId) | ||
| 169 | - sleep(10) | 166 | +```js |
| 167 | +// AndroidWechatTrackerSQLCipher | ||
| 168 | +await tracker.init(); // SELECT MAX(msgId) 初始化 lastMsgId | ||
| 169 | +tracker.startPolling(3000); // 每 3 秒轮询 | ||
| 170 | + | ||
| 171 | +// 每次轮询执行: | ||
| 172 | +SELECT msgId, type, isSend, talker, content, createTime | ||
| 173 | +FROM message | ||
| 174 | +WHERE msgId > ${lastMsgId} AND isSend = 0 AND type IN (1, 10002) | ||
| 175 | +ORDER BY msgId ASC | ||
| 170 | ``` | 176 | ``` |
| 171 | 177 | ||
| 172 | **关键参数**: | 178 | **关键参数**: |
| @@ -175,22 +181,37 @@ while True: | @@ -175,22 +181,37 @@ while True: | ||
| 175 | | SQLCipher key | `e03ce81` | 7 字符字符串密码 | | 181 | | SQLCipher key | `e03ce81` | 7 字符字符串密码 | |
| 176 | | cipher_compatibility | `1` | WCDB 兼容模式 | | 182 | | cipher_compatibility | `1` | WCDB 兼容模式 | |
| 177 | | cipher_page_size | `1024` | 页大小 | | 183 | | cipher_page_size | `1024` | 页大小 | |
| 178 | -| 轮询间隔 | `10s` | 可配置 | | ||
| 179 | -| WAL 文件 | **必须复制** | 否则丢失最新数据 | | 184 | +| 轮询间隔 | `3000ms` | 可配置 | |
| 185 | +| 查询条件 | `isSend = 0` | 只查收到的消息 | | ||
| 186 | +| 消息类型 | `type IN (1, 10002)` | 文本消息 + 撤回通知 | | ||
| 180 | 187 | ||
| 181 | -### 4.2 通道 B: 通知监听(辅助) | 188 | +**优势**: |
| 189 | +- 不依赖 LSPosed hook(WeChat 8.0.72 上 Xposed 不稳定) | ||
| 190 | +- 数据完整(含群聊名称、发送者等) | ||
| 191 | +- 不需要复制 WAL 文件 | ||
| 182 | 192 | ||
| 183 | -**原理**: 通过 Termux:API 的 `termux-notification-list` 读取系统通知栏,发现微信通知后立即触发一次 DB 轮询。 | 193 | +### 4.2 通道 B: 通知监听(息屏兜底) |
| 184 | 194 | ||
| 185 | -**流程**: | ||
| 186 | -```python | ||
| 187 | -while True: | ||
| 188 | - notifs = termux-notification-list | ||
| 189 | - wechat_notifs = [n for n in notifs if n.pkgName == "com.tencent.mm"] | ||
| 190 | - for n in wechat_notifs: | ||
| 191 | - if is_new(n): | ||
| 192 | - trigger_urgent_db_poll() # 不等 10 秒周期 | ||
| 193 | - sleep(3) | 195 | +**原理**: 通过 Termux:API 的 `termux-notification-list` 读取系统通知栏,发现微信通知后执行屏幕唤醒流程。 |
| 196 | + | ||
| 197 | +**唤醒流程**: | ||
| 198 | +``` | ||
| 199 | +检测到微信通知 | ||
| 200 | + │ | ||
| 201 | + ▼ | ||
| 202 | +屏幕关闭? → input keyevent 224 (KEYCODE_WAKEUP) | ||
| 203 | + │ | ||
| 204 | + ▼ | ||
| 205 | +锁屏状态? → wm dismiss-keyguard (无密码设备) | ||
| 206 | + │ | ||
| 207 | + ▼ | ||
| 208 | +微信不在前台? → am start -n com.tencent.mm/.ui.LauncherUI | ||
| 209 | + │ | ||
| 210 | + ▼ | ||
| 211 | +轮询确认 screen on + unlocked + WeChat foreground(最多 20 秒) | ||
| 212 | + │ | ||
| 213 | + ▼ | ||
| 214 | +tracker 恢复 DB 轮询,捕获新消息 | ||
| 194 | ``` | 215 | ``` |
| 195 | 216 | ||
| 196 | **权限要求**: | 217 | **权限要求**: |
| @@ -201,82 +222,102 @@ while True: | @@ -201,82 +222,102 @@ while True: | ||
| 201 | ### 4.3 双通道协同 | 222 | ### 4.3 双通道协同 |
| 202 | 223 | ||
| 203 | ``` | 224 | ``` |
| 204 | -正常状态: DB 轮询每 10 秒执行一次 | ||
| 205 | - | ||
| 206 | -收到新消息: | ||
| 207 | - 通知栏产生通知 ──→ termux-notification-list 在 3 秒内发现 | ||
| 208 | - │ | ||
| 209 | - ▼ | ||
| 210 | - 触发紧急 DB 轮询(不等 10 秒周期) | ||
| 211 | - │ | ||
| 212 | - ▼ | ||
| 213 | - 获取完整消息内容 ──→ HTTP POST hermes ──→ 发送回复 | 225 | +正常状态: SQLCipher tracker 每 3 秒轮询 DB |
| 226 | + | ||
| 227 | +息屏后微信被冻结: | ||
| 228 | + 微信消息到来 → 系统通知栏产生通知 | ||
| 229 | + │ | ||
| 230 | + ▼ | ||
| 231 | + termux-notification-list 在 3 秒内发现 | ||
| 232 | + │ | ||
| 233 | + ▼ | ||
| 234 | + 执行 wakeWechat() 唤醒流程(约 3-5 秒) | ||
| 235 | + │ | ||
| 236 | + ▼ | ||
| 237 | + 微信前台运行 → SQLCipher tracker 恢复轮询 | ||
| 238 | + │ | ||
| 239 | + ▼ | ||
| 240 | + 获取完整消息内容 → HTTP POST hermes-agent → 发送回复 | ||
| 214 | ``` | 241 | ``` |
| 215 | 242 | ||
| 216 | --- | 243 | --- |
| 217 | 244 | ||
| 218 | -## 5. Hermes HTTP API 调用 | 245 | +## 5. Hermes-Agent HTTP API 调用 |
| 219 | 246 | ||
| 220 | ### 5.1 API 端点 | 247 | ### 5.1 API 端点 |
| 221 | 248 | ||
| 222 | | 端点 | 方法 | 说明 | | 249 | | 端点 | 方法 | 说明 | |
| 223 | |------|------|------| | 250 | |------|------|------| |
| 224 | -| `POST /api/master/chat` | SSE | 发送消息,流式返回处理结果 | | 251 | +| `POST /v1/chat/completions` | HTTP | OpenAI-compatible,发送消息,返回 JSON | |
| 225 | | `GET /api/health` | HTTP | 健康检查 | | 252 | | `GET /api/health` | HTTP | 健康检查 | |
| 226 | 253 | ||
| 227 | ### 5.2 请求格式 | 254 | ### 5.2 请求格式 |
| 228 | 255 | ||
| 229 | ```json | 256 | ```json |
| 230 | -POST /api/master/chat | 257 | +POST /v1/chat/completions |
| 231 | Content-Type: application/json | 258 | Content-Type: application/json |
| 259 | +X-Hermes-Session-Id: wx-filehelper | ||
| 232 | 260 | ||
| 233 | { | 261 | { |
| 234 | - "message": "收到消息(来自 filehelper):你好\n请回复:", | ||
| 235 | - "sessionId": "wx-filehelper" | 262 | + "model": "hermes-agent", |
| 263 | + "messages": [ | ||
| 264 | + {"role": "system", "content": "你是 lemonAI 助手..."}, | ||
| 265 | + {"role": "user", "content": "收到微信私聊消息(来自 filehelper):你好\n请给出自然、简短、得体的中文回复。只输出回复内容,不要加任何解释。"} | ||
| 266 | + ], | ||
| 267 | + "stream": false | ||
| 236 | } | 268 | } |
| 237 | ``` | 269 | ``` |
| 238 | 270 | ||
| 239 | -### 5.3 SSE 响应格式 | 271 | +### 5.3 响应格式 |
| 240 | 272 | ||
| 273 | +```json | ||
| 274 | +{ | ||
| 275 | + "choices": [ | ||
| 276 | + { | ||
| 277 | + "message": { | ||
| 278 | + "role": "assistant", | ||
| 279 | + "content": "你好!很高兴收到你的消息。" | ||
| 280 | + } | ||
| 281 | + } | ||
| 282 | + ] | ||
| 283 | +} | ||
| 241 | ``` | 284 | ``` |
| 242 | -event: session | ||
| 243 | -data: {"sessionId": "wx-filehelper"} | ||
| 244 | - | ||
| 245 | -event: progress | ||
| 246 | -data: {"step": "initializing", "message": "🤖 Master Agent 启动中..."} | ||
| 247 | 285 | ||
| 248 | -event: agent_output | ||
| 249 | -data: {"agent": "master", "message": "你好!很高兴收到你的消息。"} | 286 | +auto-reply.js 提取 `choices[0].message.content` 作为最终回复。 |
| 250 | 287 | ||
| 251 | -event: result | ||
| 252 | -data: {"sessionId": "wx-filehelper", "type": "text", "output": "你好!很高兴收到你的消息。"} | ||
| 253 | -``` | 288 | +--- |
| 254 | 289 | ||
| 255 | -daemon 提取 `result` 事件的 `output` 字段作为最终回复。 | 290 | +## 6. 消息发送 |
| 256 | 291 | ||
| 257 | -### 5.4 Prompt 构造 | 292 | +### 6.1 发送机制(Frida Daemon — 主推) |
| 258 | 293 | ||
| 259 | -daemon 发送给 hermes 的消息格式: | 294 | +Frida Daemon 注入微信主进程,通过 Java.perform 调用微信内部 API: |
| 260 | 295 | ||
| 261 | ``` | 296 | ``` |
| 262 | -你是一个微信智能助手。根据收到的消息,给出简短、自然、得体的中文回复。 | ||
| 263 | - | ||
| 264 | -最近对话上下文: | ||
| 265 | - 对方: 在吗? | ||
| 266 | - 我: 在的,有什么可以帮你的? | ||
| 267 | - | ||
| 268 | -收到新消息(来自 filehelper):帮我查一下明天的天气 | ||
| 269 | - | ||
| 270 | -请直接给出回复内容,不要加任何解释或格式标记。 | 297 | +auto-reply.js |
| 298 | + │ | ||
| 299 | + ├── 写入队列文件 /data/local/tmp/frida_daemon/queue.jsonl | ||
| 300 | + │ {"talker": "filehelper", "content": "回复内容", "id": "msg_xxx"} | ||
| 301 | + │ | ||
| 302 | + ▼ | ||
| 303 | +Frida Daemon(注入微信进程) | ||
| 304 | + │ | ||
| 305 | + ├── 读取 queue.jsonl | ||
| 306 | + │ | ||
| 307 | + ▼ | ||
| 308 | +Java.perform → 调用微信内部发送 API | ||
| 309 | + │ | ||
| 310 | + ▼ | ||
| 311 | +微信服务器 | ||
| 271 | ``` | 312 | ``` |
| 272 | 313 | ||
| 273 | ---- | ||
| 274 | - | ||
| 275 | -## 6. 消息发送 | 314 | +**前置条件**: |
| 315 | +1. frida-server 运行中 | ||
| 316 | +2. frida_daemon.js 已注入微信进程 | ||
| 276 | 317 | ||
| 277 | -### 6.1 发送机制 | 318 | +### 6.2 发送机制(LSPosed Broadcast — 备用) |
| 278 | 319 | ||
| 279 | -LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceiver: | 320 | +当 Frida Daemon 不可用时,回退到 am broadcast: |
| 280 | 321 | ||
| 281 | ```bash | 322 | ```bash |
| 282 | /system/bin/am broadcast \ | 323 | /system/bin/am broadcast \ |
| @@ -285,13 +326,13 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive | @@ -285,13 +326,13 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive | ||
| 285 | --es content "回复内容" | 326 | --es content "回复内容" |
| 286 | ``` | 327 | ``` |
| 287 | 328 | ||
| 288 | -### 6.2 发送限制 | 329 | +### 6.3 发送限制 |
| 289 | 330 | ||
| 290 | | 限制 | 说明 | | 331 | | 限制 | 说明 | |
| 291 | |------|------| | 332 | |------|------| |
| 292 | -| 仅文本消息 | 当前模块只支持文本 | | ||
| 293 | -| 主线程调用 | NetScene 提交必须在主线程 | | ||
| 294 | -| 微信前台 | 建议保持微信在前台或后台运行 | | 333 | +| 仅文本消息 | 当前只支持文本 | |
| 334 | +| Frida 依赖 | 需要 frida-server + 注入的 daemon | | ||
| 335 | +| 微信主进程 | 发送必须在微信主进程存活时 | | ||
| 295 | 336 | ||
| 296 | --- | 337 | --- |
| 297 | 338 | ||
| @@ -304,7 +345,7 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive | @@ -304,7 +345,7 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive | ||
| 304 | ```bash | 345 | ```bash |
| 305 | #!/bin/bash | 346 | #!/bin/bash |
| 306 | 347 | ||
| 307 | -# 启动 frida-server(可选) | 348 | +# 启动 frida-server |
| 308 | if [ -f "/data/local/tmp/frida-server" ]; then | 349 | if [ -f "/data/local/tmp/frida-server" ]; then |
| 309 | su -c "/data/local/tmp/frida-server &" | 350 | su -c "/data/local/tmp/frida-server &" |
| 310 | fi | 351 | fi |
| @@ -315,21 +356,21 @@ if ! su -c "pgrep -x com.tencent.mm" > /dev/null 2>&1; then | @@ -315,21 +356,21 @@ if ! su -c "pgrep -x com.tencent.mm" > /dev/null 2>&1; then | ||
| 315 | sleep 5 | 356 | sleep 5 |
| 316 | fi | 357 | fi |
| 317 | 358 | ||
| 318 | -# 启动 hermes (lemonPCRM) — 在 proot 内 | 359 | +# 启动 hermes-agent (lemonPCRM) — 在 proot 内 |
| 319 | export PATH=/data/data/com.termux/files/usr/bin:$PATH | 360 | export PATH=/data/data/com.termux/files/usr/bin:$PATH |
| 320 | -proot-distro login --shared-home ubuntu -- bash -c \ | ||
| 321 | - "cd /root/aipcrm && npm start &" | 361 | +proot-distro login --shared-home ubuntu --user ubuntu -- bash -c \ |
| 362 | + "cd /data/data/com.termux/files/home/hermes-wechat && . .env && node server/index.js &" | ||
| 322 | 363 | ||
| 323 | sleep 5 | 364 | sleep 5 |
| 324 | 365 | ||
| 325 | -# 启动守护进程 | ||
| 326 | -proot-distro login --shared-home ubuntu -- bash -c \ | ||
| 327 | - "cd /root/local-testkit && python3 hermes_wechat_daemon.py --daemon" | 366 | +# 启动 auto-reply plugin |
| 367 | +proot-distro login --shared-home ubuntu --user ubuntu -- bash -c \ | ||
| 368 | + "cd /data/data/com.termux/files/home/hermes-wechat/server/services/wechat && MY_WXID=xxx MY_NICKNAME=xxx node auto-reply.js &" | ||
| 328 | 369 | ||
| 329 | # 发送启动通知 | 370 | # 发送启动通知 |
| 330 | termux-notification \ | 371 | termux-notification \ |
| 331 | --title "Hermes WeChat" \ | 372 | --title "Hermes WeChat" \ |
| 332 | - --content "守护进程已启动" \ | 373 | + --content "自动回复已启动" \ |
| 333 | --priority low | 374 | --priority low |
| 334 | ``` | 375 | ``` |
| 335 | 376 | ||
| @@ -346,38 +387,50 @@ chmod +x ~/.termux/boot/start-hermes.sh | @@ -346,38 +387,50 @@ chmod +x ~/.termux/boot/start-hermes.sh | ||
| 346 | ## 8. 目录结构 | 387 | ## 8. 目录结构 |
| 347 | 388 | ||
| 348 | ``` | 389 | ``` |
| 349 | -~/local-testkit/ # daemon 目录 | ||
| 350 | -├── hermes_wechat_daemon.py # 主守护进程 | ||
| 351 | -├── config.json # 运行时配置 | ||
| 352 | -├── test_env.py # 环境预检 | ||
| 353 | -├── test_db_poll.py # DB 轮询测试 | ||
| 354 | -├── test_send_msg.py # 发送消息测试 | ||
| 355 | -├── test_termux_api.py # Termux API 测试 | ||
| 356 | -├── test_hermes_api.py # hermes HTTP API 测试 | ||
| 357 | -├── run_all_tests.sh # 一键测试 | ||
| 358 | -├── setup_termux_boot.sh # 开机自启脚本 | ||
| 359 | -└── HERMES_WECHAT_ARCHITECTURE.md # 本方案文档 | ||
| 360 | - | ||
| 361 | -~/aipcrm/ # hermes (lemonPCRM) 目录 | ||
| 362 | -├── server/ # Express 后端 | ||
| 363 | -├── packages/ # ACP 协议 + Agent 运行时 | ||
| 364 | -├── package.json | ||
| 365 | -└── ... | ||
| 366 | - | ||
| 367 | -/sdcard/hermes/ # 运行时数据 | ||
| 368 | -├── dbs/ # DB 副本 | ||
| 369 | -│ ├── EnMicroMsg.db | ||
| 370 | -│ ├── EnMicroMsg.db-wal | ||
| 371 | -│ └── EnMicroMsg.db.decrypted.db | ||
| 372 | -├── wechat_msgs.jsonl # 消息日志 | ||
| 373 | -└── daemon.log # 守护进程日志 | 390 | +~/hermes-wechat/server/services/wechat/ # 主 plugin |
| 391 | +├── auto-reply.js # 主入口 | ||
| 392 | +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪 | ||
| 393 | +├── android-wechat-sender-frida.js # **主推** 消息发送 | ||
| 394 | +├── android-wechat-tracker.js # 备用消息跟踪 | ||
| 395 | +├── android-wechat-sender.js # 备用消息发送 | ||
| 396 | +├── index.js # 统一入口 | ||
| 397 | +└── README.md # plugin 文档 | ||
| 398 | + | ||
| 399 | +~/local-testkit/ # 备用 daemon / 工具 | ||
| 400 | +├── hermes_wechat_daemon.py # 备用 Python 守护进程 | ||
| 401 | +├── config.json # 运行时配置 | ||
| 402 | +├── keepalive.sh # 保活脚本 | ||
| 403 | +├── test_env.py # 环境预检 | ||
| 404 | +├── test_send_msg.py # 发送消息测试 | ||
| 405 | +├── run_all_tests.sh # 一键测试 | ||
| 406 | +└── docs/ | ||
| 407 | + ├── README.md # 使用文档 | ||
| 408 | + ├── HERMES_WECHAT_ARCHITECTURE.md # 本方案文档 | ||
| 409 | + ├── REVERSE_ENGINEERING_LOG.md | ||
| 410 | + ├── NOTIFICATION_STRATEGY.md | ||
| 411 | + └── plan.md | ||
| 412 | + | ||
| 413 | +/sdcard/hermes/ # 运行时数据 | ||
| 414 | +├── dbs/ # DB 副本(备用方案用) | ||
| 415 | +├── wechat_msgs.jsonl # 消息日志(备用方案用) | ||
| 416 | +└── auto-reply.log # auto-reply plugin 日志 | ||
| 374 | ``` | 417 | ``` |
| 375 | 418 | ||
| 376 | --- | 419 | --- |
| 377 | 420 | ||
| 378 | ## 9. 配置说明 | 421 | ## 9. 配置说明 |
| 379 | 422 | ||
| 380 | -`config.json`: | 423 | +### auto-reply.js 环境变量 |
| 424 | + | ||
| 425 | +| 变量 | 默认值 | 说明 | | ||
| 426 | +|------|--------|------| | ||
| 427 | +| `MY_WXID` | — | 你的微信 wxid(必填) | | ||
| 428 | +| `MY_NICKNAME` | — | 你的微信昵称(必填,用于判断群聊@) | | ||
| 429 | +| `HERMES_HOST` | `127.0.0.1` | hermes-agent API 地址 | | ||
| 430 | +| `HERMES_PORT` | `8642` | hermes-agent API 端口 | | ||
| 431 | +| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底 | | ||
| 432 | + | ||
| 433 | +### config.json(Python daemon 备用) | ||
| 381 | 434 | ||
| 382 | ```json | 435 | ```json |
| 383 | { | 436 | { |
| @@ -390,9 +443,7 @@ chmod +x ~/.termux/boot/start-hermes.sh | @@ -390,9 +443,7 @@ chmod +x ~/.termux/boot/start-hermes.sh | ||
| 390 | "max_msgs_per_poll": 50, | 443 | "max_msgs_per_poll": 50, |
| 391 | "hermes": { | 444 | "hermes": { |
| 392 | "api_url": "http://localhost:3010/api/master/chat", | 445 | "api_url": "http://localhost:3010/api/master/chat", |
| 393 | - "timeout_sec": 120, | ||
| 394 | - "default_session_prefix": "wx-", | ||
| 395 | - "system_hint": "你是一个微信智能助手。根据收到的消息,给出简短、自然、得体的中文回复。" | 446 | + "timeout_sec": 120 |
| 396 | }, | 447 | }, |
| 397 | "auto_reply": { | 448 | "auto_reply": { |
| 398 | "enabled": false, | 449 | "enabled": false, |
| @@ -412,7 +463,7 @@ chmod +x ~/.termux/boot/start-hermes.sh | @@ -412,7 +463,7 @@ chmod +x ~/.termux/boot/start-hermes.sh | ||
| 412 | } | 463 | } |
| 413 | ``` | 464 | ``` |
| 414 | 465 | ||
| 415 | -**注意**:daemon 不配置 LLM API key,所有 LLM 配置在 hermes (lemonPCRM) 中。 | 466 | +**注意**:主流程(auto-reply.js)不读取 config.json,所有配置通过环境变量传入。 |
| 416 | 467 | ||
| 417 | --- | 468 | --- |
| 418 | 469 | ||
| @@ -426,51 +477,54 @@ pkg update | @@ -426,51 +477,54 @@ pkg update | ||
| 426 | pkg install python sqlcipher termux-api | 477 | pkg install python sqlcipher termux-api |
| 427 | 478 | ||
| 428 | # 安装 Termux:Boot 和 Termux:API 应用(F-Droid 或 GitHub) | 479 | # 安装 Termux:Boot 和 Termux:API 应用(F-Droid 或 GitHub) |
| 480 | +# 关闭电池优化(Termux / Termux:API / 微信) | ||
| 429 | ``` | 481 | ``` |
| 430 | 482 | ||
| 431 | -### Step 2: 部署 hermes (lemonPCRM) | 483 | +### Step 2: 部署 hermes-agent (lemonPCRM) |
| 432 | 484 | ||
| 433 | ```bash | 485 | ```bash |
| 434 | # 在 proot Ubuntu 中 | 486 | # 在 proot Ubuntu 中 |
| 435 | -cd ~/aipcrm | 487 | +cd ~/hermes-wechat |
| 436 | npm install | 488 | npm install |
| 437 | # 配置 .env 中的 Claude API key | 489 | # 配置 .env 中的 Claude API key |
| 438 | npm start # 或 npm run dev(热重载) | 490 | npm start # 或 npm run dev(热重载) |
| 439 | ``` | 491 | ``` |
| 440 | 492 | ||
| 441 | -确认 hermes 监听 `localhost:3010`。 | ||
| 442 | - | ||
| 443 | -### Step 3: 部署 daemon | ||
| 444 | - | 493 | +确认 hermes-agent 监听 `127.0.0.1:8642`: |
| 445 | ```bash | 494 | ```bash |
| 446 | -cd ~/local-testkit | 495 | +curl http://127.0.0.1:8642/v1/chat/completions \ |
| 496 | + -X POST -H "Content-Type: application/json" \ | ||
| 497 | + -d '{"model":"hermes-agent","messages":[{"role":"user","content":"hi"}]}' | ||
| 447 | ``` | 498 | ``` |
| 448 | 499 | ||
| 449 | -### Step 4: 运行测试 | 500 | +### Step 3: 部署 Frida Daemon |
| 450 | 501 | ||
| 451 | ```bash | 502 | ```bash |
| 452 | -chmod +x run_all_tests.sh | ||
| 453 | -./run_all_tests.sh | ||
| 454 | -``` | 503 | +# 1. 确保 frida-server 运行 |
| 504 | +su -c "/data/local/tmp/frida-server &" | ||
| 455 | 505 | ||
| 456 | -预期结果: | ||
| 457 | -- `test_env.py`: 全部 [OK] | ||
| 458 | -- `test_db_poll.py`: DB 复制、解密、查询通过 | ||
| 459 | -- `test_send_msg.py`: am broadcast 发送成功 | ||
| 460 | -- `test_termux_api.py`: 通知读取成功 | ||
| 461 | -- `test_hermes_api.py`: hermes API 调用成功,收到回复 | 506 | +# 2. 注入 daemon 到微信进程(从 Mac 或手机端) |
| 507 | +frida -U -n com.tencent.mm -l frida_daemon.js | ||
| 508 | +``` | ||
| 462 | 509 | ||
| 463 | -### Step 5: 启动守护进程 | 510 | +### Step 4: 部署 auto-reply plugin |
| 464 | 511 | ||
| 465 | ```bash | 512 | ```bash |
| 466 | -# 单次测试 | ||
| 467 | -python3 hermes_wechat_daemon.py --once | 513 | +cd ~/hermes-wechat/server/services/wechat |
| 514 | +export MY_WXID=wxid_xxxxxx | ||
| 515 | +export MY_NICKNAME=你的昵称 | ||
| 516 | +export HERMES_HOST=127.0.0.1 | ||
| 517 | +export HERMES_PORT=8642 | ||
| 468 | 518 | ||
| 469 | -# 正常运行(前台) | ||
| 470 | -python3 hermes_wechat_daemon.py | 519 | +nohup node auto-reply.js > auto-reply.log 2>&1 & |
| 520 | +``` | ||
| 521 | + | ||
| 522 | +### Step 5: 启动保活脚本 | ||
| 471 | 523 | ||
| 472 | -# 后台运行 | ||
| 473 | -python3 hermes_wechat_daemon.py --daemon | 524 | +```bash |
| 525 | +cd ~/local-testkit | ||
| 526 | +chmod +x keepalive.sh | ||
| 527 | +nohup ./keepalive.sh > keepalive.log 2>&1 & | ||
| 474 | ``` | 528 | ``` |
| 475 | 529 | ||
| 476 | ### Step 6: 设置开机自启 | 530 | ### Step 6: 设置开机自启 |
| @@ -484,29 +538,34 @@ chmod +x ~/.termux/boot/start-hermes.sh | @@ -484,29 +538,34 @@ chmod +x ~/.termux/boot/start-hermes.sh | ||
| 484 | 538 | ||
| 485 | ## 11. 故障排查 | 539 | ## 11. 故障排查 |
| 486 | 540 | ||
| 487 | -### Q: DB 解密失败 | 541 | +### Q: DB 轮询报错 "database is locked" |
| 542 | +**A**: 微信正在写入 DB 时 sqlcipher 尝试打开。tracker 已对此静默处理,会在下次轮询重试。如频繁出现,可增大轮询间隔。 | ||
| 543 | + | ||
| 544 | +### Q: sqlcipher 解密失败 | ||
| 488 | **A**: | 545 | **A**: |
| 489 | -1. 确认 key 正确 | ||
| 490 | -2. 确认 WAL 文件已复制:`ls -la /sdcard/hermes/dbs/EnMicroMsg.db*` | ||
| 491 | -3. 微信运行时 DB 被锁定,必须通过 `su -c cp` 复制后再解密 | 546 | +1. 确认 key 正确:`e03ce81` |
| 547 | +2. 确认 cipher_compatibility=1 和 cipher_page_size=1024 | ||
| 548 | +3. 直接测试:`sqlcipher /path/to/EnMicroMsg.db "PRAGMA key='e03ce81'; PRAGMA cipher_compatibility=1; SELECT COUNT(*) FROM message;"` | ||
| 492 | 549 | ||
| 493 | -### Q: hermes API 调用超时 | 550 | +### Q: hermes-agent API 调用超时 |
| 494 | **A**: | 551 | **A**: |
| 495 | -1. 确认 hermes 已启动:`curl http://localhost:3010/api/health` | ||
| 496 | -2. 检查 hermes 日志是否有 Claude CLI 调用错误 | ||
| 497 | -3. 增加 `hermes.timeout_sec`(Claude 推理可能需要 30-60 秒) | 552 | +1. 确认 hermes-agent 已启动:`curl http://127.0.0.1:8642/api/health` |
| 553 | +2. 检查 hermes-agent 日志是否有 Claude CLI 调用错误 | ||
| 554 | +3. 增加 `HERMES_TIMEOUT`(Claude 推理可能需要 30-60 秒) | ||
| 498 | 555 | ||
| 499 | -### Q: hermes 返回空回复 | 556 | +### Q: Frida 发送失败 |
| 500 | **A**: | 557 | **A**: |
| 501 | -1. 检查 hermes 的 `system/agents/` 目录是否有微信相关 Agent | ||
| 502 | -2. 查看 hermes 日志中的 SSE 事件流 | ||
| 503 | -3. 确认 Claude CLI 进程正常运行(非 mock 模式) | 558 | +1. 确认 frida-server 运行中:`ps aux | grep frida-server` |
| 559 | +2. 确认 frida_daemon.js 已注入微信进程 | ||
| 560 | +3. 检查队列文件权限:`ls -la /data/local/tmp/frida_daemon/queue.jsonl` | ||
| 561 | +4. 备用方案:回退到 `sendTextMessageBroadcast()` | ||
| 504 | 562 | ||
| 505 | -### Q: am broadcast 发送成功但微信没收到 | 563 | +### Q: 息屏后完全失效 |
| 506 | **A**: | 564 | **A**: |
| 507 | -1. 检查 LSPosed 模块是否启用且作用域包含微信 | ||
| 508 | -2. 检查日志:`su -c "cat /data/adb/lspd/log/modules_*.log | grep HermesWX | tail -20"` | ||
| 509 | -3. 确认微信在主进程运行 | 565 | +1. 确认电池优化已关闭(Termux / Termux:API / 微信) |
| 566 | +2. 检查 `keepalive.sh` 是否运行 | ||
| 567 | +3. 检查通知权限:Termux:API 是否有通知访问权限 | ||
| 568 | +4. 检查日志:`tail -f auto-reply.log` 看是否有 `[NOTIF]` 或 `[WAKE]` 日志 | ||
| 510 | 569 | ||
| 511 | ### Q: termux-notification-list 返回空 | 570 | ### Q: termux-notification-list 返回空 |
| 512 | **A**: | 571 | **A**: |
| @@ -516,16 +575,26 @@ chmod +x ~/.termux/boot/start-hermes.sh | @@ -516,16 +575,26 @@ chmod +x ~/.termux/boot/start-hermes.sh | ||
| 516 | 575 | ||
| 517 | --- | 576 | --- |
| 518 | 577 | ||
| 519 | -## 12. 安全与风险提示 | 578 | +## 12. 版本演进 |
| 579 | + | ||
| 580 | +| 版本 | 日期 | 主要变更 | | ||
| 581 | +|------|------|---------| | ||
| 582 | +| 1.0 | 2026-05-20 | 初始方案:Mac adb + Python daemon | | ||
| 583 | +| 2.0 | 2026-05-22 | Pixel 本地方案:LSPosed 日志 tail + am broadcast + SSE API | | ||
| 584 | +| **3.0** | **2026-05-23** | **SQLCipher DB 轮询 + Frida 发送 + OpenAI-compatible API** | | ||
| 585 | + | ||
| 586 | +--- | ||
| 587 | + | ||
| 588 | +## 13. 安全与风险提示 | ||
| 520 | 589 | ||
| 521 | 1. **微信封号风险**: 自动化操作存在被检测风险,建议使用测试小号 | 590 | 1. **微信封号风险**: 自动化操作存在被检测风险,建议使用测试小号 |
| 522 | 2. **Root 权限**: 需要 Magisk root | 591 | 2. **Root 权限**: 需要 Magisk root |
| 523 | -3. **LSPosed 模块**: 仅作用于微信进程 | ||
| 524 | -4. **API Key**: Claude API key 仅在 hermes 中配置,daemon 不持有 | 592 | +3. **Frida 注入**: 需要 frida-server 常驻运行 |
| 593 | +4. **API Key**: Claude API key 仅在 hermes-agent 中配置,plugin 不持有 | ||
| 525 | 594 | ||
| 526 | --- | 595 | --- |
| 527 | 596 | ||
| 528 | -## 13. 扩展方向 | 597 | +## 14. 扩展方向 |
| 529 | 598 | ||
| 530 | - [ ] 支持群聊 @识别和回复 | 599 | - [ ] 支持群聊 @识别和回复 |
| 531 | - [ ] 支持图片/语音消息处理 | 600 | - [ ] 支持图片/语音消息处理 |
wechat_plain/scripts/local-testkit/docs/README.md
| @@ -11,19 +11,13 @@ | @@ -11,19 +11,13 @@ | ||
| 11 | ### 主流程:Node.js Plugin(推荐) | 11 | ### 主流程:Node.js Plugin(推荐) |
| 12 | 12 | ||
| 13 | ``` | 13 | ``` |
| 14 | -LSPosed 模块 hook WCDB insert | 14 | +sqlcipher 直接查询 EnMicroMsg.db |
| 15 | │ | 15 | │ |
| 16 | ▼ | 16 | ▼ |
| 17 | -┌─────────────────────────┐ | ||
| 18 | -│ XposedBridge.log() │────→ /data/adb/lspd/log/verbose_*.log | ||
| 19 | -│ writeMsgToFile() │────→ /sdcard/hermes/wechat_msgs.jsonl | ||
| 20 | -└─────────────────────────┘ | ||
| 21 | - │ | ||
| 22 | - ▼ | ||
| 23 | -┌─────────────────────────┐ ┌─────────────────────────┐ | ||
| 24 | -│ AndroidWechatTracker │────→│ tail -f 日志 │ | ||
| 25 | -│ (Node.js EventEmitter) │ │ 实时事件驱动 │ | ||
| 26 | -└─────────────────────────┘ └─────────────────────────┘ | 17 | +┌─────────────────────────────────┐ |
| 18 | +│ AndroidWechatTrackerSQLCipher │────→ 轮询 message 表 (msgId > last) | ||
| 19 | +│ (Node.js EventEmitter) │ PRAGMA key="e03ce81" | ||
| 20 | +└─────────────────────────────────┘ | ||
| 27 | │ | 21 | │ |
| 28 | ▼ | 22 | ▼ |
| 29 | ┌─────────────────────────┐ | 23 | ┌─────────────────────────┐ |
| @@ -31,50 +25,49 @@ LSPosed 模块 hook WCDB insert | @@ -31,50 +25,49 @@ LSPosed 模块 hook WCDB insert | ||
| 31 | │ handleMessage() │ | 25 | │ handleMessage() │ |
| 32 | │ - 判断单聊/群聊@/引用 │ | 26 | │ - 判断单聊/群聊@/引用 │ |
| 33 | │ - 检查 cooldown │ | 27 | │ - 检查 cooldown │ |
| 28 | +│ - 息屏唤醒微信 │ | ||
| 34 | └─────────────────────────┘ | 29 | └─────────────────────────┘ |
| 35 | │ | 30 | │ |
| 36 | ▼ | 31 | ▼ |
| 37 | ┌─────────────────────────┐ ┌─────────────────────────┐ | 32 | ┌─────────────────────────┐ ┌─────────────────────────┐ |
| 38 | -│ callHermesApi() │────→│ POST localhost:3010 │ | ||
| 39 | -│ buildHermesPrompt() │ │ /api/master/chat │ | ||
| 40 | -│ 构造 prompt + session │ │ SSE 流式响应 │ | 33 | +│ callHermesApi() │────→│ POST 127.0.0.1:8642 │ |
| 34 | +│ buildHermesMessages() │ │ /v1/chat/completions │ | ||
| 35 | +│ OpenAI messages 格式 │ │ (OpenAI-compatible) │ | ||
| 41 | └─────────────────────────┘ └─────────────────────────┘ | 36 | └─────────────────────────┘ └─────────────────────────┘ |
| 42 | │ │ | 37 | │ │ |
| 43 | │ ▼ | 38 | │ ▼ |
| 44 | │ ┌─────────────────────────┐ | 39 | │ ┌─────────────────────────┐ |
| 45 | - │ │ hermes (lemonPCRM) │ | ||
| 46 | - │ │ MasterSession │ | ||
| 47 | - │ │ → Claude CLI / Agents │ | 40 | + │ │ hermes-agent │ |
| 41 | + │ │ (lemonPCRM wrapper) │ | ||
| 48 | │ └─────────────────────────┘ | 42 | │ └─────────────────────────┘ |
| 49 | │ │ | 43 | │ │ |
| 50 | ▼ ▼ | 44 | ▼ ▼ |
| 51 | ┌─────────────────────────┐ ┌─────────────────────────┐ | 45 | ┌─────────────────────────┐ ┌─────────────────────────┐ |
| 52 | -│ parseSSE() 提取 output │────→│ 回复文本 │ | 46 | +│ JSON 提取 content │────→│ 回复文本 │ |
| 53 | └─────────────────────────┘ └─────────────────────────┘ | 47 | └─────────────────────────┘ └─────────────────────────┘ |
| 54 | │ | 48 | │ |
| 55 | ▼ | 49 | ▼ |
| 56 | ┌─────────────────────────┐ ┌─────────────────────────┐ | 50 | ┌─────────────────────────┐ ┌─────────────────────────┐ |
| 57 | -│ 微信在前台? │ No →│ am start 唤醒微信 │ | ||
| 58 | -└─────────────────────────┘ │ 等待 2s │ | 51 | +│ 微信在前台? │ No →│ wakeWechat() 点亮+解锁 │ |
| 52 | +└─────────────────────────┘ │ + am start 启动微信 │ | ||
| 59 | │Yes └─────────────────────────┘ | 53 | │Yes └─────────────────────────┘ |
| 60 | ▼ | 54 | ▼ |
| 61 | ┌─────────────────────────┐ | 55 | ┌─────────────────────────┐ |
| 62 | -│ sendTextMessageBroadcast │────→ am broadcast | ||
| 63 | -│ (android-wechat-sender) │ action: com.hermes.wechat.SEND_MSG | 56 | +│ sendTextMessageFrida │────→ 写入 /data/local/tmp/frida_daemon/queue.jsonl |
| 57 | +│ (android-wechat-sender-frida) │ | ||
| 64 | └─────────────────────────┘ | 58 | └─────────────────────────┘ |
| 65 | │ | 59 | │ |
| 66 | ▼ | 60 | ▼ |
| 67 | ┌─────────────────────────┐ | 61 | ┌─────────────────────────┐ |
| 68 | -│ LSPosed 模块 │────→ u01.r1.a() → builder → m1.a() | ||
| 69 | -│ HermesWeChatModule │ setIntField f=0, i=4 | 62 | +│ Frida Daemon (注入微信) │────→ Java.perform → 调用微信内部发送 API |
| 70 | └─────────────────────────┘ | 63 | └─────────────────────────┘ |
| 71 | ``` | 64 | ``` |
| 72 | 65 | ||
| 73 | **优势**: | 66 | **优势**: |
| 74 | -- 事件驱动(tail -f),比 DB 轮询实时性高 | ||
| 75 | -- 不需要 sqlcipher 解密,不需要复制 WAL | ||
| 76 | -- 通过 hermes API 享受完整的 Agent 编排、工具调用、记忆能力 | ||
| 77 | -- hermes 不可用时自动 fallback 到直接 LLM API | 67 | +- 不依赖 LSPosed hook(WeChat 8.0.72 上 Xposed hook 不稳定) |
| 68 | +- sqlcipher 直接查询,数据完整(含群聊名称、发送者等) | ||
| 69 | +- Frida 发送比 am broadcast 更可靠(绕过 Android 10+ 后台限制) | ||
| 70 | +- 通过 hermes-agent API 享受完整的 Agent 编排、工具调用、记忆能力 | ||
| 78 | 71 | ||
| 79 | ### 备用流程:Python Daemon | 72 | ### 备用流程:Python Daemon |
| 80 | 73 | ||
| @@ -118,9 +111,11 @@ isWechatForeground()? No → wakeWechat() → sleep 3s | @@ -118,9 +111,11 @@ isWechatForeground()? No → wakeWechat() → sleep 3s | ||
| 118 | 111 | ||
| 119 | | 文件 | 用途 | 环境 | | 112 | | 文件 | 用途 | 环境 | |
| 120 | |------|------|------| | 113 | |------|------|------| |
| 121 | -| `../../server/services/wechat/auto-reply.js` | **主 plugin**:tracker → hermes API → sender | proot Ubuntu | | ||
| 122 | -| `../../server/services/wechat/android-wechat-tracker.js` | 手机端消息跟踪(tail -f LSPosed 日志) | proot Ubuntu | | ||
| 123 | -| `../../server/services/wechat/android-wechat-sender.js` | 手机端消息发送(am broadcast) | proot Ubuntu | | 114 | +| `../../server/services/wechat/auto-reply.js` | **主 plugin**:tracker → hermes-agent API → sender | proot Ubuntu | |
| 115 | +| `../../server/services/wechat/android-wechat-tracker-sqlcipher.js` | **主推** 消息跟踪(sqlcipher 轮询 DB) | proot Ubuntu | | ||
| 116 | +| `../../server/services/wechat/android-wechat-sender-frida.js` | **主推** 消息发送(Frida daemon 队列) | proot Ubuntu | | ||
| 117 | +| `../../server/services/wechat/android-wechat-tracker.js` | 备用消息跟踪(tail -f LSPosed 日志) | proot Ubuntu | | ||
| 118 | +| `../../server/services/wechat/android-wechat-sender.js` | 备用消息发送(am broadcast / content insert) | proot Ubuntu | | ||
| 124 | | `hermes_wechat_daemon.py` | **备用 daemon**:DB 轮询 → hermes/LLM API → sender | Termux / proot | | 119 | | `hermes_wechat_daemon.py` | **备用 daemon**:DB 轮询 → hermes/LLM API → sender | Termux / proot | |
| 125 | | `config.json` | 运行时配置 | Termux / proot | | 120 | | `config.json` | 运行时配置 | Termux / proot | |
| 126 | | `keepalive.sh` | **保活脚本**:wake-lock + 进程守护 | Termux | | 121 | | `keepalive.sh` | **保活脚本**:wake-lock + 进程守护 | Termux | |
| @@ -156,9 +151,9 @@ cp -r /sdcard/hermes/local-testkit ~/local-testkit | @@ -156,9 +151,9 @@ cp -r /sdcard/hermes/local-testkit ~/local-testkit | ||
| 156 | cd ~/local-testkit | 151 | cd ~/local-testkit |
| 157 | ``` | 152 | ``` |
| 158 | 153 | ||
| 159 | -### 3. 启动 hermes server(如果还没运行) | 154 | +### 3. 启动 hermes-agent(如果还没运行) |
| 160 | 155 | ||
| 161 | -**必须在 proot Ubuntu 内以 ubuntu 用户运行**(Claude CLI 安全机制拒绝 root + `--dangerously-skip-permissions`): | 156 | +hermes-agent 是 lemonPCRM 的 OpenAI-compatible wrapper,默认监听 `127.0.0.1:8642`: |
| 162 | 157 | ||
| 163 | ```bash | 158 | ```bash |
| 164 | proot-distro login ubuntu --user ubuntu -- bash -c \ | 159 | proot-distro login ubuntu --user ubuntu -- bash -c \ |
| @@ -172,19 +167,36 @@ nohup proot-distro login ubuntu --user ubuntu -- bash -c \ | @@ -172,19 +167,36 @@ nohup proot-distro login ubuntu --user ubuntu -- bash -c \ | ||
| 172 | > hermes-server.log 2>&1 & | 167 | > hermes-server.log 2>&1 & |
| 173 | ``` | 168 | ``` |
| 174 | 169 | ||
| 175 | -### 4. 启动 auto-reply plugin(推荐主流程) | 170 | +确认 agent 就绪: |
| 171 | +```bash | ||
| 172 | +curl http://127.0.0.1:8642/v1/chat/completions -X POST \ | ||
| 173 | + -H "Content-Type: application/json" \ | ||
| 174 | + -d '{"model":"hermes-agent","messages":[{"role":"user","content":"hi"}]}' | ||
| 175 | +``` | ||
| 176 | + | ||
| 177 | +### 4. 启动 Frida Daemon(发送端依赖) | ||
| 178 | + | ||
| 179 | +```bash | ||
| 180 | +# 确保 frida-server 在运行 | ||
| 181 | +su -c "/data/local/tmp/frida-server &" | ||
| 182 | + | ||
| 183 | +# 注入 daemon 到微信进程(从 Mac 端执行) | ||
| 184 | +frida -U -n com.tencent.mm -l frida_daemon.js | ||
| 185 | +``` | ||
| 186 | + | ||
| 187 | +### 5. 启动 auto-reply plugin(推荐主流程) | ||
| 176 | 188 | ||
| 177 | ```bash | 189 | ```bash |
| 178 | cd ~/hermes-wechat/server/services/wechat | 190 | cd ~/hermes-wechat/server/services/wechat |
| 179 | export MY_WXID=wxid_xxxxxx | 191 | export MY_WXID=wxid_xxxxxx |
| 180 | export MY_NICKNAME=你的昵称 | 192 | export MY_NICKNAME=你的昵称 |
| 181 | -export HERMES_HOST=localhost | ||
| 182 | -export HERMES_PORT=3010 | 193 | +export HERMES_HOST=127.0.0.1 |
| 194 | +export HERMES_PORT=8642 | ||
| 183 | 195 | ||
| 184 | nohup node auto-reply.js > auto-reply.log 2>&1 & | 196 | nohup node auto-reply.js > auto-reply.log 2>&1 & |
| 185 | ``` | 197 | ``` |
| 186 | 198 | ||
| 187 | -### 5. 启动保活脚本(推荐) | 199 | +### 6. 启动保活脚本(推荐) |
| 188 | 200 | ||
| 189 | ```bash | 201 | ```bash |
| 190 | cd ~/local-testkit | 202 | cd ~/local-testkit |
| @@ -196,7 +208,7 @@ chmod +x keepalive.sh | @@ -196,7 +208,7 @@ chmod +x keepalive.sh | ||
| 196 | nohup ./keepalive.sh > keepalive.log 2>&1 & | 208 | nohup ./keepalive.sh > keepalive.log 2>&1 & |
| 197 | ``` | 209 | ``` |
| 198 | 210 | ||
| 199 | -### 6. 备用:启动 Python daemon | 211 | +### 7. 备用:启动 Python daemon |
| 200 | 212 | ||
| 201 | ```bash | 213 | ```bash |
| 202 | cd ~/local-testkit | 214 | cd ~/local-testkit |
| @@ -213,9 +225,9 @@ python3 hermes_wechat_daemon.py --daemon | @@ -213,9 +225,9 @@ python3 hermes_wechat_daemon.py --daemon | ||
| 213 | |------|--------|------| | 225 | |------|--------|------| |
| 214 | | `MY_WXID` | — | 你的微信 wxid(必填) | | 226 | | `MY_WXID` | — | 你的微信 wxid(必填) | |
| 215 | | `MY_NICKNAME` | — | 你的微信昵称(必填,用于判断群聊@) | | 227 | | `MY_NICKNAME` | — | 你的微信昵称(必填,用于判断群聊@) | |
| 216 | -| `HERMES_HOST` | `localhost` | hermes server 地址 | | ||
| 217 | -| `HERMES_PORT` | `3010` | hermes server 端口 | | ||
| 218 | -| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底 | | 228 | +| `HERMES_HOST` | `127.0.0.1` | hermes-agent API 地址 | |
| 229 | +| `HERMES_PORT` | `8642` | hermes-agent API 端口(OpenAI-compatible) | | ||
| 230 | +| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底(息屏唤醒) | | ||
| 219 | 231 | ||
| 220 | ### Python daemon config.json | 232 | ### Python daemon config.json |
| 221 | 233 | ||
| @@ -244,12 +256,13 @@ python3 hermes_wechat_daemon.py --daemon | @@ -244,12 +256,13 @@ python3 hermes_wechat_daemon.py --daemon | ||
| 244 | 256 | ||
| 245 | | 问题 | 排查步骤 | | 257 | | 问题 | 排查步骤 | |
| 246 | |------|---------| | 258 | |------|---------| |
| 247 | -| auto-reply 收不到消息 | 检查 LSPosed 模块日志:`su -c "cat /data/adb/lspd/log/verbose_*.log \| grep HermesWX \| tail -20"` | | ||
| 248 | -| hermes API 返回空 | 检查 hermes server 是否运行:`curl http://localhost:3010/api/health` | | ||
| 249 | -| 息屏后完全失效 | 确认电池优化已关闭;检查 `keepalive.sh` 是否运行 | | ||
| 250 | -| 发送失败 | 检查微信是否在前台;检查 LSPosed 模块是否启用 | | 259 | +| auto-reply 收不到消息 | 检查 sqlcipher 能否查询:`su -c "sqlcipher /data/.../EnMicroMsg.db 'PRAGMA key=\\\"e03ce81\\\"; SELECT COUNT(*) FROM message;'"` | |
| 260 | +| hermes-agent API 返回空 | 检查 hermes-agent 是否运行:`curl http://127.0.0.1:8642/v1/chat/completions ...` | | ||
| 261 | +| 息屏后完全失效 | 确认电池优化已关闭;检查 `keepalive.sh` 是否运行;检查通知权限 | | ||
| 262 | +| 发送失败(Frida) | 确认 frida-server 运行中;确认 frida_daemon.js 已注入微信进程;检查队列文件 `/data/local/tmp/frida_daemon/queue.jsonl` | | ||
| 263 | +| 发送失败(broadcast 备用) | 检查微信是否在前台;检查 LSPosed 模块是否启用 | | ||
| 251 | | Python daemon 查询不到新消息 | 检查微信是否在前台;检查 `sqlcipher_key`;确认 WAL 已复制 | | 264 | | Python daemon 查询不到新消息 | 检查微信是否在前台;检查 `sqlcipher_key`;确认 WAL 已复制 | |
| 252 | -| Claude CLI 报错 `root/sudo privileges` | hermes server 必须以 proot 内 **ubuntu 用户** 运行,不能是 root | | 265 | +| Claude CLI 报错 `root/sudo privileges` | hermes 必须以 proot 内 **ubuntu 用户** 运行,不能是 root | |
| 253 | 266 | ||
| 254 | --- | 267 | --- |
| 255 | 268 | ||
| @@ -271,9 +284,11 @@ local-testkit/ | @@ -271,9 +284,11 @@ local-testkit/ | ||
| 271 | └── plan.md | 284 | └── plan.md |
| 272 | 285 | ||
| 273 | server/services/wechat/ # 主 plugin(Node.js) | 286 | server/services/wechat/ # 主 plugin(Node.js) |
| 274 | -├── auto-reply.js # 主入口:tracker → hermes → sender | ||
| 275 | -├── android-wechat-tracker.js # 消息跟踪 | ||
| 276 | -├── android-wechat-sender.js # 消息发送 | 287 | +├── auto-reply.js # 主入口:tracker → hermes-agent → sender |
| 288 | +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪(sqlcipher 轮询 DB) | ||
| 289 | +├── android-wechat-sender-frida.js # **主推** 消息发送(Frida daemon 队列) | ||
| 290 | +├── android-wechat-tracker.js # 备用消息跟踪(tail -f LSPosed 日志) | ||
| 291 | +├── android-wechat-sender.js # 备用消息发送(am broadcast / content insert) | ||
| 277 | ├── index.js # 统一入口 | 292 | ├── index.js # 统一入口 |
| 278 | └── README.md # plugin 文档 | 293 | └── README.md # plugin 文档 |
| 279 | ``` | 294 | ``` |