@chainlesschain/personal-data-hub 0.3.9 → 0.4.0

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 (57) hide show
  1. package/__tests__/adapters/apple-health.test.js +95 -0
  2. package/__tests__/adapters/email-templates.test.js +123 -0
  3. package/__tests__/adapters/family-23-collectors-scaffold.test.js +178 -0
  4. package/__tests__/adapters/game-genshin-scaffold.test.js +107 -0
  5. package/__tests__/adapters/git-activity.test.js +7 -1
  6. package/__tests__/adapters/local-im-pc.test.js +149 -0
  7. package/__tests__/adapters/netease-music.test.js +74 -0
  8. package/__tests__/adapters/qq-pc-direct-read.test.js +186 -0
  9. package/__tests__/adapters/system-data-adapter.test.js +4 -1
  10. package/__tests__/adapters/wechat-pc-direct-read.test.js +207 -0
  11. package/__tests__/adapters/weread.test.js +123 -0
  12. package/__tests__/analysis.test.js +120 -15
  13. package/__tests__/mobile-extractor-encrypted.test.js +460 -0
  14. package/__tests__/prompt-builder.test.js +25 -0
  15. package/__tests__/registry-readiness.test.js +233 -0
  16. package/__tests__/social-douyin-im-direct-read.test.js +311 -0
  17. package/__tests__/social-douyin-snapshot.test.js +5 -2
  18. package/__tests__/vault.test.js +99 -0
  19. package/lib/adapter-guide.js +520 -0
  20. package/lib/adapter-readiness.js +257 -0
  21. package/lib/adapters/_local-im-db-reader.js +218 -0
  22. package/lib/adapters/_local-im-pc-adapter.js +162 -0
  23. package/lib/adapters/apple-health/index.js +329 -0
  24. package/lib/adapters/dingtalk-pc/index.js +29 -0
  25. package/lib/adapters/edu-huawei-learning/api-client.js +47 -0
  26. package/lib/adapters/edu-huawei-learning/index.js +255 -0
  27. package/lib/adapters/edu-zuoyebang/api-client.js +48 -0
  28. package/lib/adapters/edu-zuoyebang/index.js +259 -0
  29. package/lib/adapters/email-imap/email-adapter.js +16 -0
  30. package/lib/adapters/email-imap/templates/bill.js +174 -18
  31. package/lib/adapters/feishu-pc/index.js +29 -0
  32. package/lib/adapters/finance-alipay/api-client.js +48 -0
  33. package/lib/adapters/finance-alipay/index.js +257 -0
  34. package/lib/adapters/game-genshin/api-client.js +59 -0
  35. package/lib/adapters/game-genshin/index.js +274 -0
  36. package/lib/adapters/game-honor-of-kings/api-client.js +54 -0
  37. package/lib/adapters/game-honor-of-kings/index.js +259 -0
  38. package/lib/adapters/netease-music/index.js +227 -0
  39. package/lib/adapters/qq-pc/index.js +200 -0
  40. package/lib/adapters/qq-pc/nt-db-reader.js +210 -0
  41. package/lib/adapters/social-douyin/index.js +194 -1
  42. package/lib/adapters/wechat/wechat-adapter.js +7 -1
  43. package/lib/adapters/wechat-pc/index.js +335 -0
  44. package/lib/adapters/wechat-pc/pc-db-reader.js +327 -0
  45. package/lib/adapters/weread/api-client.js +128 -0
  46. package/lib/adapters/weread/index.js +337 -0
  47. package/lib/analysis.js +65 -0
  48. package/lib/index.js +39 -0
  49. package/lib/mobile-extractor/bplist.js +233 -0
  50. package/lib/mobile-extractor/ios-backup-crypto.js +315 -0
  51. package/lib/mobile-extractor/ios.js +131 -16
  52. package/lib/prompt-builder.js +11 -1
  53. package/lib/registry.js +170 -0
  54. package/lib/vault.js +105 -0
  55. package/package.json +1 -1
  56. package/scripts/run-native-tests-sandbox.sh +2 -0
  57. package/vitest.config.js +79 -1
