@web-auto/webauto 0.1.8 → 0.1.9
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/dist/main/index.mjs +800 -89
- package/apps/desktop-console/dist/main/preload.mjs +3 -0
- package/apps/desktop-console/dist/renderer/index.html +9 -1
- package/apps/desktop-console/dist/renderer/index.js +784 -331
- package/apps/desktop-console/entry/ui-cli.mjs +23 -8
- package/apps/desktop-console/entry/ui-console.mjs +8 -3
- package/apps/webauto/entry/account.mjs +69 -8
- package/apps/webauto/entry/lib/account-detect.mjs +106 -25
- package/apps/webauto/entry/lib/account-store.mjs +121 -22
- package/apps/webauto/entry/lib/schedule-store.mjs +0 -12
- package/apps/webauto/entry/profilepool.mjs +45 -3
- package/apps/webauto/entry/schedule.mjs +44 -2
- package/apps/webauto/entry/weibo-unified.mjs +2 -2
- package/apps/webauto/entry/xhs-install.mjs +220 -51
- package/apps/webauto/entry/xhs-unified.mjs +33 -6
- package/bin/webauto.mjs +80 -4
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
- package/dist/services/unified-api/server.js +5 -0
- package/dist/services/unified-api/task-state.js +2 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +142 -14
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +16 -1
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +104 -0
- package/modules/camo-runtime/src/autoscript/runtime.mjs +14 -4
- package/modules/camo-runtime/src/autoscript/schema.mjs +9 -0
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +9 -2
- package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +107 -1
- package/modules/camo-runtime/src/container/runtime-core/subscription.mjs +24 -2
- package/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
- package/package.json +6 -3
- package/scripts/bump-version.mjs +120 -0
- package/services/unified-api/server.ts +4 -0
- package/services/unified-api/task-state.ts +5 -0
|
@@ -75,7 +75,7 @@ function renderPreflight(root, ctx2) {
|
|
|
75
75
|
const batchDeleteFp = createEl("input", { type: "checkbox" });
|
|
76
76
|
const onboardingSummary = createEl("div", { className: "muted" }, ["\u6B63\u5728\u52A0\u8F7D profile \u4FE1\u606F..."]);
|
|
77
77
|
const onboardingTips = createEl("div", { className: "muted", style: "font-size:12px; margin-top:6px;" }, [
|
|
78
|
-
"\u9996\u6B21\u4F7F\u7528\u5EFA\u8BAE\
|
|
78
|
+
"\u9996\u6B21\u4F7F\u7528\u5EFA\u8BAE\uFF1AProfile \u4F7F\u7528\u4E2D\u6027\u547D\u540D profile-0/1/2\uFF1B\u767B\u5F55\u540E\u53EF\u8BBE\u7F6E alias\uFF08\u8D26\u53F7\u540D\uFF09\u7528\u4E8E\u533A\u5206\uFF0C\u9ED8\u8BA4\u4F1A\u81EA\u52A8\u83B7\u53D6\u7528\u6237\u540D\u3002"
|
|
79
79
|
]);
|
|
80
80
|
const gotoXhsBtn = createEl("button", { className: "secondary", type: "button" }, ["\u53BB\u5C0F\u7EA2\u4E66\u9996\u9875"]);
|
|
81
81
|
const browserStatus = createEl("div", { className: "muted" }, ["\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u672A\u68C0\u67E5"]);
|
|
@@ -372,7 +372,7 @@ ${mergedOutput}`);
|
|
|
372
372
|
createEl("div", { className: "muted" }, [`\u63D0\u793A\uFF1Aprofile \u4E0E fingerprint \u7684\u771F\u5B9E\u8DEF\u5F84\u5747\u5728 ${webautoRoot} \u4E0B\uFF1Balias \u53EA\u5F71\u54CD UI \u663E\u793A\uFF0C\u4E0D\u5F71\u54CD profileId\u3002`])
|
|
373
373
|
])
|
|
374
374
|
);
|
|
375
|
-
const keywordInput = createEl("input", { value: "
|
|
375
|
+
const keywordInput = createEl("input", { value: "profile", placeholder: "Profile \u524D\u7F00\uFF08\u53EF\u9009\uFF09\uFF0C\u9ED8\u8BA4 profile\uFF0C\u751F\u6210\u5982 profile-0/profile-1" });
|
|
376
376
|
const ensureCountInput = createEl("input", { value: "0", type: "number", min: "0" });
|
|
377
377
|
const timeoutInput = createEl("input", { value: String(ctx2.settings?.timeouts?.loginTimeoutSec || 900), type: "number", min: "30" });
|
|
378
378
|
const keepSession = createEl("input", { type: "checkbox" });
|
|
@@ -1019,6 +1019,7 @@ function renderSettings(root, ctx2) {
|
|
|
1019
1019
|
const keyword = createEl("input", { value: ctx2.settings?.defaultKeyword || "" });
|
|
1020
1020
|
const loginTimeout = createEl("input", { value: String(ctx2.settings?.timeouts?.loginTimeoutSec || 900), type: "number", min: "30" });
|
|
1021
1021
|
const cmdTimeout = createEl("input", { value: String(ctx2.settings?.timeouts?.cmdTimeoutSec || 0), type: "number", min: "0" });
|
|
1022
|
+
const idleTimeout = createEl("input", { value: ctx2.settings?.idleTimeout || "30m", placeholder: "30m" });
|
|
1022
1023
|
const aiEnabled = createEl("input", { type: "checkbox", checked: ctx2.settings?.aiReply?.enabled ?? false });
|
|
1023
1024
|
const aiBaseUrl = createEl("input", { value: ctx2.settings?.aiReply?.baseUrl || "http://127.0.0.1:5520", placeholder: "http://127.0.0.1:5520" });
|
|
1024
1025
|
const aiApiKey = createEl("input", { value: ctx2.settings?.aiReply?.apiKey || "", type: "password", placeholder: "sk-..." });
|
|
@@ -1080,6 +1081,7 @@ function renderSettings(root, ctx2) {
|
|
|
1080
1081
|
loginTimeoutSec: Number(loginTimeout.value || "900"),
|
|
1081
1082
|
cmdTimeoutSec: Number(cmdTimeout.value || "0")
|
|
1082
1083
|
},
|
|
1084
|
+
idleTimeout: idleTimeout.value.trim() || "30m",
|
|
1083
1085
|
aiReply: {
|
|
1084
1086
|
enabled: aiEnabled.checked,
|
|
1085
1087
|
baseUrl: aiBaseUrl.value.trim(),
|
|
@@ -1107,7 +1109,8 @@ function renderSettings(root, ctx2) {
|
|
|
1107
1109
|
]),
|
|
1108
1110
|
createEl("div", { className: "row" }, [
|
|
1109
1111
|
labeledInput("loginTimeoutSec", loginTimeout),
|
|
1110
|
-
labeledInput("cmdTimeoutSec", cmdTimeout)
|
|
1112
|
+
labeledInput("cmdTimeoutSec", cmdTimeout),
|
|
1113
|
+
labeledInput("idleTimeout (e.g., 30m, 1h)", idleTimeout)
|
|
1111
1114
|
]),
|
|
1112
1115
|
createEl("div", { className: "row" }, [
|
|
1113
1116
|
createEl("button", {}, ["\u4FDD\u5B58"])
|
|
@@ -1524,12 +1527,13 @@ function normalizeRow(row) {
|
|
|
1524
1527
|
updatedAt: asText(row?.updatedAt)
|
|
1525
1528
|
};
|
|
1526
1529
|
}
|
|
1527
|
-
async function listAccountProfiles(api) {
|
|
1530
|
+
async function listAccountProfiles(api, options = {}) {
|
|
1528
1531
|
const script = api.pathJoin("apps", "webauto", "entry", "account.mjs");
|
|
1532
|
+
const platform = asText(options?.platform);
|
|
1529
1533
|
const out = await api.cmdRunJson({
|
|
1530
1534
|
title: "account list",
|
|
1531
1535
|
cwd: "",
|
|
1532
|
-
args: [script, "list", "--json"],
|
|
1536
|
+
args: [script, "list", ...platform ? ["--platform", platform] : [], "--json"],
|
|
1533
1537
|
timeoutMs: 2e4
|
|
1534
1538
|
});
|
|
1535
1539
|
const rows = Array.isArray(out?.json?.profiles) ? out.json.profiles : [];
|
|
@@ -1537,6 +1541,14 @@ async function listAccountProfiles(api) {
|
|
|
1537
1541
|
}
|
|
1538
1542
|
|
|
1539
1543
|
// src/renderer/tabs-new/setup-wizard.mts
|
|
1544
|
+
function formatProfileTag(profileId) {
|
|
1545
|
+
const id = String(profileId || "").trim();
|
|
1546
|
+
const m = id.match(/^profile-(\d+)$/i);
|
|
1547
|
+
if (!m) return id;
|
|
1548
|
+
const seq = Number(m[1]);
|
|
1549
|
+
if (!Number.isFinite(seq)) return id;
|
|
1550
|
+
return `P${String(seq).padStart(3, "0")}`;
|
|
1551
|
+
}
|
|
1540
1552
|
function renderSetupWizard(root, ctx2) {
|
|
1541
1553
|
root.innerHTML = "";
|
|
1542
1554
|
const autoSyncTimers = /* @__PURE__ */ new Map();
|
|
@@ -1574,7 +1586,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1574
1586
|
<div class="env-item" id="env-firefox" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
|
|
1575
1587
|
<span style="display:flex; align-items:center; gap:8px; min-width:0;">
|
|
1576
1588
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1577
|
-
<span class="env-label"
|
|
1589
|
+
<span class="env-label">\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox Firefox\uFF09</span>
|
|
1578
1590
|
</span>
|
|
1579
1591
|
<button id="repair-runtime-btn" class="secondary" style="display:none; flex:0 0 auto;">\u4E00\u952E\u4FEE\u590D</button>
|
|
1580
1592
|
</div>
|
|
@@ -1648,30 +1660,31 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1648
1660
|
let envCheckInFlight = false;
|
|
1649
1661
|
let accountCheckInFlight = false;
|
|
1650
1662
|
let busUnsubscribe = null;
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
geoip: !snapshot?.geoip?.installed
|
|
1660
|
-
});
|
|
1663
|
+
const getMissing = (snapshot) => snapshot?.missing || {
|
|
1664
|
+
core: true,
|
|
1665
|
+
runtimeService: true,
|
|
1666
|
+
camo: true,
|
|
1667
|
+
runtime: true,
|
|
1668
|
+
geoip: true
|
|
1669
|
+
};
|
|
1670
|
+
const isEnvReady = (snapshot) => Boolean(snapshot?.allReady);
|
|
1661
1671
|
async function collectEnvironment() {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1672
|
+
if (typeof ctx2.api?.envCheckAll !== "function") {
|
|
1673
|
+
throw new Error("envCheckAll unavailable");
|
|
1674
|
+
}
|
|
1675
|
+
const snapshot = await ctx2.api.envCheckAll();
|
|
1676
|
+
if (snapshot && typeof snapshot === "object" && snapshot.camo && snapshot.services) {
|
|
1677
|
+
return snapshot;
|
|
1678
|
+
}
|
|
1679
|
+
throw new Error("invalid envCheckAll response");
|
|
1669
1680
|
}
|
|
1670
1681
|
function applyEnvironment(snapshot) {
|
|
1682
|
+
const browserReady = Boolean(snapshot.browserReady);
|
|
1683
|
+
const browserDetail = snapshot.firefox?.installed ? "\u5DF2\u5B89\u88C5" : snapshot.services?.camoRuntime ? "\u7531 Runtime \u670D\u52A1\u63D0\u4F9B" : "\u672A\u5B89\u88C5";
|
|
1671
1684
|
updateEnvItem("env-camo", snapshot.camo?.installed, snapshot.camo?.version || (snapshot.camo?.installed ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5"));
|
|
1672
1685
|
updateEnvItem("env-unified", snapshot.services?.unifiedApi, "7701");
|
|
1673
1686
|
updateEnvItem("env-browser", snapshot.services?.camoRuntime, "7704");
|
|
1674
|
-
updateEnvItem("env-firefox",
|
|
1687
|
+
updateEnvItem("env-firefox", browserReady, snapshot.firefox?.path || browserDetail);
|
|
1675
1688
|
updateEnvItem("env-geoip", snapshot.geoip?.installed, snapshot.geoip?.installed ? "\u5DF2\u5B89\u88C5\uFF08\u53EF\u9009\uFF09" : "\u672A\u5B89\u88C5\uFF08\u53EF\u9009\uFF09");
|
|
1676
1689
|
envReady = isEnvReady(snapshot);
|
|
1677
1690
|
syncRepairButtons(snapshot);
|
|
@@ -1720,7 +1733,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1720
1733
|
}
|
|
1721
1734
|
async function repairInstall({ browser, geoip, reinstall, uninstall }) {
|
|
1722
1735
|
if (typeof ctx2.api?.envRepairDeps === "function") {
|
|
1723
|
-
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\
|
|
1736
|
+
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\uFF08\u6D4F\u89C8\u5668\u5185\u6838/GeoIP\uFF09..." : geoip && browser ? "\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56\uFF08\u6D4F\u89C8\u5668\u5185\u6838/GeoIP\uFF09..." : geoip ? "\u6B63\u5728\u5B89\u88C5 GeoIP\uFF08\u53EF\u9009\uFF09..." : "\u6B63\u5728\u5B89\u88C5\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox\uFF09...";
|
|
1724
1737
|
const res = await ctx2.api.envRepairDeps({
|
|
1725
1738
|
browser: Boolean(browser),
|
|
1726
1739
|
geoip: Boolean(geoip),
|
|
@@ -1732,7 +1745,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1732
1745
|
return { ok, detail };
|
|
1733
1746
|
}
|
|
1734
1747
|
if (typeof ctx2.api?.cmdRunJson === "function") {
|
|
1735
|
-
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\
|
|
1748
|
+
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\uFF08\u6D4F\u89C8\u5668\u5185\u6838/GeoIP\uFF09..." : geoip && browser ? "\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56\uFF08\u6D4F\u89C8\u5668\u5185\u6838/GeoIP\uFF09..." : geoip ? "\u6B63\u5728\u5B89\u88C5 GeoIP\uFF08\u53EF\u9009\uFF09..." : "\u6B63\u5728\u5B89\u88C5\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox\uFF09...";
|
|
1736
1749
|
const script = ctx2.api.pathJoin("apps", "webauto", "entry", "xhs-install.mjs");
|
|
1737
1750
|
const args = [script];
|
|
1738
1751
|
if (reinstall) args.push("--reinstall");
|
|
@@ -1810,14 +1823,14 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1810
1823
|
applyEnvironment(latest);
|
|
1811
1824
|
updateCompleteStatus();
|
|
1812
1825
|
if (!detail) {
|
|
1813
|
-
if (label.includes("Camoufox") || label.includes("Runtime")) {
|
|
1814
|
-
ok = Boolean(latest.
|
|
1815
|
-
} else if (label.includes("
|
|
1826
|
+
if (label.includes("\u6D4F\u89C8\u5668\u5185\u6838") || label.includes("Camoufox") || label.includes("Runtime")) {
|
|
1827
|
+
ok = Boolean(latest.browserReady);
|
|
1828
|
+
} else if (label.includes("CLI") || label.includes("camo")) {
|
|
1816
1829
|
ok = Boolean(latest.camo?.installed);
|
|
1817
1830
|
} else if (label.includes("\u6838\u5FC3")) {
|
|
1818
1831
|
ok = Boolean(latest.services?.unifiedApi && latest.services?.camoRuntime);
|
|
1819
1832
|
} else {
|
|
1820
|
-
ok =
|
|
1833
|
+
ok = Boolean(latest.allReady);
|
|
1821
1834
|
}
|
|
1822
1835
|
}
|
|
1823
1836
|
}
|
|
@@ -1840,12 +1853,13 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1840
1853
|
applyEnvironment(snapshot);
|
|
1841
1854
|
updateCompleteStatus();
|
|
1842
1855
|
if (!envReady) {
|
|
1856
|
+
const missingFlags = getMissing(snapshot);
|
|
1843
1857
|
const missing = [];
|
|
1844
|
-
if (
|
|
1845
|
-
if (
|
|
1846
|
-
if (
|
|
1858
|
+
if (missingFlags.camo) missing.push("camo-cli");
|
|
1859
|
+
if (missingFlags.core) missing.push("unified-api");
|
|
1860
|
+
if (missingFlags.runtime) missing.push("browser-kernel");
|
|
1847
1861
|
setupStatusText.textContent = `\u5B58\u5728\u5F85\u4FEE\u590D\u9879: ${missing.join(", ")}`;
|
|
1848
|
-
if (
|
|
1862
|
+
if (missingFlags.runtimeService) {
|
|
1849
1863
|
setupStatusText.textContent += "\uFF08camo-runtime \u672A\u5C31\u7EEA\uFF0C\u5F53\u524D\u4E3A\u53EF\u9009\uFF09";
|
|
1850
1864
|
}
|
|
1851
1865
|
} else if (!snapshot?.geoip?.installed) {
|
|
@@ -1917,8 +1931,8 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1917
1931
|
style: "display:flex; justify-content:space-between; align-items:center; padding:8px 12px; border-bottom:1px solid var(--border);"
|
|
1918
1932
|
}, [
|
|
1919
1933
|
createEl("div", {}, [
|
|
1920
|
-
createEl("div", { style: "font-weight:600; margin-bottom:2px;" }, [acc.alias || acc.name || acc.profileId]),
|
|
1921
|
-
createEl("div", { className: "muted", style: "font-size:11px;" }, [acc.profileId])
|
|
1934
|
+
createEl("div", { style: "font-weight:600; margin-bottom:2px;" }, [acc.alias || acc.name || formatProfileTag(acc.profileId)]),
|
|
1935
|
+
createEl("div", { className: "muted", style: "font-size:11px;" }, [`${formatProfileTag(acc.profileId)} (${acc.profileId}) \xB7 ${String(acc.platform || "xiaohongshu")}`])
|
|
1922
1936
|
]),
|
|
1923
1937
|
createEl("span", {
|
|
1924
1938
|
className: `status-badge ${statusClass}`
|
|
@@ -2025,6 +2039,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
2025
2039
|
"sync",
|
|
2026
2040
|
id,
|
|
2027
2041
|
"--pending-while-login",
|
|
2042
|
+
"--resolve-alias",
|
|
2028
2043
|
"--json"
|
|
2029
2044
|
],
|
|
2030
2045
|
timeoutMs: 2e4
|
|
@@ -2081,12 +2096,12 @@ function renderSetupWizard(root, ctx2) {
|
|
|
2081
2096
|
repairCoreBtn.onclick = () => void runRepair("\u4FEE\u590D\u6838\u5FC3\u670D\u52A1", repairCoreServices);
|
|
2082
2097
|
repairCore2Btn.onclick = () => void runRepair("\u4FEE\u590D\u6838\u5FC3\u670D\u52A1", repairCoreServices);
|
|
2083
2098
|
repairCamoBtn.onclick = () => void runRepair("\u4FEE\u590D Camo CLI", repairCoreServices);
|
|
2084
|
-
repairRuntimeBtn.onclick = () => void runRepair("\u4FEE\u590D
|
|
2099
|
+
repairRuntimeBtn.onclick = () => void runRepair("\u4FEE\u590D\u6D4F\u89C8\u5668\u5185\u6838", () => repairInstall({ browser: true }));
|
|
2085
2100
|
repairGeoipBtn.onclick = () => void runRepair("\u5B89\u88C5 GeoIP", () => repairInstall({ geoip: true }));
|
|
2086
2101
|
addAccountBtn.onclick = addAccount;
|
|
2087
2102
|
enterMainBtn.onclick = () => {
|
|
2088
2103
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2089
|
-
ctx2.setActiveTab("
|
|
2104
|
+
ctx2.setActiveTab("tasks");
|
|
2090
2105
|
}
|
|
2091
2106
|
};
|
|
2092
2107
|
void tickEnvironment();
|
|
@@ -2182,6 +2197,21 @@ function parseTaskRows(payload) {
|
|
|
2182
2197
|
runHistory: parseRunHistory(row?.runHistory)
|
|
2183
2198
|
})).filter((row) => row.id);
|
|
2184
2199
|
}
|
|
2200
|
+
function inferUiScheduleEditorState(task, nowMs = Date.now()) {
|
|
2201
|
+
const scheduleType = normalizeScheduleType(task?.scheduleType);
|
|
2202
|
+
if (scheduleType === "interval") {
|
|
2203
|
+
return { mode: "periodic", periodicType: "interval" };
|
|
2204
|
+
}
|
|
2205
|
+
if (scheduleType === "daily" || scheduleType === "weekly") {
|
|
2206
|
+
return { mode: "periodic", periodicType: scheduleType };
|
|
2207
|
+
}
|
|
2208
|
+
const runAtText = String(task?.runAt || "").trim();
|
|
2209
|
+
const runAtMs = Date.parse(runAtText);
|
|
2210
|
+
if (Number.isFinite(runAtMs) && runAtMs > nowMs + 6e4) {
|
|
2211
|
+
return { mode: "scheduled", periodicType: "interval" };
|
|
2212
|
+
}
|
|
2213
|
+
return { mode: "immediate", periodicType: "interval" };
|
|
2214
|
+
}
|
|
2185
2215
|
function getTasksForPlatform(platform) {
|
|
2186
2216
|
const p = platform;
|
|
2187
2217
|
return PLATFORM_TASKS[p] || [];
|
|
@@ -2208,7 +2238,8 @@ var DEFAULT_FORM = {
|
|
|
2208
2238
|
collectBody: true,
|
|
2209
2239
|
doLikes: false,
|
|
2210
2240
|
likeKeywords: "",
|
|
2211
|
-
|
|
2241
|
+
scheduleMode: "immediate",
|
|
2242
|
+
periodicType: "interval",
|
|
2212
2243
|
intervalMinutes: 30,
|
|
2213
2244
|
runAt: null,
|
|
2214
2245
|
maxRuns: null
|
|
@@ -2217,19 +2248,6 @@ function parseSortableTime(value) {
|
|
|
2217
2248
|
const ts = Date.parse(String(value || ""));
|
|
2218
2249
|
return Number.isFinite(ts) ? ts : 0;
|
|
2219
2250
|
}
|
|
2220
|
-
function isKeywordRequired(taskType) {
|
|
2221
|
-
return taskType === "xhs-unified" || taskType === "weibo-search" || taskType === "1688-search";
|
|
2222
|
-
}
|
|
2223
|
-
function commandTypeToWeiboTaskType(commandType) {
|
|
2224
|
-
if (commandType === "weibo-search") return "search";
|
|
2225
|
-
if (commandType === "weibo-monitor") return "monitor";
|
|
2226
|
-
return "timeline";
|
|
2227
|
-
}
|
|
2228
|
-
function fallbackTaskName(data) {
|
|
2229
|
-
const keyword = String(data.keyword || "").trim();
|
|
2230
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2231
|
-
return keyword ? `${data.taskType}-${keyword}` : `${data.taskType}-${stamp}`;
|
|
2232
|
-
}
|
|
2233
2251
|
function renderTasksPanel(root, ctx2) {
|
|
2234
2252
|
root.innerHTML = "";
|
|
2235
2253
|
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
@@ -2284,8 +2302,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2284
2302
|
<input id="task-target" type="number" min="1" value="50" style="width: 80px;" />
|
|
2285
2303
|
</div>
|
|
2286
2304
|
<div>
|
|
2287
|
-
<label>Profile</label>
|
|
2288
|
-
<input id="task-profile" placeholder="
|
|
2305
|
+
<label>Profile\uFF08\u53EF\u7559\u7A7A\u81EA\u52A8\u9009\uFF09</label>
|
|
2306
|
+
<input id="task-profile" placeholder="\u7559\u7A7A\u81EA\u52A8\u9009\u62E9\u8BE5\u5E73\u53F0\u6709\u6548\u8D26\u53F7" style="width: 220px;" />
|
|
2307
|
+
<div id="task-profile-hint" class="muted" style="font-size:11px; margin-top:2px;">\u63A8\u8350: -</div>
|
|
2289
2308
|
</div>
|
|
2290
2309
|
<div>
|
|
2291
2310
|
<label>\u73AF\u5883</label>
|
|
@@ -2323,14 +2342,20 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2323
2342
|
<div style="font-size:12px; color:var(--text-secondary); margin-bottom:var(--gap-sm);">\u8C03\u5EA6\u8BBE\u7F6E\uFF08\u53EF\u9009\uFF09</div>
|
|
2324
2343
|
<div class="row">
|
|
2325
2344
|
<div>
|
|
2326
|
-
<select id="task-schedule-type" style="width:
|
|
2327
|
-
<option value="
|
|
2328
|
-
<option value="
|
|
2345
|
+
<select id="task-schedule-type" style="width: 140px;">
|
|
2346
|
+
<option value="immediate">\u9A6C\u4E0A\u6267\u884C\uFF08\u4EC5\u4E00\u6B21\uFF09</option>
|
|
2347
|
+
<option value="periodic">\u5468\u671F\u4EFB\u52A1</option>
|
|
2348
|
+
<option value="scheduled">\u5B9A\u65F6\u4EFB\u52A1</option>
|
|
2349
|
+
</select>
|
|
2350
|
+
</div>
|
|
2351
|
+
<div id="task-periodic-type-wrap" style="display:none;">
|
|
2352
|
+
<select id="task-periodic-type" style="width: 100px;">
|
|
2353
|
+
<option value="interval">\u6309\u95F4\u9694</option>
|
|
2329
2354
|
<option value="daily">\u6BCF\u5929</option>
|
|
2330
2355
|
<option value="weekly">\u6BCF\u5468</option>
|
|
2331
2356
|
</select>
|
|
2332
2357
|
</div>
|
|
2333
|
-
<div id="task-interval-wrap">
|
|
2358
|
+
<div id="task-interval-wrap" style="display:none;">
|
|
2334
2359
|
<input id="task-interval" type="number" min="1" value="30" style="width: 70px;" />
|
|
2335
2360
|
<span style="font-size:11px;color:var(--text-tertiary);">\u5206\u949F</span>
|
|
2336
2361
|
</div>
|
|
@@ -2347,7 +2372,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2347
2372
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
2348
2373
|
<button id="task-save-btn" style="flex:1;">\u4FDD\u5B58\u4EFB\u52A1</button>
|
|
2349
2374
|
<button id="task-run-btn" class="primary" style="flex:1;">\u4FDD\u5B58\u5E76\u6267\u884C</button>
|
|
2350
|
-
<button id="task-run-ephemeral-btn" class="secondary" style="flex:1;">\
|
|
2375
|
+
<button id="task-run-ephemeral-btn" class="secondary" style="flex:1;">\u7ACB\u5373\u6267\u884C(\u4E0D\u4FDD\u5B58)</button>
|
|
2351
2376
|
<button id="task-reset-btn" class="secondary" style="flex:0.6;">\u91CD\u7F6E</button>
|
|
2352
2377
|
</div>
|
|
2353
2378
|
`;
|
|
@@ -2377,15 +2402,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2377
2402
|
root.appendChild(mainGrid);
|
|
2378
2403
|
const recentCard = createEl("div", { className: "bento-cell", style: "margin-top: var(--gap);" });
|
|
2379
2404
|
recentCard.innerHTML = `
|
|
2380
|
-
<div class="bento-title">\
|
|
2405
|
+
<div class="bento-title">\u5DF2\u4FDD\u5B58\u4EFB\u52A1\u5217\u8868</div>
|
|
2381
2406
|
<div class="row" style="margin-bottom: var(--gap-sm);">
|
|
2382
2407
|
<select id="task-history-select" style="min-width: 320px;">
|
|
2383
2408
|
<option value="">\u9009\u62E9\u5386\u53F2\u4EFB\u52A1...</option>
|
|
2384
2409
|
</select>
|
|
2385
2410
|
<button id="task-history-edit-btn" class="secondary">\u8F7D\u5165\u7F16\u8F91</button>
|
|
2386
2411
|
<button id="task-history-clone-btn" class="secondary">\u8F7D\u5165\u53E6\u5B58</button>
|
|
2412
|
+
<button id="task-history-run-btn">\u7ACB\u5373\u6267\u884C</button>
|
|
2387
2413
|
<button id="task-history-refresh-btn" class="secondary">\u5237\u65B0</button>
|
|
2388
2414
|
</div>
|
|
2415
|
+
<div class="muted" style="font-size:12px; margin-bottom:6px;">\u53CC\u51FB\u5217\u8868\u9879\u53EF\u76F4\u63A5\u5207\u6362\u4E3A\u5F53\u524D\u4EFB\u52A1\u3002</div>
|
|
2389
2416
|
<div id="recent-tasks-list"></div>
|
|
2390
2417
|
`;
|
|
2391
2418
|
root.appendChild(recentCard);
|
|
@@ -2396,6 +2423,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2396
2423
|
const keywordInput = formCard.querySelector("#task-keyword");
|
|
2397
2424
|
const targetInput = formCard.querySelector("#task-target");
|
|
2398
2425
|
const profileInput = formCard.querySelector("#task-profile");
|
|
2426
|
+
const profileHint = formCard.querySelector("#task-profile-hint");
|
|
2399
2427
|
const envSelect = formCard.querySelector("#task-env");
|
|
2400
2428
|
const userIdWrap = formCard.querySelector("#task-user-id-wrap");
|
|
2401
2429
|
const userIdInput = formCard.querySelector("#task-user-id");
|
|
@@ -2404,6 +2432,8 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2404
2432
|
const likesInput = formCard.querySelector("#task-likes");
|
|
2405
2433
|
const likeKeywordsInput = formCard.querySelector("#task-like-keywords");
|
|
2406
2434
|
const scheduleTypeSelect = formCard.querySelector("#task-schedule-type");
|
|
2435
|
+
const periodicTypeWrap = formCard.querySelector("#task-periodic-type-wrap");
|
|
2436
|
+
const periodicTypeSelect = formCard.querySelector("#task-periodic-type");
|
|
2407
2437
|
const intervalInput = formCard.querySelector("#task-interval");
|
|
2408
2438
|
const intervalWrap = formCard.querySelector("#task-interval-wrap");
|
|
2409
2439
|
const runAtInput = formCard.querySelector("#task-runat");
|
|
@@ -2419,22 +2449,62 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2419
2449
|
const historySelect = recentCard.querySelector("#task-history-select");
|
|
2420
2450
|
const historyEditBtn = recentCard.querySelector("#task-history-edit-btn");
|
|
2421
2451
|
const historyCloneBtn = recentCard.querySelector("#task-history-clone-btn");
|
|
2452
|
+
const historyRunBtn = recentCard.querySelector("#task-history-run-btn");
|
|
2422
2453
|
const historyRefreshBtn = recentCard.querySelector("#task-history-refresh-btn");
|
|
2423
2454
|
const recentTasksList = recentCard.querySelector("#recent-tasks-list");
|
|
2424
2455
|
const statRunning = statsCard.querySelector("#stat-running");
|
|
2425
2456
|
const statToday = statsCard.querySelector("#stat-today");
|
|
2426
2457
|
const statSaved = statsCard.querySelector("#stat-saved");
|
|
2427
2458
|
let tasks = [];
|
|
2459
|
+
let accountRows = [];
|
|
2428
2460
|
const activeRunIds = /* @__PURE__ */ new Set();
|
|
2429
2461
|
let unsubscribeActiveRuns = null;
|
|
2430
2462
|
const joinPath2 = (...parts) => {
|
|
2431
2463
|
if (typeof ctx2?.api?.pathJoin === "function") return ctx2.api.pathJoin(...parts);
|
|
2432
2464
|
return parts.filter(Boolean).join("/");
|
|
2433
2465
|
};
|
|
2434
|
-
const scheduleScript = joinPath2("apps", "webauto", "entry", "schedule.mjs");
|
|
2435
2466
|
const quotaScript = joinPath2("apps", "webauto", "entry", "lib", "quota-status.mjs");
|
|
2436
|
-
|
|
2437
|
-
|
|
2467
|
+
function normalizePlatform2(value) {
|
|
2468
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
2469
|
+
if (raw === "weibo") return "weibo";
|
|
2470
|
+
if (raw === "1688") return "1688";
|
|
2471
|
+
return "xiaohongshu";
|
|
2472
|
+
}
|
|
2473
|
+
function platformToAccountPlatform(value) {
|
|
2474
|
+
return value === "xiaohongshu" ? "xiaohongshu" : value;
|
|
2475
|
+
}
|
|
2476
|
+
async function refreshPlatformAccountRows(platform) {
|
|
2477
|
+
try {
|
|
2478
|
+
accountRows = await listAccountProfiles(ctx2.api, { platform: platformToAccountPlatform(platform) });
|
|
2479
|
+
} catch {
|
|
2480
|
+
accountRows = [];
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
function getRecommendedProfile(platform) {
|
|
2484
|
+
const candidates = accountRows.filter((row) => row.valid).sort((a, b) => {
|
|
2485
|
+
const ta = Date.parse(String(a.updatedAt || "")) || 0;
|
|
2486
|
+
const tb = Date.parse(String(b.updatedAt || "")) || 0;
|
|
2487
|
+
if (tb !== ta) return tb - ta;
|
|
2488
|
+
return String(a.profileId || "").localeCompare(String(b.profileId || ""));
|
|
2489
|
+
});
|
|
2490
|
+
return candidates[0] || null;
|
|
2491
|
+
}
|
|
2492
|
+
function updateProfileHint(platform) {
|
|
2493
|
+
const recommended = getRecommendedProfile(platform);
|
|
2494
|
+
if (!recommended) {
|
|
2495
|
+
profileHint.textContent = `\u63A8\u8350: \u5F53\u524D\u5E73\u53F0(${platform})\u65E0\u6709\u6548\u8D26\u53F7\uFF0C\u8BF7\u5148\u5230\u8D26\u53F7\u9875\u767B\u5F55`;
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
const label = recommended.alias || recommended.name || recommended.profileId;
|
|
2499
|
+
profileHint.textContent = `\u63A8\u8350: ${label} (${recommended.profileId})`;
|
|
2500
|
+
}
|
|
2501
|
+
function maybeAutofillProfile(platform) {
|
|
2502
|
+
const current = String(profileInput.value || "").trim();
|
|
2503
|
+
if (current) return;
|
|
2504
|
+
const recommended = getRecommendedProfile(platform);
|
|
2505
|
+
if (!recommended) return;
|
|
2506
|
+
profileInput.value = recommended.profileId;
|
|
2507
|
+
}
|
|
2438
2508
|
function getTaskById(taskId) {
|
|
2439
2509
|
const id = String(taskId || "").trim();
|
|
2440
2510
|
if (!id) return null;
|
|
@@ -2452,13 +2522,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2452
2522
|
formTitle.textContent = "\u65B0\u5EFA\u4EFB\u52A1";
|
|
2453
2523
|
}
|
|
2454
2524
|
function updateTaskTypeOptions(preferredType = "") {
|
|
2455
|
-
const platform = platformSelect.value;
|
|
2525
|
+
const platform = normalizePlatform2(platformSelect.value);
|
|
2456
2526
|
const options = getTasksForPlatform(platform);
|
|
2457
2527
|
taskTypeSelect.innerHTML = options.map((item) => `<option value="${item.type}">${item.icon} ${item.label}</option>`).join("");
|
|
2458
2528
|
const target = String(preferredType || "").trim();
|
|
2459
2529
|
const matched = options.find((item) => item.type === target);
|
|
2460
2530
|
taskTypeSelect.value = matched?.type || options[0]?.type || "";
|
|
2461
2531
|
updatePlatformFields();
|
|
2532
|
+
void refreshPlatformAccountRows(platform).then(() => {
|
|
2533
|
+
updateProfileHint(platform);
|
|
2534
|
+
maybeAutofillProfile(platform);
|
|
2535
|
+
});
|
|
2462
2536
|
}
|
|
2463
2537
|
function updatePlatformFields() {
|
|
2464
2538
|
const taskType = String(taskTypeSelect.value || "").trim();
|
|
@@ -2466,9 +2540,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2466
2540
|
userIdWrap.style.display = isWeiboMonitor ? "" : "none";
|
|
2467
2541
|
}
|
|
2468
2542
|
function updateScheduleVisibility() {
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
|
|
2543
|
+
const mode = String(scheduleTypeSelect.value || "immediate").trim();
|
|
2544
|
+
const periodicType = String(periodicTypeSelect.value || "interval").trim();
|
|
2545
|
+
const periodic = mode === "periodic";
|
|
2546
|
+
const scheduled = mode === "scheduled";
|
|
2547
|
+
periodicTypeWrap.style.display = periodic ? "inline-flex" : "none";
|
|
2548
|
+
intervalWrap.style.display = periodic && periodicType === "interval" ? "inline-flex" : "none";
|
|
2549
|
+
runAtWrap.style.display = scheduled || periodic && periodicType !== "interval" ? "inline-flex" : "none";
|
|
2550
|
+
maxRunsInput.disabled = mode === "immediate";
|
|
2551
|
+
if (mode === "immediate") {
|
|
2552
|
+
maxRunsInput.value = "";
|
|
2553
|
+
}
|
|
2472
2554
|
}
|
|
2473
2555
|
function updateLikeKeywordsState() {
|
|
2474
2556
|
likeKeywordsInput.disabled = !likesInput.checked;
|
|
@@ -2476,6 +2558,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2476
2558
|
function collectFormData() {
|
|
2477
2559
|
const maxRunsRaw = String(maxRunsInput.value || "").trim();
|
|
2478
2560
|
const maxRunsNum = maxRunsRaw ? Number(maxRunsRaw) : 0;
|
|
2561
|
+
const scheduleMode = scheduleTypeSelect.value;
|
|
2562
|
+
const periodicType = periodicTypeSelect.value;
|
|
2563
|
+
const runAtText = String(runAtInput.value || "").trim();
|
|
2479
2564
|
return {
|
|
2480
2565
|
id: String(editingIdInput.value || "").trim() || void 0,
|
|
2481
2566
|
name: String(nameInput.value || "").trim(),
|
|
@@ -2491,9 +2576,10 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2491
2576
|
collectBody: bodyInput.checked,
|
|
2492
2577
|
doLikes: likesInput.checked,
|
|
2493
2578
|
likeKeywords: String(likeKeywordsInput.value || "").trim(),
|
|
2494
|
-
|
|
2579
|
+
scheduleMode,
|
|
2580
|
+
periodicType,
|
|
2495
2581
|
intervalMinutes: Math.max(1, Number(intervalInput.value || 30) || 30),
|
|
2496
|
-
runAt: toIsoOrNull(
|
|
2582
|
+
runAt: toIsoOrNull(runAtText),
|
|
2497
2583
|
maxRuns: Number.isFinite(maxRunsNum) && maxRunsNum > 0 ? Math.max(1, Math.floor(maxRunsNum)) : null
|
|
2498
2584
|
};
|
|
2499
2585
|
}
|
|
@@ -2513,7 +2599,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2513
2599
|
bodyInput.checked = task.commandArgv?.["fetch-body"] !== false;
|
|
2514
2600
|
likesInput.checked = task.commandArgv?.["do-likes"] === true;
|
|
2515
2601
|
likeKeywordsInput.value = String(task.commandArgv?.["like-keywords"] || "").trim();
|
|
2516
|
-
|
|
2602
|
+
const uiSchedule = inferUiScheduleEditorState(task);
|
|
2603
|
+
scheduleTypeSelect.value = uiSchedule.mode;
|
|
2604
|
+
periodicTypeSelect.value = uiSchedule.periodicType;
|
|
2517
2605
|
intervalInput.value = String(task.intervalMinutes || 30);
|
|
2518
2606
|
runAtInput.value = toLocalDatetimeValue(task.runAt);
|
|
2519
2607
|
maxRunsInput.value = task.maxRuns ? String(task.maxRuns) : "";
|
|
@@ -2536,7 +2624,8 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2536
2624
|
bodyInput.checked = DEFAULT_FORM.collectBody;
|
|
2537
2625
|
likesInput.checked = DEFAULT_FORM.doLikes;
|
|
2538
2626
|
likeKeywordsInput.value = DEFAULT_FORM.likeKeywords;
|
|
2539
|
-
scheduleTypeSelect.value = DEFAULT_FORM.
|
|
2627
|
+
scheduleTypeSelect.value = DEFAULT_FORM.scheduleMode;
|
|
2628
|
+
periodicTypeSelect.value = DEFAULT_FORM.periodicType;
|
|
2540
2629
|
intervalInput.value = String(DEFAULT_FORM.intervalMinutes);
|
|
2541
2630
|
runAtInput.value = "";
|
|
2542
2631
|
maxRunsInput.value = "";
|
|
@@ -2570,19 +2659,29 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2570
2659
|
}
|
|
2571
2660
|
}
|
|
2572
2661
|
function renderRecentTasks() {
|
|
2573
|
-
const rows = sortedTasksByRecent()
|
|
2662
|
+
const rows = sortedTasksByRecent();
|
|
2574
2663
|
if (rows.length === 0) {
|
|
2575
2664
|
recentTasksList.innerHTML = '<div class="muted" style="font-size:12px;">\u6682\u65E0\u4EFB\u52A1</div>';
|
|
2576
2665
|
return;
|
|
2577
2666
|
}
|
|
2578
2667
|
recentTasksList.innerHTML = rows.map((task) => `
|
|
2579
|
-
<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;">
|
|
2668
|
+
<div class="task-row task-item" data-id="${task.id}" style="display:flex;gap:var(--gap-sm);padding:var(--gap-xs)0;border-bottom:1px solid var(--border-subtle);align-items:center;cursor:pointer;">
|
|
2580
2669
|
<span style="flex:1;font-size:12px;">${task.name || task.id}</span>
|
|
2581
2670
|
<span style="font-size:11px;color:var(--text-tertiary);">${task.commandType}</span>
|
|
2582
2671
|
<span style="font-size:11px;color:${task.enabled ? "var(--accent-success)" : "var(--text-muted)"};">${task.enabled ? "\u542F\u7528" : "\u7981\u7528"}</span>
|
|
2583
2672
|
<button class="secondary edit-task-btn" data-id="${task.id}" style="padding:2px 6px;font-size:10px;height:auto;">\u7F16\u8F91</button>
|
|
2673
|
+
<button class="run-task-btn" data-id="${task.id}" style="padding:2px 6px;font-size:10px;height:auto;">\u7ACB\u5373\u6267\u884C</button>
|
|
2584
2674
|
</div>
|
|
2585
2675
|
`).join("");
|
|
2676
|
+
recentTasksList.querySelectorAll(".task-item").forEach((item) => {
|
|
2677
|
+
item.addEventListener("dblclick", () => {
|
|
2678
|
+
const taskId = item.dataset.id || "";
|
|
2679
|
+
const task = getTaskById(taskId);
|
|
2680
|
+
if (!task) return;
|
|
2681
|
+
historySelect.value = task.id;
|
|
2682
|
+
applyTaskToForm(task, "edit");
|
|
2683
|
+
});
|
|
2684
|
+
});
|
|
2586
2685
|
recentTasksList.querySelectorAll(".edit-task-btn").forEach((btn) => {
|
|
2587
2686
|
btn.addEventListener("click", () => {
|
|
2588
2687
|
const taskId = btn.dataset.id || "";
|
|
@@ -2592,6 +2691,14 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2592
2691
|
applyTaskToForm(task, "edit");
|
|
2593
2692
|
});
|
|
2594
2693
|
});
|
|
2694
|
+
recentTasksList.querySelectorAll(".run-task-btn").forEach((btn) => {
|
|
2695
|
+
btn.addEventListener("click", () => {
|
|
2696
|
+
const taskId = btn.dataset.id || "";
|
|
2697
|
+
const task = getTaskById(taskId);
|
|
2698
|
+
if (!task) return;
|
|
2699
|
+
void runTaskImmediately(task);
|
|
2700
|
+
});
|
|
2701
|
+
});
|
|
2595
2702
|
}
|
|
2596
2703
|
function updateStats() {
|
|
2597
2704
|
statSaved.textContent = String(tasks.length);
|
|
@@ -2599,21 +2706,27 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2599
2706
|
const totalRunCount = tasks.reduce((sum, row) => sum + (Number(row.runCount) || 0), 0);
|
|
2600
2707
|
statToday.textContent = String(totalRunCount);
|
|
2601
2708
|
}
|
|
2602
|
-
async function
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
timeoutMs
|
|
2608
|
-
});
|
|
2709
|
+
async function invokeSchedule(input) {
|
|
2710
|
+
if (typeof ctx2.api?.scheduleInvoke !== "function") {
|
|
2711
|
+
throw new Error("scheduleInvoke unavailable");
|
|
2712
|
+
}
|
|
2713
|
+
const ret = await ctx2.api.scheduleInvoke(input);
|
|
2609
2714
|
if (!ret?.ok) {
|
|
2610
|
-
const reason = String(ret?.error ||
|
|
2611
|
-
throw new Error(reason || "command failed");
|
|
2715
|
+
const reason = String(ret?.error || "schedule command failed").trim();
|
|
2716
|
+
throw new Error(reason || "schedule command failed");
|
|
2612
2717
|
}
|
|
2613
|
-
return ret
|
|
2718
|
+
return ret?.json ?? ret;
|
|
2614
2719
|
}
|
|
2615
|
-
async function
|
|
2616
|
-
|
|
2720
|
+
async function invokeTaskRunEphemeral(input) {
|
|
2721
|
+
if (typeof ctx2.api?.taskRunEphemeral !== "function") {
|
|
2722
|
+
throw new Error("taskRunEphemeral unavailable");
|
|
2723
|
+
}
|
|
2724
|
+
const ret = await ctx2.api.taskRunEphemeral(input);
|
|
2725
|
+
if (!ret?.ok) {
|
|
2726
|
+
const reason = String(ret?.error || "run ephemeral failed").trim();
|
|
2727
|
+
throw new Error(reason || "run ephemeral failed");
|
|
2728
|
+
}
|
|
2729
|
+
return ret;
|
|
2617
2730
|
}
|
|
2618
2731
|
async function loadQuotaStatus() {
|
|
2619
2732
|
try {
|
|
@@ -2642,7 +2755,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2642
2755
|
}
|
|
2643
2756
|
async function loadTasks() {
|
|
2644
2757
|
try {
|
|
2645
|
-
const out = await
|
|
2758
|
+
const out = await invokeSchedule({ action: "list" });
|
|
2646
2759
|
tasks = parseTaskRows(out);
|
|
2647
2760
|
renderHistorySelect();
|
|
2648
2761
|
renderRecentTasks();
|
|
@@ -2653,7 +2766,6 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2653
2766
|
}
|
|
2654
2767
|
function buildCommandArgv(data) {
|
|
2655
2768
|
const argv = {
|
|
2656
|
-
profile: data.profileId,
|
|
2657
2769
|
keyword: data.keyword,
|
|
2658
2770
|
"max-notes": data.targetCount,
|
|
2659
2771
|
target: data.targetCount,
|
|
@@ -2663,36 +2775,70 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2663
2775
|
"do-likes": data.doLikes,
|
|
2664
2776
|
"like-keywords": data.likeKeywords
|
|
2665
2777
|
};
|
|
2778
|
+
const profileId = String(data.profileId || "").trim();
|
|
2779
|
+
if (profileId) argv.profile = profileId;
|
|
2666
2780
|
if (String(data.taskType || "").startsWith("weibo-")) {
|
|
2667
|
-
argv["task-type"] = commandTypeToWeiboTaskType(data.taskType);
|
|
2668
2781
|
if (data.userId) argv["user-id"] = data.userId;
|
|
2669
2782
|
}
|
|
2670
2783
|
return argv;
|
|
2671
2784
|
}
|
|
2672
|
-
function
|
|
2673
|
-
if (
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
const args = data.id ? ["update", data.id] : ["add"];
|
|
2681
|
-
args.push("--name", data.name || fallbackTaskName(data));
|
|
2682
|
-
args.push("--enabled", String(data.enabled));
|
|
2683
|
-
args.push("--command-type", data.taskType || "xhs-unified");
|
|
2684
|
-
args.push("--schedule-type", data.scheduleType);
|
|
2685
|
-
if (data.scheduleType === "interval") {
|
|
2686
|
-
args.push("--interval-minutes", String(data.intervalMinutes));
|
|
2687
|
-
} else {
|
|
2688
|
-
args.push("--run-at", String(data.runAt || ""));
|
|
2785
|
+
function resolveSchedule(data) {
|
|
2786
|
+
if (data.scheduleMode === "immediate") {
|
|
2787
|
+
return {
|
|
2788
|
+
scheduleType: "once",
|
|
2789
|
+
intervalMinutes: data.intervalMinutes,
|
|
2790
|
+
runAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2791
|
+
maxRuns: 1
|
|
2792
|
+
};
|
|
2689
2793
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2794
|
+
if (data.scheduleMode === "scheduled") {
|
|
2795
|
+
return {
|
|
2796
|
+
scheduleType: "once",
|
|
2797
|
+
intervalMinutes: data.intervalMinutes,
|
|
2798
|
+
runAt: data.runAt,
|
|
2799
|
+
maxRuns: 1
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
const periodicType = data.periodicType;
|
|
2803
|
+
if (periodicType === "daily" || periodicType === "weekly") {
|
|
2804
|
+
return {
|
|
2805
|
+
scheduleType: periodicType,
|
|
2806
|
+
intervalMinutes: data.intervalMinutes,
|
|
2807
|
+
runAt: data.runAt,
|
|
2808
|
+
maxRuns: data.maxRuns
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
return {
|
|
2812
|
+
scheduleType: "interval",
|
|
2813
|
+
intervalMinutes: data.intervalMinutes,
|
|
2814
|
+
runAt: null,
|
|
2815
|
+
maxRuns: data.maxRuns
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
function toSchedulePayload(data) {
|
|
2819
|
+
const schedule = resolveSchedule(data);
|
|
2820
|
+
return {
|
|
2821
|
+
id: data.id || "",
|
|
2822
|
+
name: data.name || "",
|
|
2823
|
+
enabled: data.enabled,
|
|
2824
|
+
commandType: data.taskType || "xhs-unified",
|
|
2825
|
+
scheduleType: schedule.scheduleType,
|
|
2826
|
+
intervalMinutes: schedule.intervalMinutes,
|
|
2827
|
+
runAt: schedule.runAt,
|
|
2828
|
+
maxRuns: schedule.maxRuns,
|
|
2829
|
+
argv: buildCommandArgv(data)
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
function taskToRunMeta(task) {
|
|
2833
|
+
return {
|
|
2834
|
+
taskType: String(task.commandType || "xhs-unified").trim() || "xhs-unified",
|
|
2835
|
+
profileId: String(task.commandArgv?.profile || task.commandArgv?.profileId || "").trim(),
|
|
2836
|
+
keyword: String(task.commandArgv?.keyword || task.commandArgv?.k || "").trim(),
|
|
2837
|
+
targetCount: Math.max(1, Number(task.commandArgv?.["max-notes"] ?? task.commandArgv?.target ?? 50) || 50)
|
|
2838
|
+
};
|
|
2693
2839
|
}
|
|
2694
2840
|
async function runSavedTask(taskId, data) {
|
|
2695
|
-
const out = await
|
|
2841
|
+
const out = await invokeSchedule({ action: "run", taskId, timeoutMs: 0 });
|
|
2696
2842
|
const runId = String(
|
|
2697
2843
|
out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
|
|
2698
2844
|
).trim();
|
|
@@ -2708,23 +2854,30 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2708
2854
|
target: data.targetCount,
|
|
2709
2855
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2710
2856
|
};
|
|
2857
|
+
ctx2.activeRunId = runId || null;
|
|
2711
2858
|
}
|
|
2712
2859
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2713
2860
|
ctx2.setActiveTab(data.taskType === "xhs-unified" ? "dashboard" : "scheduler");
|
|
2714
2861
|
}
|
|
2715
2862
|
}
|
|
2863
|
+
async function runTaskImmediately(task) {
|
|
2864
|
+
const taskId = String(task.id || "").trim();
|
|
2865
|
+
if (!taskId) return;
|
|
2866
|
+
historySelect.value = taskId;
|
|
2867
|
+
applyTaskToForm(task, "edit");
|
|
2868
|
+
await runSavedTask(taskId, taskToRunMeta(task));
|
|
2869
|
+
}
|
|
2716
2870
|
async function saveTask(runImmediately = false) {
|
|
2717
2871
|
const data = collectFormData();
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
alert(invalidReason);
|
|
2872
|
+
if (runImmediately && data.scheduleMode === "immediate") {
|
|
2873
|
+
await runWithoutSave();
|
|
2721
2874
|
return;
|
|
2722
2875
|
}
|
|
2723
2876
|
saveBtn.disabled = true;
|
|
2724
2877
|
runBtn.disabled = true;
|
|
2725
2878
|
runEphemeralBtn.disabled = true;
|
|
2726
2879
|
try {
|
|
2727
|
-
const out = await
|
|
2880
|
+
const out = await invokeSchedule({ action: "save", payload: toSchedulePayload(data) });
|
|
2728
2881
|
const taskId = String(out?.task?.id || data.id || "").trim();
|
|
2729
2882
|
if (!taskId) {
|
|
2730
2883
|
throw new Error("task id missing after save");
|
|
@@ -2734,7 +2887,13 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2734
2887
|
await loadTasks();
|
|
2735
2888
|
historySelect.value = taskId;
|
|
2736
2889
|
if (runImmediately) {
|
|
2737
|
-
|
|
2890
|
+
const resolvedProfile = String(out?.task?.commandArgv?.profile || data.profileId || "").trim();
|
|
2891
|
+
await runSavedTask(taskId, {
|
|
2892
|
+
taskType: data.taskType,
|
|
2893
|
+
profileId: resolvedProfile,
|
|
2894
|
+
keyword: data.keyword,
|
|
2895
|
+
targetCount: data.targetCount
|
|
2896
|
+
});
|
|
2738
2897
|
} else {
|
|
2739
2898
|
alert("\u4EFB\u52A1\u5DF2\u4FDD\u5B58");
|
|
2740
2899
|
}
|
|
@@ -2746,83 +2905,13 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2746
2905
|
runEphemeralBtn.disabled = false;
|
|
2747
2906
|
}
|
|
2748
2907
|
}
|
|
2749
|
-
function buildEphemeralRunSpec(data) {
|
|
2750
|
-
if (!data.profileId) return null;
|
|
2751
|
-
if (data.taskType === "xhs-unified") {
|
|
2752
|
-
if (!data.keyword) return null;
|
|
2753
|
-
return {
|
|
2754
|
-
title: `xhs: ${data.keyword}`,
|
|
2755
|
-
groupKey: "xhs-unified",
|
|
2756
|
-
args: [
|
|
2757
|
-
xhsScript,
|
|
2758
|
-
"--profile",
|
|
2759
|
-
data.profileId,
|
|
2760
|
-
"--keyword",
|
|
2761
|
-
data.keyword,
|
|
2762
|
-
"--target",
|
|
2763
|
-
String(data.targetCount),
|
|
2764
|
-
"--max-notes",
|
|
2765
|
-
String(data.targetCount),
|
|
2766
|
-
"--env",
|
|
2767
|
-
data.env,
|
|
2768
|
-
"--do-comments",
|
|
2769
|
-
String(data.collectComments),
|
|
2770
|
-
"--fetch-body",
|
|
2771
|
-
String(data.collectBody),
|
|
2772
|
-
"--do-likes",
|
|
2773
|
-
String(data.doLikes),
|
|
2774
|
-
"--like-keywords",
|
|
2775
|
-
data.likeKeywords
|
|
2776
|
-
]
|
|
2777
|
-
};
|
|
2778
|
-
}
|
|
2779
|
-
if (data.taskType === "weibo-search") {
|
|
2780
|
-
if (!data.keyword) return null;
|
|
2781
|
-
return {
|
|
2782
|
-
title: `weibo: ${data.keyword}`,
|
|
2783
|
-
groupKey: "weibo-search",
|
|
2784
|
-
args: [
|
|
2785
|
-
weiboScript,
|
|
2786
|
-
"search",
|
|
2787
|
-
"--profile",
|
|
2788
|
-
data.profileId,
|
|
2789
|
-
"--keyword",
|
|
2790
|
-
data.keyword,
|
|
2791
|
-
"--target",
|
|
2792
|
-
String(data.targetCount),
|
|
2793
|
-
"--env",
|
|
2794
|
-
data.env
|
|
2795
|
-
]
|
|
2796
|
-
};
|
|
2797
|
-
}
|
|
2798
|
-
return null;
|
|
2799
|
-
}
|
|
2800
2908
|
async function runWithoutSave() {
|
|
2801
2909
|
const data = collectFormData();
|
|
2802
|
-
if (!data.profileId) {
|
|
2803
|
-
alert("\u8BF7\u8F93\u5165 Profile ID");
|
|
2804
|
-
return;
|
|
2805
|
-
}
|
|
2806
|
-
if (isKeywordRequired(data.taskType) && !data.keyword) {
|
|
2807
|
-
alert("\u8BF7\u8F93\u5165\u5173\u952E\u8BCD");
|
|
2808
|
-
return;
|
|
2809
|
-
}
|
|
2810
|
-
if (data.taskType === "weibo-monitor" && !data.userId) {
|
|
2811
|
-
alert("\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id");
|
|
2812
|
-
return;
|
|
2813
|
-
}
|
|
2814
|
-
const spec = buildEphemeralRunSpec(data);
|
|
2815
|
-
if (!spec) {
|
|
2816
|
-
alert(`\u5F53\u524D\u4EFB\u52A1\u7C7B\u578B\u6682\u4E0D\u652F\u6301\u4EC5\u6267\u884C(\u4E0D\u4FDD\u5B58): ${data.taskType}`);
|
|
2817
|
-
return;
|
|
2818
|
-
}
|
|
2819
2910
|
runEphemeralBtn.disabled = true;
|
|
2820
2911
|
try {
|
|
2821
|
-
const ret = await
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
args: spec.args,
|
|
2825
|
-
groupKey: spec.groupKey
|
|
2912
|
+
const ret = await invokeTaskRunEphemeral({
|
|
2913
|
+
commandType: data.taskType,
|
|
2914
|
+
argv: buildCommandArgv(data)
|
|
2826
2915
|
});
|
|
2827
2916
|
const runId = String(ret?.runId || "").trim();
|
|
2828
2917
|
if (runId) {
|
|
@@ -2830,17 +2919,19 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2830
2919
|
updateStats();
|
|
2831
2920
|
}
|
|
2832
2921
|
if (typeof ctx2.setStatus === "function") {
|
|
2833
|
-
ctx2.setStatus(`started: ${
|
|
2922
|
+
ctx2.setStatus(`started: ${data.taskType}`);
|
|
2834
2923
|
}
|
|
2835
2924
|
if (data.taskType === "xhs-unified" && ctx2 && typeof ctx2 === "object") {
|
|
2925
|
+
const resolvedProfile = String(ret?.profile || data.profileId || "").trim();
|
|
2836
2926
|
ctx2.xhsCurrentRun = {
|
|
2837
2927
|
runId: runId || null,
|
|
2838
2928
|
taskId: null,
|
|
2839
|
-
profileId:
|
|
2929
|
+
profileId: resolvedProfile,
|
|
2840
2930
|
keyword: data.keyword,
|
|
2841
2931
|
target: data.targetCount,
|
|
2842
2932
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2843
2933
|
};
|
|
2934
|
+
ctx2.activeRunId = runId || null;
|
|
2844
2935
|
}
|
|
2845
2936
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2846
2937
|
ctx2.setActiveTab(data.taskType === "xhs-unified" ? "dashboard" : "scheduler");
|
|
@@ -2873,6 +2964,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2873
2964
|
});
|
|
2874
2965
|
taskTypeSelect.addEventListener("change", () => updatePlatformFields());
|
|
2875
2966
|
scheduleTypeSelect.addEventListener("change", () => updateScheduleVisibility());
|
|
2967
|
+
periodicTypeSelect.addEventListener("change", () => updateScheduleVisibility());
|
|
2876
2968
|
likesInput.addEventListener("change", () => updateLikeKeywordsState());
|
|
2877
2969
|
saveBtn.addEventListener("click", () => {
|
|
2878
2970
|
void saveTask(false);
|
|
@@ -2906,6 +2998,14 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2906
2998
|
}
|
|
2907
2999
|
applyTaskToForm(task, "clone");
|
|
2908
3000
|
});
|
|
3001
|
+
historyRunBtn.addEventListener("click", () => {
|
|
3002
|
+
const task = selectedHistoryTask();
|
|
3003
|
+
if (!task) {
|
|
3004
|
+
alert("\u8BF7\u5148\u9009\u62E9\u5386\u53F2\u4EFB\u52A1");
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
void runTaskImmediately(task);
|
|
3008
|
+
});
|
|
2909
3009
|
gotoSchedulerBtn.addEventListener("click", () => {
|
|
2910
3010
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2911
3011
|
ctx2.setActiveTab("scheduler");
|
|
@@ -2993,6 +3093,11 @@ function renderDashboard(root, ctx2) {
|
|
|
2993
3093
|
<div id="recent-errors-empty" class="muted" style="font-size: 12px;">\u6682\u65E0\u9519\u8BEF</div>
|
|
2994
3094
|
<ul id="recent-errors-list" style="margin: 6px 0 0 16px; padding: 0; font-size: 12px; line-height: 1.5; display:none;"></ul>
|
|
2995
3095
|
</div>
|
|
3096
|
+
<div style="margin-top: 10px;">
|
|
3097
|
+
<label>\u70B9\u8D5E\u94FE\u63A5\uFF08\u6700\u591A 30 \u6761\uFF09</label>
|
|
3098
|
+
<div id="liked-links-empty" class="muted" style="font-size: 12px;">\u6682\u65E0\u70B9\u8D5E\u8BB0\u5F55</div>
|
|
3099
|
+
<ul id="liked-links-list" style="margin: 6px 0 0 16px; padding: 0; font-size: 12px; line-height: 1.5; display:none;"></ul>
|
|
3100
|
+
</div>
|
|
2996
3101
|
`;
|
|
2997
3102
|
runSummaryGrid.appendChild(runSummaryCard);
|
|
2998
3103
|
root.appendChild(runSummaryGrid);
|
|
@@ -3087,6 +3192,8 @@ function renderDashboard(root, ctx2) {
|
|
|
3087
3192
|
const errorCountText = root.querySelector("#error-count-text");
|
|
3088
3193
|
const recentErrorsEmpty = root.querySelector("#recent-errors-empty");
|
|
3089
3194
|
const recentErrorsList = root.querySelector("#recent-errors-list");
|
|
3195
|
+
const likedLinksEmpty = root.querySelector("#liked-links-empty");
|
|
3196
|
+
const likedLinksList = root.querySelector("#liked-links-list");
|
|
3090
3197
|
const logsContainer = root.querySelector("#logs-container");
|
|
3091
3198
|
const toggleLogsBtn = root.querySelector("#toggle-logs-btn");
|
|
3092
3199
|
const pauseBtn = root.querySelector("#pause-btn");
|
|
@@ -3103,23 +3210,175 @@ function renderDashboard(root, ctx2) {
|
|
|
3103
3210
|
let startTime = Date.now();
|
|
3104
3211
|
let stoppedAt = null;
|
|
3105
3212
|
let elapsedTimer = null;
|
|
3213
|
+
let statePollTimer = null;
|
|
3106
3214
|
let unsubscribeState = null;
|
|
3107
3215
|
let unsubscribeCmd = null;
|
|
3108
|
-
|
|
3216
|
+
const contextRun = ctx2?.xhsCurrentRun && typeof ctx2.xhsCurrentRun === "object" ? ctx2.xhsCurrentRun : null;
|
|
3217
|
+
const contextStartedAtMs = Date.parse(String(contextRun?.startedAt || ""));
|
|
3218
|
+
let activeRunId = String(contextRun?.runId || ctx2?.activeRunId || "").trim();
|
|
3219
|
+
let activeProfileId = String(contextRun?.profileId || "").trim();
|
|
3109
3220
|
let activeStatus = "";
|
|
3110
3221
|
let errorCountTotal = 0;
|
|
3111
3222
|
const recentErrors = [];
|
|
3223
|
+
const likedLinks = /* @__PURE__ */ new Map();
|
|
3112
3224
|
const maxLogs = 500;
|
|
3113
3225
|
const maxRecentErrors = 8;
|
|
3114
|
-
const
|
|
3226
|
+
const maxLikedLinks = 30;
|
|
3227
|
+
const initialTaskId = String(contextRun?.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3115
3228
|
if (initialTaskId) {
|
|
3116
3229
|
taskConfigId.textContent = initialTaskId;
|
|
3117
3230
|
}
|
|
3118
3231
|
const normalizeStatus = (value) => String(value || "").trim().toLowerCase();
|
|
3119
3232
|
const isRunningStatus = (value) => ["running", "queued", "pending", "starting"].includes(normalizeStatus(value));
|
|
3120
3233
|
const isTerminalStatus = (value) => ["completed", "done", "success", "succeeded", "failed", "error", "stopped", "canceled"].includes(normalizeStatus(value));
|
|
3234
|
+
const isXhsCommandTitle = (title) => {
|
|
3235
|
+
const normalized = String(title || "").trim().toLowerCase();
|
|
3236
|
+
if (!normalized) return false;
|
|
3237
|
+
return normalized.includes("xhs unified") || normalized.startsWith("xhs:") || normalized.startsWith("xhs unified:");
|
|
3238
|
+
};
|
|
3239
|
+
const hasRenderableValue = (value) => {
|
|
3240
|
+
const text = String(value ?? "").trim();
|
|
3241
|
+
return text.length > 0 && text !== "-";
|
|
3242
|
+
};
|
|
3243
|
+
if (contextRun) {
|
|
3244
|
+
if (hasRenderableValue(contextRun.keyword)) taskKeyword.textContent = String(contextRun.keyword);
|
|
3245
|
+
if (Number(contextRun.target) > 0) taskTarget.textContent = String(Number(contextRun.target));
|
|
3246
|
+
if (hasRenderableValue(contextRun.profileId)) {
|
|
3247
|
+
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3248
|
+
const profileId = String(contextRun.profileId);
|
|
3249
|
+
taskAccount.textContent = aliases[profileId] || profileId;
|
|
3250
|
+
activeProfileId = profileId;
|
|
3251
|
+
}
|
|
3252
|
+
if (hasRenderableValue(contextRun.taskId)) taskConfigId.textContent = String(contextRun.taskId);
|
|
3253
|
+
const startedAtTs = Date.parse(String(contextRun.startedAt || ""));
|
|
3254
|
+
if (Number.isFinite(startedAtTs) && startedAtTs > 0) {
|
|
3255
|
+
startTime = startedAtTs;
|
|
3256
|
+
updateElapsed();
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
function normalizeDetails(details) {
|
|
3260
|
+
if (details === void 0 || details === null) return null;
|
|
3261
|
+
try {
|
|
3262
|
+
const text = typeof details === "string" ? details : JSON.stringify(details, null, 2);
|
|
3263
|
+
const trimmed = String(text || "").trim();
|
|
3264
|
+
if (!trimmed) return null;
|
|
3265
|
+
return trimmed.length > 2e3 ? `${trimmed.slice(0, 2e3)}
|
|
3266
|
+
...` : trimmed;
|
|
3267
|
+
} catch {
|
|
3268
|
+
return String(details || "").trim() || null;
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
function normalizeNoteId(value) {
|
|
3272
|
+
const text = String(value || "").trim();
|
|
3273
|
+
if (!text) return null;
|
|
3274
|
+
if (/^[a-zA-Z0-9_-]{6,}$/.test(text)) return text;
|
|
3275
|
+
return null;
|
|
3276
|
+
}
|
|
3277
|
+
function normalizeLink(urlLike, noteIdLike) {
|
|
3278
|
+
const rawUrl = String(urlLike || "").trim();
|
|
3279
|
+
const noteId = normalizeNoteId(noteIdLike);
|
|
3280
|
+
if (rawUrl) {
|
|
3281
|
+
if (/^https?:\/\//i.test(rawUrl)) return { url: rawUrl, noteId };
|
|
3282
|
+
if (rawUrl.startsWith("/")) return { url: `https://www.xiaohongshu.com${rawUrl}`, noteId };
|
|
3283
|
+
if (/^[a-zA-Z0-9_-]{6,}$/.test(rawUrl)) {
|
|
3284
|
+
return { url: `https://www.xiaohongshu.com/explore/${rawUrl}`, noteId: noteId || rawUrl };
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
if (noteId) {
|
|
3288
|
+
return { url: `https://www.xiaohongshu.com/explore/${noteId}`, noteId };
|
|
3289
|
+
}
|
|
3290
|
+
return null;
|
|
3291
|
+
}
|
|
3292
|
+
function pushLikedLink(entry) {
|
|
3293
|
+
const url = String(entry.url || "").trim();
|
|
3294
|
+
if (!url) return;
|
|
3295
|
+
const previous = likedLinks.get(url);
|
|
3296
|
+
likedLinks.set(url, {
|
|
3297
|
+
url,
|
|
3298
|
+
noteId: entry.noteId || previous?.noteId || null,
|
|
3299
|
+
source: entry.source || previous?.source || "comment_like",
|
|
3300
|
+
profileId: entry.profileId || previous?.profileId || activeProfileId || null,
|
|
3301
|
+
ts: entry.ts || previous?.ts || (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }),
|
|
3302
|
+
count: (previous?.count || 0) + 1
|
|
3303
|
+
});
|
|
3304
|
+
const keys = Array.from(likedLinks.keys());
|
|
3305
|
+
if (keys.length > maxLikedLinks) {
|
|
3306
|
+
likedLinks.delete(keys[0]);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
function renderLikedLinks() {
|
|
3310
|
+
likedLinksList.innerHTML = "";
|
|
3311
|
+
const entries = Array.from(likedLinks.values());
|
|
3312
|
+
if (entries.length === 0) {
|
|
3313
|
+
likedLinksEmpty.style.display = "block";
|
|
3314
|
+
likedLinksList.style.display = "none";
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
likedLinksEmpty.style.display = "none";
|
|
3318
|
+
likedLinksList.style.display = "block";
|
|
3319
|
+
for (const item of entries) {
|
|
3320
|
+
const li = document.createElement("li");
|
|
3321
|
+
const wrap = document.createElement("div");
|
|
3322
|
+
wrap.style.display = "flex";
|
|
3323
|
+
wrap.style.alignItems = "center";
|
|
3324
|
+
wrap.style.gap = "6px";
|
|
3325
|
+
wrap.style.flexWrap = "wrap";
|
|
3326
|
+
const label = document.createElement("span");
|
|
3327
|
+
label.textContent = `[${item.ts}]`;
|
|
3328
|
+
wrap.appendChild(label);
|
|
3329
|
+
const link = document.createElement("a");
|
|
3330
|
+
link.href = "#";
|
|
3331
|
+
link.textContent = item.noteId ? `note:${item.noteId}` : "\u6253\u5F00\u94FE\u63A5";
|
|
3332
|
+
link.onclick = (evt) => {
|
|
3333
|
+
evt.preventDefault();
|
|
3334
|
+
void openLikedLink(item.url, item.profileId || activeProfileId || null);
|
|
3335
|
+
};
|
|
3336
|
+
wrap.appendChild(link);
|
|
3337
|
+
const hint = document.createElement("span");
|
|
3338
|
+
hint.style.color = "var(--text-4)";
|
|
3339
|
+
hint.textContent = `(${item.source}${item.count > 1 ? ` x${item.count}` : ""})`;
|
|
3340
|
+
wrap.appendChild(hint);
|
|
3341
|
+
li.appendChild(wrap);
|
|
3342
|
+
likedLinksList.appendChild(li);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
async function openLikedLink(url, profileId) {
|
|
3346
|
+
const targetUrl = String(url || "").trim();
|
|
3347
|
+
if (!targetUrl) return;
|
|
3348
|
+
const pid = String(profileId || "").trim();
|
|
3349
|
+
try {
|
|
3350
|
+
if (pid && typeof ctx2.api?.cmdRunJson === "function") {
|
|
3351
|
+
const ret = await ctx2.api.cmdRunJson({
|
|
3352
|
+
title: `goto ${pid}`,
|
|
3353
|
+
cwd: "",
|
|
3354
|
+
args: [
|
|
3355
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
3356
|
+
"goto-profile",
|
|
3357
|
+
pid,
|
|
3358
|
+
"--url",
|
|
3359
|
+
targetUrl,
|
|
3360
|
+
"--json"
|
|
3361
|
+
],
|
|
3362
|
+
timeoutMs: 3e4
|
|
3363
|
+
});
|
|
3364
|
+
if (ret?.ok) {
|
|
3365
|
+
addLog(`\u5DF2\u5728 ${pid} \u6253\u5F00\u70B9\u8D5E\u94FE\u63A5`, "info");
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
if (typeof ctx2.api?.osOpenPath === "function") {
|
|
3370
|
+
await ctx2.api.osOpenPath(targetUrl);
|
|
3371
|
+
addLog("\u5DF2\u901A\u8FC7\u7CFB\u7EDF\u6253\u5F00\u70B9\u8D5E\u94FE\u63A5", "warn");
|
|
3372
|
+
}
|
|
3373
|
+
} catch (err) {
|
|
3374
|
+
pushRecentError("\u70B9\u8D5E\u94FE\u63A5\u6253\u5F00\u5931\u8D25", "like_link", err?.message || String(err));
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3121
3377
|
function renderRunSummary() {
|
|
3122
3378
|
runIdText.textContent = activeRunId || "-";
|
|
3379
|
+
if (ctx2 && typeof ctx2 === "object") {
|
|
3380
|
+
ctx2.activeRunId = activeRunId || null;
|
|
3381
|
+
}
|
|
3123
3382
|
errorCountText.textContent = String(errorCountTotal);
|
|
3124
3383
|
recentErrorsList.innerHTML = "";
|
|
3125
3384
|
if (recentErrors.length === 0) {
|
|
@@ -3131,18 +3390,35 @@ function renderDashboard(root, ctx2) {
|
|
|
3131
3390
|
recentErrorsList.style.display = "block";
|
|
3132
3391
|
recentErrors.forEach((item) => {
|
|
3133
3392
|
const li = document.createElement("li");
|
|
3134
|
-
|
|
3393
|
+
const line = document.createElement("div");
|
|
3394
|
+
line.textContent = `[${item.ts}] ${item.source}: ${item.message}`;
|
|
3395
|
+
li.appendChild(line);
|
|
3396
|
+
if (item.details) {
|
|
3397
|
+
const details = document.createElement("details");
|
|
3398
|
+
const summary = document.createElement("summary");
|
|
3399
|
+
summary.textContent = "\u8BE6\u60C5";
|
|
3400
|
+
const pre = document.createElement("pre");
|
|
3401
|
+
pre.style.margin = "4px 0 0 0";
|
|
3402
|
+
pre.style.whiteSpace = "pre-wrap";
|
|
3403
|
+
pre.style.wordBreak = "break-word";
|
|
3404
|
+
pre.textContent = item.details;
|
|
3405
|
+
details.appendChild(summary);
|
|
3406
|
+
details.appendChild(pre);
|
|
3407
|
+
li.appendChild(details);
|
|
3408
|
+
}
|
|
3135
3409
|
recentErrorsList.appendChild(li);
|
|
3136
3410
|
});
|
|
3411
|
+
renderLikedLinks();
|
|
3137
3412
|
}
|
|
3138
|
-
function pushRecentError(message, source = "runtime") {
|
|
3413
|
+
function pushRecentError(message, source = "runtime", details = null) {
|
|
3139
3414
|
const msg = String(message || "").trim();
|
|
3140
3415
|
if (!msg) return;
|
|
3141
3416
|
errorCountTotal += 1;
|
|
3142
3417
|
recentErrors.push({
|
|
3143
3418
|
ts: (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }),
|
|
3144
3419
|
source: String(source || "runtime").trim() || "runtime",
|
|
3145
|
-
message: msg
|
|
3420
|
+
message: msg,
|
|
3421
|
+
details: normalizeDetails(details)
|
|
3146
3422
|
});
|
|
3147
3423
|
while (recentErrors.length > maxRecentErrors) recentErrors.shift();
|
|
3148
3424
|
renderRunSummary();
|
|
@@ -3164,6 +3440,19 @@ function renderDashboard(root, ctx2) {
|
|
|
3164
3440
|
clearInterval(elapsedTimer);
|
|
3165
3441
|
elapsedTimer = null;
|
|
3166
3442
|
}
|
|
3443
|
+
function startStatePoll() {
|
|
3444
|
+
if (statePollTimer) return;
|
|
3445
|
+
if (typeof ctx2.api?.stateGetTasks !== "function") return;
|
|
3446
|
+
statePollTimer = setInterval(() => {
|
|
3447
|
+
if (paused) return;
|
|
3448
|
+
void fetchCurrentState();
|
|
3449
|
+
}, 5e3);
|
|
3450
|
+
}
|
|
3451
|
+
function stopStatePoll() {
|
|
3452
|
+
if (!statePollTimer) return;
|
|
3453
|
+
clearInterval(statePollTimer);
|
|
3454
|
+
statePollTimer = null;
|
|
3455
|
+
}
|
|
3167
3456
|
function addLog(line, type = "info") {
|
|
3168
3457
|
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
|
|
3169
3458
|
const logLine = createEl("div", { className: "log-line" });
|
|
@@ -3176,8 +3465,39 @@ function renderDashboard(root, ctx2) {
|
|
|
3176
3465
|
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
3177
3466
|
}
|
|
3178
3467
|
}
|
|
3468
|
+
function resetDashboardForNewRun(reason, startedAtMs) {
|
|
3469
|
+
commentsCount = 0;
|
|
3470
|
+
likesCount = 0;
|
|
3471
|
+
likesSkippedCount = 0;
|
|
3472
|
+
likesAlreadyCount = 0;
|
|
3473
|
+
likesDedupCount = 0;
|
|
3474
|
+
errorCountTotal = 0;
|
|
3475
|
+
recentErrors.length = 0;
|
|
3476
|
+
likedLinks.clear();
|
|
3477
|
+
logsContainer.innerHTML = "";
|
|
3478
|
+
statCollected.textContent = "0";
|
|
3479
|
+
statSuccess.textContent = "0";
|
|
3480
|
+
statFailed.textContent = "0";
|
|
3481
|
+
statRemaining.textContent = "0";
|
|
3482
|
+
statComments.textContent = "0\u6761";
|
|
3483
|
+
statLikes.textContent = "0\u6B21 (\u8DF3\u8FC7:0, \u5DF2\u8D5E:0, \u53BB\u91CD:0)";
|
|
3484
|
+
progressPercent.textContent = "0%";
|
|
3485
|
+
progressBar.style.width = "0%";
|
|
3486
|
+
currentAction.textContent = reason || "-";
|
|
3487
|
+
currentPhase.textContent = "\u8FD0\u884C\u4E2D";
|
|
3488
|
+
startTime = Number.isFinite(Number(startedAtMs)) && Number(startedAtMs) > 0 ? Number(startedAtMs) : Date.now();
|
|
3489
|
+
stoppedAt = null;
|
|
3490
|
+
updateElapsed();
|
|
3491
|
+
startElapsedTimer();
|
|
3492
|
+
renderRunSummary();
|
|
3493
|
+
}
|
|
3179
3494
|
function updateFromTaskState(state) {
|
|
3180
3495
|
if (!state) return;
|
|
3496
|
+
const incomingRunId = String(state.runId || "").trim();
|
|
3497
|
+
if (incomingRunId && activeRunId && incomingRunId !== activeRunId && (isTerminalStatus(activeStatus) || !isRunningStatus(activeStatus)) && isRunningStatus(state.status)) {
|
|
3498
|
+
activeRunId = incomingRunId;
|
|
3499
|
+
resetDashboardForNewRun("\u5207\u6362\u5230\u65B0\u4EFB\u52A1");
|
|
3500
|
+
}
|
|
3181
3501
|
const progressObj = state.progress && typeof state.progress === "object" ? state.progress : null;
|
|
3182
3502
|
const processedRaw = progressObj?.processed ?? progressObj?.current ?? state.progress ?? state.collected ?? state.current ?? 0;
|
|
3183
3503
|
const totalRaw = progressObj?.total ?? state.total ?? state.target ?? state.maxNotes ?? 0;
|
|
@@ -3230,6 +3550,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3230
3550
|
if (state.profileId) {
|
|
3231
3551
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3232
3552
|
taskAccount.textContent = aliases[state.profileId] || state.profileId;
|
|
3553
|
+
activeProfileId = String(state.profileId || "").trim();
|
|
3233
3554
|
}
|
|
3234
3555
|
const taskId = String(state.taskId || state.scheduleTaskId || state.configTaskId || "").trim();
|
|
3235
3556
|
if (taskId) {
|
|
@@ -3271,26 +3592,32 @@ function renderDashboard(root, ctx2) {
|
|
|
3271
3592
|
}
|
|
3272
3593
|
}
|
|
3273
3594
|
if (state.error) {
|
|
3274
|
-
pushRecentError(String(state.error), "state");
|
|
3595
|
+
pushRecentError(String(state.error), "state", state);
|
|
3275
3596
|
}
|
|
3276
3597
|
}
|
|
3277
3598
|
function pickTaskFromList(tasks) {
|
|
3278
3599
|
const target = activeRunId;
|
|
3279
|
-
const running = tasks.find((item) => isRunningStatus(item?.status));
|
|
3280
3600
|
const sorted = [...tasks].sort((a, b) => {
|
|
3281
3601
|
const aTs = Number(a?.updatedAt ?? a?.completedAt ?? a?.startedAt ?? 0) || 0;
|
|
3282
3602
|
const bTs = Number(b?.updatedAt ?? b?.completedAt ?? b?.startedAt ?? 0) || 0;
|
|
3283
3603
|
return bTs - aTs;
|
|
3284
3604
|
});
|
|
3605
|
+
const running = sorted.find((item) => isRunningStatus(item?.status)) || null;
|
|
3285
3606
|
const latest = sorted[0] || null;
|
|
3607
|
+
const launchingFresh = Number.isFinite(contextStartedAtMs) && contextStartedAtMs > 0 && Date.now() - contextStartedAtMs < 12e4;
|
|
3286
3608
|
if (target) {
|
|
3287
3609
|
const matched = tasks.find((item) => String(item?.runId || "").trim() === target);
|
|
3288
3610
|
if (matched) {
|
|
3289
|
-
if (isRunningStatus(matched?.status)) return matched;
|
|
3290
|
-
if (running) return running;
|
|
3291
|
-
if (latest && String(latest?.runId || "").trim() !== target) return latest;
|
|
3292
3611
|
return matched;
|
|
3293
3612
|
}
|
|
3613
|
+
if (launchingFresh) {
|
|
3614
|
+
return null;
|
|
3615
|
+
}
|
|
3616
|
+
if (running) return running;
|
|
3617
|
+
return null;
|
|
3618
|
+
}
|
|
3619
|
+
if (launchingFresh) {
|
|
3620
|
+
return running || null;
|
|
3294
3621
|
}
|
|
3295
3622
|
return running || latest || null;
|
|
3296
3623
|
}
|
|
@@ -3301,29 +3628,14 @@ function renderDashboard(root, ctx2) {
|
|
|
3301
3628
|
currentPhase.textContent = "\u8FD0\u884C\u4E2D";
|
|
3302
3629
|
currentAction.textContent = "\u542F\u52A8 autoscript";
|
|
3303
3630
|
activeStatus = "running";
|
|
3304
|
-
statCollected.textContent = "0";
|
|
3305
|
-
statSuccess.textContent = "0";
|
|
3306
|
-
statFailed.textContent = "0";
|
|
3307
|
-
statRemaining.textContent = "0";
|
|
3308
|
-
progressPercent.textContent = "0%";
|
|
3309
|
-
progressBar.style.width = "0%";
|
|
3310
|
-
commentsCount = 0;
|
|
3311
|
-
likesCount = 0;
|
|
3312
|
-
likesSkippedCount = 0;
|
|
3313
|
-
likesAlreadyCount = 0;
|
|
3314
|
-
likesDedupCount = 0;
|
|
3315
|
-
statComments.textContent = `0\u6761`;
|
|
3316
|
-
statLikes.textContent = `0\u6B21 (\u8DF3\u8FC7:0, \u5DF2\u8D5E:0, \u53BB\u91CD:0)`;
|
|
3317
3631
|
const ts = Date.parse(String(payload.ts || "")) || Date.now();
|
|
3318
|
-
startTime = ts;
|
|
3319
|
-
stoppedAt = null;
|
|
3320
|
-
updateElapsed();
|
|
3321
|
-
startElapsedTimer();
|
|
3322
3632
|
if (payload.runId) {
|
|
3323
3633
|
activeRunId = String(payload.runId || "").trim() || activeRunId;
|
|
3324
3634
|
}
|
|
3635
|
+
resetDashboardForNewRun("\u65B0\u4EFB\u52A1\u542F\u52A8", ts);
|
|
3325
3636
|
if (payload.keyword) taskKeyword.textContent = String(payload.keyword);
|
|
3326
3637
|
if (payload.maxNotes) taskTarget.textContent = String(payload.maxNotes);
|
|
3638
|
+
if (payload.profileId) activeProfileId = String(payload.profileId || "").trim();
|
|
3327
3639
|
if (payload.taskId) {
|
|
3328
3640
|
const taskId = String(payload.taskId || "").trim();
|
|
3329
3641
|
if (taskId) {
|
|
@@ -3372,6 +3684,23 @@ function renderDashboard(root, ctx2) {
|
|
|
3372
3684
|
likesAlreadyCount = Math.max(0, likesAlreadyCount + already);
|
|
3373
3685
|
likesDedupCount = Math.max(0, likesDedupCount + dedup);
|
|
3374
3686
|
statLikes.textContent = `${likesCount}\u6B21 (\u8DF3\u8FC7:${likesSkippedCount}, \u5DF2\u8D5E:${likesAlreadyCount}, \u53BB\u91CD:${likesDedupCount})`;
|
|
3687
|
+
const candidates = [];
|
|
3688
|
+
const direct = normalizeLink(opResult?.noteUrl || opResult?.url || opResult?.href || opResult?.link, opResult?.noteId);
|
|
3689
|
+
if (direct) candidates.push({ ...direct, source: "comment_like" });
|
|
3690
|
+
const likedComments = Array.isArray(opResult?.likedComments) ? opResult.likedComments : [];
|
|
3691
|
+
for (const row of likedComments) {
|
|
3692
|
+
const item = normalizeLink(row?.noteUrl || row?.url || row?.href || row?.link, row?.noteId || opResult?.noteId);
|
|
3693
|
+
if (!item) continue;
|
|
3694
|
+
candidates.push({ ...item, source: "liked_comment" });
|
|
3695
|
+
}
|
|
3696
|
+
for (const item of candidates) {
|
|
3697
|
+
pushLikedLink({
|
|
3698
|
+
...item,
|
|
3699
|
+
profileId: activeProfileId || null,
|
|
3700
|
+
ts: (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false })
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
renderRunSummary();
|
|
3375
3704
|
}
|
|
3376
3705
|
return;
|
|
3377
3706
|
}
|
|
@@ -3380,7 +3709,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3380
3709
|
statFailed.textContent = String(failed + 1);
|
|
3381
3710
|
const opId = String(payload?.operationId || "").trim();
|
|
3382
3711
|
const err = String(payload?.error || payload?.message || payload?.code || event).trim();
|
|
3383
|
-
pushRecentError(opId ? `${opId}: ${err}` : err, event);
|
|
3712
|
+
pushRecentError(opId ? `${opId}: ${err}` : err, event, payload);
|
|
3384
3713
|
return;
|
|
3385
3714
|
}
|
|
3386
3715
|
if (event === "xhs.unified.merged") {
|
|
@@ -3402,7 +3731,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3402
3731
|
currentPhase.textContent = reason && reason !== "script_failure" ? "\u5DF2\u7ED3\u675F" : "\u5931\u8D25";
|
|
3403
3732
|
currentAction.textContent = reason || "stop";
|
|
3404
3733
|
if (reason && !successReasons.has(reason)) {
|
|
3405
|
-
pushRecentError(`stop reason=${reason}`, event);
|
|
3734
|
+
pushRecentError(`stop reason=${reason}`, event, payload);
|
|
3406
3735
|
}
|
|
3407
3736
|
renderRunSummary();
|
|
3408
3737
|
}
|
|
@@ -3460,6 +3789,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3460
3789
|
if (profile?.profileId) {
|
|
3461
3790
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3462
3791
|
taskAccount.textContent = aliases[profile.profileId] || profile.profileId;
|
|
3792
|
+
activeProfileId = String(profile.profileId || "").trim();
|
|
3463
3793
|
}
|
|
3464
3794
|
const runId = String(profile?.runId || summary?.runId || "").trim();
|
|
3465
3795
|
if (runId) {
|
|
@@ -3535,7 +3865,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3535
3865
|
if (payload.action) addLog(String(payload.action), "info");
|
|
3536
3866
|
if (payload.error) {
|
|
3537
3867
|
addLog(String(payload.error), "error");
|
|
3538
|
-
pushRecentError(String(payload.error), "state");
|
|
3868
|
+
pushRecentError(String(payload.error), "state", payload);
|
|
3539
3869
|
}
|
|
3540
3870
|
}
|
|
3541
3871
|
});
|
|
@@ -3552,10 +3882,12 @@ function renderDashboard(root, ctx2) {
|
|
|
3552
3882
|
unsubscribeCmd = ctx2.api.onCmdEvent((evt) => {
|
|
3553
3883
|
if (paused) return;
|
|
3554
3884
|
const runId = String(evt?.runId || "").trim();
|
|
3555
|
-
|
|
3885
|
+
const preferredRunId = String(ctx2?.activeRunId || "").trim();
|
|
3886
|
+
const shouldAdoptStartedRun = evt?.type === "started" && runId && isXhsCommandTitle(evt?.title) && (!activeRunId || isTerminalStatus(activeStatus) || preferredRunId && preferredRunId === runId);
|
|
3887
|
+
if (shouldAdoptStartedRun) {
|
|
3556
3888
|
activeRunId = runId;
|
|
3557
3889
|
activeStatus = "running";
|
|
3558
|
-
|
|
3890
|
+
resetDashboardForNewRun("\u8FDB\u7A0B\u542F\u52A8");
|
|
3559
3891
|
renderRunSummary();
|
|
3560
3892
|
}
|
|
3561
3893
|
if (activeRunId && runId && runId !== activeRunId) return;
|
|
@@ -3564,7 +3896,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3564
3896
|
parseLineEvent(String(evt.line || "").trim());
|
|
3565
3897
|
} else if (evt.type === "stderr") {
|
|
3566
3898
|
addLog(evt.line, "error");
|
|
3567
|
-
pushRecentError(String(evt.line || ""), "stderr");
|
|
3899
|
+
pushRecentError(String(evt.line || ""), "stderr", evt);
|
|
3568
3900
|
const failed = Number(statFailed.textContent || "0") || 0;
|
|
3569
3901
|
statFailed.textContent = String(failed + 1);
|
|
3570
3902
|
} else if (evt.type === "exit") {
|
|
@@ -3579,7 +3911,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3579
3911
|
stopElapsedTimer();
|
|
3580
3912
|
}
|
|
3581
3913
|
if (Number(evt.exitCode || 0) !== 0) {
|
|
3582
|
-
pushRecentError(`\u8FDB\u7A0B\u9000\u51FA code=${evt.exitCode ?? "null"}`, "exit");
|
|
3914
|
+
pushRecentError(`\u8FDB\u7A0B\u9000\u51FA code=${evt.exitCode ?? "null"}`, "exit", evt);
|
|
3583
3915
|
}
|
|
3584
3916
|
renderRunSummary();
|
|
3585
3917
|
}
|
|
@@ -3590,13 +3922,17 @@ function renderDashboard(root, ctx2) {
|
|
|
3590
3922
|
try {
|
|
3591
3923
|
const config = await ctx2.api.configLoadLast();
|
|
3592
3924
|
if (config) {
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3925
|
+
if (!hasRenderableValue(contextRun?.keyword)) {
|
|
3926
|
+
taskKeyword.textContent = config.keyword || "-";
|
|
3927
|
+
}
|
|
3928
|
+
if (!(Number(contextRun?.target) > 0)) {
|
|
3929
|
+
taskTarget.textContent = String(config.target || 50);
|
|
3930
|
+
}
|
|
3931
|
+
if (!hasRenderableValue(contextRun?.profileId) && config.lastProfileId) {
|
|
3596
3932
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3597
3933
|
taskAccount.textContent = aliases[config.lastProfileId] || config.lastProfileId;
|
|
3598
3934
|
}
|
|
3599
|
-
const taskId = String(config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3935
|
+
const taskId = String(contextRun?.taskId || config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3600
3936
|
if (taskId) {
|
|
3601
3937
|
taskConfigId.textContent = taskId;
|
|
3602
3938
|
}
|
|
@@ -3660,23 +3996,25 @@ function renderDashboard(root, ctx2) {
|
|
|
3660
3996
|
}
|
|
3661
3997
|
setTimeout(() => {
|
|
3662
3998
|
if (typeof ctx2.setActiveTab === "function") {
|
|
3663
|
-
ctx2.setActiveTab("
|
|
3999
|
+
ctx2.setActiveTab("tasks");
|
|
3664
4000
|
}
|
|
3665
4001
|
}, 1500);
|
|
3666
4002
|
}
|
|
3667
4003
|
};
|
|
3668
4004
|
backConfigBtn.onclick = () => {
|
|
3669
4005
|
if (typeof ctx2.setActiveTab === "function") {
|
|
3670
|
-
ctx2.setActiveTab("
|
|
4006
|
+
ctx2.setActiveTab("tasks");
|
|
3671
4007
|
}
|
|
3672
4008
|
};
|
|
3673
4009
|
renderRunSummary();
|
|
3674
4010
|
loadTaskInfo();
|
|
3675
4011
|
subscribeToUpdates();
|
|
3676
4012
|
fetchCurrentState();
|
|
4013
|
+
startStatePoll();
|
|
3677
4014
|
startElapsedTimer();
|
|
3678
4015
|
return () => {
|
|
3679
4016
|
stopElapsedTimer();
|
|
4017
|
+
stopStatePoll();
|
|
3680
4018
|
if (unsubscribeState) unsubscribeState();
|
|
3681
4019
|
if (unsubscribeCmd) unsubscribeCmd();
|
|
3682
4020
|
if (unsubscribeBus) unsubscribeBus();
|
|
@@ -3724,6 +4062,14 @@ function toTimestamp(value) {
|
|
|
3724
4062
|
if (!Number.isFinite(parsed)) return null;
|
|
3725
4063
|
return parsed;
|
|
3726
4064
|
}
|
|
4065
|
+
function formatProfileTag2(profileId) {
|
|
4066
|
+
const id = String(profileId || "").trim();
|
|
4067
|
+
const m = id.match(/^profile-(\d+)$/i);
|
|
4068
|
+
if (!m) return id;
|
|
4069
|
+
const seq = Number(m[1]);
|
|
4070
|
+
if (!Number.isFinite(seq)) return id;
|
|
4071
|
+
return `P${String(seq).padStart(3, "0")}`;
|
|
4072
|
+
}
|
|
3727
4073
|
function renderAccountManager(root, ctx2) {
|
|
3728
4074
|
root.innerHTML = "";
|
|
3729
4075
|
const autoSyncTimers = /* @__PURE__ */ new Map();
|
|
@@ -3746,7 +4092,7 @@ function renderAccountManager(root, ctx2) {
|
|
|
3746
4092
|
</div>
|
|
3747
4093
|
<div class="env-item" id="env-firefox">
|
|
3748
4094
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
3749
|
-
<span
|
|
4095
|
+
<span>\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox Firefox\uFF09</span>
|
|
3750
4096
|
</div>
|
|
3751
4097
|
</div>
|
|
3752
4098
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
@@ -3789,15 +4135,18 @@ function renderAccountManager(root, ctx2) {
|
|
|
3789
4135
|
let busUnsubscribe = null;
|
|
3790
4136
|
async function checkEnvironment() {
|
|
3791
4137
|
try {
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
updateEnvItem("env-
|
|
4138
|
+
if (typeof ctx2.api?.envCheckAll !== "function") {
|
|
4139
|
+
throw new Error("envCheckAll unavailable");
|
|
4140
|
+
}
|
|
4141
|
+
const unified = await ctx2.api.envCheckAll();
|
|
4142
|
+
if (!unified || typeof unified !== "object") {
|
|
4143
|
+
throw new Error("invalid envCheckAll response");
|
|
4144
|
+
}
|
|
4145
|
+
const browserReady = Boolean(unified.browserReady);
|
|
4146
|
+
updateEnvItem("env-camo", Boolean(unified.camo?.installed));
|
|
4147
|
+
updateEnvItem("env-unified", Boolean(unified.services?.unifiedApi));
|
|
4148
|
+
updateEnvItem("env-browser", Boolean(unified.services?.camoRuntime));
|
|
4149
|
+
updateEnvItem("env-firefox", browserReady);
|
|
3801
4150
|
} catch (err) {
|
|
3802
4151
|
console.error("Environment check failed:", err);
|
|
3803
4152
|
}
|
|
@@ -3811,6 +4160,18 @@ function renderAccountManager(root, ctx2) {
|
|
|
3811
4160
|
envCheckInFlight = false;
|
|
3812
4161
|
}
|
|
3813
4162
|
}
|
|
4163
|
+
async function cleanupEnvironment() {
|
|
4164
|
+
try {
|
|
4165
|
+
console.log("[account-manager] Starting environment cleanup...");
|
|
4166
|
+
const result = await ctx2.api.envCleanup();
|
|
4167
|
+
console.log("[account-manager] Cleanup result:", result);
|
|
4168
|
+
alert("\u73AF\u5883\u6E05\u7406\u5B8C\u6210\uFF01\\n" + JSON.stringify(result, null, 2));
|
|
4169
|
+
await checkEnvironment();
|
|
4170
|
+
} catch (err) {
|
|
4171
|
+
console.error("Environment cleanup failed:", err);
|
|
4172
|
+
alert("\u73AF\u5883\u6E05\u7406\u5931\u8D25\uFF1A" + err.message);
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
3814
4175
|
function updateEnvItem(id, ok) {
|
|
3815
4176
|
const el = root.querySelector(`#${id}`);
|
|
3816
4177
|
if (!el) return;
|
|
@@ -3826,7 +4187,11 @@ function renderAccountManager(root, ctx2) {
|
|
|
3826
4187
|
platform: normalizePlatform(row.platform),
|
|
3827
4188
|
statusView: row.valid ? "valid" : row.status === "pending" ? "pending" : "expired",
|
|
3828
4189
|
lastCheckAt: toTimestamp(row.updatedAt)
|
|
3829
|
-
}))
|
|
4190
|
+
})).sort((a, b) => {
|
|
4191
|
+
const p = String(a.profileId || "").localeCompare(String(b.profileId || ""));
|
|
4192
|
+
if (p !== 0) return p;
|
|
4193
|
+
return String(a.platform || "").localeCompare(String(b.platform || ""));
|
|
4194
|
+
});
|
|
3830
4195
|
renderAccountList();
|
|
3831
4196
|
} catch (err) {
|
|
3832
4197
|
console.error("Failed to load accounts:", err);
|
|
@@ -3860,11 +4225,11 @@ function renderAccountManager(root, ctx2) {
|
|
|
3860
4225
|
const nameDiv = createEl("div", { style: "min-width: 0; flex: 1;" }, [
|
|
3861
4226
|
createEl("div", { className: "account-name", style: "display: flex; gap: 6px; align-items: center;" }, [
|
|
3862
4227
|
createEl("span", { style: "font-size: 13px;" }, [platform.icon]),
|
|
3863
|
-
createEl("span", {}, [acc.alias || acc.name || acc.profileId]),
|
|
4228
|
+
createEl("span", {}, [acc.alias || acc.name || formatProfileTag2(acc.profileId)]),
|
|
3864
4229
|
createEl("span", { style: "font-size: 11px; color: var(--text-3);" }, [platform.label])
|
|
3865
4230
|
]),
|
|
3866
4231
|
createEl("div", { className: "account-alias", style: "font-size: 11px; color: var(--text-3);" }, [
|
|
3867
|
-
`profile: ${acc.profileId} \xB7 \u4E0A\u6B21\u68C0\u67E5: ${formatTs(acc.lastCheckAt)}`
|
|
4232
|
+
`profile: ${formatProfileTag2(acc.profileId)} (${acc.profileId}) \xB7 \u4E0A\u6B21\u68C0\u67E5: ${formatTs(acc.lastCheckAt)}`
|
|
3868
4233
|
])
|
|
3869
4234
|
]);
|
|
3870
4235
|
const statusBadge = createEl("span", {
|
|
@@ -4186,11 +4551,6 @@ Profile ID: ${acc.profileId}
|
|
|
4186
4551
|
}
|
|
4187
4552
|
|
|
4188
4553
|
// src/renderer/tabs-new/scheduler.mts
|
|
4189
|
-
function commandTypeToWeiboTaskType2(commandType) {
|
|
4190
|
-
if (commandType === "weibo-search") return "search";
|
|
4191
|
-
if (commandType === "weibo-monitor") return "monitor";
|
|
4192
|
-
return "timeline";
|
|
4193
|
-
}
|
|
4194
4554
|
function renderSchedulerPanel(root, ctx2) {
|
|
4195
4555
|
root.innerHTML = "";
|
|
4196
4556
|
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
@@ -4259,18 +4619,25 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4259
4619
|
<div>
|
|
4260
4620
|
<label>\u8C03\u5EA6\u7C7B\u578B</label>
|
|
4261
4621
|
<select id="scheduler-type" style="width: 140px;">
|
|
4262
|
-
<option value="
|
|
4263
|
-
<option value="
|
|
4622
|
+
<option value="immediate">\u9A6C\u4E0A\u6267\u884C\uFF08\u4EC5\u4E00\u6B21\uFF09</option>
|
|
4623
|
+
<option value="periodic">\u5468\u671F\u4EFB\u52A1</option>
|
|
4624
|
+
<option value="scheduled">\u5B9A\u65F6\u4EFB\u52A1</option>
|
|
4625
|
+
</select>
|
|
4626
|
+
</div>
|
|
4627
|
+
<div id="scheduler-periodic-type-wrap" style="display:none;">
|
|
4628
|
+
<label>\u5468\u671F\u7C7B\u578B</label>
|
|
4629
|
+
<select id="scheduler-periodic-type" style="width: 120px;">
|
|
4630
|
+
<option value="interval">\u6309\u95F4\u9694</option>
|
|
4264
4631
|
<option value="daily">\u6BCF\u5929</option>
|
|
4265
4632
|
<option value="weekly">\u6BCF\u5468</option>
|
|
4266
4633
|
</select>
|
|
4267
4634
|
</div>
|
|
4268
|
-
<div id="scheduler-interval-wrap">
|
|
4635
|
+
<div id="scheduler-interval-wrap" style="display:none;">
|
|
4269
4636
|
<label>\u95F4\u9694\u5206\u949F</label>
|
|
4270
4637
|
<input id="scheduler-interval" type="number" min="1" value="30" style="width: 120px;" />
|
|
4271
4638
|
</div>
|
|
4272
4639
|
<div id="scheduler-runat-wrap" style="display:none;">
|
|
4273
|
-
<label>\
|
|
4640
|
+
<label>\u6267\u884C\u65F6\u95F4</label>
|
|
4274
4641
|
<input id="scheduler-runat" type="datetime-local" style="width: 220px;" />
|
|
4275
4642
|
</div>
|
|
4276
4643
|
<div>
|
|
@@ -4280,8 +4647,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4280
4647
|
</div>
|
|
4281
4648
|
<div class="row">
|
|
4282
4649
|
<div>
|
|
4283
|
-
<label>Profile</label>
|
|
4284
|
-
<input id="scheduler-profile" placeholder="
|
|
4650
|
+
<label>Profile\uFF08\u53EF\u7559\u7A7A\u81EA\u52A8\u9009\uFF09</label>
|
|
4651
|
+
<input id="scheduler-profile" placeholder="\u7559\u7A7A\u81EA\u52A8\u9009\u62E9\u8BE5\u5E73\u53F0\u6709\u6548\u8D26\u53F7" style="width: 260px;" />
|
|
4652
|
+
<div id="scheduler-profile-hint" class="muted" style="font-size:11px; margin-top:2px;">\u63A8\u8350: -</div>
|
|
4285
4653
|
</div>
|
|
4286
4654
|
<div>
|
|
4287
4655
|
<label>\u5173\u952E\u8BCD</label>
|
|
@@ -4331,6 +4699,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4331
4699
|
</div>
|
|
4332
4700
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
4333
4701
|
<button id="scheduler-save-btn" style="flex:1;">\u4FDD\u5B58\u4EFB\u52A1</button>
|
|
4702
|
+
<button id="scheduler-run-now-btn" class="secondary" style="flex:1;">\u7ACB\u5373\u6267\u884C(\u4E0D\u4FDD\u5B58)</button>
|
|
4334
4703
|
<button id="scheduler-reset-btn" class="secondary" style="flex:1;">\u6E05\u7A7A\u8868\u5355</button>
|
|
4335
4704
|
</div>
|
|
4336
4705
|
`;
|
|
@@ -4359,12 +4728,15 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4359
4728
|
const nameInput = root.querySelector("#scheduler-name");
|
|
4360
4729
|
const enabledInput = root.querySelector("#scheduler-enabled");
|
|
4361
4730
|
const typeSelect = root.querySelector("#scheduler-type");
|
|
4731
|
+
const periodicTypeWrap = root.querySelector("#scheduler-periodic-type-wrap");
|
|
4732
|
+
const periodicTypeSelect = root.querySelector("#scheduler-periodic-type");
|
|
4362
4733
|
const intervalWrap = root.querySelector("#scheduler-interval-wrap");
|
|
4363
4734
|
const runAtWrap = root.querySelector("#scheduler-runat-wrap");
|
|
4364
4735
|
const intervalInput = root.querySelector("#scheduler-interval");
|
|
4365
4736
|
const runAtInput = root.querySelector("#scheduler-runat");
|
|
4366
4737
|
const maxRunsInput = root.querySelector("#scheduler-max-runs");
|
|
4367
4738
|
const profileInput = root.querySelector("#scheduler-profile");
|
|
4739
|
+
const profileHint = root.querySelector("#scheduler-profile-hint");
|
|
4368
4740
|
const keywordInput = root.querySelector("#scheduler-keyword");
|
|
4369
4741
|
const userIdWrap = root.querySelector("#scheduler-user-id-wrap");
|
|
4370
4742
|
const userIdInput = root.querySelector("#scheduler-user-id");
|
|
@@ -4376,8 +4748,10 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4376
4748
|
const dryRunInput = root.querySelector("#scheduler-dryrun");
|
|
4377
4749
|
const likeKeywordsInput = root.querySelector("#scheduler-like-keywords");
|
|
4378
4750
|
const saveBtn = root.querySelector("#scheduler-save-btn");
|
|
4751
|
+
const runNowBtn = root.querySelector("#scheduler-run-now-btn");
|
|
4379
4752
|
const resetBtn = root.querySelector("#scheduler-reset-btn");
|
|
4380
4753
|
let tasks = [];
|
|
4754
|
+
let accountRows = [];
|
|
4381
4755
|
let daemonRunId = "";
|
|
4382
4756
|
let unsubscribeCmd = null;
|
|
4383
4757
|
let pendingFocusTaskId = String(ctx2?.activeTaskConfigId || "").trim();
|
|
@@ -4394,14 +4768,21 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4394
4768
|
function openConfigTab(taskId) {
|
|
4395
4769
|
setActiveTaskContext(taskId);
|
|
4396
4770
|
if (typeof ctx2.setActiveTab === "function") {
|
|
4397
|
-
ctx2.setActiveTab("
|
|
4771
|
+
ctx2.setActiveTab("tasks");
|
|
4398
4772
|
}
|
|
4399
4773
|
}
|
|
4400
4774
|
function updateTypeFields() {
|
|
4401
|
-
const mode = typeSelect.value;
|
|
4402
|
-
const
|
|
4403
|
-
|
|
4404
|
-
|
|
4775
|
+
const mode = String(typeSelect.value || "immediate").trim();
|
|
4776
|
+
const periodicType = String(periodicTypeSelect.value || "interval").trim();
|
|
4777
|
+
const periodic = mode === "periodic";
|
|
4778
|
+
const scheduled = mode === "scheduled";
|
|
4779
|
+
periodicTypeWrap.style.display = periodic ? "" : "none";
|
|
4780
|
+
runAtWrap.style.display = scheduled || periodic && periodicType !== "interval" ? "" : "none";
|
|
4781
|
+
intervalWrap.style.display = periodic && periodicType === "interval" ? "" : "none";
|
|
4782
|
+
maxRunsInput.disabled = mode === "immediate" || mode === "scheduled";
|
|
4783
|
+
if (mode === "immediate" || mode === "scheduled") {
|
|
4784
|
+
maxRunsInput.value = "";
|
|
4785
|
+
}
|
|
4405
4786
|
}
|
|
4406
4787
|
function updateTaskTypeOptions() {
|
|
4407
4788
|
const platform = platformSelect.value;
|
|
@@ -4411,6 +4792,36 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4411
4792
|
taskTypeSelect.value = taskTypeSelect.options[0]?.value || "";
|
|
4412
4793
|
}
|
|
4413
4794
|
updatePlatformFields();
|
|
4795
|
+
void refreshPlatformAccounts(platform);
|
|
4796
|
+
}
|
|
4797
|
+
function normalizePlatform2(value) {
|
|
4798
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
4799
|
+
if (raw === "weibo") return "weibo";
|
|
4800
|
+
if (raw === "1688") return "1688";
|
|
4801
|
+
return "xiaohongshu";
|
|
4802
|
+
}
|
|
4803
|
+
async function refreshPlatformAccounts(platformValue) {
|
|
4804
|
+
const platform = normalizePlatform2(platformValue);
|
|
4805
|
+
try {
|
|
4806
|
+
accountRows = await listAccountProfiles(ctx2.api, { platform: platform === "xiaohongshu" ? "xiaohongshu" : platform });
|
|
4807
|
+
} catch {
|
|
4808
|
+
accountRows = [];
|
|
4809
|
+
}
|
|
4810
|
+
const recommended = accountRows.filter((row) => row.valid).sort((a, b) => {
|
|
4811
|
+
const ta = Date.parse(String(a.updatedAt || "")) || 0;
|
|
4812
|
+
const tb = Date.parse(String(b.updatedAt || "")) || 0;
|
|
4813
|
+
if (tb !== ta) return tb - ta;
|
|
4814
|
+
return String(a.profileId || "").localeCompare(String(b.profileId || ""));
|
|
4815
|
+
})[0];
|
|
4816
|
+
if (!recommended) {
|
|
4817
|
+
profileHint.textContent = `\u63A8\u8350: \u5F53\u524D\u5E73\u53F0(${platform})\u65E0\u6709\u6548\u8D26\u53F7`;
|
|
4818
|
+
return;
|
|
4819
|
+
}
|
|
4820
|
+
const label = recommended.alias || recommended.name || recommended.profileId;
|
|
4821
|
+
profileHint.textContent = `\u63A8\u8350: ${label} (${recommended.profileId})`;
|
|
4822
|
+
if (!String(profileInput.value || "").trim()) {
|
|
4823
|
+
profileInput.value = recommended.profileId;
|
|
4824
|
+
}
|
|
4414
4825
|
}
|
|
4415
4826
|
function updatePlatformFields() {
|
|
4416
4827
|
const commandType = String(taskTypeSelect.value || "").trim();
|
|
@@ -4423,7 +4834,8 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4423
4834
|
editingIdInput.value = "";
|
|
4424
4835
|
nameInput.value = "";
|
|
4425
4836
|
enabledInput.checked = true;
|
|
4426
|
-
typeSelect.value = "
|
|
4837
|
+
typeSelect.value = "immediate";
|
|
4838
|
+
periodicTypeSelect.value = "interval";
|
|
4427
4839
|
intervalInput.value = "30";
|
|
4428
4840
|
runAtInput.value = "";
|
|
4429
4841
|
maxRunsInput.value = "";
|
|
@@ -4446,7 +4858,6 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4446
4858
|
const maxRuns = maxRunsRaw ? Math.max(1, Number(maxRunsRaw) || 1) : null;
|
|
4447
4859
|
const commandType = String(taskTypeSelect.value || "xhs-unified").trim();
|
|
4448
4860
|
const argv = {
|
|
4449
|
-
profile: profileInput.value.trim(),
|
|
4450
4861
|
keyword: keywordInput.value.trim(),
|
|
4451
4862
|
"max-notes": Number(maxNotesInput.value || 50) || 50,
|
|
4452
4863
|
env: envSelect.value,
|
|
@@ -4456,19 +4867,40 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4456
4867
|
headless: headlessInput.checked,
|
|
4457
4868
|
"dry-run": dryRunInput.checked
|
|
4458
4869
|
};
|
|
4870
|
+
const profileValue = profileInput.value.trim();
|
|
4871
|
+
if (profileValue) argv.profile = profileValue;
|
|
4459
4872
|
if (commandType.startsWith("weibo")) {
|
|
4460
|
-
argv["task-type"] = commandTypeToWeiboTaskType2(commandType);
|
|
4461
4873
|
argv["user-id"] = userIdInput.value.trim();
|
|
4462
4874
|
}
|
|
4875
|
+
const mode = String(typeSelect.value || "immediate").trim();
|
|
4876
|
+
const periodicType = String(periodicTypeSelect.value || "interval").trim();
|
|
4877
|
+
let scheduleType = "once";
|
|
4878
|
+
let runAt = toIsoOrNull(runAtInput.value);
|
|
4879
|
+
let maxRunsFinal = maxRuns;
|
|
4880
|
+
if (mode === "immediate") {
|
|
4881
|
+
scheduleType = "once";
|
|
4882
|
+
runAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4883
|
+
maxRunsFinal = 1;
|
|
4884
|
+
} else if (mode === "periodic") {
|
|
4885
|
+
if (periodicType === "daily" || periodicType === "weekly") {
|
|
4886
|
+
scheduleType = periodicType;
|
|
4887
|
+
} else {
|
|
4888
|
+
scheduleType = "interval";
|
|
4889
|
+
runAt = null;
|
|
4890
|
+
}
|
|
4891
|
+
} else {
|
|
4892
|
+
scheduleType = "once";
|
|
4893
|
+
maxRunsFinal = 1;
|
|
4894
|
+
}
|
|
4463
4895
|
return {
|
|
4464
4896
|
id: editingIdInput.value.trim(),
|
|
4465
4897
|
name: nameInput.value.trim(),
|
|
4466
4898
|
enabled: enabledInput.checked,
|
|
4467
4899
|
commandType,
|
|
4468
|
-
scheduleType
|
|
4900
|
+
scheduleType,
|
|
4469
4901
|
intervalMinutes: Number(intervalInput.value || 30) || 30,
|
|
4470
|
-
runAt
|
|
4471
|
-
maxRuns,
|
|
4902
|
+
runAt,
|
|
4903
|
+
maxRuns: maxRunsFinal,
|
|
4472
4904
|
argv
|
|
4473
4905
|
};
|
|
4474
4906
|
}
|
|
@@ -4481,7 +4913,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4481
4913
|
editingIdInput.value = task.id;
|
|
4482
4914
|
nameInput.value = task.name || "";
|
|
4483
4915
|
enabledInput.checked = task.enabled !== false;
|
|
4484
|
-
|
|
4916
|
+
const uiSchedule = inferUiScheduleEditorState(task);
|
|
4917
|
+
typeSelect.value = uiSchedule.mode;
|
|
4918
|
+
periodicTypeSelect.value = uiSchedule.periodicType;
|
|
4485
4919
|
intervalInput.value = String(task.intervalMinutes || 30);
|
|
4486
4920
|
runAtInput.value = toLocalDatetimeValue(task.runAt);
|
|
4487
4921
|
maxRunsInput.value = task.maxRuns ? String(task.maxRuns) : "";
|
|
@@ -4499,19 +4933,27 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4499
4933
|
updatePlatformFields();
|
|
4500
4934
|
updateTypeFields();
|
|
4501
4935
|
}
|
|
4502
|
-
async function
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
args: [script, ...args, "--json"],
|
|
4508
|
-
timeoutMs
|
|
4509
|
-
});
|
|
4936
|
+
async function invokeSchedule(input) {
|
|
4937
|
+
if (typeof ctx2.api?.scheduleInvoke !== "function") {
|
|
4938
|
+
throw new Error("scheduleInvoke unavailable");
|
|
4939
|
+
}
|
|
4940
|
+
const ret = await ctx2.api.scheduleInvoke(input);
|
|
4510
4941
|
if (!ret?.ok) {
|
|
4511
|
-
const reason = String(ret?.error ||
|
|
4942
|
+
const reason = String(ret?.error || "schedule command failed").trim();
|
|
4512
4943
|
throw new Error(reason || "schedule command failed");
|
|
4513
4944
|
}
|
|
4514
|
-
return ret
|
|
4945
|
+
return ret?.json ?? ret;
|
|
4946
|
+
}
|
|
4947
|
+
async function invokeTaskRunEphemeral(input) {
|
|
4948
|
+
if (typeof ctx2.api?.taskRunEphemeral !== "function") {
|
|
4949
|
+
throw new Error("taskRunEphemeral unavailable");
|
|
4950
|
+
}
|
|
4951
|
+
const ret = await ctx2.api.taskRunEphemeral(input);
|
|
4952
|
+
if (!ret?.ok) {
|
|
4953
|
+
const reason = String(ret?.error || "run ephemeral failed").trim();
|
|
4954
|
+
throw new Error(reason || "run ephemeral failed");
|
|
4955
|
+
}
|
|
4956
|
+
return ret;
|
|
4515
4957
|
}
|
|
4516
4958
|
function downloadJson(fileName, payload) {
|
|
4517
4959
|
const text = JSON.stringify(payload, null, 2);
|
|
@@ -4533,7 +4975,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4533
4975
|
const card = createEl("div", {
|
|
4534
4976
|
style: "border:1px solid var(--border); border-radius:10px; padding:10px; margin-bottom:10px; background: var(--panel-soft);"
|
|
4535
4977
|
});
|
|
4536
|
-
const scheduleText = task.scheduleType === "once" ?
|
|
4978
|
+
const scheduleText = task.scheduleType === "once" ? `\u5B9A\u65F6\u4EFB\u52A1 @ ${task.runAt || "-"}` : task.scheduleType === "daily" ? `\u5468\u671F\u4EFB\u52A1(\u65E5) @ ${task.runAt || "-"}` : task.scheduleType === "weekly" ? `\u5468\u671F\u4EFB\u52A1(\u5468) @ ${task.runAt || "-"}` : `\u5468\u671F\u4EFB\u52A1(\u95F4\u9694 ${task.intervalMinutes}m)`;
|
|
4537
4979
|
const statusText = task.lastStatus ? `${task.lastStatus} / run=${task.runCount} / fail=${task.failCount}` : "never run";
|
|
4538
4980
|
const headRow = createEl("div", { style: "display:flex; justify-content:space-between; gap:8px; margin-bottom:6px;" });
|
|
4539
4981
|
headRow.appendChild(createEl("div", { style: "font-weight:600;" }, [task.name || task.id]));
|
|
@@ -4582,7 +5024,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4582
5024
|
runBtn.onclick = async () => {
|
|
4583
5025
|
try {
|
|
4584
5026
|
setActiveTaskContext(task.id);
|
|
4585
|
-
const out = await
|
|
5027
|
+
const out = await invokeSchedule({ action: "run", taskId: task.id, timeoutMs: 0 });
|
|
4586
5028
|
const runId = String(
|
|
4587
5029
|
out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
|
|
4588
5030
|
).trim();
|
|
@@ -4596,6 +5038,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4596
5038
|
target: Number(argv["max-notes"] || argv.target || 0) || 0,
|
|
4597
5039
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4598
5040
|
};
|
|
5041
|
+
ctx2.activeRunId = runId || null;
|
|
4599
5042
|
}
|
|
4600
5043
|
if (typeof ctx2.setStatus === "function") {
|
|
4601
5044
|
ctx2.setStatus(`running: ${task.id}`);
|
|
@@ -4610,7 +5053,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4610
5053
|
};
|
|
4611
5054
|
exportBtn.onclick = async () => {
|
|
4612
5055
|
try {
|
|
4613
|
-
const out = await
|
|
5056
|
+
const out = await invokeSchedule({ action: "export", taskId: task.id });
|
|
4614
5057
|
downloadJson(`${task.id}.json`, out);
|
|
4615
5058
|
} catch (err) {
|
|
4616
5059
|
alert(`\u5BFC\u51FA\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4619,7 +5062,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4619
5062
|
delBtn.onclick = async () => {
|
|
4620
5063
|
if (!confirm(`\u786E\u8BA4\u5220\u9664\u4EFB\u52A1 ${task.id} ?`)) return;
|
|
4621
5064
|
try {
|
|
4622
|
-
await
|
|
5065
|
+
await invokeSchedule({ action: "delete", taskId: task.id });
|
|
4623
5066
|
await refreshList();
|
|
4624
5067
|
} catch (err) {
|
|
4625
5068
|
alert(`\u5220\u9664\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4629,7 +5072,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4629
5072
|
}
|
|
4630
5073
|
}
|
|
4631
5074
|
async function refreshList() {
|
|
4632
|
-
const out = await
|
|
5075
|
+
const out = await invokeSchedule({ action: "list" });
|
|
4633
5076
|
tasks = parseTaskRows(out);
|
|
4634
5077
|
if (!pendingFocusTaskId) {
|
|
4635
5078
|
pendingFocusTaskId = String(ctx2?.activeTaskConfigId || "").trim();
|
|
@@ -4649,48 +5092,8 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4649
5092
|
}
|
|
4650
5093
|
async function saveTask() {
|
|
4651
5094
|
const payload = readFormAsPayload();
|
|
4652
|
-
if (!payload.name) {
|
|
4653
|
-
alert("\u4EFB\u52A1\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4654
|
-
return;
|
|
4655
|
-
}
|
|
4656
|
-
if (!payload.argv.profile && !payload.argv.profiles && !payload.argv.profilepool) {
|
|
4657
|
-
alert("profile/profiles/profilepool \u81F3\u5C11\u586B\u5199\u4E00\u4E2A");
|
|
4658
|
-
return;
|
|
4659
|
-
}
|
|
4660
|
-
const commandType = String(payload.commandType || "").trim();
|
|
4661
|
-
const keywordRequired = commandType === "xhs-unified" || commandType === "weibo-search" || commandType === "1688-search";
|
|
4662
|
-
if (keywordRequired && !payload.argv.keyword) {
|
|
4663
|
-
alert("\u5173\u952E\u8BCD\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4664
|
-
return;
|
|
4665
|
-
}
|
|
4666
|
-
if (commandType === "weibo-monitor" && !payload.argv["user-id"]) {
|
|
4667
|
-
alert("\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id");
|
|
4668
|
-
return;
|
|
4669
|
-
}
|
|
4670
|
-
const args = payload.id ? ["update", payload.id] : ["add"];
|
|
4671
|
-
args.push("--name", payload.name);
|
|
4672
|
-
args.push("--enabled", String(payload.enabled));
|
|
4673
|
-
args.push("--command-type", commandType || "xhs-unified");
|
|
4674
|
-
args.push("--schedule-type", payload.scheduleType);
|
|
4675
|
-
if (payload.scheduleType === "once") {
|
|
4676
|
-
if (!payload.runAt) {
|
|
4677
|
-
alert("\u4E00\u6B21\u6027\u4EFB\u52A1\u9700\u8981\u951A\u70B9\u65F6\u95F4");
|
|
4678
|
-
return;
|
|
4679
|
-
}
|
|
4680
|
-
args.push("--run-at", payload.runAt);
|
|
4681
|
-
} else if (payload.scheduleType === "daily" || payload.scheduleType === "weekly") {
|
|
4682
|
-
if (!payload.runAt) {
|
|
4683
|
-
alert(`${payload.scheduleType} \u4EFB\u52A1\u9700\u8981\u951A\u70B9\u65F6\u95F4`);
|
|
4684
|
-
return;
|
|
4685
|
-
}
|
|
4686
|
-
args.push("--run-at", payload.runAt);
|
|
4687
|
-
} else {
|
|
4688
|
-
args.push("--interval-minutes", String(Math.max(1, payload.intervalMinutes)));
|
|
4689
|
-
}
|
|
4690
|
-
args.push("--max-runs", payload.maxRuns === null ? "0" : String(payload.maxRuns));
|
|
4691
|
-
args.push("--argv-json", JSON.stringify(payload.argv));
|
|
4692
5095
|
try {
|
|
4693
|
-
const out = await
|
|
5096
|
+
const out = await invokeSchedule({ action: "save", payload });
|
|
4694
5097
|
const savedId = String(out?.task?.id || payload.id || "").trim();
|
|
4695
5098
|
pendingFocusTaskId = savedId;
|
|
4696
5099
|
if (savedId) setActiveTaskContext(savedId);
|
|
@@ -4699,9 +5102,44 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4699
5102
|
alert(`\u4FDD\u5B58\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4700
5103
|
}
|
|
4701
5104
|
}
|
|
5105
|
+
async function runNowFromForm() {
|
|
5106
|
+
runNowBtn.disabled = true;
|
|
5107
|
+
const prevText = runNowBtn.textContent;
|
|
5108
|
+
runNowBtn.textContent = "\u6267\u884C\u4E2D...";
|
|
5109
|
+
try {
|
|
5110
|
+
const payload = readFormAsPayload();
|
|
5111
|
+
const ret = await invokeTaskRunEphemeral({
|
|
5112
|
+
commandType: payload.commandType,
|
|
5113
|
+
argv: payload.argv
|
|
5114
|
+
});
|
|
5115
|
+
const runId = String(ret?.runId || "").trim();
|
|
5116
|
+
if (payload.commandType === "xhs-unified" && ctx2 && typeof ctx2 === "object") {
|
|
5117
|
+
ctx2.xhsCurrentRun = {
|
|
5118
|
+
runId: runId || null,
|
|
5119
|
+
taskId: null,
|
|
5120
|
+
profileId: String(payload.argv.profile || ""),
|
|
5121
|
+
keyword: String(payload.argv.keyword || ""),
|
|
5122
|
+
target: Number(payload.argv["max-notes"] || payload.argv.target || 0) || 0,
|
|
5123
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5124
|
+
};
|
|
5125
|
+
ctx2.activeRunId = runId || null;
|
|
5126
|
+
}
|
|
5127
|
+
if (typeof ctx2.setStatus === "function") {
|
|
5128
|
+
ctx2.setStatus(`started: ${payload.commandType}`);
|
|
5129
|
+
}
|
|
5130
|
+
if (payload.commandType === "xhs-unified" && typeof ctx2.setActiveTab === "function") {
|
|
5131
|
+
ctx2.setActiveTab("dashboard");
|
|
5132
|
+
}
|
|
5133
|
+
} catch (err) {
|
|
5134
|
+
alert(`\u6267\u884C\u5931\u8D25: ${err?.message || String(err)}`);
|
|
5135
|
+
} finally {
|
|
5136
|
+
runNowBtn.disabled = false;
|
|
5137
|
+
runNowBtn.textContent = prevText || "\u7ACB\u5373\u6267\u884C(\u4E0D\u4FDD\u5B58)";
|
|
5138
|
+
}
|
|
5139
|
+
}
|
|
4702
5140
|
async function runDueNow() {
|
|
4703
5141
|
try {
|
|
4704
|
-
const out = await
|
|
5142
|
+
const out = await invokeSchedule({ action: "run-due", limit: 20, timeoutMs: 0 });
|
|
4705
5143
|
alert(`\u5230\u70B9\u4EFB\u52A1\u6267\u884C\u5B8C\u6210\uFF1Adue=${out.count || 0}, success=${out.success || 0}, failed=${out.failed || 0}`);
|
|
4706
5144
|
await refreshList();
|
|
4707
5145
|
} catch (err) {
|
|
@@ -4710,7 +5148,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4710
5148
|
}
|
|
4711
5149
|
async function exportAll() {
|
|
4712
5150
|
try {
|
|
4713
|
-
const out = await
|
|
5151
|
+
const out = await invokeSchedule({ action: "export" });
|
|
4714
5152
|
downloadJson("webauto-schedules.json", out);
|
|
4715
5153
|
} catch (err) {
|
|
4716
5154
|
alert(`\u5BFC\u51FA\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4725,7 +5163,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4725
5163
|
if (!file) return;
|
|
4726
5164
|
try {
|
|
4727
5165
|
const text = await file.text();
|
|
4728
|
-
await
|
|
5166
|
+
await invokeSchedule({ action: "import", payloadJson: text, mode: "merge" });
|
|
4729
5167
|
await refreshList();
|
|
4730
5168
|
} catch (err) {
|
|
4731
5169
|
alert(`\u5BFC\u5165\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4739,13 +5177,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4739
5177
|
return;
|
|
4740
5178
|
}
|
|
4741
5179
|
const interval = Math.max(5, Number(daemonIntervalInput.value || 30) || 30);
|
|
4742
|
-
const
|
|
4743
|
-
const ret = await ctx2.api.cmdSpawn({
|
|
4744
|
-
title: `schedule daemon ${interval}s`,
|
|
4745
|
-
cwd: "",
|
|
4746
|
-
args: [script, "daemon", "--interval-sec", String(interval), "--limit", "20", "--json"],
|
|
4747
|
-
groupKey: "scheduler"
|
|
4748
|
-
});
|
|
5180
|
+
const ret = await invokeSchedule({ action: "daemon-start", intervalSec: interval, limit: 20 });
|
|
4749
5181
|
daemonRunId = String(ret?.runId || "").trim();
|
|
4750
5182
|
setDaemonStatus(daemonRunId ? `daemon: \u8FD0\u884C\u4E2D (${daemonRunId})` : "daemon: \u542F\u52A8\u5931\u8D25");
|
|
4751
5183
|
}
|
|
@@ -4764,7 +5196,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4764
5196
|
platformSelect.addEventListener("change", updateTaskTypeOptions);
|
|
4765
5197
|
taskTypeSelect.addEventListener("change", updatePlatformFields);
|
|
4766
5198
|
typeSelect.addEventListener("change", updateTypeFields);
|
|
5199
|
+
periodicTypeSelect.addEventListener("change", updateTypeFields);
|
|
4767
5200
|
saveBtn.onclick = () => void saveTask();
|
|
5201
|
+
runNowBtn.onclick = () => void runNowFromForm();
|
|
4768
5202
|
resetBtn.onclick = () => resetForm();
|
|
4769
5203
|
refreshBtn.onclick = () => void refreshList();
|
|
4770
5204
|
runDueBtn.onclick = () => void runDueNow();
|
|
@@ -4816,6 +5250,8 @@ var tabs = [
|
|
|
4816
5250
|
var tabsEl = document.getElementById("tabs");
|
|
4817
5251
|
var contentEl = document.getElementById("content");
|
|
4818
5252
|
var statusEl = document.getElementById("status");
|
|
5253
|
+
var appTitleEl = document.getElementById("app-title");
|
|
5254
|
+
var appVersionEl = document.getElementById("app-version");
|
|
4819
5255
|
var activeTabCleanup = null;
|
|
4820
5256
|
var mutableApi = { ...window.api || {}, settings: null };
|
|
4821
5257
|
var tabIcons = {
|
|
@@ -4891,6 +5327,22 @@ function startDesktopHeartbeat() {
|
|
|
4891
5327
|
async function loadSettings() {
|
|
4892
5328
|
await ctx.refreshSettings();
|
|
4893
5329
|
}
|
|
5330
|
+
async function applyVersionBadge() {
|
|
5331
|
+
try {
|
|
5332
|
+
if (typeof window.api?.appGetVersion !== "function") return;
|
|
5333
|
+
const info = await window.api.appGetVersion();
|
|
5334
|
+
const webauto = String(info?.webauto || "").trim();
|
|
5335
|
+
const desktop = String(info?.desktop || "").trim();
|
|
5336
|
+
const badge = String(info?.badge || "").trim();
|
|
5337
|
+
if (appTitleEl && webauto) {
|
|
5338
|
+
appTitleEl.textContent = `WebAuto Console v${webauto}`;
|
|
5339
|
+
}
|
|
5340
|
+
if (appVersionEl) {
|
|
5341
|
+
appVersionEl.textContent = badge || (desktop && desktop !== webauto ? `webauto v${webauto} \xB7 console v${desktop}` : webauto ? `v${webauto}` : "v-");
|
|
5342
|
+
}
|
|
5343
|
+
} catch {
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
4894
5346
|
function focusTabButton(tabId) {
|
|
4895
5347
|
const button = tabsEl.querySelector(`[data-tab-id="${tabId}"]`);
|
|
4896
5348
|
button?.focus();
|
|
@@ -4998,6 +5450,7 @@ async function detectStartupTab() {
|
|
|
4998
5450
|
}
|
|
4999
5451
|
async function main() {
|
|
5000
5452
|
startDesktopHeartbeat();
|
|
5453
|
+
await applyVersionBadge();
|
|
5001
5454
|
await loadSettings();
|
|
5002
5455
|
installCmdEvents();
|
|
5003
5456
|
const startupTab = await detectStartupTab();
|