@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.
- package/__tests__/adapters/edu-huawei-learning-live.test.js +198 -0
- package/__tests__/adapters/edu-zuoyebang-live.test.js +226 -0
- package/__tests__/adapters/family-23-collectors-scaffold.test.js +5 -1
- package/__tests__/adapters/finance-alipay-live.test.js +258 -0
- package/__tests__/adapters/game-genshin-live.test.js +238 -0
- package/__tests__/adapters/game-genshin-scaffold.test.js +4 -3
- package/__tests__/adapters/game-honor-of-kings-live.test.js +230 -0
- package/__tests__/adapters/messaging-whatsapp.test.js +289 -0
- package/__tests__/adapters/netease-music-live.test.js +244 -0
- package/__tests__/adapters/shopping-base.test.js +179 -0
- package/__tests__/adapters/social-douyin-adb-aweme-detail.test.js +165 -0
- package/__tests__/adapters/social-douyin-adb-watch-history.test.js +192 -0
- package/__tests__/adapters/social-kuaishou-adb-api-client.test.js +64 -0
- package/__tests__/adapters/social-kuaishou-adb-cookies-extension.test.js +11 -0
- package/__tests__/adapters/social-toutiao-adb-account-reader.test.js +135 -0
- package/__tests__/adapters/social-toutiao-adb-api-client.test.js +89 -0
- package/__tests__/adapters/social-toutiao-adb-collector.test.js +95 -2
- package/__tests__/adapters/social-toutiao-adb-cookies-extension.test.js +30 -0
- package/__tests__/adapters/social-xiaohongshu-adb-api-client.test.js +431 -0
- package/__tests__/adapters/social-xiaohongshu-adb-cookies-extension.test.js +0 -0
- package/__tests__/adapters/social-xiaohongshu-adb-snapshot-builder.test.js +200 -0
- package/__tests__/adapters/travel-12306.test.js +279 -0
- package/__tests__/adapters/travel-amap.test.js +219 -0
- package/__tests__/adapters/travel-baidu-map.test.js +305 -0
- package/__tests__/adapters/travel-base.test.js +205 -0
- package/__tests__/adapters/travel-ctrip.test.js +203 -0
- package/__tests__/adapters/travel-tencent-map.test.js +207 -0
- package/lib/adapters/_live-json-helpers.js +50 -0
- package/lib/adapters/edu-huawei-learning/api-client.js +178 -5
- package/lib/adapters/edu-huawei-learning/index.js +83 -9
- package/lib/adapters/edu-zuoyebang/api-client.js +181 -6
- package/lib/adapters/edu-zuoyebang/index.js +83 -9
- package/lib/adapters/finance-alipay/api-client.js +268 -6
- package/lib/adapters/finance-alipay/index.js +85 -9
- package/lib/adapters/game-genshin/api-client.js +207 -6
- package/lib/adapters/game-genshin/index.js +90 -9
- package/lib/adapters/game-honor-of-kings/api-client.js +235 -12
- package/lib/adapters/game-honor-of-kings/index.js +80 -9
- package/lib/adapters/netease-music/api-client.js +284 -0
- package/lib/adapters/netease-music/index.js +85 -9
- package/lib/adapters/social-douyin/index.js +2 -0
- package/lib/adapters/social-douyin-adb/aweme-detail-client.js +119 -0
- package/lib/adapters/social-douyin-adb/collector.js +114 -0
- package/lib/adapters/social-douyin-adb/index.js +18 -1
- package/lib/adapters/social-douyin-adb/watch-history-reader.js +188 -0
- package/lib/adapters/social-kuaishou/index.js +7 -2
- package/lib/adapters/social-kuaishou-adb/api-client.js +38 -18
- package/lib/adapters/social-kuaishou-adb/cookies-extension.js +16 -15
- package/lib/adapters/social-toutiao/index.js +8 -4
- package/lib/adapters/social-toutiao-adb/account-reader.js +179 -0
- package/lib/adapters/social-toutiao-adb/api-client.js +41 -17
- package/lib/adapters/social-toutiao-adb/collector.js +55 -19
- package/lib/adapters/social-toutiao-adb/cookies-extension.js +21 -1
- package/lib/adapters/social-toutiao-adb/index.js +6 -0
- package/lib/adapters/social-xiaohongshu-adb/cookies-extension.js +19 -1
- package/lib/adapters/travel-base/index.js +9 -2
- package/lib/index.js +1 -1
- 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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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:
|
|
118
|
+
uid: null,
|
|
89
119
|
nickname: null,
|
|
90
120
|
eventCounts: { profile: 0, feed: 0, collection: 0, search: 0, total: 0 },
|
|
91
|
-
lastErrorCode:
|
|
92
|
-
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:
|
|
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:
|
|
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
|
-
|
|
141
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
154
|
-
|
|
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.
|
|
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
|
+
"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",
|