Files
notiMessage/docs/HOOK_GUIDE.md
Mars 125dfe583b feat: Telegram 双通道抓取、PC 调试台与 Hook 后台转发修复
v2.2.1 更新说明:

- 新增 xposed-module(Telegram/微信/SQLite Hook),双 APK + LSPosed 作用域

- HookMessageReceiver 后台直接 DebugForwarder + goAsync,修复 notiMessage 退后台丢消息

- MessageLogStore 日志持久化;AppConfig 调试/上传开关;PC 调试台 debug-server

- 健康检查去掉联网限制;通知/Hook 通道增加诊断日志

- 安装脚本 install-full/configure-lsposed/start-debug-server;文档 CHANGELOG + HOOK_GUIDE
2026-07-02 16:50:12 +08:00

291 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 消息抓取架构与 Hook 扩展指南
本文说明 notiMessage 的双通道抓取机制、Telegram 的实现方式,以及日后接入其他 App 的步骤。
---
## 1. 总体架构
notiMessage 使用 **两条独立通道** 抓取消息,互为补充:
```
┌─────────────────────────────────────────────────────────────────┐
│ 目标 App如 Telegram
└───────────────┬─────────────────────────────┬───────────────────┘
│ │
后台/锁屏弹系统通知 前台打开 App、消息入库/解码
│ │
▼ ▼
NotificationListenerService Xposed Hook 模块
(NotificationService) (xposed-module)
│ │
│ │ Broadcast
│ ▼
│ HookMessageReceiver
│ │ goAsync()
│ ├─ MessageLogStore立即写入
│ ├─ DebugForwarder直接转发 PC
│ └─ submitFromHook仅正式上传模式
│ │
└──────────────┬──────────────┘
NotificationService.submitNotification()(通知通道)
┌────────────────┼────────────────┐
▼ ▼ ▼
MessageLogStore DebugForwarder (可选)正式后端上传
App 本地日志) PC 调试台) AppConfig.ENABLE_SERVER_UPLOAD
```
| 通道 | 触发条件 | 代码入口 | 日志前缀 |
|------|----------|----------|----------|
| **通知监听** | 目标 App 在后台且弹出系统通知 | `NotificationService.onNotificationPosted()` | 无前缀 |
| **Hook** | 目标 App 进程收到新消息(通常在前台) | `HookMessageReceiver` → 直接 `DebugForwarder` | `[Hook/xposed_telegram]` 等 |
**重要**:仅开通知监听时,目标 App 在前台通常 **不会** 产生系统通知,因此抓不到消息。要覆盖前台场景,必须启用 Xposed Hook。
---
## 2. Telegram 实现方式
### 2.1 包名与作用域
Pixel 6 等设备上 Telegram 常见包名:
| 安装来源 | 包名 |
|----------|------|
| Play / 官网 APK | `org.telegram.messenger` |
| 部分渠道 / Web 版 | `org.telegram.messenger.web` |
LSPosed 作用域需勾选 **目标 Telegram 包名** + **notiMessage 主 App**
默认作用域见 `xposed-module/src/main/res/values/arrays.xml`
### 2.2 为何不 Hook SQLite
Telegram 消息存在 SQLite 的 **`data` 字段(加密二进制 TL 序列化)**,不是明文 `content`/`text`
通用 `SqliteMessageHook` 只能读明文列,对 Telegram **无效**。因此单独实现 `TelegramMessageHook`
### 2.3 Hook 点
**主路径**Hook `NotificationCenter.postNotificationName(int, Object[])`
- 读取静态字段 `NotificationCenter.didReceiveNewMessages` 作为事件 ID
-`id == didReceiveNewMessages` 时,从参数里的 `List<MessageObject>` 取新消息
- 只处理 **非 outgoing**`messageOwner.out == false`)的消息
**兜底路径**Hook `MessageObject` 全部构造函数(主路径失败时启用)
源码位置:`xposed-module/.../hook/TelegramMessageHook.java`
### 2.4 字段提取逻辑
| 字段 | 提取方式 | 用途 |
|------|----------|------|
| **群名/会话名** | `MessagesController.getPeerTitle(dialogId)``MessageObject.getName()``getChat().title` | 分组标题、转发 `group` |
| **发送者** | `MessageObject.getFromName()` | 群内消息前缀 `发送者: 内容` |
| **正文** | `messageText``messageOwner.message``caption` | 文本内容 |
| **图片+说明** | 识别 `[图片]` 等媒体标签 + 合并 `caption` | 避免只显示「图片」丢失说明 |
| **dialogId** | `MessageObject.getDialogId()` | 去重键 `dialogId:messageId` |
**注意**:不要把 `messageOwner.from_id``TLRPC$TL_peerUser` 对象)直接 `toString()` 当标题,会出现 `TLRPC$TL_peerUser@xxxx`
### 2.5 转发到主 App
Hook 模块通过 `HookForwarder` 发送广播:
```
Action: com.miraclegarden.smsmessage.action.HOOK_MESSAGE
Package: com.miraclegarden.smsmessage
Extras: packageName, title, content, timestamp, source=xposed_telegram
```
主 App 的 `HookMessageReceiver` 接收后:
1. 检查该 `packageName` 是否在监听列表(`App.getMessageByNotiList`
2. 写本地日志(`MessageLogStore`,立即持久化)
3. **直接**转发 PC`DebugForwarder`,不依赖 `startService`,后台可用)
4. 若开启正式上传(`ENABLE_SERVER_UPLOAD`),再经 `submitFromHook` 交给 `NotificationService`
---
## 3. 主 App 相关模块
| 文件 | 职责 |
|------|------|
| `NotificationService.java` | 通知监听 + 统一提交入口 |
| `HookMessageReceiver.java` | 接收 Xposed 广播 |
| `MessageLogStore.java` | 日志持久化(后台也能保留历史) |
| `DebugForwarder.java` | POST 到 PC 调试服务 |
| `AppConfig.java` | `ENABLE_DEBUG_FORWARD``ENABLE_SERVER_UPLOAD` 开关 |
### PC 调试台
```powershell
# 启动本地服务(含 adb reverse
powershell -ExecutionPolicy Bypass -File scripts\start-debug-server.ps1
# 浏览器打开 http://127.0.0.1:8765 ,按群/会话分组展示
```
---
## 4. 日后接入其他 App 的步骤
### 4.1 先判断用哪种 Hook 策略
```
目标 App 前台消息是否需要抓取?
├─ 否 → 仅通知监听即可,在 App「监听设置」里添加包名
└─ 是 → 需要 Xposed Hook
├─ 消息以明文写入 SQLitecontent/text/body 等)?
│ └─ 是 → 可复用 SqliteMessageHook默认兜底
├─ 微信?
│ └─ 是 → 已有 WeChatMessageHookHook WCDB message 表)
└─ 其他Telegram、WhatsApp、银行 App 等)
└─ 需编写 **专用 Hook 类**
```
### 4.2 新增专用 Hook 的标准流程
以新 App `com.example.chat` 为例:
#### 步骤 1分析目标 App
1.`adb shell pm path <包名>` 拉出 APK
2. jadx / dexdump 找消息解码后的类(类似 Telegram 的 `MessageObject`
3. 确认:消息明文出现在哪个字段、哪个时机(构造函数 / 事件总线 / 通知回调)
#### 步骤 2编写 Hook 类
`xposed-module/src/main/java/.../hook/` 新建 `ExampleChatMessageHook.java`
```java
public final class ExampleChatMessageHook {
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
// 1. findClass + findAndHookMethod / hookAllConstructors
// 2. 提取 title群名、content正文、过滤 outgoing/系统消息
// 3. 去重messageId + dialogId
// 4. HookForwarder.forward(context, lpparam.packageName, title, content, "xposed_example");
}
}
```
`HookBridge.java` 增加 source 常量,例如 `SOURCE_XPOSED_EXAMPLE = "xposed_example"`
#### 步骤 3注册到 MainHook
编辑 `MainHook.java`
```java
if ("com.example.chat".equals(lpparam.packageName)) {
ExampleChatMessageHook.install(lpparam);
return;
}
```
#### 步骤 4更新 LSPosed 作用域
编辑 `xposed-module/src/main/res/values/arrays.xml`
```xml
<item>com.example.chat</item>
```
#### 步骤 5主 App 添加监听
在 notiMessage「监听设置」中添加该 App或通过 `shared_prefs/server.xml``saveNotiList`)。
`bankInfoId` 本地调试可用 `local:<包名>`
#### 步骤 6构建、安装、验证
```powershell
# 完整安装(主 App + Xposed + LSPosed 作用域)
powershell -ExecutionPolicy Bypass -File scripts\install-full.ps1
```
验证清单:
- [ ] LSPosed 中模块已启用,作用域勾选 **目标 App + notiMessage**
- [ ] 强制停止并重启目标 App让 Hook 重新加载)
- [ ] notiMessage 监听控制台「开始监听」
- [ ] **后台场景**:收消息 → 通知通道有日志
- [ ] **前台场景**:收消息 → `[Hook/xxx]` 日志 + PC 调试台有数据
- [ ] `adb logcat | grep notiMessageHook` 可见 `installed for <包名>`
### 4.3 复用通用 SqliteMessageHook 的条件
`SqliteMessageHook` 会在 `MainHook` 中作为 **默认兜底** 安装(非微信、非 Telegram 时)。
适用 App 特征:
- 使用标准 `SQLiteDatabase` / `FrameworkSQLiteDatabase`
- 表名含 `message` / `messages`
- `ContentValues` 中有明文列:`content``text``body``msg`
**不适用**Telegram、WhatsApp、Signal 等端到端加密或二进制 `data` 列的 App。
### 4.4 已有专用 Hook 参考
| App | 类 | Hook 目标 |
|-----|-----|-----------|
| 微信 | `WeChatMessageHook` | `com.tencent.wcdb.database.SQLiteDatabase` insert`message` 表 |
| Telegram | `TelegramMessageHook` | `NotificationCenter.didReceiveNewMessages` + `MessageObject` |
| 其他 | `SqliteMessageHook` | 通用 SQLite insert 兜底 |
---
## 5. 构建与安装
| 脚本 | 用途 |
|------|------|
| `scripts/build-debug.ps1` | 构建主 App + Xposed 双 APK |
| `scripts/install-debug.ps1` | 仅安装双 APK |
| `scripts/install-full.ps1` | **推荐**:构建 + 双 APK + LSPosed 作用域 + adb reverse + 电池白名单 |
| `scripts/start-debug-server.ps1` | 启动 PC 调试台 |
**注意**Android Studio 点 Run 只安装 `:app` 主模块,**不会**安装 Xposed 模块。完整功能必须用 `install-full.ps1` 或手动安装两个 APK。
---
## 6. 常见问题
| 现象 | 原因 | 处理 |
|------|------|------|
| 前台有消息App 无日志 | 仅通知通道Hook 未生效 | 检查 LSPosed 作用域、重启目标 App |
| 标题显示 `TLRPC$...` | 误把对象当字符串 | 用 `getPeerTitle` 等 API见 TelegramMessageHook |
| 图片消息只有「图片」 | 说明在 caption 字段 | 合并 `caption` + 媒体标签 |
| PC 有数据App 无历史 | 旧版日志未持久化 | 已用 `MessageLogStore` 修复,更新主 App |
| Hook 日志前缀 `[Hook/...]` 无 | Xposed 未注入 | 确认 Magisk + LSPosed + 模块启用 |
---
## 7. 相关文件索引
```
xposed-module/
├── src/main/assets/xposed_init # 入口 MainHook
├── src/main/java/.../MainHook.java # 包名路由
├── src/main/java/.../HookForwarder.java # 广播转发
├── src/main/java/.../hook/
│ ├── TelegramMessageHook.java
│ ├── WeChatMessageHook.java
│ └── SqliteMessageHook.java
└── src/main/res/values/arrays.xml # xposed_scope 默认作用域
app/
├── src/main/java/.../service/
│ ├── NotificationService.java
│ └── HookMessageReceiver.java
├── src/main/java/.../MessageLogStore.java
├── src/main/java/.../network/DebugForwarder.java
└── src/main/java/.../AppConfig.java
debug-server/server.py # PC 调试台
scripts/install-full.ps1 # 一键完整部署
```