@chainlesschain/personal-data-hub 0.3.1 → 0.3.7

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 (79) hide show
  1. package/__tests__/adapters/email-adapter-snapshot.test.js +237 -0
  2. package/__tests__/adapters/email-adapter.test.js +1 -1
  3. package/__tests__/adapters/email-pdf-extractor.test.js +1 -1
  4. package/__tests__/adapters/email-retry-progress.test.js +1 -1
  5. package/__tests__/adapters/email-templates.test.js +1 -1
  6. package/__tests__/adapters/social-bilibili-adb-api-client.test.js +721 -0
  7. package/__tests__/adapters/social-bilibili-adb-chromium-cookies-reader.test.js +346 -0
  8. package/__tests__/adapters/social-bilibili-adb-collector.test.js +284 -0
  9. package/__tests__/adapters/social-bilibili-adb-cookies-extension.test.js +343 -0
  10. package/__tests__/adapters/social-bilibili-adb-snapshot-builder.test.js +296 -0
  11. package/__tests__/adapters/social-douyin-adb-collector.test.js +254 -0
  12. package/__tests__/adapters/social-douyin-adb-im-db-parser.test.js +304 -0
  13. package/__tests__/adapters/social-douyin-adb-snapshot-builder.test.js +216 -0
  14. package/__tests__/adapters/social-kuaishou-adb-api-client.test.js +432 -0
  15. package/__tests__/adapters/social-kuaishou-adb-collector.test.js +276 -0
  16. package/__tests__/adapters/social-kuaishou-adb-cookies-extension.test.js +141 -0
  17. package/__tests__/adapters/social-kuaishou-adb-snapshot-builder.test.js +178 -0
  18. package/__tests__/adapters/social-toutiao-adb-api-client.test.js +537 -0
  19. package/__tests__/adapters/social-toutiao-adb-collector.test.js +285 -0
  20. package/__tests__/adapters/social-toutiao-adb-cookies-extension.test.js +163 -0
  21. package/__tests__/adapters/social-toutiao-adb-snapshot-builder.test.js +196 -0
  22. package/__tests__/adapters/social-weibo-adb-api-client.test.js +362 -0
  23. package/__tests__/adapters/social-weibo-adb-collector.test.js +201 -0
  24. package/__tests__/adapters/social-weibo-adb-snapshot-builder.test.js +189 -0
  25. package/__tests__/adapters/social-xiaohongshu-adb-collector.test.js +207 -0
  26. package/__tests__/adapters/social-xiaohongshu-adb-sign-provider-injection.test.js +351 -0
  27. package/__tests__/adapters/social-xiaohongshu-adb-sign.test.js +130 -0
  28. package/__tests__/adapters/system-data-android.test.js +32 -1
  29. package/__tests__/longtail-adapters.test.js +15 -2
  30. package/__tests__/shopping-adapters.test.js +96 -0
  31. package/__tests__/sign-providers.test.js +62 -0
  32. package/__tests__/travel-adapters.test.js +66 -0
  33. package/__tests__/whatsapp-adapter.test.js +5 -2
  34. package/lib/adapters/browser-history-chrome/chrome-db-reader.js +11 -1
  35. package/lib/adapters/email-imap/email-adapter.js +224 -17
  36. package/lib/adapters/messaging-telegram/index.js +15 -12
  37. package/lib/adapters/messaging-whatsapp/index.js +15 -12
  38. package/lib/adapters/shopping-taobao/index.js +161 -21
  39. package/lib/adapters/social-bilibili-adb/api-client.js +555 -0
  40. package/lib/adapters/social-bilibili-adb/chromium-cookies-reader.js +296 -0
  41. package/lib/adapters/social-bilibili-adb/collector.js +190 -0
  42. package/lib/adapters/social-bilibili-adb/cookies-extension.js +250 -0
  43. package/lib/adapters/social-bilibili-adb/index.js +51 -0
  44. package/lib/adapters/social-bilibili-adb/snapshot-builder.js +197 -0
  45. package/lib/adapters/social-douyin/index.js +4 -0
  46. package/lib/adapters/social-douyin-adb/collector.js +165 -0
  47. package/lib/adapters/social-douyin-adb/db-extension.js +281 -0
  48. package/lib/adapters/social-douyin-adb/im-db-parser.js +287 -0
  49. package/lib/adapters/social-douyin-adb/index.js +57 -0
  50. package/lib/adapters/social-douyin-adb/snapshot-builder.js +174 -0
  51. package/lib/adapters/social-kuaishou-adb/api-client.js +397 -0
  52. package/lib/adapters/social-kuaishou-adb/collector.js +196 -0
  53. package/lib/adapters/social-kuaishou-adb/cookies-extension.js +261 -0
  54. package/lib/adapters/social-kuaishou-adb/index.js +53 -0
  55. package/lib/adapters/social-kuaishou-adb/snapshot-builder.js +145 -0
  56. package/lib/adapters/social-toutiao-adb/api-client.js +377 -0
  57. package/lib/adapters/social-toutiao-adb/collector.js +200 -0
  58. package/lib/adapters/social-toutiao-adb/cookies-extension.js +266 -0
  59. package/lib/adapters/social-toutiao-adb/index.js +52 -0
  60. package/lib/adapters/social-toutiao-adb/snapshot-builder.js +148 -0
  61. package/lib/adapters/social-weibo-adb/api-client.js +281 -0
  62. package/lib/adapters/social-weibo-adb/collector.js +169 -0
  63. package/lib/adapters/social-weibo-adb/cookies-extension.js +251 -0
  64. package/lib/adapters/social-weibo-adb/index.js +55 -0
  65. package/lib/adapters/social-weibo-adb/snapshot-builder.js +145 -0
  66. package/lib/adapters/social-xiaohongshu-adb/api-client.js +309 -0
  67. package/lib/adapters/social-xiaohongshu-adb/collector.js +209 -0
  68. package/lib/adapters/social-xiaohongshu-adb/cookies-extension.js +211 -0
  69. package/lib/adapters/social-xiaohongshu-adb/index.js +50 -0
  70. package/lib/adapters/social-xiaohongshu-adb/sign.js +90 -0
  71. package/lib/adapters/social-xiaohongshu-adb/snapshot-builder.js +126 -0
  72. package/lib/adapters/system-data-android/adapter.js +77 -3
  73. package/lib/adapters/travel-amap/index.js +16 -10
  74. package/lib/adapters/travel-ctrip/index.js +25 -9
  75. package/lib/adapters/vscode/vscode-reader.js +7 -1
  76. package/lib/sign-providers/index.js +20 -0
  77. package/lib/sign-providers/interface.js +82 -0
  78. package/lib/sign-providers/null-sign-provider.js +30 -0
  79. package/package.json +10 -1