@@ -0,0 +1,520 @@
1
+ /**
2
+ * Adapter import guides — step-by-step "如何把这个平台/App 的数据导入个人 AI
3
+ * 中台" instructions, surfaced in the UI next to each data source.
4
+ *
5
+ * Why this exists: readiness() tells the user WHETHER a source can collect
6
+ * and a one-line reason. But "needs_setup / 需手机采集" isn't actionable on
7
+ * its own — the user needs the concrete steps (装 App → 进采集页 → ...,
8
+ * or root + ADB pull DB, or 填 IMAP 授权码). This module is the single
9
+ * source of truth for those steps, reused by web-shell / desktop / CLI /
10
+ * Android so guidance never drifts per shell.
11
+ *
12
+ * Structure: most sources collect via one of a few shared MECHANISMS keyed
13
+ * by category (local / snapshot / device / credential). So the guide is
14
+ * category-driven, with per-adapter display names + optional overrides for
15
+ * sources that have a bespoke flow (email IMAP, Alipay bill, WeChat).
16
+ *
17
+ * A guide = {
18
+ * displayName, // 中文平台名
19
+ * category, // local | snapshot | device | credential | platform
20
+ * summary, // one-line "what this is"
21
+ * methods: [ // one or more ways to import; first = recommended
22
+ * { label, recommended?, steps: string[], note?: string }
23
+ * ],
24
+ * }
25
+ */
26
+
27
+ "use strict";
28
+
29
+ const { READINESS_CATEGORY } = require("./adapter-readiness");
30
+
31
+ // adapter name → 中文显示名. Keeps UI labels consistent across shells.
32
+ const DISPLAY_NAMES = Object.freeze({
33
+ "social-bilibili": "哔哩哔哩",
34
+ "social-weibo": "微博",
35
+ "social-douyin": "抖音",
36
+ "social-xiaohongshu": "小红书",
37
+ "social-toutiao": "今日头条",
38
+ "social-kuaishou": "快手",
39
+ "messaging-qq": "QQ(手机)",
40
+ "messaging-telegram": "Telegram",
41
+ "messaging-whatsapp": "WhatsApp",
42
+ "wechat": "微信(手机)",
43
+ "wechat-pc": "微信(电脑版)",
44
+ "qq-pc": "QQ(电脑版 NT)",
45
+ "dingtalk-pc": "钉钉(电脑版)",
46
+ "feishu-pc": "飞书(电脑版)",
47
+ "email-imap": "邮箱(IMAP)",
48
+ "finance-alipay": "支付宝",
49
+ "alipay-bill": "支付宝账单",
50
+ "shopping-taobao": "淘宝",
51
+ "shopping-jd": "京东",
52
+ "shopping-meituan": "美团",
53
+ "shopping-pinduoduo": "拼多多",
54
+ "travel-12306": "12306 铁路",
55
+ "travel-ctrip": "携程",
56
+ "travel-amap": "高德地图",
57
+ "travel-baidu-map": "百度地图",
58
+ "travel-tencent-map": "腾讯地图",
59
+ "game-genshin": "原神",
60
+ "game-honor-of-kings": "王者荣耀",
61
+ "edu-zuoyebang": "作业帮",
62
+ "edu-huawei-learning": "华为教育中心",
63
+ "ai-chat-history": "AI 对话历史",
64
+ "apple-health": "Apple 健康",
65
+ "netease-music": "网易云音乐",
66
+ "weread": "微信读书",
67
+ "browser-history-chrome": "Chrome 浏览历史",
68
+ "browser-history-edge": "Edge 浏览历史",
69
+ "vscode": "VS Code",
70
+ "win-recent": "Windows 最近使用",
71
+ "git-activity": "Git 提交记录",
72
+ "shell-history": "命令行历史",
73
+ "local-files": "本地文件",
74
+ "system-data-android": "Android 系统数据",
75
+ });
76
+
77
+ // Shared guide for honest best-effort desktop IM local-DB sources (钉钉/飞书).
78
+ function localImPcGuide(platform) {
79
+ const adapterName = platform === "钉钉" ? "dingtalk-pc" : "feishu-pc";
80
+ return {
81
+ summary: `采集${platform}电脑版的聊天记录(来自本地数据库)。⚠️ v0.1 实验性:${platform}桌面库为私有结构、可能加密、随版本变化,文本解析为尽力而为,原始行会完整保留以便后续解析。`,
82
+ methods: [
83
+ {
84
+ label: "解密本地库后直读(推荐)",
85
+ recommended: true,
86
+ steps: [
87
+ `登录${platform}电脑版,定位其数据目录下的本地 SQLite 库。`,
88
+ "若加密,用工具解密为明文(或采集时附带 --key)。",
89
+ `执行 cc hub sync-adapter ${adapterName} --input <本地库路径>,或界面「📂 选择文件采集」。`,
90
+ "中台自动发现消息表并入库;诊断会显示找到了哪些表/列。",
91
+ ],
92
+ note: "纯个人使用、全程本地。聊天记录敏感,首次会要求法律确认。文本若未解析出,原始行已保留,可后续在真机上微调列。",
93
+ },
94
+ ],
95
+ };
96
+ }
97
+
98
+ function displayName(name) {
99
+ return DISPLAY_NAMES[name] || name;
100
+ }
101
+
102
+ // ── category-level mechanism guides (the common path) ────────────────────
103
+
104
+ const CATEGORY_GUIDES = Object.freeze({
105
+ [READINESS_CATEGORY.LOCAL]: {
106
+ summary: "数据就在这台电脑上,中台直接读取,无需登录或联网。",
107
+ methods: [
108
+ {
109
+ label: "本机直接采集",
110
+ recommended: true,
111
+ steps: [
112
+ "确认对应程序/数据在本机存在(如浏览器已安装、代码仓库在本地)。",
113
+ "点这一行的「同步」按钮即可入库。",
114
+ "若状态是「待配置」,按提示在设置里指定数据目录后再同步。",
115
+ ],
116
+ },
117
+ ],
118
+ },
119
+
120
+ [READINESS_CATEGORY.SNAPSHOT]: {
121
+ summary:
122
+ "该平台数据在手机 App 里。中台不直接抓取网页,而是在手机 App 内用你已登录的会话采集,再回传到中台——绕开平台风控,最稳。",
123
+ methods: [
124
+ {
125
+ label: "方式一:手机 App 内采集(推荐)",
126
+ recommended: true,
127
+ steps: [
128
+ "在手机上安装并打开 ChainlessChain App。",
129
+ "进入「个人数据中心 → 数据源」,找到对应平台,点「采集」。",
130
+ "按提示在内置浏览器里登录该平台(仅本机保存登录态)。",
131
+ "App 采集完成后会生成快照并自动同步到中台 vault。",
132
+ ],
133
+ note: "登录态只存在你自己的设备上,采集动作 100% 本地。",
134
+ },
135
+ {
136
+ label: "方式二:电脑通过 USB 连手机自动拉取",
137
+ steps: [
138
+ "手机开启「开发者选项 → USB 调试」,用数据线连接电脑。",
139
+ "确保电脑已安装 adb(命令行 `adb devices` 能看到设备)。",
140
+ "在手机 App 内先完成一次该平台采集(生成快照)。",
141
+ "回到中台点这一行「同步」,会自动通过 ADB 拉取手机上的快照入库。",
142
+ ],
143
+ },
144
+ ],
145
+ },
146
+
147
+ [READINESS_CATEGORY.DEVICE]: {
148
+ summary:
149
+ "该数据存在 App 的本地数据库里(加密或受保护)。需要 root 的手机或在电脑上做本地解密,把数据库导出后再导入——这是最可靠、不依赖网络接口的方式。",
150
+ methods: [
151
+ {
152
+ label: "方式一:root 手机 + ADB 拉取数据库(推荐)",
153
+ recommended: true,
154
+ steps: [
155
+ "手机已 root,开启 USB 调试并用数据线连接电脑。",
156
+ "通过 ADB 从 `/data/data/<App 包名>/databases/` 拉取目标数据库。",
157
+ "若数据库加密,按该平台说明取出解密密钥(如微信用 frida 提取)。",
158
+ "在中台点「同步」并指定数据库路径(或用对应的一键采集按钮)。",
159
+ ],
160
+ note: "纯个人使用、数据全程本地,不上传任何服务器。",
161
+ },
162
+ {
163
+ label: "方式二:电脑客户端本地数据库解密",
164
+ steps: [
165
+ "在电脑上登录该平台的桌面客户端(如微信 PC 版)。",
166
+ "定位客户端的本地数据库文件。",
167
+ "用本地密钥解密后,在中台「同步」时指定该数据库路径。",
168
+ ],
169
+ },
170
+ ],
171
+ },
172
+
173
+ [READINESS_CATEGORY.CREDENTIAL]: {
174
+ summary: "该数据源需要你提供账号或登录态后才能采集。",
175
+ methods: [
176
+ {
177
+ label: "添加账号后采集",
178
+ recommended: true,
179
+ steps: [
180
+ "在「数据源」上方点对应的「添加账号」按钮。",
181
+ "按表单填入账号信息 / 完成登录授权。",
182
+ "保存后回到列表点「同步」即可入库。",
183
+ ],
184
+ },
185
+ ],
186
+ },
187
+
188
+ [READINESS_CATEGORY.PLATFORM]: {
189
+ summary: "当前操作系统或运行环境不支持该数据源。",
190
+ methods: [
191
+ {
192
+ label: "无法在此设备采集",
193
+ steps: [
194
+ "该数据源仅在特定平台可用(如部分功能仅 Windows)。",
195
+ "请在受支持的设备上打开中台后再采集。",
196
+ ],
197
+ },
198
+ ],
199
+ },
200
+ });
201
+
202
+ // ── per-adapter overrides (bespoke flows) ────────────────────────────────
203
+
204
+ const ADAPTER_OVERRIDES = Object.freeze({
205
+ "email-imap": {
206
+ summary:
207
+ "通过 IMAP 协议拉取邮件(账单、订单、行程、注册信息等会自动分类提取)。",
208
+ methods: [
209
+ {
210
+ label: "添加 IMAP 邮箱账号(推荐)",
211
+ recommended: true,
212
+ steps: [
213
+ "在邮箱网页端开启 IMAP 服务,并生成一个「授权码 / 应用专用密码」。",
214
+ "点上方「添加邮箱账号」,填入邮箱地址 + 授权码(不是登录密码)。",
215
+ "可先点「测试」验证连通,保存后点「同步」拉取邮件。",
216
+ ],
217
+ note: "常见邮箱(QQ/163/Gmail 等)已内置服务器配置,只需授权码。",
218
+ },
219
+ {
220
+ label: "手机 App 内采集邮件快照",
221
+ steps: [
222
+ "在手机 App 内完成邮箱采集,生成快照后同步到中台。",
223
+ ],
224
+ },
225
+ ],
226
+ },
227
+
228
+ "alipay-bill": {
229
+ summary: "导入从支付宝导出的账单文件(含交易明细、对手方、金额)。",
230
+ methods: [
231
+ {
232
+ label: "导入支付宝账单文件(推荐)",
233
+ recommended: true,
234
+ steps: [
235
+ "支付宝 App →「我的 → 账单 → 右上角 ... → 开具交易流水证明 / 申请账单」。",
236
+ "选择用于「个人对账」,邮箱会收到带密码的 ZIP(CSV)账单。",
237
+ "点上方「导入支付宝账单」,选择 ZIP/CSV 文件并填入解压密码。",
238
+ ],
239
+ },
240
+ ],
241
+ },
242
+
243
+ "finance-alipay": {
244
+ summary: "导入从支付宝导出的账单文件(含交易明细、对手方、金额)。",
245
+ methods: [
246
+ {
247
+ label: "导入支付宝账单文件(推荐)",
248
+ recommended: true,
249
+ steps: [
250
+ "支付宝 App →「我的 → 账单」申请交易流水(用途选个人对账)。",
251
+ "从邮箱下载带密码的账单 ZIP/CSV。",
252
+ "点上方「导入支付宝账单」,选择文件并填入解压密码。",
253
+ ],
254
+ },
255
+ ],
256
+ },
257
+
258
+ "wechat": {
259
+ summary:
260
+ "采集微信聊天记录 / 联系人 / 群(来自本地加密数据库 EnMicroMsg.db)。需要 root 手机或电脑本地解密。",
261
+ methods: [
262
+ {
263
+ label: "方式一:root 手机 + frida 提取密钥(推荐)",
264
+ recommended: true,
265
+ steps: [
266
+ "手机已 root,安装并运行 frida-server,开启 USB 调试连电脑。",
267
+ "点上方「添加 WeChat」,按向导探测环境并提取数据库密钥。",
268
+ "提取成功后点「同步」解密并入库。",
269
+ ],
270
+ note: "纯个人使用、全程本地。聊天记录敏感,首次会要求法律确认。",
271
+ },
272
+ {
273
+ label: "方式二:电脑微信本地数据库解密",
274
+ steps: [
275
+ "电脑登录微信 PC 版,定位本地聊天数据库。",
276
+ "用本地密钥解密后,在「添加 WeChat」向导里指定数据库路径。",
277
+ ],
278
+ },
279
+ ],
280
+ },
281
+
282
+ "ai-chat-history": {
283
+ summary: "采集你在各 AI 助手(DeepSeek/Kimi/豆包/通义等)里的对话历史。",
284
+ methods: [
285
+ {
286
+ label: "WebView 登录向导(推荐)",
287
+ recommended: true,
288
+ steps: [
289
+ "点上方「添加 AI 对话账号」,选择服务商。",
290
+ "在弹出的内置浏览器里登录该 AI 服务(登录态仅本机保存)。",
291
+ "向导抓取所需 Cookie 后注册账号,回列表点「同步」拉取对话。",
292
+ ],
293
+ },
294
+ ],
295
+ },
296
+
297
+ "messaging-qq": {
298
+ summary:
299
+ "采集手机 QQ 的聊天记录 + 联系人 + 群(来自 App 本地数据库 <uin>.db)。库本身是明文 SQLite,消息正文按设备 IMEI 做 XOR 加密——root 拉库 + 提供 IMEI 即可本地直读。",
300
+ methods: [
301
+ {
302
+ label: "方式一:root 手机拉 <uin>.db + IMEI 本地直读(推荐)",
303
+ recommended: true,
304
+ steps: [
305
+ "root 手机开启 USB 调试连电脑。",
306
+ "拉取 `/data/data/com.tencent.mobileqq/databases/<uin>.db`(uin 为你的 QQ 号)。",
307
+ "在数据源设置中登记 QQ 账号(uin) + 设备 IMEI(用于 XOR 解密消息正文)。",
308
+ "点「同步」直接读取联系人 / 群 / 消息入库。",
309
+ ],
310
+ note: "DB 明文、仅消息正文 XOR;纯个人使用、全程本地。",
311
+ },
312
+ {
313
+ label: "方式二:手机 App 内 root 采集",
314
+ steps: ["在已 root 的手机 App 内触发 QQ 采集,生成快照同步到中台。"],
315
+ },
316
+ ],
317
+ },
318
+
319
+ "qq-pc": {
320
+ summary:
321
+ "采集电脑版 QQ(NT 新版)的聊天记录(来自本地 nt_msg.db)。⚠️ v0.1 实验性:QQ NT 用数字化列名 + protobuf 消息体且 schema 随版本变化,文本解析为尽力而为,原始行会完整保留以便后续解析。",
322
+ methods: [
323
+ {
324
+ label: "方式一:解密 nt_msg.db 后本地直读(推荐)",
325
+ recommended: true,
326
+ steps: [
327
+ "登录电脑版 QQ,定位数据目录(默认 文档\\Tencent Files 或 %APPDATA%\\Tencent\\QQ\\nt_qq_*\\nt_db\\)。",
328
+ "用工具(如 QQNTDecrypt / PyQQ 等)解密 nt_msg.db 为明文 SQLite。",
329
+ "执行 `cc hub sync-adapter qq-pc --input <解密后的 nt_msg.db>`。",
330
+ "中台读取 c2c_msg_table / group_msg_table 入库(文本尽力解析,原始数据保留)。",
331
+ ],
332
+ note: "QQ NT 消息正文为 protobuf BLOB,部分类型文本可能需后续在真机上微调列/解码。诊断会显示找到了哪些表/列。",
333
+ },
334
+ {
335
+ label: "方式二:提供密钥让中台尝试直接解密(试验性)",
336
+ steps: ["附带 `--key <hex>`,中台用 SQLCipher 尝试打开;失败则回退方式一。"],
337
+ },
338
+ ],
339
+ },
340
+
341
+ "wechat-pc": {
342
+ summary:
343
+ "采集电脑版微信的聊天记录 + 联系人(来自本地 MSG*.db 与 MicroMsg.db)。数据库经 SQLCipher 加密,需先解密成明文或提供 32 字节密钥再本地直读。",
344
+ methods: [
345
+ {
346
+ label: "方式一:先解密成明文再直读(推荐,最可靠)",
347
+ recommended: true,
348
+ steps: [
349
+ "在电脑登录微信 PC 版,定位数据目录(默认 文档\\WeChat Files\\<wxid>\\Msg\\)。",
350
+ "用工具(如 PyWxDump)从运行中的微信进程提取 32 字节密钥并解密 MSG0.db / MicroMsg.db 为明文 SQLite。",
351
+ "执行 `cc hub sync-adapter wechat-pc --input <解密后的 MSG0.db>` 采集聊天记录;再对 MicroMsg.db 跑一次采集联系人。",
352
+ "中台直接读取消息 + 联系人入库(明文 SQLite,无需再解密)。",
353
+ ],
354
+ note: "纯个人使用、全程本地。聊天记录敏感,首次会要求法律确认。多个 MSG*.db 逐个采集即可累积。",
355
+ },
356
+ {
357
+ label: "方式二:提供密钥让中台直接解密(试验性)",
358
+ steps: [
359
+ "提取到 64 位十六进制密钥后,采集时附带 `--key <64位hex>`。",
360
+ "中台用 SQLCipher 配置尝试直接打开加密库;部分微信版本可直接读,失败则回退方式一。",
361
+ ],
362
+ },
363
+ ],
364
+ },
365
+
366
+ "dingtalk-pc": localImPcGuide("钉钉"),
367
+ "feishu-pc": localImPcGuide("飞书"),
368
+
369
+ "social-douyin": {
370
+ summary:
371
+ "采集抖音私信 + 联系人(来自 App 本地明文数据库 <uid>_im.db)。明文 SQLite、无加密、无 X-Bogus 签名——本地直读是最可靠的方式。",
372
+ methods: [
373
+ {
374
+ label: "方式一:本地直读 <uid>_im.db(推荐,最可靠)",
375
+ recommended: true,
376
+ steps: [
377
+ "root 手机开启 USB 调试连电脑(adb 可见)。",
378
+ "从 `/data/data/com.ss.android.ugc.aweme/databases/` 拉取 `<uid>_im.db`(uid 为 19 位数字)。",
379
+ "执行 `cc hub sync-adapter social-douyin --input <本地 im.db 路径>`,或在界面「同步」时选择该文件。",
380
+ "中台自动识别 SQLite 并直接读取私信 + 联系人入库(无需快照、无需联网)。",
381
+ ],
382
+ note: "im.db 明文存储(多篇 DFIR 取证已证实),不需要 frida。纯个人使用、全程本地。",
383
+ },
384
+ {
385
+ label: "方式二:电脑通过 USB 一键 ADB 采集",
386
+ steps: [
387
+ "root 手机连电脑,确保 adb 可见。",
388
+ "点界面「通过 PC ADB 同步 Douyin」,自动拉取 im.db 并入库。",
389
+ ],
390
+ },
391
+ {
392
+ label: "方式三:手机 App 内 root 采集",
393
+ steps: [
394
+ "在已 root 的手机 App 内授予权限并触发抖音采集,生成快照同步到中台。",
395
+ ],
396
+ },
397
+ ],
398
+ },
399
+
400
+ "weread": {
401
+ summary:
402
+ "采集微信读书的书架 / 划线 / 想法,构建你的阅读画像。走网页版 cookie——登录一次抓取登录态即可,无需 root。",
403
+ methods: [
404
+ {
405
+ label: "登录抓取 cookie 后一键采集(推荐)",
406
+ recommended: true,
407
+ steps: [
408
+ "电脑浏览器登录 weread.qq.com(微信扫码)。",
409
+ "在中台点这一行采集,按提示粘贴登录态 cookie(或用内置登录窗口抓取)。",
410
+ "中台自动拉取你有笔记的书 + 划线 + 想法入库。",
411
+ ],
412
+ note: "cookie 仅本地保存;wr_skey 会过期,过期后重新登录抓取即可。纯个人使用。",
413
+ },
414
+ {
415
+ label: "已有快照文件则直接选择采集",
416
+ steps: ["点「📂 选择文件采集」选中微信读书快照 JSON 即可入库。"],
417
+ },
418
+ ],
419
+ },
420
+
421
+ "apple-health": {
422
+ summary:
423
+ "导入 iPhone「健康」App 导出的数据(步数 / 心率 / 睡眠 / 体重 / 运动等)。这是最省事的健康数据来源——自己导出,无需越狱或连接,文件直读。",
424
+ methods: [
425
+ {
426
+ label: "导出健康数据后一键选择文件采集(推荐)",
427
+ recommended: true,
428
+ steps: [
429
+ "iPhone 打开「健康」App → 右上角头像 → 最下方「导出所有健康数据」。",
430
+ "会生成一个 zip,解压得到 export.xml(可发到电脑)。",
431
+ "在中台点这一行的「📂 选择文件采集」,选中 export.xml 即可自动入库。",
432
+ ],
433
+ note: "完全本地、无需越狱。文件较大时首次导入稍慢,会自动分批。",
434
+ },
435
+ ],
436
+ },
437
+
438
+ "netease-music": {
439
+ summary: "采集网易云音乐的听歌记录 / 收藏 / 歌单,构建你的音乐口味画像。",
440
+ methods: [
441
+ {
442
+ label: "手机 App 内采集(推荐)",
443
+ recommended: true,
444
+ steps: [
445
+ "在手机 App「个人数据中心」里打开网易云音乐采集页。",
446
+ "登录后采集听歌记录 / 歌单,生成快照自动同步到中台。",
447
+ ],
448
+ },
449
+ {
450
+ label: "已有快照文件则直接选择采集",
451
+ steps: ["点「📂 选择文件采集」选中网易云快照 JSON 即可入库。"],
452
+ },
453
+ ],
454
+ },
455
+
456
+ "system-data-android": {
457
+ summary: "采集 Android 通讯录、已装应用列表、短信、通话记录等系统数据。",
458
+ methods: [
459
+ {
460
+ label: "手机 App 内采集(推荐)",
461
+ recommended: true,
462
+ steps: [
463
+ "在手机 App「个人数据中心」里授予所需权限。",
464
+ "点「采集系统数据」生成快照并同步到中台。",
465
+ ],
466
+ },
467
+ {
468
+ label: "电脑通过 USB 实时拉取",
469
+ steps: [
470
+ "手机开启 USB 调试连电脑(adb 可见)。",
471
+ "中台点「同步」,自动通过 ADB 实时读取通讯录 + 应用列表。",
472
+ ],
473
+ },
474
+ ],
475
+ },
476
+ });
477
+
478
+ /**
479
+ * Get the import guide for one adapter.
480
+ *
481
+ * @param {string} name adapter name (e.g. "social-bilibili")
482
+ * @param {string} category readiness category (local/snapshot/device/...)
483
+ * @returns {{displayName, category, summary, methods}}
484
+ */
485
+ function getAdapterGuide(name, category) {
486
+ const override = ADAPTER_OVERRIDES[name];
487
+ const cat = category || _inferCategory(name);
488
+ const base = CATEGORY_GUIDES[cat] || CATEGORY_GUIDES[READINESS_CATEGORY.LOCAL];
489
+ return {
490
+ displayName: displayName(name),
491
+ category: cat,
492
+ summary: (override && override.summary) || base.summary,
493
+ methods: (override && override.methods) || base.methods,
494
+ };
495
+ }
496
+
497
+ // Fallback category inference when caller doesn't pass one (keeps the guide
498
+ // usable standalone, e.g. CLI without a live readiness probe).
499
+ function _inferCategory(name) {
500
+ if (ADAPTER_OVERRIDES[name] && name === "wechat") return READINESS_CATEGORY.DEVICE;
501
+ if (/^(email-imap|finance-alipay|alipay-bill|ai-chat-history|weread)$/.test(name))
502
+ return READINESS_CATEGORY.CREDENTIAL;
503
+ if (/^(messaging-(telegram|whatsapp)|wechat|wechat-pc|messaging-qq|qq-pc|dingtalk-pc|feishu-pc|travel-amap)$/.test(name))
504
+ return READINESS_CATEGORY.DEVICE;
505
+ if (
506
+ /^(browser-history-|vscode|win-recent|git-activity|shell-history|local-files|apple-health)/.test(
507
+ name,
508
+ )
509
+ )
510
+ return READINESS_CATEGORY.LOCAL;
511
+ return READINESS_CATEGORY.SNAPSHOT;
512
+ }
513
+
514
+ module.exports = {
515
+ DISPLAY_NAMES,
516
+ displayName,
517
+ CATEGORY_GUIDES,
518
+ ADAPTER_OVERRIDES,
519
+ getAdapterGuide,
520
+ };