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:
2026-07-02 16:50:12 +08:00
parent 7c80b7073a
commit 125dfe583b
33 changed files with 2164 additions and 88 deletions

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="false"
android:label="@string/module_name">
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="@string/xposed_description" />
<meta-data
android:name="xposedminversion"
android:value="82" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />
</application>
</manifest>

View File

@@ -0,0 +1 @@
com.miraclegarden.smsmessage.xposed.MainHook

View File

@@ -0,0 +1,18 @@
package com.miraclegarden.smsmessage.xposed;
public final class HookBridge {
public static final String TARGET_APP_PACKAGE = "com.miraclegarden.smsmessage";
public static final String ACTION_HOOK_MESSAGE = "com.miraclegarden.smsmessage.action.HOOK_MESSAGE";
public static final String EXTRA_PACKAGE_NAME = "packageName";
public static final String EXTRA_TITLE = "title";
public static final String EXTRA_CONTENT = "content";
public static final String EXTRA_TIMESTAMP = "timestamp";
public static final String EXTRA_SOURCE = "source";
public static final String SOURCE_XPOSED_SQLITE = "xposed_sqlite";
public static final String SOURCE_XPOSED_WECHAT = "xposed_wechat";
public static final String SOURCE_XPOSED_TELEGRAM = "xposed_telegram";
private HookBridge() {
}
}

View File

@@ -0,0 +1,36 @@
package com.miraclegarden.smsmessage.xposed;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import de.robv.android.xposed.XposedBridge;
public final class HookForwarder {
private static final String TAG = "notiMessageHook";
private HookForwarder() {
}
public static void forward(Context context, String packageName, String title,
String content, String source) {
if (context == null || TextUtils.isEmpty(packageName) || TextUtils.isEmpty(content)) {
return;
}
try {
Intent intent = new Intent(HookBridge.ACTION_HOOK_MESSAGE);
intent.setPackage(HookBridge.TARGET_APP_PACKAGE);
intent.putExtra(HookBridge.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(HookBridge.EXTRA_TITLE,
TextUtils.isEmpty(title) ? packageName : title);
intent.putExtra(HookBridge.EXTRA_CONTENT, content);
intent.putExtra(HookBridge.EXTRA_TIMESTAMP, System.currentTimeMillis());
intent.putExtra(HookBridge.EXTRA_SOURCE, source);
context.sendBroadcast(intent);
} catch (Throwable t) {
XposedBridge.log(TAG + " sendBroadcast failed: " + t.getMessage());
}
}
}

View File

@@ -0,0 +1,39 @@
package com.miraclegarden.smsmessage.xposed;
import com.miraclegarden.smsmessage.xposed.hook.SqliteMessageHook;
import com.miraclegarden.smsmessage.xposed.hook.TelegramMessageHook;
import com.miraclegarden.smsmessage.xposed.hook.WeChatMessageHook;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class MainHook implements IXposedHookLoadPackage {
private static final String WECHAT_PACKAGE = "com.tencent.mm";
private static final String TELEGRAM_PACKAGE = "org.telegram.messenger";
private static final String TELEGRAM_WEB_PACKAGE = "org.telegram.messenger.web";
private static final String MAIN_APP_PACKAGE = "com.miraclegarden.smsmessage";
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (lpparam == null || lpparam.packageName == null) {
return;
}
if (MAIN_APP_PACKAGE.equals(lpparam.packageName)) {
return;
}
if (WECHAT_PACKAGE.equals(lpparam.packageName)) {
WeChatMessageHook.install(lpparam);
return;
}
if (TELEGRAM_PACKAGE.equals(lpparam.packageName)
|| TELEGRAM_WEB_PACKAGE.equals(lpparam.packageName)) {
TelegramMessageHook.install(lpparam);
return;
}
SqliteMessageHook.install(lpparam);
}
}

View File

