@chainlesschain/personal-data-hub 0.3.0 → 0.3.6

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 (61) 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-weibo-adb-api-client.test.js +362 -0
  15. package/__tests__/adapters/social-weibo-adb-collector.test.js +201 -0
  16. package/__tests__/adapters/social-weibo-adb-snapshot-builder.test.js +189 -0
  17. package/__tests__/adapters/social-xiaohongshu-adb-collector.test.js +207 -0
  18. package/__tests__/adapters/social-xiaohongshu-adb-sign.test.js +130 -0
  19. package/__tests__/adapters/system-data-android.test.js +32 -1
  20. package/__tests__/longtail-adapters.test.js +15 -2
  21. package/__tests__/shopping-adapters.test.js +96 -0
  22. package/__tests__/sign-providers.test.js +62 -0
  23. package/__tests__/travel-adapters.test.js +163 -5
  24. package/__tests__/whatsapp-adapter.test.js +5 -2
  25. package/lib/adapters/browser-history-chrome/chrome-db-reader.js +11 -1
  26. package/lib/adapters/email-imap/email-adapter.js +224 -17
  27. package/lib/adapters/messaging-telegram/index.js +15 -12
  28. package/lib/adapters/messaging-whatsapp/index.js +15 -12
  29. package/lib/adapters/shopping-taobao/index.js +161 -21
  30. package/lib/adapters/social-bilibili-adb/api-client.js +555 -0
  31. package/lib/adapters/social-bilibili-adb/chromium-cookies-reader.js +296 -0
  32. package/lib/adapters/social-bilibili-adb/collector.js +190 -0
  33. package/lib/adapters/social-bilibili-adb/cookies-extension.js +250 -0
  34. package/lib/adapters/social-bilibili-adb/index.js +51 -0
  35. package/lib/adapters/social-bilibili-adb/snapshot-builder.js +197 -0
  36. package/lib/adapters/social-douyin/index.js +4 -0
  37. package/lib/adapters/social-douyin-adb/collector.js +165 -0
  38. package/lib/adapters/social-douyin-adb/db-extension.js +281 -0
  39. package/lib/adapters/social-douyin-adb/im-db-parser.js +287 -0
  40. package/lib/adapters/social-douyin-adb/index.js +57 -0
  41. package/lib/adapters/social-douyin-adb/snapshot-builder.js +174 -0
  42. package/lib/adapters/social-weibo-adb/api-client.js +281 -0
  43. package/lib/adapters/social-weibo-adb/collector.js +169 -0
  44. package/lib/adapters/social-weibo-adb/cookies-extension.js +251 -0
  45. package/lib/adapters/social-weibo-adb/index.js +55 -0
  46. package/lib/adapters/social-weibo-adb/snapshot-builder.js +145 -0
  47. package/lib/adapters/social-xiaohongshu-adb/api-client.js +278 -0
  48. package/lib/adapters/social-xiaohongshu-adb/collector.js +158 -0
  49. package/lib/adapters/social-xiaohongshu-adb/cookies-extension.js +211 -0
  50. package/lib/adapters/social-xiaohongshu-adb/index.js +50 -0
  51. package/lib/adapters/social-xiaohongshu-adb/sign.js +90 -0
  52. package/lib/adapters/social-xiaohongshu-adb/snapshot-builder.js +126 -0
  53. package/lib/adapters/system-data-android/adapter.js +77 -3
  54. package/lib/adapters/travel-12306/index.js +215 -29
  55. package/lib/adapters/travel-amap/index.js +16 -10
  56. package/lib/adapters/travel-ctrip/index.js +25 -9
  57. package/lib/adapters/vscode/vscode-reader.js +7 -1
  58. package/lib/sign-providers/index.js +20 -0
  59. package/lib/sign-providers/interface.js +82 -0
  60. package/lib/sign-providers/null-sign-provider.js +30 -0
  61. package/package.json +6 -1
@@ -19,20 +19,22 @@ const fs = require("node:fs");
19
19
  const { normalizeTravelRecord, parseChineseDateTime } = require("../travel-base");
