@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
|
@@ -134,7 +134,17 @@ export class SessionManager {
|
|
|
134
134
|
(process as any).emit(SESSION_CLOSED_EVENT, id);
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
|
-
|
|
137
|
+
try {
|
|
138
|
+
await session.start(options.initialUrl);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
this.debugLog('createSession:start_failed', {
|
|
141
|
+
profileId,
|
|
142
|
+
error: (err as Error)?.message || String(err),
|
|
143
|
+
});
|
|
144
|
+
session.onExit = undefined;
|
|
145
|
+
await session.close().catch(() => {});
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
138
148
|
this.sessions.set(profileId, session);
|
|
139
149
|
|
|
140
150
|
this.debugLog('createSession:started', { profileId });
|
|
@@ -156,6 +166,7 @@ export class SessionManager {
|
|
|
156
166
|
current_url: session.getCurrentUrl(),
|
|
157
167
|
mode: session.modeName,
|
|
158
168
|
owner_pid: this.owners.get(session.id)?.pid || null,
|
|
169
|
+
recording: session.getRecordingStatus(),
|
|
159
170
|
}));
|
|
160
171
|
}
|
|
161
172
|
|
|
@@ -36,3 +36,8 @@ export function resolveLocksRoot() {
|
|
|
36
36
|
return path.join(resolveDataRoot(), 'locks');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function resolveRecordsRoot() {
|
|
40
|
+
const envRoot = String(process.env.WEBAUTO_PATHS_RECORDS || '').trim();
|
|
41
|
+
if (envRoot) return envRoot;
|
|
42
|
+
return path.join(resolveDataRoot(), 'records');
|
|
43
|
+
}
|
|
@@ -86,6 +86,43 @@ export function buildCommentsHarvestScript(params = {}) {
|
|
|
86
86
|
}
|
|
87
87
|
return null;
|
|
88
88
|
};
|
|
89
|
+
const normalizeInlineText = (value) => String(value || '').replace(/\\s+/g, ' ').trim();
|
|
90
|
+
const sanitizeAuthorText = (raw, commentText = '') => {
|
|
91
|
+
const text = normalizeInlineText(raw);
|
|
92
|
+
if (!text) return '';
|
|
93
|
+
if (commentText && text === commentText) return '';
|
|
94
|
+
if (text.length > 40) return '';
|
|
95
|
+
if (/^(回复|展开|收起|查看更多|评论|赞|分享|发送)$/.test(text)) return '';
|
|
96
|
+
return text;
|
|
97
|
+
};
|
|
98
|
+
const readAuthor = (item, commentText = '') => {
|
|
99
|
+
const attrNames = ['data-user-name', 'data-username', 'data-user_nickname', 'data-nickname'];
|
|
100
|
+
for (const attr of attrNames) {
|
|
101
|
+
const value = sanitizeAuthorText(item.getAttribute?.(attr), commentText);
|
|
102
|
+
if (value) return value;
|
|
103
|
+
}
|
|
104
|
+
const selectors = [
|
|
105
|
+
'.comment-user .name',
|
|
106
|
+
'.comment-user .username',
|
|
107
|
+
'.comment-user .user-name',
|
|
108
|
+
'.author .name',
|
|
109
|
+
'.author',
|
|
110
|
+
'.user-name',
|
|
111
|
+
'.username',
|
|
112
|
+
'.name',
|
|
113
|
+
'a[href*="/user/profile/"]',
|
|
114
|
+
'a[href*="/user/"]',
|
|
115
|
+
];
|
|
116
|
+
for (const selector of selectors) {
|
|
117
|
+
const node = item.querySelector(selector);
|
|
118
|
+
if (!node) continue;
|
|
119
|
+
const title = sanitizeAuthorText(node.getAttribute?.('title'), commentText);
|
|
120
|
+
if (title) return title;
|
|
121
|
+
const text = sanitizeAuthorText(node.textContent, commentText);
|
|
122
|
+
if (text) return text;
|
|
123
|
+
}
|
|
124
|
+
return '';
|
|
125
|
+
};
|
|
89
126
|
|
|
90
127
|
const scroller = document.querySelector('.note-scroller')
|
|
91
128
|
|| document.querySelector('.comments-el')
|
|
@@ -105,9 +142,8 @@ export function buildCommentsHarvestScript(params = {}) {
|
|
|
105
142
|
const nodes = Array.from(document.querySelectorAll('.comment-item, [class*="comment-item"]'));
|
|
106
143
|
for (const item of nodes) {
|
|
107
144
|
const textNode = item.querySelector('.content, .comment-content, p');
|
|
108
|
-
const authorNode = item.querySelector('.name, .author, .user-name, [class*="author"], [class*="name"]');
|
|
109
145
|
const text = String((textNode && textNode.textContent) || '').trim();
|
|
110
|
-
const author =
|
|
146
|
+
const author = readAuthor(item, text);
|
|
111
147
|
if (!text) continue;
|
|
112
148
|
const key = author + '::' + text;
|
|
113
149
|
if (commentMap.has(key)) continue;
|
|
@@ -74,6 +74,42 @@ function buildCollectLikeTargetsScript() {
|
|
|
74
74
|
}
|
|
75
75
|
return '';
|
|
76
76
|
};
|
|
77
|
+
const sanitizeUserName = (raw, commentText = '') => {
|
|
78
|
+
const text = String(raw || '').replace(/\\s+/g, ' ').trim();
|
|
79
|
+
if (!text) return '';
|
|
80
|
+
if (commentText && text === commentText) return '';
|
|
81
|
+
if (text.length > 40) return '';
|
|
82
|
+
if (/^(回复|展开|收起|查看更多|评论|赞|分享|发送)$/.test(text)) return '';
|
|
83
|
+
return text;
|
|
84
|
+
};
|
|
85
|
+
const readUserName = (item, commentText = '') => {
|
|
86
|
+
const attrNames = ['data-user-name', 'data-username', 'data-user_nickname', 'data-nickname'];
|
|
87
|
+
for (const attr of attrNames) {
|
|
88
|
+
const value = sanitizeUserName(item.getAttribute?.(attr), commentText);
|
|
89
|
+
if (value) return value;
|
|
90
|
+
}
|
|
91
|
+
const selectors = [
|
|
92
|
+
'.comment-user .name',
|
|
93
|
+
'.comment-user .username',
|
|
94
|
+
'.comment-user .user-name',
|
|
95
|
+
'.author .name',
|
|
96
|
+
'.author',
|
|
97
|
+
'.user-name',
|
|
98
|
+
'.username',
|
|
99
|
+
'.name',
|
|
100
|
+
'a[href*="/user/profile/"]',
|
|
101
|
+
'a[href*="/user/"]',
|
|
102
|
+
];
|
|
103
|
+
for (const selector of selectors) {
|
|
104
|
+
const node = item.querySelector(selector);
|
|
105
|
+
if (!node) continue;
|
|
106
|
+
const title = sanitizeUserName(node.getAttribute?.('title'), commentText);
|
|
107
|
+
if (title) return title;
|
|
108
|
+
const text = sanitizeUserName(node.textContent, commentText);
|
|
109
|
+
if (text) return text;
|
|
110
|
+
}
|
|
111
|
+
return '';
|
|
112
|
+
};
|
|
77
113
|
const readAttr = (item, attrNames) => {
|
|
78
114
|
for (const attr of attrNames) {
|
|
79
115
|
const value = String(item.getAttribute?.(attr) || '').trim();
|
|
@@ -81,6 +117,15 @@ function buildCollectLikeTargetsScript() {
|
|
|
81
117
|
}
|
|
82
118
|
return '';
|
|
83
119
|
};
|
|
120
|
+
const readUserId = (item) => {
|
|
121
|
+
const value = readAttr(item, ['data-user-id', 'data-userid', 'data-user_id']);
|
|
122
|
+
if (value) return value;
|
|
123
|
+
const anchor = item.querySelector('a[href*="/user/profile/"], a[href*="/user/"]');
|
|
124
|
+
const href = String(anchor?.getAttribute?.('href') || '').trim();
|
|
125
|
+
if (!href) return '';
|
|
126
|
+
const matched = href.match(/\\/user\\/(?:profile\\/)?([a-zA-Z0-9_-]+)/);
|
|
127
|
+
return matched && matched[1] ? matched[1] : '';
|
|
128
|
+
};
|
|
84
129
|
|
|
85
130
|
const matchedSet = new Set(
|
|
86
131
|
Array.isArray(state.matchedComments)
|
|
@@ -92,8 +137,8 @@ function buildCollectLikeTargetsScript() {
|
|
|
92
137
|
const item = items[index];
|
|
93
138
|
const text = readText(item, ['.content', '.comment-content', 'p']);
|
|
94
139
|
if (!text) continue;
|
|
95
|
-
const userName =
|
|
96
|
-
const userId =
|
|
140
|
+
const userName = readUserName(item, text);
|
|
141
|
+
const userId = readUserId(item);
|
|
97
142
|
const timestamp = readText(item, ['.date', '.time', '.timestamp', '[class*="time"]']);
|
|
98
143
|
const likeControl = findLikeControl(item);
|
|
99
144
|
rows.push({
|
|
@@ -54,6 +54,19 @@ export function buildOpenDetailScript(params = {}) {
|
|
|
54
54
|
const mode = String(params.mode || 'first').trim().toLowerCase();
|
|
55
55
|
const maxNotes = Math.max(1, Number(params.maxNotes ?? params.limit ?? 20) || 20);
|
|
56
56
|
const keyword = String(params.keyword || '').trim();
|
|
57
|
+
const resume = params.resume !== false;
|
|
58
|
+
const incrementalMax = params.incrementalMax !== false;
|
|
59
|
+
const excludeNoteIds = Array.isArray(params.excludeNoteIds)
|
|
60
|
+
? params.excludeNoteIds.map((item) => String(item || '').trim()).filter(Boolean)
|
|
61
|
+
: [];
|
|
62
|
+
const seedCollectCount = Math.max(0, Number(params.seedCollectCount || 0) || 0);
|
|
63
|
+
const seedCollectMaxRounds = Math.max(0, Number(params.seedCollectMaxRounds || 0) || 0);
|
|
64
|
+
const seedCollectStep = Math.max(120, Number(params.seedCollectStep || 360) || 360);
|
|
65
|
+
const seedCollectSettleMs = Math.max(100, Number(params.seedCollectSettleMs || 260) || 260);
|
|
66
|
+
const seedResetToTop = params.seedResetToTop !== false;
|
|
67
|
+
const nextSeekRounds = Math.max(0, Number(params.nextSeekRounds || 8) || 8);
|
|
68
|
+
const nextSeekStep = Math.max(0, Number(params.nextSeekStep || 0) || 0);
|
|
69
|
+
const nextSeekSettleMs = Math.max(120, Number(params.nextSeekSettleMs || 320) || 320);
|
|
57
70
|
|
|
58
71
|
return `(async () => {
|
|
59
72
|
const STATE_KEY = '__camoXhsState';
|
|
@@ -91,36 +104,103 @@ export function buildOpenDetailScript(params = {}) {
|
|
|
91
104
|
const requestedKeyword = ${JSON.stringify(keyword)};
|
|
92
105
|
const mode = ${JSON.stringify(mode)};
|
|
93
106
|
const previousKeyword = String(state.keyword || '').trim();
|
|
107
|
+
const keywordChanged = Boolean(requestedKeyword && previousKeyword && requestedKeyword !== previousKeyword);
|
|
94
108
|
if (mode === 'first') {
|
|
109
|
+
if (!${resume ? 'true' : 'false'} || keywordChanged) {
|
|
110
|
+
state.visitedNoteIds = [];
|
|
111
|
+
}
|
|
112
|
+
} else if (keywordChanged) {
|
|
95
113
|
state.visitedNoteIds = [];
|
|
96
|
-
} else if (requestedKeyword && previousKeyword && requestedKeyword !== previousKeyword) {
|
|
97
|
-
state.visitedNoteIds = [];
|
|
98
114
|
}
|
|
99
|
-
|
|
115
|
+
const requestedMaxNotes = Number(${maxNotes});
|
|
116
|
+
if (mode === 'first') {
|
|
117
|
+
if (${incrementalMax ? 'true' : 'false'} && ${resume ? 'true' : 'false'} && !keywordChanged) {
|
|
118
|
+
state.maxNotes = Number(state.visitedNoteIds.length || 0) + requestedMaxNotes;
|
|
119
|
+
} else {
|
|
120
|
+
state.maxNotes = requestedMaxNotes;
|
|
121
|
+
}
|
|
122
|
+
} else if (!Number.isFinite(Number(state.maxNotes)) || Number(state.maxNotes) <= 0) {
|
|
123
|
+
state.maxNotes = requestedMaxNotes;
|
|
124
|
+
}
|
|
100
125
|
if (requestedKeyword) state.keyword = requestedKeyword;
|
|
101
126
|
|
|
102
127
|
if (mode === 'next' && state.visitedNoteIds.length >= state.maxNotes) {
|
|
103
128
|
throw new Error('AUTOSCRIPT_DONE_MAX_NOTES');
|
|
104
129
|
}
|
|
105
130
|
|
|
106
|
-
const
|
|
131
|
+
const excludedNoteIds = new Set(${JSON.stringify(excludeNoteIds)});
|
|
132
|
+
const mapNodes = () => Array.from(document.querySelectorAll('.note-item'))
|
|
107
133
|
.map((item, index) => {
|
|
108
134
|
const cover = item.querySelector('a.cover');
|
|
109
135
|
if (!cover) return null;
|
|
110
136
|
const href = String(cover.getAttribute('href') || '').trim();
|
|
111
|
-
const
|
|
137
|
+
const lastSegment = href.split('/').filter(Boolean).pop() || '';
|
|
138
|
+
const normalized = lastSegment.split('?')[0].split('#')[0];
|
|
139
|
+
const noteId = normalized || ('idx_' + index);
|
|
112
140
|
return { cover, href, noteId };
|
|
113
141
|
})
|
|
114
142
|
.filter(Boolean);
|
|
143
|
+
let nodes = mapNodes();
|
|
144
|
+
const seedCollectedSet = new Set();
|
|
145
|
+
const seedCollectEnabled = mode === 'first'
|
|
146
|
+
&& Number(${seedCollectCount}) > 0
|
|
147
|
+
&& Number(${seedCollectMaxRounds}) > 0;
|
|
148
|
+
if (seedCollectEnabled) {
|
|
149
|
+
const collectVisible = () => {
|
|
150
|
+
for (const row of mapNodes()) {
|
|
151
|
+
if (!row || !row.noteId) continue;
|
|
152
|
+
seedCollectedSet.add(row.noteId);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
collectVisible();
|
|
156
|
+
const maxRounds = Number(${seedCollectMaxRounds});
|
|
157
|
+
const targetCount = Number(${seedCollectCount});
|
|
158
|
+
for (let round = 0; round < maxRounds && seedCollectedSet.size < targetCount; round += 1) {
|
|
159
|
+
window.scrollBy({ top: Number(${seedCollectStep}), left: 0, behavior: 'auto' });
|
|
160
|
+
await new Promise((resolve) => setTimeout(resolve, Number(${seedCollectSettleMs})));
|
|
161
|
+
collectVisible();
|
|
162
|
+
}
|
|
163
|
+
if (${seedResetToTop ? 'true' : 'false'}) {
|
|
164
|
+
window.scrollTo({ top: 0, behavior: 'auto' });
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, Number(${seedCollectSettleMs})));
|
|
166
|
+
}
|
|
167
|
+
nodes = mapNodes();
|
|
168
|
+
}
|
|
115
169
|
if (nodes.length === 0) throw new Error('NO_SEARCH_RESULT_ITEM');
|
|
116
170
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
171
|
+
const isEligible = (row) => (
|
|
172
|
+
row
|
|
173
|
+
&& row.noteId
|
|
174
|
+
&& !excludedNoteIds.has(row.noteId)
|
|
175
|
+
&& !state.visitedNoteIds.includes(row.noteId)
|
|
176
|
+
);
|
|
177
|
+
const resolveSeekStep = () => {
|
|
178
|
+
const configured = Number(${nextSeekStep});
|
|
179
|
+
if (Number.isFinite(configured) && configured > 0) return configured;
|
|
180
|
+
const viewportHeight = Math.max(
|
|
181
|
+
Number(window.innerHeight || 0) || 0,
|
|
182
|
+
Number(document.documentElement?.clientHeight || 0) || 0,
|
|
183
|
+
);
|
|
184
|
+
return Math.max(240, Math.floor(viewportHeight * 0.9));
|
|
185
|
+
};
|
|
186
|
+
const seekStep = resolveSeekStep();
|
|
187
|
+
|
|
188
|
+
let next = nodes.find((row) => isEligible(row));
|
|
189
|
+
if (!next) {
|
|
190
|
+
let stagnantRounds = 0;
|
|
191
|
+
for (let round = 0; !next && round < Number(${nextSeekRounds}); round += 1) {
|
|
192
|
+
const beforeTop = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
|
193
|
+
window.scrollBy({ top: seekStep, left: 0, behavior: 'auto' });
|
|
194
|
+
await new Promise((resolve) => setTimeout(resolve, Number(${nextSeekSettleMs})));
|
|
195
|
+
nodes = mapNodes();
|
|
196
|
+
next = nodes.find((row) => isEligible(row));
|
|
197
|
+
const afterTop = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
|
198
|
+
if (Math.abs(afterTop - beforeTop) < 2) stagnantRounds += 1;
|
|
199
|
+
else stagnantRounds = 0;
|
|
200
|
+
if (stagnantRounds >= 2) break;
|
|
201
|
+
}
|
|
123
202
|
}
|
|
203
|
+
if (!next) throw new Error('AUTOSCRIPT_DONE_NO_MORE_NOTES');
|
|
124
204
|
|
|
125
205
|
const detailSelectors = [
|
|
126
206
|
'.note-detail-mask',
|
|
@@ -176,6 +256,9 @@ export function buildOpenDetailScript(params = {}) {
|
|
|
176
256
|
openByClick: true,
|
|
177
257
|
beforeUrl,
|
|
178
258
|
afterUrl,
|
|
259
|
+
excludedCount: excludedNoteIds.size,
|
|
260
|
+
seedCollectedCount: seedCollectedSet.size,
|
|
261
|
+
seedCollectedNoteIds: Array.from(seedCollectedSet),
|
|
179
262
|
};
|
|
180
263
|
})()`;
|
|
181
264
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
1
3
|
import { asErrorPayload } from '../../container/runtime-core/utils.mjs';
|
|
2
4
|
import {
|
|
3
5
|
createEvaluateHandler,
|
|
@@ -21,6 +23,210 @@ import {
|
|
|
21
23
|
} from './xhs/persistence.mjs';
|
|
22
24
|
import { buildOpenDetailScript, buildSubmitSearchScript } from './xhs/search.mjs';
|
|
23
25
|
|
|
26
|
+
const XHS_OPERATION_LOCKS = new Map();
|
|
27
|
+
|
|
28
|
+
function toLockKey(text, fallback = '') {
|
|
29
|
+
const value = String(text || '').trim();
|
|
30
|
+
return value || fallback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function withSerializedLock(lockKey, fn) {
|
|
34
|
+
const key = toLockKey(lockKey);
|
|
35
|
+
if (!key) return fn();
|
|
36
|
+
const previous = XHS_OPERATION_LOCKS.get(key) || Promise.resolve();
|
|
37
|
+
let release;
|
|
38
|
+
const gate = new Promise((resolve) => {
|
|
39
|
+
release = resolve;
|
|
40
|
+
});
|
|
41
|
+
XHS_OPERATION_LOCKS.set(key, previous.catch(() => null).then(() => gate));
|
|
42
|
+
await previous.catch(() => null);
|
|
43
|
+
try {
|
|
44
|
+
return await fn();
|
|
45
|
+
} finally {
|
|
46
|
+
release();
|
|
47
|
+
if (XHS_OPERATION_LOCKS.get(key) === gate) XHS_OPERATION_LOCKS.delete(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeNoteIdList(items) {
|
|
52
|
+
if (!Array.isArray(items)) return [];
|
|
53
|
+
const out = [];
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
for (const item of items) {
|
|
56
|
+
const noteId = String(item || '').trim();
|
|
57
|
+
if (!noteId || seen.has(noteId)) continue;
|
|
58
|
+
seen.add(noteId);
|
|
59
|
+
out.push(noteId);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function resolveSharedClaimPath(params = {}) {
|
|
65
|
+
const raw = String(params.sharedHarvestPath || params.sharedClaimPath || '').trim();
|
|
66
|
+
return raw ? path.resolve(raw) : '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveSearchLockKey(params = {}) {
|
|
70
|
+
const raw = String(params.searchSerialKey || params.searchLockKey || '').trim();
|
|
71
|
+
if (raw) return raw;
|
|
72
|
+
const claimPath = resolveSharedClaimPath(params);
|
|
73
|
+
return claimPath ? `claim:${claimPath}` : '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function loadSharedClaimDoc(filePath) {
|
|
77
|
+
if (!filePath) {
|
|
78
|
+
return { noteIds: [], byNoteId: {}, updatedAt: null };
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const raw = await fsp.readFile(filePath, 'utf8');
|
|
82
|
+
const parsed = JSON.parse(raw);
|
|
83
|
+
const noteIds = normalizeNoteIdList(parsed?.noteIds);
|
|
84
|
+
const byNoteId = parsed?.byNoteId && typeof parsed.byNoteId === 'object' ? parsed.byNoteId : {};
|
|
85
|
+
return {
|
|
86
|
+
noteIds,
|
|
87
|
+
byNoteId,
|
|
88
|
+
updatedAt: parsed?.updatedAt || null,
|
|
89
|
+
};
|
|
90
|
+
} catch {
|
|
91
|
+
return { noteIds: [], byNoteId: {}, updatedAt: null };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function saveSharedClaimDoc(filePath, doc) {
|
|
96
|
+
if (!filePath) return;
|
|
97
|
+
const noteIds = normalizeNoteIdList(doc?.noteIds);
|
|
98
|
+
const payload = {
|
|
99
|
+
updatedAt: new Date().toISOString(),
|
|
100
|
+
noteIds,
|
|
101
|
+
byNoteId: doc?.byNoteId && typeof doc.byNoteId === 'object' ? doc.byNoteId : {},
|
|
102
|
+
};
|
|
103
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
104
|
+
await fsp.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function executeSubmitSearchOperation({ profileId, params = {} }) {
|
|
108
|
+
const script = buildSubmitSearchScript(params);
|
|
109
|
+
const highlight = params.highlight !== false;
|
|
110
|
+
const lockKey = resolveSearchLockKey(params);
|
|
111
|
+
return withSerializedLock(lockKey ? `xhs_submit_search:${lockKey}` : '', () => evaluateWithScript({
|
|
112
|
+
profileId,
|
|
113
|
+
script,
|
|
114
|
+
message: 'xhs_submit_search done',
|
|
115
|
+
highlight,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function executeOpenDetailOperation({ profileId, params = {} }) {
|
|
120
|
+
const highlight = params.highlight !== false;
|
|
121
|
+
const claimPath = resolveSharedClaimPath(params);
|
|
122
|
+
const lockKey = claimPath ? `xhs_open_detail:${claimPath}` : '';
|
|
123
|
+
|
|
124
|
+
const mapOpenDetailError = (err, paramsRef = {}) => {
|
|
125
|
+
const message = String(err?.message || err || '');
|
|
126
|
+
const mode = String(paramsRef?.mode || '').trim().toLowerCase();
|
|
127
|
+
if (message.includes('AUTOSCRIPT_DONE_NO_MORE_NOTES')) {
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
code: 'AUTOSCRIPT_DONE_NO_MORE_NOTES',
|
|
131
|
+
message: 'no more notes',
|
|
132
|
+
data: { stopReason: 'no_more_notes' },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (message.includes('NO_SEARCH_RESULT_ITEM')) {
|
|
136
|
+
if (mode === 'first') return null;
|
|
137
|
+
return {
|
|
138
|
+
ok: true,
|
|
139
|
+
code: 'OPERATION_SKIPPED_NO_SEARCH_RESULT_ITEM',
|
|
140
|
+
message: 'search result item missing',
|
|
141
|
+
data: { skipped: true },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const runWithExclude = async (excludeNoteIds) => {
|
|
148
|
+
const script = buildOpenDetailScript({
|
|
149
|
+
...params,
|
|
150
|
+
excludeNoteIds,
|
|
151
|
+
});
|
|
152
|
+
const operationResult = await evaluateWithScript({
|
|
153
|
+
profileId,
|
|
154
|
+
script,
|
|
155
|
+
message: 'xhs_open_detail done',
|
|
156
|
+
highlight,
|
|
157
|
+
});
|
|
158
|
+
const payload = extractEvaluateResultData(operationResult.data) || {};
|
|
159
|
+
return {
|
|
160
|
+
operationResult,
|
|
161
|
+
payload: payload && typeof payload === 'object' ? payload : {},
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (!claimPath) {
|
|
166
|
+
try {
|
|
167
|
+
const { operationResult } = await runWithExclude([]);
|
|
168
|
+
return operationResult;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
const mapped = mapOpenDetailError(err, params);
|
|
171
|
+
if (mapped) return mapped;
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const runLocked = async () => {
|
|
177
|
+
const claimDoc = await loadSharedClaimDoc(claimPath);
|
|
178
|
+
const excludeNoteIds = normalizeNoteIdList(claimDoc.noteIds);
|
|
179
|
+
const { operationResult, payload } = await runWithExclude(excludeNoteIds);
|
|
180
|
+
|
|
181
|
+
const claimSet = new Set(excludeNoteIds);
|
|
182
|
+
const claimAdded = [];
|
|
183
|
+
const markClaim = (noteId, source = 'open_detail') => {
|
|
184
|
+
const id = String(noteId || '').trim();
|
|
185
|
+
if (!id || claimSet.has(id)) return;
|
|
186
|
+
claimSet.add(id);
|
|
187
|
+
claimAdded.push(id);
|
|
188
|
+
claimDoc.byNoteId[id] = {
|
|
189
|
+
noteId: id,
|
|
190
|
+
profileId,
|
|
191
|
+
source,
|
|
192
|
+
ts: new Date().toISOString(),
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const seeded = normalizeNoteIdList(payload.seedCollectedNoteIds);
|
|
197
|
+
for (const noteId of seeded) markClaim(noteId, 'seed_collect');
|
|
198
|
+
if (payload.opened === true) markClaim(payload.noteId, 'open_detail');
|
|
199
|
+
claimDoc.noteIds = Array.from(claimSet);
|
|
200
|
+
if (claimAdded.length > 0) {
|
|
201
|
+
await saveSharedClaimDoc(claimPath, claimDoc);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const mergedPayload = {
|
|
205
|
+
...payload,
|
|
206
|
+
sharedClaimPath: claimPath,
|
|
207
|
+
sharedClaimCount: claimDoc.noteIds.length,
|
|
208
|
+
sharedClaimAdded: claimAdded,
|
|
209
|
+
dedupExcluded: excludeNoteIds.length,
|
|
210
|
+
};
|
|
211
|
+
const mergedData = operationResult.data && typeof operationResult.data === 'object'
|
|
212
|
+
? { ...operationResult.data, result: mergedPayload }
|
|
213
|
+
: { result: mergedPayload };
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
...operationResult,
|
|
217
|
+
data: mergedData,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
return await withSerializedLock(lockKey, runLocked);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
const mapped = mapOpenDetailError(err, params);
|
|
225
|
+
if (mapped) return mapped;
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
24
230
|
function buildReadStateScript() {
|
|
25
231
|
return `(() => {
|
|
26
232
|
const state = window.__camoXhsState || {};
|
|
@@ -108,8 +314,8 @@ async function executeCommentsHarvestOperation({ profileId, params = {} }) {
|
|
|
108
314
|
|
|
109
315
|
const XHS_ACTION_HANDLERS = {
|
|
110
316
|
raise_error: handleRaiseError,
|
|
111
|
-
xhs_submit_search:
|
|
112
|
-
xhs_open_detail:
|
|
317
|
+
xhs_submit_search: executeSubmitSearchOperation,
|
|
318
|
+
xhs_open_detail: executeOpenDetailOperation,
|
|
113
319
|
xhs_detail_harvest: createEvaluateHandler('xhs_detail_harvest done', buildDetailHarvestScript),
|
|
114
320
|
xhs_expand_replies: createEvaluateHandler('xhs_expand_replies done', buildExpandRepliesScript),
|
|
115
321
|
xhs_comments_harvest: executeCommentsHarvestOperation,
|
|
@@ -359,6 +359,11 @@ export class AutoscriptRunner {
|
|
|
359
359
|
|
|
360
360
|
resolveTimeoutMs(operation) {
|
|
361
361
|
const pacing = this.resolvePacing(operation);
|
|
362
|
+
const disableTimeout = Boolean(
|
|
363
|
+
operation?.disableTimeout ?? this.script?.defaults?.disableTimeout,
|
|
364
|
+
);
|
|
365
|
+
if (disableTimeout) return 0;
|
|
366
|
+
if (pacing.timeoutMs === 0) return 0;
|
|
362
367
|
if (Number.isFinite(pacing.timeoutMs) && pacing.timeoutMs > 0) return pacing.timeoutMs;
|
|
363
368
|
return this.getDefaultTimeoutMs(operation);
|
|
364
369
|
}
|
|
@@ -778,8 +783,9 @@ export class AutoscriptRunner {
|
|
|
778
783
|
};
|
|
779
784
|
}
|
|
780
785
|
|
|
786
|
+
const failureStatus = operation?.onFailure === 'continue' ? 'skipped' : 'failed';
|
|
781
787
|
this.operationState.set(operation.id, {
|
|
782
|
-
status:
|
|
788
|
+
status: failureStatus,
|
|
783
789
|
runs: Number(this.operationState.get(operation.id)?.runs || 0) + 1,
|
|
784
790
|
lastError: result.message || 'operation failed',
|
|
785
791
|
updatedAt: nowIso(),
|