@chainlesschain/personal-data-hub 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/__tests__/adapters/edu-huawei-learning-live.test.js +198 -0
  2. package/__tests__/adapters/edu-zuoyebang-live.test.js +226 -0
  3. package/__tests__/adapters/family-23-collectors-scaffold.test.js +5 -1
  4. package/__tests__/adapters/finance-alipay-live.test.js +258 -0
  5. package/__tests__/adapters/game-genshin-live.test.js +238 -0
  6. package/__tests__/adapters/game-genshin-scaffold.test.js +4 -3
  7. package/__tests__/adapters/game-honor-of-kings-live.test.js +230 -0
  8. package/__tests__/adapters/messaging-whatsapp.test.js +289 -0
  9. package/__tests__/adapters/netease-music-live.test.js +244 -0
  10. package/__tests__/adapters/shopping-base.test.js +179 -0
  11. package/__tests__/adapters/social-douyin-adb-aweme-detail.test.js +165 -0
  12. package/__tests__/adapters/social-douyin-adb-watch-history.test.js +192 -0
  13. package/__tests__/adapters/social-kuaishou-adb-api-client.test.js +64 -0
  14. package/__tests__/adapters/social-kuaishou-adb-cookies-extension.test.js +11 -0
  15. package/__tests__/adapters/social-toutiao-adb-account-reader.test.js +135 -0
  16. package/__tests__/adapters/social-toutiao-adb-api-client.test.js +89 -0
  17. package/__tests__/adapters/social-toutiao-adb-collector.test.js +95 -2
  18. package/__tests__/adapters/social-toutiao-adb-cookies-extension.test.js +30 -0
  19. package/__tests__/adapters/social-xiaohongshu-adb-api-client.test.js +431 -0
  20. package/__tests__/adapters/social-xiaohongshu-adb-cookies-extension.test.js +0 -0
  21. package/__tests__/adapters/social-xiaohongshu-adb-snapshot-builder.test.js +200 -0
  22. package/__tests__/adapters/travel-12306.test.js +279 -0
  23. package/__tests__/adapters/travel-amap.test.js +219 -0
  24. package/__tests__/adapters/travel-baidu-map.test.js +305 -0
  25. package/__tests__/adapters/travel-base.test.js +205 -0
  26. package/__tests__/adapters/travel-ctrip.test.js +203 -0
  27. package/__tests__/adapters/travel-tencent-map.test.js +207 -0
  28. package/lib/adapters/_live-json-helpers.js +50 -0
  29. package/lib/adapters/edu-huawei-learning/api-client.js +178 -5
  30. package/lib/adapters/edu-huawei-learning/index.js +83 -9
  31. package/lib/adapters/edu-zuoyebang/api-client.js +181 -6
  32. package/lib/adapters/edu-zuoyebang/index.js +83 -9
  33. package/lib/adapters/finance-alipay/api-client.js +268 -6
  34. package/lib/adapters/finance-alipay/index.js +85 -9
  35. package/lib/adapters/game-genshin/api-client.js +207 -6
  36. package/lib/adapters/game-genshin/index.js +90 -9
  37. package/lib/adapters/game-honor-of-kings/api-client.js +235 -12
  38. package/lib/adapters/game-honor-of-kings/index.js +80 -9
  39. package/lib/adapters/netease-music/api-client.js +284 -0
  40. package/lib/adapters/netease-music/index.js +85 -9
  41. package/lib/adapters/social-douyin/index.js +2 -0
  42. package/lib/adapters/social-douyin-adb/aweme-detail-client.js +119 -0
  43. package/lib/adapters/social-douyin-adb/collector.js +114 -0
  44. package/lib/adapters/social-douyin-adb/index.js +18 -1
  45. package/lib/adapters/social-douyin-adb/watch-history-reader.js +188 -0
  46. package/lib/adapters/social-kuaishou/index.js +7 -2
  47. package/lib/adapters/social-kuaishou-adb/api-client.js +38 -18
  48. package/lib/adapters/social-kuaishou-adb/cookies-extension.js +16 -15
  49. package/lib/adapters/social-toutiao/index.js +8 -4
  50. package/lib/adapters/social-toutiao-adb/account-reader.js +179 -0
  51. package/lib/adapters/social-toutiao-adb/api-client.js +41 -17
  52. package/lib/adapters/social-toutiao-adb/collector.js +55 -19
  53. package/lib/adapters/social-toutiao-adb/cookies-extension.js +21 -1
  54. package/lib/adapters/social-toutiao-adb/index.js +6 -0
  55. package/lib/adapters/social-xiaohongshu-adb/cookies-extension.js +19 -1
  56. package/lib/adapters/travel-base/index.js +9 -2
  57. package/lib/index.js +1 -1
  58. package/package.json +1 -1
