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
This commit is contained in:
290
docs/HOOK_GUIDE.md
Normal file
290
docs/HOOK_GUIDE.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# 消息抓取架构与 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
|
||||
│
|
||||
├─ 消息以明文写入 SQLite(content/text/body 等)?
|
||||
│ └─ 是 → 可复用 SqliteMessageHook(默认兜底)
|
||||
│
|
||||
├─ 微信?
|
||||
│ └─ 是 → 已有 WeChatMessageHook(Hook 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 # 一键完整部署
|
||||
```
|
||||
Reference in New Issue
Block a user