@chainlesschain/personal-data-hub 0.4.23 → 0.4.25

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 (39) hide show
  1. package/__tests__/adapters/bank-family.test.js +125 -0
  2. package/__tests__/adapters/car-mercedesme.test.js +74 -0
  3. package/__tests__/adapters/finance-dcep.test.js +74 -0
  4. package/__tests__/adapters/fitness-joyrun.test.js +82 -0
  5. package/__tests__/adapters/gov-12123.test.js +103 -0
  6. package/__tests__/adapters/gov-ixiamen.test.js +2 -2
  7. package/__tests__/adapters/music-qq.test.js +112 -0
  8. package/__tests__/adapters/reading-family.test.js +108 -0
  9. package/__tests__/adapters/travel-didi-consumer.test.js +66 -0
  10. package/__tests__/audio-ximalaya-snapshot.test.js +279 -0
  11. package/__tests__/fitness-keep-snapshot.test.js +224 -0
  12. package/__tests__/shopping-eleme-snapshot.test.js +454 -0
  13. package/__tests__/shopping-vipshop-snapshot.test.js +425 -0
  14. package/__tests__/shopping-xianyu-snapshot.test.js +451 -0
  15. package/__tests__/social-douban-snapshot.test.js +351 -0
  16. package/lib/adapter-guide.js +19 -1
  17. package/lib/adapters/_bank-base.js +405 -0
  18. package/lib/adapters/_reading-base.js +315 -0
  19. package/lib/adapters/audio-ximalaya/index.js +414 -0
  20. package/lib/adapters/bank-bankcomm/index.js +27 -0
  21. package/lib/adapters/bank-boc/index.js +26 -0
  22. package/lib/adapters/bank-cmbc/index.js +26 -0
  23. package/lib/adapters/bank-icbc/index.js +27 -0
  24. package/lib/adapters/car-mercedesme/index.js +225 -0
  25. package/lib/adapters/finance-dcep/index.js +302 -0
  26. package/lib/adapters/fitness-joyrun/index.js +295 -0
  27. package/lib/adapters/fitness-keep/index.js +343 -0
  28. package/lib/adapters/gov-12123/index.js +391 -0
  29. package/lib/adapters/gov-ixiamen/index.js +17 -10
  30. package/lib/adapters/music-qq/index.js +372 -0
  31. package/lib/adapters/reading-fanqie/index.js +61 -0
  32. package/lib/adapters/reading-qimao/index.js +61 -0
  33. package/lib/adapters/shopping-eleme/index.js +441 -0
  34. package/lib/adapters/shopping-vipshop/index.js +429 -0
  35. package/lib/adapters/shopping-xianyu/index.js +454 -0
  36. package/lib/adapters/social-douban/index.js +564 -0
  37. package/lib/adapters/travel-didi-consumer/index.js +148 -0
  38. package/lib/index.js +36 -0
  39. package/package.json +1 -1
