From b47aed621223a535eaf4df5d21e292e7d77c1e95 Mon Sep 17 00:00:00 2001 From: yibo Date: Tue, 30 Jun 2026 01:02:18 +0800 Subject: [PATCH] feat(xhshook): 实时转发 edith 请求到 Hermes 本地 server --- xhs_lsposed_a16_fix/rebuild_xhshook.sh | 3 +-- xhs_lsposed_a16_fix/update_xhshook_db.sh | 38 +++++++++++++++++++++++++++++--------- xhs_lsposed_a16_fix/xhshook_src/Hook.java | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- xhs_lsposed_a16_fix/xhshook_v2_signed.apk | Bin 0 -> 16863 bytes 4 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 xhs_lsposed_a16_fix/xhshook_v2_signed.apk diff --git a/xhs_lsposed_a16_fix/rebuild_xhshook.sh b/xhs_lsposed_a16_fix/rebuild_xhshook.sh index 2ef8e06..5aea50b 100755 --- a/xhs_lsposed_a16_fix/rebuild_xhshook.sh +++ b/xhs_lsposed_a16_fix/rebuild_xhshook.sh @@ -32,8 +32,7 @@ $D8 \ --classpath "$LEGACY_JAR" \ --min-api 27 \ --output "$BUILD_DIR/dex" \ - "$BUILD_DIR/compiled/com/hermes/xhshook/Hook.class" \ - "$BUILD_DIR/compiled/com/hermes/xhshook/Hook\$1.class" + $(find "$BUILD_DIR/compiled/com/hermes/xhshook" -name "*.class") echo "== Packing APK ==" cp "$SCRIPT_DIR/xhshook_src/AndroidManifest.xml" "$BUILD_DIR/pack/" 2>/dev/null || \ diff --git a/xhs_lsposed_a16_fix/update_xhshook_db.sh b/xhs_lsposed_a16_fix/update_xhshook_db.sh index cd544fe..8616c83 100755 --- a/xhs_lsposed_a16_fix/update_xhshook_db.sh +++ b/xhs_lsposed_a16_fix/update_xhshook_db.sh @@ -6,22 +6,42 @@ set -e ADB="${ADB:-$(dirname "$0")/../platform-tools/adb}" if [ ! -f "$ADB" ]; then ADB=adb; fi +TERMUX_PY=/data/data/com.termux/files/usr/bin/python3 NEW_PATH=$($ADB shell 'pm path com.hermes.xhshook' | tr -d '\r' | sed 's/package://') echo "New APK path: $NEW_PATH" -$ADB shell 'su -c "cp /data/adb/lspd/config/modules_config.db /data/local/tmp/mc_upd.db && chmod 644 /data/local/tmp/mc_upd.db"' -$ADB pull /data/local/tmp/mc_upd.db /tmp/mc_upd.db -sqlite3 /tmp/mc_upd.db "UPDATE modules SET apk_path='${NEW_PATH}' WHERE module_pkg_name='com.hermes.xhshook';" -# Use Python in-place UPDATE to preserve inode → triggers lspd FileObserver. -# (adb push / cp replaces the file, changing inode, which FileObserver misses.) -TERMUX_PY=/data/data/com.termux/files/usr/bin/python3 +# --- Phase 1: Python in-place UPDATE (preserves inode, works while lspd is running) --- +# commit() writes to WAL so lspd can see it immediately via its DB connection. +# Checkpoint may fail if lspd holds a write lock — that's OK, we skip it. $ADB shell "su -c \"$TERMUX_PY -c \\\" import sqlite3 db = sqlite3.connect('/data/adb/lspd/config/modules_config.db') db.execute(\\\\\\\"UPDATE modules SET apk_path='${NEW_PATH}' WHERE module_pkg_name='com.hermes.xhshook'\\\\\\\") -db.execute('PRAGMA wal_checkpoint(TRUNCATE)') db.commit() +try: + db.execute('PRAGMA wal_checkpoint(TRUNCATE)') +except Exception as e: + pass # lspd holds write lock; skip checkpoint, WAL commit is enough db.close() -print('done') +print('phase1 done') \\\"\"" 2>&1 -echo "DB updated in-place. Now: am force-stop com.xingin.xhs → open XHS manually." + +# --- Phase 2: pull + overwrite main file (ensures correct path survives reboot) --- +# After phase 1, WAL has the correct path. Pull the DB+WAL, checkpoint locally, +# then push clean main file back and remove WAL/SHM on device. +$ADB shell 'su -c "cp /data/adb/lspd/config/modules_config.db /data/local/tmp/mc_upd.db && cp /data/adb/lspd/config/modules_config.db-wal /data/local/tmp/mc_upd.db-wal 2>/dev/null; chmod 644 /data/local/tmp/mc_upd.db /data/local/tmp/mc_upd.db-wal 2>/dev/null; true"' +$ADB pull /data/local/tmp/mc_upd.db /tmp/mc_upd.db 2>/dev/null +$ADB pull /data/local/tmp/mc_upd.db-wal /tmp/mc_upd.db-wal 2>/dev/null || true + +# Checkpoint locally (Mac sqlite3 has no lock contention) +sqlite3 /tmp/mc_upd.db "PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null || true +# Verify the path is there +ACTUAL=$(sqlite3 /tmp/mc_upd.db "SELECT apk_path FROM modules WHERE module_pkg_name='com.hermes.xhshook';") +echo "Verified path in DB: $ACTUAL" + +# Push clean DB and remove WAL/SHM on device +$ADB push /tmp/mc_upd.db /data/local/tmp/mc_upd_clean.db +$ADB shell 'su -c "cp /data/local/tmp/mc_upd_clean.db /data/adb/lspd/config/modules_config.db && rm -f /data/adb/lspd/config/modules_config.db-wal /data/adb/lspd/config/modules_config.db-shm && echo phase2 done"' + +echo "DB updated. Now: am force-stop com.xingin.xhs → open XHS manually." +echo "(If lspd cache doesn't auto-refresh, reboot is needed for changes to take effect.)" diff --git a/xhs_lsposed_a16_fix/xhshook_src/Hook.java b/xhs_lsposed_a16_fix/xhshook_src/Hook.java index c1fd0d0..5ad39d2 100644 --- a/xhs_lsposed_a16_fix/xhshook_src/Hook.java +++ b/xhs_lsposed_a16_fix/xhshook_src/Hook.java @@ -6,7 +6,15 @@ import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; + public class Hook implements IXposedHookLoadPackage { + // Hermes event_server endpoint — POST xhs_params events here + private static final String HERMES_URL = "http://127.0.0.1:8765/xhs_params"; + @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (!lpparam.packageName.equals("com.xingin.xhs")) return; @@ -22,22 +30,26 @@ public class Hook implements IXposedHookLoadPackage { Object url = XposedHelpers.getObjectField(param.thisObject, "url"); String urlStr = url != null ? url.toString() : "null"; - // Only log edith.xiaohongshu.com (the signed API endpoint) if (!urlStr.contains("edith.xiaohongshu.com")) return; Object method = XposedHelpers.getObjectField(param.thisObject, "method"); String methodStr = method != null ? method.toString() : "GET"; - // headers field is Headers.Builder — call build() to get actual headers Object headersBuilder = XposedHelpers.getObjectField(param.thisObject, "headers"); - String headersStr = "null"; + String headersStr = ""; if (headersBuilder != null) { Object headers = XposedHelpers.callMethod(headersBuilder, "build"); - headersStr = headers != null ? headers.toString() : "null"; + headersStr = headers != null ? headers.toString() : ""; } XposedBridge.log("[XHSHook] " + methodStr + " " + urlStr); XposedBridge.log("[XHSHook] Headers:\n" + headersStr); + + final String json = buildJson(methodStr, urlStr, headersStr); + new Thread(new Runnable() { + public void run() { postToHermes(json); } + }).start(); + } catch (Throwable t) { XposedBridge.log("[XHSHook] Error in hook: " + t.getMessage()); } @@ -49,4 +61,55 @@ public class Hook implements IXposedHookLoadPackage { XposedBridge.log(t); } } + + private static String buildJson(String method, String url, String headersStr) { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + sb.append("\"method\":").append(jsonStr(method)).append(","); + sb.append("\"url\":").append(jsonStr(url)).append(","); + sb.append("\"headers\":{"); + boolean first = true; + for (String line : headersStr.split("\n")) { + int colon = line.indexOf(": "); + if (colon < 0) continue; + String k = line.substring(0, colon).trim(); + String v = line.substring(colon + 2).trim(); + if (!first) sb.append(","); + sb.append(jsonStr(k)).append(":").append(jsonStr(v)); + first = false; + } + sb.append("}}"); + return sb.toString(); + } + + private static String jsonStr(String s) { + if (s == null) return "null"; + return "\"" + s.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + "\""; + } + + private static void postToHermes(String json) { + try { + HttpURLConnection conn = (HttpURLConnection) new URL(HERMES_URL).openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + conn.setConnectTimeout(1000); + conn.setReadTimeout(1000); + byte[] body = json.getBytes(StandardCharsets.UTF_8); + conn.setFixedLengthStreamingMode(body.length); + try (OutputStream os = conn.getOutputStream()) { + os.write(body); + } + int code = conn.getResponseCode(); + conn.disconnect(); + if (code != 200 && code != 201 && code != 204) { + XposedBridge.log("[XHSHook] Hermes POST " + code); + } + } catch (Throwable t) { + // Hermes server not running — silent fail, logcat capture still works + } + } } diff --git a/xhs_lsposed_a16_fix/xhshook_v2_signed.apk b/xhs_lsposed_a16_fix/xhshook_v2_signed.apk new file mode 100644 index 0000000..a851d60 Binary files /dev/null and b/xhs_lsposed_a16_fix/xhshook_v2_signed.apk differ -- libgit2 0.21.4