Commit b47aed621223a535eaf4df5d21e292e7d77c1e95

Authored by yibo
1 parent 01a2cf55

feat(xhshook): 实时转发 edith 请求到 Hermes 本地 server

Hook.java v3:在现有 logcat 日志基础上,后台线程 POST 每条
edith.xiaohongshu.com 请求(method/url/全量 headers)到
Hermes event_server(127.0.0.1:8765/xhs_params)。
Hermes server 未运行时静默失败,logcat 捕获不受影响。

rebuild_xhshook.sh:d8 步骤改用 find 通配所有 .class 文件,
避免新增匿名类时漏掉(之前写死 Hook$1.class)。

update_xhshook_db.sh:两阶段更新策略
- Phase 1:Python in-place UPDATE + commit(不做 checkpoint,
  lspd 持有写锁时 checkpoint 会 OperationalError)
- Phase 2:pull DB+WAL → Mac 本地 checkpoint → push 干净主文件
  → 删设备端 WAL/SHM,确保重启后 DB 正确

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
xhs_lsposed_a16_fix/rebuild_xhshook.sh
... ... @@ -32,8 +32,7 @@ $D8 \
32 32 --classpath "$LEGACY_JAR" \
33 33 --min-api 27 \
34 34 --output "$BUILD_DIR/dex" \
35   - "$BUILD_DIR/compiled/com/hermes/xhshook/Hook.class" \
36   - "$BUILD_DIR/compiled/com/hermes/xhshook/Hook\$1.class"
  35 + $(find "$BUILD_DIR/compiled/com/hermes/xhshook" -name "*.class")
37 36  
38 37 echo "== Packing APK =="
39 38 cp "$SCRIPT_DIR/xhshook_src/AndroidManifest.xml" "$BUILD_DIR/pack/" 2>/dev/null || \
... ...
xhs_lsposed_a16_fix/update_xhshook_db.sh
... ... @@ -6,22 +6,42 @@ set -e
6 6 ADB="${ADB:-$(dirname "$0")/../platform-tools/adb}"
7 7 if [ ! -f "$ADB" ]; then ADB=adb; fi
8 8  
  9 +TERMUX_PY=/data/data/com.termux/files/usr/bin/python3
9 10 NEW_PATH=$($ADB shell 'pm path com.hermes.xhshook' | tr -d '\r' | sed 's/package://')
10 11 echo "New APK path: $NEW_PATH"
11 12  
12   -$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"'
13   -$ADB pull /data/local/tmp/mc_upd.db /tmp/mc_upd.db
14   -sqlite3 /tmp/mc_upd.db "UPDATE modules SET apk_path='${NEW_PATH}' WHERE module_pkg_name='com.hermes.xhshook';"
15   -# Use Python in-place UPDATE to preserve inode → triggers lspd FileObserver.
16   -# (adb push / cp replaces the file, changing inode, which FileObserver misses.)
17   -TERMUX_PY=/data/data/com.termux/files/usr/bin/python3
  13 +# --- Phase 1: Python in-place UPDATE (preserves inode, works while lspd is running) ---
  14 +# commit() writes to WAL so lspd can see it immediately via its DB connection.
  15 +# Checkpoint may fail if lspd holds a write lock — that's OK, we skip it.