20
20
 
21
21
  const NAME = "travel-amap";
22
- const VERSION = "0.5.0";
22
+ const VERSION = "0.6.0"; // 2026-05-25 — account.deviceId OPTIONAL + inputPath alias
23
23
 
24
24
  class AmapAdapter {
25
25
  constructor(opts = {}) {
26
- if (!opts.account || !opts.account.deviceId) {
27
- throw new Error("AmapAdapter: opts.account.deviceId required");
28
- }
29
- this.account = opts.account;
30
- this._dbPath = opts.dbPath || null;
26
+ // 2026-05-25 account.deviceId OPTIONAL (mirror Taobao/Ctrip/Telegram).
27
+ // sqlite-mode adapter still requires user to provide a pulled amap.db
28
+ // (`/data/data/com.autonavi.minimap/databases/amap.db`). Earlier strict
29
+ // ctor blocked auto-register at boot → silent "no adapter travel-amap"
30
+ // when Android collector ships extracted db.
31
+ this.account = opts.account || null;
32
+ this._dbPath = opts.dbPath || opts.inputPath || null;
31
33
  this._dbDriverFactory = opts.dbDriverFactory || null;
32
34
 
33
35
  this.name = NAME;
34
36
  this.version = VERSION;
35
- this.capabilities = ["sync:sqlite", "parse:amap-history"];
37
+ this.capabilities = ["sync:sqlite", "sync:snapshot", "parse:amap-history"];
36
38
  this.extractMode = "device-pull";
37
39
  this.rateLimits = {};
38
40
  this.dataDisclosure = {
@@ -46,8 +48,12 @@ class AmapAdapter {
46
48
  };
47
49
  }
48
50
 
49
- async authenticate() {
50
- return { ok: true, account: this.account.deviceId };
51
+ async authenticate(ctx = {}) {
52
+ const dbPath = (ctx && (ctx.inputPath || ctx.dbPath)) || this._dbPath;
53
+ if (!dbPath || !fs.existsSync(dbPath)) {
54
+ return { ok: true, account: this.account ? this.account.deviceId : null, mode: "ready" };
55
+ }
56
+ return { ok: true, account: this.account ? this.account.deviceId : null, mode: "snapshot-file" };
51
57
  }
52
58
 
53
59
  async healthCheck() {
@@ -55,7 +61,7 @@ class AmapAdapter {
55
61
  }
56
62
 
57
63
  async *sync(opts = {}) {
58
- const dbPath = opts.dbPath || this._dbPath;
64
+ const dbPath = opts.inputPath || opts.dbPath || this._dbPath;
59
65
  if (!dbPath || !fs.existsSync(dbPath)) return;
60
66
  const Database = this._dbDriverFactory || (() => require("better-sqlite3-multiple-ciphers"));
61
67
  const Driver = typeof Database === "function" ? Database() : Database;
@@ -16,19 +16,21 @@ const fs = require("node:fs");
16
16
  const { normalizeTravelRecord, parseChineseDateTime } = require("../travel-base");
17
17
 
18
18
  const NAME = "travel-ctrip";
19
- const VERSION = "0.5.0";
19
+ const VERSION = "0.6.0"; // §9.3b — account.email OPTIONAL + inputPath snapshot alias
20
20
 
21
21
  class CtripAdapter {
22
22
  constructor(opts = {}) {
23
- if (!opts.account || !opts.account.email) {
24
- throw new Error("CtripAdapter: opts.account.email required");
25
- }
26
- this.account = opts.account;
23
+ // §9.3b 2026-05-25 account.email OPTIONAL (mirror shopping-jd/taobao
24
+ // dual-mode). file-import mode is stateless; bookkeeping account.email
25
+ // is informational, not gating. Earlier strict ctor blocked
26
+ // auto-register at boot → Android collector ship JSON staging path
27
+ // failed with silent "no adapter travel-ctrip".
28
+ this.account = opts.account || null;
27
29
  this._dataPath = opts.dataPath || null;
28
30
 
29
31
  this.name = NAME;
30
32
  this.version = VERSION;
31
- this.capabilities = ["import:json", "parse:ctrip-orders"];
33
+ this.capabilities = ["import:json", "sync:snapshot", "parse:ctrip-orders"];
32
34
  this.extractMode = "file-import";
33
35
  this.rateLimits = {};
34
36
  this.dataDisclosure = {
@@ -40,8 +42,19 @@ class CtripAdapter {
40
42
  };
41
43
  }
42
44
 
43
- async authenticate() {
44
- return { ok: true, account: this.account.email };
45
+ async authenticate(ctx = {}) {
46
+ // Snapshot / file-import path: validate file readable when an inputPath
47
+ // / dataPath is provided. Otherwise return ok with whatever account
48
+ // bookkeeping we have (file path can be supplied later via sync(opts)).
49
+ const filePath = (ctx && ctx.inputPath) || ctx.dataPath || this._dataPath;
50
+ if (filePath) {
51
+ try { fs.accessSync(filePath, fs.constants.R_OK); }
52
+ catch (err) {
53
+ return { ok: false, reason: "INPUT_PATH_UNREADABLE", message: `not readable at ${filePath}: ${err.message}` };
54
+ }
55
+ return { ok: true, mode: "snapshot-file" };
56
+ }
57
+ return { ok: true, account: this.account ? this.account.email : null, mode: "ready" };
45
58
  }
46
59
 
47
60
  async healthCheck() {
@@ -49,7 +62,10 @@ class CtripAdapter {
49
62
  }
50
63
 
51
64
  async *sync(opts = {}) {
52
- const dataPath = opts.dataPath || this._dataPath;
65
+ // Snapshot mode aliases dataPath inputPath so Android in-APK cc can
66
+ // call syncAdapter("travel-ctrip", path) with the same shape it uses
67
+ // for the other snapshot-mode adapters (shopping-jd / travel-12306).
68
+ const dataPath = opts.inputPath || opts.dataPath || this._dataPath;
53
69
  if (!dataPath || !fs.existsSync(dataPath)) return;
54
70
  const text = fs.readFileSync(dataPath, "utf-8");
55
71
  let records;
@@ -23,7 +23,12 @@ const os = require("node:os");
23
23
  // Dual-load: bs3mc tracks Electron's ABI 140 (runtime path), plain
24
24
  // better-sqlite3 tracks Node's ABI 127 (test path). Whichever loads
25
25
  // wins. See chrome-db-reader.js for the same pattern + rationale.
26
+ //
27
+ // CRITICAL: must be lazy. Top-level invocation kills main process when
28
+ // both modules absent/ABI-mismatched (v5.0.3.87 startup crash).
29
+ let _cachedDatabaseClass = null;
26
30
  function loadDatabase() {
31
+ if (_cachedDatabaseClass) return _cachedDatabaseClass;
27
32
  for (const mod of ["better-sqlite3-multiple-ciphers", "better-sqlite3"]) {
28
33
  let cls;
29
34
  try {
@@ -35,6 +40,7 @@ function loadDatabase() {
35
40
  try {
36
41
  const probe = new cls(":memory:");
37
42
  probe.close();
43
+ _cachedDatabaseClass = cls;
38
44
  return cls;
39
45
  } catch (_e) {
40
46
  /* ABI mismatch, try next */
@@ -44,7 +50,6 @@ function loadDatabase() {
44
50
  "vscode-reader: neither better-sqlite3-multiple-ciphers nor better-sqlite3 loaded — both ABI-mismatched",
45
51
  );
46
52
  }
47
- const Database = loadDatabase();
48
53
 
49
54
  function defaultVscodeRoot() {
50
55
  if (process.platform === "win32") {
@@ -136,6 +141,7 @@ function readTerminalHistory(vscodeRoot, opts = {}) {
136
141
  }
137
142
  }
138
143
  try {
144
+ const Database = loadDatabase();
139
145
  const db = new Database(tmp, { readonly: true });
140
146
  const get = (k) => {
141
147
  try {
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Phase 6a (2026-05-25) — sign-providers entry point.
5
+ *
6
+ * Re-exports the abstract `SignProvider` contract + the default
7
+ * `NullSignProvider` impl. Real implementations live in the desktop
8
+ * Electron main process (`desktop-app-vue/src/main/sign-bridge/`)
9
+ * because they need a WebContentsView. CLI / web-shell / test code
10
+ * uses NullSignProvider unless desktop-side wiring injects a real one.
11
+ */
12
+
13
+ const { SignProvider } = require("./interface");
14
+ const { NullSignProvider, NULL_SIGN_PROVIDER } = require("./null-sign-provider");
15
+
16
+ module.exports = {
17
+ SignProvider,
18
+ NullSignProvider,
19
+ NULL_SIGN_PROVIDER,
20
+ };
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Phase 6a (2026-05-25) — SignProvider abstract contract for platforms
5
+ * that require per-request signatures (Toutiao `_signature` / Kuaishou
6
+ * `NS_sig3` / Xhs `X-S` / Douyin `X-Bogus`).
7
+ *
8
+ * Mirror of Android-side `pdh/social/SignProvider.kt`. Same 3-method
9
+ * interface so Node API clients can swap impl without changes:
10
+ *
11
+ * - `signUrl(rawUrl, purpose)` — Some platforms (Toutiao, Kuaishou)
12
+ * append `_signature=...` / `NS_sig3=...` to the URL itself; this
13
+ * returns a NEW URL with sig appended, OR `null` if signing failed.
14
+ *
15
+ * - `signedHeaders(rawUrl, purpose)` — Other platforms (Xhs) leave
16
+ * the URL alone and put `X-S` / `X-T` / `X-S-Common` in HTTP
17
+ * headers; this returns an object of header name → value, possibly
18
+ * empty when signing failed.
19
+ *
20
+ * - `shutdown()` — Release the WebContentsView / WebView held by the
21
+ * bridge implementation. Idempotent.
22
+ *
23
+ * **The two methods are independent** — a bridge for Toutiao implements
24
+ * signUrl (URL mutation), Xhs implements signedHeaders (header set).
25
+ * The base abstract returns null/empty for both so subclasses only
26
+ * implement what they need.
27
+ *
28
+ * **`purpose` string** is platform-defined opaque context the JS in the
29
+ * WebContentsView needs to discriminate which signing function to call.
30
+ * For Xhs we encode as `"<pathWithQuery>|<bodyJsonOrEmpty>"`. For
31
+ * Toutiao we use a string like `"feed"` / `"collection"` to pick the
32
+ * acrawler.js entry point. Subclasses define the schema.
33
+ */
34
+
35
+ /**
36
+ * Abstract base — direct subclassing in JS uses prototypal extension;
37
+ * the methods here are stubs returning null/empty so subclasses can
38
+ * implement only what their platform needs.
39
+ */
40
+ class SignProvider {
41
+ /**
42
+ * Sign a URL by appending platform-specific query params (e.g.
43
+ * Toutiao's `_signature`). Returns a new URL string with sig
44
+ * appended, or `null` if signing failed (bridge cold / JS rotated).
45
+ *
46
+ * The caller (api-client) MUST handle `null` by surfacing a
47
+ * lastErrorCode like -99 ("_signature unavailable") and returning
48
+ * empty result for that endpoint, NOT throwing.
49
+ *
50
+ * @param {URL|string} rawUrl the unsigned URL
51
+ * @param {string} purpose opaque context for the bridge's JS
52
+ * @returns {Promise<string|null>} signed URL or null
53
+ */
54
+ async signUrl(_rawUrl, _purpose) {
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * Sign by returning HTTP headers to merge into the request (e.g.
60
+ * Xhs `X-S` / `X-T` / `X-S-Common`). Returns an object — empty
61
+ * map `{}` when signing failed (NOT null) so callers can spread
62
+ * the result unconditionally.
63
+ *
64
+ * @param {URL|string} rawUrl
65
+ * @param {string} purpose
66
+ * @returns {Promise<{[name: string]: string}>}
67
+ */
68
+ async signedHeaders(_rawUrl, _purpose) {
69
+ return {};
70
+ }
71
+
72
+ /**
73
+ * Release WebContentsView and any background resources. Idempotent —
74
+ * the api-client calls this in a `finally` so racing shutdowns
75
+ * must not throw.
76
+ */
77
+ async shutdown() {
78
+ // no-op default
79
+ }
80
+ }
81
+
82
+ module.exports = { SignProvider };
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Phase 6a (2026-05-25) — NullSignProvider: default no-op for callers
5
+ * that don't have a WebContentsView context (e.g. headless `cc serve`
6
+ * without Electron, or unit tests).
7
+ *
8
+ * Mirror of Android `pdh/social/NullSignProvider.kt`.
9
+ *
10
+ * API clients use this as the default — when wiring upgrades to
11
+ * Electron+WebContentsView, swap to `XhsSignBridge` / `ToutiaoSignBridge`
12
+ * etc. **without changing the api-client code**. The signing endpoints
13
+ * gracefully degrade to "best-effort" or "empty result" rather than
14
+ * throwing.
15
+ */
16
+
17
+ const { SignProvider } = require("./interface");
18
+
19
+ class NullSignProvider extends SignProvider {
20
+ // Inherits all stubs from base — signUrl returns null,
21
+ // signedHeaders returns {}, shutdown is no-op.
22
+ }
23
+
24
+ /** Frozen singleton — callers should not create multiple NullSignProviders. */
25
+ const NULL_SIGN_PROVIDER = Object.freeze(new NullSignProvider());
26
+
27
+ module.exports = {
28
+ NullSignProvider,
29
+ NULL_SIGN_PROVIDER,
30
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainlesschain/personal-data-hub",
3
- "version": "0.3.0",
3
+ "version": "0.3.6",
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",
@@ -27,6 +27,7 @@
27
27
  "./bridges/cc-llm-adapter": "./lib/bridges/cc-llm-adapter.js",
28
28
  "./bridges/cc-kg-sink": "./lib/bridges/cc-kg-sink.js",
29
29
  "./bridges/cc-rag-sink": "./lib/bridges/cc-rag-sink.js",
30
+ "./sign-providers": "./lib/sign-providers/index.js",
30
31
  "./adapters/email-imap": "./lib/adapters/email-imap/index.js",
31
32
  "./adapters/alipay-bill": "./lib/adapters/alipay-bill/index.js",
32
33
  "./adapters/system-data": "./lib/adapters/system-data/index.js",
@@ -53,9 +54,13 @@
53
54
  "./adapters/shopping-jd": "./lib/adapters/shopping-jd/index.js",
54
55
  "./adapters/shopping-meituan": "./lib/adapters/shopping-meituan/index.js",
55
56
  "./adapters/social-bilibili": "./lib/adapters/social-bilibili/index.js",
57
+ "./adapters/social-bilibili-adb": "./lib/adapters/social-bilibili-adb/index.js",
56
58
  "./adapters/social-weibo": "./lib/adapters/social-weibo/index.js",
59
+ "./adapters/social-weibo-adb": "./lib/adapters/social-weibo-adb/index.js",
57
60
  "./adapters/social-douyin": "./lib/adapters/social-douyin/index.js",
61
+ "./adapters/social-douyin-adb": "./lib/adapters/social-douyin-adb/index.js",
58
62
  "./adapters/social-xiaohongshu": "./lib/adapters/social-xiaohongshu/index.js",
63
+ "./adapters/social-xiaohongshu-adb": "./lib/adapters/social-xiaohongshu-adb/index.js",
59
64
  "./adapters/messaging-qq": "./lib/adapters/messaging-qq/index.js",
60
65
  "./adapters/messaging-telegram": "./lib/adapters/messaging-telegram/index.js",
61
66
  "./adapters/messaging-whatsapp": "./lib/adapters/messaging-whatsapp/index.js",