@web-auto/webauto 0.1.4 → 0.1.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 (174) hide show
  1. package/apps/desktop-console/default-settings.json +2 -2
  2. package/apps/desktop-console/dist/main/index.mjs +983 -128
  3. package/apps/desktop-console/dist/main/preload.mjs +7 -0
  4. package/apps/desktop-console/dist/renderer/index.html +622 -50
  5. package/apps/desktop-console/dist/renderer/index.js +2423 -469
  6. package/apps/desktop-console/dist/renderer/run.mts +6 -5
  7. package/apps/desktop-console/entry/ui-cli.mjs +672 -0
  8. package/apps/desktop-console/entry/ui-console.mjs +416 -29
  9. package/apps/webauto/entry/account.mjs +89 -53
  10. package/apps/webauto/entry/browser-status.mjs +7 -10
  11. package/apps/webauto/entry/lib/account-detect.mjs +254 -28
  12. package/apps/webauto/entry/lib/account-store.mjs +219 -30
  13. package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
  14. package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
  15. package/apps/webauto/entry/lib/profilepool.mjs +14 -5
  16. package/apps/webauto/entry/lib/quota-status.mjs +23 -0
  17. package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
  18. package/apps/webauto/entry/profilepool.mjs +106 -17
  19. package/apps/webauto/entry/schedule.mjs +612 -0
  20. package/apps/webauto/entry/weibo-unified.mjs +134 -0
  21. package/apps/webauto/entry/xhs-install.mjs +256 -31
  22. package/apps/webauto/entry/xhs-status.mjs +5 -2
  23. package/apps/webauto/entry/xhs-unified.mjs +631 -98
  24. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
  25. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
  26. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
  27. package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
  28. package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
  29. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
  30. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
  31. package/bin/camoufox-cli.mjs +61 -0
  32. package/bin/webauto.mjs +301 -54
  33. package/dist/modules/camo-backend/src/index.js +49 -1
  34. package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
  35. package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
  36. package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
  37. package/dist/modules/collection-manager/bloom-filter.js +91 -0
  38. package/dist/modules/collection-manager/date-utils.js +275 -0
  39. package/dist/modules/collection-manager/index.js +258 -0
  40. package/dist/modules/collection-manager/storage.js +195 -0
  41. package/dist/modules/collection-manager/types.js +47 -0
  42. package/dist/modules/logging/src/index.js +1 -1
  43. package/dist/modules/process-registry/index.js +230 -0
  44. package/dist/modules/rate-limiter/index.js +242 -0
  45. package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
  46. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
  47. package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
  48. package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
  49. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
  50. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
  51. package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
  52. package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
  53. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
  54. package/dist/modules/workflow/config/workflowRegistry.js +2 -0
  55. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
  56. package/dist/modules/workflow/src/runner.js +6 -0
  57. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
  58. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
  59. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
  60. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
  61. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
  62. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
  63. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
  64. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
  65. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
  66. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
  67. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  68. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
  69. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  70. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
  71. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
  72. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
  73. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
  74. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
  75. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
  76. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
  77. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  78. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
  79. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  80. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
  81. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  82. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
  83. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  84. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
  85. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  86. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
  87. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  88. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
  89. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  90. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
  91. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  92. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
  93. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  94. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
  95. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  96. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
  97. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  98. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
  99. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  100. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
  101. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  102. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
  103. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  104. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
  105. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  106. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
  107. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  108. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
  109. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  110. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
  111. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  112. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
  113. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  114. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
  115. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  116. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
  117. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  118. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
  119. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  120. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
  121. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
  122. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
  123. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  124. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
  125. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
  126. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
  127. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  128. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
  129. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  130. package/dist/services/shared/serviceProcessLogger.js +1 -1
  131. package/dist/services/unified-api/server.js +105 -11
  132. package/modules/camo-backend/src/index.ts +46 -1
  133. package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
  134. package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
  135. package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
  136. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
  137. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
  138. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
  139. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
  140. package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
  141. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
  142. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
  143. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
  144. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
  145. package/modules/collection-manager/bloom-filter.ts +112 -0
  146. package/modules/collection-manager/date-utils.ts +316 -0
  147. package/modules/collection-manager/index.ts +309 -0
  148. package/modules/collection-manager/package.json +10 -0
  149. package/modules/collection-manager/storage.ts +174 -0
  150. package/modules/collection-manager/types.ts +156 -0
  151. package/modules/logging/src/index.ts +1 -1
  152. package/modules/process-registry/index.ts +284 -0
  153. package/modules/rate-limiter/index.ts +322 -0
  154. package/modules/state/src/paths.ts +9 -1
  155. package/modules/task-scheduler/index.ts +293 -0
  156. package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
  157. package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
  158. package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
  159. package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
  160. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
  161. package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
  162. package/modules/workflow/config/workflowRegistry.ts +2 -0
  163. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
  164. package/modules/workflow/src/runner.ts +6 -0
  165. package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
  166. package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
  167. package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
  168. package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
  169. package/package.json +13 -4
  170. package/scripts/postinstall-resources.mjs +62 -0
  171. package/scripts/test/run-coverage.mjs +76 -0
  172. package/scripts/weibo/search.ts +49 -0
  173. package/services/shared/serviceProcessLogger.ts +1 -1
  174. package/services/unified-api/server.ts +98 -12
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import minimist from 'minimist';
3
3
  import path from 'node:path';