@@ -0,0 +1,148 @@
1
+ /**
2
+ * §12.1 Phase 13+ — 滴滴出行 consumer app (com.sdu.didi.psnger) ride adapter.
3
+ * Device-discovered gap (2026-06-15): distinct from travel-didi (滴滴企业版,
4
+ * com.didi.es.psngr). Same ride→car TravelRecord shape, so it REUSES
5
+ * travel-didi's order mapping helpers; only NAME + the consumer order-centre
6
+ * endpoint differ.
7
+ *
8
+ * BEST-EFFORT: api.udache.com / common.diditaxi.com.cn endpoints are FABRICATED
9
+ * placeholders (overridable via opts.ordersUrl, NOT field-verified — FAMILY-23
10
+ * playbook); snapshot/file-import is the reliable path; cookie path surfaces
11
+ * auth.unverified=true. sensitivity:"medium" (ride start/end addresses).
12
+ */
13
+
14
+ "use strict";
15
+
16
+ const fs = require("node:fs");
17
+ const { normalizeTravelRecord } = require("../travel-base");
18
+ const { CookieAuth } = require("../shopping-base");
19
+ const { orderToRecord, extractOrders, parseRecords } = require("../travel-didi");
20
+
21
+ const NAME = "travel-didi-consumer";
22
+ const VERSION = "0.1.0";
23
+
24
+ // Best-effort 滴滴出行 (consumer) order-list endpoint. Overridable via opts.ordersUrl.
25
+ const DIDI_CONSUMER_ORDERS_URL = "https://api.udache.com/gulfstream/api/v1/order/list";
26
+ const DEFAULT_PAGE_SIZE = 20;
27
+ const DEFAULT_MAX_PAGES = 10;
28
+
29
+ class DidiConsumerAdapter {
30
+ constructor(opts = {}) {
31
+ this.account = opts.account || null;
32
+ this._dataPath = opts.dataPath || null;
33
+ this._cookieAuth =
34
+ opts.account && opts.account.cookies ? new CookieAuth({ platform: "didi", cookies: opts.account.cookies }) : null;
35
+ this._fetchFn = typeof opts.fetchFn === "function" ? opts.fetchFn : defaultFetch;
36
+ this._signProvider = typeof opts.signProvider === "function" ? opts.signProvider : null;
37
+ this._ordersUrl =
38
+ typeof opts.ordersUrl === "string" && opts.ordersUrl.length > 0 ? opts.ordersUrl : DIDI_CONSUMER_ORDERS_URL;
39
+
40
+ this.name = NAME;
41
+ this.version = VERSION;
42
+ this.capabilities = ["import:json", "sync:snapshot", "sync:cookie-api", "parse:didi-rides"];
43
+ this.extractMode = "file-import";
44
+ this.rateLimits = {};
45
+ this.dataDisclosure = {
46
+ fields: ["didi:orderId / fromAddress / toAddress / departTime / arriveTime / fare / carType"],
47
+ sensitivity: "medium",
48
+ legalGate: false,
49
+ };
50
+ this._deps = { fs };
51
+ }
52
+
53
+ async authenticate(ctx = {}) {
54
+ const filePath = (ctx && ctx.inputPath) || ctx.dataPath || this._dataPath;
55
+ if (filePath) {
56
+ try {
57
+ this._deps.fs.accessSync(filePath, this._deps.fs.constants.R_OK);
58
+ } catch (err) {
59
+ return { ok: false, reason: "INPUT_PATH_UNREADABLE", message: `not readable at ${filePath}: ${err.message}` };
60
+ }
61
+ return { ok: true, mode: "snapshot-file" };
62
+ }
63
+ if (this._cookieAuth) {
64
+ const ok = await this._cookieAuth.validate();
65
+ if (!ok) return { ok: false, reason: "INVALID_COOKIE", error: "cookies missing" };
66
+ return { ok: true, account: (this.account && this.account.phone) || null, mode: "cookie", unverified: true };
67
+ }
68
+ return { ok: true, account: null, mode: "ready" };
69
+ }
70
+
71
+ async healthCheck() {
72
+ if (this._cookieAuth) {
73
+ const r = await this.authenticate();
74
+ return r.ok ? { ok: true, lastChecked: Date.now(), unverified: true } : { ok: false, reason: r.reason, error: r.error };
75
+ }
76
+ return { ok: true, lastChecked: Date.now() };
77
+ }
78
+
79
+ async *sync(opts = {}) {
80
+ const dataPath = opts.inputPath || opts.dataPath || this._dataPath;
81
+ if (dataPath) {
82
+ if (!this._deps.fs.existsSync(dataPath)) return;
83
+ const text = this._deps.fs.readFileSync(dataPath, "utf-8");
84
+ let records;
85
+ try {
86
+ records = parseRecords(text);
87
+ } catch (err) {
88
+ throw new Error(`DidiConsumerAdapter: parse failed: ${err.message}`);
89
+ }
90
+ for (const r of records) {
91
+ // re-stamp vendor so dedup IDs don't collide with the enterprise adapter
92
+ yield { adapter: NAME, originalId: r.recordId, capturedAt: r.bookedAt || r.departureMs || Date.now(), payload: { record: r } };
93
+ }
94
+ return;
95
+ }
96
+ if (this._cookieAuth) yield* this._syncViaCookie(opts);
97
+ }
98
+
99
+ async *_syncViaCookie(opts = {}) {
100
+ if (!(await this._cookieAuth.validate())) return;
101
+ const cookies = this._cookieAuth.toHeader();
102
+ const sinceMs = opts.sinceWatermark != null ? parseInt(String(opts.sinceWatermark), 10) || 0 : Date.now() - 365 * 24 * 3600_000;
103
+ const pageSize = Number.isFinite(opts.pageSize) ? opts.pageSize : DEFAULT_PAGE_SIZE;
104
+ const maxPages = Number.isInteger(opts.maxPages) && opts.maxPages > 0 ? opts.maxPages : DEFAULT_MAX_PAGES;
105
+ const limit = Number.isInteger(opts.limit) && opts.limit > 0 ? opts.limit : Infinity;
106
+
107
+ let emitted = 0;
108
+ let pageIndex = 1;
109
+ while (pageIndex <= maxPages) {
110
+ const query = { pageIndex, pageSize, ts: Date.now() };
111
+ let sign = null;
112
+ if (this._signProvider) sign = await this._signProvider({ url: this._ordersUrl, query, cookies });
113
+ const resp = await this._fetchFn({ url: this._ordersUrl, cookies, query, sign });
114
+ const rides = extractOrders(resp);
115
+ if (!rides.length) break;
116
+ let pageHasNew = false;
117
+ let reachedWatermark = false;
118
+ for (const raw of rides) {
119
+ const rec = orderToRecord(raw, { capturedVia: "cookie-api" });
120
+ if (!rec) continue;
121
+ const ts = rec.departureMs || rec.bookedAt || null;
122
+ if (ts && ts < sinceMs) {
123
+ reachedWatermark = true;
124
+ break;
125
+ }
126
+ pageHasNew = true;
127
+ if (emitted >= limit) return;
128
+ yield { adapter: NAME, originalId: rec.recordId, capturedAt: ts || Date.now(), payload: { record: rec } };
129
+ emitted += 1;
130
+ }
131
+ if (reachedWatermark || !pageHasNew || rides.length < pageSize) break;
132
+ pageIndex += 1;
133
+ }
134
+ }
135
+
136
+ normalize(raw) {
137
+ if (!raw || !raw.payload || !raw.payload.record) {
138
+ throw new Error("DidiConsumerAdapter.normalize: raw.payload.record missing");
139
+ }
140
+ return normalizeTravelRecord(raw.payload.record, { adapterName: NAME, adapterVersion: VERSION });
141
+ }
142
+ }
143
+
144
+ async function defaultFetch(_opts) {
145
+ throw new Error("travel-didi-consumer: no fetchFn configured for cookie-api mode");
146
+ }
147
+
148
+ module.exports = { DidiConsumerAdapter, NAME, VERSION };
package/lib/index.js CHANGED
@@ -48,11 +48,15 @@ const shoppingBase = require("./adapters/shopping-base");
48
48
  const { TaobaoAdapter } = require("./adapters/shopping-taobao");
