@web-auto/webauto 0.1.4 → 0.1.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.
- package/apps/desktop-console/default-settings.json +2 -2
- package/apps/desktop-console/dist/main/index.mjs +915 -85
- package/apps/desktop-console/dist/main/preload.mjs +7 -0
- package/apps/desktop-console/dist/renderer/index.html +622 -50
- package/apps/desktop-console/dist/renderer/index.js +2415 -470
- package/apps/desktop-console/dist/renderer/run.mts +6 -5
- package/apps/desktop-console/entry/ui-cli.mjs +672 -0
- package/apps/desktop-console/entry/ui-console.mjs +416 -29
- package/apps/webauto/entry/account.mjs +89 -53
- package/apps/webauto/entry/browser-status.mjs +7 -10
- package/apps/webauto/entry/lib/account-detect.mjs +254 -28
- package/apps/webauto/entry/lib/account-store.mjs +219 -30
- package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
- package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
- package/apps/webauto/entry/lib/profilepool.mjs +14 -5
- package/apps/webauto/entry/lib/quota-status.mjs +23 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
- package/apps/webauto/entry/profilepool.mjs +106 -17
- package/apps/webauto/entry/schedule.mjs +612 -0
- package/apps/webauto/entry/weibo-unified.mjs +134 -0
- package/apps/webauto/entry/xhs-install.mjs +236 -29
- package/apps/webauto/entry/xhs-status.mjs +5 -2
- package/apps/webauto/entry/xhs-unified.mjs +631 -98
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
- package/bin/camoufox-cli.mjs +61 -0
- package/bin/webauto.mjs +301 -54
- package/dist/modules/camo-backend/src/index.js +49 -1
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
- package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
- package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
- package/dist/modules/collection-manager/bloom-filter.js +91 -0
- package/dist/modules/collection-manager/date-utils.js +275 -0
- package/dist/modules/collection-manager/index.js +258 -0
- package/dist/modules/collection-manager/storage.js +195 -0
- package/dist/modules/collection-manager/types.js +47 -0
- package/dist/modules/logging/src/index.js +1 -1
- package/dist/modules/process-registry/index.js +230 -0
- package/dist/modules/rate-limiter/index.js +242 -0
- package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
- package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
- package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
- package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
- package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
- package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
- package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
- package/dist/modules/workflow/config/workflowRegistry.js +2 -0
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
- package/dist/modules/workflow/src/runner.js +6 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
- package/dist/services/shared/serviceProcessLogger.js +1 -1
- package/dist/services/unified-api/server.js +105 -11
- package/modules/camo-backend/src/index.ts +46 -1
- package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
- package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
- package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
- package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
- package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
- package/modules/collection-manager/bloom-filter.ts +112 -0
- package/modules/collection-manager/date-utils.ts +316 -0
- package/modules/collection-manager/index.ts +309 -0
- package/modules/collection-manager/package.json +10 -0
- package/modules/collection-manager/storage.ts +174 -0
- package/modules/collection-manager/types.ts +156 -0
- package/modules/logging/src/index.ts +1 -1
- package/modules/process-registry/index.ts +284 -0
- package/modules/rate-limiter/index.ts +322 -0
- package/modules/state/src/paths.ts +9 -1
- package/modules/task-scheduler/index.ts +293 -0
- package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
- package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
- package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
- package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
- package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
- package/modules/workflow/config/workflowRegistry.ts +2 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
- package/modules/workflow/src/runner.ts +6 -0
- package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
- package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
- package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
- package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
- package/package.json +13 -4
- package/scripts/postinstall-resources.mjs +62 -0
- package/scripts/test/run-coverage.mjs +76 -0
- package/scripts/weibo/search.ts +49 -0
- package/services/shared/serviceProcessLogger.ts +1 -1
- 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: '
|
|
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 {
|
|
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
|
|
12
|
-
const ret =
|
|
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.
|
|
19
|
-
console.error(JSON.stringify({ ok: false, code: ret.
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
|
56
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
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;
|