18 16 $ADB shell "su -c \"$TERMUX_PY -c \\\"
19 17 import sqlite3
20 18 db = sqlite3.connect('/data/adb/lspd/config/modules_config.db')
21 19 db.execute(\\\\\\\"UPDATE modules SET apk_path='${NEW_PATH}' WHERE module_pkg_name='com.hermes.xhshook'\\\\\\\")
22   -db.execute('PRAGMA wal_checkpoint(TRUNCATE)')
23 20 db.commit()
  21 +try:
  22 + db.execute('PRAGMA wal_checkpoint(TRUNCATE)')
  23 +except Exception as e:
  24 + pass # lspd holds write lock; skip checkpoint, WAL commit is enough
24 25 db.close()
25   -print('done')
  26 +print('phase1 done')
26 27 \\\"\"" 2>&1
27   -echo "DB updated in-place. Now: am force-stop com.xingin.xhs → open XHS manually."
  28 +
  29 +# --- Phase 2: pull + overwrite main file (ensures correct path survives reboot) ---
  30 +# After phase 1, WAL has the correct path. Pull the DB+WAL, checkpoint locally,
  31 +# then push clean main file back and remove WAL/SHM on device.
  32 +$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"'
  33 +$ADB pull /data/local/tmp/mc_upd.db /tmp/mc_upd.db 2>/dev/null
  34 +$ADB pull /data/local/tmp/mc_upd.db-wal /tmp/mc_upd.db-wal 2>/dev/null || true
  35 +
  36 +# Checkpoint locally (Mac sqlite3 has no lock contention)
  37 +sqlite3 /tmp/mc_upd.db "PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null || true
  38 +# Verify the path is there
  39 +ACTUAL=$(sqlite3 /tmp/mc_upd.db "SELECT apk_path FROM modules WHERE module_pkg_name='com.hermes.xhshook';")
  40 +echo "Verified path in DB: $ACTUAL"
  41 +
  42 +# Push clean DB and remove WAL/SHM on device
  43 +$ADB push /tmp/mc_upd.db /data/local/tmp/mc_upd_clean.db
  44 +$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"'
  45 +
  46 +echo "DB updated. Now: am force-stop com.xingin.xhs → open XHS manually."
  47 +echo "(If lspd cache doesn't auto-refresh, reboot is needed for changes to take effect.)"
... ...
xhs_lsposed_a16_fix/xhshook_src/Hook.java
... ... @@ -6,7 +6,15 @@ import de.robv.android.xposed.XposedBridge;
6 6 import de.robv.android.xposed.XposedHelpers;
7 7 import de.robv.android.xposed.callbacks.XC_LoadPackage;
8 8  
  9 +import java.io.OutputStream;
  10 +import java.net.HttpURLConnection;
  11 +import java.net.URL;
  12 +import java.nio.charset.StandardCharsets;
  13 +
9 14 public class Hook implements IXposedHookLoadPackage {
  15 + // Hermes event_server endpoint — POST xhs_params events here
  16 + private static final String HERMES_URL = "http://127.0.0.1:8765/xhs_params";
  17 +
10 18 @Override
11 19 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
12 20 if (!lpparam.packageName.equals("com.xingin.xhs")) return;
... ... @@ -22,22 +30,26 @@ public class Hook implements IXposedHookLoadPackage {
22 30 Object url = XposedHelpers.getObjectField(param.thisObject, "url");
23 31 String urlStr = url != null ? url.toString() : "null";
24 32  
25   - // Only log edith.xiaohongshu.com (the signed API endpoint)
26 33 if (!urlStr.contains("edith.xiaohongshu.com")) return;
27 34  
28 35 Object method = XposedHelpers.getObjectField(param.thisObject, "method");
29 36 String methodStr = method != null ? method.toString() : "GET";
30 37  
31   - // headers field is Headers.Builder — call build() to get actual headers
32 38 Object headersBuilder = XposedHelpers.getObjectField(param.thisObject, "headers");
33   - String headersStr = "null";
  39 + String headersStr = "";
34 40 if (headersBuilder != null) {
35 41 Object headers = XposedHelpers.callMethod(headersBuilder, "build");
36   - headersStr = headers != null ? headers.toString() : "null";
  42 + headersStr = headers != null ? headers.toString() : "";
37 43 }
38 44  
39 45 XposedBridge.log("[XHSHook] " + methodStr + " " + urlStr);
40 46 XposedBridge.log("[XHSHook] Headers:\n" + headersStr);
  47 +
  48 + final String json = buildJson(methodStr, urlStr, headersStr);
  49 + new Thread(new Runnable() {
  50 + public void run() { postToHermes(json); }
  51 + }).start();
  52 +
41 53 } catch (Throwable t) {
42 54 XposedBridge.log("[XHSHook] Error in hook: " + t.getMessage());
43 55 }
... ... @@ -49,4 +61,55 @@ public class Hook implements IXposedHookLoadPackage {
49 61 XposedBridge.log(t);
50 62 }
51 63 }
  64 +
  65 + private static String buildJson(String method, String url, String headersStr) {
  66 + StringBuilder sb = new StringBuilder();
  67 + sb.append("{");
  68 + sb.append("\"method\":").append(jsonStr(method)).append(",");
  69 + sb.append("\"url\":").append(jsonStr(url)).append(",");
  70 + sb.append("\"headers\":{");
  71 + boolean first = true;
  72 + for (String line : headersStr.split("\n")) {
  73 + int colon = line.indexOf(": ");
  74 + if (colon < 0) continue;
  75 + String k = line.substring(0, colon).trim();
  76 + String v = line.substring(colon + 2).trim();
  77 + if (!first) sb.append(",");
  78 + sb.append(jsonStr(k)).append(":").append(jsonStr(v));
  79 + first = false;
  80 + }
  81 + sb.append("}}");
  82 + return sb.toString();
  83 + }
  84 +
  85 + private static String jsonStr(String s) {
  86 + if (s == null) return "null";
  87 + return "\"" + s.replace("\\", "\\\\")
  88 + .replace("\"", "\\\"")
  89 + .replace("\n", "\\n")
  90 + .replace("\r", "\\r") + "\"";
  91 + }
  92 +
  93 + private static void postToHermes(String json) {
  94 + try {
  95 + HttpURLConnection conn = (HttpURLConnection) new URL(HERMES_URL).openConnection();
  96 + conn.setRequestMethod("POST");
  97 + conn.setRequestProperty("Content-Type", "application/json");
  98 + conn.setDoOutput(true);
  99 + conn.setConnectTimeout(1000);
  100 + conn.setReadTimeout(1000);
  101 + byte[] body = json.getBytes(StandardCharsets.UTF_8);
  102 + conn.setFixedLengthStreamingMode(body.length);
  103 + try (OutputStream os = conn.getOutputStream()) {
  104 + os.write(body);
  105 + }
  106 + int code = conn.getResponseCode();
  107 + conn.disconnect();
  108 + if (code != 200 && code != 201 && code != 204) {
  109 + XposedBridge.log("[XHSHook] Hermes POST " + code);
  110 + }
  111 + } catch (Throwable t) {
  112 + // Hermes server not running — silent fail, logcat capture still works
  113 + }
  114 + }
52 115 }
... ...
xhs_lsposed_a16_fix/xhshook_v2_signed.apk 0 → 100644
No preview for this file type