49
49
  const { JdAdapter } = require("./adapters/shopping-jd");
50
50
  const { MeituanAdapter } = require("./adapters/shopping-meituan");
51
+ const { ElemeAdapter } = require("./adapters/shopping-eleme");
51
52
  const { PinduoduoAdapter } = require("./adapters/shopping-pinduoduo");
52
53
  const { DianpingAdapter } = require("./adapters/shopping-dianping");
54
+ const { XianyuAdapter } = require("./adapters/shopping-xianyu");
55
+ const { VipshopAdapter } = require("./adapters/shopping-vipshop");
53
56
  const { BilibiliAdapter } = require("./adapters/social-bilibili");
54
57
  const { WeiboAdapter } = require("./adapters/social-weibo");
55
58
  const { ZhihuAdapter } = require("./adapters/social-zhihu");
59
+ const { DoubanAdapter } = require("./adapters/social-douban");
56
60
  const { BossZhipinAdapter } = require("./adapters/recruit-boss");
57
61
  const { CsdnAdapter } = require("./adapters/social-csdn");
58
62
  const { DongchediAdapter } = require("./adapters/social-dongchedi");
@@ -73,6 +77,14 @@ const { QQPcAdapter } = require("./adapters/qq-pc");
73
77
  const { AppleHealthAdapter } = require("./adapters/apple-health");
