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