@web-auto/webauto 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/desktop-console/default-settings.json +2 -2
- package/apps/desktop-console/dist/main/index.mjs +983 -128
- 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 +2423 -469
- 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 +256 -31
- 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
|
@@ -422,9 +422,7 @@ ${mergedOutput}`);
|
|
|
422
422
|
String(timeoutSec),
|
|
423
423
|
"--check-interval-sec",
|
|
424
424
|
"2",
|
|
425
|
-
"--keep-session"
|
|
426
|
-
...ctx2.settings?.unifiedApiUrl ? ["--unified-api", String(ctx2.settings.unifiedApiUrl)] : [],
|
|
427
|
-
...ctx2.settings?.browserServiceUrl ? ["--browser-service", String(ctx2.settings.browserServiceUrl)] : []
|
|
425
|
+
"--keep-session"
|
|
428
426
|
]);
|
|
429
427
|
await window.api.cmdSpawn({
|
|
430
428
|
title: `profilepool login-profile ${createdProfileId}`,
|
|
@@ -444,8 +442,6 @@ ${mergedOutput}`);
|
|
|
444
442
|
window.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
445
443
|
"login",
|
|
446
444
|
kw,
|
|
447
|
-
...ctx2.settings?.unifiedApiUrl ? ["--unified-api", String(ctx2.settings.unifiedApiUrl)] : [],
|
|
448
|
-
...ctx2.settings?.browserServiceUrl ? ["--browser-service", String(ctx2.settings.browserServiceUrl)] : [],
|
|
449
445
|
"--timeout-sec",
|
|
450
446
|
String(timeoutSec),
|
|
451
447
|
...ensureCount > 0 ? ["--ensure-count", String(ensureCount)] : [],
|
|
@@ -497,7 +493,7 @@ function renderRun(root, ctx2) {
|
|
|
497
493
|
const targetInput = createEl("input", { value: String(ctx2.settings?.defaultTarget || ""), placeholder: "target", type: "number", min: "1" });
|
|
498
494
|
const envSel = createEl("select");
|
|
499
495
|
["debug", "prod"].forEach((x) => envSel.appendChild(createEl("option", { value: x }, [x])));
|
|
500
|
-
envSel.value = ctx2.settings?.defaultEnv || "
|
|
496
|
+
envSel.value = ctx2.settings?.defaultEnv || "prod";
|
|
501
497
|
const dryRun = createEl("input", { type: "checkbox" });
|
|
502
498
|
dryRun.checked = ctx2.settings?.defaultDryRun === true;
|
|
503
499
|
const profileModeSel = createEl("select");
|
|
@@ -702,8 +698,6 @@ function renderRun(root, ctx2) {
|
|
|
702
698
|
return;
|
|
703
699
|
}
|
|
704
700
|
}
|
|
705
|
-
const targetNum = Number(target);
|
|
706
|
-
persistRunInputs({ keyword, target: Number.isFinite(targetNum) ? targetNum : void 0, env, dryRun: dryRun.checked });
|
|
707
701
|
const common = buildArgs2([
|
|
708
702
|
...keyword ? ["--keyword", keyword] : [],
|
|
709
703
|
...target ? ["--target", target] : [],
|
|
@@ -716,6 +710,8 @@ function renderRun(root, ctx2) {
|
|
|
716
710
|
return;
|
|
717
711
|
}
|
|
718
712
|
const profileArgs = resolved.args;
|
|
713
|
+
const targetNum = Number(target);
|
|
714
|
+
persistRunInputs({ keyword, target: Number.isFinite(targetNum) ? targetNum : void 0, env, dryRun: dryRun.checked });
|
|
719
715
|
const extraArgs = extra ? extra.split(" ").filter(Boolean) : [];
|
|
720
716
|
let script = "";
|
|
721
717
|
let args = [];
|
|
@@ -1019,7 +1015,7 @@ function renderSettings(root, ctx2) {
|
|
|
1019
1015
|
const download = createEl("input", { value: ctx2.settings?.downloadRoot || "" });
|
|
1020
1016
|
const env = createEl("select");
|
|
1021
1017
|
["debug", "prod"].forEach((x) => env.appendChild(createEl("option", { value: x }, [x])));
|
|
1022
|
-
env.value = ctx2.settings?.defaultEnv || "
|
|
1018
|
+
env.value = ctx2.settings?.defaultEnv || "prod";
|
|
1023
1019
|
const keyword = createEl("input", { value: ctx2.settings?.defaultKeyword || "" });
|
|
1024
1020
|
const loginTimeout = createEl("input", { value: String(ctx2.settings?.timeouts?.loginTimeoutSec || 900), type: "number", min: "30" });
|
|
1025
1021
|
const cmdTimeout = createEl("input", { value: String(ctx2.settings?.timeouts?.cmdTimeoutSec || 0), type: "number", min: "0" });
|
|
@@ -1518,6 +1514,7 @@ function normalizeRow(row) {
|
|
|
1518
1514
|
return {
|
|
1519
1515
|
profileId,
|
|
1520
1516
|
accountRecordId: asText(row?.accountRecordId),
|
|
1517
|
+
platform: asText(row?.platform) || "xiaohongshu",
|
|
1521
1518
|
accountId: asText(row?.accountId),
|
|
1522
1519
|
alias: asText(row?.alias),
|
|
1523
1520
|
name: asText(row?.name),
|
|
@@ -1542,9 +1539,10 @@ async function listAccountProfiles(api) {
|
|
|
1542
1539
|
// src/renderer/tabs-new/setup-wizard.mts
|
|
1543
1540
|
function renderSetupWizard(root, ctx2) {
|
|
1544
1541
|
root.innerHTML = "";
|
|
1542
|
+
const autoSyncTimers = /* @__PURE__ */ new Map();
|
|
1545
1543
|
const header = createEl("div", { style: "margin-bottom:20px;" }, [
|
|
1546
1544
|
createEl("h2", { style: "margin:0 0 8px 0; font-size:20px; color:#dbeafe;" }, ["\u73AF\u5883\u4E0E\u8D26\u6237\u521D\u59CB\u5316"]),
|
|
1547
|
-
createEl("div", { className: "muted", style: "font-size:13px;" }, [
|
|
1545
|
+
createEl("div", { className: "muted", style: "font-size:13px;" }, ["\u5EFA\u8BAE\u5148\u5B8C\u6210\u73AF\u5883\u68C0\u67E5\uFF1B\u8D26\u53F7\u53EF\u5148\u4E0D\u767B\u5F55\uFF0C\u540E\u7EED\u81EA\u52A8\u8BC6\u522B\u8D26\u6237\u540D\u5E76\u56DE\u586B alias"])
|
|
1548
1546
|
]);
|
|
1549
1547
|
root.appendChild(header);
|
|
1550
1548
|
const bentoGrid = createEl("div", { className: "bento-grid bento-sidebar" });
|
|
@@ -1552,42 +1550,60 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1552
1550
|
envCard.innerHTML = `
|
|
1553
1551
|
<div class="bento-title"><span style="color: var(--warning);">\u25CF</span> \u73AF\u5883\u68C0\u67E5</div>
|
|
1554
1552
|
<div class="env-status-grid">
|
|
1555
|
-
<div class="env-item" id="env-camo">
|
|
1556
|
-
<span
|
|
1557
|
-
|
|
1553
|
+
<div class="env-item" id="env-camo" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
|
|
1554
|
+
<span style="display:flex; align-items:center; gap:8px; min-width:0;">
|
|
1555
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1556
|
+
<span class="env-label">Camo CLI (@web-auto/camo)</span>
|
|
1557
|
+
</span>
|
|
1558
|
+
<button id="repair-camo-btn" class="secondary" style="display:none; flex:0 0 auto;">\u4E00\u952E\u4FEE\u590D</button>
|
|
1558
1559
|
</div>
|
|
1559
|
-
<div class="env-item" id="env-unified">
|
|
1560
|
-
<span
|
|
1561
|
-
|
|
1560
|
+
<div class="env-item" id="env-unified" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
|
|
1561
|
+
<span style="display:flex; align-items:center; gap:8px; min-width:0;">
|
|
1562
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1563
|
+
<span class="env-label">Unified API (7701)</span>
|
|
1564
|
+
</span>
|
|
1565
|
+
<button id="repair-core-btn" class="secondary" style="display:none; flex:0 0 auto;">\u4E00\u952E\u4FEE\u590D</button>
|
|
1562
1566
|
</div>
|
|
1563
|
-
<div class="env-item" id="env-browser">
|
|
1564
|
-
<span
|
|
1565
|
-
|
|
1567
|
+
<div class="env-item" id="env-browser" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
|
|
1568
|
+
<span style="display:flex; align-items:center; gap:8px; min-width:0;">
|
|
1569
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1570
|
+
<span class="env-label">Camo Runtime Service (7704\uFF0C\u53EF\u9009)</span>
|
|
1571
|
+
</span>
|
|
1572
|
+
<button id="repair-core2-btn" class="secondary" style="display:none; flex:0 0 auto;">\u4E00\u952E\u4FEE\u590D</button>
|
|
1566
1573
|
</div>
|
|
1567
|
-
<div class="env-item" id="env-firefox">
|
|
1568
|
-
<span
|
|
1569
|
-
|
|
1574
|
+
<div class="env-item" id="env-firefox" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
|
|
1575
|
+
<span style="display:flex; align-items:center; gap:8px; min-width:0;">
|
|
1576
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1577
|
+
<span class="env-label">Camoufox Runtime (python -m camoufox)</span>
|
|
1578
|
+
</span>
|
|
1579
|
+
<button id="repair-runtime-btn" class="secondary" style="display:none; flex:0 0 auto;">\u4E00\u952E\u4FEE\u590D</button>
|
|
1570
1580
|
</div>
|
|
1571
|
-
<div class="env-item" id="env-geoip">
|
|
1572
|
-
<span
|
|
1573
|
-
|
|
1581
|
+
<div class="env-item" id="env-geoip" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
|
|
1582
|
+
<span style="display:flex; align-items:center; gap:8px; min-width:0;">
|
|
1583
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1584
|
+
<span class="env-label">GeoIP Database\uFF08\u53EF\u9009\uFF09</span>
|
|
1585
|
+
</span>
|
|
1586
|
+
<button id="repair-geoip-btn" class="secondary" style="display:none; flex:0 0 auto;">\u53EF\u9009\u5B89\u88C5</button>
|
|
1574
1587
|
</div>
|
|
1575
1588
|
</div>
|
|
1576
1589
|
<div style="margin-top: var(--gap);">
|
|
1577
1590
|
<button id="env-check-btn" class="secondary" style="width: 100%;">\u68C0\u67E5\u73AF\u5883</button>
|
|
1591
|
+
<button id="env-repair-all-btn" style="width: 100%; margin-top: 8px; display: none;">\u4E00\u952E\u4FEE\u590D\u7F3A\u5931\u9879</button>
|
|
1592
|
+
<button id="env-reinstall-all-btn" class="secondary" style="width: 100%; margin-top: 8px;">\u4E00\u952E\u5378\u8F7D\u91CD\u88C5\u8D44\u6E90</button>
|
|
1578
1593
|
</div>
|
|
1594
|
+
<div id="env-repair-history" class="muted" style="margin-top: 10px; font-size: 12px;"></div>
|
|
1579
1595
|
`;
|
|
1580
1596
|
bentoGrid.appendChild(envCard);
|
|
1581
1597
|
const accountCard = createEl("div", { className: "bento-cell" });
|
|
1582
1598
|
accountCard.innerHTML = `
|
|
1583
1599
|
<div class="bento-title">\u8D26\u6237\u8BBE\u7F6E</div>
|
|
1584
|
-
<div class="account-list" id="account-list" style="margin-bottom: var(--gap);
|
|
1600
|
+
<div class="account-list" id="account-list" style="margin-bottom: var(--gap);">
|
|
1585
1601
|
<div style="padding:12px; text-align:center; color:#8b93a6;">\u6682\u65E0\u8D26\u6237\uFF0C\u8BF7\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u6DFB\u52A0</div>
|
|
1586
1602
|
</div>
|
|
1587
1603
|
<div class="row">
|
|
1588
1604
|
<div>
|
|
1589
|
-
<label>\u65B0\u8D26\u6237\u522B\u540D</label>
|
|
1590
|
-
<input id="new-alias-input" placeholder="\
|
|
1605
|
+
<label>\u65B0\u8D26\u6237\u522B\u540D\uFF08\u53EF\u9009\uFF09</label>
|
|
1606
|
+
<input id="new-alias-input" placeholder="\u53EF\u7559\u7A7A\uFF0C\u767B\u5F55\u540E\u81EA\u52A8\u8BC6\u522B" style="width: 200px;" />
|
|
1591
1607
|
</div>
|
|
1592
1608
|
<button id="add-account-btn" style="flex: 0 0 auto; min-width: 120px;">\u6DFB\u52A0\u8D26\u6237</button>
|
|
1593
1609
|
</div>
|
|
@@ -1604,7 +1620,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1604
1620
|
\u8BF7\u5B8C\u6210\u4E0A\u8FF0\u6B65\u9AA4
|
|
1605
1621
|
</div>
|
|
1606
1622
|
<div style="font-size: 12px; color: var(--text-3);" id="setup-status-text">
|
|
1607
|
-
\u73AF\u5883\u68C0\u67E5\
|
|
1623
|
+
\u5B8C\u6210\u73AF\u5883\u68C0\u67E5\u540E\u5373\u53EF\u8FDB\u5165\u4E3B\u754C\u9762\uFF1B\u8D26\u53F7\u53EF\u7A0D\u540E\u767B\u5F55
|
|
1608
1624
|
</div>
|
|
1609
1625
|
</div>
|
|
1610
1626
|
<button id="enter-main-btn" class="secondary" disabled style="padding: 12px 32px; font-size: 14px;">\u8FDB\u5165\u4E3B\u754C\u9762</button>
|
|
@@ -1613,16 +1629,35 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1613
1629
|
statusRow.appendChild(statusCard);
|
|
1614
1630
|
root.appendChild(statusRow);
|
|
1615
1631
|
const envCheckBtn = root.querySelector("#env-check-btn");
|
|
1632
|
+
const envRepairAllBtn = root.querySelector("#env-repair-all-btn");
|
|
1633
|
+
const envReinstallAllBtn = root.querySelector("#env-reinstall-all-btn");
|
|
1634
|
+
const repairCamoBtn = root.querySelector("#repair-camo-btn");
|
|
1635
|
+
const repairCoreBtn = root.querySelector("#repair-core-btn");
|
|
1636
|
+
const repairCore2Btn = root.querySelector("#repair-core2-btn");
|
|
1637
|
+
const repairRuntimeBtn = root.querySelector("#repair-runtime-btn");
|
|
1638
|
+
const repairGeoipBtn = root.querySelector("#repair-geoip-btn");
|
|
1616
1639
|
const addAccountBtn = root.querySelector("#add-account-btn");
|
|
1617
1640
|
const newAliasInput = root.querySelector("#new-alias-input");
|
|
1618
1641
|
const enterMainBtn = root.querySelector("#enter-main-btn");
|
|
1619
1642
|
const accountListEl = root.querySelector("#account-list");
|
|
1620
1643
|
const setupStatusText = root.querySelector("#setup-status-text");
|
|
1644
|
+
const envRepairHistoryEl = root.querySelector("#env-repair-history");
|
|
1621
1645
|
let envReady = false;
|
|
1622
1646
|
let accounts = [];
|
|
1647
|
+
let repairHistory = Array.isArray(ctx2.api?.settings?.envRepairHistory) ? [...ctx2.api.settings.envRepairHistory] : [];
|
|
1648
|
+
let envCheckInFlight = false;
|
|
1649
|
+
let accountCheckInFlight = false;
|
|
1650
|
+
let busUnsubscribe = null;
|
|
1623
1651
|
const isEnvReady = (snapshot) => Boolean(
|
|
1624
|
-
snapshot?.camo?.installed && snapshot?.services?.unifiedApi && snapshot?.
|
|
1652
|
+
snapshot?.camo?.installed && snapshot?.services?.unifiedApi && snapshot?.firefox?.installed
|
|
1625
1653
|
);
|
|
1654
|
+
const getMissing = (snapshot) => ({
|
|
1655
|
+
core: !snapshot?.services?.unifiedApi,
|
|
1656
|
+
runtimeService: !snapshot?.services?.camoRuntime,
|
|
1657
|
+
camo: !snapshot?.camo?.installed,
|
|
1658
|
+
runtime: !snapshot?.firefox?.installed,
|
|
1659
|
+
geoip: !snapshot?.geoip?.installed
|
|
1660
|
+
});
|
|
1626
1661
|
async function collectEnvironment() {
|
|
1627
1662
|
const [camo, services, firefox, geoip] = await Promise.all([
|
|
1628
1663
|
ctx2.api.envCheckCamo(),
|
|
@@ -1635,57 +1670,189 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1635
1670
|
function applyEnvironment(snapshot) {
|
|
1636
1671
|
updateEnvItem("env-camo", snapshot.camo?.installed, snapshot.camo?.version || (snapshot.camo?.installed ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5"));
|
|
1637
1672
|
updateEnvItem("env-unified", snapshot.services?.unifiedApi, "7701");
|
|
1638
|
-
updateEnvItem("env-browser", snapshot.services?.
|
|
1673
|
+
updateEnvItem("env-browser", snapshot.services?.camoRuntime, "7704");
|
|
1639
1674
|
updateEnvItem("env-firefox", snapshot.firefox?.installed, snapshot.firefox?.path ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5");
|
|
1640
|
-
updateEnvItem("env-geoip", snapshot.geoip?.installed, snapshot.geoip?.installed ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5");
|
|
1675
|
+
updateEnvItem("env-geoip", snapshot.geoip?.installed, snapshot.geoip?.installed ? "\u5DF2\u5B89\u88C5\uFF08\u53EF\u9009\uFF09" : "\u672A\u5B89\u88C5\uFF08\u53EF\u9009\uFF09");
|
|
1641
1676
|
envReady = isEnvReady(snapshot);
|
|
1677
|
+
syncRepairButtons(snapshot);
|
|
1678
|
+
}
|
|
1679
|
+
function renderRepairHistory() {
|
|
1680
|
+
if (!envRepairHistoryEl) return;
|
|
1681
|
+
if (!repairHistory.length) {
|
|
1682
|
+
envRepairHistoryEl.textContent = "\u4FEE\u590D\u8BB0\u5F55\uFF1A\u6682\u65E0";
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const lines = repairHistory.slice(-5).map((item) => {
|
|
1686
|
+
const stamp = item.ts.replace("T", " ").replace("Z", "");
|
|
1687
|
+
const status = item.ok ? "\u6210\u529F" : "\u5931\u8D25";
|
|
1688
|
+
const detail = item.detail ? ` \xB7 ${item.detail}` : "";
|
|
1689
|
+
return `${stamp} ${item.action}\uFF1A${status}${detail}`;
|
|
1690
|
+
});
|
|
1691
|
+
envRepairHistoryEl.textContent = `\u4FEE\u590D\u8BB0\u5F55\uFF1A${lines.join(" | ")}`;
|
|
1692
|
+
}
|
|
1693
|
+
async function pushRepairHistory(entry) {
|
|
1694
|
+
repairHistory = [...repairHistory, entry].slice(-30);
|
|
1695
|
+
if (typeof ctx2.api?.settingsSet === "function") {
|
|
1696
|
+
const updated = await ctx2.api.settingsSet({ envRepairHistory: repairHistory }).catch(() => null);
|
|
1697
|
+
if (updated) {
|
|
1698
|
+
ctx2.api.settings = updated;
|
|
1699
|
+
repairHistory = Array.isArray(updated.envRepairHistory) ? [...updated.envRepairHistory] : repairHistory;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
renderRepairHistory();
|
|
1642
1703
|
}
|
|
1643
|
-
async function
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1704
|
+
async function repairCoreServices() {
|
|
1705
|
+
if (typeof ctx2.api?.envRepairDeps === "function") {
|
|
1706
|
+
setupStatusText.textContent = "\u6B63\u5728\u62C9\u8D77\u6838\u5FC3\u670D\u52A1...";
|
|
1707
|
+
const res = await ctx2.api.envRepairDeps({ core: true }).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
1708
|
+
const ok = res?.ok !== false;
|
|
1709
|
+
const detail = res?.error || res?.core?.error || res?.core?.services?.error || (ok ? "" : "\u6838\u5FC3\u670D\u52A1\u542F\u52A8\u5931\u8D25");
|
|
1710
|
+
return { ok, detail };
|
|
1711
|
+
}
|
|
1712
|
+
if (typeof ctx2.api?.envRepairCore === "function") {
|
|
1651
1713
|
setupStatusText.textContent = "\u6B63\u5728\u62C9\u8D77\u6838\u5FC3\u670D\u52A1...";
|
|
1652
|
-
await ctx2.api.envRepairCore().catch(() => null);
|
|
1714
|
+
const res = await ctx2.api.envRepairCore().catch(() => null);
|
|
1715
|
+
const ok = res?.ok !== false;
|
|
1716
|
+
const detail = ok ? "" : "\u6838\u5FC3\u670D\u52A1\u542F\u52A8\u5931\u8D25";
|
|
1717
|
+
return { ok, detail };
|
|
1718
|
+
}
|
|
1719
|
+
return { ok: false, detail: "\u4E0D\u652F\u6301\u4FEE\u590D\u6838\u5FC3\u670D\u52A1" };
|
|
1720
|
+
}
|
|
1721
|
+
async function repairInstall({ browser, geoip, reinstall, uninstall }) {
|
|
1722
|
+
if (typeof ctx2.api?.envRepairDeps === "function") {
|
|
1723
|
+
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\uFF08Camoufox Runtime/GeoIP\uFF09..." : geoip && browser ? "\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56\uFF08Camoufox Runtime/GeoIP\uFF09..." : geoip ? "\u6B63\u5728\u5B89\u88C5 GeoIP\uFF08\u53EF\u9009\uFF09..." : "\u6B63\u5728\u5B89\u88C5 Camoufox Runtime...";
|
|
1724
|
+
const res = await ctx2.api.envRepairDeps({
|
|
1725
|
+
browser: Boolean(browser),
|
|
1726
|
+
geoip: Boolean(geoip),
|
|
1727
|
+
reinstall: Boolean(reinstall),
|
|
1728
|
+
uninstall: Boolean(uninstall)
|
|
1729
|
+
}).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
1730
|
+
const ok = res?.ok !== false;
|
|
1731
|
+
const detail = res?.error || res?.install?.error || res?.install?.stderr || res?.install?.stdout || res?.install?.json?.error || (ok ? "" : "\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25");
|
|
1732
|
+
return { ok, detail };
|
|
1653
1733
|
}
|
|
1654
|
-
if (
|
|
1655
|
-
setupStatusText.textContent = "\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56\uFF08Camoufox/GeoIP\uFF09...";
|
|
1734
|
+
if (typeof ctx2.api?.cmdRunJson === "function") {
|
|
1735
|
+
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\uFF08Camoufox Runtime/GeoIP\uFF09..." : geoip && browser ? "\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56\uFF08Camoufox Runtime/GeoIP\uFF09..." : geoip ? "\u6B63\u5728\u5B89\u88C5 GeoIP\uFF08\u53EF\u9009\uFF09..." : "\u6B63\u5728\u5B89\u88C5 Camoufox Runtime...";
|
|
1656
1736
|
const script = ctx2.api.pathJoin("apps", "webauto", "entry", "xhs-install.mjs");
|
|
1657
1737
|
const args = [script];
|
|
1658
|
-
if (
|
|
1659
|
-
if (
|
|
1660
|
-
args.push("--
|
|
1661
|
-
|
|
1738
|
+
if (reinstall) args.push("--reinstall");
|
|
1739
|
+
else if (uninstall) args.push("--uninstall");
|
|
1740
|
+
if (browser) args.push("--download-browser");
|
|
1741
|
+
if (geoip) args.push("--download-geoip");
|
|
1742
|
+
if (!uninstall) args.push("--ensure-backend");
|
|
1743
|
+
const res = await ctx2.api.cmdRunJson({
|
|
1662
1744
|
title: "setup auto repair",
|
|
1663
1745
|
cwd: "",
|
|
1664
1746
|
args,
|
|
1665
1747
|
timeoutMs: 3e5
|
|
1666
|
-
}).catch(() =>
|
|
1748
|
+
}).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
1749
|
+
const ok = res?.ok !== false;
|
|
1750
|
+
const detail = res?.error || (ok ? "" : "\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25");
|
|
1751
|
+
return { ok, detail };
|
|
1667
1752
|
}
|
|
1753
|
+
return { ok: false, detail: "\u4E0D\u652F\u6301\u5B89\u88C5\u4F9D\u8D56" };
|
|
1668
1754
|
}
|
|
1669
|
-
async function
|
|
1755
|
+
async function repairMissing(snapshot) {
|
|
1756
|
+
const missing = getMissing(snapshot);
|
|
1757
|
+
let ok = true;
|
|
1758
|
+
let detail = "";
|
|
1759
|
+
if (missing.core) {
|
|
1760
|
+
const res = await repairCoreServices();
|
|
1761
|
+
if (!res.ok) ok = false;
|
|
1762
|
+
if (res.detail) detail = res.detail;
|
|
1763
|
+
}
|
|
1764
|
+
if (missing.camo && !missing.core) {
|
|
1765
|
+
const res = await repairCoreServices();
|
|
1766
|
+
if (!res.ok) ok = false;
|
|
1767
|
+
if (res.detail) detail = res.detail;
|
|
1768
|
+
}
|
|
1769
|
+
if (missing.runtime) {
|
|
1770
|
+
const res = await repairInstall({ browser: true });
|
|
1771
|
+
if (!res.ok) ok = false;
|
|
1772
|
+
if (res.detail) detail = res.detail;
|
|
1773
|
+
}
|
|
1774
|
+
if (missing.geoip) {
|
|
1775
|
+
const res = await repairInstall({ geoip: true });
|
|
1776
|
+
if (!res.ok) ok = false;
|
|
1777
|
+
if (res.detail) detail = res.detail;
|
|
1778
|
+
}
|
|
1779
|
+
return { ok, detail };
|
|
1780
|
+
}
|
|
1781
|
+
function syncRepairButtons(snapshot) {
|
|
1782
|
+
const missing = getMissing(snapshot);
|
|
1783
|
+
repairCoreBtn.style.display = missing.core ? "" : "none";
|
|
1784
|
+
repairCore2Btn.style.display = missing.runtimeService ? "" : "none";
|
|
1785
|
+
repairCamoBtn.style.display = missing.camo ? "" : "none";
|
|
1786
|
+
repairRuntimeBtn.style.display = missing.runtime ? "" : "none";
|
|
1787
|
+
repairGeoipBtn.style.display = missing.geoip ? "" : "none";
|
|
1788
|
+
const hasRequiredMissing = missing.core || missing.camo || missing.runtime;
|
|
1789
|
+
envRepairAllBtn.style.display = hasRequiredMissing ? "" : "none";
|
|
1790
|
+
envRepairAllBtn.disabled = !hasRequiredMissing;
|
|
1791
|
+
}
|
|
1792
|
+
async function runRepair(label, action) {
|
|
1670
1793
|
envCheckBtn.disabled = true;
|
|
1671
|
-
|
|
1794
|
+
envRepairAllBtn.disabled = true;
|
|
1795
|
+
envReinstallAllBtn.disabled = true;
|
|
1796
|
+
repairCamoBtn.disabled = true;
|
|
1797
|
+
repairCoreBtn.disabled = true;
|
|
1798
|
+
repairCore2Btn.disabled = true;
|
|
1799
|
+
repairRuntimeBtn.disabled = true;
|
|
1800
|
+
repairGeoipBtn.disabled = true;
|
|
1801
|
+
setupStatusText.textContent = `${label}\u4E2D...`;
|
|
1802
|
+
let ok = false;
|
|
1803
|
+
let detail = "";
|
|
1672
1804
|
try {
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1675
|
-
if (
|
|
1676
|
-
|
|
1805
|
+
const result = await action();
|
|
1806
|
+
ok = result?.ok !== false;
|
|
1807
|
+
if (result?.detail) detail = String(result.detail || "");
|
|
1808
|
+
} finally {
|
|
1809
|
+
const latest = await collectEnvironment().catch(() => null);
|
|
1810
|
+
if (latest) {
|
|
1811
|
+
applyEnvironment(latest);
|
|
1812
|
+
updateCompleteStatus();
|
|
1813
|
+
if (!detail) {
|
|
1814
|
+
if (label.includes("Camoufox") || label.includes("Runtime")) {
|
|
1815
|
+
ok = Boolean(latest.firefox?.installed);
|
|
1816
|
+
} else if (label.includes("Camoufox CLI") || label.includes("CLI") || label.includes("camo")) {
|
|
1817
|
+
ok = Boolean(latest.camo?.installed);
|
|
1818
|
+
} else if (label.includes("\u6838\u5FC3")) {
|
|
1819
|
+
ok = Boolean(latest.services?.unifiedApi && latest.services?.camoRuntime);
|
|
1820
|
+
} else {
|
|
1821
|
+
ok = isEnvReady(latest);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1677
1824
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1825
|
+
setupStatusText.textContent = `${label}${ok ? "\u6210\u529F" : "\u5931\u8D25"}${detail ? `\uFF1A${detail}` : ""}`;
|
|
1826
|
+
await pushRepairHistory({
|
|
1827
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1828
|
+
action: label,
|
|
1829
|
+
ok,
|
|
1830
|
+
detail: detail || void 0
|
|
1831
|
+
});
|
|
1832
|
+
envCheckBtn.disabled = false;
|
|
1833
|
+
envReinstallAllBtn.disabled = false;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
async function checkEnvironment() {
|
|
1837
|
+
envCheckBtn.disabled = true;
|
|
1838
|
+
envCheckBtn.textContent = "\u68C0\u67E5\u4E2D...";
|
|
1839
|
+
try {
|
|
1840
|
+
const snapshot = await collectEnvironment();
|
|
1841
|
+
applyEnvironment(snapshot);
|
|
1680
1842
|
updateCompleteStatus();
|
|
1681
1843
|
if (!envReady) {
|
|
1682
1844
|
const missing = [];
|
|
1683
|
-
if (!
|
|
1684
|
-
if (!
|
|
1685
|
-
if (!
|
|
1686
|
-
|
|
1687
|
-
if (!
|
|
1688
|
-
|
|
1845
|
+
if (!snapshot?.camo?.installed) missing.push("camo-cli");
|
|
1846
|
+
if (!snapshot?.services?.unifiedApi) missing.push("unified-api");
|
|
1847
|
+
if (!snapshot?.firefox?.installed) missing.push("camoufox-runtime");
|
|
1848
|
+
setupStatusText.textContent = `\u5B58\u5728\u5F85\u4FEE\u590D\u9879: ${missing.join(", ")}`;
|
|
1849
|
+
if (!snapshot?.services?.camoRuntime) {
|
|
1850
|
+
setupStatusText.textContent += "\uFF08camo-runtime \u672A\u5C31\u7EEA\uFF0C\u5F53\u524D\u4E3A\u53EF\u9009\uFF09";
|
|
1851
|
+
}
|
|
1852
|
+
} else if (!snapshot?.geoip?.installed) {
|
|
1853
|
+
setupStatusText.textContent = "\u73AF\u5883\u5C31\u7EEA\uFF08GeoIP \u53EF\u9009\uFF0C\u672A\u5B89\u88C5\u4E0D\u5F71\u54CD\u4F7F\u7528\uFF09";
|
|
1854
|
+
} else if (!snapshot?.services?.camoRuntime) {
|
|
1855
|
+
setupStatusText.textContent = "\u73AF\u5883\u5C31\u7EEA\uFF08camo-runtime \u672A\u5C31\u7EEA\uFF0C\u5F53\u524D\u4E0D\u963B\u585E\uFF09";
|
|
1689
1856
|
}
|
|
1690
1857
|
} catch (err) {
|
|
1691
1858
|
console.error("Environment check failed:", err);
|
|
@@ -1694,16 +1861,40 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1694
1861
|
envCheckBtn.disabled = false;
|
|
1695
1862
|
envCheckBtn.textContent = "\u91CD\u65B0\u68C0\u67E5";
|
|
1696
1863
|
}
|
|
1864
|
+
async function tickEnvironment() {
|
|
1865
|
+
if (envCheckInFlight) return;
|
|
1866
|
+
envCheckInFlight = true;
|
|
1867
|
+
try {
|
|
1868
|
+
await checkEnvironment();
|
|
1869
|
+
} finally {
|
|
1870
|
+
envCheckInFlight = false;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1697
1873
|
function updateEnvItem(id, ok, detail) {
|
|
1698
1874
|
const el = root.querySelector(`#${id}`);
|
|
1699
1875
|
if (!el) return;
|
|
1700
1876
|
const icon = el.querySelector(".icon");
|
|
1701
|
-
const text = el.querySelector("
|
|
1877
|
+
const text = el.querySelector(".env-label");
|
|
1702
1878
|
const baseLabel = el.dataset.label || text.textContent || "";
|
|
1703
1879
|
el.dataset.label = baseLabel;
|
|
1704
1880
|
icon.textContent = ok ? "\u2713" : "\u2717";
|
|
1705
1881
|
icon.style.color = ok ? "var(--success)" : "var(--danger)";
|
|
1706
|
-
|
|
1882
|
+
const safeDetail = String(detail || "").trim();
|
|
1883
|
+
const shouldAppend = safeDetail && !String(baseLabel || "").includes(safeDetail);
|
|
1884
|
+
text.textContent = shouldAppend ? `${baseLabel} \xB7 ${safeDetail}` : baseLabel;
|
|
1885
|
+
}
|
|
1886
|
+
async function tickAccounts() {
|
|
1887
|
+
if (accountCheckInFlight) return;
|
|
1888
|
+
accountCheckInFlight = true;
|
|
1889
|
+
try {
|
|
1890
|
+
await refreshAccounts();
|
|
1891
|
+
const pending = accounts.filter((acc) => acc.status === "pending");
|
|
1892
|
+
for (const acc of pending) {
|
|
1893
|
+
await syncProfileAccount(acc.profileId);
|
|
1894
|
+
}
|
|
1895
|
+
} finally {
|
|
1896
|
+
accountCheckInFlight = false;
|
|
1897
|
+
}
|
|
1707
1898
|
}
|
|
1708
1899
|
async function refreshAccounts() {
|
|
1709
1900
|
try {
|
|
@@ -1739,46 +1930,56 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1739
1930
|
}
|
|
1740
1931
|
async function addAccount() {
|
|
1741
1932
|
const alias = newAliasInput.value.trim();
|
|
1742
|
-
if (!alias) {
|
|
1743
|
-
alert("\u8BF7\u8F93\u5165\u8D26\u6237\u522B\u540D");
|
|
1744
|
-
return;
|
|
1745
|
-
}
|
|
1746
1933
|
addAccountBtn.disabled = true;
|
|
1747
1934
|
addAccountBtn.textContent = "\u521B\u5EFA\u4E2D...";
|
|
1748
1935
|
try {
|
|
1749
|
-
const batchKey = "xiaohongshu";
|
|
1750
1936
|
const out = await ctx2.api.cmdRunJson({
|
|
1751
|
-
title: "
|
|
1937
|
+
title: "account add",
|
|
1752
1938
|
cwd: "",
|
|
1753
|
-
args: [
|
|
1939
|
+
args: [
|
|
1940
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
|
|
1941
|
+
"add",
|
|
1942
|
+
"--platform",
|
|
1943
|
+
"xiaohongshu",
|
|
1944
|
+
"--status",
|
|
1945
|
+
"pending",
|
|
1946
|
+
...alias ? ["--alias", alias] : [],
|
|
1947
|
+
"--json"
|
|
1948
|
+
]
|
|
1754
1949
|
});
|
|
1755
|
-
|
|
1950
|
+
const profileId = String(out?.json?.account?.profileId || "").trim();
|
|
1951
|
+
if (!out?.ok || !profileId) {
|
|
1756
1952
|
alert("\u521B\u5EFA\u8D26\u53F7\u5931\u8D25: " + (out?.error || "\u672A\u77E5\u9519\u8BEF"));
|
|
1757
1953
|
return;
|
|
1758
1954
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1955
|
+
if (alias) {
|
|
1956
|
+
const aliases = { ...ctx2.api.settings?.profileAliases, [profileId]: alias };
|
|
1957
|
+
await ctx2.api.settingsSet({ profileAliases: aliases });
|
|
1958
|
+
if (typeof ctx2.refreshSettings === "function") {
|
|
1959
|
+
await ctx2.refreshSettings();
|
|
1960
|
+
}
|
|
1764
1961
|
}
|
|
1962
|
+
await refreshAccounts();
|
|
1963
|
+
setupStatusText.textContent = `\u8D26\u53F7 ${profileId} \u5DF2\u521B\u5EFA\uFF0C\u7B49\u5F85\u767B\u5F55...`;
|
|
1765
1964
|
const timeoutSec = ctx2.api.settings?.timeouts?.loginTimeoutSec || 900;
|
|
1766
1965
|
const loginArgs = [
|
|
1767
1966
|
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
1768
1967
|
"login-profile",
|
|
1769
1968
|
profileId,
|
|
1969
|
+
"--wait-sync",
|
|
1970
|
+
"false",
|
|
1770
1971
|
"--timeout-sec",
|
|
1771
1972
|
String(timeoutSec),
|
|
1772
1973
|
"--keep-session"
|
|
1773
1974
|
];
|
|
1774
1975
|
await ctx2.api.cmdSpawn({
|
|
1775
|
-
title: `\u767B\u5F55 ${alias}`,
|
|
1976
|
+
title: `\u767B\u5F55 ${alias || profileId}`,
|
|
1776
1977
|
cwd: "",
|
|
1777
1978
|
args: loginArgs,
|
|
1778
1979
|
groupKey: "profilepool"
|
|
1779
1980
|
});
|
|
1780
1981
|
newAliasInput.value = "";
|
|
1781
|
-
|
|
1982
|
+
startAutoSyncProfile(profileId);
|
|
1782
1983
|
} catch (err) {
|
|
1783
1984
|
alert("\u6DFB\u52A0\u8D26\u53F7\u5931\u8D25: " + (err?.message || String(err)));
|
|
1784
1985
|
} finally {
|
|
@@ -1788,395 +1989,964 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1788
1989
|
}
|
|
1789
1990
|
function updateCompleteStatus() {
|
|
1790
1991
|
const hasValidAccount = accounts.some((a) => a.valid);
|
|
1791
|
-
const canProceed = envReady
|
|
1992
|
+
const canProceed = envReady;
|
|
1792
1993
|
enterMainBtn.disabled = !canProceed;
|
|
1793
1994
|
if (canProceed) {
|
|
1794
|
-
setupStatusText.textContent = `\u73AF\u5883\u5C31\u7EEA\uFF0C${accounts.length} \u4E2A\u8D26\u6237\u914D\u7F6E\u5B8C\u6210
|
|
1995
|
+
setupStatusText.textContent = hasValidAccount ? `\u73AF\u5883\u5C31\u7EEA\uFF0C${accounts.length} \u4E2A\u8D26\u6237\u914D\u7F6E\u5B8C\u6210` : "\u73AF\u5883\u5C31\u7EEA\uFF0C\u53EF\u5148\u8FDB\u5165\u4E3B\u754C\u9762\u540E\u767B\u5F55\u8D26\u53F7\uFF08alias \u5C06\u5728\u767B\u5F55\u540E\u81EA\u52A8\u8BC6\u522B\uFF09";
|
|
1795
1996
|
enterMainBtn.className = "";
|
|
1796
1997
|
} else {
|
|
1797
1998
|
const missing = [];
|
|
1798
1999
|
if (!envReady) missing.push("\u73AF\u5883\u68C0\u67E5");
|
|
1799
|
-
if (!hasValidAccount) missing.push("\
|
|
2000
|
+
if (!hasValidAccount) missing.push("\u8D26\u6237\u767B\u5F55\uFF08\u53EF\u7A0D\u540E\uFF09");
|
|
1800
2001
|
setupStatusText.textContent = `\u5C1A\u672A\u5B8C\u6210: ${missing.join("\u3001")}`;
|
|
1801
2002
|
}
|
|
1802
2003
|
}
|
|
2004
|
+
function getSettingsAlias(profileId) {
|
|
2005
|
+
return String(ctx2.api?.settings?.profileAliases?.[profileId] || "").trim();
|
|
2006
|
+
}
|
|
2007
|
+
async function upsertAliasFromProfile(profile) {
|
|
2008
|
+
const profileId = String(profile?.profileId || "").trim();
|
|
2009
|
+
const alias = String(profile?.alias || "").trim();
|
|
2010
|
+
if (!profileId || !alias) return;
|
|
2011
|
+
if (getSettingsAlias(profileId) === alias) return;
|
|
2012
|
+
const aliases = { ...ctx2.api?.settings?.profileAliases || {}, [profileId]: alias };
|
|
2013
|
+
await ctx2.api.settingsSet({ profileAliases: aliases }).catch(() => null);
|
|
2014
|
+
if (typeof ctx2.refreshSettings === "function") {
|
|
2015
|
+
await ctx2.refreshSettings().catch(() => null);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
async function syncProfileAccount(profileId) {
|
|
2019
|
+
const id = String(profileId || "").trim();
|
|
2020
|
+
if (!id) return false;
|
|
2021
|
+
const result = await ctx2.api.cmdRunJson({
|
|
2022
|
+
title: `account sync ${id}`,
|
|
2023
|
+
cwd: "",
|
|
2024
|
+
args: [
|
|
2025
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
|
|
2026
|
+
"sync",
|
|
2027
|
+
id,
|
|
2028
|
+
"--pending-while-login",
|
|
2029
|
+
"--json"
|
|
2030
|
+
],
|
|
2031
|
+
timeoutMs: 2e4
|
|
2032
|
+
}).catch(() => null);
|
|
2033
|
+
const profile = result?.json?.profile;
|
|
2034
|
+
if (!profile || String(profile.profileId || "").trim() !== id) return false;
|
|
2035
|
+
await upsertAliasFromProfile(profile);
|
|
2036
|
+
await refreshAccounts();
|
|
2037
|
+
const hasAccountId = Boolean(String(profile.accountId || "").trim());
|
|
2038
|
+
if (hasAccountId) {
|
|
2039
|
+
setupStatusText.textContent = `\u8D26\u53F7 ${id} \u5DF2\u8BC6\u522B\uFF0Calias=${String(profile.alias || "").trim() || "\u672A\u547D\u540D"}`;
|
|
2040
|
+
return true;
|
|
2041
|
+
}
|
|
2042
|
+
if (String(profile.status || "").trim() === "pending") {
|
|
2043
|
+
setupStatusText.textContent = `\u8D26\u53F7 ${id} \u5F85\u767B\u5F55\uFF0C\u7B49\u5F85\u68C0\u6D4B\u767B\u5F55\u5B8C\u6210...`;
|
|
2044
|
+
}
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
function startAutoSyncProfile(profileId) {
|
|
2048
|
+
const id = String(profileId || "").trim();
|
|
2049
|
+
if (!id) return;
|
|
2050
|
+
const existing = autoSyncTimers.get(id);
|
|
2051
|
+
if (existing) clearInterval(existing);
|
|
2052
|
+
const timeoutSec = Math.max(30, Number(ctx2.api?.settings?.timeouts?.loginTimeoutSec || 900));
|
|
2053
|
+
const intervalMs = 2e3;
|
|
2054
|
+
const maxAttempts = Math.ceil(timeoutSec * 1e3 / intervalMs);
|
|
2055
|
+
let attempts = 0;
|
|
2056
|
+
void syncProfileAccount(id);
|
|
2057
|
+
const timer = setInterval(() => {
|
|
2058
|
+
attempts += 1;
|
|
2059
|
+
void syncProfileAccount(id).then((done) => {
|
|
2060
|
+
if (done || attempts >= maxAttempts) {
|
|
2061
|
+
const current = autoSyncTimers.get(id);
|
|
2062
|
+
if (current) clearInterval(current);
|
|
2063
|
+
autoSyncTimers.delete(id);
|
|
2064
|
+
if (!done) {
|
|
2065
|
+
setupStatusText.textContent = `\u8D26\u53F7 ${id} \u767B\u5F55\u68C0\u6D4B\u8D85\u65F6\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u5728\u6D4F\u89C8\u5668\u5B8C\u6210\u767B\u5F55`;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
}, intervalMs);
|
|
2070
|
+
autoSyncTimers.set(id, timer);
|
|
2071
|
+
}
|
|
1803
2072
|
envCheckBtn.onclick = checkEnvironment;
|
|
2073
|
+
envRepairAllBtn.onclick = () => void runRepair("\u4E00\u952E\u4FEE\u590D\u7F3A\u5931\u9879", async () => {
|
|
2074
|
+
const snapshot = await collectEnvironment();
|
|
2075
|
+
return await repairMissing(snapshot);
|
|
2076
|
+
});
|
|
2077
|
+
envReinstallAllBtn.onclick = () => void runRepair("\u4E00\u952E\u5378\u8F7D\u91CD\u88C5\u8D44\u6E90", () => (async () => {
|
|
2078
|
+
const core = await repairCoreServices();
|
|
2079
|
+
if (!core.ok) return core;
|
|
2080
|
+
return repairInstall({ browser: true, geoip: true, reinstall: true });
|
|
2081
|
+
})());
|
|
2082
|
+
repairCoreBtn.onclick = () => void runRepair("\u4FEE\u590D\u6838\u5FC3\u670D\u52A1", repairCoreServices);
|
|
2083
|
+
repairCore2Btn.onclick = () => void runRepair("\u4FEE\u590D\u6838\u5FC3\u670D\u52A1", repairCoreServices);
|
|
2084
|
+
repairCamoBtn.onclick = () => void runRepair("\u4FEE\u590D Camo CLI", repairCoreServices);
|
|
2085
|
+
repairRuntimeBtn.onclick = () => void runRepair("\u4FEE\u590D Camoufox Runtime", () => repairInstall({ browser: true }));
|
|
2086
|
+
repairGeoipBtn.onclick = () => void runRepair("\u5B89\u88C5 GeoIP", () => repairInstall({ geoip: true }));
|
|
1804
2087
|
addAccountBtn.onclick = addAccount;
|
|
1805
2088
|
enterMainBtn.onclick = () => {
|
|
1806
2089
|
if (typeof ctx2.setActiveTab === "function") {
|
|
1807
2090
|
ctx2.setActiveTab("config");
|
|
1808
2091
|
}
|
|
1809
2092
|
};
|
|
1810
|
-
void
|
|
1811
|
-
void
|
|
2093
|
+
void tickEnvironment();
|
|
2094
|
+
void tickAccounts();
|
|
2095
|
+
if (typeof ctx2.api?.onBusEvent === "function") {
|
|
2096
|
+
busUnsubscribe = ctx2.api.onBusEvent((evt) => {
|
|
2097
|
+
const type = String(evt?.type || evt?.event || "").trim().toLowerCase();
|
|
2098
|
+
if (!type) return;
|
|
2099
|
+
if (type.startsWith("account:")) {
|
|
2100
|
+
void tickAccounts();
|
|
2101
|
+
}
|
|
2102
|
+
if (type.startsWith("env:")) {
|
|
2103
|
+
void tickEnvironment();
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
renderRepairHistory();
|
|
2108
|
+
return () => {
|
|
2109
|
+
for (const timer of autoSyncTimers.values()) clearInterval(timer);
|
|
2110
|
+
autoSyncTimers.clear();
|
|
2111
|
+
if (typeof busUnsubscribe === "function") busUnsubscribe();
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// src/renderer/tabs-new/schedule-task-bridge.mts
|
|
2116
|
+
var PLATFORM_TASKS = {
|
|
2117
|
+
xiaohongshu: [
|
|
2118
|
+
{ type: "xhs-unified", label: "\u641C\u7D22\u4EFB\u52A1", icon: "\u{1F4D5}", platform: "xiaohongshu" }
|
|
2119
|
+
],
|
|
2120
|
+
weibo: [
|
|
2121
|
+
{ type: "weibo-timeline", label: "\u4E3B\u9875\u65F6\u95F4\u7EBF", icon: "\u{1F4F0}", platform: "weibo" },
|
|
2122
|
+
{ type: "weibo-search", label: "\u641C\u7D22\u4EFB\u52A1", icon: "\u{1F50D}", platform: "weibo" },
|
|
2123
|
+
{ type: "weibo-monitor", label: "\u76D1\u63A7\u4E2A\u4EBA\u4E3B\u9875", icon: "\u{1F441}\uFE0F", platform: "weibo" }
|
|
2124
|
+
],
|
|
2125
|
+
"1688": [
|
|
2126
|
+
{ type: "1688-search", label: "\u641C\u7D22\u4EFB\u52A1", icon: "\u{1F6D2}", platform: "1688" }
|
|
2127
|
+
]
|
|
2128
|
+
};
|
|
2129
|
+
function normalizeScheduleType(value) {
|
|
2130
|
+
const text = String(value || "interval").trim().toLowerCase();
|
|
2131
|
+
if (text === "once" || text === "daily" || text === "weekly") return text;
|
|
2132
|
+
return "interval";
|
|
2133
|
+
}
|
|
2134
|
+
function toLocalDatetimeValue(iso) {
|
|
2135
|
+
const text = String(iso || "").trim();
|
|
2136
|
+
if (!text) return "";
|
|
2137
|
+
const ts = Date.parse(text);
|
|
2138
|
+
if (!Number.isFinite(ts)) return "";
|
|
2139
|
+
const date = new Date(ts);
|
|
2140
|
+
const yyyy = date.getFullYear();
|
|
2141
|
+
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
2142
|
+
const dd = String(date.getDate()).padStart(2, "0");
|
|
2143
|
+
const hh = String(date.getHours()).padStart(2, "0");
|
|
2144
|
+
const min = String(date.getMinutes()).padStart(2, "0");
|
|
2145
|
+
return `${yyyy}-${mm}-${dd}T${hh}:${min}`;
|
|
2146
|
+
}
|
|
2147
|
+
function toIsoOrNull(localDateTime) {
|
|
2148
|
+
const text = String(localDateTime || "").trim();
|
|
2149
|
+
if (!text) return null;
|
|
2150
|
+
const ts = Date.parse(text);
|
|
2151
|
+
if (!Number.isFinite(ts)) return null;
|
|
2152
|
+
return new Date(ts).toISOString();
|
|
2153
|
+
}
|
|
2154
|
+
function parseRunHistory(items) {
|
|
2155
|
+
if (!Array.isArray(items)) return [];
|
|
2156
|
+
return items.map((item) => ({
|
|
2157
|
+
timestamp: String(item?.timestamp || "").trim(),
|
|
2158
|
+
status: String(item?.status || "").trim() === "failure" ? "failure" : "success",
|
|
2159
|
+
durationMs: Number.isFinite(Number(item?.durationMs)) ? Math.max(0, Number(item.durationMs)) : 0
|
|
2160
|
+
})).filter((item) => item.timestamp);
|
|
2161
|
+
}
|
|
2162
|
+
function parseTaskRows(payload) {
|
|
2163
|
+
const rows = Array.isArray(payload?.tasks) ? payload.tasks : [];
|
|
2164
|
+
return rows.map((row) => ({
|
|
2165
|
+
id: String(row?.id || "").trim(),
|
|
2166
|
+
seq: Number.isFinite(Number(row?.seq)) ? Math.max(0, Math.floor(Number(row.seq))) : 0,
|
|
2167
|
+
name: String(row?.name || row?.id || "").trim(),
|
|
2168
|
+
enabled: row?.enabled !== false,
|
|
2169
|
+
scheduleType: normalizeScheduleType(row?.scheduleType),
|
|
2170
|
+
intervalMinutes: Number.isFinite(Number(row?.intervalMinutes)) ? Math.max(1, Math.floor(Number(row.intervalMinutes))) : 30,
|
|
2171
|
+
runAt: String(row?.runAt || "").trim() || null,
|
|
2172
|
+
maxRuns: Number.isFinite(Number(row?.maxRuns)) && Number(row.maxRuns) > 0 ? Math.floor(Number(row.maxRuns)) : null,
|
|
2173
|
+
nextRunAt: String(row?.nextRunAt || "").trim() || null,
|
|
2174
|
+
commandType: String(row?.commandType || "xhs-unified").trim() || "xhs-unified",
|
|
2175
|
+
commandArgv: row?.commandArgv && typeof row.commandArgv === "object" ? row.commandArgv : {},
|
|
2176
|
+
createdAt: String(row?.createdAt || "").trim() || null,
|
|
2177
|
+
updatedAt: String(row?.updatedAt || "").trim() || null,
|
|
2178
|
+
lastRunAt: String(row?.lastRunAt || "").trim() || null,
|
|
2179
|
+
lastStatus: String(row?.lastStatus || "").trim() || null,
|
|
2180
|
+
lastError: String(row?.lastError || "").trim() || null,
|
|
2181
|
+
runCount: Number(row?.runCount || 0) || 0,
|
|
2182
|
+
failCount: Number(row?.failCount || 0) || 0,
|
|
2183
|
+
runHistory: parseRunHistory(row?.runHistory)
|
|
2184
|
+
})).filter((row) => row.id);
|
|
2185
|
+
}
|
|
2186
|
+
function getTasksForPlatform(platform) {
|
|
2187
|
+
const p = platform;
|
|
2188
|
+
return PLATFORM_TASKS[p] || [];
|
|
2189
|
+
}
|
|
2190
|
+
function getPlatformForCommandType(commandType) {
|
|
2191
|
+
if (commandType.startsWith("xhs")) return "xiaohongshu";
|
|
2192
|
+
if (commandType.startsWith("weibo")) return "weibo";
|
|
2193
|
+
if (commandType.startsWith("1688")) return "1688";
|
|
2194
|
+
return "xiaohongshu";
|
|
1812
2195
|
}
|
|
1813
2196
|
|
|
1814
|
-
// src/renderer/tabs-new/
|
|
1815
|
-
|
|
2197
|
+
// src/renderer/tabs-new/tasks.mts
|
|
2198
|
+
var DEFAULT_FORM = {
|
|
2199
|
+
name: "",
|
|
2200
|
+
enabled: true,
|
|
2201
|
+
platform: "xiaohongshu",
|
|
2202
|
+
taskType: "xhs-unified",
|
|
2203
|
+
profileId: "",
|
|
2204
|
+
keyword: "",
|
|
2205
|
+
targetCount: 50,
|
|
2206
|
+
env: "debug",
|
|
2207
|
+
userId: "",
|
|
2208
|
+
collectComments: true,
|
|
2209
|
+
collectBody: true,
|
|
2210
|
+
doLikes: false,
|
|
2211
|
+
likeKeywords: "",
|
|
2212
|
+
scheduleType: "interval",
|
|
2213
|
+
intervalMinutes: 30,
|
|
2214
|
+
runAt: null,
|
|
2215
|
+
maxRuns: null
|
|
2216
|
+
};
|
|
2217
|
+
function parseSortableTime(value) {
|
|
2218
|
+
const ts = Date.parse(String(value || ""));
|
|
2219
|
+
return Number.isFinite(ts) ? ts : 0;
|
|
2220
|
+
}
|
|
2221
|
+
function isKeywordRequired(taskType) {
|
|
2222
|
+
return taskType === "xhs-unified" || taskType === "weibo-search" || taskType === "1688-search";
|
|
2223
|
+
}
|
|
2224
|
+
function commandTypeToWeiboTaskType(commandType) {
|
|
2225
|
+
if (commandType === "weibo-search") return "search";
|
|
2226
|
+
if (commandType === "weibo-monitor") return "monitor";
|
|
2227
|
+
return "timeline";
|
|
2228
|
+
}
|
|
2229
|
+
function fallbackTaskName(data) {
|
|
2230
|
+
const keyword = String(data.keyword || "").trim();
|
|
2231
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2232
|
+
return keyword ? `${data.taskType}-${keyword}` : `${data.taskType}-${stamp}`;
|
|
2233
|
+
}
|
|
2234
|
+
function renderTasksPanel(root, ctx2) {
|
|
1816
2235
|
root.innerHTML = "";
|
|
1817
2236
|
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
1818
2237
|
"\u5F53\u524D: ",
|
|
1819
|
-
createEl("span", {}, ["\
|
|
1820
|
-
" \u2192 \
|
|
1821
|
-
createEl("span", {}, ["\u770B\u677F\u9875"])
|
|
2238
|
+
createEl("span", {}, ["\u4EFB\u52A1\u7BA1\u7406"]),
|
|
2239
|
+
" \u2192 \u521B\u5EFA\u3001\u7F16\u8F91\u3001\u6267\u884C\u4EFB\u52A1"
|
|
1822
2240
|
]);
|
|
1823
2241
|
root.appendChild(pageIndicator);
|
|
1824
|
-
const
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
2242
|
+
const quotaBar = createEl("div", { className: "bento-cell", style: "margin-bottom: var(--gap); padding: var(--gap-sm);" });
|
|
2243
|
+
quotaBar.innerHTML = `
|
|
2244
|
+
<div style="display: flex; gap: var(--gap); align-items: center; flex-wrap: wrap;">
|
|
2245
|
+
<span style="font-size: 12px; color: var(--text-secondary);">\u914D\u989D\u72B6\u6001:</span>
|
|
2246
|
+
<span id="quota-search" class="quota-item" style="font-size: 11px;">\u641C\u7D22: -/-</span>
|
|
2247
|
+
<span id="quota-like" class="quota-item" style="font-size: 11px;">\u70B9\u8D5E: -/-</span>
|
|
2248
|
+
<span id="quota-comment" class="quota-item" style="font-size: 11px;">\u8BC4\u8BBA: -/-</span>
|
|
2249
|
+
<button id="quota-refresh-btn" class="secondary" style="padding: 4px 8px; font-size: 11px; height: auto;">\u5237\u65B0</button>
|
|
2250
|
+
</div>
|
|
2251
|
+
`;
|
|
2252
|
+
root.appendChild(quotaBar);
|
|
2253
|
+
const mainGrid = createEl("div", { className: "bento-grid bento-sidebar" });
|
|
2254
|
+
const formCard = createEl("div", { className: "bento-cell" });
|
|
2255
|
+
formCard.innerHTML = `
|
|
2256
|
+
<div id="task-form-title" class="bento-title">\u65B0\u5EFA\u4EFB\u52A1</div>
|
|
2257
|
+
<input type="hidden" id="task-editing-id" />
|
|
1828
2258
|
|
|
1829
2259
|
<div class="row">
|
|
1830
2260
|
<div>
|
|
1831
|
-
<label>\
|
|
1832
|
-
<
|
|
2261
|
+
<label>\u5E73\u53F0</label>
|
|
2262
|
+
<select id="task-platform" style="width: 130px;">
|
|
2263
|
+
<option value="xiaohongshu">\u{1F4D5} \u5C0F\u7EA2\u4E66</option>
|
|
2264
|
+
<option value="weibo">\u{1F4F0} \u5FAE\u535A</option>
|
|
2265
|
+
<option value="1688">\u{1F6D2} 1688</option>
|
|
2266
|
+
</select>
|
|
2267
|
+
</div>
|
|
2268
|
+
<div>
|
|
2269
|
+
<label>\u4EFB\u52A1\u7C7B\u578B</label>
|
|
2270
|
+
<select id="task-type" style="width: 140px;"></select>
|
|
2271
|
+
</div>
|
|
2272
|
+
<div>
|
|
2273
|
+
<label>\u4EFB\u52A1\u540D</label>
|
|
2274
|
+
<input id="task-name" placeholder="\u53EF\u9009\uFF0C\u4FBF\u4E8E\u8BC6\u522B" style="width: 180px;" />
|
|
1833
2275
|
</div>
|
|
1834
2276
|
</div>
|
|
1835
2277
|
|
|
1836
2278
|
<div class="row">
|
|
1837
2279
|
<div>
|
|
1838
|
-
<label>\
|
|
1839
|
-
<input id="
|
|
2280
|
+
<label>\u5173\u952E\u8BCD</label>
|
|
2281
|
+
<input id="task-keyword" placeholder="\u641C\u7D22\u5173\u952E\u8BCD" style="width: 180px;" />
|
|
2282
|
+
</div>
|
|
2283
|
+
<div>
|
|
2284
|
+
<label>\u76EE\u6807\u6570</label>
|
|
2285
|
+
<input id="task-target" type="number" min="1" value="50" style="width: 80px;" />
|
|
2286
|
+
</div>
|
|
2287
|
+
<div>
|
|
2288
|
+
<label>Profile</label>
|
|
2289
|
+
<input id="task-profile" placeholder="xiaohongshu-batch-1" style="width: 160px;" />
|
|
1840
2290
|
</div>
|
|
1841
2291
|
<div>
|
|
1842
|
-
<label>\
|
|
1843
|
-
<select id="env
|
|
1844
|
-
<option value="debug"
|
|
1845
|
-
<option value="prod"
|
|
2292
|
+
<label>\u73AF\u5883</label>
|
|
2293
|
+
<select id="task-env" style="width: 80px;">
|
|
2294
|
+
<option value="debug">debug</option>
|
|
2295
|
+
<option value="prod">prod</option>
|
|
1846
2296
|
</select>
|
|
1847
2297
|
</div>
|
|
1848
2298
|
</div>
|
|
1849
2299
|
|
|
1850
|
-
<div>
|
|
1851
|
-
<
|
|
1852
|
-
|
|
1853
|
-
<
|
|
1854
|
-
</select>
|
|
1855
|
-
</div>
|
|
1856
|
-
|
|
1857
|
-
<div style="margin-top: var(--gap); padding-top: var(--gap); border-top: 1px solid var(--border);">
|
|
1858
|
-
<div class="bento-title" style="font-size: 13px;">\u914D\u7F6E\u9884\u8BBE</div>
|
|
1859
|
-
<div class="row">
|
|
1860
|
-
<select id="preset-select" style="width: 200px;">
|
|
1861
|
-
<option value="last">\u4E0A\u6B21\u914D\u7F6E</option>
|
|
1862
|
-
<option value="full">\u9884\u8BBE1\uFF1A\u5168\u91CF\u722C\u53D6</option>
|
|
1863
|
-
<option value="body-only">\u9884\u8BBE2\uFF1A\u4EC5\u6B63\u6587</option>
|
|
1864
|
-
<option value="quick">\u9884\u8BBE3\uFF1A\u5FEB\u901F\u91C7\u96C6</option>
|
|
1865
|
-
</select>
|
|
1866
|
-
</div>
|
|
1867
|
-
<div class="btn-group">
|
|
1868
|
-
<button id="import-btn" class="secondary" style="flex: 1;">\u5BFC\u5165\u914D\u7F6E</button>
|
|
1869
|
-
<button id="export-btn" class="secondary" style="flex: 1;">\u5BFC\u51FA\u914D\u7F6E</button>
|
|
2300
|
+
<div id="task-user-id-wrap" class="row" style="display:none;">
|
|
2301
|
+
<div>
|
|
2302
|
+
<label>\u5FAE\u535A\u7528\u6237ID (monitor \u5FC5\u586B)</label>
|
|
2303
|
+
<input id="task-user-id" placeholder="\u4F8B\u5982: 1234567890" style="width: 220px;" />
|
|
1870
2304
|
</div>
|
|
1871
2305
|
</div>
|
|
1872
|
-
`;
|
|
1873
|
-
bentoGrid.appendChild(targetCard);
|
|
1874
|
-
const optionsCard = createEl("div", { className: "bento-cell" });
|
|
1875
|
-
optionsCard.innerHTML = `
|
|
1876
|
-
<div class="bento-title">\u722C\u53D6\u9009\u9879</div>
|
|
1877
2306
|
|
|
1878
|
-
<div
|
|
1879
|
-
<label style="display:
|
|
1880
|
-
<input id="
|
|
1881
|
-
<span>\
|
|
2307
|
+
<div class="row">
|
|
2308
|
+
<label style="display:flex;align-items:center;gap:6px;">
|
|
2309
|
+
<input id="task-comments" type="checkbox" checked />
|
|
2310
|
+
<span style="font-size:12px;">\u8BC4\u8BBA</span>
|
|
1882
2311
|
</label>
|
|
1883
|
-
<label style="display:
|
|
1884
|
-
<input id="
|
|
1885
|
-
<span>\
|
|
2312
|
+
<label style="display:flex;align-items:center;gap:6px;">
|
|
2313
|
+
<input id="task-body" type="checkbox" checked />
|
|
2314
|
+
<span style="font-size:12px;">\u6B63\u6587</span>
|
|
1886
2315
|
</label>
|
|
2316
|
+
<label style="display:flex;align-items:center;gap:6px;">
|
|
2317
|
+
<input id="task-likes" type="checkbox" />
|
|
2318
|
+
<span style="font-size:12px;">\u70B9\u8D5E</span>
|
|
2319
|
+
</label>
|
|
2320
|
+
<input id="task-like-keywords" placeholder="\u70B9\u8D5E\u5173\u952E\u8BCD(\u9017\u53F7\u5206\u9694)" style="flex:1; min-width:120px;" disabled />
|
|
1887
2321
|
</div>
|
|
1888
2322
|
|
|
1889
|
-
<div
|
|
1890
|
-
<div>
|
|
1891
|
-
|
|
1892
|
-
<
|
|
2323
|
+
<div style="margin-top: var(--gap); padding-top: var(--gap-sm); border-top: 1px solid var(--border);">
|
|
2324
|
+
<div style="font-size:12px; color:var(--text-secondary); margin-bottom:var(--gap-sm);">\u8C03\u5EA6\u8BBE\u7F6E\uFF08\u53EF\u9009\uFF09</div>
|
|
2325
|
+
<div class="row">
|
|
2326
|
+
<div>
|
|
2327
|
+
<select id="task-schedule-type" style="width: 100px;">
|
|
2328
|
+
<option value="interval">\u5FAA\u73AF\u95F4\u9694</option>
|
|
2329
|
+
<option value="once">\u4E00\u6B21\u6027</option>
|
|
2330
|
+
<option value="daily">\u6BCF\u5929</option>
|
|
2331
|
+
<option value="weekly">\u6BCF\u5468</option>
|
|
2332
|
+
</select>
|
|
2333
|
+
</div>
|
|
2334
|
+
<div id="task-interval-wrap">
|
|
2335
|
+
<input id="task-interval" type="number" min="1" value="30" style="width: 70px;" />
|
|
2336
|
+
<span style="font-size:11px;color:var(--text-tertiary);">\u5206\u949F</span>
|
|
2337
|
+
</div>
|
|
2338
|
+
<div id="task-runat-wrap" style="display:none;">
|
|
2339
|
+
<input id="task-runat" type="datetime-local" style="width: 160px;" />
|
|
2340
|
+
</div>
|
|
2341
|
+
<div>
|
|
2342
|
+
<input id="task-max-runs" type="number" min="1" placeholder="\u4E0D\u9650" style="width: 70px;" />
|
|
2343
|
+
<span style="font-size:11px;color:var(--text-tertiary);">\u6B21</span>
|
|
2344
|
+
</div>
|
|
1893
2345
|
</div>
|
|
1894
2346
|
</div>
|
|
1895
2347
|
|
|
1896
|
-
<div style="margin-top: var(--gap);
|
|
1897
|
-
<
|
|
1898
|
-
<
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2348
|
+
<div class="btn-group" style="margin-top: var(--gap);">
|
|
2349
|
+
<button id="task-save-btn" style="flex:1;">\u4FDD\u5B58\u4EFB\u52A1</button>
|
|
2350
|
+
<button id="task-run-btn" class="primary" style="flex:1;">\u4FDD\u5B58\u5E76\u6267\u884C</button>
|
|
2351
|
+
<button id="task-run-ephemeral-btn" class="secondary" style="flex:1;">\u4EC5\u6267\u884C(\u4E0D\u4FDD\u5B58)</button>
|
|
2352
|
+
<button id="task-reset-btn" class="secondary" style="flex:0.6;">\u91CD\u7F6E</button>
|
|
2353
|
+
</div>
|
|
2354
|
+
`;
|
|
2355
|
+
mainGrid.appendChild(formCard);
|
|
2356
|
+
const statsCard = createEl("div", { className: "bento-cell", style: "max-width: 300px;" });
|
|
2357
|
+
statsCard.innerHTML = `
|
|
2358
|
+
<div class="bento-title">\u5FEB\u901F\u72B6\u6001</div>
|
|
2359
|
+
<div id="quick-stats">
|
|
2360
|
+
<div style="margin-bottom: var(--gap-sm);">
|
|
2361
|
+
<span style="font-size:11px;color:var(--text-tertiary);">\u8FD0\u884C\u4E2D\u4EFB\u52A1</span>
|
|
2362
|
+
<div id="stat-running" style="font-size:18px;font-weight:700;color:var(--accent-success);">0</div>
|
|
2363
|
+
</div>
|
|
2364
|
+
<div style="margin-bottom: var(--gap-sm);">
|
|
2365
|
+
<span style="font-size:11px;color:var(--text-tertiary);">\u7D2F\u8BA1\u6267\u884C</span>
|
|
2366
|
+
<div id="stat-today" style="font-size:18px;font-weight:700;">0</div>
|
|
2367
|
+
</div>
|
|
1902
2368
|
<div>
|
|
1903
|
-
<
|
|
1904
|
-
<
|
|
2369
|
+
<span style="font-size:11px;color:var(--text-tertiary);">\u5DF2\u4FDD\u5B58\u4EFB\u52A1</span>
|
|
2370
|
+
<div id="stat-saved" style="font-size:18px;font-weight:700;">0</div>
|
|
1905
2371
|
</div>
|
|
1906
2372
|
</div>
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
<div class="bento-title" style="font-size: 13px;">\u9AD8\u7EA7\u9009\u9879</div>
|
|
1910
|
-
<div style="display: flex; gap: var(--gap);">
|
|
1911
|
-
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
|
1912
|
-
<input id="headless-cb" type="checkbox" />
|
|
1913
|
-
<span>Headless</span>
|
|
1914
|
-
</label>
|
|
1915
|
-
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
|
1916
|
-
<input id="dry-run-cb" type="checkbox" />
|
|
1917
|
-
<span>Dry Run</span>
|
|
1918
|
-
</label>
|
|
1919
|
-
</div>
|
|
2373
|
+
<div style="margin-top: var(--gap);">
|
|
2374
|
+
<button id="goto-scheduler-btn" class="secondary" style="width:100%;">\u67E5\u770B\u4EFB\u52A1\u5217\u8868</button>
|
|
1920
2375
|
</div>
|
|
1921
2376
|
`;
|
|
1922
|
-
|
|
1923
|
-
root.appendChild(
|
|
1924
|
-
const
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
<div style="
|
|
1928
|
-
<
|
|
2377
|
+
mainGrid.appendChild(statsCard);
|
|
2378
|
+
root.appendChild(mainGrid);
|
|
2379
|
+
const recentCard = createEl("div", { className: "bento-cell", style: "margin-top: var(--gap);" });
|
|
2380
|
+
recentCard.innerHTML = `
|
|
2381
|
+
<div class="bento-title">\u5386\u53F2\u4EFB\u52A1</div>
|
|
2382
|
+
<div class="row" style="margin-bottom: var(--gap-sm);">
|
|
2383
|
+
<select id="task-history-select" style="min-width: 320px;">
|
|
2384
|
+
<option value="">\u9009\u62E9\u5386\u53F2\u4EFB\u52A1...</option>
|
|
2385
|
+
</select>
|
|
2386
|
+
<button id="task-history-edit-btn" class="secondary">\u8F7D\u5165\u7F16\u8F91</button>
|
|
2387
|
+
<button id="task-history-clone-btn" class="secondary">\u8F7D\u5165\u53E6\u5B58</button>
|
|
2388
|
+
<button id="task-history-refresh-btn" class="secondary">\u5237\u65B0</button>
|
|
1929
2389
|
</div>
|
|
2390
|
+
<div id="recent-tasks-list"></div>
|
|
1930
2391
|
`;
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
const
|
|
1934
|
-
const
|
|
1935
|
-
const
|
|
1936
|
-
const
|
|
1937
|
-
const
|
|
1938
|
-
const
|
|
1939
|
-
const
|
|
1940
|
-
const
|
|
1941
|
-
const
|
|
1942
|
-
const
|
|
1943
|
-
const
|
|
1944
|
-
const
|
|
1945
|
-
const
|
|
1946
|
-
const
|
|
1947
|
-
const
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2392
|
+
root.appendChild(recentCard);
|
|
2393
|
+
const formTitle = formCard.querySelector("#task-form-title");
|
|
2394
|
+
const platformSelect = formCard.querySelector("#task-platform");
|
|
2395
|
+
const taskTypeSelect = formCard.querySelector("#task-type");
|
|
2396
|
+
const nameInput = formCard.querySelector("#task-name");
|
|
2397
|
+
const keywordInput = formCard.querySelector("#task-keyword");
|
|
2398
|
+
const targetInput = formCard.querySelector("#task-target");
|
|
2399
|
+
const profileInput = formCard.querySelector("#task-profile");
|
|
2400
|
+
const envSelect = formCard.querySelector("#task-env");
|
|
2401
|
+
const userIdWrap = formCard.querySelector("#task-user-id-wrap");
|
|
2402
|
+
const userIdInput = formCard.querySelector("#task-user-id");
|
|
2403
|
+
const commentsInput = formCard.querySelector("#task-comments");
|
|
2404
|
+
const bodyInput = formCard.querySelector("#task-body");
|
|
2405
|
+
const likesInput = formCard.querySelector("#task-likes");
|
|
2406
|
+
const likeKeywordsInput = formCard.querySelector("#task-like-keywords");
|
|
2407
|
+
const scheduleTypeSelect = formCard.querySelector("#task-schedule-type");
|
|
2408
|
+
const intervalInput = formCard.querySelector("#task-interval");
|
|
2409
|
+
const intervalWrap = formCard.querySelector("#task-interval-wrap");
|
|
2410
|
+
const runAtInput = formCard.querySelector("#task-runat");
|
|
2411
|
+
const runAtWrap = formCard.querySelector("#task-runat-wrap");
|
|
2412
|
+
const maxRunsInput = formCard.querySelector("#task-max-runs");
|
|
2413
|
+
const editingIdInput = formCard.querySelector("#task-editing-id");
|
|
2414
|
+
const saveBtn = formCard.querySelector("#task-save-btn");
|
|
2415
|
+
const runBtn = formCard.querySelector("#task-run-btn");
|
|
2416
|
+
const runEphemeralBtn = formCard.querySelector("#task-run-ephemeral-btn");
|
|
2417
|
+
const resetBtn = formCard.querySelector("#task-reset-btn");
|
|
2418
|
+
const quotaRefreshBtn = quotaBar.querySelector("#quota-refresh-btn");
|
|
2419
|
+
const gotoSchedulerBtn = statsCard.querySelector("#goto-scheduler-btn");
|
|
2420
|
+
const historySelect = recentCard.querySelector("#task-history-select");
|
|
2421
|
+
const historyEditBtn = recentCard.querySelector("#task-history-edit-btn");
|
|
2422
|
+
const historyCloneBtn = recentCard.querySelector("#task-history-clone-btn");
|
|
2423
|
+
const historyRefreshBtn = recentCard.querySelector("#task-history-refresh-btn");
|
|
2424
|
+
const recentTasksList = recentCard.querySelector("#recent-tasks-list");
|
|
2425
|
+
const statRunning = statsCard.querySelector("#stat-running");
|
|
2426
|
+
const statToday = statsCard.querySelector("#stat-today");
|
|
2427
|
+
const statSaved = statsCard.querySelector("#stat-saved");
|
|
2428
|
+
let tasks = [];
|
|
2429
|
+
const activeRunIds = /* @__PURE__ */ new Set();
|
|
2430
|
+
let unsubscribeActiveRuns = null;
|
|
2431
|
+
const joinPath2 = (...parts) => {
|
|
2432
|
+
if (typeof ctx2?.api?.pathJoin === "function") return ctx2.api.pathJoin(...parts);
|
|
2433
|
+
return parts.filter(Boolean).join("/");
|
|
2434
|
+
};
|
|
2435
|
+
const scheduleScript = joinPath2("apps", "webauto", "entry", "schedule.mjs");
|
|
2436
|
+
const quotaScript = joinPath2("apps", "webauto", "entry", "lib", "quota-status.mjs");
|
|
2437
|
+
const xhsScript = joinPath2("apps", "webauto", "entry", "xhs-unified.mjs");
|
|
2438
|
+
const weiboScript = joinPath2("apps", "webauto", "entry", "weibo-unified.mjs");
|
|
2439
|
+
function getTaskById(taskId) {
|
|
2440
|
+
const id = String(taskId || "").trim();
|
|
2441
|
+
if (!id) return null;
|
|
2442
|
+
return tasks.find((row) => row.id === id) || null;
|
|
2443
|
+
}
|
|
2444
|
+
function updateFormTitle(mode) {
|
|
2445
|
+
if (mode === "edit") {
|
|
2446
|
+
formTitle.textContent = "\u7F16\u8F91\u4EFB\u52A1";
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
if (mode === "clone") {
|
|
2450
|
+
formTitle.textContent = "\u53E6\u5B58\u4E3A\u65B0\u4EFB\u52A1";
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
formTitle.textContent = "\u65B0\u5EFA\u4EFB\u52A1";
|
|
2454
|
+
}
|
|
2455
|
+
function updateTaskTypeOptions(preferredType = "") {
|
|
2456
|
+
const platform = platformSelect.value;
|
|
2457
|
+
const options = getTasksForPlatform(platform);
|
|
2458
|
+
taskTypeSelect.innerHTML = options.map((item) => `<option value="${item.type}">${item.icon} ${item.label}</option>`).join("");
|
|
2459
|
+
const target = String(preferredType || "").trim();
|
|
2460
|
+
const matched = options.find((item) => item.type === target);
|
|
2461
|
+
taskTypeSelect.value = matched?.type || options[0]?.type || "";
|
|
2462
|
+
updatePlatformFields();
|
|
2463
|
+
}
|
|
2464
|
+
function updatePlatformFields() {
|
|
2465
|
+
const taskType = String(taskTypeSelect.value || "").trim();
|
|
2466
|
+
const isWeiboMonitor = taskType === "weibo-monitor";
|
|
2467
|
+
userIdWrap.style.display = isWeiboMonitor ? "" : "none";
|
|
2468
|
+
}
|
|
2469
|
+
function updateScheduleVisibility() {
|
|
2470
|
+
const scheduleType = String(scheduleTypeSelect.value || "interval").trim();
|
|
2471
|
+
intervalWrap.style.display = scheduleType === "interval" ? "inline-flex" : "none";
|
|
2472
|
+
runAtWrap.style.display = scheduleType === "once" || scheduleType === "daily" || scheduleType === "weekly" ? "inline-flex" : "none";
|
|
2473
|
+
}
|
|
2474
|
+
function updateLikeKeywordsState() {
|
|
2475
|
+
likeKeywordsInput.disabled = !likesInput.checked;
|
|
2476
|
+
}
|
|
2477
|
+
function collectFormData() {
|
|
2478
|
+
const maxRunsRaw = String(maxRunsInput.value || "").trim();
|
|
2479
|
+
const maxRunsNum = maxRunsRaw ? Number(maxRunsRaw) : 0;
|
|
1952
2480
|
return {
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2481
|
+
id: String(editingIdInput.value || "").trim() || void 0,
|
|
2482
|
+
name: String(nameInput.value || "").trim(),
|
|
2483
|
+
enabled: true,
|
|
2484
|
+
platform: platformSelect.value,
|
|
2485
|
+
taskType: String(taskTypeSelect.value || "").trim(),
|
|
2486
|
+
profileId: String(profileInput.value || "").trim(),
|
|
2487
|
+
keyword: String(keywordInput.value || "").trim(),
|
|
2488
|
+
targetCount: Math.max(1, Number(targetInput.value || 50) || 50),
|
|
2489
|
+
env: String(envSelect.value || "debug").trim() === "prod" ? "prod" : "debug",
|
|
2490
|
+
userId: String(userIdInput.value || "").trim(),
|
|
2491
|
+
collectComments: commentsInput.checked,
|
|
2492
|
+
collectBody: bodyInput.checked,
|
|
2493
|
+
doLikes: likesInput.checked,
|
|
2494
|
+
likeKeywords: String(likeKeywordsInput.value || "").trim(),
|
|
2495
|
+
scheduleType: scheduleTypeSelect.value,
|
|
2496
|
+
intervalMinutes: Math.max(1, Number(intervalInput.value || 30) || 30),
|
|
2497
|
+
runAt: toIsoOrNull(String(runAtInput.value || "")),
|
|
2498
|
+
maxRuns: Number.isFinite(maxRunsNum) && maxRunsNum > 0 ? Math.max(1, Math.floor(maxRunsNum)) : null
|
|
1964
2499
|
};
|
|
1965
2500
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
2501
|
+
function applyTaskToForm(task, mode) {
|
|
2502
|
+
const taskType = String(task.commandType || "xhs-unified").trim() || "xhs-unified";
|
|
2503
|
+
const platform = getPlatformForCommandType(taskType);
|
|
2504
|
+
platformSelect.value = platform;
|
|
2505
|
+
updateTaskTypeOptions(taskType);
|
|
2506
|
+
editingIdInput.value = mode === "edit" ? String(task.id || "") : "";
|
|
2507
|
+
nameInput.value = mode === "clone" ? `${String(task.name || task.id || "").trim()}-copy` : String(task.name || "").trim();
|
|
2508
|
+
keywordInput.value = String(task.commandArgv?.keyword || task.commandArgv?.k || "").trim();
|
|
2509
|
+
targetInput.value = String(task.commandArgv?.["max-notes"] ?? task.commandArgv?.target ?? 50);
|
|
2510
|
+
profileInput.value = String(task.commandArgv?.profile || task.commandArgv?.profileId || "").trim();
|
|
2511
|
+
envSelect.value = String(task.commandArgv?.env || "debug").trim() === "prod" ? "prod" : "debug";
|
|
2512
|
+
userIdInput.value = String(task.commandArgv?.["user-id"] || task.commandArgv?.userId || "").trim();
|
|
2513
|
+
commentsInput.checked = task.commandArgv?.["do-comments"] !== false;
|
|
2514
|
+
bodyInput.checked = task.commandArgv?.["fetch-body"] !== false;
|
|
2515
|
+
likesInput.checked = task.commandArgv?.["do-likes"] === true;
|
|
2516
|
+
likeKeywordsInput.value = String(task.commandArgv?.["like-keywords"] || "").trim();
|
|
2517
|
+
scheduleTypeSelect.value = String(task.scheduleType || "interval");
|
|
2518
|
+
intervalInput.value = String(task.intervalMinutes || 30);
|
|
2519
|
+
runAtInput.value = toLocalDatetimeValue(task.runAt);
|
|
2520
|
+
maxRunsInput.value = task.maxRuns ? String(task.maxRuns) : "";
|
|
2521
|
+
updatePlatformFields();
|
|
2522
|
+
updateScheduleVisibility();
|
|
2523
|
+
updateLikeKeywordsState();
|
|
2524
|
+
updateFormTitle(mode);
|
|
2525
|
+
}
|
|
2526
|
+
function resetForm() {
|
|
2527
|
+
editingIdInput.value = "";
|
|
2528
|
+
nameInput.value = DEFAULT_FORM.name;
|
|
2529
|
+
platformSelect.value = DEFAULT_FORM.platform;
|
|
2530
|
+
updateTaskTypeOptions(DEFAULT_FORM.taskType);
|
|
2531
|
+
keywordInput.value = DEFAULT_FORM.keyword;
|
|
2532
|
+
targetInput.value = String(DEFAULT_FORM.targetCount);
|
|
2533
|
+
profileInput.value = DEFAULT_FORM.profileId;
|
|
2534
|
+
envSelect.value = DEFAULT_FORM.env;
|
|
2535
|
+
userIdInput.value = DEFAULT_FORM.userId;
|
|
2536
|
+
commentsInput.checked = DEFAULT_FORM.collectComments;
|
|
2537
|
+
bodyInput.checked = DEFAULT_FORM.collectBody;
|
|
2538
|
+
likesInput.checked = DEFAULT_FORM.doLikes;
|
|
2539
|
+
likeKeywordsInput.value = DEFAULT_FORM.likeKeywords;
|
|
2540
|
+
scheduleTypeSelect.value = DEFAULT_FORM.scheduleType;
|
|
2541
|
+
intervalInput.value = String(DEFAULT_FORM.intervalMinutes);
|
|
2542
|
+
runAtInput.value = "";
|
|
2543
|
+
maxRunsInput.value = "";
|
|
2544
|
+
updatePlatformFields();
|
|
2545
|
+
updateScheduleVisibility();
|
|
2546
|
+
updateLikeKeywordsState();
|
|
2547
|
+
updateFormTitle("new");
|
|
2548
|
+
}
|
|
2549
|
+
function sortedTasksByRecent() {
|
|
2550
|
+
return [...tasks].sort((a, b) => {
|
|
2551
|
+
const byUpdated = parseSortableTime(b.updatedAt) - parseSortableTime(a.updatedAt);
|
|
2552
|
+
if (byUpdated !== 0) return byUpdated;
|
|
2553
|
+
const byCreated = parseSortableTime(b.createdAt) - parseSortableTime(a.createdAt);
|
|
2554
|
+
if (byCreated !== 0) return byCreated;
|
|
2555
|
+
return (Number(b.seq) || 0) - (Number(a.seq) || 0);
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
function renderHistorySelect() {
|
|
2559
|
+
const previous = String(historySelect.value || "").trim();
|
|
2560
|
+
const rows = sortedTasksByRecent();
|
|
2561
|
+
historySelect.innerHTML = '<option value="">\u9009\u62E9\u5386\u53F2\u4EFB\u52A1...</option>';
|
|
2562
|
+
for (const row of rows) {
|
|
2563
|
+
const label = `${row.name || row.id} (${row.id})`;
|
|
2564
|
+
const option = document.createElement("option");
|
|
2565
|
+
option.value = row.id;
|
|
2566
|
+
option.textContent = label;
|
|
2567
|
+
historySelect.appendChild(option);
|
|
2568
|
+
}
|
|
2569
|
+
if (previous && rows.some((row) => row.id === previous)) {
|
|
2570
|
+
historySelect.value = previous;
|
|
1985
2571
|
}
|
|
1986
2572
|
}
|
|
1987
|
-
function
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2573
|
+
function renderRecentTasks() {
|
|
2574
|
+
const rows = sortedTasksByRecent().slice(0, 8);
|
|
2575
|
+
if (rows.length === 0) {
|
|
2576
|
+
recentTasksList.innerHTML = '<div class="muted" style="font-size:12px;">\u6682\u65E0\u4EFB\u52A1</div>';
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
recentTasksList.innerHTML = rows.map((task) => `
|
|
2580
|
+
<div class="task-row" style="display:flex;gap:var(--gap-sm);padding:var(--gap-xs)0;border-bottom:1px solid var(--border-subtle);align-items:center;">
|
|
2581
|
+
<span style="flex:1;font-size:12px;">${task.name || task.id}</span>
|
|
2582
|
+
<span style="font-size:11px;color:var(--text-tertiary);">${task.commandType}</span>
|
|
2583
|
+
<span style="font-size:11px;color:${task.enabled ? "var(--accent-success)" : "var(--text-muted)"};">${task.enabled ? "\u542F\u7528" : "\u7981\u7528"}</span>
|
|
2584
|
+
<button class="secondary edit-task-btn" data-id="${task.id}" style="padding:2px 6px;font-size:10px;height:auto;">\u7F16\u8F91</button>
|
|
2585
|
+
</div>
|
|
2586
|
+
`).join("");
|
|
2587
|
+
recentTasksList.querySelectorAll(".edit-task-btn").forEach((btn) => {
|
|
2588
|
+
btn.addEventListener("click", () => {
|
|
2589
|
+
const taskId = btn.dataset.id || "";
|
|
2590
|
+
const task = getTaskById(taskId);
|
|
2591
|
+
if (!task) return;
|
|
2592
|
+
historySelect.value = task.id;
|
|
2593
|
+
applyTaskToForm(task, "edit");
|
|
2594
|
+
});
|
|
2595
|
+
});
|
|
1997
2596
|
}
|
|
1998
|
-
|
|
2597
|
+
function updateStats() {
|
|
2598
|
+
statSaved.textContent = String(tasks.length);
|
|
2599
|
+
statRunning.textContent = String(activeRunIds.size);
|
|
2600
|
+
const totalRunCount = tasks.reduce((sum, row) => sum + (Number(row.runCount) || 0), 0);
|
|
2601
|
+
statToday.textContent = String(totalRunCount);
|
|
2602
|
+
}
|
|
2603
|
+
async function runJsonScript(scriptPath, args, timeoutMs = 6e4) {
|
|
2604
|
+
const ret = await ctx2.api.cmdRunJson({
|
|
2605
|
+
title: `task-panel ${args.join(" ")}`.trim(),
|
|
2606
|
+
cwd: "",
|
|
2607
|
+
args: [scriptPath, ...args, "--json"],
|
|
2608
|
+
timeoutMs
|
|
2609
|
+
});
|
|
2610
|
+
if (!ret?.ok) {
|
|
2611
|
+
const reason = String(ret?.error || ret?.stderr || ret?.stdout || "unknown_error").trim();
|
|
2612
|
+
throw new Error(reason || "command failed");
|
|
2613
|
+
}
|
|
2614
|
+
return ret.json || {};
|
|
2615
|
+
}
|
|
2616
|
+
async function runScheduleJson(args, timeoutMs = 6e4) {
|
|
2617
|
+
return runJsonScript(scheduleScript, args, timeoutMs);
|
|
2618
|
+
}
|
|
2619
|
+
async function loadQuotaStatus() {
|
|
1999
2620
|
try {
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
const label = row.alias ? `${row.alias} (${profileId})` : row.name || profileId;
|
|
2006
|
-
const opt = createEl("option", { value: profileId }, [label]);
|
|
2007
|
-
accountSelect.appendChild(opt);
|
|
2621
|
+
const ret = await ctx2.api.cmdRunJson({
|
|
2622
|
+
title: "quota status",
|
|
2623
|
+
cwd: "",
|
|
2624
|
+
args: [quotaScript],
|
|
2625
|
+
timeoutMs: 3e4
|
|
2008
2626
|
});
|
|
2009
|
-
if (
|
|
2010
|
-
|
|
2627
|
+
if (!ret?.ok) return;
|
|
2628
|
+
const payload = ret?.json || {};
|
|
2629
|
+
const quotas = Array.isArray(payload?.quotas) ? payload.quotas : [];
|
|
2630
|
+
for (const quota of quotas) {
|
|
2631
|
+
const type = String(quota?.type || "").trim();
|
|
2632
|
+
if (!type) continue;
|
|
2633
|
+
const count = Number(quota?.count || 0);
|
|
2634
|
+
const max = Number(quota?.max || 0);
|
|
2635
|
+
const el = quotaBar.querySelector(`#quota-${type}`);
|
|
2636
|
+
if (!el) continue;
|
|
2637
|
+
el.textContent = `${type}: ${count}/${max || "-"}`;
|
|
2638
|
+
el.style.color = max > 0 && count >= max ? "var(--accent-danger)" : "";
|
|
2011
2639
|
}
|
|
2012
2640
|
} catch (err) {
|
|
2013
|
-
console.error("
|
|
2641
|
+
console.error("load quota failed:", err);
|
|
2014
2642
|
}
|
|
2015
2643
|
}
|
|
2016
|
-
async function
|
|
2644
|
+
async function loadTasks() {
|
|
2017
2645
|
try {
|
|
2018
|
-
const
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
const result = await ctx2.api.configExport({ filePath, config });
|
|
2024
|
-
if (result.ok) {
|
|
2025
|
-
alert(`\u914D\u7F6E\u5DF2\u5BFC\u51FA\u5230: ${result.path}`);
|
|
2026
|
-
}
|
|
2646
|
+
const out = await runScheduleJson(["list"]);
|
|
2647
|
+
tasks = parseTaskRows(out);
|
|
2648
|
+
renderHistorySelect();
|
|
2649
|
+
renderRecentTasks();
|
|
2650
|
+
updateStats();
|
|
2027
2651
|
} catch (err) {
|
|
2028
|
-
|
|
2652
|
+
console.error("load tasks failed:", err);
|
|
2029
2653
|
}
|
|
2030
2654
|
}
|
|
2031
|
-
|
|
2032
|
-
const
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
targetInput.value = String(config.target || 50);
|
|
2043
|
-
envSelect.value = config.env || "debug";
|
|
2044
|
-
fetchBodyCb.checked = config.fetchBody !== false;
|
|
2045
|
-
fetchCommentsCb.checked = config.fetchComments !== false;
|
|
2046
|
-
maxCommentsInput.value = String(config.maxComments || 100);
|
|
2047
|
-
autoLikeCb.checked = config.autoLike === true;
|
|
2048
|
-
likeKeywordsInput.value = config.likeKeywords || "";
|
|
2049
|
-
headlessCb.checked = config.headless === true;
|
|
2050
|
-
dryRunCb.checked = config.dryRun === true;
|
|
2051
|
-
updateLikeKeywordsState();
|
|
2052
|
-
saveConfig();
|
|
2053
|
-
alert("\u914D\u7F6E\u5DF2\u5BFC\u5165");
|
|
2054
|
-
} catch (err) {
|
|
2055
|
-
alert("\u5BFC\u5165\u5931\u8D25: " + (err?.message || String(err)));
|
|
2056
|
-
}
|
|
2655
|
+
function buildCommandArgv(data) {
|
|
2656
|
+
const argv = {
|
|
2657
|
+
profile: data.profileId,
|
|
2658
|
+
keyword: data.keyword,
|
|
2659
|
+
"max-notes": data.targetCount,
|
|
2660
|
+
target: data.targetCount,
|
|
2661
|
+
env: data.env,
|
|
2662
|
+
"do-comments": data.collectComments,
|
|
2663
|
+
"fetch-body": data.collectBody,
|
|
2664
|
+
"do-likes": data.doLikes,
|
|
2665
|
+
"like-keywords": data.likeKeywords
|
|
2057
2666
|
};
|
|
2058
|
-
|
|
2667
|
+
if (String(data.taskType || "").startsWith("weibo-")) {
|
|
2668
|
+
argv["task-type"] = commandTypeToWeiboTaskType(data.taskType);
|
|
2669
|
+
if (data.userId) argv["user-id"] = data.userId;
|
|
2670
|
+
}
|
|
2671
|
+
return argv;
|
|
2059
2672
|
}
|
|
2060
|
-
function
|
|
2061
|
-
|
|
2062
|
-
|
|
2673
|
+
function validateBeforeSave(data) {
|
|
2674
|
+
if (!data.profileId) return "\u8BF7\u8F93\u5165 Profile ID";
|
|
2675
|
+
if (isKeywordRequired(data.taskType) && !data.keyword) return "\u8BF7\u8F93\u5165\u5173\u952E\u8BCD";
|
|
2676
|
+
if (data.taskType === "weibo-monitor" && !data.userId) return "\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id";
|
|
2677
|
+
if (data.scheduleType !== "interval" && !data.runAt) return `${data.scheduleType} \u4EFB\u52A1\u9700\u8981\u6267\u884C\u65F6\u95F4`;
|
|
2678
|
+
return null;
|
|
2063
2679
|
}
|
|
2064
|
-
|
|
2065
|
-
const
|
|
2066
|
-
|
|
2067
|
-
|
|
2680
|
+
function buildSaveArgs(data) {
|
|
2681
|
+
const args = data.id ? ["update", data.id] : ["add"];
|
|
2682
|
+
args.push("--name", data.name || fallbackTaskName(data));
|
|
2683
|
+
args.push("--enabled", String(data.enabled));
|
|
2684
|
+
args.push("--command-type", data.taskType || "xhs-unified");
|
|
2685
|
+
args.push("--schedule-type", data.scheduleType);
|
|
2686
|
+
if (data.scheduleType === "interval") {
|
|
2687
|
+
args.push("--interval-minutes", String(data.intervalMinutes));
|
|
2688
|
+
} else {
|
|
2689
|
+
args.push("--run-at", String(data.runAt || ""));
|
|
2690
|
+
}
|
|
2691
|
+
args.push("--max-runs", data.maxRuns === null ? "0" : String(data.maxRuns));
|
|
2692
|
+
args.push("--argv-json", JSON.stringify(buildCommandArgv(data)));
|
|
2693
|
+
return args;
|
|
2694
|
+
}
|
|
2695
|
+
async function runSavedTask(taskId, data) {
|
|
2696
|
+
const out = await runScheduleJson(["run", taskId], 0);
|
|
2697
|
+
const runId = String(
|
|
2698
|
+
out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
|
|
2699
|
+
).trim();
|
|
2700
|
+
if (typeof ctx2.setStatus === "function") {
|
|
2701
|
+
ctx2.setStatus(`running: ${taskId}`);
|
|
2702
|
+
}
|
|
2703
|
+
if (data.taskType === "xhs-unified" && ctx2 && typeof ctx2 === "object") {
|
|
2704
|
+
ctx2.xhsCurrentRun = {
|
|
2705
|
+
runId: runId || null,
|
|
2706
|
+
taskId,
|
|
2707
|
+
profileId: data.profileId,
|
|
2708
|
+
keyword: data.keyword,
|
|
2709
|
+
target: data.targetCount,
|
|
2710
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
if (typeof ctx2.setActiveTab === "function") {
|
|
2714
|
+
ctx2.setActiveTab(data.taskType === "xhs-unified" ? "dashboard" : "scheduler");
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
async function saveTask(runImmediately = false) {
|
|
2718
|
+
const data = collectFormData();
|
|
2719
|
+
const invalidReason = validateBeforeSave(data);
|
|
2720
|
+
if (invalidReason) {
|
|
2721
|
+
alert(invalidReason);
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
saveBtn.disabled = true;
|
|
2725
|
+
runBtn.disabled = true;
|
|
2726
|
+
runEphemeralBtn.disabled = true;
|
|
2727
|
+
try {
|
|
2728
|
+
const out = await runScheduleJson(buildSaveArgs(data));
|
|
2729
|
+
const taskId = String(out?.task?.id || data.id || "").trim();
|
|
2730
|
+
if (!taskId) {
|
|
2731
|
+
throw new Error("task id missing after save");
|
|
2732
|
+
}
|
|
2733
|
+
editingIdInput.value = taskId;
|
|
2734
|
+
updateFormTitle("edit");
|
|
2735
|
+
await loadTasks();
|
|
2736
|
+
historySelect.value = taskId;
|
|
2737
|
+
if (runImmediately) {
|
|
2738
|
+
await runSavedTask(taskId, data);
|
|
2739
|
+
} else {
|
|
2740
|
+
alert("\u4EFB\u52A1\u5DF2\u4FDD\u5B58");
|
|
2741
|
+
}
|
|
2742
|
+
} catch (err) {
|
|
2743
|
+
alert(`\u4FDD\u5B58\u5931\u8D25: ${err?.message || String(err)}`);
|
|
2744
|
+
} finally {
|
|
2745
|
+
saveBtn.disabled = false;
|
|
2746
|
+
runBtn.disabled = false;
|
|
2747
|
+
runEphemeralBtn.disabled = false;
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
function buildEphemeralRunSpec(data) {
|
|
2751
|
+
if (!data.profileId) return null;
|
|
2752
|
+
if (data.taskType === "xhs-unified") {
|
|
2753
|
+
if (!data.keyword) return null;
|
|
2754
|
+
return {
|
|
2755
|
+
title: `xhs: ${data.keyword}`,
|
|
2756
|
+
groupKey: "xhs-unified",
|
|
2757
|
+
args: [
|
|
2758
|
+
xhsScript,
|
|
2759
|
+
"--profile",
|
|
2760
|
+
data.profileId,
|
|
2761
|
+
"--keyword",
|
|
2762
|
+
data.keyword,
|
|
2763
|
+
"--target",
|
|
2764
|
+
String(data.targetCount),
|
|
2765
|
+
"--max-notes",
|
|
2766
|
+
String(data.targetCount),
|
|
2767
|
+
"--env",
|
|
2768
|
+
data.env,
|
|
2769
|
+
"--do-comments",
|
|
2770
|
+
String(data.collectComments),
|
|
2771
|
+
"--fetch-body",
|
|
2772
|
+
String(data.collectBody),
|
|
2773
|
+
"--do-likes",
|
|
2774
|
+
String(data.doLikes),
|
|
2775
|
+
"--like-keywords",
|
|
2776
|
+
data.likeKeywords
|
|
2777
|
+
]
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
if (data.taskType === "weibo-search") {
|
|
2781
|
+
if (!data.keyword) return null;
|
|
2782
|
+
return {
|
|
2783
|
+
title: `weibo: ${data.keyword}`,
|
|
2784
|
+
groupKey: "weibo-search",
|
|
2785
|
+
args: [
|
|
2786
|
+
weiboScript,
|
|
2787
|
+
"search",
|
|
2788
|
+
"--profile",
|
|
2789
|
+
data.profileId,
|
|
2790
|
+
"--keyword",
|
|
2791
|
+
data.keyword,
|
|
2792
|
+
"--target",
|
|
2793
|
+
String(data.targetCount),
|
|
2794
|
+
"--env",
|
|
2795
|
+
data.env
|
|
2796
|
+
]
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
return null;
|
|
2800
|
+
}
|
|
2801
|
+
async function runWithoutSave() {
|
|
2802
|
+
const data = collectFormData();
|
|
2803
|
+
if (!data.profileId) {
|
|
2804
|
+
alert("\u8BF7\u8F93\u5165 Profile ID");
|
|
2068
2805
|
return;
|
|
2069
2806
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
alert("\u8BF7\u9009\u62E9\u8D26\u6237");
|
|
2807
|
+
if (isKeywordRequired(data.taskType) && !data.keyword) {
|
|
2808
|
+
alert("\u8BF7\u8F93\u5165\u5173\u952E\u8BCD");
|
|
2073
2809
|
return;
|
|
2074
2810
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
alert("\u5F53\u524D\u8D26\u6237\u65E0\u6548\uFF0C\u8BF7\u5148\u5230\u201C\u8D26\u6237\u7BA1\u7406\u201D\u5B8C\u6210\u767B\u5F55\u5E76\u6821\u9A8C");
|
|
2811
|
+
if (data.taskType === "weibo-monitor" && !data.userId) {
|
|
2812
|
+
alert("\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id");
|
|
2078
2813
|
return;
|
|
2079
2814
|
}
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2815
|
+
const spec = buildEphemeralRunSpec(data);
|
|
2816
|
+
if (!spec) {
|
|
2817
|
+
alert(`\u5F53\u524D\u4EFB\u52A1\u7C7B\u578B\u6682\u4E0D\u652F\u6301\u4EC5\u6267\u884C(\u4E0D\u4FDD\u5B58): ${data.taskType}`);
|
|
2818
|
+
return;
|
|
2084
2819
|
}
|
|
2085
|
-
|
|
2086
|
-
const outputRoot = String(ctx2?.settings?.downloadRoot || "").trim();
|
|
2087
|
-
const args = [
|
|
2088
|
-
script,
|
|
2089
|
-
"--profile",
|
|
2090
|
-
profileId,
|
|
2091
|
-
"--keyword",
|
|
2092
|
-
config.keyword,
|
|
2093
|
-
"--max-notes",
|
|
2094
|
-
String(config.target),
|
|
2095
|
-
"--env",
|
|
2096
|
-
config.env,
|
|
2097
|
-
"--do-comments",
|
|
2098
|
-
config.fetchComments ? "true" : "false",
|
|
2099
|
-
"--persist-comments",
|
|
2100
|
-
config.fetchComments ? "true" : "false",
|
|
2101
|
-
"--do-likes",
|
|
2102
|
-
config.autoLike ? "true" : "false",
|
|
2103
|
-
"--like-keywords",
|
|
2104
|
-
config.likeKeywords || "",
|
|
2105
|
-
"--headless",
|
|
2106
|
-
config.headless ? "true" : "false"
|
|
2107
|
-
];
|
|
2108
|
-
if (outputRoot) args.push("--output-root", outputRoot);
|
|
2109
|
-
if (config.dryRun) args.push("--dry-run");
|
|
2110
|
-
else args.push("--no-dry-run");
|
|
2111
|
-
startBtn.disabled = true;
|
|
2112
|
-
const prevText = startBtn.textContent;
|
|
2113
|
-
startBtn.textContent = "\u542F\u52A8\u4E2D...";
|
|
2820
|
+
runEphemeralBtn.disabled = true;
|
|
2114
2821
|
try {
|
|
2115
2822
|
const ret = await ctx2.api.cmdSpawn({
|
|
2116
|
-
title:
|
|
2823
|
+
title: spec.title,
|
|
2117
2824
|
cwd: "",
|
|
2118
|
-
args,
|
|
2119
|
-
groupKey:
|
|
2825
|
+
args: spec.args,
|
|
2826
|
+
groupKey: spec.groupKey
|
|
2120
2827
|
});
|
|
2121
2828
|
const runId = String(ret?.runId || "").trim();
|
|
2122
|
-
if (
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
ctx2.xhsCurrentRun = {
|
|
2126
|
-
runId,
|
|
2127
|
-
profileId,
|
|
2128
|
-
keyword: config.keyword,
|
|
2129
|
-
target: config.target,
|
|
2130
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2131
|
-
};
|
|
2132
|
-
if (typeof ctx2.appendLog === "function") {
|
|
2133
|
-
ctx2.appendLog(`[ui] started xhs-unified runId=${runId} profile=${profileId} keyword=${config.keyword}`);
|
|
2829
|
+
if (runId) {
|
|
2830
|
+
activeRunIds.add(runId);
|
|
2831
|
+
updateStats();
|
|
2134
2832
|
}
|
|
2135
2833
|
if (typeof ctx2.setStatus === "function") {
|
|
2136
|
-
ctx2.setStatus(`
|
|
2834
|
+
ctx2.setStatus(`started: ${spec.title}`);
|
|
2137
2835
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2836
|
+
if (data.taskType === "xhs-unified" && ctx2 && typeof ctx2 === "object") {
|
|
2837
|
+
ctx2.xhsCurrentRun = {
|
|
2838
|
+
runId: runId || null,
|
|
2839
|
+
taskId: null,
|
|
2840
|
+
profileId: data.profileId,
|
|
2841
|
+
keyword: data.keyword,
|
|
2842
|
+
target: data.targetCount,
|
|
2843
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2844
|
+
};
|
|
2142
2845
|
}
|
|
2143
|
-
|
|
2846
|
+
if (typeof ctx2.setActiveTab === "function") {
|
|
2847
|
+
ctx2.setActiveTab(data.taskType === "xhs-unified" ? "dashboard" : "scheduler");
|
|
2848
|
+
}
|
|
2849
|
+
} catch (err) {
|
|
2850
|
+
alert(`\u6267\u884C\u5931\u8D25: ${err?.message || String(err)}`);
|
|
2144
2851
|
} finally {
|
|
2145
|
-
|
|
2146
|
-
startBtn.textContent = prevText || "\u5F00\u59CB\u722C\u53D6";
|
|
2147
|
-
}
|
|
2148
|
-
if (typeof ctx2.setActiveTab === "function") {
|
|
2149
|
-
ctx2.setActiveTab("dashboard");
|
|
2852
|
+
runEphemeralBtn.disabled = false;
|
|
2150
2853
|
}
|
|
2151
2854
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
dryRunCb
|
|
2168
|
-
].forEach((el) => {
|
|
2169
|
-
el.onchange = saveConfig;
|
|
2170
|
-
if (el.tagName === "INPUT" && el.type !== "checkbox") {
|
|
2171
|
-
el.oninput = saveConfig;
|
|
2855
|
+
function selectedHistoryTask() {
|
|
2856
|
+
const taskId = String(historySelect.value || "").trim();
|
|
2857
|
+
if (!taskId) return null;
|
|
2858
|
+
return getTaskById(taskId);
|
|
2859
|
+
}
|
|
2860
|
+
async function loadLastProfile() {
|
|
2861
|
+
try {
|
|
2862
|
+
const config = await ctx2.api.configLoadLast();
|
|
2863
|
+
if (!profileInput.value && config?.lastProfileId) {
|
|
2864
|
+
profileInput.value = String(config.lastProfileId || "");
|
|
2865
|
+
}
|
|
2866
|
+
if (!keywordInput.value && config?.keyword) {
|
|
2867
|
+
keywordInput.value = String(config.keyword || "");
|
|
2868
|
+
}
|
|
2869
|
+
} catch {
|
|
2172
2870
|
}
|
|
2871
|
+
}
|
|
2872
|
+
platformSelect.addEventListener("change", () => {
|
|
2873
|
+
updateTaskTypeOptions();
|
|
2173
2874
|
});
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2875
|
+
taskTypeSelect.addEventListener("change", () => updatePlatformFields());
|
|
2876
|
+
scheduleTypeSelect.addEventListener("change", () => updateScheduleVisibility());
|
|
2877
|
+
likesInput.addEventListener("change", () => updateLikeKeywordsState());
|
|
2878
|
+
saveBtn.addEventListener("click", () => {
|
|
2879
|
+
void saveTask(false);
|
|
2880
|
+
});
|
|
2881
|
+
runBtn.addEventListener("click", () => {
|
|
2882
|
+
void saveTask(true);
|
|
2883
|
+
});
|
|
2884
|
+
runEphemeralBtn.addEventListener("click", () => {
|
|
2885
|
+
void runWithoutSave();
|
|
2886
|
+
});
|
|
2887
|
+
resetBtn.addEventListener("click", resetForm);
|
|
2888
|
+
quotaRefreshBtn.addEventListener("click", () => {
|
|
2889
|
+
void loadQuotaStatus();
|
|
2890
|
+
});
|
|
2891
|
+
historyRefreshBtn.addEventListener("click", () => {
|
|
2892
|
+
void loadTasks();
|
|
2893
|
+
});
|
|
2894
|
+
historyEditBtn.addEventListener("click", () => {
|
|
2895
|
+
const task = selectedHistoryTask();
|
|
2896
|
+
if (!task) {
|
|
2897
|
+
alert("\u8BF7\u5148\u9009\u62E9\u5386\u53F2\u4EFB\u52A1");
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
applyTaskToForm(task, "edit");
|
|
2901
|
+
});
|
|
2902
|
+
historyCloneBtn.addEventListener("click", () => {
|
|
2903
|
+
const task = selectedHistoryTask();
|
|
2904
|
+
if (!task) {
|
|
2905
|
+
alert("\u8BF7\u5148\u9009\u62E9\u5386\u53F2\u4EFB\u52A1");
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
applyTaskToForm(task, "clone");
|
|
2909
|
+
});
|
|
2910
|
+
gotoSchedulerBtn.addEventListener("click", () => {
|
|
2911
|
+
if (typeof ctx2.setActiveTab === "function") {
|
|
2912
|
+
ctx2.setActiveTab("scheduler");
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
if (typeof ctx2.api?.onCmdEvent === "function") {
|
|
2916
|
+
unsubscribeActiveRuns = ctx2.api.onCmdEvent((evt) => {
|
|
2917
|
+
const runId = String(evt?.runId || "").trim();
|
|
2918
|
+
if (!runId) return;
|
|
2919
|
+
if (evt?.type === "started") {
|
|
2920
|
+
activeRunIds.add(runId);
|
|
2921
|
+
updateStats();
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
if (evt?.type === "exit") {
|
|
2925
|
+
activeRunIds.delete(runId);
|
|
2926
|
+
updateStats();
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
resetForm();
|
|
2931
|
+
updateTaskTypeOptions(DEFAULT_FORM.taskType);
|
|
2932
|
+
updateScheduleVisibility();
|
|
2933
|
+
updateLikeKeywordsState();
|
|
2934
|
+
void loadQuotaStatus();
|
|
2935
|
+
void loadTasks();
|
|
2936
|
+
void loadLastProfile();
|
|
2937
|
+
return () => {
|
|
2938
|
+
if (unsubscribeActiveRuns) {
|
|
2939
|
+
try {
|
|
2940
|
+
unsubscribeActiveRuns();
|
|
2941
|
+
} catch {
|
|
2942
|
+
}
|
|
2943
|
+
unsubscribeActiveRuns = null;
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// src/renderer/tabs-new/dashboard.mts
|
|
2949
|
+
function renderDashboard(root, ctx2) {
|
|
2180
2950
|
root.innerHTML = "";
|
|
2181
2951
|
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
2182
2952
|
"\u5F53\u524D: ",
|
|
@@ -2245,6 +3015,10 @@ function renderDashboard(root, ctx2) {
|
|
|
2245
3015
|
<label>\u4F7F\u7528\u8D26\u6237</label>
|
|
2246
3016
|
<div id="task-account" style="font-weight: 600; color: var(--text-1);">-</div>
|
|
2247
3017
|
</div>
|
|
3018
|
+
<div>
|
|
3019
|
+
<label>\u914D\u7F6EID</label>
|
|
3020
|
+
<div id="task-config-id" style="font-weight: 600; color: var(--text-1);">-</div>
|
|
3021
|
+
</div>
|
|
2248
3022
|
</div>
|
|
2249
3023
|
|
|
2250
3024
|
<div class="phase-indicator" style="margin-bottom: var(--gap);">
|
|
@@ -2282,12 +3056,13 @@ function renderDashboard(root, ctx2) {
|
|
|
2282
3056
|
\u5B9E\u65F6\u65E5\u5FD7
|
|
2283
3057
|
<button id="toggle-logs-btn" class="secondary" style="margin-left: auto; padding: 4px 10px; font-size: 11px;">\u5C55\u5F00</button>
|
|
2284
3058
|
</div>
|
|
2285
|
-
<div id="logs-container" class="log-container" style="display: none;
|
|
3059
|
+
<div id="logs-container" class="log-container" style="display: none;"></div>
|
|
2286
3060
|
|
|
2287
3061
|
<div style="margin-top: var(--gap);">
|
|
2288
3062
|
<div class="btn-group">
|
|
2289
3063
|
<button id="pause-btn" class="secondary" style="flex: 1;">\u6682\u505C</button>
|
|
2290
3064
|
<button id="stop-btn" class="danger" style="flex: 1;">\u505C\u6B62</button>
|
|
3065
|
+
<button id="back-config-btn" class="secondary" style="flex: 1;">\u8FD4\u56DE\u914D\u7F6E</button>
|
|
2291
3066
|
</div>
|
|
2292
3067
|
</div>
|
|
2293
3068
|
`;
|
|
@@ -2300,6 +3075,7 @@ function renderDashboard(root, ctx2) {
|
|
|
2300
3075
|
const taskKeyword = root.querySelector("#task-keyword");
|
|
2301
3076
|
const taskTarget = root.querySelector("#task-target");
|
|
2302
3077
|
const taskAccount = root.querySelector("#task-account");
|
|
3078
|
+
const taskConfigId = root.querySelector("#task-config-id");
|
|
2303
3079
|
const currentPhase = root.querySelector("#current-phase");
|
|
2304
3080
|
const currentAction = root.querySelector("#current-action");
|
|
2305
3081
|
const progressPercent = root.querySelector("#progress-percent");
|
|
@@ -2316,17 +3092,33 @@ function renderDashboard(root, ctx2) {
|
|
|
2316
3092
|
const toggleLogsBtn = root.querySelector("#toggle-logs-btn");
|
|
2317
3093
|
const pauseBtn = root.querySelector("#pause-btn");
|
|
2318
3094
|
const stopBtn = root.querySelector("#stop-btn");
|
|
3095
|
+
const backConfigBtn = root.querySelector("#back-config-btn");
|
|
2319
3096
|
let logsExpanded = false;
|
|
2320
3097
|
let paused = false;
|
|
3098
|
+
let unsubscribeBus = null;
|
|
3099
|
+
let commentsCount = 0;
|
|
3100
|
+
let likesCount = 0;
|
|
3101
|
+
let likesSkippedCount = 0;
|
|
3102
|
+
let likesAlreadyCount = 0;
|
|
3103
|
+
let likesDedupCount = 0;
|
|
2321
3104
|
let startTime = Date.now();
|
|
3105
|
+
let stoppedAt = null;
|
|
2322
3106
|
let elapsedTimer = null;
|
|
2323
3107
|
let unsubscribeState = null;
|
|
2324
3108
|
let unsubscribeCmd = null;
|
|
2325
3109
|
let activeRunId = String(ctx2?.xhsCurrentRun?.runId || "").trim();
|
|
3110
|
+
let activeStatus = "";
|
|
2326
3111
|
let errorCountTotal = 0;
|
|
2327
3112
|
const recentErrors = [];
|
|
2328
3113
|
const maxLogs = 500;
|
|
2329
3114
|
const maxRecentErrors = 8;
|
|
3115
|
+
const initialTaskId = String(ctx2?.xhsCurrentRun?.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3116
|
+
if (initialTaskId) {
|
|
3117
|
+
taskConfigId.textContent = initialTaskId;
|
|
3118
|
+
}
|
|
3119
|
+
const normalizeStatus = (value) => String(value || "").trim().toLowerCase();
|
|
3120
|
+
const isRunningStatus = (value) => ["running", "queued", "pending", "starting"].includes(normalizeStatus(value));
|
|
3121
|
+
const isTerminalStatus = (value) => ["completed", "done", "success", "succeeded", "failed", "error", "stopped", "canceled"].includes(normalizeStatus(value));
|
|
2330
3122
|
function renderRunSummary() {
|
|
2331
3123
|
runIdText.textContent = activeRunId || "-";
|
|
2332
3124
|
errorCountText.textContent = String(errorCountTotal);
|
|
@@ -2357,12 +3149,22 @@ function renderDashboard(root, ctx2) {
|
|
|
2357
3149
|
renderRunSummary();
|
|
2358
3150
|
}
|
|
2359
3151
|
function updateElapsed() {
|
|
2360
|
-
const
|
|
3152
|
+
const base = stoppedAt ?? Date.now();
|
|
3153
|
+
const elapsed = Math.max(0, Math.floor((base - startTime) / 1e3));
|
|
2361
3154
|
const h = Math.floor(elapsed / 3600);
|
|
2362
3155
|
const m = Math.floor(elapsed % 3600 / 60);
|
|
2363
3156
|
const s = elapsed % 60;
|
|
2364
3157
|
statElapsed.textContent = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
2365
3158
|
}
|
|
3159
|
+
function startElapsedTimer() {
|
|
3160
|
+
if (elapsedTimer) return;
|
|
3161
|
+
elapsedTimer = setInterval(updateElapsed, 1e3);
|
|
3162
|
+
}
|
|
3163
|
+
function stopElapsedTimer() {
|
|
3164
|
+
if (!elapsedTimer) return;
|
|
3165
|
+
clearInterval(elapsedTimer);
|
|
3166
|
+
elapsedTimer = null;
|
|
3167
|
+
}
|
|
2366
3168
|
function addLog(line, type = "info") {
|
|
2367
3169
|
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
|
|
2368
3170
|
const logLine = createEl("div", { className: "log-line" });
|
|
@@ -2377,29 +3179,45 @@ function renderDashboard(root, ctx2) {
|
|
|
2377
3179
|
}
|
|
2378
3180
|
function updateFromTaskState(state) {
|
|
2379
3181
|
if (!state) return;
|
|
2380
|
-
const
|
|
2381
|
-
const
|
|
2382
|
-
const
|
|
2383
|
-
const
|
|
3182
|
+
const progressObj = state.progress && typeof state.progress === "object" ? state.progress : null;
|
|
3183
|
+
const processedRaw = progressObj?.processed ?? progressObj?.current ?? state.progress ?? state.collected ?? state.current ?? 0;
|
|
3184
|
+
const totalRaw = progressObj?.total ?? state.total ?? state.target ?? state.maxNotes ?? 0;
|
|
3185
|
+
const failedRaw = progressObj?.failed ?? state.failed ?? state.errors ?? 0;
|
|
3186
|
+
const collected = Number(processedRaw) || 0;
|
|
3187
|
+
const target = Number(totalRaw) || 0;
|
|
3188
|
+
const success = Number(state.success ?? collected) || 0;
|
|
3189
|
+
const failed = Number(failedRaw) || 0;
|
|
2384
3190
|
const remaining = Math.max(0, target - collected);
|
|
2385
3191
|
statCollected.textContent = String(collected);
|
|
2386
3192
|
statSuccess.textContent = String(success);
|
|
2387
3193
|
statFailed.textContent = String(failed);
|
|
2388
3194
|
statRemaining.textContent = String(remaining);
|
|
2389
|
-
|
|
3195
|
+
let percent = 0;
|
|
3196
|
+
if (target > 0) {
|
|
3197
|
+
percent = Math.round(collected / target * 100);
|
|
3198
|
+
} else if (progressObj && Number.isFinite(Number(progressObj.percent))) {
|
|
3199
|
+
const pct = Number(progressObj.percent);
|
|
3200
|
+
percent = pct <= 1 ? Math.round(pct * 100) : Math.round(pct);
|
|
3201
|
+
}
|
|
2390
3202
|
progressPercent.textContent = `${percent}%`;
|
|
2391
3203
|
progressBar.style.width = `${percent}%`;
|
|
2392
3204
|
if (state.phase) {
|
|
2393
3205
|
currentPhase.textContent = state.phase;
|
|
2394
3206
|
}
|
|
2395
|
-
|
|
2396
|
-
|
|
3207
|
+
const action = String(state.action || state.message || state.step || "").trim();
|
|
3208
|
+
if (action) {
|
|
3209
|
+
currentAction.textContent = action;
|
|
2397
3210
|
}
|
|
2398
|
-
|
|
2399
|
-
|
|
3211
|
+
const stats = state.stats && typeof state.stats === "object" ? state.stats : null;
|
|
3212
|
+
const comments = Number(stats?.commentsCollected ?? state.comments);
|
|
3213
|
+
if (Number.isFinite(comments)) {
|
|
3214
|
+
commentsCount = Math.max(0, Math.floor(comments));
|
|
3215
|
+
statComments.textContent = `${commentsCount}\u6761`;
|
|
2400
3216
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
3217
|
+
const likes = Number(stats?.likesPerformed ?? state.likes);
|
|
3218
|
+
if (Number.isFinite(likes)) {
|
|
3219
|
+
likesCount = Math.max(0, Math.floor(likes));
|
|
3220
|
+
statLikes.textContent = `${likesCount}\u6B21 (\u8DF3\u8FC7:${likesSkippedCount}, \u5DF2\u8D5E:${likesAlreadyCount}, \u53BB\u91CD:${likesDedupCount})`;
|
|
2403
3221
|
}
|
|
2404
3222
|
if (state.ratelimits) {
|
|
2405
3223
|
statRatelimit.textContent = `${state.ratelimits}\u6B21`;
|
|
@@ -2414,22 +3232,68 @@ function renderDashboard(root, ctx2) {
|
|
|
2414
3232
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
2415
3233
|
taskAccount.textContent = aliases[state.profileId] || state.profileId;
|
|
2416
3234
|
}
|
|
3235
|
+
const taskId = String(state.taskId || state.scheduleTaskId || state.configTaskId || "").trim();
|
|
3236
|
+
if (taskId) {
|
|
3237
|
+
taskConfigId.textContent = taskId;
|
|
3238
|
+
if (ctx2 && typeof ctx2 === "object") {
|
|
3239
|
+
ctx2.activeTaskConfigId = taskId;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
2417
3242
|
if (state.runId) {
|
|
2418
3243
|
activeRunId = String(state.runId);
|
|
2419
3244
|
renderRunSummary();
|
|
2420
3245
|
}
|
|
3246
|
+
if (state.startedAt) {
|
|
3247
|
+
const ts = Number(state.startedAt) || Date.parse(String(state.startedAt));
|
|
3248
|
+
if (Number.isFinite(ts) && ts > 0) {
|
|
3249
|
+
startTime = ts;
|
|
3250
|
+
if (!stoppedAt) {
|
|
3251
|
+
updateElapsed();
|
|
3252
|
+
startElapsedTimer();
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
const status = normalizeStatus(state.status);
|
|
3257
|
+
if (status) {
|
|
3258
|
+
activeStatus = status;
|
|
3259
|
+
}
|
|
3260
|
+
if (status === "completed" || status === "done" || status === "success" || status === "succeeded") {
|
|
3261
|
+
if (!stoppedAt) {
|
|
3262
|
+
stoppedAt = Date.now();
|
|
3263
|
+
updateElapsed();
|
|
3264
|
+
stopElapsedTimer();
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
if (status === "failed" || status === "error") {
|
|
3268
|
+
if (!stoppedAt) {
|
|
3269
|
+
stoppedAt = Date.now();
|
|
3270
|
+
updateElapsed();
|
|
3271
|
+
stopElapsedTimer();
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
2421
3274
|
if (state.error) {
|
|
2422
3275
|
pushRecentError(String(state.error), "state");
|
|
2423
3276
|
}
|
|
2424
3277
|
}
|
|
2425
3278
|
function pickTaskFromList(tasks) {
|
|
2426
3279
|
const target = activeRunId;
|
|
3280
|
+
const running = tasks.find((item) => isRunningStatus(item?.status));
|
|
3281
|
+
const sorted = [...tasks].sort((a, b) => {
|
|
3282
|
+
const aTs = Number(a?.updatedAt ?? a?.completedAt ?? a?.startedAt ?? 0) || 0;
|
|
3283
|
+
const bTs = Number(b?.updatedAt ?? b?.completedAt ?? b?.startedAt ?? 0) || 0;
|
|
3284
|
+
return bTs - aTs;
|
|
3285
|
+
});
|
|
3286
|
+
const latest = sorted[0] || null;
|
|
2427
3287
|
if (target) {
|
|
2428
3288
|
const matched = tasks.find((item) => String(item?.runId || "").trim() === target);
|
|
2429
|
-
if (matched)
|
|
3289
|
+
if (matched) {
|
|
3290
|
+
if (isRunningStatus(matched?.status)) return matched;
|
|
3291
|
+
if (running) return running;
|
|
3292
|
+
if (latest && String(latest?.runId || "").trim() !== target) return latest;
|
|
3293
|
+
return matched;
|
|
3294
|
+
}
|
|
2430
3295
|
}
|
|
2431
|
-
|
|
2432
|
-
return running || tasks[0] || null;
|
|
3296
|
+
return running || latest || null;
|
|
2433
3297
|
}
|
|
2434
3298
|
function updateFromEventPayload(payload) {
|
|
2435
3299
|
const event = String(payload?.event || "").trim();
|
|
@@ -2437,11 +3301,39 @@ function renderDashboard(root, ctx2) {
|
|
|
2437
3301
|
if (event === "xhs.unified.start") {
|
|
2438
3302
|
currentPhase.textContent = "\u8FD0\u884C\u4E2D";
|
|
2439
3303
|
currentAction.textContent = "\u542F\u52A8 autoscript";
|
|
3304
|
+
activeStatus = "running";
|
|
3305
|
+
statCollected.textContent = "0";
|
|
3306
|
+
statSuccess.textContent = "0";
|
|
3307
|
+
statFailed.textContent = "0";
|
|
3308
|
+
statRemaining.textContent = "0";
|
|
3309
|
+
progressPercent.textContent = "0%";
|
|
3310
|
+
progressBar.style.width = "0%";
|
|
3311
|
+
commentsCount = 0;
|
|
3312
|
+
likesCount = 0;
|
|
3313
|
+
likesSkippedCount = 0;
|
|
3314
|
+
likesAlreadyCount = 0;
|
|
3315
|
+
likesDedupCount = 0;
|
|
3316
|
+
statComments.textContent = `0\u6761`;
|
|
3317
|
+
statLikes.textContent = `0\u6B21 (\u8DF3\u8FC7:0, \u5DF2\u8D5E:0, \u53BB\u91CD:0)`;
|
|
3318
|
+
const ts = Date.parse(String(payload.ts || "")) || Date.now();
|
|
3319
|
+
startTime = ts;
|
|
3320
|
+
stoppedAt = null;
|
|
3321
|
+
updateElapsed();
|
|
3322
|
+
startElapsedTimer();
|
|
2440
3323
|
if (payload.runId) {
|
|
2441
3324
|
activeRunId = String(payload.runId || "").trim() || activeRunId;
|
|
2442
3325
|
}
|
|
2443
3326
|
if (payload.keyword) taskKeyword.textContent = String(payload.keyword);
|
|
2444
3327
|
if (payload.maxNotes) taskTarget.textContent = String(payload.maxNotes);
|
|
3328
|
+
if (payload.taskId) {
|
|
3329
|
+
const taskId = String(payload.taskId || "").trim();
|
|
3330
|
+
if (taskId) {
|
|
3331
|
+
taskConfigId.textContent = taskId;
|
|
3332
|
+
if (ctx2 && typeof ctx2 === "object") {
|
|
3333
|
+
ctx2.activeTaskConfigId = taskId;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
2445
3337
|
renderRunSummary();
|
|
2446
3338
|
return;
|
|
2447
3339
|
}
|
|
@@ -2449,15 +3341,38 @@ function renderDashboard(root, ctx2) {
|
|
|
2449
3341
|
const opId = String(payload.operationId || "").trim();
|
|
2450
3342
|
currentAction.textContent = opId || currentAction.textContent;
|
|
2451
3343
|
const result = payload.result && typeof payload.result === "object" ? payload.result : {};
|
|
3344
|
+
const opResult = result && typeof result === "object" && "result" in result ? result.result : result;
|
|
3345
|
+
if (opId === "open_first_detail" || opId === "open_next_detail") {
|
|
3346
|
+
const visited = Number(opResult?.visited || 0);
|
|
3347
|
+
const maxNotes = Number(opResult?.maxNotes || 0);
|
|
3348
|
+
if (visited > 0) {
|
|
3349
|
+
statCollected.textContent = String(visited);
|
|
3350
|
+
statSuccess.textContent = String(visited);
|
|
3351
|
+
if (maxNotes > 0) {
|
|
3352
|
+
const remaining = Math.max(0, maxNotes - visited);
|
|
3353
|
+
statRemaining.textContent = String(remaining);
|
|
3354
|
+
taskTarget.textContent = String(maxNotes);
|
|
3355
|
+
const pct = Math.round(visited / maxNotes * 100);
|
|
3356
|
+
progressPercent.textContent = `${pct}%`;
|
|
3357
|
+
progressBar.style.width = `${pct}%`;
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
2452
3361
|
if (opId === "comments_harvest") {
|
|
2453
|
-
const
|
|
2454
|
-
|
|
2455
|
-
statComments.textContent = `${
|
|
3362
|
+
const added = Number(opResult?.collected || 0);
|
|
3363
|
+
commentsCount = Math.max(0, commentsCount + added);
|
|
3364
|
+
statComments.textContent = `${commentsCount}\u6761`;
|
|
2456
3365
|
}
|
|
2457
3366
|
if (opId === "comment_like") {
|
|
2458
|
-
const
|
|
2459
|
-
const
|
|
2460
|
-
|
|
3367
|
+
const added = Number(opResult?.likedCount || 0);
|
|
3368
|
+
const skipped = Number(opResult?.skippedCount || 0);
|
|
3369
|
+
const already = Number(opResult?.alreadyLikedSkipped || 0);
|
|
3370
|
+
const dedup = Number(opResult?.dedupSkipped || 0);
|
|
3371
|
+
likesCount = Math.max(0, likesCount + added);
|
|
3372
|
+
likesSkippedCount = Math.max(0, likesSkippedCount + skipped);
|
|
3373
|
+
likesAlreadyCount = Math.max(0, likesAlreadyCount + already);
|
|
3374
|
+
likesDedupCount = Math.max(0, likesDedupCount + dedup);
|
|
3375
|
+
statLikes.textContent = `${likesCount}\u6B21 (\u8DF3\u8FC7:${likesSkippedCount}, \u5DF2\u8D5E:${likesAlreadyCount}, \u53BB\u91CD:${likesDedupCount})`;
|
|
2461
3376
|
}
|
|
2462
3377
|
return;
|
|
2463
3378
|
}
|
|
@@ -2479,13 +3394,24 @@ function renderDashboard(root, ctx2) {
|
|
|
2479
3394
|
}
|
|
2480
3395
|
if (event === "xhs.unified.stop") {
|
|
2481
3396
|
const reason = String(payload.reason || "").trim();
|
|
3397
|
+
const stoppedTs = Date.parse(String(payload.stoppedAt || payload.ts || "")) || Date.now();
|
|
3398
|
+
stoppedAt = stoppedTs;
|
|
3399
|
+
activeStatus = reason ? normalizeStatus(reason) || "stopped" : "stopped";
|
|
3400
|
+
updateElapsed();
|
|
3401
|
+
stopElapsedTimer();
|
|
3402
|
+
const successReasons = /* @__PURE__ */ new Set(["completed", "script_complete"]);
|
|
2482
3403
|
currentPhase.textContent = reason && reason !== "script_failure" ? "\u5DF2\u7ED3\u675F" : "\u5931\u8D25";
|
|
2483
3404
|
currentAction.textContent = reason || "stop";
|
|
2484
|
-
if (reason && reason
|
|
3405
|
+
if (reason && !successReasons.has(reason)) {
|
|
2485
3406
|
pushRecentError(`stop reason=${reason}`, event);
|
|
2486
3407
|
}
|
|
2487
3408
|
renderRunSummary();
|
|
2488
3409
|
}
|
|
3410
|
+
if (event === "autoscript:operation_terminal") {
|
|
3411
|
+
const code = String(payload.code || "").trim();
|
|
3412
|
+
currentAction.textContent = code ? `terminal:${code}` : "terminal";
|
|
3413
|
+
renderRunSummary();
|
|
3414
|
+
}
|
|
2489
3415
|
}
|
|
2490
3416
|
function parseLineEvent(line) {
|
|
2491
3417
|
const text = String(line || "").trim();
|
|
@@ -2496,12 +3422,110 @@ function renderDashboard(root, ctx2) {
|
|
|
2496
3422
|
} catch {
|
|
2497
3423
|
}
|
|
2498
3424
|
}
|
|
3425
|
+
function applySummary(summary) {
|
|
3426
|
+
if (!summary || typeof summary !== "object") return;
|
|
3427
|
+
const totals = summary?.totals && typeof summary.totals === "object" ? summary.totals : {};
|
|
3428
|
+
const profiles = Array.isArray(summary?.profiles) ? summary.profiles : [];
|
|
3429
|
+
const profile = profiles[0] || null;
|
|
3430
|
+
const stats = profile?.stats && typeof profile.stats === "object" ? profile.stats : totals;
|
|
3431
|
+
const assigned = Number(stats?.assignedNotes ?? totals?.assignedNotes ?? summary?.target ?? 0) || 0;
|
|
3432
|
+
const opened = Number(stats?.openedNotes ?? totals?.openedNotes ?? totals?.assignedNotes ?? 0) || 0;
|
|
3433
|
+
const failed = Number(totals?.operationErrors ?? 0) || 0;
|
|
3434
|
+
const remaining = Math.max(0, assigned - opened);
|
|
3435
|
+
statCollected.textContent = String(opened);
|
|
3436
|
+
statSuccess.textContent = String(opened);
|
|
3437
|
+
statFailed.textContent = String(failed);
|
|
3438
|
+
statRemaining.textContent = String(remaining);
|
|
3439
|
+
let percent = 0;
|
|
3440
|
+
if (assigned > 0) {
|
|
3441
|
+
percent = Math.round(opened / assigned * 100);
|
|
3442
|
+
}
|
|
3443
|
+
progressPercent.textContent = `${percent}%`;
|
|
3444
|
+
progressBar.style.width = `${percent}%`;
|
|
3445
|
+
const comments = Number(stats?.commentsCollected ?? totals?.commentsCollected ?? 0);
|
|
3446
|
+
if (Number.isFinite(comments)) {
|
|
3447
|
+
commentsCount = Math.max(0, Math.floor(comments));
|
|
3448
|
+
statComments.textContent = `${commentsCount}\u6761`;
|
|
3449
|
+
}
|
|
3450
|
+
const likesNew = Number(stats?.likesNewCount ?? totals?.likesNewCount ?? 0);
|
|
3451
|
+
const likesSkipped = Number(stats?.likesSkippedCount ?? totals?.likesSkippedCount ?? 0);
|
|
3452
|
+
const likesAlready = Number(stats?.likesAlreadyCount ?? totals?.likesAlreadyCount ?? 0);
|
|
3453
|
+
const likesDedup = Number(stats?.likesDedupCount ?? totals?.likesDedupCount ?? 0);
|
|
3454
|
+
likesCount = Math.max(0, Math.floor(likesNew || 0));
|
|
3455
|
+
likesSkippedCount = Math.max(0, Math.floor(likesSkipped || 0));
|
|
3456
|
+
likesAlreadyCount = Math.max(0, Math.floor(likesAlready || 0));
|
|
3457
|
+
likesDedupCount = Math.max(0, Math.floor(likesDedup || 0));
|
|
3458
|
+
statLikes.textContent = `${likesCount}\u6B21(\u8DF3\u8FC7:${likesSkippedCount}, \u5DF2\u8D5E:${likesAlreadyCount}, \u53BB\u91CD:${likesDedupCount})`;
|
|
3459
|
+
if (summary.keyword) taskKeyword.textContent = String(summary.keyword);
|
|
3460
|
+
if (assigned) taskTarget.textContent = String(assigned);
|
|
3461
|
+
if (profile?.profileId) {
|
|
3462
|
+
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3463
|
+
taskAccount.textContent = aliases[profile.profileId] || profile.profileId;
|
|
3464
|
+
}
|
|
3465
|
+
const runId = String(profile?.runId || summary?.runId || "").trim();
|
|
3466
|
+
if (runId) {
|
|
3467
|
+
activeRunId = runId;
|
|
3468
|
+
renderRunSummary();
|
|
3469
|
+
}
|
|
3470
|
+
const reason = String(profile?.reason || summary?.status || "").trim();
|
|
3471
|
+
if (reason) {
|
|
3472
|
+
const okReasons = /* @__PURE__ */ new Set(["script_complete", "completed", "success", "succeeded"]);
|
|
3473
|
+
currentPhase.textContent = okReasons.has(reason) ? "\u5DF2\u7ED3\u675F" : "\u5931\u8D25";
|
|
3474
|
+
currentAction.textContent = reason;
|
|
3475
|
+
activeStatus = normalizeStatus(reason) || activeStatus;
|
|
3476
|
+
}
|
|
3477
|
+
const summaryTs = Date.parse(String(summary?.generatedAt || "")) || Date.now();
|
|
3478
|
+
stoppedAt = summaryTs;
|
|
3479
|
+
updateElapsed();
|
|
3480
|
+
stopElapsedTimer();
|
|
3481
|
+
const errorTotal = Number(totals?.operationErrors ?? 0) + Number(totals?.recoveryFailed ?? 0);
|
|
3482
|
+
if (Number.isFinite(errorTotal)) {
|
|
3483
|
+
errorCountTotal = Math.max(0, Math.floor(errorTotal));
|
|
3484
|
+
renderRunSummary();
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
async function loadLatestSummary() {
|
|
3488
|
+
if (typeof ctx2.api?.resultsScan !== "function") return null;
|
|
3489
|
+
if (typeof ctx2.api?.fsListDir !== "function") return null;
|
|
3490
|
+
if (typeof ctx2.api?.fsReadTextPreview !== "function") return null;
|
|
3491
|
+
const res = await ctx2.api.resultsScan({ downloadRoot: ctx2.settings?.downloadRoot });
|
|
3492
|
+
if (!res?.ok || !Array.isArray(res?.entries) || res.entries.length === 0) return null;
|
|
3493
|
+
const keyword = String(taskKeyword.textContent || "").trim();
|
|
3494
|
+
const matched = keyword ? res.entries.find((e) => e?.keyword === keyword) : null;
|
|
3495
|
+
const entry = matched || res.entries[0];
|
|
3496
|
+
if (!entry?.path) return null;
|
|
3497
|
+
const mergedRoot = ctx2.api.pathJoin(entry.path, "merged");
|
|
3498
|
+
const list = await ctx2.api.fsListDir({ root: mergedRoot, recursive: true, maxEntries: 3e3 });
|
|
3499
|
+
if (!list?.ok || !Array.isArray(list?.entries)) return null;
|
|
3500
|
+
const summaries = list.entries.filter((e) => !e?.isDir && e?.name === "summary.json");
|
|
3501
|
+
if (summaries.length === 0) return null;
|
|
3502
|
+
summaries.sort((a, b) => (b?.mtimeMs || 0) - (a?.mtimeMs || 0));
|
|
3503
|
+
const summaryPath = summaries[0].path;
|
|
3504
|
+
if (!summaryPath) return null;
|
|
3505
|
+
const textRes = await ctx2.api.fsReadTextPreview({ path: summaryPath, maxBytes: 1e6, maxLines: 2e4 });
|
|
3506
|
+
if (!textRes?.ok || !textRes?.text) return null;
|
|
3507
|
+
try {
|
|
3508
|
+
return JSON.parse(textRes.text);
|
|
3509
|
+
} catch {
|
|
3510
|
+
return null;
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
2499
3513
|
function subscribeToUpdates() {
|
|
2500
3514
|
if (typeof ctx2.api?.onStateUpdate === "function") {
|
|
2501
3515
|
unsubscribeState = ctx2.api.onStateUpdate((update) => {
|
|
2502
3516
|
if (paused) return;
|
|
2503
3517
|
const runId = String(update?.runId || "").trim();
|
|
2504
|
-
|
|
3518
|
+
const status = normalizeStatus(update?.data?.status);
|
|
3519
|
+
if (activeRunId && runId && runId !== activeRunId) {
|
|
3520
|
+
if (isTerminalStatus(activeStatus) && (isRunningStatus(status) || status)) {
|
|
3521
|
+
activeRunId = runId;
|
|
3522
|
+
activeStatus = status || "running";
|
|
3523
|
+
stoppedAt = null;
|
|
3524
|
+
renderRunSummary();
|
|
3525
|
+
} else {
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
2505
3529
|
if (!activeRunId && runId) {
|
|
2506
3530
|
activeRunId = runId;
|
|
2507
3531
|
renderRunSummary();
|
|
@@ -2517,12 +3541,22 @@ function renderDashboard(root, ctx2) {
|
|
|
2517
3541
|
}
|
|
2518
3542
|
});
|
|
2519
3543
|
}
|
|
3544
|
+
if (typeof ctx2.api?.onBusEvent === "function") {
|
|
3545
|
+
unsubscribeBus = ctx2.api.onBusEvent((payload) => {
|
|
3546
|
+
if (paused) return;
|
|
3547
|
+
if (payload && payload.event) {
|
|
3548
|
+
updateFromEventPayload(payload);
|
|
3549
|
+
}
|
|
3550
|
+
});
|
|
3551
|
+
}
|
|
2520
3552
|
if (typeof ctx2.api?.onCmdEvent === "function") {
|
|
2521
3553
|
unsubscribeCmd = ctx2.api.onCmdEvent((evt) => {
|
|
2522
3554
|
if (paused) return;
|
|
2523
3555
|
const runId = String(evt?.runId || "").trim();
|
|
2524
|
-
if (!activeRunId && evt?.type === "started" && String(evt?.title || "").includes("xhs unified")) {
|
|
3556
|
+
if ((isTerminalStatus(activeStatus) || !activeRunId) && evt?.type === "started" && String(evt?.title || "").includes("xhs unified")) {
|
|
2525
3557
|
activeRunId = runId;
|
|
3558
|
+
activeStatus = "running";
|
|
3559
|
+
stoppedAt = null;
|
|
2526
3560
|
renderRunSummary();
|
|
2527
3561
|
}
|
|
2528
3562
|
if (activeRunId && runId && runId !== activeRunId) return;
|
|
@@ -2535,9 +3569,16 @@ function renderDashboard(root, ctx2) {
|
|
|
2535
3569
|
const failed = Number(statFailed.textContent || "0") || 0;
|
|
2536
3570
|
statFailed.textContent = String(failed + 1);
|
|
2537
3571
|
} else if (evt.type === "exit") {
|
|
2538
|
-
|
|
2539
|
-
|
|
3572
|
+
if (!stoppedAt) {
|
|
3573
|
+
currentPhase.textContent = Number(evt.exitCode || 0) === 0 ? "\u5DF2\u7ED3\u675F" : "\u5931\u8D25";
|
|
3574
|
+
currentAction.textContent = `exit(${evt.exitCode ?? "null"})`;
|
|
3575
|
+
}
|
|
2540
3576
|
addLog(`\u8FDB\u7A0B\u9000\u51FA: code=${evt.exitCode}`, evt.exitCode === 0 ? "success" : "error");
|
|
3577
|
+
if (!stoppedAt) {
|
|
3578
|
+
stoppedAt = Date.now();
|
|
3579
|
+
updateElapsed();
|
|
3580
|
+
stopElapsedTimer();
|
|
3581
|
+
}
|
|
2541
3582
|
if (Number(evt.exitCode || 0) !== 0) {
|
|
2542
3583
|
pushRecentError(`\u8FDB\u7A0B\u9000\u51FA code=${evt.exitCode ?? "null"}`, "exit");
|
|
2543
3584
|
}
|
|
@@ -2556,6 +3597,10 @@ function renderDashboard(root, ctx2) {
|
|
|
2556
3597
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
2557
3598
|
taskAccount.textContent = aliases[config.lastProfileId] || config.lastProfileId;
|
|
2558
3599
|
}
|
|
3600
|
+
const taskId = String(config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3601
|
+
if (taskId) {
|
|
3602
|
+
taskConfigId.textContent = taskId;
|
|
3603
|
+
}
|
|
2559
3604
|
}
|
|
2560
3605
|
} catch (err) {
|
|
2561
3606
|
console.error("Failed to load task info:", err);
|
|
@@ -2574,6 +3619,9 @@ function renderDashboard(root, ctx2) {
|
|
|
2574
3619
|
}
|
|
2575
3620
|
updateFromTaskState(picked);
|
|
2576
3621
|
}
|
|
3622
|
+
} else {
|
|
3623
|
+
const summary = await loadLatestSummary();
|
|
3624
|
+
if (summary) applySummary(summary);
|
|
2577
3625
|
}
|
|
2578
3626
|
} catch (err) {
|
|
2579
3627
|
console.error("Failed to fetch state:", err);
|
|
@@ -2597,9 +3645,16 @@ function renderDashboard(root, ctx2) {
|
|
|
2597
3645
|
if (confirm("\u786E\u5B9A\u8981\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u5417\uFF1F")) {
|
|
2598
3646
|
try {
|
|
2599
3647
|
const tasks = await ctx2.api.stateGetTasks();
|
|
2600
|
-
|
|
2601
|
-
|
|
3648
|
+
let runIdToStop = String(activeRunId || "").trim();
|
|
3649
|
+
if (!runIdToStop && Array.isArray(tasks)) {
|
|
3650
|
+
const running = tasks.find((item) => ["running", "queued", "pending", "starting"].includes(String(item?.status || "").toLowerCase()));
|
|
3651
|
+
runIdToStop = String(running?.runId || tasks[0]?.runId || "").trim();
|
|
3652
|
+
}
|
|
3653
|
+
if (runIdToStop) {
|
|
3654
|
+
await ctx2.api.cmdKill(runIdToStop);
|
|
2602
3655
|
addLog("\u4EFB\u52A1\u5DF2\u505C\u6B62", "warn");
|
|
3656
|
+
} else {
|
|
3657
|
+
addLog("\u672A\u627E\u5230\u53EF\u505C\u6B62\u7684\u8FD0\u884C\u4EFB\u52A1", "warn");
|
|
2603
3658
|
}
|
|
2604
3659
|
} catch (err) {
|
|
2605
3660
|
console.error("Failed to stop task:", err);
|
|
@@ -2611,21 +3666,68 @@ function renderDashboard(root, ctx2) {
|
|
|
2611
3666
|
}, 1500);
|
|
2612
3667
|
}
|
|
2613
3668
|
};
|
|
3669
|
+
backConfigBtn.onclick = () => {
|
|
3670
|
+
if (typeof ctx2.setActiveTab === "function") {
|
|
3671
|
+
ctx2.setActiveTab("config");
|
|
3672
|
+
}
|
|
3673
|
+
};
|
|
2614
3674
|
renderRunSummary();
|
|
2615
3675
|
loadTaskInfo();
|
|
2616
3676
|
subscribeToUpdates();
|
|
2617
3677
|
fetchCurrentState();
|
|
2618
|
-
|
|
3678
|
+
startElapsedTimer();
|
|
2619
3679
|
return () => {
|
|
2620
|
-
|
|
3680
|
+
stopElapsedTimer();
|
|
2621
3681
|
if (unsubscribeState) unsubscribeState();
|
|
2622
3682
|
if (unsubscribeCmd) unsubscribeCmd();
|
|
3683
|
+
if (unsubscribeBus) unsubscribeBus();
|
|
2623
3684
|
};
|
|
2624
3685
|
}
|
|
2625
3686
|
|
|
2626
3687
|
// src/renderer/tabs-new/account-manager.mts
|
|
3688
|
+
var PLATFORM_ICON = {
|
|
3689
|
+
xiaohongshu: "\u{1F4D5}",
|
|
3690
|
+
xhs: "\u{1F4D5}",
|
|
3691
|
+
weibo: "\u{1F9E3}"
|
|
3692
|
+
};
|
|
3693
|
+
var PLATFORM_LABEL = {
|
|
3694
|
+
xiaohongshu: "\u5C0F\u7EA2\u4E66",
|
|
3695
|
+
xhs: "\u5C0F\u7EA2\u4E66",
|
|
3696
|
+
weibo: "\u5FAE\u535A"
|
|
3697
|
+
};
|
|
3698
|
+
function normalizePlatform(value) {
|
|
3699
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
3700
|
+
if (!normalized) return "xiaohongshu";
|
|
3701
|
+
if (normalized === "xhs") return "xiaohongshu";
|
|
3702
|
+
return normalized;
|
|
3703
|
+
}
|
|
3704
|
+
function getPlatformInfo(platform) {
|
|
3705
|
+
const key = normalizePlatform(platform);
|
|
3706
|
+
return {
|
|
3707
|
+
key,
|
|
3708
|
+
icon: PLATFORM_ICON[key] || "\u{1F310}",
|
|
3709
|
+
label: PLATFORM_LABEL[key] || key,
|
|
3710
|
+
loginUrl: key === "weibo" ? "https://weibo.com" : "https://www.xiaohongshu.com"
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3713
|
+
function formatTs(value) {
|
|
3714
|
+
if (!Number.isFinite(value) || Number(value) <= 0) return "\u672A\u68C0\u67E5";
|
|
3715
|
+
try {
|
|
3716
|
+
return new Date(Number(value)).toLocaleString("zh-CN");
|
|
3717
|
+
} catch {
|
|
3718
|
+
return "\u672A\u68C0\u67E5";
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
function toTimestamp(value) {
|
|
3722
|
+
const text = String(value || "").trim();
|
|
3723
|
+
if (!text) return null;
|
|
3724
|
+
const parsed = Date.parse(text);
|
|
3725
|
+
if (!Number.isFinite(parsed)) return null;
|
|
3726
|
+
return parsed;
|
|
3727
|
+
}
|
|
2627
3728
|
function renderAccountManager(root, ctx2) {
|
|
2628
3729
|
root.innerHTML = "";
|
|
3730
|
+
const autoSyncTimers = /* @__PURE__ */ new Map();
|
|
2629
3731
|
const bentoGrid = createEl("div", { className: "bento-grid bento-sidebar" });
|
|
2630
3732
|
const envCard = createEl("div", { className: "bento-cell" });
|
|
2631
3733
|
envCard.innerHTML = `
|
|
@@ -2633,7 +3735,7 @@ function renderAccountManager(root, ctx2) {
|
|
|
2633
3735
|
<div class="env-status-grid">
|
|
2634
3736
|
<div class="env-item" id="env-camo">
|
|
2635
3737
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
2636
|
-
<span>
|
|
3738
|
+
<span>Camo CLI (@web-auto/camo)</span>
|
|
2637
3739
|
</div>
|
|
2638
3740
|
<div class="env-item" id="env-unified">
|
|
2639
3741
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
@@ -2641,15 +3743,16 @@ function renderAccountManager(root, ctx2) {
|
|
|
2641
3743
|
</div>
|
|
2642
3744
|
<div class="env-item" id="env-browser">
|
|
2643
3745
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
2644
|
-
<span>
|
|
3746
|
+
<span>Camo Runtime Service (7704\uFF0C\u53EF\u9009)</span>
|
|
2645
3747
|
</div>
|
|
2646
3748
|
<div class="env-item" id="env-firefox">
|
|
2647
3749
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
2648
|
-
<span>
|
|
3750
|
+
<span>Camoufox Runtime (python -m camoufox)</span>
|
|
2649
3751
|
</div>
|
|
2650
3752
|
</div>
|
|
2651
|
-
<div style="margin-top: var(--gap);">
|
|
2652
|
-
<button id="recheck-env-btn" class="secondary" style="
|
|
3753
|
+
<div class="btn-group" style="margin-top: var(--gap);">
|
|
3754
|
+
<button id="recheck-env-btn" class="secondary" style="flex: 1;">\u91CD\u65B0\u68C0\u67E5</button>
|
|
3755
|
+
<button id="env-cleanup-btn" class="secondary" style="flex: 1;">\u4E00\u952E\u6E05\u7406</button>
|
|
2653
3756
|
</div>
|
|
2654
3757
|
`;
|
|
2655
3758
|
bentoGrid.appendChild(envCard);
|
|
@@ -2659,7 +3762,11 @@ function renderAccountManager(root, ctx2) {
|
|
|
2659
3762
|
\u8D26\u6237\u5217\u8868
|
|
2660
3763
|
<button id="add-account-btn" style="margin-left: auto; padding: 6px 12px; font-size: 12px;">\u6DFB\u52A0\u8D26\u6237</button>
|
|
2661
3764
|
</div>
|
|
2662
|
-
<div
|
|
3765
|
+
<div class="row" style="margin: 8px 0; gap: 8px; align-items: center;">
|
|
3766
|
+
<input id="new-account-alias-input" placeholder="\u522B\u540D\u53EF\u9009\uFF08\u767B\u5F55\u540E\u81EA\u52A8\u8BC6\u522B\uFF09" style="flex: 1; min-width: 180px;" />
|
|
3767
|
+
<button id="add-account-confirm-btn" class="secondary" style="flex: 0 0 auto;">\u521B\u5EFA\u5E76\u767B\u5F55</button>
|
|
3768
|
+
</div>
|
|
3769
|
+
<div id="account-list" class="account-list" style="margin-bottom: var(--gap);"></div>
|
|
2663
3770
|
<div style="margin-top: var(--gap);">
|
|
2664
3771
|
<div class="btn-group">
|
|
2665
3772
|
<button id="check-all-btn" class="secondary" style="flex: 1;">\u68C0\u67E5\u6240\u6709</button>
|
|
@@ -2670,11 +3777,17 @@ function renderAccountManager(root, ctx2) {
|
|
|
2670
3777
|
bentoGrid.appendChild(accountCard);
|
|
2671
3778
|
root.appendChild(bentoGrid);
|
|
2672
3779
|
const recheckEnvBtn = root.querySelector("#recheck-env-btn");
|
|
3780
|
+
const envCleanupBtn = root.querySelector("#env-cleanup-btn");
|
|
2673
3781
|
const addAccountBtn = root.querySelector("#add-account-btn");
|
|
3782
|
+
const addAccountConfirmBtn = root.querySelector("#add-account-confirm-btn");
|
|
3783
|
+
const newAccountAliasInput = root.querySelector("#new-account-alias-input");
|
|
2674
3784
|
const checkAllBtn = root.querySelector("#check-all-btn");
|
|
2675
3785
|
const refreshExpiredBtn = root.querySelector("#refresh-expired-btn");
|
|
2676
3786
|
const accountListEl = root.querySelector("#account-list");
|
|
2677
3787
|
let accounts = [];
|
|
3788
|
+
let envCheckInFlight = false;
|
|
3789
|
+
let accountCheckInFlight = false;
|
|
3790
|
+
let busUnsubscribe = null;
|
|
2678
3791
|
async function checkEnvironment() {
|
|
2679
3792
|
try {
|
|
2680
3793
|
const [camo, services, firefox] = await Promise.all([
|
|
@@ -2684,12 +3797,21 @@ function renderAccountManager(root, ctx2) {
|
|
|
2684
3797
|
]);
|
|
2685
3798
|
updateEnvItem("env-camo", camo.installed);
|
|
2686
3799
|
updateEnvItem("env-unified", services.unifiedApi);
|
|
2687
|
-
updateEnvItem("env-browser", services.
|
|
3800
|
+
updateEnvItem("env-browser", services.camoRuntime);
|
|
2688
3801
|
updateEnvItem("env-firefox", firefox.installed);
|
|
2689
3802
|
} catch (err) {
|
|
2690
3803
|
console.error("Environment check failed:", err);
|
|
2691
3804
|
}
|
|
2692
3805
|
}
|
|
3806
|
+
async function tickEnvironment() {
|
|
3807
|
+
if (envCheckInFlight) return;
|
|
3808
|
+
envCheckInFlight = true;
|
|
3809
|
+
try {
|
|
3810
|
+
await checkEnvironment();
|
|
3811
|
+
} finally {
|
|
3812
|
+
envCheckInFlight = false;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
2693
3815
|
function updateEnvItem(id, ok) {
|
|
2694
3816
|
const el = root.querySelector(`#${id}`);
|
|
2695
3817
|
if (!el) return;
|
|
@@ -2702,13 +3824,28 @@ function renderAccountManager(root, ctx2) {
|
|
|
2702
3824
|
const rows = await listAccountProfiles(ctx2.api);
|
|
2703
3825
|
accounts = rows.map((row) => ({
|
|
2704
3826
|
...row,
|
|
2705
|
-
|
|
3827
|
+
platform: normalizePlatform(row.platform),
|
|
3828
|
+
statusView: row.valid ? "valid" : row.status === "pending" ? "pending" : "expired",
|
|
3829
|
+
lastCheckAt: toTimestamp(row.updatedAt)
|
|
2706
3830
|
}));
|
|
2707
3831
|
renderAccountList();
|
|
2708
3832
|
} catch (err) {
|
|
2709
3833
|
console.error("Failed to load accounts:", err);
|
|
2710
3834
|
}
|
|
2711
3835
|
}
|
|
3836
|
+
async function tickAccounts() {
|
|
3837
|
+
if (accountCheckInFlight) return;
|
|
3838
|
+
accountCheckInFlight = true;
|
|
3839
|
+
try {
|
|
3840
|
+
await loadAccounts();
|
|
3841
|
+
const pending = accounts.filter((acc) => acc.statusView === "pending");
|
|
3842
|
+
for (const acc of pending) {
|
|
3843
|
+
await checkAccountStatus(acc.profileId, { pendingWhileLogin: true });
|
|
3844
|
+
}
|
|
3845
|
+
} finally {
|
|
3846
|
+
accountCheckInFlight = false;
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
2712
3849
|
function renderAccountList() {
|
|
2713
3850
|
accountListEl.innerHTML = "";
|
|
2714
3851
|
if (accounts.length === 0) {
|
|
@@ -2716,24 +3853,34 @@ function renderAccountManager(root, ctx2) {
|
|
|
2716
3853
|
return;
|
|
2717
3854
|
}
|
|
2718
3855
|
accounts.forEach((acc) => {
|
|
3856
|
+
const platform = getPlatformInfo(acc.platform);
|
|
2719
3857
|
const row = createEl("div", {
|
|
2720
3858
|
className: "account-item",
|
|
2721
|
-
style: "display:
|
|
3859
|
+
style: "display: flex; gap: var(--gap-sm); padding: var(--gap-sm); align-items: center; border-bottom: 1px solid var(--border);"
|
|
2722
3860
|
});
|
|
2723
|
-
const nameDiv = createEl("div", {}, [
|
|
2724
|
-
createEl("div", { className: "account-name"
|
|
2725
|
-
|
|
3861
|
+
const nameDiv = createEl("div", { style: "min-width: 0; flex: 1;" }, [
|
|
3862
|
+
createEl("div", { className: "account-name", style: "display: flex; gap: 6px; align-items: center;" }, [
|
|
3863
|
+
createEl("span", { style: "font-size: 13px;" }, [platform.icon]),
|
|
3864
|
+
createEl("span", {}, [acc.alias || acc.name || acc.profileId]),
|
|
3865
|
+
createEl("span", { style: "font-size: 11px; color: var(--text-3);" }, [platform.label])
|
|
3866
|
+
]),
|
|
3867
|
+
createEl("div", { className: "account-alias", style: "font-size: 11px; color: var(--text-3);" }, [
|
|
3868
|
+
`profile: ${acc.profileId} \xB7 \u4E0A\u6B21\u68C0\u67E5: ${formatTs(acc.lastCheckAt)}`
|
|
3869
|
+
])
|
|
2726
3870
|
]);
|
|
2727
3871
|
const statusBadge = createEl("span", {
|
|
2728
|
-
className: `status-badge ${acc.statusView === "valid" ? "status-valid" : acc.statusView === "expired" ? "status-expired" : "status-pending"}
|
|
3872
|
+
className: `status-badge ${acc.statusView === "valid" ? "status-valid" : acc.statusView === "expired" ? "status-expired" : "status-pending"}`,
|
|
3873
|
+
style: "min-width: 76px; text-align: center;"
|
|
2729
3874
|
}, [
|
|
2730
|
-
acc.statusView === "valid" ? "\u2713 \u6709\u6548" : acc.statusView === "expired" ? "\u2717 \u5931\u6548" : acc.statusView === "checking" ? "\u23F3 \u68C0\u67E5\u4E2D" : "\u23F3 \u5F85\
|
|
3875
|
+
acc.statusView === "valid" ? "\u2713 \u6709\u6548" : acc.statusView === "expired" ? "\u2717 \u5931\u6548" : acc.statusView === "checking" ? "\u23F3 \u68C0\u67E5\u4E2D" : "\u23F3 \u5F85\u767B\u5F55"
|
|
2731
3876
|
]);
|
|
2732
|
-
const
|
|
2733
|
-
className: "
|
|
2734
|
-
style: "
|
|
2735
|
-
}
|
|
2736
|
-
const
|
|
3877
|
+
const actionsDiv = createEl("div", {
|
|
3878
|
+
className: "btn-group",
|
|
3879
|
+
style: "display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; flex: 0 0 auto;"
|
|
3880
|
+
});
|
|
3881
|
+
const checkBtn = createEl("button", { className: "secondary", style: "padding: 6px 8px; font-size: 10px;" }, ["\u68C0\u67E5"]);
|
|
3882
|
+
const openBtn = createEl("button", { className: "secondary", style: "padding: 6px 8px; font-size: 10px;" }, ["\u6253\u5F00"]);
|
|
3883
|
+
const fixBtn = createEl("button", { className: "secondary", style: "padding: 6px 8px; font-size: 10px;" }, ["\u4FEE\u590D"]);
|
|
2737
3884
|
const detailBtn = createEl("button", {
|
|
2738
3885
|
className: "secondary",
|
|
2739
3886
|
style: "padding: 6px 8px; font-size: 10px;"
|
|
@@ -2742,22 +3889,34 @@ function renderAccountManager(root, ctx2) {
|
|
|
2742
3889
|
className: "danger",
|
|
2743
3890
|
style: "padding: 6px 8px; font-size: 10px;"
|
|
2744
3891
|
}, ["\u5220\u9664"]);
|
|
3892
|
+
actionsDiv.appendChild(checkBtn);
|
|
3893
|
+
actionsDiv.appendChild(openBtn);
|
|
3894
|
+
actionsDiv.appendChild(fixBtn);
|
|
2745
3895
|
actionsDiv.appendChild(detailBtn);
|
|
2746
3896
|
actionsDiv.appendChild(deleteBtn);
|
|
2747
3897
|
row.appendChild(nameDiv);
|
|
2748
3898
|
row.appendChild(statusBadge);
|
|
2749
|
-
row.appendChild(checkBtn);
|
|
2750
3899
|
row.appendChild(actionsDiv);
|
|
2751
|
-
checkBtn.onclick = () =>
|
|
3900
|
+
checkBtn.onclick = () => {
|
|
3901
|
+
void checkAccountStatus(acc.profileId, { resolveAlias: true });
|
|
3902
|
+
};
|
|
3903
|
+
openBtn.onclick = () => {
|
|
3904
|
+
void openAccountLogin(acc, { reason: "manual_open" });
|
|
3905
|
+
};
|
|
3906
|
+
fixBtn.onclick = () => {
|
|
3907
|
+
void fixAccount(acc);
|
|
3908
|
+
};
|
|
2752
3909
|
detailBtn.onclick = () => {
|
|
2753
3910
|
alert(`\u8D26\u6237\u8BE6\u60C5:
|
|
2754
3911
|
|
|
3912
|
+
\u5E73\u53F0: ${platform.label}
|
|
2755
3913
|
Profile ID: ${acc.profileId}
|
|
2756
3914
|
\u8D26\u53F7ID: ${acc.accountId || "\u672A\u8BC6\u522B"}
|
|
2757
3915
|
\u522B\u540D: ${acc.alias || "\u672A\u8BBE\u7F6E"}
|
|
2758
3916
|
\u72B6\u6001: ${acc.status}
|
|
2759
3917
|
\u539F\u56E0: ${acc.reason || "-"}
|
|
2760
|
-
\u6700\u540E\u68C0\u67E5: ${acc.
|
|
3918
|
+
\u6700\u540E\u68C0\u67E5: ${formatTs(acc.lastCheckAt)}
|
|
3919
|
+
\u767B\u5F55\u5165\u53E3: ${platform.loginUrl}`);
|
|
2761
3920
|
};
|
|
2762
3921
|
deleteBtn.onclick = async () => {
|
|
2763
3922
|
if (confirm(`\u786E\u5B9A\u5220\u9664\u8D26\u6237 "${acc.alias || acc.profileId}" \u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002`)) {
|
|
@@ -2787,9 +3946,9 @@ Profile ID: ${acc.profileId}
|
|
|
2787
3946
|
accountListEl.appendChild(row);
|
|
2788
3947
|
});
|
|
2789
3948
|
}
|
|
2790
|
-
async function checkAccountStatus(profileId) {
|
|
3949
|
+
async function checkAccountStatus(profileId, options = {}) {
|
|
2791
3950
|
const account = accounts.find((a) => a.profileId === profileId);
|
|
2792
|
-
if (!account) return;
|
|
3951
|
+
if (!account) return false;
|
|
2793
3952
|
account.statusView = "checking";
|
|
2794
3953
|
renderAccountList();
|
|
2795
3954
|
try {
|
|
@@ -2800,51 +3959,110 @@ Profile ID: ${acc.profileId}
|
|
|
2800
3959
|
ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
|
|
2801
3960
|
"sync",
|
|
2802
3961
|
profileId,
|
|
3962
|
+
...options.pendingWhileLogin ? ["--pending-while-login"] : [],
|
|
3963
|
+
...options.resolveAlias ? ["--resolve-alias"] : [],
|
|
2803
3964
|
"--json"
|
|
2804
3965
|
]
|
|
2805
3966
|
});
|
|
2806
3967
|
const profile = result?.json?.profile;
|
|
2807
3968
|
if (profile && String(profile.profileId || "").trim() === profileId) {
|
|
2808
3969
|
account.accountId = String(profile.accountId || "").trim() || null;
|
|
2809
|
-
|
|
3970
|
+
const detectedAlias = String(profile.alias || "").trim();
|
|
3971
|
+
account.alias = detectedAlias || account.alias;
|
|
3972
|
+
account.platform = normalizePlatform(String(profile.platform || account.platform || "").trim());
|
|
2810
3973
|
account.status = String(profile.status || "").trim() || account.status;
|
|
2811
3974
|
account.valid = profile.valid === true && Boolean(account.accountId);
|
|
2812
3975
|
account.reason = String(profile.reason || "").trim() || null;
|
|
3976
|
+
if (detectedAlias) {
|
|
3977
|
+
const aliases = { ...ctx2.api?.settings?.profileAliases || {}, [profileId]: detectedAlias };
|
|
3978
|
+
await ctx2.api.settingsSet({ profileAliases: aliases }).catch(() => null);
|
|
3979
|
+
if (typeof ctx2.refreshSettings === "function") {
|
|
3980
|
+
await ctx2.refreshSettings().catch(() => null);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
2813
3983
|
}
|
|
2814
|
-
account.statusView = account.valid ? "valid" : "expired";
|
|
2815
|
-
account.
|
|
3984
|
+
account.statusView = account.valid ? "valid" : account.status === "pending" ? "pending" : "expired";
|
|
3985
|
+
account.lastCheckAt = Date.now();
|
|
2816
3986
|
} catch (err) {
|
|
2817
|
-
account.statusView = "expired";
|
|
3987
|
+
account.statusView = options.pendingWhileLogin ? "pending" : "expired";
|
|
3988
|
+
account.lastCheckAt = Date.now();
|
|
2818
3989
|
}
|
|
2819
3990
|
renderAccountList();
|
|
3991
|
+
return Boolean(account.valid);
|
|
3992
|
+
}
|
|
3993
|
+
async function openAccountLogin(account, options = {}) {
|
|
3994
|
+
if (!String(account.profileId || "").trim()) return false;
|
|
3995
|
+
const platform = getPlatformInfo(account.platform);
|
|
3996
|
+
const timeoutSec = Math.max(30, Number(ctx2.api?.settings?.timeouts?.loginTimeoutSec || 900));
|
|
3997
|
+
account.status = "pending";
|
|
3998
|
+
account.statusView = "pending";
|
|
3999
|
+
account.reason = String(options.reason || "manual_relogin");
|
|
4000
|
+
account.lastCheckAt = Date.now();
|
|
4001
|
+
renderAccountList();
|
|
4002
|
+
await ctx2.api.cmdSpawn({
|
|
4003
|
+
title: `\u767B\u5F55 ${account.alias || account.profileId}`,
|
|
4004
|
+
cwd: "",
|
|
4005
|
+
args: [
|
|
4006
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
4007
|
+
"login-profile",
|
|
4008
|
+
account.profileId,
|
|
4009
|
+
"--url",
|
|
4010
|
+
platform.loginUrl,
|
|
4011
|
+
"--wait-sync",
|
|
4012
|
+
"false",
|
|
4013
|
+
"--timeout-sec",
|
|
4014
|
+
String(timeoutSec),
|
|
4015
|
+
"--keep-session"
|
|
4016
|
+
],
|
|
4017
|
+
groupKey: "profilepool"
|
|
4018
|
+
});
|
|
4019
|
+
startAutoSyncProfile(account.profileId);
|
|
4020
|
+
return true;
|
|
4021
|
+
}
|
|
4022
|
+
async function fixAccount(account) {
|
|
4023
|
+
const ok = await checkAccountStatus(account.profileId, { resolveAlias: true });
|
|
4024
|
+
if (ok) return;
|
|
4025
|
+
await openAccountLogin(account, { reason: "fix_relogin" });
|
|
2820
4026
|
}
|
|
2821
4027
|
async function addAccount() {
|
|
2822
|
-
const alias =
|
|
2823
|
-
if (!alias?.trim()) return;
|
|
4028
|
+
const alias = newAccountAliasInput.value.trim();
|
|
2824
4029
|
try {
|
|
2825
4030
|
const out = await ctx2.api.cmdRunJson({
|
|
2826
|
-
title: "
|
|
4031
|
+
title: "account add",
|
|
2827
4032
|
cwd: "",
|
|
2828
|
-
args: [
|
|
4033
|
+
args: [
|
|
4034
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
|
|
4035
|
+
"add",
|
|
4036
|
+
"--platform",
|
|
4037
|
+
"xiaohongshu",
|
|
4038
|
+
"--status",
|
|
4039
|
+
"pending",
|
|
4040
|
+
...alias ? ["--alias", alias] : [],
|
|
4041
|
+
"--json"
|
|
4042
|
+
]
|
|
2829
4043
|
});
|
|
2830
|
-
|
|
4044
|
+
const profileId = String(out?.json?.account?.profileId || "").trim();
|
|
4045
|
+
if (!out?.ok || !profileId) {
|
|
2831
4046
|
alert("\u521B\u5EFA\u8D26\u53F7\u5931\u8D25: " + (out?.error || "\u672A\u77E5\u9519\u8BEF"));
|
|
2832
4047
|
return;
|
|
2833
4048
|
}
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
4049
|
+
if (alias) {
|
|
4050
|
+
const aliases = { ...ctx2.api.settings?.profileAliases, [profileId]: alias };
|
|
4051
|
+
await ctx2.api.settingsSet({ profileAliases: aliases });
|
|
4052
|
+
if (typeof ctx2.refreshSettings === "function") {
|
|
4053
|
+
await ctx2.refreshSettings();
|
|
4054
|
+
}
|
|
2839
4055
|
}
|
|
2840
4056
|
const timeoutSec = ctx2.api.settings?.timeouts?.loginTimeoutSec || 900;
|
|
2841
4057
|
await ctx2.api.cmdSpawn({
|
|
2842
|
-
title: `\u767B\u5F55 ${alias}`,
|
|
4058
|
+
title: `\u767B\u5F55 ${alias || profileId}`,
|
|
2843
4059
|
cwd: "",
|
|
2844
4060
|
args: [
|
|
2845
4061
|
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
2846
4062
|
"login-profile",
|
|
2847
4063
|
profileId,
|
|
4064
|
+
"--wait-sync",
|
|
4065
|
+
"false",
|
|
2848
4066
|
"--timeout-sec",
|
|
2849
4067
|
String(timeoutSec),
|
|
2850
4068
|
"--keep-session"
|
|
@@ -2852,21 +4070,52 @@ Profile ID: ${acc.profileId}
|
|
|
2852
4070
|
groupKey: "profilepool"
|
|
2853
4071
|
});
|
|
2854
4072
|
await loadAccounts();
|
|
4073
|
+
newAccountAliasInput.value = "";
|
|
4074
|
+
startAutoSyncProfile(profileId);
|
|
2855
4075
|
} catch (err) {
|
|
2856
4076
|
alert("\u6DFB\u52A0\u8D26\u53F7\u5931\u8D25: " + (err?.message || String(err)));
|
|
2857
4077
|
}
|
|
2858
4078
|
}
|
|
4079
|
+
function startAutoSyncProfile(profileId) {
|
|
4080
|
+
const id = String(profileId || "").trim();
|
|
4081
|
+
if (!id) return;
|
|
4082
|
+
const existing = autoSyncTimers.get(id);
|
|
4083
|
+
if (existing) clearInterval(existing);
|
|
4084
|
+
const timeoutSec = Math.max(30, Number(ctx2.api?.settings?.timeouts?.loginTimeoutSec || 900));
|
|
4085
|
+
const intervalMs = 2e3;
|
|
4086
|
+
const maxAttempts = Math.ceil(timeoutSec * 1e3 / intervalMs);
|
|
4087
|
+
let attempts = 0;
|
|
4088
|
+
void checkAccountStatus(id, { pendingWhileLogin: true }).then((ok) => {
|
|
4089
|
+
if (ok) {
|
|
4090
|
+
const timer2 = autoSyncTimers.get(id);
|
|
4091
|
+
if (timer2) clearInterval(timer2);
|
|
4092
|
+
autoSyncTimers.delete(id);
|
|
4093
|
+
void checkAccountStatus(id, { resolveAlias: true }).catch(() => null);
|
|
4094
|
+
}
|
|
4095
|
+
});
|
|
4096
|
+
const timer = setInterval(() => {
|
|
4097
|
+
attempts += 1;
|
|
4098
|
+
void checkAccountStatus(id, { pendingWhileLogin: true }).then((ok) => {
|
|
4099
|
+
if (ok || attempts >= maxAttempts) {
|
|
4100
|
+
const current = autoSyncTimers.get(id);
|
|
4101
|
+
if (current) clearInterval(current);
|
|
4102
|
+
autoSyncTimers.delete(id);
|
|
4103
|
+
}
|
|
4104
|
+
});
|
|
4105
|
+
}, intervalMs);
|
|
4106
|
+
autoSyncTimers.set(id, timer);
|
|
4107
|
+
}
|
|
2859
4108
|
async function checkAllAccounts() {
|
|
2860
4109
|
checkAllBtn.disabled = true;
|
|
2861
4110
|
checkAllBtn.textContent = "\u68C0\u67E5\u4E2D...";
|
|
2862
4111
|
for (const acc of accounts) {
|
|
2863
|
-
await checkAccountStatus(acc.profileId);
|
|
4112
|
+
await checkAccountStatus(acc.profileId, { resolveAlias: true });
|
|
2864
4113
|
}
|
|
2865
4114
|
checkAllBtn.disabled = false;
|
|
2866
4115
|
checkAllBtn.textContent = "\u68C0\u67E5\u6240\u6709";
|
|
2867
4116
|
}
|
|
2868
4117
|
async function refreshExpiredAccounts() {
|
|
2869
|
-
const expired = accounts.filter((a) => !a.valid);
|
|
4118
|
+
const expired = accounts.filter((a) => !a.valid && a.status !== "pending");
|
|
2870
4119
|
if (expired.length === 0) {
|
|
2871
4120
|
alert("\u6CA1\u6709\u5931\u6548\u7684\u8D26\u6237\u9700\u8981\u5237\u65B0");
|
|
2872
4121
|
return;
|
|
@@ -2875,20 +4124,7 @@ Profile ID: ${acc.profileId}
|
|
|
2875
4124
|
refreshExpiredBtn.textContent = "\u5237\u65B0\u4E2D...";
|
|
2876
4125
|
for (const acc of expired) {
|
|
2877
4126
|
try {
|
|
2878
|
-
|
|
2879
|
-
await ctx2.api.cmdSpawn({
|
|
2880
|
-
title: `\u91CD\u65B0\u767B\u5F55 ${acc.alias || acc.profileId}`,
|
|
2881
|
-
cwd: "",
|
|
2882
|
-
args: [
|
|
2883
|
-
ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
|
|
2884
|
-
"login",
|
|
2885
|
-
accountKey,
|
|
2886
|
-
"--url",
|
|
2887
|
-
"https://www.xiaohongshu.com",
|
|
2888
|
-
"--json"
|
|
2889
|
-
],
|
|
2890
|
-
groupKey: "profilepool"
|
|
2891
|
-
});
|
|
4127
|
+
await openAccountLogin(acc, { reason: "refresh_expired" });
|
|
2892
4128
|
} catch (err) {
|
|
2893
4129
|
console.error(`Failed to refresh ${acc.profileId}:`, err);
|
|
2894
4130
|
}
|
|
@@ -2896,19 +4132,683 @@ Profile ID: ${acc.profileId}
|
|
|
2896
4132
|
refreshExpiredBtn.disabled = false;
|
|
2897
4133
|
refreshExpiredBtn.textContent = "\u5237\u65B0\u5931\u6548";
|
|
2898
4134
|
}
|
|
4135
|
+
async function cleanupEnvironment() {
|
|
4136
|
+
if (!envCleanupBtn) return;
|
|
4137
|
+
envCleanupBtn.disabled = true;
|
|
4138
|
+
const previous = envCleanupBtn.textContent;
|
|
4139
|
+
envCleanupBtn.textContent = "\u6E05\u7406\u4E2D...";
|
|
4140
|
+
try {
|
|
4141
|
+
const result = typeof ctx2.api?.envCleanup === "function" ? await ctx2.api.envCleanup() : await ctx2.api.invoke?.("env:cleanup");
|
|
4142
|
+
if (!result?.ok) {
|
|
4143
|
+
alert(`\u73AF\u5883\u6E05\u7406\u5931\u8D25: ${result?.error || "\u672A\u77E5\u9519\u8BEF"}`);
|
|
4144
|
+
}
|
|
4145
|
+
await tickEnvironment();
|
|
4146
|
+
await tickAccounts();
|
|
4147
|
+
} catch (err) {
|
|
4148
|
+
alert(`\u73AF\u5883\u6E05\u7406\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4149
|
+
} finally {
|
|
4150
|
+
envCleanupBtn.disabled = false;
|
|
4151
|
+
envCleanupBtn.textContent = previous || "\u4E00\u952E\u6E05\u7406";
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
2899
4154
|
recheckEnvBtn.onclick = checkEnvironment;
|
|
4155
|
+
if (envCleanupBtn) envCleanupBtn.onclick = () => {
|
|
4156
|
+
void cleanupEnvironment();
|
|
4157
|
+
};
|
|
2900
4158
|
addAccountBtn.onclick = addAccount;
|
|
4159
|
+
addAccountConfirmBtn.onclick = addAccount;
|
|
4160
|
+
newAccountAliasInput.onkeydown = (ev) => {
|
|
4161
|
+
if (ev.key === "Enter") {
|
|
4162
|
+
ev.preventDefault();
|
|
4163
|
+
void addAccount();
|
|
4164
|
+
}
|
|
4165
|
+
};
|
|
2901
4166
|
checkAllBtn.onclick = checkAllAccounts;
|
|
2902
4167
|
refreshExpiredBtn.onclick = refreshExpiredAccounts;
|
|
2903
|
-
void
|
|
2904
|
-
void
|
|
4168
|
+
void tickEnvironment();
|
|
4169
|
+
void tickAccounts();
|
|
4170
|
+
if (typeof ctx2.api?.onBusEvent === "function") {
|
|
4171
|
+
busUnsubscribe = ctx2.api.onBusEvent((evt) => {
|
|
4172
|
+
const type = String(evt?.type || evt?.event || "").trim().toLowerCase();
|
|
4173
|
+
if (!type) return;
|
|
4174
|
+
if (type.startsWith("account:")) {
|
|
4175
|
+
void tickAccounts();
|
|
4176
|
+
}
|
|
4177
|
+
if (type.startsWith("env:")) {
|
|
4178
|
+
void tickEnvironment();
|
|
4179
|
+
}
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
return () => {
|
|
4183
|
+
for (const timer of autoSyncTimers.values()) clearInterval(timer);
|
|
4184
|
+
autoSyncTimers.clear();
|
|
4185
|
+
if (typeof busUnsubscribe === "function") busUnsubscribe();
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
|
|
4189
|
+
// src/renderer/tabs-new/scheduler.mts
|
|
4190
|
+
function commandTypeToWeiboTaskType2(commandType) {
|
|
4191
|
+
if (commandType === "weibo-search") return "search";
|
|
4192
|
+
if (commandType === "weibo-monitor") return "monitor";
|
|
4193
|
+
return "timeline";
|
|
4194
|
+
}
|
|
4195
|
+
function renderSchedulerPanel(root, ctx2) {
|
|
4196
|
+
root.innerHTML = "";
|
|
4197
|
+
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
4198
|
+
"\u5F53\u524D: ",
|
|
4199
|
+
createEl("span", {}, ["\u5B9A\u65F6\u4EFB\u52A1"]),
|
|
4200
|
+
" \u2192 \u914D\u7F6E\u5E76\u5B9A\u65F6\u6267\u884C\u591A\u6761\u4EFB\u52A1"
|
|
4201
|
+
]);
|
|
4202
|
+
root.appendChild(pageIndicator);
|
|
4203
|
+
const toolbar = createEl("div", { className: "bento-grid", style: "margin-bottom: var(--gap);" });
|
|
4204
|
+
const toolbarCell = createEl("div", { className: "bento-cell" });
|
|
4205
|
+
toolbarCell.innerHTML = `
|
|
4206
|
+
<div class="bento-title">\u8C03\u5EA6\u63A7\u5236</div>
|
|
4207
|
+
<div class="row">
|
|
4208
|
+
<button id="scheduler-refresh-btn" class="secondary">\u5237\u65B0\u5217\u8868</button>
|
|
4209
|
+
<button id="scheduler-run-due-btn" class="secondary">\u7ACB\u5373\u6267\u884C\u5230\u70B9\u4EFB\u52A1</button>
|
|
4210
|
+
<button id="scheduler-export-all-btn" class="secondary">\u5BFC\u51FA\u5168\u90E8</button>
|
|
4211
|
+
<button id="scheduler-import-btn" class="secondary">\u5BFC\u5165</button>
|
|
4212
|
+
</div>
|
|
4213
|
+
<div class="row" style="margin-top: 8px; align-items: center;">
|
|
4214
|
+
<span class="muted">\u5F53\u524D\u914D\u7F6E: <strong id="scheduler-active-task-id">-</strong></span>
|
|
4215
|
+
<button id="scheduler-open-config-btn" class="secondary">\u6253\u5F00\u914D\u7F6E\u9875</button>
|
|
4216
|
+
</div>
|
|
4217
|
+
<div class="row" style="margin-top: 8px; align-items: end;">
|
|
4218
|
+
<div>
|
|
4219
|
+
<label>Daemon \u95F4\u9694(\u79D2)</label>
|
|
4220
|
+
<input id="scheduler-daemon-interval" type="number" min="5" value="30" style="width: 120px;" />
|
|
4221
|
+
</div>
|
|
4222
|
+
<button id="scheduler-daemon-start-btn">\u542F\u52A8 Daemon</button>
|
|
4223
|
+
<button id="scheduler-daemon-stop-btn" class="danger">\u505C\u6B62 Daemon</button>
|
|
4224
|
+
<span id="scheduler-daemon-status" class="muted">daemon: \u672A\u542F\u52A8</span>
|
|
4225
|
+
</div>
|
|
4226
|
+
`;
|
|
4227
|
+
toolbar.appendChild(toolbarCell);
|
|
4228
|
+
root.appendChild(toolbar);
|
|
4229
|
+
const grid = createEl("div", { className: "bento-grid bento-sidebar" });
|
|
4230
|
+
const formCell = createEl("div", { className: "bento-cell" });
|
|
4231
|
+
formCell.innerHTML = `
|
|
4232
|
+
<div class="bento-title">\u4EFB\u52A1\u7F16\u8F91</div>
|
|
4233
|
+
<input id="scheduler-editing-id" type="hidden" />
|
|
4234
|
+
<div class="row">
|
|
4235
|
+
<div>
|
|
4236
|
+
<label>\u5E73\u53F0</label>
|
|
4237
|
+
<select id="scheduler-platform" style="width: 140px;">
|
|
4238
|
+
<option value="xiaohongshu">\u{1F4D5} \u5C0F\u7EA2\u4E66</option>
|
|
4239
|
+
<option value="weibo">\u{1F4F0} \u5FAE\u535A</option>
|
|
4240
|
+
<option value="1688">\u{1F6D2} 1688</option>
|
|
4241
|
+
</select>
|
|
4242
|
+
</div>
|
|
4243
|
+
<div>
|
|
4244
|
+
<label>\u4EFB\u52A1\u7C7B\u578B</label>
|
|
4245
|
+
<select id="scheduler-task-type" style="width: 160px;">
|
|
4246
|
+
</select>
|
|
4247
|
+
</div>
|
|
4248
|
+
</div>
|
|
4249
|
+
<div class="row">
|
|
4250
|
+
<div>
|
|
4251
|
+
<label>\u4EFB\u52A1\u540D</label>
|
|
4252
|
+
<input id="scheduler-name" placeholder="\u4F8B\u5982\uFF1Adeepseek-\u6BCF30\u5206\u949F" style="width: 240px;" />
|
|
4253
|
+
</div>
|
|
4254
|
+
<label style="display:flex; align-items:center; gap:8px; margin-top: 22px;">
|
|
4255
|
+
<input id="scheduler-enabled" type="checkbox" checked />
|
|
4256
|
+
<span>\u542F\u7528</span>
|
|
4257
|
+
</label>
|
|
4258
|
+
</div>
|
|
4259
|
+
<div class="row">
|
|
4260
|
+
<div>
|
|
4261
|
+
<label>\u8C03\u5EA6\u7C7B\u578B</label>
|
|
4262
|
+
<select id="scheduler-type" style="width: 140px;">
|
|
4263
|
+
<option value="interval">\u5FAA\u73AF\u95F4\u9694</option>
|
|
4264
|
+
<option value="once">\u4E00\u6B21\u6027</option>
|
|
4265
|
+
<option value="daily">\u6BCF\u5929</option>
|
|
4266
|
+
<option value="weekly">\u6BCF\u5468</option>
|
|
4267
|
+
</select>
|
|
4268
|
+
</div>
|
|
4269
|
+
<div id="scheduler-interval-wrap">
|
|
4270
|
+
<label>\u95F4\u9694\u5206\u949F</label>
|
|
4271
|
+
<input id="scheduler-interval" type="number" min="1" value="30" style="width: 120px;" />
|
|
4272
|
+
</div>
|
|
4273
|
+
<div id="scheduler-runat-wrap" style="display:none;">
|
|
4274
|
+
<label>\u951A\u70B9\u65F6\u95F4</label>
|
|
4275
|
+
<input id="scheduler-runat" type="datetime-local" style="width: 220px;" />
|
|
4276
|
+
</div>
|
|
4277
|
+
<div>
|
|
4278
|
+
<label>\u6700\u5927\u6267\u884C\u6B21\u6570</label>
|
|
4279
|
+
<input id="scheduler-max-runs" type="number" min="1" placeholder="\u4E0D\u9650" style="width: 120px;" />
|
|
4280
|
+
</div>
|
|
4281
|
+
</div>
|
|
4282
|
+
<div class="row">
|
|
4283
|
+
<div>
|
|
4284
|
+
<label>Profile</label>
|
|
4285
|
+
<input id="scheduler-profile" placeholder="xiaohongshu-batch-1" style="width: 220px;" />
|
|
4286
|
+
</div>
|
|
4287
|
+
<div>
|
|
4288
|
+
<label>\u5173\u952E\u8BCD</label>
|
|
4289
|
+
<input id="scheduler-keyword" placeholder="deepseek\u65B0\u6A21\u578B" style="width: 220px;" />
|
|
4290
|
+
</div>
|
|
4291
|
+
</div>
|
|
4292
|
+
<div class="row" id="scheduler-user-id-wrap" style="display:none;">
|
|
4293
|
+
<div>
|
|
4294
|
+
<label>\u5FAE\u535A\u7528\u6237ID (monitor \u5FC5\u586B)</label>
|
|
4295
|
+
<input id="scheduler-user-id" placeholder="\u4F8B\u5982: 1234567890" style="width: 220px;" />
|
|
4296
|
+
</div>
|
|
4297
|
+
</div>
|
|
4298
|
+
<div class="row">
|
|
4299
|
+
<div>
|
|
4300
|
+
<label>\u76EE\u6807\u5E16\u5B50\u6570</label>
|
|
4301
|
+
<input id="scheduler-max-notes" type="number" min="1" value="50" style="width: 120px;" />
|
|
4302
|
+
</div>
|
|
4303
|
+
<div>
|
|
4304
|
+
<label>\u73AF\u5883</label>
|
|
4305
|
+
<select id="scheduler-env" style="width: 120px;">
|
|
4306
|
+
<option value="debug">debug</option>
|
|
4307
|
+
<option value="prod">prod</option>
|
|
4308
|
+
</select>
|
|
4309
|
+
</div>
|
|
4310
|
+
</div>
|
|
4311
|
+
<div class="row">
|
|
4312
|
+
<label style="display:flex; align-items:center; gap:8px;">
|
|
4313
|
+
<input id="scheduler-comments" type="checkbox" checked />
|
|
4314
|
+
<span>\u6293\u8BC4\u8BBA</span>
|
|
4315
|
+
</label>
|
|
4316
|
+
<label style="display:flex; align-items:center; gap:8px;">
|
|
4317
|
+
<input id="scheduler-likes" type="checkbox" />
|
|
4318
|
+
<span>\u70B9\u8D5E</span>
|
|
4319
|
+
</label>
|
|
4320
|
+
<label style="display:flex; align-items:center; gap:8px;">
|
|
4321
|
+
<input id="scheduler-headless" type="checkbox" />
|
|
4322
|
+
<span>headless</span>
|
|
4323
|
+
</label>
|
|
4324
|
+
<label style="display:flex; align-items:center; gap:8px;">
|
|
4325
|
+
<input id="scheduler-dryrun" type="checkbox" />
|
|
4326
|
+
<span>dry-run</span>
|
|
4327
|
+
</label>
|
|
4328
|
+
</div>
|
|
4329
|
+
<div>
|
|
4330
|
+
<label>\u70B9\u8D5E\u5173\u952E\u8BCD\uFF08\u9017\u53F7\u5206\u9694\uFF09</label>
|
|
4331
|
+
<input id="scheduler-like-keywords" placeholder="\u771F\u725B\u903C,\u8D2D\u4E70\u94FE\u63A5" />
|
|
4332
|
+
</div>
|
|
4333
|
+
<div class="btn-group" style="margin-top: var(--gap);">
|
|
4334
|
+
<button id="scheduler-save-btn" style="flex:1;">\u4FDD\u5B58\u4EFB\u52A1</button>
|
|
4335
|
+
<button id="scheduler-reset-btn" class="secondary" style="flex:1;">\u6E05\u7A7A\u8868\u5355</button>
|
|
4336
|
+
</div>
|
|
4337
|
+
`;
|
|
4338
|
+
grid.appendChild(formCell);
|
|
4339
|
+
const listCell = createEl("div", { className: "bento-cell" });
|
|
4340
|
+
listCell.innerHTML = `
|
|
4341
|
+
<div class="bento-title">\u4EFB\u52A1\u5217\u8868</div>
|
|
4342
|
+
<div id="scheduler-list"></div>
|
|
4343
|
+
`;
|
|
4344
|
+
grid.appendChild(listCell);
|
|
4345
|
+
root.appendChild(grid);
|
|
4346
|
+
const refreshBtn = root.querySelector("#scheduler-refresh-btn");
|
|
4347
|
+
const runDueBtn = root.querySelector("#scheduler-run-due-btn");
|
|
4348
|
+
const exportAllBtn = root.querySelector("#scheduler-export-all-btn");
|
|
4349
|
+
const importBtn = root.querySelector("#scheduler-import-btn");
|
|
4350
|
+
const openConfigBtn = root.querySelector("#scheduler-open-config-btn");
|
|
4351
|
+
const daemonStartBtn = root.querySelector("#scheduler-daemon-start-btn");
|
|
4352
|
+
const daemonStopBtn = root.querySelector("#scheduler-daemon-stop-btn");
|
|
4353
|
+
const daemonIntervalInput = root.querySelector("#scheduler-daemon-interval");
|
|
4354
|
+
const daemonStatus = root.querySelector("#scheduler-daemon-status");
|
|
4355
|
+
const activeTaskIdText = root.querySelector("#scheduler-active-task-id");
|
|
4356
|
+
const listEl = root.querySelector("#scheduler-list");
|
|
4357
|
+
const platformSelect = root.querySelector("#scheduler-platform");
|
|
4358
|
+
const taskTypeSelect = root.querySelector("#scheduler-task-type");
|
|
4359
|
+
const editingIdInput = root.querySelector("#scheduler-editing-id");
|
|
4360
|
+
const nameInput = root.querySelector("#scheduler-name");
|
|
4361
|
+
const enabledInput = root.querySelector("#scheduler-enabled");
|
|
4362
|
+
const typeSelect = root.querySelector("#scheduler-type");
|
|
4363
|
+
const intervalWrap = root.querySelector("#scheduler-interval-wrap");
|
|
4364
|
+
const runAtWrap = root.querySelector("#scheduler-runat-wrap");
|
|
4365
|
+
const intervalInput = root.querySelector("#scheduler-interval");
|
|
4366
|
+
const runAtInput = root.querySelector("#scheduler-runat");
|
|
4367
|
+
const maxRunsInput = root.querySelector("#scheduler-max-runs");
|
|
4368
|
+
const profileInput = root.querySelector("#scheduler-profile");
|
|
4369
|
+
const keywordInput = root.querySelector("#scheduler-keyword");
|
|
4370
|
+
const userIdWrap = root.querySelector("#scheduler-user-id-wrap");
|
|
4371
|
+
const userIdInput = root.querySelector("#scheduler-user-id");
|
|
4372
|
+
const maxNotesInput = root.querySelector("#scheduler-max-notes");
|
|
4373
|
+
const envSelect = root.querySelector("#scheduler-env");
|
|
4374
|
+
const commentsInput = root.querySelector("#scheduler-comments");
|
|
4375
|
+
const likesInput = root.querySelector("#scheduler-likes");
|
|
4376
|
+
const headlessInput = root.querySelector("#scheduler-headless");
|
|
4377
|
+
const dryRunInput = root.querySelector("#scheduler-dryrun");
|
|
4378
|
+
const likeKeywordsInput = root.querySelector("#scheduler-like-keywords");
|
|
4379
|
+
const saveBtn = root.querySelector("#scheduler-save-btn");
|
|
4380
|
+
const resetBtn = root.querySelector("#scheduler-reset-btn");
|
|
4381
|
+
let tasks = [];
|
|
4382
|
+
let daemonRunId = "";
|
|
4383
|
+
let unsubscribeCmd = null;
|
|
4384
|
+
let pendingFocusTaskId = String(ctx2?.activeTaskConfigId || "").trim();
|
|
4385
|
+
function setDaemonStatus(text) {
|
|
4386
|
+
daemonStatus.textContent = text;
|
|
4387
|
+
}
|
|
4388
|
+
function setActiveTaskContext(taskId) {
|
|
4389
|
+
const id = String(taskId || "").trim();
|
|
4390
|
+
activeTaskIdText.textContent = id || "-";
|
|
4391
|
+
if (ctx2 && typeof ctx2 === "object") {
|
|
4392
|
+
ctx2.activeTaskConfigId = id;
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
function openConfigTab(taskId) {
|
|
4396
|
+
setActiveTaskContext(taskId);
|
|
4397
|
+
if (typeof ctx2.setActiveTab === "function") {
|
|
4398
|
+
ctx2.setActiveTab("config");
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
function updateTypeFields() {
|
|
4402
|
+
const mode = typeSelect.value;
|
|
4403
|
+
const useRunAt = mode === "once" || mode === "daily" || mode === "weekly";
|
|
4404
|
+
runAtWrap.style.display = useRunAt ? "" : "none";
|
|
4405
|
+
intervalWrap.style.display = useRunAt ? "none" : "";
|
|
4406
|
+
}
|
|
4407
|
+
function updateTaskTypeOptions() {
|
|
4408
|
+
const platform = platformSelect.value;
|
|
4409
|
+
const tasks2 = getTasksForPlatform(platform);
|
|
4410
|
+
taskTypeSelect.innerHTML = tasks2.map((t) => `<option value="${t.type}">${t.icon} ${t.label}</option>`).join("");
|
|
4411
|
+
if (taskTypeSelect.options.length > 0) {
|
|
4412
|
+
taskTypeSelect.value = taskTypeSelect.options[0]?.value || "";
|
|
4413
|
+
}
|
|
4414
|
+
updatePlatformFields();
|
|
4415
|
+
}
|
|
4416
|
+
function updatePlatformFields() {
|
|
4417
|
+
const commandType = String(taskTypeSelect.value || "").trim();
|
|
4418
|
+
const isWeiboMonitor = commandType === "weibo-monitor";
|
|
4419
|
+
userIdWrap.style.display = isWeiboMonitor ? "" : "none";
|
|
4420
|
+
}
|
|
4421
|
+
function resetForm() {
|
|
4422
|
+
platformSelect.value = "xiaohongshu";
|
|
4423
|
+
updateTaskTypeOptions();
|
|
4424
|
+
editingIdInput.value = "";
|
|
4425
|
+
nameInput.value = "";
|
|
4426
|
+
enabledInput.checked = true;
|
|
4427
|
+
typeSelect.value = "interval";
|
|
4428
|
+
intervalInput.value = "30";
|
|
4429
|
+
runAtInput.value = "";
|
|
4430
|
+
maxRunsInput.value = "";
|
|
4431
|
+
profileInput.value = "";
|
|
4432
|
+
keywordInput.value = "";
|
|
4433
|
+
userIdInput.value = "";
|
|
4434
|
+
maxNotesInput.value = "50";
|
|
4435
|
+
envSelect.value = "debug";
|
|
4436
|
+
commentsInput.checked = true;
|
|
4437
|
+
likesInput.checked = false;
|
|
4438
|
+
headlessInput.checked = false;
|
|
4439
|
+
dryRunInput.checked = false;
|
|
4440
|
+
likeKeywordsInput.value = "";
|
|
4441
|
+
setActiveTaskContext("");
|
|
4442
|
+
updatePlatformFields();
|
|
4443
|
+
updateTypeFields();
|
|
4444
|
+
}
|
|
4445
|
+
function readFormAsPayload() {
|
|
4446
|
+
const maxRunsRaw = maxRunsInput.value.trim();
|
|
4447
|
+
const maxRuns = maxRunsRaw ? Math.max(1, Number(maxRunsRaw) || 1) : null;
|
|
4448
|
+
const commandType = String(taskTypeSelect.value || "xhs-unified").trim();
|
|
4449
|
+
const argv = {
|
|
4450
|
+
profile: profileInput.value.trim(),
|
|
4451
|
+
keyword: keywordInput.value.trim(),
|
|
4452
|
+
"max-notes": Number(maxNotesInput.value || 50) || 50,
|
|
4453
|
+
env: envSelect.value,
|
|
4454
|
+
"do-comments": commentsInput.checked,
|
|
4455
|
+
"do-likes": likesInput.checked,
|
|
4456
|
+
"like-keywords": likeKeywordsInput.value.trim(),
|
|
4457
|
+
headless: headlessInput.checked,
|
|
4458
|
+
"dry-run": dryRunInput.checked
|
|
4459
|
+
};
|
|
4460
|
+
if (commandType.startsWith("weibo")) {
|
|
4461
|
+
argv["task-type"] = commandTypeToWeiboTaskType2(commandType);
|
|
4462
|
+
argv["user-id"] = userIdInput.value.trim();
|
|
4463
|
+
}
|
|
4464
|
+
return {
|
|
4465
|
+
id: editingIdInput.value.trim(),
|
|
4466
|
+
name: nameInput.value.trim(),
|
|
4467
|
+
enabled: enabledInput.checked,
|
|
4468
|
+
commandType,
|
|
4469
|
+
scheduleType: typeSelect.value,
|
|
4470
|
+
intervalMinutes: Number(intervalInput.value || 30) || 30,
|
|
4471
|
+
runAt: toIsoOrNull(runAtInput.value),
|
|
4472
|
+
maxRuns,
|
|
4473
|
+
argv
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
function applyTaskToForm(task) {
|
|
4477
|
+
pendingFocusTaskId = "";
|
|
4478
|
+
const platform = getPlatformForCommandType(String(task.commandType || "xhs-unified"));
|
|
4479
|
+
platformSelect.value = platform;
|
|
4480
|
+
updateTaskTypeOptions();
|
|
4481
|
+
taskTypeSelect.value = String(task.commandType || taskTypeSelect.value || "xhs-unified");
|
|
4482
|
+
editingIdInput.value = task.id;
|
|
4483
|
+
nameInput.value = task.name || "";
|
|
4484
|
+
enabledInput.checked = task.enabled !== false;
|
|
4485
|
+
typeSelect.value = task.scheduleType;
|
|
4486
|
+
intervalInput.value = String(task.intervalMinutes || 30);
|
|
4487
|
+
runAtInput.value = toLocalDatetimeValue(task.runAt);
|
|
4488
|
+
maxRunsInput.value = task.maxRuns ? String(task.maxRuns) : "";
|
|
4489
|
+
profileInput.value = String(task.commandArgv?.profile || "");
|
|
4490
|
+
keywordInput.value = String(task.commandArgv?.keyword || task.commandArgv?.k || "");
|
|
4491
|
+
userIdInput.value = String(task.commandArgv?.["user-id"] || task.commandArgv?.userId || "");
|
|
4492
|
+
maxNotesInput.value = String(task.commandArgv?.["max-notes"] ?? task.commandArgv?.target ?? 50);
|
|
4493
|
+
envSelect.value = String(task.commandArgv?.env || "debug");
|
|
4494
|
+
commentsInput.checked = task.commandArgv?.["do-comments"] !== false;
|
|
4495
|
+
likesInput.checked = task.commandArgv?.["do-likes"] === true;
|
|
4496
|
+
headlessInput.checked = task.commandArgv?.headless === true;
|
|
4497
|
+
dryRunInput.checked = task.commandArgv?.["dry-run"] === true;
|
|
4498
|
+
likeKeywordsInput.value = String(task.commandArgv?.["like-keywords"] || "");
|
|
4499
|
+
setActiveTaskContext(task.id);
|
|
4500
|
+
updatePlatformFields();
|
|
4501
|
+
updateTypeFields();
|
|
4502
|
+
}
|
|
4503
|
+
async function runScheduleJson(args, timeoutMs = 6e4) {
|
|
4504
|
+
const script = ctx2.api.pathJoin("apps", "webauto", "entry", "schedule.mjs");
|
|
4505
|
+
const ret = await ctx2.api.cmdRunJson({
|
|
4506
|
+
title: `schedule ${args.join(" ")}`,
|
|
4507
|
+
cwd: "",
|
|
4508
|
+
args: [script, ...args, "--json"],
|
|
4509
|
+
timeoutMs
|
|
4510
|
+
});
|
|
4511
|
+
if (!ret?.ok) {
|
|
4512
|
+
const reason = String(ret?.error || ret?.stderr || ret?.stdout || "unknown_error").trim();
|
|
4513
|
+
throw new Error(reason || "schedule command failed");
|
|
4514
|
+
}
|
|
4515
|
+
return ret.json || {};
|
|
4516
|
+
}
|
|
4517
|
+
function downloadJson(fileName, payload) {
|
|
4518
|
+
const text = JSON.stringify(payload, null, 2);
|
|
4519
|
+
const blob = new Blob([text], { type: "application/json" });
|
|
4520
|
+
const url = URL.createObjectURL(blob);
|
|
4521
|
+
const a = document.createElement("a");
|
|
4522
|
+
a.href = url;
|
|
4523
|
+
a.download = fileName;
|
|
4524
|
+
a.click();
|
|
4525
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
4526
|
+
}
|
|
4527
|
+
function renderTaskList() {
|
|
4528
|
+
listEl.innerHTML = "";
|
|
4529
|
+
if (tasks.length === 0) {
|
|
4530
|
+
listEl.innerHTML = '<div class="muted" style="padding: 12px;">\u6682\u65E0\u4EFB\u52A1</div>';
|
|
4531
|
+
return;
|
|
4532
|
+
}
|
|
4533
|
+
for (const task of tasks) {
|
|
4534
|
+
const card = createEl("div", {
|
|
4535
|
+
style: "border:1px solid var(--border); border-radius:10px; padding:10px; margin-bottom:10px; background: var(--panel-soft);"
|
|
4536
|
+
});
|
|
4537
|
+
const scheduleText = task.scheduleType === "once" ? `once @ ${task.runAt || "-"}` : task.scheduleType === "daily" ? `daily @ ${task.runAt || "-"}` : task.scheduleType === "weekly" ? `weekly @ ${task.runAt || "-"}` : `interval ${task.intervalMinutes}m`;
|
|
4538
|
+
const statusText = task.lastStatus ? `${task.lastStatus} / run=${task.runCount} / fail=${task.failCount}` : "never run";
|
|
4539
|
+
const headRow = createEl("div", { style: "display:flex; justify-content:space-between; gap:8px; margin-bottom:6px;" });
|
|
4540
|
+
headRow.appendChild(createEl("div", { style: "font-weight:600;" }, [task.name || task.id]));
|
|
4541
|
+
headRow.appendChild(
|
|
4542
|
+
createEl(
|
|
4543
|
+
"span",
|
|
4544
|
+
{ style: `font-size:12px; color:${task.enabled ? "var(--accent-success)" : "var(--accent-danger)"};` },
|
|
4545
|
+
[task.enabled ? "enabled" : "disabled"]
|
|
4546
|
+
)
|
|
4547
|
+
);
|
|
4548
|
+
card.appendChild(headRow);
|
|
4549
|
+
card.appendChild(createEl("div", { className: "muted", style: "font-size:12px; margin-bottom:4px;" }, [`id=${task.id}`]));
|
|
4550
|
+
card.appendChild(createEl("div", { style: "font-size:12px;" }, [`schedule: ${scheduleText}`]));
|
|
4551
|
+
card.appendChild(createEl("div", { style: "font-size:12px;" }, [`maxRuns: ${task.maxRuns || "unlimited"}`]));
|
|
4552
|
+
card.appendChild(createEl("div", { style: "font-size:12px;" }, [`nextRunAt: ${task.nextRunAt || "-"}`]));
|
|
4553
|
+
card.appendChild(createEl("div", { style: "font-size:12px;" }, [`status: ${statusText}`]));
|
|
4554
|
+
const recent = (task.runHistory || []).slice(-5);
|
|
4555
|
+
if (recent.length > 0) {
|
|
4556
|
+
const recentRow = createEl("div", { style: "font-size:12px;" }, ["recent: "]);
|
|
4557
|
+
for (const h of recent) {
|
|
4558
|
+
const icon = h.status === "success" ? "\u2705" : "\u274C";
|
|
4559
|
+
const duration = h.durationMs ? `${Math.round(h.durationMs / 1e3)}s` : "-";
|
|
4560
|
+
const badge = createEl("span", { title: `${h.timestamp} ${duration}` }, [icon]);
|
|
4561
|
+
recentRow.appendChild(badge);
|
|
4562
|
+
recentRow.appendChild(document.createTextNode(" "));
|
|
4563
|
+
}
|
|
4564
|
+
card.appendChild(recentRow);
|
|
4565
|
+
}
|
|
4566
|
+
if (task.lastError) {
|
|
4567
|
+
card.appendChild(createEl("div", { style: "font-size:12px; color:var(--accent-danger);" }, [`error: ${task.lastError}`]));
|
|
4568
|
+
}
|
|
4569
|
+
const actions = createEl("div", { className: "btn-group", style: "margin-top: 8px;" });
|
|
4570
|
+
const editBtn = createEl("button", { className: "secondary" }, ["\u7F16\u8F91"]);
|
|
4571
|
+
const loadBtn = createEl("button", { className: "secondary" }, ["\u8F7D\u5165\u914D\u7F6E"]);
|
|
4572
|
+
const runBtn = createEl("button", { className: "secondary" }, ["\u6267\u884C"]);
|
|
4573
|
+
const exportBtn = createEl("button", { className: "secondary" }, ["\u5BFC\u51FA"]);
|
|
4574
|
+
const delBtn = createEl("button", { className: "danger" }, ["\u5220\u9664"]);
|
|
4575
|
+
actions.appendChild(editBtn);
|
|
4576
|
+
actions.appendChild(loadBtn);
|
|
4577
|
+
actions.appendChild(runBtn);
|
|
4578
|
+
actions.appendChild(exportBtn);
|
|
4579
|
+
actions.appendChild(delBtn);
|
|
4580
|
+
card.appendChild(actions);
|
|
4581
|
+
editBtn.onclick = () => applyTaskToForm(task);
|
|
4582
|
+
loadBtn.onclick = () => openConfigTab(task.id);
|
|
4583
|
+
runBtn.onclick = async () => {
|
|
4584
|
+
try {
|
|
4585
|
+
setActiveTaskContext(task.id);
|
|
4586
|
+
const out = await runScheduleJson(["run", task.id], 0);
|
|
4587
|
+
const runId = String(
|
|
4588
|
+
out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
|
|
4589
|
+
).trim();
|
|
4590
|
+
if (task.commandType === "xhs-unified" && ctx2 && typeof ctx2 === "object") {
|
|
4591
|
+
const argv = task.commandArgv || {};
|
|
4592
|
+
ctx2.xhsCurrentRun = {
|
|
4593
|
+
runId: runId || null,
|
|
4594
|
+
taskId: task.id,
|
|
4595
|
+
profileId: String(argv.profile || ""),
|
|
4596
|
+
keyword: String(argv.keyword || argv.k || ""),
|
|
4597
|
+
target: Number(argv["max-notes"] || argv.target || 0) || 0,
|
|
4598
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
4601
|
+
if (typeof ctx2.setStatus === "function") {
|
|
4602
|
+
ctx2.setStatus(`running: ${task.id}`);
|
|
4603
|
+
}
|
|
4604
|
+
if (task.commandType === "xhs-unified" && typeof ctx2.setActiveTab === "function") {
|
|
4605
|
+
ctx2.setActiveTab("dashboard");
|
|
4606
|
+
}
|
|
4607
|
+
await refreshList();
|
|
4608
|
+
} catch (err) {
|
|
4609
|
+
alert(`\u6267\u884C\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4610
|
+
}
|
|
4611
|
+
};
|
|
4612
|
+
exportBtn.onclick = async () => {
|
|
4613
|
+
try {
|
|
4614
|
+
const out = await runScheduleJson(["export", task.id]);
|
|
4615
|
+
downloadJson(`${task.id}.json`, out);
|
|
4616
|
+
} catch (err) {
|
|
4617
|
+
alert(`\u5BFC\u51FA\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4618
|
+
}
|
|
4619
|
+
};
|
|
4620
|
+
delBtn.onclick = async () => {
|
|
4621
|
+
if (!confirm(`\u786E\u8BA4\u5220\u9664\u4EFB\u52A1 ${task.id} ?`)) return;
|
|
4622
|
+
try {
|
|
4623
|
+
await runScheduleJson(["delete", task.id]);
|
|
4624
|
+
await refreshList();
|
|
4625
|
+
} catch (err) {
|
|
4626
|
+
alert(`\u5220\u9664\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4627
|
+
}
|
|
4628
|
+
};
|
|
4629
|
+
listEl.appendChild(card);
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
async function refreshList() {
|
|
4633
|
+
const out = await runScheduleJson(["list"]);
|
|
4634
|
+
tasks = parseTaskRows(out);
|
|
4635
|
+
if (!pendingFocusTaskId) {
|
|
4636
|
+
pendingFocusTaskId = String(ctx2?.activeTaskConfigId || "").trim();
|
|
4637
|
+
}
|
|
4638
|
+
if (pendingFocusTaskId) {
|
|
4639
|
+
const target = tasks.find((item) => item.id === pendingFocusTaskId);
|
|
4640
|
+
if (target) {
|
|
4641
|
+
applyTaskToForm(target);
|
|
4642
|
+
} else {
|
|
4643
|
+
setActiveTaskContext("");
|
|
4644
|
+
}
|
|
4645
|
+
pendingFocusTaskId = "";
|
|
4646
|
+
} else {
|
|
4647
|
+
setActiveTaskContext(String(ctx2?.activeTaskConfigId || "").trim());
|
|
4648
|
+
}
|
|
4649
|
+
renderTaskList();
|
|
4650
|
+
}
|
|
4651
|
+
async function saveTask() {
|
|
4652
|
+
const payload = readFormAsPayload();
|
|
4653
|
+
if (!payload.name) {
|
|
4654
|
+
alert("\u4EFB\u52A1\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4655
|
+
return;
|
|
4656
|
+
}
|
|
4657
|
+
if (!payload.argv.profile && !payload.argv.profiles && !payload.argv.profilepool) {
|
|
4658
|
+
alert("profile/profiles/profilepool \u81F3\u5C11\u586B\u5199\u4E00\u4E2A");
|
|
4659
|
+
return;
|
|
4660
|
+
}
|
|
4661
|
+
const commandType = String(payload.commandType || "").trim();
|
|
4662
|
+
const keywordRequired = commandType === "xhs-unified" || commandType === "weibo-search" || commandType === "1688-search";
|
|
4663
|
+
if (keywordRequired && !payload.argv.keyword) {
|
|
4664
|
+
alert("\u5173\u952E\u8BCD\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4665
|
+
return;
|
|
4666
|
+
}
|
|
4667
|
+
if (commandType === "weibo-monitor" && !payload.argv["user-id"]) {
|
|
4668
|
+
alert("\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id");
|
|
4669
|
+
return;
|
|
4670
|
+
}
|
|
4671
|
+
const args = payload.id ? ["update", payload.id] : ["add"];
|
|
4672
|
+
args.push("--name", payload.name);
|
|
4673
|
+
args.push("--enabled", String(payload.enabled));
|
|
4674
|
+
args.push("--command-type", commandType || "xhs-unified");
|
|
4675
|
+
args.push("--schedule-type", payload.scheduleType);
|
|
4676
|
+
if (payload.scheduleType === "once") {
|
|
4677
|
+
if (!payload.runAt) {
|
|
4678
|
+
alert("\u4E00\u6B21\u6027\u4EFB\u52A1\u9700\u8981\u951A\u70B9\u65F6\u95F4");
|
|
4679
|
+
return;
|
|
4680
|
+
}
|
|
4681
|
+
args.push("--run-at", payload.runAt);
|
|
4682
|
+
} else if (payload.scheduleType === "daily" || payload.scheduleType === "weekly") {
|
|
4683
|
+
if (!payload.runAt) {
|
|
4684
|
+
alert(`${payload.scheduleType} \u4EFB\u52A1\u9700\u8981\u951A\u70B9\u65F6\u95F4`);
|
|
4685
|
+
return;
|
|
4686
|
+
}
|
|
4687
|
+
args.push("--run-at", payload.runAt);
|
|
4688
|
+
} else {
|
|
4689
|
+
args.push("--interval-minutes", String(Math.max(1, payload.intervalMinutes)));
|
|
4690
|
+
}
|
|
4691
|
+
args.push("--max-runs", payload.maxRuns === null ? "0" : String(payload.maxRuns));
|
|
4692
|
+
args.push("--argv-json", JSON.stringify(payload.argv));
|
|
4693
|
+
try {
|
|
4694
|
+
const out = await runScheduleJson(args);
|
|
4695
|
+
const savedId = String(out?.task?.id || payload.id || "").trim();
|
|
4696
|
+
pendingFocusTaskId = savedId;
|
|
4697
|
+
if (savedId) setActiveTaskContext(savedId);
|
|
4698
|
+
await refreshList();
|
|
4699
|
+
} catch (err) {
|
|
4700
|
+
alert(`\u4FDD\u5B58\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
async function runDueNow() {
|
|
4704
|
+
try {
|
|
4705
|
+
const out = await runScheduleJson(["run-due", "--limit", "20"], 0);
|
|
4706
|
+
alert(`\u5230\u70B9\u4EFB\u52A1\u6267\u884C\u5B8C\u6210\uFF1Adue=${out.count || 0}, success=${out.success || 0}, failed=${out.failed || 0}`);
|
|
4707
|
+
await refreshList();
|
|
4708
|
+
} catch (err) {
|
|
4709
|
+
alert(`\u6267\u884C\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
async function exportAll() {
|
|
4713
|
+
try {
|
|
4714
|
+
const out = await runScheduleJson(["export"]);
|
|
4715
|
+
downloadJson("webauto-schedules.json", out);
|
|
4716
|
+
} catch (err) {
|
|
4717
|
+
alert(`\u5BFC\u51FA\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
async function importFromFile() {
|
|
4721
|
+
const input = document.createElement("input");
|
|
4722
|
+
input.type = "file";
|
|
4723
|
+
input.accept = ".json";
|
|
4724
|
+
input.onchange = async (evt) => {
|
|
4725
|
+
const file = evt.target.files?.[0];
|
|
4726
|
+
if (!file) return;
|
|
4727
|
+
try {
|
|
4728
|
+
const text = await file.text();
|
|
4729
|
+
await runScheduleJson(["import", "--payload-json", text, "--mode", "merge"]);
|
|
4730
|
+
await refreshList();
|
|
4731
|
+
} catch (err) {
|
|
4732
|
+
alert(`\u5BFC\u5165\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4733
|
+
}
|
|
4734
|
+
};
|
|
4735
|
+
input.click();
|
|
4736
|
+
}
|
|
4737
|
+
async function startDaemon() {
|
|
4738
|
+
if (daemonRunId) {
|
|
4739
|
+
alert("daemon \u5DF2\u542F\u52A8");
|
|
4740
|
+
return;
|
|
4741
|
+
}
|
|
4742
|
+
const interval = Math.max(5, Number(daemonIntervalInput.value || 30) || 30);
|
|
4743
|
+
const script = ctx2.api.pathJoin("apps", "webauto", "entry", "schedule.mjs");
|
|
4744
|
+
const ret = await ctx2.api.cmdSpawn({
|
|
4745
|
+
title: `schedule daemon ${interval}s`,
|
|
4746
|
+
cwd: "",
|
|
4747
|
+
args: [script, "daemon", "--interval-sec", String(interval), "--limit", "20", "--json"],
|
|
4748
|
+
groupKey: "scheduler"
|
|
4749
|
+
});
|
|
4750
|
+
daemonRunId = String(ret?.runId || "").trim();
|
|
4751
|
+
setDaemonStatus(daemonRunId ? `daemon: \u8FD0\u884C\u4E2D (${daemonRunId})` : "daemon: \u542F\u52A8\u5931\u8D25");
|
|
4752
|
+
}
|
|
4753
|
+
async function stopDaemon() {
|
|
4754
|
+
if (!daemonRunId) {
|
|
4755
|
+
setDaemonStatus("daemon: \u672A\u542F\u52A8");
|
|
4756
|
+
return;
|
|
4757
|
+
}
|
|
4758
|
+
try {
|
|
4759
|
+
await ctx2.api.cmdKill({ runId: daemonRunId });
|
|
4760
|
+
} catch {
|
|
4761
|
+
}
|
|
4762
|
+
daemonRunId = "";
|
|
4763
|
+
setDaemonStatus("daemon: \u5DF2\u505C\u6B62");
|
|
4764
|
+
}
|
|
4765
|
+
platformSelect.addEventListener("change", updateTaskTypeOptions);
|
|
4766
|
+
taskTypeSelect.addEventListener("change", updatePlatformFields);
|
|
4767
|
+
typeSelect.addEventListener("change", updateTypeFields);
|
|
4768
|
+
saveBtn.onclick = () => void saveTask();
|
|
4769
|
+
resetBtn.onclick = () => resetForm();
|
|
4770
|
+
refreshBtn.onclick = () => void refreshList();
|
|
4771
|
+
runDueBtn.onclick = () => void runDueNow();
|
|
4772
|
+
exportAllBtn.onclick = () => void exportAll();
|
|
4773
|
+
importBtn.onclick = () => void importFromFile();
|
|
4774
|
+
openConfigBtn.onclick = () => {
|
|
4775
|
+
const id = String(editingIdInput.value || activeTaskIdText.textContent || "").trim();
|
|
4776
|
+
openConfigTab(id);
|
|
4777
|
+
};
|
|
4778
|
+
daemonStartBtn.onclick = () => void startDaemon();
|
|
4779
|
+
daemonStopBtn.onclick = () => void stopDaemon();
|
|
4780
|
+
if (typeof ctx2.api?.onCmdEvent === "function") {
|
|
4781
|
+
unsubscribeCmd = ctx2.api.onCmdEvent((evt) => {
|
|
4782
|
+
const runId = String(evt?.runId || "").trim();
|
|
4783
|
+
if (!daemonRunId || runId !== daemonRunId) return;
|
|
4784
|
+
if (evt?.type === "exit") {
|
|
4785
|
+
daemonRunId = "";
|
|
4786
|
+
setDaemonStatus("daemon: \u5DF2\u9000\u51FA");
|
|
4787
|
+
}
|
|
4788
|
+
});
|
|
4789
|
+
}
|
|
4790
|
+
resetForm();
|
|
4791
|
+
updateTaskTypeOptions();
|
|
4792
|
+
void refreshList().catch((err) => {
|
|
4793
|
+
listEl.innerHTML = `<div class="muted" style="padding: 12px;">\u52A0\u8F7D\u5931\u8D25: ${err?.message || String(err)}</div>`;
|
|
4794
|
+
});
|
|
4795
|
+
return () => {
|
|
4796
|
+
if (unsubscribeCmd) {
|
|
4797
|
+
try {
|
|
4798
|
+
unsubscribeCmd();
|
|
4799
|
+
} catch {
|
|
4800
|
+
}
|
|
4801
|
+
unsubscribeCmd = null;
|
|
4802
|
+
}
|
|
4803
|
+
};
|
|
2905
4804
|
}
|
|
2906
4805
|
|
|
2907
4806
|
// src/renderer/index.mts
|
|
2908
4807
|
var tabs = [
|
|
2909
4808
|
{ id: "setup-wizard", label: "\u521D\u59CB\u5316", render: renderSetupWizard },
|
|
2910
|
-
{ id: "
|
|
4809
|
+
{ id: "tasks", label: "\u4EFB\u52A1", render: renderTasksPanel },
|
|
2911
4810
|
{ id: "dashboard", label: "\u770B\u677F", render: renderDashboard },
|
|
4811
|
+
{ id: "scheduler", label: "\u5B9A\u65F6\u4EFB\u52A1", render: renderSchedulerPanel },
|
|
2912
4812
|
{ id: "account-manager", label: "\u8D26\u6237\u7BA1\u7406", render: renderAccountManager },
|
|
2913
4813
|
{ id: "preflight", label: "\u65E7\u9884\u5904\u7406", render: renderPreflight, hidden: true },
|
|
2914
4814
|
{ id: "logs", label: "\u65E5\u5FD7", render: renderLogs },
|
|
@@ -2918,8 +4818,19 @@ var tabsEl = document.getElementById("tabs");
|
|
|
2918
4818
|
var contentEl = document.getElementById("content");
|
|
2919
4819
|
var statusEl = document.getElementById("status");
|
|
2920
4820
|
var activeTabCleanup = null;
|
|
4821
|
+
var mutableApi = { ...window.api || {}, settings: null };
|
|
4822
|
+
var tabIcons = {
|
|
4823
|
+
"setup-wizard": "\u26A1",
|
|
4824
|
+
"tasks": "\u{1F4DD}",
|
|
4825
|
+
"dashboard": "\u{1F4CA}",
|
|
4826
|
+
"scheduler": "\u23F0",
|
|
4827
|
+
"account-manager": "\u{1F464}",
|
|
4828
|
+
"preflight": "\u{1F527}",
|
|
4829
|
+
"logs": "\u{1F4DD}",
|
|
4830
|
+
"settings": "\u{1F528}"
|
|
4831
|
+
};
|
|
2921
4832
|
var ctx = {
|
|
2922
|
-
api:
|
|
4833
|
+
api: mutableApi,
|
|
2923
4834
|
settings: null,
|
|
2924
4835
|
xhsCurrentRun: null,
|
|
2925
4836
|
activeRunId: null,
|
|
@@ -2981,6 +4892,10 @@ function startDesktopHeartbeat() {
|
|
|
2981
4892
|
async function loadSettings() {
|
|
2982
4893
|
await ctx.refreshSettings();
|
|
2983
4894
|
}
|
|
4895
|
+
function focusTabButton(tabId) {
|
|
4896
|
+
const button = tabsEl.querySelector(`[data-tab-id="${tabId}"]`);
|
|
4897
|
+
button?.focus();
|
|
4898
|
+
}
|
|
2984
4899
|
function setActiveTab(id) {
|
|
2985
4900
|
if (activeTabCleanup) {
|
|
2986
4901
|
try {
|
|
@@ -2989,13 +4904,54 @@ function setActiveTab(id) {
|
|
|
2989
4904
|
}
|
|
2990
4905
|
activeTabCleanup = null;
|
|
2991
4906
|
}
|
|
4907
|
+
const visibleTabs = tabs.filter((x) => !x.hidden);
|
|
4908
|
+
tabsEl.setAttribute("role", "tablist");
|
|
2992
4909
|
tabsEl.textContent = "";
|
|
2993
|
-
for (
|
|
2994
|
-
const
|
|
4910
|
+
for (let index = 0; index < visibleTabs.length; index += 1) {
|
|
4911
|
+
const t = visibleTabs[index];
|
|
4912
|
+
const isActive = t.id === id;
|
|
4913
|
+
const icon = tabIcons[t.id] || "";
|
|
4914
|
+
const el = createEl("button", { className: `tab ${isActive ? "active" : ""}`, type: "button" }, [
|
|
4915
|
+
createEl("span", { className: "tab-icon" }, [icon]),
|
|
4916
|
+
t.label
|
|
4917
|
+
]);
|
|
4918
|
+
el.dataset.tabId = t.id;
|
|
4919
|
+
el.setAttribute("role", "tab");
|
|
4920
|
+
el.setAttribute("aria-selected", String(isActive));
|
|
4921
|
+
el.tabIndex = isActive ? 0 : -1;
|
|
2995
4922
|
el.addEventListener("click", () => setActiveTab(t.id));
|
|
4923
|
+
el.addEventListener("keydown", (evt) => {
|
|
4924
|
+
const key = evt.key;
|
|
4925
|
+
if (key === "Enter" || key === " ") {
|
|
4926
|
+
evt.preventDefault();
|
|
4927
|
+
setActiveTab(t.id);
|
|
4928
|
+
return;
|
|
4929
|
+
}
|
|
4930
|
+
let nextIndex = -1;
|
|
4931
|
+
if (key === "ArrowRight") nextIndex = (index + 1) % visibleTabs.length;
|
|
4932
|
+
else if (key === "ArrowLeft") nextIndex = (index - 1 + visibleTabs.length) % visibleTabs.length;
|
|
4933
|
+
else if (key === "Home") nextIndex = 0;
|
|
4934
|
+
else if (key === "End") nextIndex = visibleTabs.length - 1;
|
|
4935
|
+
if (nextIndex >= 0) {
|
|
4936
|
+
evt.preventDefault();
|
|
4937
|
+
const nextTab = visibleTabs[nextIndex];
|
|
4938
|
+
if (!nextTab) return;
|
|
4939
|
+
setActiveTab(nextTab.id);
|
|
4940
|
+
requestAnimationFrame(() => focusTabButton(nextTab.id));
|
|
4941
|
+
}
|
|
4942
|
+
});
|
|
2996
4943
|
tabsEl.appendChild(el);
|
|
2997
4944
|
}
|
|
2998
4945
|
contentEl.textContent = "";
|
|
4946
|
+
const reducedMotion = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false;
|
|
4947
|
+
if (!reducedMotion) {
|
|
4948
|
+
contentEl.classList.remove("animate-fade-in");
|
|
4949
|
+
if (typeof requestAnimationFrame === "function") {
|
|
4950
|
+
requestAnimationFrame(() => contentEl.classList.add("animate-fade-in"));
|
|
4951
|
+
} else {
|
|
4952
|
+
contentEl.classList.add("animate-fade-in");
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
2999
4955
|
const tab = tabs.find((x) => x.id === id);
|
|
3000
4956
|
const dispose = tab.render(contentEl, ctx);
|
|
3001
4957
|
if (typeof dispose === "function") activeTabCleanup = dispose;
|
|
@@ -3035,10 +4991,8 @@ function installCmdEvents() {
|
|
|
3035
4991
|
async function detectStartupTab() {
|
|
3036
4992
|
try {
|
|
3037
4993
|
const env = typeof window.api?.envCheckAll === "function" ? await window.api.envCheckAll() : null;
|
|
3038
|
-
const rows = await listAccountProfiles(window.api).catch(() => []);
|
|
3039
|
-
const hasAccount = rows.some((row) => row.valid);
|
|
3040
4994
|
const envReady = Boolean(env?.allReady);
|
|
3041
|
-
if (envReady
|
|
4995
|
+
if (envReady) return "tasks";
|
|
3042
4996
|
} catch {
|
|
3043
4997
|
}
|
|
3044
4998
|
return "setup-wizard";
|