@@ -18,28 +18,36 @@
18
18
 
19
19
  "use strict";
20
20
 
21
+ const fs = require("node:fs");
21
22
  const { normalizeOrderRecord, CookieAuth } = require("../shopping-base");
22
23
 
23
24
  const NAME = "shopping-taobao";
24
- const VERSION = "0.5.0";
25
+ const VERSION = "0.6.0"; // §2.4d snapshot mode for Android in-APK cc
26
+ const SNAPSHOT_SCHEMA_VERSION = 1;
27
+
28
+ const KIND_ORDER = "order";
29
+ const VALID_SNAPSHOT_KINDS = Object.freeze([KIND_ORDER]);
25
30
 
26
31
  const TAOBAO_ORDERS_URL = "https://h5.m.taobao.com/mlapp/olist.html";
27
32
 
28
33
  class TaobaoAdapter {
29
34
  constructor(opts = {}) {
30
- if (!opts.account || !opts.account.userId) {
31
- throw new Error("TaobaoAdapter: opts.account.userId required");
32
- }
33
- this.account = opts.account;
34
- this._cookieAuth = new CookieAuth({
35
- platform: "taobao",
36
- cookies: opts.account.cookies || "",
37
- });
35
+ // §2.4d v0.2 account.userId OPTIONAL (mirror shopping-jd/meituan/pinduoduo
36
+ // dual-mode). Snapshot mode is stateless; cookie mode requires it; checked
37
+ // at sync time, not construction. Earlier strict ctor blocked auto-register
38
+ // at boot → user-driven HTML import worked but JSON snapshot path didn't.
39
+ this.account = opts.account || null;
40
+ this._cookieAuth = opts.account
41
+ ? new CookieAuth({
42
+ platform: "taobao",
43
+ cookies: opts.account.cookies || "",
44
+ })
45
+ : null;
38
46
  this._fetchFn = typeof opts.fetchFn === "function" ? opts.fetchFn : defaultFetch;
39
47
 
40
48
  this.name = NAME;
41
49
  this.version = VERSION;
42
- this.capabilities = ["sync:cookie-api", "parse:taobao-orders"];
50
+ this.capabilities = ["sync:snapshot", "sync:cookie-api", "parse:taobao-orders"];
43
51
  this.extractMode = "web-api";
44
52
  this.rateLimits = { perMinute: 6, perDay: 200 }; // respect Taobao风控
45
53
  this.dataDisclosure = {
@@ -48,23 +56,132 @@ class TaobaoAdapter {
48
56
  ],
49
57
  sensitivity: "high",
50
58
  legalGate: false,
59
+ defaultInclude: { order: true },
51
60
  };
61
+
62
+ // _deps injection seam — vi.mock fs doesn't intercept inlined CJS require.
63
+ this._deps = { fs };
52
64
  }
53
65
 
54
- async authenticate() {
55
- const ok = await this._cookieAuth.validate();
56
- if (!ok) return { ok: false, reason: "INVALID_COOKIE", error: "cookies missing or empty" };
57
- return { ok: true, account: this.account.userId };
66
+ async authenticate(ctx = {}) {
67
+ if (ctx && typeof ctx.inputPath === "string" && ctx.inputPath.length > 0) {
68
+ try {
69
+ this._deps.fs.accessSync(ctx.inputPath, this._deps.fs.constants.R_OK);
70
+ } catch (err) {
71
+ return {
72
+ ok: false,
73
+ reason: "INPUT_PATH_UNREADABLE",
74
+ message: `snapshot not readable at ${ctx.inputPath}: ${err.message}`,
75
+ };
76
+ }
77
+ return { ok: true, mode: "snapshot-file" };
78
+ }
79
+ if (this._cookieAuth) {
80
+ const ok = await this._cookieAuth.validate();
81
+ if (!ok) return { ok: false, reason: "INVALID_COOKIE", error: "cookies missing or empty" };
82
+ if (!this.account || !this.account.userId) {
83
+ return { ok: false, reason: "NO_ACCOUNT_USERID", message: "cookie mode requires account.userId" };
84
+ }
85
+ return { ok: true, account: this.account.userId, mode: "cookie" };
86
+ }
87
+ return {
88
+ ok: false,
89
+ reason: "NO_INPUT",
90
+ message: "TaobaoAdapter.authenticate: needs opts.inputPath (snapshot mode) OR opts.account.cookies (cookie mode)",
91
+ };
58
92
  }
59
93
 
60
94
  async healthCheck() {
61
- const r = await this.authenticate();
62
- return r.ok
63
- ? { ok: true, lastChecked: Date.now() }
64
- : { ok: false, reason: r.reason, error: r.error };
95
+ if (this._cookieAuth) {
96
+ const r = await this.authenticate();
97
+ return r.ok
98
+ ? { ok: true, lastChecked: Date.now() }
99
+ : { ok: false, reason: r.reason, error: r.error };
100
+ }
101
+ return { ok: true, lastChecked: Date.now() };
65
102
  }
66
103
 
67
104
  async *sync(opts = {}) {
105
+ if (typeof opts.inputPath === "string" && opts.inputPath.length > 0) {
106
+ yield* this._syncViaSnapshot(opts);
107
+ return;
108
+ }
109
+ if (this._cookieAuth) {
110
+ yield* this._syncViaCookie(opts);
111
+ return;
112
+ }
113
+ throw new Error(
114
+ "TaobaoAdapter.sync: needs opts.inputPath (snapshot mode, Android in-APK cc) OR opts.account.cookies (cookie mode)",
115
+ );
116
+ }
117
+
118
+ async *_syncViaSnapshot(opts) {
119
+ const raw = this._deps.fs.readFileSync(opts.inputPath, "utf-8");
120
+ let snapshot;
121
+ try {
122
+ snapshot = JSON.parse(raw);
123
+ } catch (err) {
124
+ throw new Error(
125
+ `shopping-taobao.sync: snapshot must be JSON (v0.3 will add HTML parsing for SAF-exported pages). Got parse error: ${err.message}`,
126
+ );
127
+ }
128
+ if (
129
+ !snapshot ||
130
+ typeof snapshot !== "object" ||
131
+ snapshot.schemaVersion !== SNAPSHOT_SCHEMA_VERSION
132
+ ) {
133
+ throw new Error(
134
+ `shopping-taobao.sync: snapshot schemaVersion mismatch (got ${snapshot && snapshot.schemaVersion}, expected ${SNAPSHOT_SCHEMA_VERSION})`,
135
+ );
136
+ }
137
+ const fallbackCapturedAt =
138
+ Number.isFinite(snapshot.snapshottedAt) && snapshot.snapshottedAt > 0
139
+ ? Math.floor(snapshot.snapshottedAt)
140
+ : Date.now();
141
+ const account =
142
+ snapshot.account && typeof snapshot.account === "object"
143
+ ? snapshot.account
144
+ : null;
145
+ const include = opts.include || {};
146
+ const limit =
147
+ Number.isInteger(opts.limit) && opts.limit > 0 ? opts.limit : Infinity;
148
+
149
+ const events = Array.isArray(snapshot.events) ? snapshot.events : [];
150
+ let emitted = 0;
151
+ for (const ev of events) {
152
+ if (emitted >= limit) return;
153
+ if (!ev || typeof ev !== "object") continue;
154
+ const kind = ev.kind;
155
+ if (!VALID_SNAPSHOT_KINDS.includes(kind)) continue;
156
+ if (include[kind] === false) continue;
157
+
158
+ const capturedAt =
159
+ parseTime(ev.capturedAt) ||
160
+ parseTime(ev.placedAt) ||
161
+ parseTime(ev.paidAt) ||
162
+ fallbackCapturedAt;
163
+ const id =
164
+ (typeof ev.id === "string" && ev.id.length > 0 && ev.id) ||
165
+ ev.orderId ||
166
+ null;
167
+
168
+ yield {
169
+ adapter: NAME,
170
+ kind,
171
+ originalId: stableOriginalId(kind, id),
172
+ capturedAt,
173
+ payload: { ...ev, account },
174
+ };
175
+ emitted += 1;
176
+ }
177
+ }
178
+
179
+ async *_syncViaCookie(opts = {}) {
180
+ if (!this.account || !this.account.userId) {
181
+ throw new Error(
182
+ "TaobaoAdapter._syncViaCookie: account.userId required (set via new TaobaoAdapter({ account: { userId } }))",
183
+ );
184
+ }
68
185
  if (!(await this._cookieAuth.validate())) return;
69
186
  const sinceMs = opts.sinceWatermark != null
70
187
  ? parseWatermarkMs(opts.sinceWatermark)
@@ -97,16 +214,39 @@ class TaobaoAdapter {
97
214
  }
98
215
 
99
216
  normalize(raw) {
100
- if (!raw || !raw.payload || !raw.payload.record) {
101
- throw new Error("TaobaoAdapter.normalize: raw.payload.record missing");
217
+ if (!raw || !raw.payload) {
218
+ throw new Error("TaobaoAdapter.normalize: raw.payload missing");
219
+ }
220
+ // Snapshot mode payload is the raw event spread + account; cookie mode
221
+ // wraps a normalized record under payload.record. Dispatch on shape.
222
+ if (raw.payload.record) {
223
+ return normalizeOrderRecord(raw.payload.record, {
224
+ adapterName: NAME,
225
+ adapterVersion: VERSION,
226
+ });
102
227
  }
103
- return normalizeOrderRecord(raw.payload.record, {
228
+ // Snapshot path: the Android collector ships records that already match
229
+ // the OrderRecord shape (vendorId/orderId/placedAt/...). Pass through.
230
+ return normalizeOrderRecord(raw.payload, {
104
231
  adapterName: NAME,
105
232
  adapterVersion: VERSION,
106
233
  });
107
234
  }
108
235
  }
109
236
 
237
+ function parseTime(v) {
238
+ if (Number.isFinite(v) && v > 0) return v < 1e12 ? v * 1000 : v;
239
+ if (typeof v === "string") {
240
+ const t = Date.parse(v);
241
+ if (Number.isFinite(t)) return t;
242
+ }
243
+ return null;
244
+ }
245
+
246
+ function stableOriginalId(kind, id) {
247
+ return id ? `taobao:${kind}:${id}` : `taobao:${kind}:unknown-${Date.now()}`;
248
+ }
249
+
110
250
  function orderToRecord(o) {
111
251
  if (!o || typeof o !== "object") return null;
112
252
  const orderId = o.bizOrderId || o.orderId || o.id;