@@ -0,0 +1,123 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.content.ContentValues;
import android.content.Context;
import android.text.TextUtils;
import com.miraclegarden.smsmessage.xposed.HookBridge;
import com.miraclegarden.smsmessage.xposed.HookForwarder;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public final class SqliteMessageHook {
private static final String TAG = "notiMessageHook/Sqlite";
private static final String[] SQLITE_CLASSES = {
"android.database.sqlite.SQLiteDatabase",
"androidx.sqlite.db.framework.FrameworkSQLiteDatabase"
};
private SqliteMessageHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
for (String sqliteClass : SQLITE_CLASSES) {
try {
hookInsert(lpparam, sqliteClass, "insert");
hookInsert(lpparam, sqliteClass, "insertWithOnConflict");
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + sqliteClass + ": " + t.getMessage());
}
}
}
private static void hookInsert(XC_LoadPackage.LoadPackageParam lpparam,
String className, String methodName) {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
methodName,
String.class,
String.class,
ContentValues.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
handleInsert(lpparam.packageName, param);
}
}
);
}
private static void handleInsert(String packageName, XC_MethodHook.MethodHookParam param) {
if (param.args == null || param.args.length < 3) {
return;
}
Object tableArg = param.args[0];
Object valuesArg = param.args[2];
if (!(tableArg instanceof String) || !(valuesArg instanceof ContentValues)) {
return;
}
String table = ((String) tableArg).toLowerCase();
if (!isMessageTable(table)) {
return;
}
ContentValues values = (ContentValues) valuesArg;
String title = firstNonEmpty(values, "talker", "sender", "from", "title", "name");
String content = firstNonEmpty(values, "content", "text", "body", "msg", "message");
if (TextUtils.isEmpty(content)) {
return;
}
Context context = getContext(param.thisObject);
if (context == null) {
return;
}
HookForwarder.forward(context, packageName, title, content,
HookBridge.SOURCE_XPOSED_SQLITE);
}
private static boolean isMessageTable(String table) {
return "message".equals(table)
|| "messages".equals(table)
|| table.endsWith("_message")
|| table.contains("message");
}
private static String firstNonEmpty(ContentValues values, String... keys) {
for (String key : keys) {
String value = values.getAsString(key);
if (!TextUtils.isEmpty(value)) {
return value;
}
}
return "";
}
private static Context getContext(Object database) {
try {
Object ctx = XposedHelpers.callMethod(database, "getContext");
if (ctx instanceof Context) {
return (Context) ctx;
}
} catch (Throwable ignored) {
}
try {
Class<?> activityThread = XposedHelpers.findClass("android.app.ActivityThread", null);
Object app = XposedHelpers.callStaticMethod(activityThread, "currentApplication");
if (app instanceof Context) {
return (Context) app;
}
} catch (Throwable ignored) {
}
return null;
}
}

View File

