From 6da2b2d99ab7c0ba2c5315129a7e0f17fe28a11a Mon Sep 17 00:00:00 2001 From: yibo Date: Sat, 23 May 2026 21:10:37 +0800 Subject: [PATCH] docs: update all WeChat auto-reply docs for SQLCipher + Frida refactor --- md/wechat-auto-reply-status.md | 85 +++++++++++++++++++++++++++++++++++++++++++++---------------------------------------- server/services/wechat/README.md | 7 +++++-- server/services/wechat/SKILL.md | 55 +++++++++++++++++++++++++++++++++++++------------------ wechat_plain/scripts/local-testkit/docs/HERMES_WECHAT_ARCHITECTURE.md | 459 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- wechat_plain/scripts/local-testkit/docs/README.md | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------- 5 files changed, 415 insertions(+), 304 deletions(-) diff --git a/md/wechat-auto-reply-status.md b/md/wechat-auto-reply-status.md index d0b9fb8..090e7fb 100644 --- a/md/wechat-auto-reply-status.md +++ b/md/wechat-auto-reply-status.md @@ -1,6 +1,6 @@ # WeChat 自动回复项目 — 完整状态报告 -> 日期:2026-05-22 +> 日期:2026-05-23 > 设备:Pixel 10 Pro Fold > 微信版本:8.0.72 > 环境: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' - ## 3. 已尝试但不可行的方案 -### 3.1 直接查询 SQLite 数据库(失败) +### 3.1 直接查询 SQLite 数据库(成功 — 已验证) -**方案**:通过 `sqlite3` 命令行直接查询 `EnMicroMsg.db` 的 message 表。 +**方案**:通过 `sqlcipher` CLI 直接查询 `EnMicroMsg.db` 的 message 表。 -**结果**:❌ 完全失败 +**结果**:✅ 已验证,可直接解密并查询 -**原因**: -- 数据库使用 SQLCipher 加密,需要正确的 key 才能打开 -- 通过 Frida hook `sqlite3_key`(offset 0x2687d8)捕获到 5 个 key -- 确认 EnMicroMsg.db 使用的 key 为 `e03ce81` -- 但使用 sqlcipher CLI 尝试 11 种不同配置(不同 KDF 迭代次数、HMAC 设置、page size),全部返回 "file is not a database" -- 通过 Frida 直接调用 `libWCDB.so` 的 `sqlite3_key`/`sqlite3_key_v2` 后执行 `PRAGMA schema_version`,同样返回错误码 26 +**关键参数**: +- SQLCipher key: `e03ce81` +- `PRAGMA cipher_compatibility = 1`(WCDB 兼容模式) +- `PRAGMA cipher_page_size = 1024` +- 无需复制 WAL(sqlcipher 可直接打开运行中的 DB,但可能遇到 `database is locked`) -**结论**:捕获的 key 可能并非真正用于解密,或者 SQLCipher 参数(salt、KDF 算法、加密页大小等)与标准配置完全不同。 +**使用方式**(在 auto-reply.js 中通过 `AndroidWechatTrackerSQLCipher`): +```bash +sqlcipher /data/data/com.tencent.mm/MicroMsg/.../EnMicroMsg.db < ${lastId} AND isSend = 0 AND type IN (1, 10002) ORDER BY msgId ASC; +EOF +``` + +**结论**:SQLCipher 解密成功,已成为主消息检测方案(替代 LSPosed 日志 tail)。 --- @@ -202,43 +213,37 @@ adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser 'filehelper' - | 功能 | 状态 | 备注 | |------|------|------| -| 发送消息 | ✅ 可用 | LSPosed broadcast | -| 消息追踪 | ❌ 阻塞 | 所有已知 hook 点均失败 | -| 数据库解密 | ❌ 阻塞 | Key 正确但参数不匹配 | -| 自动回复 daemon | ⏳ 待联调 | 发送端可用,但缺少输入源 | - -**核心瓶颈**:消息追踪。没有可靠的消息来源,auto-reply.js 无法判断何时回复、回复给谁。 +| 发送消息 | ✅ 可用 | LSPosed broadcast(备用) | +| 发送消息(Frida) | ✅ 可用 | Frida daemon 队列注入(主推) | +| 消息追踪 | ✅ 可用 | SQLCipher DB 轮询(主推) | +| 数据库解密 | ✅ 可用 | sqlcipher CLI + WCDB 兼容参数 | +| 自动回复 daemon | ✅ 可用 | auto-reply.js 主流程已跑通 | +| 息屏唤醒 | ✅ 可用 | notification-list 兜底 + 自动亮屏解锁 | + +**当前主推方案**:Node.js plugin (`auto-reply.js`) +- 消息检测:`AndroidWechatTrackerSQLCipher`(sqlcipher 轮询) +- 回复生成:`callHermesApi()` → `hermes-agent` OpenAI-compatible API +- 消息发送:`sendTextMessageFrida()`(Frida daemon 队列) +- 息屏兜底:`pollNotifications()`(通知监听 → 唤醒微信) --- ## 6. 下一步计划 -### 方案 A:LSPosed 模块扩展(推荐优先尝试) - -1. 在 Android Studio 中重新编译 LSPosed 模块(含 WCDB insert hook) -2. 安装新模块,重启 WeChat -3. 发送测试消息,检查 `/sdcard/hermes/wechat_msgs.jsonl` 是否有数据写入 -4. 如果有数据,修改 auto-reply.js 读取该文件实现消息追踪 - -### 方案 B:Frida :push 进程 hook - -1. 对 `:push` 进程(PID 18531)运行 Frida,hook WCDB insert -2. 发送消息到该设备,观察 push 进程是否有数据库操作 -3. 如果成功,考虑将 hook 逻辑转为 LSPosed 模块(持久化) - -### 方案 C:AccessibilityService +### 已完成(当前主流程) -1. 开发一个 Android AccessibilityService -2. 监听微信通知栏或消息列表的文本变化 -3. 将捕获的消息写入文件供 Termux 读取 -4. 需要用户手动授权无障碍权限 +1. **SQLCipher DB 轮询** — `AndroidWechatTrackerSQLCipher` 已实现并验证,替代 LSPosed 日志 tail +2. **Frida Daemon 发送** — `sendTextMessageFrida` 已实现并验证,替代 am broadcast(绕过 Android 10+ 后台限制) +3. **hermes-agent API** — `auto-reply.js` 已切换到 OpenAI-compatible `/v1/chat/completions` +4. **息屏自动唤醒** — 通知监听 + `wakeWechat()` 点亮/解锁/启动微信流程已验证 -### 方案 D:妥协方案 — 基于通知的简化版 +### 待优化 -1. 使用 `dumpsys notification` 定期轮询 -2. 仅检测"有新消息"事件(不获取具体内容) -3. 收到通知后,通过 ContentProvider 或数据库查询尝试获取完整内容 -4. 如仍无法获取,可回退到"收到消息后固定回复"模式 +1. **息屏发送稳定性** — 当前息屏时 Frida daemon 可能因微信进程被冻结而失效,需要进一步验证 +2. **群聊 @识别精度** — 依赖昵称匹配,考虑加入 wxid 匹配 +3. **Frida daemon 持久化** — 目前需要手动注入,考虑 Termux:Boot 自动注入 +4. **消息去重** — 息屏唤醒后 tracker 可能捕获到已处理过的消息,需优化 state 管理 +5. **本地 LLM 部署** — 减少 API 延迟和网络依赖 --- diff --git a/server/services/wechat/README.md b/server/services/wechat/README.md index 2e47a25..9103577 100644 --- a/server/services/wechat/README.md +++ b/server/services/wechat/README.md @@ -119,8 +119,11 @@ hermes / adb shell content → 模块 Provider 进程 → 发送广播 |------|------|--------| | `wechat-sender.js` | 发送消息(adb) | Mac | | `wechat-tracker.js` | 跟踪消息(adb) | Mac | -| `android-wechat-sender.js` | 发送消息(su) | Pixel | -| `android-wechat-tracker.js` | 跟踪消息(su) | Pixel | +| `android-wechat-tracker-sqlcipher.js` | **主推** 跟踪消息(sqlcipher 轮询 DB) | Pixel | +| `android-wechat-sender-frida.js` | **主推** 发送消息(Frida daemon 队列) | Pixel | +| `android-wechat-sender.js` | 备用发送消息(am broadcast / content insert) | Pixel | +| `android-wechat-tracker.js` | 备用跟踪消息(tail -f LSPosed 日志) | Pixel | +| `auto-reply.js` | Pixel 自动回复 plugin(hermes-agent API) | Pixel | | `index.js` | 统一入口,自动检测环境 | 通用 | | `test-phone.js` | 手机端一键测试 | Pixel | | `deploy-to-phone.sh` | 推送到手机 | Mac | diff --git a/server/services/wechat/SKILL.md b/server/services/wechat/SKILL.md index f407625..fb5807a 100644 --- a/server/services/wechat/SKILL.md +++ b/server/services/wechat/SKILL.md @@ -14,23 +14,38 @@ ``` server/services/wechat/ -├── android-wechat-sender.js # 消息发送 -├── android-wechat-tracker.js # 消息跟踪 -└── wechat-reader.js # 数据库读取(已有) +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪(sqlcipher 轮询 DB) +├── android-wechat-sender-frida.js # **主推** 消息发送(Frida daemon 队列) +├── android-wechat-sender.js # 备用发送(am broadcast / content insert) +├── android-wechat-tracker.js # 备用跟踪(tail -f LSPosed 日志) +├── auto-reply.js # 自动回复主入口 +└── wechat-reader.js # 数据库读取 ``` ## 发送消息 -### 单条发送(推荐) +### 方式1:Frida Daemon(推荐) + +需要 frida-server 运行且 frida_daemon.js 已注入微信进程: + +```js +const { sendTextMessageFrida } = require('./android-wechat-sender-frida'); + +const r = await sendTextMessageFrida('wxid_xxxxxx', '你好,来自 hermes'); +console.log(r); // { ok: true } +// 写入 /data/local/tmp/frida_daemon/queue.jsonl,daemon 异步消费 +``` + +### 方式2:ContentProvider / Broadcast(备用) ```js const { sendTextMessage, sendTextMessageBroadcast } = require('./android-wechat-sender'); -// 方式1:通过 ContentProvider(与 Mac 端 API 一致) -const r1 = await sendTextMessage('filehelper', '你好,来自 hermes'); +// ContentProvider(与 Mac 端 API 一致) +const r1 = await sendTextMessage('filehelper', '你好'); console.log(r1); // { ok: true, uri: 'content://.../send/0' } -// 方式2:直接广播(更可靠,绕过 Provider 进程) +// 直接广播(绕过 Provider 进程) const r2 = await sendTextMessageBroadcast('wxid_xxxxxx', '测试消息'); console.log(r2); // { ok: true } ``` @@ -56,37 +71,41 @@ console.log(s); // { status: 'pending' } | { status: 'true' } | { status: 'false ## 跟踪消息 -### 轮询模式(轻量,推荐) +### 方式1:SQLCipher 轮询(推荐) + +直接查询微信加密 DB,不依赖 LSPosed hook: ```js -const { AndroidWechatTracker } = require('./android-wechat-tracker'); -const tracker = new AndroidWechatTracker(); +const { AndroidWechatTrackerSQLCipher } = require('./android-wechat-tracker-sqlcipher'); +const tracker = new AndroidWechatTrackerSQLCipher(); tracker.on('message', msg => { console.log(`[${msg.type.toUpperCase()}] ${msg.talker}: ${msg.content}`); }); -tracker.on('sendResult', result => { - console.log('发送结果:', result.success); -}); +tracker.on('error', err => console.error('Tracker error:', err.message)); -tracker.startPolling(3000); // 每 3 秒轮询 +await tracker.init(); // 读取当前最大 msgId +tracker.startPolling(3000); // 每 3 秒轮询 // tracker.stopPolling(); ``` -### 流式模式(更实时) +### 方式2:LSPosed 日志 tail(备用) ```js +const { AndroidWechatTracker } = require('./android-wechat-tracker'); const tracker = new AndroidWechatTracker(); + tracker.on('message', msg => console.log(msg)); -tracker.startStream(); // tail -f 日志 -// tracker.stopStream(); +tracker.startPolling(3000); +// tracker.startStream(); // tail -f 模式(更实时) +// tracker.stopPolling(); ``` ### 一次性读取最近消息 ```js -const msgs = await tracker.fetchRecent(20); +const msgs = await tracker.fetchRecent(20); // 仅 AndroidWechatTracker 支持 ``` ## 消息格式 diff --git a/wechat_plain/scripts/local-testkit/docs/HERMES_WECHAT_ARCHITECTURE.md b/wechat_plain/scripts/local-testkit/docs/HERMES_WECHAT_ARCHITECTURE.md index 5b39c39..d3f9ebc 100644 --- a/wechat_plain/scripts/local-testkit/docs/HERMES_WECHAT_ARCHITECTURE.md +++ b/wechat_plain/scripts/local-testkit/docs/HERMES_WECHAT_ARCHITECTURE.md @@ -1,7 +1,7 @@ # Hermes WeChat 本地闭环架构方案 -> 版本: 2.0 (HTTP API 模式) -> 日期: 2026-05-22 +> 版本: 3.0 (Node.js Plugin + SQLCipher + Frida) +> 日期: 2026-05-23 > 目标设备: Pixel 10 Fold (Android) > 运行环境: Termux + Ubuntu proot-distro > 微信版本: 8.0.72 @@ -13,15 +13,17 @@ 在 Pixel 手机本地实现微信全自动闭环: ``` -收到消息 → 读取内容 → HTTP 调用 hermes → 生成回复 → 发送回复 +收到消息 → 读取内容 → HTTP 调用 hermes-agent → 生成回复 → 发送回复 ``` **不依赖 Mac 远程 adb**,所有组件运行在 Android 本地。 **核心设计原则**: -- **daemon** 是常驻后台进程,只负责数据读写和消息收发 -- **hermes** (lemonPCRM) 是独立的 AI 服务,负责 LLM 推理和回复生成 -- 两者通过 HTTP API (`/api/master/chat`) 通信 +- **auto-reply.js** (Node.js plugin) 是常驻后台进程,负责消息检测、AI 调用、回复发送 +- **hermes-agent** (lemonPCRM wrapper) 是独立的 OpenAI-compatible AI 服务,负责 LLM 推理 +- 两者通过 HTTP API (`POST /v1/chat/completions`) 通信 +- **SQLCipher DB 轮询** 替代 LSPosed 日志 tail,更稳定可靠 +- **Frida Daemon** 替代 am broadcast,绕过 Android 10+ 后台限制 --- @@ -34,35 +36,35 @@ │ ┌───────────────────────────────────────────────────────────────────────┐ │ │ │ Ubuntu proot-distro 容器 │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ -│ │ │ hermes (lemonPCRM) │ │ │ -│ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────┐ │ │ │ -│ │ │ │ Node.js │ │ Master │ │ Claude │ │ │ │ -│ │ │ │ Express │ │ Session │ │ CLI │ │ │ │ -│ │ │ │ :3010 │ │ Manager │ │ Protocol │ │ │ │ -│ │ │ └──────────────┘ └──────────┘ └──────────┘ │ │ │ +│ │ │ hermes-agent (:8642) │ │ │ +│ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────────────────────┐ │ │ │ +│ │ │ │ Node.js │ │ OpenAI │ │ Claude CLI / Agents │ │ │ │ +│ │ │ │ Express │ │ compat │ │ (wrapped via lemonPCRM) │ │ │ │ +│ │ │ └──────────────┘ └──────────┘ └──────────────────────────┘ │ │ │ │ │ │ ↑ │ │ │ -│ │ │ │ POST /api/master/chat (SSE) │ │ │ -│ │ │ │ {message, sessionId} │ │ │ +│ │ │ │ POST /v1/chat/completions │ │ │ +│ │ │ │ {model, messages, stream} │ │ │ │ │ └─────────┼────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ┌─────────┴─────────────────────────────────────────────────────────┐ │ │ -│ │ │ daemon (Python) │ │ │ +│ │ │ auto-reply.js (Node.js) │ │ │ │ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ -│ │ │ │ DB 轮询/ │→ │ 新消息 │→ │ HTTP │→ │ am broadcast │ │ │ │ -│ │ │ │ 通知触发 │ │ 检测 │ │ hermes │ │ 发送回复 │ │ │ │ +│ │ │ │ SQLCipher │→ │ 新消息 │→ │ HTTP │→ │ Frida Queue │ │ │ │ +│ │ │ │ DB 轮询 │ │ 检测 │ │ hermes- │ │ /tmp/frida_ │ │ │ │ +│ │ │ │ │ │ │ │ agent │ │ daemon/ │ │ │ │ │ │ │ └──────────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │ │ │ │ │ ↑ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ ┌──────────┴─────┐ │ │ │ -│ │ │ │ sqlcipher │ │ 回复内容 │ │ │ │ -│ │ │ │ 解密读取 │ │ (SSE result) │ │ │ │ +│ │ │ │ sqlcipher │ │ Frida Daemon │ │ │ │ +│ │ │ │ 直接查询 │ │ (注入微信进程) │ │ │ │ │ │ │ └──────────────┘ └────────────────┘ │ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ ↑ │ │ │ │ │ │ │ │ │ ┌──────────────┐ ┌──────────┴──┐│ -│ │ │ su -c cp DB │ │ LSPosed ││ -│ │ │ + WAL + SHM │ │ WeChatHook ││ +│ │ │ EnMicroMsg.db│ │ com.tencent ││ +│ │ │ (SQLCipher) │ │ .mm 主进程 ││ │ │ └──────────────┘ └─────────────┘│ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ ↑ │ │ @@ -70,14 +72,18 @@ │ ┌───────────────────────────────────────────────────────────────────────┐ │ │ │ Termux 环境 (与 proot 共享 home) │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ -│ │ │ 通知监听线程 (辅助通道) │ │ │ +│ │ │ 通知监听线程(息屏兜底) │ │ │ │ │ │ termux-notification-list 每 3 秒轮询 │ │ │ -│ │ │ 发现微信通知 → 触发紧急 DB 轮询 │ │ │ +│ │ │ 发现微信通知 → 点亮屏幕 → 解锁 → 启动微信 → tracker 恢复工作 │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ Termux:Boot 开机自启 │ │ │ │ │ │ ~/.termux/boot/start-hermes.sh │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ +│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ +│ │ │ Frida Server (可选,用于注入 daemon) │ │ │ +│ │ │ /data/local/tmp/frida-server │ │ │ +│ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────────┘ │ │ ↑ │ │ │ │ ▼ │ @@ -98,75 +104,75 @@ ### 3.1 完整消息处理流程 ``` -1. 通知触发(或定时轮询) +1. 息屏状态:通知监听线程发现微信通知 │ ▼ -2. daemon 复制 EnMicroMsg.db + WAL + SHM 到 /sdcard/hermes/dbs/ +2. 点亮屏幕 → 解除锁屏 → 启动微信到前台(约 3-5 秒) │ ▼ -3. daemon 用 sqlcipher 解密 DB +3. auto-reply.js 的 SQLCipher tracker 轮询 EnMicroMsg.db │ ▼ -4. daemon 查询 message 表:SELECT * WHERE msgId > last_msg_id +4. 查询 message 表:SELECT ... WHERE msgId > last_msg_id AND isSend = 0 │ ▼ -5. daemon 提取消息内容 + 最近 5 条上下文 +5. 提取消息内容(单聊 / 群聊@ / 群聊引用) │ ▼ -6. daemon 构造 prompt,HTTP POST 到 hermes /api/master/chat +6. 构造 OpenAI messages 数组,HTTP POST 到 hermes-agent /v1/chat/completions │ { - │ "message": "收到消息(来自 xxx):...\n上下文: ...", - │ "sessionId": "wx-xxx" + │ "model": "hermes-agent", + │ "messages": [{"role": "system", ...}, {"role": "user", ...}], + │ "stream": false │ } │ ▼ -7. hermes (lemonPCRM) 调用 Claude CLI 处理消息,返回 SSE 流 - │ events: session → progress → agent_output → result +7. hermes-agent (lemonPCRM) 调用 Claude CLI 处理消息,返回 JSON + │ {"choices": [{"message": {"content": "回复内容"}}]} │ ▼ -8. daemon 解析 SSE,提取 result.output 作为回复文本 +8. auto-reply.js 提取 content 作为回复文本 │ ▼ -9. daemon 执行 am broadcast 发送回复到微信 +9. 写入 /data/local/tmp/frida_daemon/queue.jsonl │ ▼ -10. LSPosed 模块拦截广播,调用微信内部 API 发送消息 +10. Frida Daemon(注入微信进程)读取队列,调用微信内部 API 发送消息 ``` ### 3.2 Session 管理 -每个微信联系人 (`talker`) 对应一个独立的 hermes session: +每个微信联系人 (`talker`) 对应一个独立的 hermes-agent session: -| talker | sessionId | 作用 | -|--------|-----------|------| +| talker | sessionId(X-Hermes-Session-Id) | 作用 | +|--------|--------------------------------|------| | `filehelper` | `wx-filehelper` | 文件传输助手 | | `wxid_abc123` | `wx-wxid_abc123` | 个人联系人 | | `123456789@chatroom` | `wx-123456789@chatroom` | 群聊 | **好处**: -- hermes 自动维护每个对话的上下文记忆 +- hermes-agent 自动维护每个对话的上下文记忆 - 不同联系人的对话互不干扰 -- session 10 分钟空闲后自动清理 --- ## 4. 消息监听双通道 -### 4.1 通道 A: DB 轮询(主推) +### 4.1 通道 A: SQLCipher DB 轮询(主推) -**原理**: 定期复制微信加密 DB → sqlcipher 解密 → 查询 message 表 → 检测 msgId 变化 +**原理**: sqlcipher CLI 直接查询微信加密 DB 的 message 表,检测 msgId 变化。 **流程**: -```python -while True: - su -c "cp /data/data/com.tencent.mm/MicroMsg//EnMicroMsg.db /sdcard/hermes/dbs/" - su -c "cp /data/data/com.tencent.mm/MicroMsg//EnMicroMsg.db-wal /sdcard/hermes/dbs/" - - sqlcipher EnMicroMsg.db "PRAGMA key='e03ce81'; ..." - - SELECT * FROM message WHERE msgId > last_msg_id ORDER BY msgId - UPDATE last_msg_id = max(msgId) - sleep(10) +```js +// AndroidWechatTrackerSQLCipher +await tracker.init(); // SELECT MAX(msgId) 初始化 lastMsgId +tracker.startPolling(3000); // 每 3 秒轮询 + +// 每次轮询执行: +SELECT msgId, type, isSend, talker, content, createTime +FROM message +WHERE msgId > ${lastMsgId} AND isSend = 0 AND type IN (1, 10002) +ORDER BY msgId ASC ``` **关键参数**: @@ -175,22 +181,37 @@ while True: | SQLCipher key | `e03ce81` | 7 字符字符串密码 | | cipher_compatibility | `1` | WCDB 兼容模式 | | cipher_page_size | `1024` | 页大小 | -| 轮询间隔 | `10s` | 可配置 | -| WAL 文件 | **必须复制** | 否则丢失最新数据 | +| 轮询间隔 | `3000ms` | 可配置 | +| 查询条件 | `isSend = 0` | 只查收到的消息 | +| 消息类型 | `type IN (1, 10002)` | 文本消息 + 撤回通知 | -### 4.2 通道 B: 通知监听(辅助) +**优势**: +- 不依赖 LSPosed hook(WeChat 8.0.72 上 Xposed 不稳定) +- 数据完整(含群聊名称、发送者等) +- 不需要复制 WAL 文件 -**原理**: 通过 Termux:API 的 `termux-notification-list` 读取系统通知栏,发现微信通知后立即触发一次 DB 轮询。 +### 4.2 通道 B: 通知监听(息屏兜底) -**流程**: -```python -while True: - notifs = termux-notification-list - wechat_notifs = [n for n in notifs if n.pkgName == "com.tencent.mm"] - for n in wechat_notifs: - if is_new(n): - trigger_urgent_db_poll() # 不等 10 秒周期 - sleep(3) +**原理**: 通过 Termux:API 的 `termux-notification-list` 读取系统通知栏,发现微信通知后执行屏幕唤醒流程。 + +**唤醒流程**: +``` +检测到微信通知 + │ + ▼ +屏幕关闭? → input keyevent 224 (KEYCODE_WAKEUP) + │ + ▼ +锁屏状态? → wm dismiss-keyguard (无密码设备) + │ + ▼ +微信不在前台? → am start -n com.tencent.mm/.ui.LauncherUI + │ + ▼ +轮询确认 screen on + unlocked + WeChat foreground(最多 20 秒) + │ + ▼ +tracker 恢复 DB 轮询,捕获新消息 ``` **权限要求**: @@ -201,82 +222,102 @@ while True: ### 4.3 双通道协同 ``` -正常状态: DB 轮询每 10 秒执行一次 - -收到新消息: - 通知栏产生通知 ──→ termux-notification-list 在 3 秒内发现 - │ - ▼ - 触发紧急 DB 轮询(不等 10 秒周期) - │ - ▼ - 获取完整消息内容 ──→ HTTP POST hermes ──→ 发送回复 +正常状态: SQLCipher tracker 每 3 秒轮询 DB + +息屏后微信被冻结: + 微信消息到来 → 系统通知栏产生通知 + │ + ▼ + termux-notification-list 在 3 秒内发现 + │ + ▼ + 执行 wakeWechat() 唤醒流程(约 3-5 秒) + │ + ▼ + 微信前台运行 → SQLCipher tracker 恢复轮询 + │ + ▼ + 获取完整消息内容 → HTTP POST hermes-agent → 发送回复 ``` --- -## 5. Hermes HTTP API 调用 +## 5. Hermes-Agent HTTP API 调用 ### 5.1 API 端点 | 端点 | 方法 | 说明 | |------|------|------| -| `POST /api/master/chat` | SSE | 发送消息,流式返回处理结果 | +| `POST /v1/chat/completions` | HTTP | OpenAI-compatible,发送消息,返回 JSON | | `GET /api/health` | HTTP | 健康检查 | ### 5.2 请求格式 ```json -POST /api/master/chat +POST /v1/chat/completions Content-Type: application/json +X-Hermes-Session-Id: wx-filehelper { - "message": "收到消息(来自 filehelper):你好\n请回复:", - "sessionId": "wx-filehelper" + "model": "hermes-agent", + "messages": [ + {"role": "system", "content": "你是 lemonAI 助手..."}, + {"role": "user", "content": "收到微信私聊消息(来自 filehelper):你好\n请给出自然、简短、得体的中文回复。只输出回复内容,不要加任何解释。"} + ], + "stream": false } ``` -### 5.3 SSE 响应格式 +### 5.3 响应格式 +```json +{ + "choices": [ + { + "message": { + "role": "assistant", + "content": "你好!很高兴收到你的消息。" + } + } + ] +} ``` -event: session -data: {"sessionId": "wx-filehelper"} - -event: progress -data: {"step": "initializing", "message": "🤖 Master Agent 启动中..."} -event: agent_output -data: {"agent": "master", "message": "你好!很高兴收到你的消息。"} +auto-reply.js 提取 `choices[0].message.content` 作为最终回复。 -event: result -data: {"sessionId": "wx-filehelper", "type": "text", "output": "你好!很高兴收到你的消息。"} -``` +--- -daemon 提取 `result` 事件的 `output` 字段作为最终回复。 +## 6. 消息发送 -### 5.4 Prompt 构造 +### 6.1 发送机制(Frida Daemon — 主推) -daemon 发送给 hermes 的消息格式: +Frida Daemon 注入微信主进程,通过 Java.perform 调用微信内部 API: ``` -你是一个微信智能助手。根据收到的消息,给出简短、自然、得体的中文回复。 - -最近对话上下文: - 对方: 在吗? - 我: 在的,有什么可以帮你的? - -收到新消息(来自 filehelper):帮我查一下明天的天气 - -请直接给出回复内容,不要加任何解释或格式标记。 +auto-reply.js + │ + ├── 写入队列文件 /data/local/tmp/frida_daemon/queue.jsonl + │ {"talker": "filehelper", "content": "回复内容", "id": "msg_xxx"} + │ + ▼ +Frida Daemon(注入微信进程) + │ + ├── 读取 queue.jsonl + │ + ▼ +Java.perform → 调用微信内部发送 API + │ + ▼ +微信服务器 ``` ---- - -## 6. 消息发送 +**前置条件**: +1. frida-server 运行中 +2. frida_daemon.js 已注入微信进程 -### 6.1 发送机制 +### 6.2 发送机制(LSPosed Broadcast — 备用) -LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceiver: +当 Frida Daemon 不可用时,回退到 am broadcast: ```bash /system/bin/am broadcast \ @@ -285,13 +326,13 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive --es content "回复内容" ``` -### 6.2 发送限制 +### 6.3 发送限制 | 限制 | 说明 | |------|------| -| 仅文本消息 | 当前模块只支持文本 | -| 主线程调用 | NetScene 提交必须在主线程 | -| 微信前台 | 建议保持微信在前台或后台运行 | +| 仅文本消息 | 当前只支持文本 | +| Frida 依赖 | 需要 frida-server + 注入的 daemon | +| 微信主进程 | 发送必须在微信主进程存活时 | --- @@ -304,7 +345,7 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive ```bash #!/bin/bash -# 启动 frida-server(可选) +# 启动 frida-server if [ -f "/data/local/tmp/frida-server" ]; then su -c "/data/local/tmp/frida-server &" fi @@ -315,21 +356,21 @@ if ! su -c "pgrep -x com.tencent.mm" > /dev/null 2>&1; then sleep 5 fi -# 启动 hermes (lemonPCRM) — 在 proot 内 +# 启动 hermes-agent (lemonPCRM) — 在 proot 内 export PATH=/data/data/com.termux/files/usr/bin:$PATH -proot-distro login --shared-home ubuntu -- bash -c \ - "cd /root/aipcrm && npm start &" +proot-distro login --shared-home ubuntu --user ubuntu -- bash -c \ + "cd /data/data/com.termux/files/home/hermes-wechat && . .env && node server/index.js &" sleep 5 -# 启动守护进程 -proot-distro login --shared-home ubuntu -- bash -c \ - "cd /root/local-testkit && python3 hermes_wechat_daemon.py --daemon" +# 启动 auto-reply plugin +proot-distro login --shared-home ubuntu --user ubuntu -- bash -c \ + "cd /data/data/com.termux/files/home/hermes-wechat/server/services/wechat && MY_WXID=xxx MY_NICKNAME=xxx node auto-reply.js &" # 发送启动通知 termux-notification \ --title "Hermes WeChat" \ - --content "守护进程已启动" \ + --content "自动回复已启动" \ --priority low ``` @@ -346,38 +387,50 @@ chmod +x ~/.termux/boot/start-hermes.sh ## 8. 目录结构 ``` -~/local-testkit/ # daemon 目录 -├── hermes_wechat_daemon.py # 主守护进程 -├── config.json # 运行时配置 -├── test_env.py # 环境预检 -├── test_db_poll.py # DB 轮询测试 -├── test_send_msg.py # 发送消息测试 -├── test_termux_api.py # Termux API 测试 -├── test_hermes_api.py # hermes HTTP API 测试 -├── run_all_tests.sh # 一键测试 -├── setup_termux_boot.sh # 开机自启脚本 -└── HERMES_WECHAT_ARCHITECTURE.md # 本方案文档 - -~/aipcrm/ # hermes (lemonPCRM) 目录 -├── server/ # Express 后端 -├── packages/ # ACP 协议 + Agent 运行时 -├── package.json -└── ... - -/sdcard/hermes/ # 运行时数据 -├── dbs/ # DB 副本 -│ ├── EnMicroMsg.db -│ ├── EnMicroMsg.db-wal -│ └── EnMicroMsg.db.decrypted.db -├── wechat_msgs.jsonl # 消息日志 -└── daemon.log # 守护进程日志 +~/hermes-wechat/server/services/wechat/ # 主 plugin +├── auto-reply.js # 主入口 +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪 +├── android-wechat-sender-frida.js # **主推** 消息发送 +├── android-wechat-tracker.js # 备用消息跟踪 +├── android-wechat-sender.js # 备用消息发送 +├── index.js # 统一入口 +└── README.md # plugin 文档 + +~/local-testkit/ # 备用 daemon / 工具 +├── hermes_wechat_daemon.py # 备用 Python 守护进程 +├── config.json # 运行时配置 +├── keepalive.sh # 保活脚本 +├── test_env.py # 环境预检 +├── test_send_msg.py # 发送消息测试 +├── run_all_tests.sh # 一键测试 +└── docs/ + ├── README.md # 使用文档 + ├── HERMES_WECHAT_ARCHITECTURE.md # 本方案文档 + ├── REVERSE_ENGINEERING_LOG.md + ├── NOTIFICATION_STRATEGY.md + └── plan.md + +/sdcard/hermes/ # 运行时数据 +├── dbs/ # DB 副本(备用方案用) +├── wechat_msgs.jsonl # 消息日志(备用方案用) +└── auto-reply.log # auto-reply plugin 日志 ``` --- ## 9. 配置说明 -`config.json`: +### auto-reply.js 环境变量 + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `MY_WXID` | — | 你的微信 wxid(必填) | +| `MY_NICKNAME` | — | 你的微信昵称(必填,用于判断群聊@) | +| `HERMES_HOST` | `127.0.0.1` | hermes-agent API 地址 | +| `HERMES_PORT` | `8642` | hermes-agent API 端口 | +| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底 | + +### config.json(Python daemon 备用) ```json { @@ -390,9 +443,7 @@ chmod +x ~/.termux/boot/start-hermes.sh "max_msgs_per_poll": 50, "hermes": { "api_url": "http://localhost:3010/api/master/chat", - "timeout_sec": 120, - "default_session_prefix": "wx-", - "system_hint": "你是一个微信智能助手。根据收到的消息,给出简短、自然、得体的中文回复。" + "timeout_sec": 120 }, "auto_reply": { "enabled": false, @@ -412,7 +463,7 @@ chmod +x ~/.termux/boot/start-hermes.sh } ``` -**注意**:daemon 不配置 LLM API key,所有 LLM 配置在 hermes (lemonPCRM) 中。 +**注意**:主流程(auto-reply.js)不读取 config.json,所有配置通过环境变量传入。 --- @@ -426,51 +477,54 @@ pkg update pkg install python sqlcipher termux-api # 安装 Termux:Boot 和 Termux:API 应用(F-Droid 或 GitHub) +# 关闭电池优化(Termux / Termux:API / 微信) ``` -### Step 2: 部署 hermes (lemonPCRM) +### Step 2: 部署 hermes-agent (lemonPCRM) ```bash # 在 proot Ubuntu 中 -cd ~/aipcrm +cd ~/hermes-wechat npm install # 配置 .env 中的 Claude API key npm start # 或 npm run dev(热重载) ``` -确认 hermes 监听 `localhost:3010`。 - -### Step 3: 部署 daemon - +确认 hermes-agent 监听 `127.0.0.1:8642`: ```bash -cd ~/local-testkit +curl http://127.0.0.1:8642/v1/chat/completions \ + -X POST -H "Content-Type: application/json" \ + -d '{"model":"hermes-agent","messages":[{"role":"user","content":"hi"}]}' ``` -### Step 4: 运行测试 +### Step 3: 部署 Frida Daemon ```bash -chmod +x run_all_tests.sh -./run_all_tests.sh -``` +# 1. 确保 frida-server 运行 +su -c "/data/local/tmp/frida-server &" -预期结果: -- `test_env.py`: 全部 [OK] -- `test_db_poll.py`: DB 复制、解密、查询通过 -- `test_send_msg.py`: am broadcast 发送成功 -- `test_termux_api.py`: 通知读取成功 -- `test_hermes_api.py`: hermes API 调用成功,收到回复 +# 2. 注入 daemon 到微信进程(从 Mac 或手机端) +frida -U -n com.tencent.mm -l frida_daemon.js +``` -### Step 5: 启动守护进程 +### Step 4: 部署 auto-reply plugin ```bash -# 单次测试 -python3 hermes_wechat_daemon.py --once +cd ~/hermes-wechat/server/services/wechat +export MY_WXID=wxid_xxxxxx +export MY_NICKNAME=你的昵称 +export HERMES_HOST=127.0.0.1 +export HERMES_PORT=8642 -# 正常运行(前台) -python3 hermes_wechat_daemon.py +nohup node auto-reply.js > auto-reply.log 2>&1 & +``` + +### Step 5: 启动保活脚本 -# 后台运行 -python3 hermes_wechat_daemon.py --daemon +```bash +cd ~/local-testkit +chmod +x keepalive.sh +nohup ./keepalive.sh > keepalive.log 2>&1 & ``` ### Step 6: 设置开机自启 @@ -484,29 +538,34 @@ chmod +x ~/.termux/boot/start-hermes.sh ## 11. 故障排查 -### Q: DB 解密失败 +### Q: DB 轮询报错 "database is locked" +**A**: 微信正在写入 DB 时 sqlcipher 尝试打开。tracker 已对此静默处理,会在下次轮询重试。如频繁出现,可增大轮询间隔。 + +### Q: sqlcipher 解密失败 **A**: -1. 确认 key 正确 -2. 确认 WAL 文件已复制:`ls -la /sdcard/hermes/dbs/EnMicroMsg.db*` -3. 微信运行时 DB 被锁定,必须通过 `su -c cp` 复制后再解密 +1. 确认 key 正确:`e03ce81` +2. 确认 cipher_compatibility=1 和 cipher_page_size=1024 +3. 直接测试:`sqlcipher /path/to/EnMicroMsg.db "PRAGMA key='e03ce81'; PRAGMA cipher_compatibility=1; SELECT COUNT(*) FROM message;"` -### Q: hermes API 调用超时 +### Q: hermes-agent API 调用超时 **A**: -1. 确认 hermes 已启动:`curl http://localhost:3010/api/health` -2. 检查 hermes 日志是否有 Claude CLI 调用错误 -3. 增加 `hermes.timeout_sec`(Claude 推理可能需要 30-60 秒) +1. 确认 hermes-agent 已启动:`curl http://127.0.0.1:8642/api/health` +2. 检查 hermes-agent 日志是否有 Claude CLI 调用错误 +3. 增加 `HERMES_TIMEOUT`(Claude 推理可能需要 30-60 秒) -### Q: hermes 返回空回复 +### Q: Frida 发送失败 **A**: -1. 检查 hermes 的 `system/agents/` 目录是否有微信相关 Agent -2. 查看 hermes 日志中的 SSE 事件流 -3. 确认 Claude CLI 进程正常运行(非 mock 模式) +1. 确认 frida-server 运行中:`ps aux | grep frida-server` +2. 确认 frida_daemon.js 已注入微信进程 +3. 检查队列文件权限:`ls -la /data/local/tmp/frida_daemon/queue.jsonl` +4. 备用方案:回退到 `sendTextMessageBroadcast()` -### Q: am broadcast 发送成功但微信没收到 +### Q: 息屏后完全失效 **A**: -1. 检查 LSPosed 模块是否启用且作用域包含微信 -2. 检查日志:`su -c "cat /data/adb/lspd/log/modules_*.log | grep HermesWX | tail -20"` -3. 确认微信在主进程运行 +1. 确认电池优化已关闭(Termux / Termux:API / 微信) +2. 检查 `keepalive.sh` 是否运行 +3. 检查通知权限:Termux:API 是否有通知访问权限 +4. 检查日志:`tail -f auto-reply.log` 看是否有 `[NOTIF]` 或 `[WAKE]` 日志 ### Q: termux-notification-list 返回空 **A**: @@ -516,16 +575,26 @@ chmod +x ~/.termux/boot/start-hermes.sh --- -## 12. 安全与风险提示 +## 12. 版本演进 + +| 版本 | 日期 | 主要变更 | +|------|------|---------| +| 1.0 | 2026-05-20 | 初始方案:Mac adb + Python daemon | +| 2.0 | 2026-05-22 | Pixel 本地方案:LSPosed 日志 tail + am broadcast + SSE API | +| **3.0** | **2026-05-23** | **SQLCipher DB 轮询 + Frida 发送 + OpenAI-compatible API** | + +--- + +## 13. 安全与风险提示 1. **微信封号风险**: 自动化操作存在被检测风险,建议使用测试小号 2. **Root 权限**: 需要 Magisk root -3. **LSPosed 模块**: 仅作用于微信进程 -4. **API Key**: Claude API key 仅在 hermes 中配置,daemon 不持有 +3. **Frida 注入**: 需要 frida-server 常驻运行 +4. **API Key**: Claude API key 仅在 hermes-agent 中配置,plugin 不持有 --- -## 13. 扩展方向 +## 14. 扩展方向 - [ ] 支持群聊 @识别和回复 - [ ] 支持图片/语音消息处理 diff --git a/wechat_plain/scripts/local-testkit/docs/README.md b/wechat_plain/scripts/local-testkit/docs/README.md index 06dd107..a676739 100644 --- a/wechat_plain/scripts/local-testkit/docs/README.md +++ b/wechat_plain/scripts/local-testkit/docs/README.md @@ -11,19 +11,13 @@ ### 主流程:Node.js Plugin(推荐) ``` -LSPosed 模块 hook WCDB insert +sqlcipher 直接查询 EnMicroMsg.db │ ▼ -┌─────────────────────────┐ -│ XposedBridge.log() │────→ /data/adb/lspd/log/verbose_*.log -│ writeMsgToFile() │────→ /sdcard/hermes/wechat_msgs.jsonl -└─────────────────────────┘ - │ - ▼ -┌─────────────────────────┐ ┌─────────────────────────┐ -│ AndroidWechatTracker │────→│ tail -f 日志 │ -│ (Node.js EventEmitter) │ │ 实时事件驱动 │ -└─────────────────────────┘ └─────────────────────────┘ +┌─────────────────────────────────┐ +│ AndroidWechatTrackerSQLCipher │────→ 轮询 message 表 (msgId > last) +│ (Node.js EventEmitter) │ PRAGMA key="e03ce81" +└─────────────────────────────────┘ │ ▼ ┌─────────────────────────┐ @@ -31,50 +25,49 @@ LSPosed 模块 hook WCDB insert │ handleMessage() │ │ - 判断单聊/群聊@/引用 │ │ - 检查 cooldown │ +│ - 息屏唤醒微信 │ └─────────────────────────┘ │ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ -│ callHermesApi() │────→│ POST localhost:3010 │ -│ buildHermesPrompt() │ │ /api/master/chat │ -│ 构造 prompt + session │ │ SSE 流式响应 │ +│ callHermesApi() │────→│ POST 127.0.0.1:8642 │ +│ buildHermesMessages() │ │ /v1/chat/completions │ +│ OpenAI messages 格式 │ │ (OpenAI-compatible) │ └─────────────────────────┘ └─────────────────────────┘ │ │ │ ▼ │ ┌─────────────────────────┐ - │ │ hermes (lemonPCRM) │ - │ │ MasterSession │ - │ │ → Claude CLI / Agents │ + │ │ hermes-agent │ + │ │ (lemonPCRM wrapper) │ │ └─────────────────────────┘ │ │ ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ -│ parseSSE() 提取 output │────→│ 回复文本 │ +│ JSON 提取 content │────→│ 回复文本 │ └─────────────────────────┘ └─────────────────────────┘ │ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ -│ 微信在前台? │ No →│ am start 唤醒微信 │ -└─────────────────────────┘ │ 等待 2s │ +│ 微信在前台? │ No →│ wakeWechat() 点亮+解锁 │ +└─────────────────────────┘ │ + am start 启动微信 │ │Yes └─────────────────────────┘ ▼ ┌─────────────────────────┐ -│ sendTextMessageBroadcast │────→ am broadcast -│ (android-wechat-sender) │ action: com.hermes.wechat.SEND_MSG +│ sendTextMessageFrida │────→ 写入 /data/local/tmp/frida_daemon/queue.jsonl +│ (android-wechat-sender-frida) │ └─────────────────────────┘ │ ▼ ┌─────────────────────────┐ -│ LSPosed 模块 │────→ u01.r1.a() → builder → m1.a() -│ HermesWeChatModule │ setIntField f=0, i=4 +│ Frida Daemon (注入微信) │────→ Java.perform → 调用微信内部发送 API └─────────────────────────┘ ``` **优势**: -- 事件驱动(tail -f),比 DB 轮询实时性高 -- 不需要 sqlcipher 解密,不需要复制 WAL -- 通过 hermes API 享受完整的 Agent 编排、工具调用、记忆能力 -- hermes 不可用时自动 fallback 到直接 LLM API +- 不依赖 LSPosed hook(WeChat 8.0.72 上 Xposed hook 不稳定) +- sqlcipher 直接查询,数据完整(含群聊名称、发送者等) +- Frida 发送比 am broadcast 更可靠(绕过 Android 10+ 后台限制) +- 通过 hermes-agent API 享受完整的 Agent 编排、工具调用、记忆能力 ### 备用流程:Python Daemon @@ -118,9 +111,11 @@ isWechatForeground()? No → wakeWechat() → sleep 3s | 文件 | 用途 | 环境 | |------|------|------| -| `../../server/services/wechat/auto-reply.js` | **主 plugin**:tracker → hermes API → sender | proot Ubuntu | -| `../../server/services/wechat/android-wechat-tracker.js` | 手机端消息跟踪(tail -f LSPosed 日志) | proot Ubuntu | -| `../../server/services/wechat/android-wechat-sender.js` | 手机端消息发送(am broadcast) | proot Ubuntu | +| `../../server/services/wechat/auto-reply.js` | **主 plugin**:tracker → hermes-agent API → sender | proot Ubuntu | +| `../../server/services/wechat/android-wechat-tracker-sqlcipher.js` | **主推** 消息跟踪(sqlcipher 轮询 DB) | proot Ubuntu | +| `../../server/services/wechat/android-wechat-sender-frida.js` | **主推** 消息发送(Frida daemon 队列) | proot Ubuntu | +| `../../server/services/wechat/android-wechat-tracker.js` | 备用消息跟踪(tail -f LSPosed 日志) | proot Ubuntu | +| `../../server/services/wechat/android-wechat-sender.js` | 备用消息发送(am broadcast / content insert) | proot Ubuntu | | `hermes_wechat_daemon.py` | **备用 daemon**:DB 轮询 → hermes/LLM API → sender | Termux / proot | | `config.json` | 运行时配置 | Termux / proot | | `keepalive.sh` | **保活脚本**:wake-lock + 进程守护 | Termux | @@ -156,9 +151,9 @@ cp -r /sdcard/hermes/local-testkit ~/local-testkit cd ~/local-testkit ``` -### 3. 启动 hermes server(如果还没运行) +### 3. 启动 hermes-agent(如果还没运行) -**必须在 proot Ubuntu 内以 ubuntu 用户运行**(Claude CLI 安全机制拒绝 root + `--dangerously-skip-permissions`): +hermes-agent 是 lemonPCRM 的 OpenAI-compatible wrapper,默认监听 `127.0.0.1:8642`: ```bash proot-distro login ubuntu --user ubuntu -- bash -c \ @@ -172,19 +167,36 @@ nohup proot-distro login ubuntu --user ubuntu -- bash -c \ > hermes-server.log 2>&1 & ``` -### 4. 启动 auto-reply plugin(推荐主流程) +确认 agent 就绪: +```bash +curl http://127.0.0.1:8642/v1/chat/completions -X POST \ + -H "Content-Type: application/json" \ + -d '{"model":"hermes-agent","messages":[{"role":"user","content":"hi"}]}' +``` + +### 4. 启动 Frida Daemon(发送端依赖) + +```bash +# 确保 frida-server 在运行 +su -c "/data/local/tmp/frida-server &" + +# 注入 daemon 到微信进程(从 Mac 端执行) +frida -U -n com.tencent.mm -l frida_daemon.js +``` + +### 5. 启动 auto-reply plugin(推荐主流程) ```bash cd ~/hermes-wechat/server/services/wechat export MY_WXID=wxid_xxxxxx export MY_NICKNAME=你的昵称 -export HERMES_HOST=localhost -export HERMES_PORT=3010 +export HERMES_HOST=127.0.0.1 +export HERMES_PORT=8642 nohup node auto-reply.js > auto-reply.log 2>&1 & ``` -### 5. 启动保活脚本(推荐) +### 6. 启动保活脚本(推荐) ```bash cd ~/local-testkit @@ -196,7 +208,7 @@ chmod +x keepalive.sh nohup ./keepalive.sh > keepalive.log 2>&1 & ``` -### 6. 备用:启动 Python daemon +### 7. 备用:启动 Python daemon ```bash cd ~/local-testkit @@ -213,9 +225,9 @@ python3 hermes_wechat_daemon.py --daemon |------|--------|------| | `MY_WXID` | — | 你的微信 wxid(必填) | | `MY_NICKNAME` | — | 你的微信昵称(必填,用于判断群聊@) | -| `HERMES_HOST` | `localhost` | hermes server 地址 | -| `HERMES_PORT` | `3010` | hermes server 端口 | -| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底 | +| `HERMES_HOST` | `127.0.0.1` | hermes-agent API 地址 | +| `HERMES_PORT` | `8642` | hermes-agent API 端口(OpenAI-compatible) | +| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底(息屏唤醒) | ### Python daemon config.json @@ -244,12 +256,13 @@ python3 hermes_wechat_daemon.py --daemon | 问题 | 排查步骤 | |------|---------| -| auto-reply 收不到消息 | 检查 LSPosed 模块日志:`su -c "cat /data/adb/lspd/log/verbose_*.log \| grep HermesWX \| tail -20"` | -| hermes API 返回空 | 检查 hermes server 是否运行:`curl http://localhost:3010/api/health` | -| 息屏后完全失效 | 确认电池优化已关闭;检查 `keepalive.sh` 是否运行 | -| 发送失败 | 检查微信是否在前台;检查 LSPosed 模块是否启用 | +| auto-reply 收不到消息 | 检查 sqlcipher 能否查询:`su -c "sqlcipher /data/.../EnMicroMsg.db 'PRAGMA key=\\\"e03ce81\\\"; SELECT COUNT(*) FROM message;'"` | +| hermes-agent API 返回空 | 检查 hermes-agent 是否运行:`curl http://127.0.0.1:8642/v1/chat/completions ...` | +| 息屏后完全失效 | 确认电池优化已关闭;检查 `keepalive.sh` 是否运行;检查通知权限 | +| 发送失败(Frida) | 确认 frida-server 运行中;确认 frida_daemon.js 已注入微信进程;检查队列文件 `/data/local/tmp/frida_daemon/queue.jsonl` | +| 发送失败(broadcast 备用) | 检查微信是否在前台;检查 LSPosed 模块是否启用 | | Python daemon 查询不到新消息 | 检查微信是否在前台;检查 `sqlcipher_key`;确认 WAL 已复制 | -| Claude CLI 报错 `root/sudo privileges` | hermes server 必须以 proot 内 **ubuntu 用户** 运行,不能是 root | +| Claude CLI 报错 `root/sudo privileges` | hermes 必须以 proot 内 **ubuntu 用户** 运行,不能是 root | --- @@ -271,9 +284,11 @@ local-testkit/ └── plan.md server/services/wechat/ # 主 plugin(Node.js) -├── auto-reply.js # 主入口:tracker → hermes → sender -├── android-wechat-tracker.js # 消息跟踪 -├── android-wechat-sender.js # 消息发送 +├── auto-reply.js # 主入口:tracker → hermes-agent → sender +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪(sqlcipher 轮询 DB) +├── android-wechat-sender-frida.js # **主推** 消息发送(Frida daemon 队列) +├── android-wechat-tracker.js # 备用消息跟踪(tail -f LSPosed 日志) +├── android-wechat-sender.js # 备用消息发送(am broadcast / content insert) ├── index.js # 统一入口 └── README.md # plugin 文档 ``` -- libgit2 0.21.4