74
78
  const { NeteaseMusicAdapter } = require("./adapters/netease-music");
75
79
  const { KugouMusicAdapter } = require("./adapters/music-kugou");
80
+ const { QQMusicAdapter } = require("./adapters/music-qq");
81
+ const { XimalayaAdapter } = require("./adapters/audio-ximalaya");
82
+ const { FanqieReadingAdapter } = require("./adapters/reading-fanqie");
83
+ const { QimaoReadingAdapter } = require("./adapters/reading-qimao");
84
+ const { JoyrunAdapter } = require("./adapters/fitness-joyrun");
85
+ const { KeepAdapter } = require("./adapters/fitness-keep");
86
+ const { DidiConsumerAdapter } = require("./adapters/travel-didi-consumer");
87
+ const { MercedesMeAdapter } = require("./adapters/car-mercedesme");
76
88
  const { IqiyiVideoAdapter } = require("./adapters/video-iqiyi");
77
89
  const { TencentVideoAdapter } = require("./adapters/video-tencent");
78
90
  const { XiguaVideoAdapter } = require("./adapters/video-xigua");
@@ -84,6 +96,12 @@ const { CamScannerDocAdapter } = require("./adapters/doc-camscanner");
84
96
  const { IXiamenAdapter } = require("./adapters/gov-ixiamen");
85
97
  const { MeiyouAdapter } = require("./adapters/health-meiyou");
86
98
  const { TaxAdapter } = require("./adapters/gov-tax");
99
+ const { CmbcBankAdapter } = require("./adapters/bank-cmbc");
100
+ const { BocBankAdapter } = require("./adapters/bank-boc");
101
+ const { BankcommBankAdapter } = require("./adapters/bank-bankcomm");
102
+ const { IcbcBankAdapter } = require("./adapters/bank-icbc");
103
+ const { DcepAdapter } = require("./adapters/finance-dcep");
104
+ const { Tmri12123Adapter } = require("./adapters/gov-12123");
87
105
  const { DingTalkPcAdapter } = require("./adapters/dingtalk-pc");
88
106
  const { FeishuPcAdapter } = require("./adapters/feishu-pc");
89
107
  const { WeWorkPcAdapter } = require("./adapters/wework-pc");
@@ -296,13 +314,17 @@ module.exports = {
296
314
  TaobaoAdapter,
297
315
  JdAdapter,
298
316
  MeituanAdapter,
317
+ ElemeAdapter,
299
318
  PinduoduoAdapter,
300
319
  DianpingAdapter,
320
+ XianyuAdapter,
321
+ VipshopAdapter,
301
322
 
302
323
  // Phase 13+ — long-tail social + messaging (借 sjqz parsers)
303
324
  BilibiliAdapter,
304
325
  WeiboAdapter,
305
326
  ZhihuAdapter,
327
+ DoubanAdapter,
306
328
  BossZhipinAdapter,
307
329
  CsdnAdapter,
308
330
  DongchediAdapter,
@@ -322,6 +344,14 @@ module.exports = {
322
344
  AppleHealthAdapter,
323
345
  NeteaseMusicAdapter,
324
346
  KugouMusicAdapter,
347
+ QQMusicAdapter,
348
+ XimalayaAdapter,
349
+ FanqieReadingAdapter,
350
+ QimaoReadingAdapter,
351
+ JoyrunAdapter,
352
+ KeepAdapter,
353
+ DidiConsumerAdapter,
354
+ MercedesMeAdapter,
325
355
  IqiyiVideoAdapter,
326
356
  TencentVideoAdapter,
327
357
  XiguaVideoAdapter,
@@ -333,6 +363,12 @@ module.exports = {
333
363
  IXiamenAdapter,
334
364
  MeiyouAdapter,
335
365
  TaxAdapter,
366
+ CmbcBankAdapter,
367
+ BocBankAdapter,
368
+ BankcommBankAdapter,
369
+ IcbcBankAdapter,
370
+ DcepAdapter,
371
+ Tmri12123Adapter,
336
372
  DingTalkPcAdapter,
337
373
  FeishuPcAdapter,
338
374
  WeWorkPcAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainlesschain/personal-data-hub",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
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",