@@ -0,0 +1,479 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.content.Context;
import android.text.TextUtils;
import com.miraclegarden.smsmessage.xposed.HookBridge;
import com.miraclegarden.smsmessage.xposed.HookForwarder;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.List;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* Telegram 专用 Hook监听 NotificationCenter.didReceiveNewMessages
* 从 MessageObject 提取已解密的 messageText。
*/
public final class TelegramMessageHook {
private static final String TAG = "notiMessageHook/Telegram";
private static final String NOTIFICATION_CENTER = "org.telegram.messenger.NotificationCenter";
private static final String MESSAGE_OBJECT = "org.telegram.messenger.MessageObject";
private static final int DEDUP_SIZE = 512;
private static final ArrayDeque<String> RECENT_KEYS = new ArrayDeque<>();
private static final HashSet<String> RECENT_SET = new HashSet<>();
private TelegramMessageHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> notificationCenter = XposedHelpers.findClass(
NOTIFICATION_CENTER, lpparam.classLoader);
final int didReceiveNewMessages = XposedHelpers.getStaticIntField(
notificationCenter, "didReceiveNewMessages");
XposedHelpers.findAndHookMethod(
notificationCenter,
"postNotificationName",
int.class,
Object[].class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
int id = (int) param.args[0];
if (id != didReceiveNewMessages) {
return;
}
Object[] args = (Object[]) param.args[1];
if (args == null || args.length == 0) {
return;
}
Context context = getContext();
if (context == null) {
return;
}
for (Object arg : args) {
if (arg instanceof List) {
processMessageList(context, lpparam.packageName, (List<?>) arg);
}
}
}
}
);
XposedBridge.log(TAG + " installed for " + lpparam.packageName
+ ", didReceiveNewMessages=" + didReceiveNewMessages);
} catch (Throwable t) {
XposedBridge.log(TAG + " install failed: " + t.getMessage());
installMessageObjectFallback(lpparam);
}
}
/** NotificationCenter Hook 失败时的兜底Hook MessageObject 构造完成后的消息。 */
private static void installMessageObjectFallback(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> messageObjectClass = XposedHelpers.findClass(
MESSAGE_OBJECT, lpparam.classLoader);
XposedBridge.hookAllConstructors(messageObjectClass, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Context context = getContext();
if (context == null) {
return;
}
forwardMessageObject(context, lpparam.packageName, param.thisObject);
}
});
XposedBridge.log(TAG + " fallback MessageObject hook installed for "
+ lpparam.packageName);
} catch (Throwable t) {
XposedBridge.log(TAG + " fallback install failed: " + t.getMessage());
}
}
private static void processMessageList(Context context, String packageName, List<?> messages) {
for (Object item : messages) {
if (item == null) {
continue;
}
if (!MESSAGE_OBJECT.equals(item.getClass().getName())) {
continue;
}
forwardMessageObject(context, packageName, item);
}
}
private static void forwardMessageObject(Context context, String packageName, Object messageObject) {
try {
Object messageOwner = XposedHelpers.getObjectField(messageObject, "messageOwner");
if (messageOwner == null) {
return;
}
if (XposedHelpers.getBooleanField(messageOwner, "out")) {
return;
}
int messageId = XposedHelpers.getIntField(messageOwner, "id");
long dialogId = extractDialogId(messageObject, messageOwner);
if (!rememberMessage(dialogId, messageId)) {
return;
}
String content = extractContent(messageObject, messageOwner);
if (TextUtils.isEmpty(content)) {
return;
}
String title = extractTitle(messageObject, messageOwner, dialogId);
long timestamp = XposedHelpers.getIntField(messageOwner, "date") * 1000L;
if (timestamp <= 0) {
timestamp = System.currentTimeMillis();
}
String sender = extractSenderName(messageObject);
String body = content;
if (!TextUtils.isEmpty(sender) && !sender.equals(title)) {
body = sender + ": " + content;
}
HookForwarder.forward(context, packageName, title, body,
HookBridge.SOURCE_XPOSED_TELEGRAM);
} catch (Throwable t) {
XposedBridge.log(TAG + " forward failed: " + t.getMessage());
}
}
private static long extractDialogId(Object messageObject, Object messageOwner) {
try {
return ((Number) XposedHelpers.callMethod(messageObject, "getDialogId")).longValue();
} catch (Throwable ignored) {
}
try {
return XposedHelpers.getLongField(messageOwner, "dialog_id");
} catch (Throwable ignored) {
}
return 0L;
}
private static String extractContent(Object messageObject, Object messageOwner) {
String caption = firstNonEmpty(
safeText(XposedHelpers.getObjectField(messageObject, "caption")),
safeText(XposedHelpers.getObjectField(messageOwner, "message")),
safeText(callMethodSafe(messageObject, "getCaption"))
);
String messageText = firstNonEmpty(
safeText(XposedHelpers.getObjectField(messageObject, "messageText")),
safeText(callMethodSafe(messageObject, "getMessageText"))
);
String mediaLabel = detectMediaLabel(messageObject, messageOwner);
if (!TextUtils.isEmpty(caption)) {
if (!TextUtils.isEmpty(mediaLabel)) {
return mediaLabel + "\n" + caption;
}
if (!TextUtils.isEmpty(messageText) && !isMediaPlaceholder(messageText)
&& !messageText.equals(caption)) {
return messageText + "\n" + caption;
}
return caption;
}
if (!TextUtils.isEmpty(messageText) && !isMediaPlaceholder(messageText)) {
return messageText;
}
if (!TextUtils.isEmpty(mediaLabel)) {
return mediaLabel;
}
if (!TextUtils.isEmpty(messageText)) {
return messageText;
}
return "";
}
private static String detectMediaLabel(Object messageObject, Object messageOwner) {
try {
if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isSticker"))) {
return "[贴纸]";
}
} catch (Throwable ignored) {
}
try {
if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isPhoto"))) {
return "[图片]";
}
} catch (Throwable ignored) {
}
try {
if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isVideo"))) {
return "[视频]";
}
} catch (Throwable ignored) {
}
try {
if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isGif"))) {
return "[GIF]";
}
} catch (Throwable ignored) {
}
try {
if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isVoice"))) {
return "[语音]";
}
} catch (Throwable ignored) {
}
try {
if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isMusic"))) {
return "[音频]";
}
} catch (Throwable ignored) {
}
Object media = XposedHelpers.getObjectField(messageOwner, "media");
if (media == null) {
return "";
}
String mediaClass = media.getClass().getName();
if (mediaClass.contains("Photo")) {
return "[图片]";
}
if (mediaClass.contains("Document")) {
return "[文件]";
}
if (mediaClass.contains("Video")) {
return "[视频]";
}
if (mediaClass.contains("Geo")) {
return "[位置]";
}
if (mediaClass.contains("Contact")) {
return "[联系人]";
}
return "[媒体消息]";
}
private static boolean isMediaPlaceholder(String text) {
if (TextUtils.isEmpty(text)) {
return true;
}
String value = text.trim();
return value.equals("图片")
|| value.equals("Photo")
|| value.equals("照片")
|| value.equals("贴纸")
|| value.equals("Sticker")
|| value.equals("Video")
|| value.equals("视频")
|| value.equals("GIF")
|| value.equals("动画")
|| value.equals("文件")
|| value.equals("Document")
|| value.equals("语音")
|| value.equals("Voice")
|| value.equals("音频")
|| value.equals("Audio")
|| value.equals("位置")
|| value.equals("Location")
|| value.startsWith("[媒体")
|| value.startsWith("[图片")
|| value.startsWith("[贴纸")
|| value.startsWith("[视频");
}
private static String firstNonEmpty(String... values) {
for (String value : values) {
if (!TextUtils.isEmpty(value)) {
return value;
}
}
return "";
}
private static String callMethodSafe(Object target, String methodName) {
try {
return safeText(XposedHelpers.callMethod(target, methodName));
} catch (Throwable ignored) {
return "";
}
}
private static String extractTitle(Object messageObject, Object messageOwner, long dialogId) {
String viaController = resolveTitleViaController(messageObject, dialogId);
if (!TextUtils.isEmpty(viaController)) {
return viaController;
}
try {
Object dialogName = XposedHelpers.callMethod(messageObject, "getName");
String name = safeText(dialogName);
if (!TextUtils.isEmpty(name)) {
return name;
}
} catch (Throwable ignored) {
}
try {
Object chat = XposedHelpers.callMethod(messageObject, "getChat");
if (chat != null) {
String chatTitle = safeText(XposedHelpers.getObjectField(chat, "title"));
if (!TextUtils.isEmpty(chatTitle)) {
return chatTitle;
}
}
} catch (Throwable ignored) {
}
String localName = safeText(XposedHelpers.getObjectField(messageObject, "localName"));
if (!TextUtils.isEmpty(localName)) {
return localName;
}
return dialogId != 0 ? ("对话 " + dialogId) : "Telegram";
}
private static String resolveTitleViaController(Object messageObject, long dialogId) {
if (dialogId == 0) {
return null;
}
try {
int account = XposedHelpers.getIntField(messageObject, "currentAccount");
ClassLoader cl = messageObject.getClass().getClassLoader();
Class<?> mcClass = XposedHelpers.findClass(
"org.telegram.messenger.MessagesController", cl);
Object mc = XposedHelpers.callStaticMethod(mcClass, "getInstance", account);
Object title = null;
try {
title = XposedHelpers.callMethod(mc, "getPeerTitle", dialogId, false);
} catch (Throwable ignored) {
title = XposedHelpers.callMethod(mc, "getPeerTitle", dialogId);
}
String text = safeText(title);
if (!TextUtils.isEmpty(text)) {
return text;
}
if (dialogId < 0) {
Object chat = XposedHelpers.callMethod(mc, "getChat", -dialogId);
if (chat == null) {
chat = XposedHelpers.callMethod(mc, "getChat", dialogId);
}
if (chat != null) {
text = safeText(XposedHelpers.getObjectField(chat, "title"));
if (!TextUtils.isEmpty(text)) {
return text;
}
}
} else {
Object user = XposedHelpers.callMethod(mc, "getUser", dialogId);
text = formatUserName(user);
if (!TextUtils.isEmpty(text)) {
return text;
}
}
} catch (Throwable t) {
XposedBridge.log(TAG + " resolveTitleViaController: " + t.getMessage());
}
return null;
}
private static String formatUserName(Object user) {
if (user == null) {
return "";
}
String first = safeText(XposedHelpers.getObjectField(user, "first_name"));
String last = safeText(XposedHelpers.getObjectField(user, "last_name"));
String username = safeText(XposedHelpers.getObjectField(user, "username"));
StringBuilder sb = new StringBuilder();
if (!TextUtils.isEmpty(first)) {
sb.append(first);
}
if (!TextUtils.isEmpty(last)) {
if (sb.length() > 0) {
sb.append(' ');
}
sb.append(last);
}
if (sb.length() > 0) {
return sb.toString();
}
if (!TextUtils.isEmpty(username)) {
return "@" + username;
}
return "";
}
private static String safeText(Object value) {
if (value == null) {
return "";
}
if (!(value instanceof CharSequence) && !(value instanceof Number) && !(value instanceof Boolean)) {
String className = value.getClass().getName();
if (className.startsWith("org.telegram.tgnet.")
|| className.contains("TLRPC$")
|| value.getClass().getName().contains("Peer")) {
return "";
}
}
String text = String.valueOf(value).trim();
if (text.contains("TLRPC$") || text.contains("org.telegram.tgnet.")) {
return "";
}
return text;
}
private static String extractSenderName(Object messageObject) {
try {
String fromName = safeText(XposedHelpers.callMethod(messageObject, "getFromName"));
if (!TextUtils.isEmpty(fromName)) {
return fromName;
}
} catch (Throwable ignored) {
}
return "";
}
private static synchronized boolean rememberMessage(long dialogId, int messageId) {
if (messageId <= 0) {
return true;
}
String key = dialogId + ":" + messageId;
if (RECENT_SET.contains(key)) {
return false;
}
RECENT_SET.add(key);
RECENT_KEYS.addLast(key);
while (RECENT_KEYS.size() > DEDUP_SIZE) {
String oldest = RECENT_KEYS.removeFirst();
RECENT_SET.remove(oldest);
}
return true;
}
private static Context getContext() {
try {
Class<?> activityThread = XposedHelpers.findClass("android.app.ActivityThread", null);
Object app = XposedHelpers.callStaticMethod(activityThread, "currentApplication");
if (app instanceof Context) {
return (Context) app;
}
} catch (Throwable ignored) {
}
return null;
}
}

