@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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Block: WaitSearchPermitBlock
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 向 SearchGate 申请搜索许可
|
|
6
|
+
* 2. 如果未获许可,自动等待并重试
|
|
7
|
+
* 3. 只有拿到许可后才成功返回
|
|
8
|
+
*/
|
|
9
|
+
export async function execute(input) {
|
|
10
|
+
const { sessionId, keyword, gateUrl = process.env.WEBAUTO_SEARCH_GATE_URL || 'http://127.0.0.1:7790', serviceUrl = 'http://127.0.0.1:7701', maxWaitMs = 300_000 } = input;
|
|
11
|
+
// Gate errors are not actionable by waiting; fail fast instead of retry loops.
|
|
12
|
+
const failFastOnGateError = String(process.env.WEBAUTO_SEARCH_GATE_FAIL_FAST || '1') !== '0';
|
|
13
|
+
const skipIfAlreadyOnSearchResult = typeof input.skipIfAlreadyOnSearchResult === 'boolean' ? input.skipIfAlreadyOnSearchResult : true;
|
|
14
|
+
const dev = typeof input.dev === 'boolean'
|
|
15
|
+
? input.dev
|
|
16
|
+
: (process.env.DEBUG === '1');
|
|
17
|
+
const devTag = typeof input.devTag === 'string' && input.devTag.trim()
|
|
18
|
+
? String(input.devTag).trim()
|
|
19
|
+
: (dev ? 'dev' : '');
|
|
20
|
+
// 若已经在正确的搜索结果页,直接跳过(避免重复申请 permit 造成“看起来像连续搜索”)
|
|
21
|
+
if (skipIfAlreadyOnSearchResult && typeof keyword === 'string' && keyword.trim()) {
|
|
22
|
+
try {
|
|
23
|
+
const { urlKeywordEquals } = await import('./helpers/searchPageState.js');
|
|
24
|
+
const controllerUrl = `${serviceUrl.replace(/\/$/, '')}/v1/controller/action`;
|
|
25
|
+
const resp = await fetch(controllerUrl, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json' },
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
action: 'browser:execute',
|
|
30
|
+
payload: { profile: sessionId, script: 'location.href' },
|
|
31
|
+
}),
|
|
32
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(5000) : undefined,
|
|
33
|
+
});
|
|
34
|
+
const data = await resp.json().catch(() => ({}));
|
|
35
|
+
const url = data?.data?.result || data?.result || '';
|
|
36
|
+
if (typeof url === 'string' && url.includes('/search_result') && urlKeywordEquals(url, keyword)) {
|
|
37
|
+
console.log(`[WaitSearchPermit] skip: already on search_result for "${keyword}"`);
|
|
38
|
+
return { success: true, granted: true, waitedMs: 0, skipped: true };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// ignore
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const permitEndpoint = `${gateUrl.replace(/\/$/, '')}/permit`;
|
|
46
|
+
const start = Date.now();
|
|
47
|
+
async function requestPermit() {
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(permitEndpoint, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
key: sessionId,
|
|
54
|
+
...(typeof keyword === 'string' && keyword.trim() ? { keyword: keyword.trim() } : {}),
|
|
55
|
+
...(dev ? { dev: true } : {}),
|
|
56
|
+
...(devTag ? { devTag } : {}),
|
|
57
|
+
windowMs: 60_000,
|
|
58
|
+
maxCount: 2
|
|
59
|
+
}),
|
|
60
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(5000) : undefined
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const text = await response.text().catch(() => '');
|
|
64
|
+
return { ok: false, retry: true, error: `HTTP ${response.status}${text ? `: ${text}` : ''}` };
|
|
65
|
+
}
|
|
66
|
+
const data = await response.json().catch(() => ({}));
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
allowed: Boolean(data.allowed),
|
|
70
|
+
waitMs: Number(data.waitMs || 0),
|
|
71
|
+
retryAfterMs: typeof data.retryAfterMs === 'number' ? Number(data.retryAfterMs) : null,
|
|
72
|
+
reason: typeof data.reason === 'string' ? data.reason : null,
|
|
73
|
+
keyword: typeof data.keyword === 'string' ? data.keyword : null,
|
|
74
|
+
consecutive: typeof data.consecutive === 'number' ? data.consecutive : null,
|
|
75
|
+
deny: data.deny && typeof data.deny === 'object'
|
|
76
|
+
? {
|
|
77
|
+
code: String(data.deny.code || 'unknown'),
|
|
78
|
+
message: String(data.deny.message || 'denied'),
|
|
79
|
+
retryAfterMs: typeof data.deny.retryAfterMs === 'number' ? Number(data.deny.retryAfterMs) : null,
|
|
80
|
+
details: data.deny.details ?? null,
|
|
81
|
+
suggestedActions: Array.isArray(data.deny.suggestedActions)
|
|
82
|
+
? data.deny.suggestedActions.map((s) => String(s))
|
|
83
|
+
: [],
|
|
84
|
+
}
|
|
85
|
+
: null,
|
|
86
|
+
retry: false
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return { ok: false, retry: true, error: err.message };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
console.log(`[WaitSearchPermit] Requesting permit for ${sessionId}...${typeof keyword === 'string' && keyword.trim() ? ` keyword="${keyword.trim()}"` : ''}`);
|
|
94
|
+
while (Date.now() - start < maxWaitMs) {
|
|
95
|
+
const result = await requestPermit();
|
|
96
|
+
if (result.ok && result.allowed) {
|
|
97
|
+
console.log(`[WaitSearchPermit] ✅ Permit granted (waited ${(Date.now() - start) / 1000}s)`);
|
|
98
|
+
return { success: true, granted: true, waitedMs: Date.now() - start, reason: result.reason ?? null };
|
|
99
|
+
}
|
|
100
|
+
if (result.ok && !result.allowed) {
|
|
101
|
+
// 开发阶段:禁止连续三次同 keyword 搜索,直接失败(不要无脑重试)
|
|
102
|
+
if (result.reason === 'dev_consecutive_keyword_limit' || result.deny?.code === 'dev_consecutive_keyword_limit') {
|
|
103
|
+
const denyMsg = result.deny?.message || '';
|
|
104
|
+
const actions = (result.deny?.suggestedActions || []).length > 0
|
|
105
|
+
? (result.deny?.suggestedActions || [])
|
|
106
|
+
: [
|
|
107
|
+
'Stop and inspect logs/screenshots; do not spam search retries.',
|
|
108
|
+
'If this is a dev-only rerun and you must search the same keyword again, restart SearchGate to clear in-memory dev keyword history.',
|
|
109
|
+
'Alternatively, wait until the dev keyword history expires (default keeps 24h).',
|
|
110
|
+
];
|
|
111
|
+
console.error(`[WaitSearchPermit] ❌ SearchGate denied: dev_consecutive_keyword_limit (keyword="${String(result.keyword || keyword || '')}", consecutive=${String(result.consecutive ?? '')})${denyMsg ? ` | ${denyMsg}` : ''}`);
|
|
112
|
+
for (const action of actions) {
|
|
113
|
+
console.error(`[WaitSearchPermit] - ${action}`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
granted: false,
|
|
118
|
+
waitedMs: Date.now() - start,
|
|
119
|
+
reason: result.reason ?? null,
|
|
120
|
+
retryAfterMs: result.retryAfterMs ?? result.deny?.retryAfterMs ?? null,
|
|
121
|
+
deny: result.deny ?? null,
|
|
122
|
+
error: `SearchGate denied: dev_consecutive_keyword_limit (keyword="${String(result.keyword || keyword || '')}", consecutive=${String(result.consecutive ?? '')})`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// 只在服务端真正计算出需要等待时才等待
|
|
126
|
+
const waitMs = Math.max(0, Number(result.retryAfterMs ?? result.waitMs ?? 0));
|
|
127
|
+
if (waitMs > 0) {
|
|
128
|
+
console.log(`[WaitSearchPermit] ⏳ Throttled${result.reason ? ` (${result.reason})` : ''}, waiting ${Math.ceil(waitMs / 1000)}s...`);
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// waitMs=0:可能是服务端拒绝但未给出等待时间;避免 busy-loop,稍等再试
|
|
134
|
+
console.warn(`[WaitSearchPermit] ⚠️ Gate denied but waitMs=0${result.reason ? ` (reason=${result.reason})` : ''}, retrying in 1s...`);
|
|
135
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!result.ok && result.retry) {
|
|
140
|
+
const errMsg = String(result.error || 'unknown error');
|
|
141
|
+
if (failFastOnGateError) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
granted: false,
|
|
145
|
+
waitedMs: Date.now() - start,
|
|
146
|
+
error: `SearchGate error: ${errMsg}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
console.warn(`[WaitSearchPermit] ⚠️ Gate error (${errMsg}), retrying in 5s...`);
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
granted: false,
|
|
158
|
+
waitedMs: Date.now() - start,
|
|
159
|
+
error: `WaitSearchPermit timeout after ${maxWaitMs / 1000}s`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=WaitSearchPermitBlock.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 容器锚点验证辅助函数(不依赖 containers:match)
|
|
3
|
+
* 直接从容器定义 JSON 读取 selector,然后用 browser:execute 高亮 + Rect 回环
|
|
4
|
+
*
|
|
5
|
+
* 注意:这里不使用 dist 里的 ContainerDefinitionLoader(V2 结构只保留了 classes,丢失 css),
|
|
6
|
+
* 而是直接读取 container-library 下的原始 JSON,保证 selector.css 可用。
|
|
7
|
+
*/
|
|
8
|
+
export interface ContainerDefinition {
|
|
9
|
+
id: string;
|
|
10
|
+
selectors?: Array<{
|
|
11
|
+
css?: string;
|
|
12
|
+
variant?: string;
|
|
13
|
+
score?: number;
|
|
14
|
+
}>;
|
|
15
|
+
extractors?: Record<string, {
|
|
16
|
+
selectors?: string[];
|
|
17
|
+
attr?: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export interface AnchorRect {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}
|
|
26
|
+
export interface AnchorVerifyResult {
|
|
27
|
+
found: boolean;
|
|
28
|
+
highlighted: boolean;
|
|
29
|
+
rect?: AnchorRect;
|
|
30
|
+
selector?: string;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function getPrimarySelectorByContainerId(containerId: string): Promise<string | null>;
|
|
34
|
+
export declare function getContainerExtractorsById(containerId: string): Promise<ContainerDefinition['extractors'] | null>;
|
|
35
|
+
export declare function verifyAnchorByContainerId(containerId: string, sessionId: string, serviceUrl?: string, highlightStyle?: string, highlightDuration?: number): Promise<AnchorVerifyResult>;
|
|
36
|
+
//# sourceMappingURL=containerAnchors.d.ts.map
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 容器锚点验证辅助函数(不依赖 containers:match)
|
|
3
|
+
* 直接从容器定义 JSON 读取 selector,然后用 browser:execute 高亮 + Rect 回环
|
|
4
|
+
*
|
|
5
|
+
* 注意:这里不使用 dist 里的 ContainerDefinitionLoader(V2 结构只保留了 classes,丢失 css),
|
|
6
|
+
* 而是直接读取 container-library 下的原始 JSON,保证 selector.css 可用。
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import { logError, logOperation } from './operationLogger.js';
|
|
12
|
+
const repoRoot = path.resolve(process.cwd());
|
|
13
|
+
const userContainerRoot = process.env.WEBAUTO_USER_CONTAINER_ROOT || path.join(os.homedir(), '.webauto', 'container-lib');
|
|
14
|
+
const containerIndexPath = process.env.WEBAUTO_CONTAINER_INDEX || path.join(repoRoot, 'apps/webauto/resources/container-library.index.json');
|
|
15
|
+
const definitionCache = new Map();
|
|
16
|
+
function loadContainerIndex() {
|
|
17
|
+
try {
|
|
18
|
+
const content = fs.readFileSync(containerIndexPath, 'utf-8');
|
|
19
|
+
return JSON.parse(content);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function findSiteKeyForContainer(containerId, index) {
|
|
26
|
+
if (!index)
|
|
27
|
+
return null;
|
|
28
|
+
const keys = Object.keys(index);
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
if (containerId.startsWith(`${key}_`))
|
|
31
|
+
return key;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function findContainerJsonPath(containerId) {
|
|
36
|
+
const index = loadContainerIndex();
|
|
37
|
+
const siteKey = findSiteKeyForContainer(containerId, index) || 'xiaohongshu';
|
|
38
|
+
const rootDir = path.join(repoRoot, 'apps/webauto/resources/container-library', siteKey);
|
|
39
|
+
if (!fs.existsSync(rootDir)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const stack = [rootDir];
|
|
43
|
+
while (stack.length > 0) {
|
|
44
|
+
const current = stack.pop();
|
|
45
|
+
const stat = fs.statSync(current);
|
|
46
|
+
if (stat.isDirectory()) {
|
|
47
|
+
const entries = fs.readdirSync(current);
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
stack.push(path.join(current, entry));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (current.endsWith('.json')) {
|
|
53
|
+
try {
|
|
54
|
+
const content = fs.readFileSync(current, 'utf-8');
|
|
55
|
+
const json = JSON.parse(content);
|
|
56
|
+
if (json && json.id === containerId) {
|
|
57
|
+
return current;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// ignore malformed JSON
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
async function findContainerDefinition(containerId) {
|
|
68
|
+
if (definitionCache.has(containerId)) {
|
|
69
|
+
const cached = definitionCache.get(containerId);
|
|
70
|
+
if (cached) {
|
|
71
|
+
return cached;
|
|
72
|
+
}
|
|
73
|
+
// 如果之前没有找到(缓存为 null),重新扫描一次,避免负缓存长期生效
|
|
74
|
+
}
|
|
75
|
+
// 优先从用户自定义容器根查找
|
|
76
|
+
if (userContainerRoot && fs.existsSync(userContainerRoot)) {
|
|
77
|
+
const userRoot = path.join(userContainerRoot, 'xiaohongshu');
|
|
78
|
+
if (fs.existsSync(userRoot)) {
|
|
79
|
+
const stack = [userRoot];
|
|
80
|
+
while (stack.length > 0) {
|
|
81
|
+
const current = stack.pop();
|
|
82
|
+
const stat = fs.statSync(current);
|
|
83
|
+
if (stat.isDirectory()) {
|
|
84
|
+
const entries = fs.readdirSync(current);
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
stack.push(path.join(current, entry));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (current.endsWith('.json')) {
|
|
90
|
+
try {
|
|
91
|
+
const content = fs.readFileSync(current, 'utf-8');
|
|
92
|
+
const json = JSON.parse(content);
|
|
93
|
+
if (json && json.id === containerId) {
|
|
94
|
+
definitionCache.set(containerId, json);
|
|
95
|
+
return json;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// ignore
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const jsonPath = findContainerJsonPath(containerId);
|
|
106
|
+
if (!jsonPath) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const content = fs.readFileSync(jsonPath, 'utf-8');
|
|
111
|
+
const json = JSON.parse(content);
|
|
112
|
+
definitionCache.set(containerId, json);
|
|
113
|
+
return json;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function pickPrimarySelector(container) {
|
|
120
|
+
const selectors = container.selectors || [];
|
|
121
|
+
if (!selectors.length)
|
|
122
|
+
return null;
|
|
123
|
+
const primary = selectors.find((item) => item.variant === 'primary') || selectors[0];
|
|
124
|
+
return primary?.css || null;
|
|
125
|
+
}
|
|
126
|
+
export async function getPrimarySelectorByContainerId(containerId) {
|
|
127
|
+
const container = await findContainerDefinition(containerId);
|
|
128
|
+
if (!container) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return pickPrimarySelector(container);
|
|
132
|
+
}
|
|
133
|
+
export async function getContainerExtractorsById(containerId) {
|
|
134
|
+
const container = await findContainerDefinition(containerId);
|
|
135
|
+
if (!container || !container.extractors || typeof container.extractors !== 'object') {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return container.extractors;
|
|
139
|
+
}
|
|
140
|
+
export async function verifyAnchorByContainerId(containerId, sessionId, serviceUrl = 'http://127.0.0.1:7701', highlightStyle = '3px solid #ff4444', highlightDuration = 2000) {
|
|
141
|
+
console.log(`[verifyAnchorByContainerId] Looking for container: ${containerId}`);
|
|
142
|
+
console.log(`[verifyAnchorByContainerId] containerIndexPath: ${containerIndexPath}`);
|
|
143
|
+
console.log(`[verifyAnchorByContainerId] repoRoot: ${repoRoot}`);
|
|
144
|
+
const container = await findContainerDefinition(containerId);
|
|
145
|
+
if (!container) {
|
|
146
|
+
console.error(`[verifyAnchorByContainerId] Container not found: ${containerId}`);
|
|
147
|
+
return { found: false, highlighted: false, error: `Container not found: ${containerId}` };
|
|
148
|
+
}
|
|
149
|
+
const selectors = (container.selectors || [])
|
|
150
|
+
.map((s) => s?.css)
|
|
151
|
+
.filter((css) => typeof css === 'string' && css.trim().length > 0);
|
|
152
|
+
if (!selectors.length) {
|
|
153
|
+
return { found: false, highlighted: false, error: `No selector defined for: ${containerId}` };
|
|
154
|
+
}
|
|
155
|
+
const matchOpId = logOperation({
|
|
156
|
+
kind: 'anchor_match_start',
|
|
157
|
+
action: 'anchor:verify',
|
|
158
|
+
sessionId,
|
|
159
|
+
payload: {
|
|
160
|
+
containerId,
|
|
161
|
+
selectorsCount: selectors.length,
|
|
162
|
+
serviceUrl,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
try {
|
|
166
|
+
const selectorsJson = JSON.stringify(selectors);
|
|
167
|
+
const script = `(() => {
|
|
168
|
+
const selectors = ${selectorsJson};
|
|
169
|
+
|
|
170
|
+
const isVisible = (el) => {
|
|
171
|
+
if (!el) return false;
|
|
172
|
+
const style = window.getComputedStyle(el);
|
|
173
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
|
|
174
|
+
const r = el.getBoundingClientRect();
|
|
175
|
+
if (!r.width || !r.height) return false;
|
|
176
|
+
if (r.bottom <= 0 || r.top >= window.innerHeight) return false;
|
|
177
|
+
return true;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
let el = null;
|
|
181
|
+
let usedSelector = null;
|
|
182
|
+
for (const sel of selectors) {
|
|
183
|
+
const candidate = document.querySelector(sel);
|
|
184
|
+
if (candidate && isVisible(candidate)) {
|
|
185
|
+
el = candidate;
|
|
186
|
+
usedSelector = sel;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!el) {
|
|
192
|
+
return { found: false, error: 'Element not found' };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 创建或复用一个 overlay 高亮框,避免被页面样式覆盖
|
|
196
|
+
let overlay = document.getElementById('webauto-anchor-highlight');
|
|
197
|
+
if (!overlay) {
|
|
198
|
+
overlay = document.createElement('div');
|
|
199
|
+
overlay.id = 'webauto-anchor-highlight';
|
|
200
|
+
overlay.style.position = 'fixed';
|
|
201
|
+
overlay.style.pointerEvents = 'none';
|
|
202
|
+
overlay.style.zIndex = '2147483647';
|
|
203
|
+
document.body.appendChild(overlay);
|
|
204
|
+
}
|
|
205
|
+
const r = el.getBoundingClientRect();
|
|
206
|
+
overlay.style.left = r.x + 'px';
|
|
207
|
+
overlay.style.top = r.y + 'px';
|
|
208
|
+
overlay.style.width = r.width + 'px';
|
|
209
|
+
overlay.style.height = r.height + 'px';
|
|
210
|
+
overlay.style.border = '${highlightStyle.replace(/'/g, "\\'")}';
|
|
211
|
+
overlay.style.boxSizing = 'border-box';
|
|
212
|
+
overlay.style.background = 'transparent';
|
|
213
|
+
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
if (overlay && overlay.parentElement) {
|
|
216
|
+
overlay.parentElement.removeChild(overlay);
|
|
217
|
+
}
|
|
218
|
+
}, ${highlightDuration});
|
|
219
|
+
const rect = el.getBoundingClientRect();
|
|
220
|
+
return {
|
|
221
|
+
found: true,
|
|
222
|
+
selector: usedSelector,
|
|
223
|
+
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
|
|
224
|
+
};
|
|
225
|
+
})()`;
|
|
226
|
+
const response = await fetch(`${serviceUrl}/v1/controller/action`, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: { 'Content-Type': 'application/json' },
|
|
229
|
+
body: JSON.stringify({
|
|
230
|
+
action: 'browser:execute',
|
|
231
|
+
payload: {
|
|
232
|
+
profile: sessionId,
|
|
233
|
+
script,
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
// 本地 Unified API 调用在部分页面初始化时可能稍慢,适当放宽超时时间
|
|
237
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(30000) : undefined,
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
return {
|
|
241
|
+
found: false,
|
|
242
|
+
highlighted: false,
|
|
243
|
+
error: `HTTP ${response.status}: ${await response.text()}`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const data = await response.json();
|
|
247
|
+
const result = data.data?.result || data.result;
|
|
248
|
+
if (!result?.found) {
|
|
249
|
+
logOperation({
|
|
250
|
+
kind: 'anchor_match_result',
|
|
251
|
+
action: 'anchor:verify',
|
|
252
|
+
sessionId,
|
|
253
|
+
result: {
|
|
254
|
+
found: false,
|
|
255
|
+
selector: result?.selector || null,
|
|
256
|
+
error: result?.error || 'Element not found',
|
|
257
|
+
},
|
|
258
|
+
meta: { opId: matchOpId, containerId },
|
|
259
|
+
});
|
|
260
|
+
return {
|
|
261
|
+
found: false,
|
|
262
|
+
highlighted: false,
|
|
263
|
+
selector: result?.selector,
|
|
264
|
+
error: result?.error || 'Element not found',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
logOperation({
|
|
268
|
+
kind: 'anchor_match_result',
|
|
269
|
+
action: 'anchor:verify',
|
|
270
|
+
sessionId,
|
|
271
|
+
result: {
|
|
272
|
+
found: true,
|
|
273
|
+
selector: result?.selector || null,
|
|
274
|
+
rect: result?.rect || null,
|
|
275
|
+
},
|
|
276
|
+
meta: { opId: matchOpId, containerId },
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
found: true,
|
|
280
|
+
highlighted: true,
|
|
281
|
+
rect: result.rect,
|
|
282
|
+
selector: result.selector,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
logError({
|
|
287
|
+
kind: 'anchor_match_error',
|
|
288
|
+
action: 'anchor:verify',
|
|
289
|
+
sessionId,
|
|
290
|
+
error: error?.message || String(error),
|
|
291
|
+
payload: { containerId, serviceUrl },
|
|
292
|
+
meta: { opId: matchOpId },
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
found: false,
|
|
296
|
+
highlighted: false,
|
|
297
|
+
error: `verifyAnchor failed: ${error.message}`,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=containerAnchors.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface OperationLogEntry {
|
|
2
|
+
kind: string;
|
|
3
|
+
action?: string;
|
|
4
|
+
sessionId?: string;
|
|
5
|
+
context?: string;
|
|
6
|
+
reason?: string;
|
|
7
|
+
payload?: Record<string, any> | null;
|
|
8
|
+
result?: Record<string, any> | null;
|
|
9
|
+
target?: Record<string, any> | null;
|
|
10
|
+
meta?: Record<string, any> | null;
|
|
11
|
+
opId?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ErrorLogEntry {
|
|
14
|
+
kind: string;
|
|
15
|
+
action?: string;
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
context?: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
payload?: Record<string, any> | null;
|
|
21
|
+
meta?: Record<string, any> | null;
|
|
22
|
+
opId?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function logOperation(entry: OperationLogEntry): number;
|
|
25
|
+
export declare function logError(entry: ErrorLogEntry): number;
|
|
26
|
+
export declare function logControllerActionStart(action: string, payload: any, meta?: Record<string, any>): number;
|
|
27
|
+
export declare function logControllerActionResult(opId: number, action: string, result: any, meta?: Record<string, any>): void;
|
|
28
|
+
export declare function logControllerActionError(opId: number, action: string, error: unknown, payload: any, meta?: Record<string, any>): void;
|
|
29
|
+
//# sourceMappingURL=operationLogger.d.ts.map
|