4
- import { existsSync } from 'node:fs';
5
- import { spawnSync } from 'node:child_process';
6
4
  import { fileURLToPath } from 'node:url';
7
5
  import {
8
6
  addAccount,
@@ -14,6 +12,8 @@ import {
14
12
  } from './lib/account-store.mjs';
15
13
  import { ensureProfile } from './lib/profilepool.mjs';
16
14
  import { syncXhsAccountByProfile, syncXhsAccountsByProfiles } from './lib/account-detect.mjs';
15
+ import { publishBusEvent } from './lib/bus-publish.mjs';
16
+ import { runCamo } from './lib/camo-cli.mjs';
17
17
 
18
18
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
19
19
  const XHS_HOME_URL = 'https://www.xiaohongshu.com';
@@ -35,42 +35,6 @@ function parseBoolean(value, fallback = false) {
35
35
  return fallback;
36
36
  }
37
37
 
38
- function getCamoRunner() {
39
- const local = path.join(ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'camo.cmd' : 'camo');
40
- if (existsSync(local)) return { cmd: local, prefix: [] };
41
- return { cmd: process.platform === 'win32' ? 'npx.cmd' : 'npx', prefix: ['@web-auto/camo'] };
42
- }
43
-
44
- function runCamo(args) {
45
- const runner = getCamoRunner();
46
- const ret = spawnSync(runner.cmd, [...runner.prefix, ...args], {
47
- cwd: ROOT,
48
- env: process.env,
49
- encoding: 'utf8',
50
- });
51
- const stdout = String(ret.stdout || '').trim();
52
- const stderr = String(ret.stderr || '').trim();
53
- let parsed = null;
54
- if (stdout) {
55
- const lines = stdout.split('\n').map((line) => line.trim()).filter(Boolean).reverse();
56
- for (const line of lines) {
57
- try {
58
- parsed = JSON.parse(line);
59
- break;
60
- } catch {
61
- continue;
62
- }
63
- }
64
- }
65
- return {
66
- ok: ret.status === 0,
67
- code: ret.status,
68
- stdout,
69
- stderr,
70
- json: parsed,
71
- };
72
- }
73
-
74
38
  function inferLoginUrl(platform) {
75
39
  const value = String(platform || '').trim().toLowerCase();
76
40
  if (!value || value === 'xiaohongshu' || value === 'xhs') return XHS_HOME_URL;
@@ -82,6 +46,18 @@ function normalizeAlias(input) {
82
46
  return value || null;
83
47
  }
84
48
 
49
+ async function publishAccountEvent(type, payload) {
50
+ try {
51
+ await publishBusEvent({
52
+ type,
53
+ payload: payload || null,
54
+ ts: new Date().toISOString(),
55
+ });
56
+ } catch {
57
+ // ignore bus errors
58
+ }
59
+ }
60
+
85
61
  async function detectAliasFromActivePage(profileId, selector) {
86
62
  const { callAPI } = await import('../../../modules/camo-runtime/src/utils/browser-service.mjs');
87
63
  const script = `(() => {
@@ -140,13 +116,13 @@ Usage:
140
116
  webauto account --help
141
117
  webauto account list [--json]
142
118
  webauto account list --records [--json]
143
- webauto account add [--platform <name>] [--alias <alias>] [--name <name>] [--username <username>] [--profile <id>] [--fingerprint <id>] [--json]
119
+ webauto account add [--platform <name>] [--alias <alias>] [--name <name>] [--username <username>] [--profile <id>] [--fingerprint <id>] [--status pending|active|disabled|archived] [--json]
144
120
  webauto account get <id|alias> [--json]
145
- webauto account update <id|alias> [--alias <alias>|--clear-alias] [--name <name>] [--username <name>] [--profile <id>] [--fingerprint <id>] [--status active|disabled|archived] [--json]
121
+ webauto account update <id|alias> [--alias <alias>|--clear-alias] [--name <name>] [--username <name>] [--profile <id>] [--fingerprint <id>] [--status pending|active|disabled|archived] [--json]
146
122
  webauto account delete <id|alias> [--delete-profile] [--delete-fingerprint] [--json]
147
- webauto account login <id|alias> [--url <url>] [--sync-alias] [--json]
123
+ webauto account login <id|alias> [--url <url>] [--idle-timeout <duration>] [--sync-alias] [--json]
148
124
  webauto account sync-alias <id|alias> [--selector <css>] [--alias <value>] [--json]
149
- webauto account sync <profileId|all> [--json]
125
+ webauto account sync <profileId|all> [--pending-while-login] [--resolve-alias] [--json]
150
126
 
151
127
  Notes:
152
128
  - 账号数据默认保存到 ~/.webauto/accounts(可用 WEBAUTO_PATHS_ACCOUNTS 覆盖)
@@ -154,12 +130,13 @@ Notes:
154
130
  - add 会自动创建并关联 profile/fingerprint(未指定时自动编号)
155
131
  - login 会通过 @web-auto/camo 拉起浏览器并绑定账号 profile
156
132
  - 只有识别到账号 id 的 profile 才会进入 valid 状态
133
+ - sync --pending-while-login 会在登录过程中保持待登录状态,避免过早标记失效
157
134
 
158
135
  Examples:
159
136
  webauto account add --platform xiaohongshu --alias 主号
160
137
  webauto account list
161
138
  webauto account sync all
162
- webauto account login xhs-0001 --url https://www.xiaohongshu.com
139
+ webauto account login xhs-0001 --url https://www.xiaohongshu.com --idle-timeout 30m
163
140
  webauto account sync-alias xhs-0001
164
141
  webauto account update xhs-0001 --alias 运营1号
165
142
  webauto account delete xhs-0001 --delete-profile --delete-fingerprint
@@ -188,6 +165,13 @@ async function cmdAdd(argv, jsonMode) {
188
165
  status: argv.status,
189
166
  });
190
167
  output({ ok: true, ...result }, jsonMode);
168
+ await publishAccountEvent('account:add', {
169
+ profileId: result?.account?.profileId || null,
170
+ accountId: result?.account?.accountId || null,
171
+ alias: result?.account?.alias || null,
172
+ status: result?.account?.status || null,
173
+ valid: result?.account?.valid === true,
174
+ });
191
175
  }
192
176
 
193
177
  async function cmdGet(idOrAlias, jsonMode) {
@@ -219,6 +203,13 @@ async function cmdUpdate(idOrAlias, argv, jsonMode) {
219
203
  }
220
204
  const account = await updateAccount(idOrAlias, patch);
221
205
  output({ ok: true, account }, jsonMode);
206
+ await publishAccountEvent('account:update', {
207
+ profileId: account?.profileId || null,
208
+ accountId: account?.accountId || null,
209
+ alias: account?.alias || null,
210
+ status: account?.status || null,
211
+ valid: account?.valid === true,
212
+ });
222
213
  }
223
214
 
224
215
  async function cmdDelete(idOrAlias, argv, jsonMode) {
@@ -227,14 +218,27 @@ async function cmdDelete(idOrAlias, argv, jsonMode) {
227
218
  deleteFingerprint: argv['delete-fingerprint'] === true,
228
219
  });
229
220
  output({ ok: true, ...result }, jsonMode);
221
+ await publishAccountEvent('account:delete', {
222
+ profileId: result?.removed?.profileId || null,
223
+ accountId: result?.removed?.accountId || null,
224
+ alias: result?.removed?.alias || null,
225
+ });
230
226
  }
231
227
 
232
228
  async function cmdLogin(idOrAlias, argv, jsonMode) {
233
229
  const account = getAccount(idOrAlias);
234
230
  await ensureProfile(account.profileId);
235
231
  const url = String(argv.url || inferLoginUrl(account.platform)).trim();
232
+ const idleTimeout = String(argv['idle-timeout'] || process.env.WEBAUTO_LOGIN_IDLE_TIMEOUT || '30m').trim() || '30m';
233
+
234
+ const pendingProfile = await syncXhsAccountByProfile(account.profileId, { pendingWhileLogin: true }).catch((error) => ({
235
+ profileId: account.profileId,
236
+ valid: false,
237
+ status: 'pending',
238
+ reason: `waiting_login_sync:${error?.message || String(error)}`,
239
+ }));
236
240
 
237
- const initResult = runCamo(['init']);
241
+ const initResult = runCamo(['init'], { rootDir: ROOT });
238
242
  if (!initResult.ok) {
239
243
  output({
240
244
  ok: false,
@@ -245,7 +249,7 @@ async function cmdLogin(idOrAlias, argv, jsonMode) {
245
249
  process.exit(1);
246
250
  }
247
251
 
248
- const startResult = runCamo(['start', account.profileId, '--url', url]);
252
+ const startResult = runCamo(['start', account.profileId, '--url', url, '--idle-timeout', idleTimeout], { rootDir: ROOT });
249
253
  if (!startResult.ok) {
250
254
  output({
251
255
  ok: false,
@@ -257,6 +261,11 @@ async function cmdLogin(idOrAlias, argv, jsonMode) {
257
261
  process.exit(1);
258
262
  }
259
263
 
264
+ const cookieAuto = runCamo(['cookies', 'auto', 'start', account.profileId, '--interval', '5000'], {
265
+ rootDir: ROOT,
266
+ timeoutMs: 20000,
267
+ });
268
+
260
269
  let aliasSync = null;
261
270
  if (parseBoolean(argv['sync-alias'], false)) {
262
271
  try {
@@ -271,11 +280,11 @@ async function cmdLogin(idOrAlias, argv, jsonMode) {
271
280
  }
272
281
  }
273
282
 
274
- const accountSync = await syncXhsAccountByProfile(account.profileId).catch((error) => ({
283
+ const accountSync = await syncXhsAccountByProfile(account.profileId, { pendingWhileLogin: true }).catch((error) => ({
275
284
  profileId: account.profileId,
276
285
  valid: false,
277
- status: 'invalid',
278
- reason: error?.message || String(error),
286
+ status: 'pending',
287
+ reason: `waiting_login_sync:${error?.message || String(error)}`,
279
288
  }));
280
289
 
281
290
  output({
@@ -283,10 +292,22 @@ async function cmdLogin(idOrAlias, argv, jsonMode) {
283
292
  account,
284
293
  profileId: account.profileId,
285
294
  url,
295
+ idleTimeout,
286
296
  camo: startResult.json || startResult.stdout || null,
297
+ pendingProfile,
298
+ cookieAuto: cookieAuto.ok
299
+ ? { ok: true }
300
+ : { ok: false, code: cookieAuto.code, error: cookieAuto.stderr || cookieAuto.stdout || 'cookie auto start failed' },
287
301
  aliasSync,
288
302
  accountSync,
289
303
  }, jsonMode);
304
+ await publishAccountEvent('account:login', {
305
+ profileId: account?.profileId || null,
306
+ accountId: account?.accountId || null,
307
+ alias: account?.alias || null,
308
+ status: accountSync?.status || null,
309
+ valid: accountSync?.valid === true,
310
+ });
290
311
  }
291
312
 
292
313
  async function cmdSyncAlias(idOrAlias, argv, jsonMode) {
@@ -308,24 +329,39 @@ async function cmdSyncAlias(idOrAlias, argv, jsonMode) {
308
329
  source,
309
330
  candidates,
310
331
  }, jsonMode);
332
+ await publishAccountEvent('account:alias', {
333
+ profileId: updated?.profileId || null,
334
+ accountId: updated?.accountId || null,
335
+ alias: updated?.alias || null,
336
+ source,
337
+ });
311
338
  }
312
339
 
313
- async function cmdSync(target, jsonMode) {
340
+ async function cmdSync(target, argv, jsonMode) {
341
+ const pendingWhileLogin = parseBoolean(argv['pending-while-login'], false);
342
+ const resolveAlias = parseBoolean(argv['resolve-alias'], false);
314
343
  const value = String(target || '').trim().toLowerCase();
315
344
  if (!value || value === 'all') {
316
345
  const rows = listAccountProfiles().profiles;
317
346
  const profileIds = rows.map((item) => item.profileId);
318
- const synced = await syncXhsAccountsByProfiles(profileIds);
347
+ const synced = await syncXhsAccountsByProfiles(profileIds, { pendingWhileLogin, resolveAlias });
319
348
  output({ ok: true, count: synced.length, profiles: synced }, jsonMode);
349
+ await publishAccountEvent('account:sync', {
350
+ count: synced.length,
351
+ profiles: synced,
352
+ });
320
353
  return;
321
354
  }
322
- const synced = await syncXhsAccountByProfile(target);
355
+ const synced = await syncXhsAccountByProfile(target, { pendingWhileLogin, resolveAlias });
323
356
  output({ ok: true, profile: synced }, jsonMode);
357
+ await publishAccountEvent('account:sync', {
358
+ profile: synced,
359
+ });
324
360
  }
325
361
 
326
362
  async function main() {
327
363
  const argv = minimist(process.argv.slice(2), {
328
- boolean: ['help', 'json', 'clear-alias', 'delete-profile', 'delete-fingerprint', 'sync-alias', 'records'],
364
+ boolean: ['help', 'json', 'clear-alias', 'delete-profile', 'delete-fingerprint', 'sync-alias', 'resolve-alias', 'records'],
329
365
  alias: { h: 'help' },
330
366
  });
331
367
  const cmd = String(argv._[0] || '').trim();
@@ -344,7 +380,7 @@ async function main() {
344
380
  if (cmd === 'delete' || cmd === 'remove' || cmd === 'rm') return cmdDelete(arg1, argv, jsonMode);
345
381
  if (cmd === 'login') return cmdLogin(arg1, argv, jsonMode);
346
382
  if (cmd === 'sync-alias') return cmdSyncAlias(arg1, argv, jsonMode);
347
- if (cmd === 'sync') return cmdSync(arg1, jsonMode);
383
+ if (cmd === 'sync') return cmdSync(arg1, argv, jsonMode);
348
384
 
349
385
  throw new Error(`unknown account command: ${cmd}`);
350
386
  }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
- import { spawnSync } from 'node:child_process';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { runCamo } from './lib/camo-cli.mjs';
4
5
 
5
6
  const profileId = String(process.argv[2] || '').trim();
6
7
  if (!profileId) {
@@ -8,17 +9,13 @@ if (!profileId) {
8
9
  process.exit(1);
9
10
  }
10
11
 
11
- const cliPath = path.resolve(process.cwd(), 'bin', 'camoufox-cli.mjs');
12
- const ret = spawnSync(process.execPath, [cliPath, 'status', profileId], { encoding: 'utf8' });
13
- const stdout = String(ret.stdout || '').trim();
14
- const stderr = String(ret.stderr || '').trim();
15
- let parsed = null;
16
- try { parsed = stdout ? JSON.parse(stdout) : null; } catch {}
12
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
13
+ const ret = runCamo(['status', profileId, '--json'], { rootDir: ROOT, timeoutMs: 20000 });
17
14
 
18
- if (ret.status !== 0) {
19
- console.error(JSON.stringify({ ok: false, code: ret.status, stderr: stderr || stdout }));
15
+ if (!ret.ok) {
16
+ console.error(JSON.stringify({ ok: false, code: ret.code, stderr: ret.stderr || ret.stdout }));
20
17
  process.exit(1);
21
18
  }
22
19
 
23
- const session = parsed?.session || null;
20
+ const session = ret.json?.session || null;
24
21
  console.log(JSON.stringify({ ok: true, profileId, online: Boolean(session), session }));
@@ -1,11 +1,17 @@
1
1
  import { callAPI } from '../../../../modules/camo-runtime/src/utils/browser-service.mjs';
2
- import { markProfileInvalid, upsertProfileAccountState } from './account-store.mjs';
2
+ import { listAccountProfiles, markProfileInvalid, markProfilePending, upsertProfileAccountState } from './account-store.mjs';
3
+
4
+ const XHS_PROFILE_URL = 'https://www.xiaohongshu.com/user/profile';
3
5
 
4
6
  function normalizeText(value) {
5
7
  const text = String(value ?? '').trim();
6
8
  return text || null;
7
9
  }
8
10
 
11
+ function sleep(ms) {
12
+ return new Promise((resolve) => setTimeout(resolve, ms));
13
+ }
14
+
9
15
  function buildDetectScript() {
10
16
  return `(() => {
11
17
  const guard = Boolean(document.querySelector('.login-container, .login-dialog, #login-container'));
@@ -16,15 +22,67 @@ function buildDetectScript() {
16
22
  if (text === '我' || text === '我的' || text === '个人主页') return null;
17
23
  return text;
18
24
  };
25
+ const isSelfLabel = (value) => {
26
+ const text = String(value || '').replace(/\\s+/g, ' ').trim();
27
+ if (!text) return false;
28
+ return (
29
+ text === '我'
30
+ || text === '我的'
31
+ || text === '个人主页'
32
+ || text === '我的主页'
33
+ || text === '我的账号'
34
+ );
35
+ };
36
+ const readLabelCandidates = (node) => {
37
+ if (!node) return [];
38
+ const text = String(node.textContent || '').replace(/\\s+/g, ' ').trim();
39
+ const title = String(node.getAttribute ? (node.getAttribute('title') || '') : '').trim();
40
+ const aria = String(node.getAttribute ? (node.getAttribute('aria-label') || '') : '').trim();
41
+ return [text, title, aria].filter((item) => Boolean(item));
42
+ };
43
+ const cleanAlias = (value) => {
44
+ let text = normalizeAlias(value);
45
+ if (!text) return null;
46
+ text = text.replace(/\\s*[-–—]\\s*(小红书|XiaoHongShu|xiaohongshu).*$/i, '').trim();
47
+ if (!text) return null;
48
+ const blocked = ['小红书', '登录', '注册', '搜索'];
49
+ if (blocked.includes(text)) return null;
50
+ return text;
51
+ };
19
52
  const pushCandidate = (id, alias, source) => {
20
53
  const value = String(id || '').trim();
21
54
  if (!value) return;
22
55
  candidates.push({
23
56
  id: value,
24
- alias: normalizeAlias(alias),
57
+ alias: cleanAlias(alias),
25
58
  source: String(source || '').trim() || null,
26
59
  });
27
60
  };
61
+ const findAliasFromDom = () => {
62
+ const selectors = [
63
+ '[class*="user"] [class*="name"]',
64
+ '[class*="nickname"]',
65
+ '[class*="account"] [class*="name"]',
66
+ 'a[href*="/user/profile"] span',
67
+ 'header a[href*="/user"] span',
68
+ 'nav a[href*="/user"] span',
69
+ ];
70
+ const picks = [];
71
+ for (const sel of selectors) {
72
+ const nodes = Array.from(document.querySelectorAll(sel)).slice(0, 6);
73
+ for (const node of nodes) {
74
+ const text = cleanAlias(node.textContent || '');
75
+ if (text) picks.push({ text, source: sel });
76
+ }
77
+ }
78
+ const metaTitle = document.querySelector('meta[property="og:title"], meta[name="og:title"]');
79
+ const metaText = cleanAlias(metaTitle ? metaTitle.getAttribute('content') || '' : '');
80
+ if (metaText) picks.push({ text: metaText, source: 'meta:og:title' });
81
+ const title = cleanAlias(document.title || '');
82
+ if (title) picks.push({ text: title, source: 'document.title' });
83
+ const picked = picks.find((item) => item.text && item.text.length >= 2) || null;
84
+ return picked ? picked.text : null;
85
+ };
28
86
 
29
87
  const initialState = (typeof window !== 'undefined' && window.__INITIAL_STATE__) || null;
30
88
  const rawUserInfo = initialState && initialState.user && initialState.user.userInfo
@@ -36,7 +94,7 @@ function buildDetectScript() {
36
94
  : null;
37
95
  if (rawUserInfo) {
38
96
  const initUserId = String(rawUserInfo.user_id || rawUserInfo.userId || '').trim();
39
- const initNickname = normalizeAlias(rawUserInfo.nickname || rawUserInfo.name || rawUserInfo.nickName || null);
97
+ const initNickname = cleanAlias(rawUserInfo.nickname || rawUserInfo.name || rawUserInfo.nickName || null);
40
98
  if (initUserId) {
41
99
  pushCandidate(initUserId, initNickname, 'initial_state.user_info');
42
100
  }
@@ -52,8 +110,8 @@ function buildDetectScript() {
52
110
 
53
111
  const selfNavEntry = Array.from(document.querySelectorAll('a[href*="/user/profile/"]'))
54
112
  .find((node) => {
55
- const text = String(node.textContent || '').replace(/\\s+/g, ' ').trim();
56
- return text === '我' || text === '我的' || text === '个人主页';
113
+ const labels = readLabelCandidates(node);
114
+ return labels.some(isSelfLabel);
57
115
  });
58
116
  if (selfNavEntry) {
59
117
  const href = String(selfNavEntry.getAttribute('href') || '').trim();
@@ -71,7 +129,12 @@ function buildDetectScript() {
71
129
  if (!matched) matched = href.match(/\\/user\\/([^/?#]+)/);
72
130
  if (!matched || !matched[1]) continue;
73
131
  const alias = String(anchor.textContent || '').trim();
74
- pushCandidate(matched[1], alias, 'anchor');
132
+ const labels = readLabelCandidates(anchor);
133
+ if (labels.some(isSelfLabel)) {
134
+ pushCandidate(matched[1], alias, 'anchor.self');
135
+ } else {
136
+ pushCandidate(matched[1], alias, 'anchor');
137
+ }
75
138
  }
76
139
 
77
140
  const avatar = document.querySelector('img[alt], [class*="avatar"] img[alt]');
@@ -83,11 +146,15 @@ function buildDetectScript() {
83
146
  }
84
147
  }
85
148
 
86
- const best = candidates
149
+ const strongCandidates = candidates.filter((item) => item.source !== 'localStorage.search_history');
150
+ const best = strongCandidates
87
151
  .find((item) => item.source === 'initial_state.user_info')
152
+ || strongCandidates.find((item) => item.source === 'nav.self')
153
+ || strongCandidates.find((item) => item.source === 'anchor.self')
154
+ || strongCandidates.find((item) => item.source === 'anchor' && item.alias)
155
+ || strongCandidates.find((item) => item.source === 'anchor')
156
+ || strongCandidates.find((item) => item.id && item.id.length >= 6)
88
157
  || candidates.find((item) => item.source === 'localStorage.search_history')
89
- || candidates.find((item) => item.source === 'nav.self')
90
- || candidates.find((item) => item.id && item.id.length >= 6)
91
158
  || candidates[0]
92
159
  || null;
93
160
  let alias = best ? best.alias : null;
@@ -95,7 +162,12 @@ function buildDetectScript() {
95
162
  const aliasNode = Array.from(document.querySelectorAll('a[href*="/user/profile/"], [class*="user"] a[href*="/user/profile/"]'))
96
163
  .find((node) => String(node.getAttribute && node.getAttribute('href') || '').includes(best.id));
97
164
  const text = aliasNode ? String(aliasNode.textContent || '').replace(/\\s+/g, ' ').trim() : '';
98
- if (text && text !== '我' && text !== '我的') alias = text;
165
+ const picked = cleanAlias(text);
166
+ if (picked) alias = picked;
167
+ }
168
+ if (!alias && (!best || !best.id)) {
169
+ const picked = findAliasFromDom();
170
+ if (picked) alias = picked;
99
171
  }
100
172
  return {
101
173
  url: location.href,
@@ -108,32 +180,174 @@ function buildDetectScript() {
108
180
  })()`;
109
181
  }
110
182
 
111
- export async function detectXhsAccountIdentity(profileId) {
112
- const payload = await callAPI('evaluate', {
113
- profileId,
114
- script: buildDetectScript(),
115
- });
116
- const result = payload?.result || payload?.data || payload || {};
117
- return {
118
- profileId: String(profileId || '').trim(),
119
- url: normalizeText(result.url),
120
- hasLoginGuard: result.hasLoginGuard === true,
121
- accountId: normalizeText(result.accountId),
122
- alias: normalizeText(result.alias),
123
- source: normalizeText(result.source),
124
- candidates: Array.isArray(result.candidates) ? result.candidates : [],
183
+ function buildAliasResolveScript() {
184
+ return `(() => {
185
+ const candidates = [];
186
+ const normalizeAlias = (value) => {
187
+ const text = String(value || '').replace(/\\s+/g, ' ').trim();
188
+ if (!text) return null;
189
+ if (text === '我' || text === '我的' || text === '个人主页') return null;
190
+ return text;
191
+ };
192
+ const cleanAlias = (value) => {
193
+ let text = normalizeAlias(value);
194
+ if (!text) return null;
195
+ text = text.replace(/\\s*[-—–]\\s*(小红书|xiaohongshu).*$/i, '').trim();
196
+ if (!text) return null;
197
+ const blocked = ['小红书', '登录', '注册', '搜索'];
198
+ if (blocked.includes(text)) return null;
199
+ return text;
200
+ };
201
+ const pushCandidate = (text, source) => {
202
+ const alias = cleanAlias(text);
203
+ if (!alias) return;
204
+ candidates.push({ text: alias, source });
205
+ };
206
+ const initialState = (typeof window !== 'undefined' && window.__INITIAL_STATE__) || null;
207
+ const rawUserInfo = initialState && initialState.user && initialState.user.userInfo
208
+ ? (
209
+ (initialState.user.userInfo._rawValue && typeof initialState.user.userInfo._rawValue === 'object' && initialState.user.userInfo._rawValue)
210
+ || (initialState.user.userInfo._value && typeof initialState.user.userInfo._value === 'object' && initialState.user.userInfo._value)
211
+ || (typeof initialState.user.userInfo === 'object' ? initialState.user.userInfo : null)
212
+ )
213
+ : null;
214
+ if (rawUserInfo) {
215
+ pushCandidate(rawUserInfo.nickname || rawUserInfo.name || rawUserInfo.nickName || null, 'initial_state.user_info');
216
+ }
217
+ const selectors = [
218
+ '[class*="user"] [class*="name"]',
219
+ '[class*="user"] [class*="nickname"]',
220
+ '[class*="nickname"]',
221
+ '[class*="user-name"]',
222
+ 'header [class*="name"]',
223
+ 'header h1',
224
+ 'header h2',
225
+ ];
226
+ for (const sel of selectors) {
227
+ const nodes = Array.from(document.querySelectorAll(sel)).slice(0, 8);
228
+ for (const node of nodes) {
229
+ pushCandidate(node.textContent || '', sel);
230
+ }
231
+ }
232
+ const metaTitle = document.querySelector('meta[property="og:title"], meta[name="og:title"]');
233
+ if (metaTitle) pushCandidate(metaTitle.getAttribute('content') || '', 'meta:og:title');
234
+ pushCandidate(document.title || '', 'document.title');
235
+ const picked = candidates.find((item) => item.text && item.text.length >= 2) || null;
236
+ return {
237
+ alias: picked ? picked.text : null,
238
+ source: picked ? picked.source : null,
239
+ candidates,
240
+ };
241
+ })()`;
242
+ }
243
+
244
+ async function resolveAliasFromProfilePage(profileId, accountId) {
245
+ if (!profileId || !accountId) return null;
246
+ let originalUrl = null;
247
+ try {
248
+ const urlPayload = await callAPI('evaluate', { profileId, script: 'window.location.href' });
249
+ originalUrl = normalizeText(urlPayload?.result || urlPayload?.data?.result || urlPayload?.url);
250
+ } catch {
251
+ originalUrl = null;
252
+ }
253
+ const targetUrl = `${XHS_PROFILE_URL}/${accountId}`;
254
+ const shouldNavigate = !originalUrl || !String(originalUrl).includes(`/user/profile/${accountId}`);
255
+ try {
256
+ if (shouldNavigate) {
257
+ await callAPI('goto', { profileId, url: targetUrl });
258
+ await sleep(1200);
259
+ }
260
+ const payload = await callAPI('evaluate', { profileId, script: buildAliasResolveScript() });
261
+ const result = payload?.result || payload?.data || payload || {};
262
+ return {
263
+ alias: normalizeText(result.alias),
264
+ source: normalizeText(result.source) || 'profile_page',
265
+ candidates: Array.isArray(result.candidates) ? result.candidates : [],
266
+ };
267
+ } catch {
268
+ return null;
269
+ } finally {
270
+ if (shouldNavigate && originalUrl) {
271
+ try {
272
+ await callAPI('goto', { profileId, url: originalUrl });
273
+ } catch {
274
+ // ignore restore failures
275
+ }
276
+ }
277
+ }
278
+ }
279
+
280
+ export async function detectXhsAccountIdentity(profileId, options = {}) {
281
+ const runDetect = async () => {
282
+ const payload = await callAPI('evaluate', {
283
+ profileId,
284
+ script: buildDetectScript(),
285
+ });
286
+ const result = payload?.result || payload?.data || payload || {};
287
+ return {
288
+ profileId: String(profileId || '').trim(),
289
+ url: normalizeText(result.url),
290
+ hasLoginGuard: result.hasLoginGuard === true,
291
+ accountId: normalizeText(result.accountId),
292
+ alias: normalizeText(result.alias),
293
+ source: normalizeText(result.source),
294
+ candidates: Array.isArray(result.candidates) ? result.candidates : [],
295
+ };
125
296
  };
297
+
298
+ let detected = await runDetect();
299
+ const shouldRetry = !detected.hasLoginGuard && (
300
+ !detected.accountId
301
+ || detected.source === 'localStorage.search_history'
302
+ || !detected.alias
303
+ );
304
+ if (shouldRetry) {
305
+ await sleep(1200);
306
+ const retry = await runDetect();
307
+ if (retry.accountId && (!detected.accountId || detected.source === 'localStorage.search_history')) {
308
+ detected.accountId = retry.accountId;
309
+ detected.source = retry.source || detected.source;
310
+ detected.candidates = retry.candidates;
311
+ }
312
+ if (retry.alias && !detected.alias) {
313
+ detected.alias = retry.alias;
314
+ if (!detected.source) detected.source = retry.source || detected.source;
315
+ }
316
+ if (retry.url && !detected.url) detected.url = retry.url;
317
+ }
318
+ if (options?.resolveAlias === true && detected.accountId && !detected.alias) {
319
+ const resolved = await resolveAliasFromProfilePage(detected.profileId, detected.accountId);
320
+ if (resolved?.alias) {
321
+ detected.alias = resolved.alias;
322
+ detected.source = resolved.source || detected.source;
323
+ }
324
+ }
325
+ return detected;
126
326
  }
127
327
 
128
- export async function syncXhsAccountByProfile(profileId) {
328
+ export async function syncXhsAccountByProfile(profileId, options = {}) {
129
329
  const normalizedProfileId = String(profileId || '').trim();
130
330
  if (!normalizedProfileId) throw new Error('profileId is required');
331
+ const pendingWhileLogin = options?.pendingWhileLogin === true;
131
332
  try {
132
- const detected = await detectXhsAccountIdentity(normalizedProfileId);
333
+ const existing = listAccountProfiles().profiles.find(
334
+ (item) => String(item?.profileId || '').trim() === normalizedProfileId,
335
+ );
336
+ const shouldResolveAlias = options?.resolveAlias === true
337
+ || (!existing?.alias && options?.resolveAlias !== false);
338
+ const detected = await detectXhsAccountIdentity(normalizedProfileId, {
339
+ resolveAlias: shouldResolveAlias,
340
+ });
133
341
  if (detected.hasLoginGuard) {
342
+ if (pendingWhileLogin) {
343
+ return markProfilePending(normalizedProfileId, 'waiting_login_guard');
344
+ }
134
345
  return markProfileInvalid(normalizedProfileId, 'login_guard');
135
346
  }
136
347
  if (!detected.accountId) {
348
+ if (pendingWhileLogin) {
349
+ return markProfilePending(normalizedProfileId, 'waiting_login');
350
+ }
137
351
  return markProfileInvalid(normalizedProfileId, 'missing_account_id');
138
352
  }
139
353
  return upsertProfileAccountState({
@@ -145,15 +359,27 @@ export async function syncXhsAccountByProfile(profileId) {
145
359
  detectedAt: new Date().toISOString(),
146
360
  });
147
361
  } catch (error) {
362
+ const msg = String(error?.message || error || '');
363
+ if (msg.toLowerCase().includes('operation is insecure')) {
364
+ try {
365
+ const existing = listAccountProfiles().profiles.find((item) => String(item?.profileId || '').trim() === normalizedProfileId);
366
+ if (existing && existing.valid) return existing;
367
+ } catch {
368
+ // ignore fallback lookup
369
+ }
370
+ }
371
+ if (pendingWhileLogin) {
372
+ return markProfilePending(normalizedProfileId, `waiting_login_sync:${error?.message || String(error)}`);
373
+ }
148
374
  return markProfileInvalid(normalizedProfileId, `sync_failed:${error?.message || String(error)}`);
149
375
  }
150
376
  }
151
377
 
152
- export async function syncXhsAccountsByProfiles(profileIds = []) {
378
+ export async function syncXhsAccountsByProfiles(profileIds = [], options = {}) {
153
379
  const list = Array.from(new Set((Array.isArray(profileIds) ? profileIds : []).map((item) => String(item || '').trim()).filter(Boolean)));
154
380
  const out = [];
155
381
  for (const profileId of list) {
156
- const state = await syncXhsAccountByProfile(profileId);
382
+ const state = await syncXhsAccountByProfile(profileId, options);
157
383
  out.push(state);
158
384
  }
159
385
  return out;