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:
@@ -26,11 +26,13 @@ import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.miraclegarden.smsmessage.Activity.NotificationActivity;
|
||||
import com.miraclegarden.smsmessage.App;
|
||||
import com.miraclegarden.smsmessage.AppConfig;
|
||||
import com.miraclegarden.smsmessage.MessageInfo;
|
||||
import com.miraclegarden.smsmessage.R;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
import com.miraclegarden.smsmessage.model.UploadNotificationResponse;
|
||||
import com.miraclegarden.smsmessage.network.ApiService;
|
||||
import com.miraclegarden.smsmessage.network.DebugForwarder;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
@@ -82,8 +84,10 @@ public class NotificationService extends NotificationListenerService {
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
|
||||
retryManager = new RetryManager(this);
|
||||
retryManager.setUploadCallback(this::performUpload);
|
||||
retryManager.restoreFromPersistence();
|
||||
if (AppConfig.ENABLE_SERVER_UPLOAD) {
|
||||
retryManager.setUploadCallback(this::performUpload);
|
||||
retryManager.restoreFromPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
private static final String ACTION_HOOK_MESSAGE = "ACTION_HOOK_MESSAGE";
|
||||
@@ -102,14 +106,25 @@ public class NotificationService extends NotificationListenerService {
|
||||
}
|
||||
|
||||
public static void submitFromHook(Context context, MessageInfo messageInfo,
|
||||
String title, String content, long timestamp) {
|
||||
String title, String content, long timestamp, String source) {
|
||||
Intent intent = new Intent(context, NotificationService.class);
|
||||
intent.setAction(ACTION_HOOK_MESSAGE);
|
||||
intent.putExtra("hook_package", messageInfo.getPackageName());
|
||||
intent.putExtra("hook_title", title);
|
||||
intent.putExtra("hook_content", content);
|
||||
intent.putExtra("hook_timestamp", timestamp);
|
||||
context.startService(intent);
|
||||
intent.putExtra("hook_source", source);
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent);
|
||||
} else {
|
||||
context.startService(intent);
|
||||
}
|
||||
Log.d(TAG, "submitFromHook: service started for " + messageInfo.getPackageName());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "submitFromHook failed: " + e.getMessage(), e);
|
||||
NotificationActivity.sendMessage("[Hook] 服务启动失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHookMessageIntent(Intent intent) {
|
||||
@@ -118,12 +133,13 @@ public class NotificationService extends NotificationListenerService {
|
||||
if (messageInfo == null) {
|
||||
return;
|
||||
}
|
||||
String source = intent.getStringExtra("hook_source");
|
||||
NotificationExtractor.Result result = new NotificationExtractor.Result(
|
||||
intent.getStringExtra("hook_title"),
|
||||
intent.getStringExtra("hook_content"),
|
||||
intent.getLongExtra("hook_timestamp", System.currentTimeMillis())
|
||||
);
|
||||
submitNotification(messageInfo, result);
|
||||
submitNotification(messageInfo, result, source != null ? source : "hook");
|
||||
}
|
||||
|
||||
public static boolean isMonitoring() {
|
||||
@@ -135,7 +151,9 @@ public class NotificationService extends NotificationListenerService {
|
||||
isMonitoring = true;
|
||||
|
||||
toggleNotificationListenerService(this);
|
||||
updateForegroundNotification(retryManager != null ? retryManager.getUploadedCount() : 0);
|
||||
updateForegroundNotification(retryManager != null
|
||||
? (AppConfig.ENABLE_SERVER_UPLOAD ? retryManager.getUploadedCount() : retryManager.getTotalCount())
|
||||
: 0);
|
||||
NotificationActivity.sendMessage("已开启持续监听模式");
|
||||
NotificationActivity.updateUI();
|
||||
}
|
||||
@@ -167,7 +185,11 @@ public class NotificationService extends NotificationListenerService {
|
||||
try {
|
||||
Intent intent = new Intent(context, NotificationService.class);
|
||||
intent.setAction("ACTION_START_MONITORING");
|
||||
context.startService(intent);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent);
|
||||
} else {
|
||||
context.startService(intent);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "启动监听失败", e);
|
||||
}
|
||||
@@ -183,8 +205,11 @@ public class NotificationService extends NotificationListenerService {
|
||||
if (messageInfo == null) return;
|
||||
|
||||
NotificationExtractor.Result result = NotificationExtractor.extract(sbn);
|
||||
Log.d(TAG, "onNotificationPosted: pkg=" + sbn.getPackageName()
|
||||
+ " title=" + result.title + " content=" + result.content);
|
||||
|
||||
if (TextUtils.isEmpty(result.title) && TextUtils.isEmpty(result.content)) {
|
||||
Log.d(TAG, "skip empty notification: " + sbn.getPackageName());
|
||||
NotificationActivity.sendMessage("[" + messageInfo.getAppName() + "] 通知内容为空,跳过");
|
||||
return;
|
||||
}
|
||||
@@ -194,50 +219,60 @@ public class NotificationService extends NotificationListenerService {
|
||||
}
|
||||
|
||||
NotificationActivity.sendMessage(result.title + " " + result.content);
|
||||
submitNotification(messageInfo, result);
|
||||
submitNotification(messageInfo, result, "notification");
|
||||
}
|
||||
|
||||
private void submitNotification(MessageInfo messageInfo, NotificationExtractor.Result result) {
|
||||
// 调试用:暂时跳过登录检查
|
||||
// if (!tokenManager.isLoggedIn()) {
|
||||
// NotificationActivity.sendMessage("未登录,请先登录");
|
||||
// return;
|
||||
// }
|
||||
private void submitNotification(MessageInfo messageInfo, NotificationExtractor.Result result,
|
||||
String debugSource) {
|
||||
DebugForwarder.forward(this, messageInfo, result.title, result.content,
|
||||
result.timestamp, debugSource);
|
||||
|
||||
// 调试用:本地模式可不绑定服务器银行账户
|
||||
// if (TextUtils.isEmpty(messageInfo.getBankInfoId())) {
|
||||
// NotificationActivity.sendMessage("该应用未关联银行账户,请先配置");
|
||||
// return;
|
||||
// }
|
||||
if (!AppConfig.ENABLE_SERVER_UPLOAD) {
|
||||
recordLocalCapture();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
NotificationActivity.sendMessage("未登录,请先登录");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(messageInfo.getBankInfoId())) {
|
||||
NotificationActivity.sendMessage("该应用未关联银行账户,请先配置");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
// 根据文档,bankInfoId 是建议字段
|
||||
jsonObject.put("bankInfoId", messageInfo.getBankInfoId());
|
||||
|
||||
// data 字段包含通知内容
|
||||
JSONObject dataObject = new JSONObject();
|
||||
dataObject.put("title", result.title);
|
||||
dataObject.put("context", result.content);
|
||||
dataObject.put("timestamp", result.timestamp);
|
||||
jsonObject.put("data", dataObject);
|
||||
|
||||
// 保留原有字段用于兼容性
|
||||
jsonObject.put("name", messageInfo.getName());
|
||||
jsonObject.put("code", messageInfo.getCode());
|
||||
jsonObject.put("remark", messageInfo.getRemark());
|
||||
jsonObject.put("appName", messageInfo.getAppName());
|
||||
jsonObject.put("packageName", messageInfo.getPackageName());
|
||||
|
||||
String payload = jsonObject.toString();
|
||||
NotificationActivity.sendMessage("准备发送服务器:成功");
|
||||
retryManager.enqueue(payload);
|
||||
retryManager.enqueue(jsonObject.toString());
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "JSON构建失败", e);
|
||||
NotificationActivity.sendMessage("JSON构建失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void recordLocalCapture() {
|
||||
if (retryManager != null) {
|
||||
retryManager.recordLocalCapture();
|
||||
updateForegroundNotification(retryManager.getTotalCount());
|
||||
}
|
||||
NotificationActivity.updateUI();
|
||||
}
|
||||
|
||||
private void performUpload(String jsonPayload, RetryManager.UploadResultListener listener) {
|
||||
acquireWakeLockForUpload();
|
||||
|
||||
@@ -345,11 +380,12 @@ public class NotificationService extends NotificationListenerService {
|
||||
}
|
||||
}
|
||||
|
||||
private Notification buildForegroundNotification(int uploadedCount, boolean monitoring) {
|
||||
private Notification buildForegroundNotification(int count, boolean monitoring) {
|
||||
String title = monitoring ? "持续监听中" : "通知监听服务";
|
||||
String countLabel = AppConfig.ENABLE_SERVER_UPLOAD ? "已上传" : "已抓取";
|
||||
String text = monitoring
|
||||
? "已上传 " + uploadedCount + " 条 · 持续运行中"
|
||||
: "已上传 " + uploadedCount + " 条";
|
||||
? countLabel + " " + count + " 条 · 持续运行中"
|
||||
: countLabel + " " + count + " 条";
|
||||
|
||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
@@ -361,10 +397,10 @@ public class NotificationService extends NotificationListenerService {
|
||||
.build();
|
||||
}
|
||||
|
||||
private void updateForegroundNotification(int uploadedCount) {
|
||||
private void updateForegroundNotification(int count) {
|
||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||
if (nm != null) {
|
||||
nm.notify(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(uploadedCount, isMonitoring));
|
||||
nm.notify(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(count, isMonitoring));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user