View File

@@ -0,0 +1,98 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.content.ContentValues;
import android.content.Context;
import android.text.TextUtils;
import com.miraclegarden.smsmessage.xposed.HookBridge;
import com.miraclegarden.smsmessage.xposed.HookForwarder;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public final class WeChatMessageHook {
private static final String TAG = "notiMessageHook/WeChat";
private static final String WCDB_CLASS = "com.tencent.wcdb.database.SQLiteDatabase";
private WeChatMessageHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
try {
hookWcdbInsert(lpparam, "insert");
hookWcdbInsert(lpparam, "insertWithOnConflict");
XposedBridge.log(TAG + " installed for " + lpparam.packageName);
} catch (Throwable t) {
XposedBridge.log(TAG + " install failed: " + t.getMessage());
}
}
private static void hookWcdbInsert(XC_LoadPackage.LoadPackageParam lpparam, String methodName) {
XposedHelpers.findAndHookMethod(
WCDB_CLASS,
lpparam.classLoader,
methodName,
String.class,
String.class,
ContentValues.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
handleWeChatInsert(param);
}
}
);
}
private static void handleWeChatInsert(XC_MethodHook.MethodHookParam param) {
if (param.args == null || param.args.length < 3) {
return;
}
Object tableArg = param.args[0];
Object valuesArg = param.args[2];
if (!(tableArg instanceof String) || !(valuesArg instanceof ContentValues)) {
return;
}
String table = (String) tableArg;
if (!"message".equalsIgnoreCase(table)) {
return;
}
ContentValues values = (ContentValues) valuesArg;
String talker = values.getAsString("talker");
String content = values.getAsString("content");
Integer type = values.getAsInteger("type");
if (TextUtils.isEmpty(content)) {
return;
}
if (type != null && (type == 10000 || type == 10002)) {
return;
}
Context context = getContext();
if (context == null) {
return;
}
HookForwarder.forward(context, "com.tencent.mm", talker, content,
HookBridge.SOURCE_XPOSED_WECHAT);
}
private static Context getContext() {
try {
Class<?> activityThread = XposedHelpers.findClass("android.app.ActivityThread", null);
Object app = XposedHelpers.callStaticMethod(activityThread, "currentApplication");
if (app instanceof Context) {
return (Context) app;
}
} catch (Throwable ignored) {
}
return null;
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="xposed_scope">
<item>org.telegram.messenger</item>
<item>org.telegram.messenger.web</item>
<item>com.tencent.mm</item>
<item>com.google.android.gm</item>
</string-array>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="module_name">notiMessage Hook</string>
<string name="xposed_description">Hook 目标 App 消息,前台也能同步到 notiMessage。需在 LSPosed 中勾选目标 App 并启用本模块。</string>
</resources>