@@ -72,26 +72,57 @@ async function collect(bridge, opts = {}) {
72
72
 
73
73
  try {
74
74
  // fetchProfile — passport endpoint, no _signature required.
75
- const profile = await client.fetchProfile(cookie);
76
- if (!profile) {
77
- // Cookie expired or sessionid missing emit empty snapshot using
78
- // best-effort cookie-derived uid (or sentinel if also absent).
79
- const uid = cookieUid || "unknown-user";
75
+ let profile = await client.fetchProfile(cookie);
76
+ const profileFailed = !profile;
77
+ // Capture the profile error BEFORE the signed fetches overwrite lastError —
78
+ // when profile is permission-denied (real-device 2026-06-11: passport
79
+ // error_code 16 该应用无权限) it's the headline diagnostic, more useful than
80
+ // a downstream -99 short-circuit.
81
+ const profileErrCode = profileFailed ? client.lastErrorCode : null;
82
+ const profileErrMsg = profileFailed ? client.lastErrorMessage : null;
83
+
84
+ // Local account_db fallback: the web profile endpoint is often
85
+ // permission-denied (error_code 16) AND the WebView cookie jar carries no
86
+ // numeric uid — but the app's local account_db has uid+nickname in
87
+ // plaintext. Recover it so the signed collection/search endpoints (which
88
+ // need a uid) can still run. Best-effort: the bridge may not expose
89
+ // "toutiao.account" (older wiring) — that's fine, we fall through.
90
+ let profileSource = profile ? "web" : null;
91
+ if (profileFailed && bridge && typeof bridge.invoke === "function") {
92
+ try {
93
+ const acct = await bridge.invoke("toutiao.account");
94
+ if (acct && acct.uid && /^\d+$/.test(String(acct.uid))) {
95
+ profile = { uid: String(acct.uid), nickname: acct.nickname || null };
96
+ profileSource = "local-account-db";
97
+ }
98
+ } catch (_e) {
99
+ // account extension unavailable / logged out — keep web error.
100
+ }
101
+ }
102
+
103
+ // The signed feed endpoint is cookie-identified and collection/search can
104
+ // use a cookie-derived uid, so a profile failure should NOT abort the whole
105
+ // sync when we still have a usable uid — only bail when there is no uid at
106
+ // all. (Previously any profile failure returned an empty snapshot, so the
107
+ // SignBridge feed/collection/search were never even attempted.)
108
+ const effectiveUid = (profile && profile.uid) || cookieUid || null;
109
+ if (!effectiveUid) {
80
110
  const snapshot = buildSnapshot({
81
- uid,
111
+ uid: "unknown-user",
82
112
  displayName: opts.displayName,
83
113
  snapshottedAt: now(),
84
114
  });
85
115
  const snapshotPath = writeSnapshotJson(snapshot, { dir: opts.stagingDir });
86
116
  return {
87
117
  snapshotPath,
88
- uid: cookieUid,
118
+ uid: null,
89
119
  nickname: null,
90
120
  eventCounts: { profile: 0, feed: 0, collection: 0, search: 0, total: 0 },
91
- lastErrorCode: client.lastErrorCode,
92
- lastErrorMessage: client.lastErrorMessage,
121
+ lastErrorCode: profileErrCode,
122
+ lastErrorMessage: profileErrMsg,
93
123
  cookieDiagnostic: cookieDiagnostic || null,
94
124
  profileFetchFailed: true,
125
+ profileSource,
95
126
  signProviderUsed: signProvider
96
127
  ? signProvider.constructor.name
97
128
  : "none",
@@ -100,7 +131,9 @@ async function collect(bridge, opts = {}) {
100
131
  };
101
132
  }
102
133
 
103
- // Parallel 3 signed endpoints — partial failure tolerated.
134
+ // Parallel 3 signed endpoints — partial failure tolerated. Attempted even
135
+ // when profile failed (as long as we have a uid), so feed can still flow
136
+ // through a SignBridge despite a permission-denied profile endpoint.
104
137
  const [feed, collection, search] = await Promise.all([
105
138
  client.fetchFeed(cookie, {
106
139
  limit: Number.isInteger(limits.feed) ? limits.feed : undefined,
@@ -116,9 +149,9 @@ async function collect(bridge, opts = {}) {
116
149
  ]);
117
150
 
118
151
  const snapshot = buildSnapshot({
119
- uid: profile.uid,
120
- displayName: opts.displayName || profile.nickname,
121
- profile,
152
+ uid: effectiveUid,
153
+ displayName: opts.displayName || (profile && profile.nickname),
154
+ profile: profile || undefined,
122
155
  feed,
123
156
  collection,
124
157
  search,
@@ -128,19 +161,22 @@ async function collect(bridge, opts = {}) {
128
161
 
129
162
  return {
130
163
  snapshotPath,
131
- uid: profile.uid,
132
- nickname: profile.nickname,
164
+ uid: effectiveUid,
165
+ nickname: profile ? profile.nickname : null,
133
166
  eventCounts: {
134
- profile: 1,
167
+ profile: profile ? 1 : 0,
135
168
  feed: feed.length,
136
169
  collection: collection.length,
137
170
  search: search.length,
138
171
  total: snapshot.events.length,
139
172
  },
140
- lastErrorCode: client.lastErrorCode,
141
- lastErrorMessage: client.lastErrorMessage,
173
+ // On profile failure surface the profile error (the headline issue), not
174
+ // the last signed-endpoint status.
175
+ lastErrorCode: profileFailed ? profileErrCode : client.lastErrorCode,
176
+ lastErrorMessage: profileFailed ? profileErrMsg : client.lastErrorMessage,
142
177
  cookieDiagnostic: cookieDiagnostic || null,
143
- profileFetchFailed: false,
178
+ profileFetchFailed: profileFailed,
179
+ profileSource,
144
180
  signProviderUsed: signProvider ? signProvider.constructor.name : "none",
145
181
  signProviderHits: client._bridgeHits,
146
182
  signProviderFallbacks: client._fallbackHits,
@@ -41,6 +41,7 @@ const {
41
41
  readChromiumCookies,
42
42
  } = require("../social-bilibili-adb/chromium-cookies-reader");
43
43
 
44
+ const TOUTIAO_PACKAGE = "com.ss.android.article.news";
44
45
  const TOUTIAO_COOKIES_REMOTE_PATH =
45
46
  "/data/data/com.ss.android.article.news/app_webview/Default/Cookies";
46
47
 
@@ -72,10 +73,29 @@ async function pullCookiesViaSu(adb, serial, opts) {
72
73
  );
73
74
  const lsLine = lsOut.replace(/\r+$/gm, "").trim();
74
75
  if (lsLine === "NOT_FOUND" || lsLine === "") {
76
+ // Distinguish "app not installed" from "installed but no webview cookie
77
+ // store yet" (logged out / never opened an in-app WebView). Both leave the
78
+ // Cookies file absent, but the user action differs — so probe pm.
79
+ const pmOut = await adb(
80
+ ["shell", "su", "-c", `pm list packages ${TOUTIAO_PACKAGE}`],
81
+ adbOpts,
82
+ );
83
+ const installed = pmOut
84
+ .replace(/\r/g, "")
85
+ .includes(`package:${TOUTIAO_PACKAGE}`);
86
+ if (installed) {
87
+ throw new Error(
88
+ "TOUTIAO_NO_WEBVIEW_COOKIES: 今日头条 App 已安装但无 WebView cookie 库 (" +
89
+ TOUTIAO_COOKIES_REMOTE_PATH +
90
+ " 不存在)。请在 App 内登录,并打开任意文章/网页(触发内置 WebView 写 cookie)后重试。注意:极速版 (.lite) 是另一个包,不支持。",
91
+ );
92
+ }
75
93
  throw new Error(
76
94
  "TOUTIAO_NOT_INSTALLED: " +
77
95
  TOUTIAO_COOKIES_REMOTE_PATH +
78
- " not found. Install Toutiao App (今日头条 com.ss.android.article.news) + log in once, then retry. Note: 极速版 (.lite) uses a different package — only the standard app is supported.",
96
+ " not found and package " +
97
+ TOUTIAO_PACKAGE +
98
+ " is not installed. Install Toutiao App (今日头条 com.ss.android.article.news) + log in once, then retry. Note: 极速版 (.lite) uses a different package — only the standard app is supported.",
79
99
  );
80
100
  }
81
101
  const idOut = await adb(["shell", "su", "-c", "id -u"], adbOpts);
@@ -34,9 +34,15 @@ const {
34
34
  SNAPSHOT_SCHEMA_VERSION,
35
35
  } = require("./snapshot-builder");
36
36
  const { collect, collectAndSync } = require("./collector");
37
+ const {
38
+ createToutiaoAccountExtension,
39
+ ACCOUNT_DB_REMOTE_PATH,
40
+ } = require("./account-reader");
37
41
 
38
42
  module.exports = {
39
43
  createToutiaoCookiesExtension,
44
+ createToutiaoAccountExtension,
45
+ ACCOUNT_DB_REMOTE_PATH,
40
46
  TOUTIAO_COOKIES_REMOTE_PATH,
41
47
  TOUTIAO_COOKIE_HOST_DOMAIN,
42
48
  TOUTIAO_SESSION_COOKIES,
@@ -36,6 +36,7 @@ const {
36
36
  readChromiumCookies,
37
37
  } = require("../social-bilibili-adb/chromium-cookies-reader");
38
38
 
39
+ const XHS_PACKAGE = "com.xingin.xhs";
39
40
  const XHS_COOKIES_REMOTE_PATH =
40
41
  "/data/data/com.xingin.xhs/app_webview/Default/Cookies";
41
42
 
@@ -56,10 +57,27 @@ async function pullCookiesViaSu(adb, serial, opts) {
56
57
  );
57
58
  const lsLine = lsOut.replace(/\r+$/gm, "").trim();
58
59
  if (lsLine === "NOT_FOUND" || lsLine === "") {
60
+ // Distinguish "app not installed" from "installed but no webview cookie
61
+ // store yet" (logged out / never opened an in-app WebView). Both leave the
62
+ // Cookies file absent, but the user action differs — so probe pm.
63
+ const pmOut = await adb(
64
+ ["shell", "su", "-c", `pm list packages ${XHS_PACKAGE}`],
65
+ adbOpts,
66
+ );
67
+ const installed = pmOut.replace(/\r/g, "").includes(`package:${XHS_PACKAGE}`);
68
+ if (installed) {
69
+ throw new Error(
70
+ "XHS_NO_WEBVIEW_COOKIES: Xiaohongshu App 已安装但无 WebView cookie 库 (" +
71
+ XHS_COOKIES_REMOTE_PATH +
72
+ " 不存在)。请在 App 内登录,并打开任意笔记/网页(触发内置 WebView 写 cookie)后重试。",
73
+ );
74
+ }
59
75
  throw new Error(
60
76
  "XHS_NOT_INSTALLED: " +
61
77
  XHS_COOKIES_REMOTE_PATH +
62
- " not found. Install Xiaohongshu App + log in once on the phone, then retry.",
78
+ " not found and package " +
79
+ XHS_PACKAGE +
80
+ " is not installed. Install Xiaohongshu App + log in once on the phone, then retry.",
63
81
  );
64
82
  }
65
83
  const idOut = await adb(["shell", "su", "-c", "id -u"], adbOpts);
@@ -150,8 +150,15 @@ function normalizeTravelRecord(rec, ctx = {}) {
150
150
 
151
151
  function buildTitle(rec) {
152
152
  const vt = rec.vehicleType || "trip";
153
- const from = rec.from ? (rec.from.station || rec.from.city || "?") : "";
154
- const to = rec.to ? (rec.to.station || rec.to.city || "?") : "";
153
+ // station > city > name name matters for Amap route/search records,
154
+ // which carry ONLY p.name (no station/city); without it every Amap trip
155
+ // event was titled "car: ? → ?".
156
+ const from = rec.from
157
+ ? (rec.from.station || rec.from.city || rec.from.name || "?")
158
+ : "";
159
+ const to = rec.to
160
+ ? (rec.to.station || rec.to.city || rec.to.name || "?")
161
+ : "";
155
162
  if (from && to) return `${vt}: ${from} → ${to}`;
156
163
  if (to) return `${vt}: → ${to}`;
157
164
  return `${vt}: ${rec.carrier || rec.recordId}`;
package/lib/index.js CHANGED
@@ -53,7 +53,7 @@ const { DouyinAdapter } = require("./adapters/social-douyin");
53
53
  const { XiaohongshuAdapter } = require("./adapters/social-xiaohongshu");
54
54
  const { ToutiaoAdapter } = require("./adapters/social-toutiao");
55
55
  const { KuaishouAdapter } = require("./adapters/social-kuaishou");
56
- // FAMILY-23 v0.1 — 家庭守护 telemetry collectors (cookie-scrape placeholder).
56
+ // FAMILY-23 v0.2 — 家庭守护 telemetry collectors (snapshot + live fetcher 双路).
57
57
  const { GenshinAdapter } = require("./adapters/game-genshin");
58
58
  const { HonorOfKingsAdapter } = require("./adapters/game-honor-of-kings");
59
59
  const { ZuoyebangAdapter } = require("./adapters/edu-zuoyebang");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainlesschain/personal-data-hub",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Personal Data Hub — UnifiedSchema + validators + KG ingest helpers for the data-back-to-the-individual middleware",
5
5
  "type": "commonjs",
6
6
  "main": "lib/index.js",