feat(mmp): 领取人ID/模块版本检测,并修正状态条误报红
展示 receiverId 与 activityPoolId;检测 Hook 是否最新;按真实时间排序;有心跳与定时拉取时不再因久无新数据爆红。
This commit is contained in:
@@ -4,11 +4,18 @@ 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 ACTION_HOOK_STATUS = "com.miraclegarden.smsmessage.action.HOOK_STATUS";
|
||||
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 EXTRA_MODULE_VERSION_CODE = "moduleVersionCode";
|
||||
public static final String EXTRA_MODULE_VERSION_NAME = "moduleVersionName";
|
||||
public static final String EXTRA_HOST_PACKAGE = "hostPackage";
|
||||
/** 与 xposed-module/build.gradle 保持同步 */
|
||||
public static final int MODULE_VERSION_CODE = 3;
|
||||
public static final String MODULE_VERSION_NAME = "1.2.0";
|
||||
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";
|
||||
|
||||
@@ -33,4 +33,23 @@ public final class HookForwarder {
|
||||
XposedBridge.log(TAG + " sendBroadcast failed: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 向主 App 汇报当前进程内 Hook 模块版本(用于检测是否最新) */
|
||||
public static void reportStatus(Context context, String hostPackage, String source) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Intent intent = new Intent(HookBridge.ACTION_HOOK_STATUS);
|
||||
intent.setPackage(HookBridge.TARGET_APP_PACKAGE);
|
||||
intent.putExtra(HookBridge.EXTRA_HOST_PACKAGE, hostPackage);
|
||||
intent.putExtra(HookBridge.EXTRA_SOURCE, source);
|
||||
intent.putExtra(HookBridge.EXTRA_MODULE_VERSION_CODE, HookBridge.MODULE_VERSION_CODE);
|
||||
intent.putExtra(HookBridge.EXTRA_MODULE_VERSION_NAME, HookBridge.MODULE_VERSION_NAME);
|
||||
intent.putExtra(HookBridge.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
||||
context.sendBroadcast(intent);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " reportStatus failed: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,9 +77,17 @@ public final class TngMoneyPacketHook {
|
||||
private static volatile long sPacketDedupMs = DEFAULT_PACKET_DEDUP_MS;
|
||||
private static volatile long sDetailGapMs = DEFAULT_DETAIL_GAP_MS;
|
||||
private static volatile boolean sOpenHistoryIfNoTemplate = true;
|
||||
/** 断线/拉历史失败时:自动进一下历史页再返回,重建模板与连接 */
|
||||
private static volatile boolean sAutoBounceHistoryOnDisconnect = true;
|
||||
private static volatile long sLastSettingsPollAt;
|
||||
private static volatile long sLastAutoHistoryAt;
|
||||
private static volatile boolean sAutoHistoryRunning;
|
||||
private static volatile long sLastHistoryBounceAt;
|
||||
private static volatile boolean sBounceHistoryPending;
|
||||
private static volatile long sLastHookStatusAt;
|
||||
private static final long HISTORY_BOUNCE_COOLDOWN_MS = 45_000L;
|
||||
private static final long HISTORY_BOUNCE_FINISH_DELAY_MS = 2_200L;
|
||||
private static final long HOOK_STATUS_COOLDOWN_MS = 20_000L;
|
||||
/** 曾拿到过 session,之后变空 / RPC 鉴权失败 → 视为断线,恢复后强制重拉 */
|
||||
private static volatile boolean sHadSession;
|
||||
private static volatile boolean sSessionDisconnected;
|
||||
@@ -119,7 +127,37 @@ public final class TngMoneyPacketHook {
|
||||
hookLoginStorage(lpparam);
|
||||
hookActivityResumeForAutoHistory(lpparam);
|
||||
startSettingsPoller();
|
||||
XposedBridge.log(TAG + " installed for " + lpparam.packageName);
|
||||
startAutoHistoryPoller();
|
||||
reportHookAlive("install");
|
||||
XposedBridge.log(TAG + " installed for " + lpparam.packageName
|
||||
+ " mod=" + HookBridge.MODULE_VERSION_NAME
|
||||
+ "/" + HookBridge.MODULE_VERSION_CODE);
|
||||
}
|
||||
|
||||
/** 按历史冷却定时拉列表 + 心跳,避免无新红包时状态条误报「久无数据」 */
|
||||
private static void startAutoHistoryPoller() {
|
||||
Thread t = new Thread(() -> {
|
||||
while (true) {
|
||||
try {
|
||||
reportHookAlive("poll");
|
||||
scheduleAutoHistory("poll");
|
||||
long sleepMs = Math.max(5_000L, sHistoryCooldownMs);
|
||||
Thread.sleep(sleepMs);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (Throwable ignored) {
|
||||
try {
|
||||
Thread.sleep(Math.max(5_000L, sHistoryCooldownMs));
|
||||
} catch (InterruptedException ie2) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "TngMmp-auto-hist-poll");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
/** 后台轮询领取台 /api/mmp/settings,约 10 秒内生效 */
|
||||
@@ -186,20 +224,24 @@ public final class TngMoneyPacketHook {
|
||||
long dedupMs = Math.max(0, json.optInt("dedupSec", 3)) * 1000L;
|
||||
long gapMs = Math.max(0, json.optInt("detailGapMs", 80));
|
||||
boolean openHistory = json.optBoolean("openHistoryIfNoTemplate", true);
|
||||
boolean bounceHistory = json.optBoolean("autoBounceHistoryOnDisconnect", true);
|
||||
boolean changed = historyMs != sHistoryCooldownMs
|
||||
|| detailMs != sDetailCooldownMs
|
||||
|| dedupMs != sPacketDedupMs
|
||||
|| gapMs != sDetailGapMs
|
||||
|| openHistory != sOpenHistoryIfNoTemplate;
|
||||
|| openHistory != sOpenHistoryIfNoTemplate
|
||||
|| bounceHistory != sAutoBounceHistoryOnDisconnect;
|
||||
sHistoryCooldownMs = historyMs;
|
||||
sDetailCooldownMs = detailMs;
|
||||
sPacketDedupMs = dedupMs;
|
||||
sDetailGapMs = gapMs;
|
||||
sOpenHistoryIfNoTemplate = openHistory;
|
||||
sAutoBounceHistoryOnDisconnect = bounceHistory;
|
||||
if (changed) {
|
||||
XposedBridge.log(TAG + " settings applied hist=" + (historyMs / 1000)
|
||||
+ "s detail=" + (detailMs / 1000) + "s dedup=" + (dedupMs / 1000)
|
||||
+ "s gap=" + gapMs + "ms openHist=" + openHistory
|
||||
+ " bounceHist=" + bounceHistory
|
||||
+ " from " + urlStr);
|
||||
}
|
||||
return true;
|
||||
@@ -400,7 +442,7 @@ public final class TngMoneyPacketHook {
|
||||
}
|
||||
MmpSnapshot probe = parseSnapshot(json);
|
||||
enrichSnapshotFromJavaResult(result, probe);
|
||||
if (probe != null && !probe.claims.isEmpty()) {
|
||||
if (probe != null && (!probe.claims.isEmpty() || snapshotHasPacketMeta(probe))) {
|
||||
if (detailReq != null) {
|
||||
sTemplateDetailRequest = cloneRequestShallow(detailReq);
|
||||
sLoginDonorRequest = detailReq;
|
||||
@@ -979,16 +1021,20 @@ public final class TngMoneyPacketHook {
|
||||
}
|
||||
|
||||
private static void rememberPacketData(String packetId, MmpSnapshot snapshot, Object result) {
|
||||
if (snapshot == null || snapshot.claims == null || snapshot.claims.isEmpty()) {
|
||||
if (snapshot == null) {
|
||||
return;
|
||||
}
|
||||
boolean hasClaims = snapshot.claims != null && !snapshot.claims.isEmpty();
|
||||
if (!hasClaims && !snapshotHasPacketMeta(snapshot)) {
|
||||
return;
|
||||
}
|
||||
String id = !TextUtils.isEmpty(packetId) ? packetId : snapshot.packetId;
|
||||
if (TextUtils.isEmpty(id) || !looksLikeActivityId(id)) {
|
||||
// 指纹 id 也记,避免反复刷
|
||||
if (!TextUtils.isEmpty(id)) {
|
||||
if (!TextUtils.isEmpty(id) && hasClaims) {
|
||||
PACKETS_WITH_DATA.add(id);
|
||||
}
|
||||
} else {
|
||||
} else if (hasClaims) {
|
||||
PACKETS_WITH_DATA.add(id);
|
||||
}
|
||||
if (result != null
|
||||
@@ -1000,7 +1046,7 @@ public final class TngMoneyPacketHook {
|
||||
if (!snapshot.finished) {
|
||||
snapshot.finished = evaluateFinished(snapshot);
|
||||
}
|
||||
if (snapshot.finished && !TextUtils.isEmpty(id)) {
|
||||
if (snapshot.finished && hasClaims && !TextUtils.isEmpty(id)) {
|
||||
if (COMPLETED_PACKETS.add(id)) {
|
||||
XposedBridge.log(TAG + " packet DONE stop-refresh id=" + id
|
||||
+ " claims=" + snapshot.claims.size()
|
||||
@@ -1068,7 +1114,13 @@ public final class TngMoneyPacketHook {
|
||||
}
|
||||
|
||||
private static void notePossibleDisconnectFromEmpty(Object result, String activityId) {
|
||||
// totalCount:0 + 无领取人 常见于缺登录态
|
||||
// 新发红包无人领取时 activityPoolInfos 为空、totalCount/claimedCount 也常为 0,不是断线
|
||||
if (looksLikeValidEmptyPacket(result, activityId)) {
|
||||
XposedBridge.log(TAG + " empty claims but valid summary id=" + activityId
|
||||
+ " → keep (0 claimants)");
|
||||
return;
|
||||
}
|
||||
// totalCount:0 + 无领取人 且无有效摘要时,才怀疑缺登录态
|
||||
String totalCnt = stringField(result, "totalCount", "getTotalCount");
|
||||
if ("0".equals(totalCnt) && sHadSession) {
|
||||
XposedBridge.log(TAG + " empty claims+totalCount0 id=" + activityId
|
||||
@@ -1078,6 +1130,64 @@ public final class TngMoneyPacketHook {
|
||||
}
|
||||
}
|
||||
|
||||
/** 详情有 summary(ACTIVE / 有总额)但领取名单为空:刚发出去、还没人领 */
|
||||
private static boolean looksLikeValidEmptyPacket(Object result, String activityId) {
|
||||
if (result == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Object summary = null;
|
||||
try {
|
||||
summary = XposedHelpers.callMethod(result, "getSummaryInfo");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
if (summary == null) {
|
||||
try {
|
||||
summary = XposedHelpers.getObjectField(result, "summaryInfo");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
if (summary == null) {
|
||||
return false;
|
||||
}
|
||||
String id = firstNonEmpty(
|
||||
stringField(summary, "activityId", "getActivityId"),
|
||||
activityId);
|
||||
if (!looksLikeActivityId(id)) {
|
||||
return false;
|
||||
}
|
||||
String status = stringField(summary, "activityStatus", "getActivityStatus");
|
||||
if (!TextUtils.isEmpty(status)) {
|
||||
String up = status.toUpperCase(Locale.US);
|
||||
if (up.contains("ACTIVE") || up.contains("OPEN") || up.contains("PROGRESS")
|
||||
|| up.contains("FINISH") || up.contains("COMPLETE")
|
||||
|| up.contains("EXPIRE")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
String total = null;
|
||||
try {
|
||||
Object money = XposedHelpers.callMethod(summary, "getTotalAmount");
|
||||
if (money != null) {
|
||||
total = normalizeMoneyText(String.valueOf(
|
||||
XposedHelpers.callMethod(money, "getAmount")));
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return !TextUtils.isEmpty(total);
|
||||
} catch (Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean snapshotHasPacketMeta(MmpSnapshot snapshot) {
|
||||
return snapshot != null
|
||||
&& looksLikeActivityId(snapshot.packetId)
|
||||
&& (!TextUtils.isEmpty(snapshot.totalAmount)
|
||||
|| !TextUtils.isEmpty(snapshot.activityStatus)
|
||||
|| !TextUtils.isEmpty(snapshot.senderName));
|
||||
}
|
||||
|
||||
private static boolean fetchDetailByActivityId(String activityId, String senderUserId) {
|
||||
ClassLoader cl = sAppClassLoader;
|
||||
ensureRpcTaskAndLogin(cl);
|
||||
@@ -1131,13 +1241,31 @@ public final class TngMoneyPacketHook {
|
||||
}
|
||||
MmpSnapshot probe = parseSnapshot(json);
|
||||
enrichSnapshotFromJavaResult(result, probe);
|
||||
if (probe == null || probe.claims.isEmpty()) {
|
||||
if (probe == null) {
|
||||
XposedBridge.log(TAG + " auto-detail parse null id=" + activityId);
|
||||
return false;
|
||||
}
|
||||
if (TextUtils.isEmpty(probe.packetId)) {
|
||||
probe.packetId = activityId;
|
||||
}
|
||||
if (probe.claims.isEmpty()) {
|
||||
String snippet = json.length() > 240 ? json.substring(0, 240) + "..." : json;
|
||||
XposedBridge.log(TAG + " auto-detail empty claims id=" + activityId
|
||||
+ " body=" + snippet
|
||||
+ " userId=" + stringField(req, "userId", "getUserId"));
|
||||
notePossibleDisconnectFromEmpty(result, activityId);
|
||||
return false;
|
||||
if (!snapshotHasPacketMeta(probe) && !looksLikeValidEmptyPacket(result, activityId)) {
|
||||
notePossibleDisconnectFromEmpty(result, activityId);
|
||||
return false;
|
||||
}
|
||||
// 刚发出、还没人领:照样入库,方便列表看到「领取中 0人」
|
||||
rememberPacketData(probe.packetId, probe, result);
|
||||
forwardSnapshot(probe, "auto:" + activityId, "rpc");
|
||||
XposedBridge.log(TAG + " auto-detail ok(empty) activityId=" + activityId
|
||||
+ " status=" + probe.activityStatus
|
||||
+ " total=" + probe.totalAmount
|
||||
+ " issued=" + probe.issueTime);
|
||||
noteSessionAlive();
|
||||
return true;
|
||||
}
|
||||
rememberPacketData(probe.packetId != null ? probe.packetId : activityId, probe, result);
|
||||
forwardSnapshot(probe, "auto:" + activityId, "rpc");
|
||||
@@ -1329,6 +1457,12 @@ public final class TngMoneyPacketHook {
|
||||
if (!PACKAGE.equals(pkg)) {
|
||||
return;
|
||||
}
|
||||
String actName = act.getClass().getName();
|
||||
if (actName.contains("MoneyPacketHistoryActivity")
|
||||
&& sBounceHistoryPending) {
|
||||
scheduleHistoryBounceFinish(act);
|
||||
}
|
||||
reportHookAlive("activity-resume");
|
||||
if (sSessionDisconnected) {
|
||||
tryRefreshLogin(sAppClassLoader);
|
||||
scheduleAutoHistoryForced("reconnect-resume");
|
||||
@@ -1373,6 +1507,7 @@ public final class TngMoneyPacketHook {
|
||||
if (ok) {
|
||||
sLastAutoHistoryAt = System.currentTimeMillis();
|
||||
noteSessionAlive();
|
||||
reportHookAlive("auto-history-ok");
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
@@ -1389,6 +1524,7 @@ public final class TngMoneyPacketHook {
|
||||
Object task = sMoneyPacketRpcTask;
|
||||
if (cl == null || task == null) {
|
||||
XposedBridge.log(TAG + " auto-history skip: no rpc task (" + reason + ")");
|
||||
maybeBounceHistoryPage("no-rpc-task");
|
||||
return false;
|
||||
}
|
||||
if (TextUtils.isEmpty(sCachedSessionId)) {
|
||||
@@ -1402,6 +1538,7 @@ public final class TngMoneyPacketHook {
|
||||
if (sHadSession) {
|
||||
sSessionDisconnected = true;
|
||||
}
|
||||
maybeBounceHistoryPage("no-session");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
@@ -1422,24 +1559,33 @@ public final class TngMoneyPacketHook {
|
||||
} finally {
|
||||
AUTO_HISTORY_SELF.remove();
|
||||
}
|
||||
if (result != null && historyHasJobs(result)) {
|
||||
XposedBridge.log(TAG + " auto-history ok(template) → schedule details");
|
||||
scheduleAutoDetailsFromHistory(result);
|
||||
if (result != null) {
|
||||
if (historyHasJobs(result)) {
|
||||
XposedBridge.log(TAG + " auto-history ok(template) → schedule details");
|
||||
scheduleAutoDetailsFromHistory(result);
|
||||
} else {
|
||||
XposedBridge.log(TAG + " auto-history ok(template) but empty list");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// 有模板但 RPC 失败:不要误当成「无模板」反复乱跳;按断线处理并短进历史页
|
||||
XposedBridge.log(TAG + " auto-history template rpc failed (" + reason + ")");
|
||||
maybeBounceHistoryPage("template-rpc-fail");
|
||||
return false;
|
||||
}
|
||||
// 2) 无模板时:自造请求易「非法参数」,直接拉起官方历史页让 App 发正确 RPC
|
||||
if (!sOpenHistoryIfNoTemplate) {
|
||||
if (!sOpenHistoryIfNoTemplate && !sAutoBounceHistoryOnDisconnect) {
|
||||
XposedBridge.log(TAG + " auto-history no template, openHistory disabled ("
|
||||
+ reason + ")");
|
||||
return false;
|
||||
}
|
||||
XposedBridge.log(TAG + " auto-history no template → open HistoryActivity (" + reason + ")");
|
||||
openMoneyPacketHistoryActivity();
|
||||
openMoneyPacketHistoryActivity(true);
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " auto-history fail: " + t.getMessage());
|
||||
markMaybeDisconnected(t, "auto-history");
|
||||
maybeBounceHistoryPage("history-exception");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1448,11 +1594,32 @@ public final class TngMoneyPacketHook {
|
||||
return !extractHistoryJobs(historyResult).isEmpty();
|
||||
}
|
||||
|
||||
/** 拉起官方历史页;App 自己的 moneyPacketHistoryList 成功后 Hook 会缓存模板并级联拉详情 */
|
||||
private static void openMoneyPacketHistoryActivity() {
|
||||
/** 断线/失败时短进历史页再建连(有冷却,避免刷屏) */
|
||||
private static void maybeBounceHistoryPage(String reason) {
|
||||
if (!sAutoBounceHistoryOnDisconnect) {
|
||||
XposedBridge.log(TAG + " bounce history disabled (" + reason + ")");
|
||||
return;
|
||||
}
|
||||
openMoneyPacketHistoryActivity(true);
|
||||
XposedBridge.log(TAG + " bounce history for " + reason);
|
||||
}
|
||||
|
||||
/** 拉起官方历史页;bounceBack=true 时约 2 秒后自动 finish 返回 */
|
||||
private static void openMoneyPacketHistoryActivity(boolean bounceBack) {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
if (bounceBack) {
|
||||
if (now - sLastHistoryBounceAt < HISTORY_BOUNCE_COOLDOWN_MS) {
|
||||
XposedBridge.log(TAG + " bounce history cooldown skip ageMs="
|
||||
+ (now - sLastHistoryBounceAt));
|
||||
return;
|
||||
}
|
||||
sLastHistoryBounceAt = now;
|
||||
sBounceHistoryPending = true;
|
||||
}
|
||||
Context ctx = getContext();
|
||||
if (ctx == null) {
|
||||
sBounceHistoryPending = false;
|
||||
return;
|
||||
}
|
||||
android.content.Intent intent = new android.content.Intent();
|
||||
@@ -1461,17 +1628,43 @@ public final class TngMoneyPacketHook {
|
||||
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
ctx.startActivity(intent);
|
||||
XposedBridge.log(TAG + " started MoneyPacketHistoryActivity");
|
||||
XposedBridge.log(TAG + " started MoneyPacketHistoryActivity bounce=" + bounceBack);
|
||||
} catch (Throwable t) {
|
||||
sBounceHistoryPending = false;
|
||||
XposedBridge.log(TAG + " start HistoryActivity fail: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void scheduleHistoryBounceFinish(final Object activity) {
|
||||
if (!(activity instanceof android.app.Activity)) {
|
||||
return;
|
||||
}
|
||||
final android.app.Activity act = (android.app.Activity) activity;
|
||||
MAIN_HANDLER.postDelayed(() -> {
|
||||
try {
|
||||
if (!sBounceHistoryPending) {
|
||||
return;
|
||||
}
|
||||
if (act.isFinishing()) {
|
||||
sBounceHistoryPending = false;
|
||||
return;
|
||||
}
|
||||
act.finish();
|
||||
XposedBridge.log(TAG + " bounce history finished → back");
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " bounce history finish fail: " + t.getMessage());
|
||||
} finally {
|
||||
sBounceHistoryPending = false;
|
||||
}
|
||||
}, HISTORY_BOUNCE_FINISH_DELAY_MS);
|
||||
}
|
||||
|
||||
private static Object callHistoryListRpc(Object task, Object req) {
|
||||
try {
|
||||
return XposedHelpers.callMethod(task, "moneyPacketHistoryList", req);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " history rpc fail: " + t.getMessage());
|
||||
markMaybeDisconnected(t, "history-rpc");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1688,7 +1881,10 @@ public final class TngMoneyPacketHook {
|
||||
}
|
||||
|
||||
private static void forwardSnapshot(MmpSnapshot snapshot, String sourceHint, String channel) {
|
||||
if (snapshot == null || snapshot.claims.isEmpty()) {
|
||||
if (snapshot == null) {
|
||||
return;
|
||||
}
|
||||
if (snapshot.claims.isEmpty() && !snapshotHasPacketMeta(snapshot)) {
|
||||
return;
|
||||
}
|
||||
dedupeClaims(snapshot);
|
||||
@@ -1739,6 +1935,11 @@ public final class TngMoneyPacketHook {
|
||||
continue;
|
||||
}
|
||||
String name = stringField(info, "receiverName", "getReceiverName");
|
||||
String receiverId = firstNonEmpty(
|
||||
stringField(info, "receiverId", "getReceiverId"),
|
||||
stringField(info, "userId", "getUserId"),
|
||||
stringField(info, "receiverUserId", "getReceiverUserId"));
|
||||
String poolId = stringField(info, "activityPoolId", "getActivityPoolId");
|
||||
String claimTime = firstNonEmpty(
|
||||
stringField(info, "gmtModified", "getGmtModified"),
|
||||
stringField(info, "gmtCreate", "getGmtCreate"),
|
||||
@@ -1755,21 +1956,36 @@ public final class TngMoneyPacketHook {
|
||||
if (TextUtils.isEmpty(amount)) {
|
||||
amount = normalizeMoneyText(stringField(info, "amount", "getAmount"));
|
||||
}
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
if (TextUtils.isEmpty(name) && TextUtils.isEmpty(receiverId)) {
|
||||
continue;
|
||||
}
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
name = "用户";
|
||||
}
|
||||
boolean matched = false;
|
||||
for (ClaimLine line : snapshot.claims) {
|
||||
if (name.equals(line.nickname)) {
|
||||
boolean sameId = !TextUtils.isEmpty(receiverId)
|
||||
&& receiverId.equals(line.receiverId);
|
||||
boolean sameName = TextUtils.isEmpty(receiverId)
|
||||
&& name.equals(line.nickname);
|
||||
if (sameId || sameName) {
|
||||
if (TextUtils.isEmpty(line.claimTime) && !TextUtils.isEmpty(claimTime)) {
|
||||
line.claimTime = claimTime;
|
||||
}
|
||||
if (TextUtils.isEmpty(line.receiverId) && !TextUtils.isEmpty(receiverId)) {
|
||||
line.receiverId = receiverId;
|
||||
}
|
||||
if (TextUtils.isEmpty(line.poolId) && !TextUtils.isEmpty(poolId)) {
|
||||
line.poolId = poolId;
|
||||
}
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
if (!matched && !TextUtils.isEmpty(amount)) {
|
||||
ClaimLine line = new ClaimLine();
|
||||
line.nickname = name;
|
||||
line.receiverId = receiverId;
|
||||
line.poolId = poolId;
|
||||
line.amount = amount;
|
||||
line.claimTime = claimTime;
|
||||
snapshot.claims.add(line);
|
||||
@@ -1860,30 +2076,43 @@ public final class TngMoneyPacketHook {
|
||||
}
|
||||
java.util.LinkedHashMap<String, ClaimLine> map = new java.util.LinkedHashMap<>();
|
||||
for (ClaimLine line : snapshot.claims) {
|
||||
if (line == null || TextUtils.isEmpty(line.nickname)) {
|
||||
if (line == null) {
|
||||
continue;
|
||||
}
|
||||
ClaimLine old = map.get(line.nickname);
|
||||
if (TextUtils.isEmpty(line.nickname) && TextUtils.isEmpty(line.receiverId)) {
|
||||
continue;
|
||||
}
|
||||
String key = !TextUtils.isEmpty(line.receiverId)
|
||||
? ("id:" + line.receiverId)
|
||||
: ("n:" + line.nickname);
|
||||
ClaimLine old = map.get(key);
|
||||
if (old == null) {
|
||||
map.put(line.nickname, line);
|
||||
map.put(key, line);
|
||||
continue;
|
||||
}
|
||||
// 同昵称保留金额更大的;时间有则补上
|
||||
try {
|
||||
double a = Double.parseDouble(old.amount);
|
||||
double b = Double.parseDouble(line.amount);
|
||||
if (b > a) {
|
||||
if (TextUtils.isEmpty(line.claimTime)) {
|
||||
line.claimTime = old.claimTime;
|
||||
}
|
||||
map.put(line.nickname, line);
|
||||
} else if (TextUtils.isEmpty(old.claimTime) && !TextUtils.isEmpty(line.claimTime)) {
|
||||
old.claimTime = line.claimTime;
|
||||
double oldAmt = parseMoneyDouble(old.amount) != null ? parseMoneyDouble(old.amount) : -1;
|
||||
double newAmt = parseMoneyDouble(line.amount) != null ? parseMoneyDouble(line.amount) : -1;
|
||||
if (newAmt >= oldAmt) {
|
||||
if (TextUtils.isEmpty(line.claimTime) && !TextUtils.isEmpty(old.claimTime)) {
|
||||
line.claimTime = old.claimTime;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
if (TextUtils.isEmpty(line.receiverId)) {
|
||||
line.receiverId = old.receiverId;
|
||||
}
|
||||
if (TextUtils.isEmpty(line.poolId)) {
|
||||
line.poolId = old.poolId;
|
||||
}
|
||||
map.put(key, line);
|
||||
} else {
|
||||
if (TextUtils.isEmpty(old.claimTime) && !TextUtils.isEmpty(line.claimTime)) {
|
||||
old.claimTime = line.claimTime;
|
||||
}
|
||||
if (TextUtils.isEmpty(old.receiverId) && !TextUtils.isEmpty(line.receiverId)) {
|
||||
old.receiverId = line.receiverId;
|
||||
}
|
||||
if (TextUtils.isEmpty(old.poolId) && !TextUtils.isEmpty(line.poolId)) {
|
||||
old.poolId = line.poolId;
|
||||
}
|
||||
}
|
||||
}
|
||||
snapshot.claims.clear();
|
||||
@@ -2094,6 +2323,18 @@ public final class TngMoneyPacketHook {
|
||||
ClaimLine line = new ClaimLine();
|
||||
line.nickname = name.trim();
|
||||
line.amount = amount;
|
||||
line.receiverId = firstNonEmpty(
|
||||
readFieldText(obj, "receiverId"),
|
||||
readFieldText(obj, "receiverUserId"),
|
||||
readFieldText(obj, "userId"),
|
||||
readFieldText(obj, "participantId"));
|
||||
// userName 字段误当 id 时:纯数字/长串才留
|
||||
if (!TextUtils.isEmpty(line.receiverId) && line.receiverId.equals(name)) {
|
||||
line.receiverId = null;
|
||||
}
|
||||
line.poolId = firstNonEmpty(
|
||||
readFieldText(obj, "activityPoolId"),
|
||||
readFieldText(obj, "poolId"));
|
||||
line.claimTime = firstNonEmpty(
|
||||
readFieldText(obj, "claimTime"),
|
||||
readFieldText(obj, "claimedTime"),
|
||||
@@ -2369,6 +2610,21 @@ public final class TngMoneyPacketHook {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void reportHookAlive(String reason) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (!"install".equals(reason) && now - sLastHookStatusAt < HOOK_STATUS_COOLDOWN_MS) {
|
||||
return;
|
||||
}
|
||||
Context ctx = getContext();
|
||||
if (ctx == null) {
|
||||
return;
|
||||
}
|
||||
sLastHookStatusAt = now;
|
||||
HookForwarder.reportStatus(ctx, PACKAGE, HookBridge.SOURCE_XPOSED_TNG_MMP);
|
||||
XposedBridge.log(TAG + " hook status reported (" + reason + ") mod="
|
||||
+ HookBridge.MODULE_VERSION_NAME);
|
||||
}
|
||||
|
||||
private static Context getContext() {
|
||||
try {
|
||||
Class<?> activityThread = XposedHelpers.findClass("android.app.ActivityThread", null);
|
||||
@@ -2461,6 +2717,8 @@ public final class TngMoneyPacketHook {
|
||||
sb.append(" | done=1");
|
||||
}
|
||||
sb.append(" | via=").append(sanitizeMeta(channel));
|
||||
sb.append(" | mod=").append(HookBridge.MODULE_VERSION_NAME)
|
||||
.append('/').append(HookBridge.MODULE_VERSION_CODE);
|
||||
if (!TextUtils.isEmpty(sourceHint)) {
|
||||
String src = sourceHint.length() > 120
|
||||
? sourceHint.substring(0, 120) + "..." : sourceHint;
|
||||
@@ -2472,6 +2730,12 @@ public final class TngMoneyPacketHook {
|
||||
if (!TextUtils.isEmpty(line.claimTime)) {
|
||||
sb.append(" (").append(line.claimTime).append(')');
|
||||
}
|
||||
if (!TextUtils.isEmpty(line.receiverId)) {
|
||||
sb.append(" #rid=").append(sanitizeMeta(line.receiverId));
|
||||
}
|
||||
if (!TextUtils.isEmpty(line.poolId)) {
|
||||
sb.append(" #pool=").append(sanitizeMeta(line.poolId));
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
return sb.toString().trim();
|
||||
@@ -2488,6 +2752,8 @@ public final class TngMoneyPacketHook {
|
||||
|
||||
private static final class ClaimLine {
|
||||
String nickname;
|
||||
String receiverId;
|
||||
String poolId;
|
||||
String amount;
|
||||
String claimTime;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user