@web-auto/webauto 0.1.7 → 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 +802 -90
- 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 -332
- 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,14 +1745,13 @@ 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");
|
|
1739
1752
|
else if (uninstall) args.push("--uninstall");
|
|
1740
1753
|
if (browser) args.push("--download-browser");
|
|
1741
1754
|
if (geoip) args.push("--download-geoip");
|
|
1742
|
-
if (!uninstall) args.push("--ensure-backend");
|
|
1743
1755
|
const res = await ctx2.api.cmdRunJson({
|
|
1744
1756
|
title: "setup auto repair",
|
|
1745
1757
|
cwd: "",
|
|
@@ -1811,14 +1823,14 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1811
1823
|
applyEnvironment(latest);
|
|
1812
1824
|
updateCompleteStatus();
|
|
1813
1825
|
if (!detail) {
|
|
1814
|
-
if (label.includes("Camoufox") || label.includes("Runtime")) {
|
|
1815
|
-
ok = Boolean(latest.
|
|
1816
|
-
} 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")) {
|
|
1817
1829
|
ok = Boolean(latest.camo?.installed);
|
|
1818
1830
|
} else if (label.includes("\u6838\u5FC3")) {
|
|
1819
1831
|
ok = Boolean(latest.services?.unifiedApi && latest.services?.camoRuntime);
|
|
1820
1832
|
} else {
|
|
1821
|
-
ok =
|
|
1833
|
+
ok = Boolean(latest.allReady);
|
|
1822
1834
|
}
|
|
1823
1835
|
}
|
|
1824
1836
|
}
|
|
@@ -1841,12 +1853,13 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1841
1853
|
applyEnvironment(snapshot);
|
|
1842
1854
|
updateCompleteStatus();
|
|
1843
1855
|
if (!envReady) {
|
|
1856
|
+
const missingFlags = getMissing(snapshot);
|
|
1844
1857
|
const missing = [];
|
|
1845
|
-
if (
|
|
1846
|
-
if (
|
|
1847
|
-
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");
|
|
1848
1861
|
setupStatusText.textContent = `\u5B58\u5728\u5F85\u4FEE\u590D\u9879: ${missing.join(", ")}`;
|
|
1849
|
-
if (
|
|
1862
|
+
if (missingFlags.runtimeService) {
|
|
1850
1863
|
setupStatusText.textContent += "\uFF08camo-runtime \u672A\u5C31\u7EEA\uFF0C\u5F53\u524D\u4E3A\u53EF\u9009\uFF09";
|
|
1851
1864
|
}
|
|
1852
1865
|
} else if (!snapshot?.geoip?.installed) {
|
|
@@ -1918,8 +1931,8 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1918
1931
|
style: "display:flex; justify-content:space-between; align-items:center; padding:8px 12px; border-bottom:1px solid var(--border);"
|
|
1919
1932
|
}, [
|
|
1920
1933
|
createEl("div", {}, [
|
|
1921
|
-
createEl("div", { style: "font-weight:600; margin-bottom:2px;" }, [acc.alias || acc.name || acc.profileId]),
|
|
1922
|
-
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")}`])
|
|
1923
1936
|
]),
|
|
1924
1937
|
createEl("span", {
|
|
1925
1938
|
className: `status-badge ${statusClass}`
|
|
@@ -2026,6 +2039,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
2026
2039
|
"sync",
|
|
2027
2040
|
id,
|
|
2028
2041
|
"--pending-while-login",
|
|
2042
|
+
"--resolve-alias",
|
|
2029
2043
|
"--json"
|
|
2030
2044
|
],
|
|
2031
2045
|
timeoutMs: 2e4
|
|
@@ -2082,12 +2096,12 @@ function renderSetupWizard(root, ctx2) {
|
|
|
2082
2096
|
repairCoreBtn.onclick = () => void runRepair("\u4FEE\u590D\u6838\u5FC3\u670D\u52A1", repairCoreServices);
|
|
2083
2097
|
repairCore2Btn.onclick = () => void runRepair("\u4FEE\u590D\u6838\u5FC3\u670D\u52A1", repairCoreServices);
|
|
2084
2098
|
repairCamoBtn.onclick = () => void runRepair("\u4FEE\u590D Camo CLI", repairCoreServices);
|
|
2085
|
-
repairRuntimeBtn.onclick = () => void runRepair("\u4FEE\u590D
|
|
2099
|
+
repairRuntimeBtn.onclick = () => void runRepair("\u4FEE\u590D\u6D4F\u89C8\u5668\u5185\u6838", () => repairInstall({ browser: true }));
|
|
2086
2100
|
repairGeoipBtn.onclick = () => void runRepair("\u5B89\u88C5 GeoIP", () => repairInstall({ geoip: true }));
|
|
2087
2101
|
addAccountBtn.onclick = addAccount;
|
|
2088
2102
|
enterMainBtn.onclick = () => {
|
|
2089
2103
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2090
|
-
ctx2.setActiveTab("
|
|
2104
|
+
ctx2.setActiveTab("tasks");
|
|
2091
2105
|
}
|
|
2092
2106
|
};
|
|
2093
2107
|
void tickEnvironment();
|
|
@@ -2183,6 +2197,21 @@ function parseTaskRows(payload) {
|
|
|
2183
2197
|
runHistory: parseRunHistory(row?.runHistory)
|
|
2184
2198
|
})).filter((row) => row.id);
|
|
2185
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
|
+
}
|
|
2186
2215
|
function getTasksForPlatform(platform) {
|
|
2187
2216
|
const p = platform;
|
|
2188
2217
|
return PLATFORM_TASKS[p] || [];
|
|
@@ -2209,7 +2238,8 @@ var DEFAULT_FORM = {
|
|
|
2209
2238
|
collectBody: true,
|
|
2210
2239
|
doLikes: false,
|
|
2211
2240
|
likeKeywords: "",
|
|
2212
|
-
|
|
2241
|
+
scheduleMode: "immediate",
|
|
2242
|
+
periodicType: "interval",
|
|
2213
2243
|
intervalMinutes: 30,
|
|
2214
2244
|
runAt: null,
|
|
2215
2245
|
maxRuns: null
|
|
@@ -2218,19 +2248,6 @@ function parseSortableTime(value) {
|
|
|
2218
2248
|
const ts = Date.parse(String(value || ""));
|
|
2219
2249
|
return Number.isFinite(ts) ? ts : 0;
|
|
2220
2250
|
}
|
|
2221
|
-
function isKeywordRequired(taskType) {
|
|
2222
|
-
return taskType === "xhs-unified" || taskType === "weibo-search" || taskType === "1688-search";
|
|
2223
|
-
}
|
|
2224
|
-
function commandTypeToWeiboTaskType(commandType) {
|
|
2225
|
-
if (commandType === "weibo-search") return "search";
|
|
2226
|
-
if (commandType === "weibo-monitor") return "monitor";
|
|
2227
|
-
return "timeline";
|
|
2228
|
-
}
|
|
2229
|
-
function fallbackTaskName(data) {
|
|
2230
|
-
const keyword = String(data.keyword || "").trim();
|
|
2231
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2232
|
-
return keyword ? `${data.taskType}-${keyword}` : `${data.taskType}-${stamp}`;
|
|
2233
|
-
}
|
|
2234
2251
|
function renderTasksPanel(root, ctx2) {
|
|
2235
2252
|
root.innerHTML = "";
|
|
2236
2253
|
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
@@ -2285,8 +2302,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2285
2302
|
<input id="task-target" type="number" min="1" value="50" style="width: 80px;" />
|
|
2286
2303
|
</div>
|
|
2287
2304
|
<div>
|
|
2288
|
-
<label>Profile</label>
|
|
2289
|
-
<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>
|
|
2290
2308
|
</div>
|
|
2291
2309
|
<div>
|
|
2292
2310
|
<label>\u73AF\u5883</label>
|
|
@@ -2324,14 +2342,20 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2324
2342
|
<div style="font-size:12px; color:var(--text-secondary); margin-bottom:var(--gap-sm);">\u8C03\u5EA6\u8BBE\u7F6E\uFF08\u53EF\u9009\uFF09</div>
|
|
2325
2343
|
<div class="row">
|
|
2326
2344
|
<div>
|
|
2327
|
-
<select id="task-schedule-type" style="width:
|
|
2328
|
-
<option value="
|
|
2329
|
-
<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>
|
|
2330
2354
|
<option value="daily">\u6BCF\u5929</option>
|
|
2331
2355
|
<option value="weekly">\u6BCF\u5468</option>
|
|
2332
2356
|
</select>
|
|
2333
2357
|
</div>
|
|
2334
|
-
<div id="task-interval-wrap">
|
|
2358
|
+
<div id="task-interval-wrap" style="display:none;">
|
|
2335
2359
|
<input id="task-interval" type="number" min="1" value="30" style="width: 70px;" />
|
|
2336
2360
|
<span style="font-size:11px;color:var(--text-tertiary);">\u5206\u949F</span>
|
|
2337
2361
|
</div>
|
|
@@ -2348,7 +2372,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2348
2372
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
2349
2373
|
<button id="task-save-btn" style="flex:1;">\u4FDD\u5B58\u4EFB\u52A1</button>
|
|
2350
2374
|
<button id="task-run-btn" class="primary" style="flex:1;">\u4FDD\u5B58\u5E76\u6267\u884C</button>
|
|
2351
|
-
<button id="task-run-ephemeral-btn" class="secondary" style="flex:1;">\
|
|
2375
|
+
<button id="task-run-ephemeral-btn" class="secondary" style="flex:1;">\u7ACB\u5373\u6267\u884C(\u4E0D\u4FDD\u5B58)</button>
|
|
2352
2376
|
<button id="task-reset-btn" class="secondary" style="flex:0.6;">\u91CD\u7F6E</button>
|
|
2353
2377
|
</div>
|
|
2354
2378
|
`;
|
|
@@ -2378,15 +2402,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2378
2402
|
root.appendChild(mainGrid);
|
|
2379
2403
|
const recentCard = createEl("div", { className: "bento-cell", style: "margin-top: var(--gap);" });
|
|
2380
2404
|
recentCard.innerHTML = `
|
|
2381
|
-
<div class="bento-title">\
|
|
2405
|
+
<div class="bento-title">\u5DF2\u4FDD\u5B58\u4EFB\u52A1\u5217\u8868</div>
|
|
2382
2406
|
<div class="row" style="margin-bottom: var(--gap-sm);">
|
|
2383
2407
|
<select id="task-history-select" style="min-width: 320px;">
|
|
2384
2408
|
<option value="">\u9009\u62E9\u5386\u53F2\u4EFB\u52A1...</option>
|
|
2385
2409
|
</select>
|
|
2386
2410
|
<button id="task-history-edit-btn" class="secondary">\u8F7D\u5165\u7F16\u8F91</button>
|
|
2387
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>
|
|
2388
2413
|
<button id="task-history-refresh-btn" class="secondary">\u5237\u65B0</button>
|
|
2389
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>
|
|
2390
2416
|
<div id="recent-tasks-list"></div>
|
|
2391
2417
|
`;
|
|
2392
2418
|
root.appendChild(recentCard);
|
|
@@ -2397,6 +2423,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2397
2423
|
const keywordInput = formCard.querySelector("#task-keyword");
|
|
2398
2424
|
const targetInput = formCard.querySelector("#task-target");
|
|
2399
2425
|
const profileInput = formCard.querySelector("#task-profile");
|
|
2426
|
+
const profileHint = formCard.querySelector("#task-profile-hint");
|
|
2400
2427
|
const envSelect = formCard.querySelector("#task-env");
|
|
2401
2428
|
const userIdWrap = formCard.querySelector("#task-user-id-wrap");
|
|
2402
2429
|
const userIdInput = formCard.querySelector("#task-user-id");
|
|
@@ -2405,6 +2432,8 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2405
2432
|
const likesInput = formCard.querySelector("#task-likes");
|
|
2406
2433
|
const likeKeywordsInput = formCard.querySelector("#task-like-keywords");
|
|
2407
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");
|
|
2408
2437
|
const intervalInput = formCard.querySelector("#task-interval");
|
|
2409
2438
|
const intervalWrap = formCard.querySelector("#task-interval-wrap");
|
|
2410
2439
|
const runAtInput = formCard.querySelector("#task-runat");
|
|
@@ -2420,22 +2449,62 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2420
2449
|
const historySelect = recentCard.querySelector("#task-history-select");
|
|
2421
2450
|
const historyEditBtn = recentCard.querySelector("#task-history-edit-btn");
|
|
2422
2451
|
const historyCloneBtn = recentCard.querySelector("#task-history-clone-btn");
|
|
2452
|
+
const historyRunBtn = recentCard.querySelector("#task-history-run-btn");
|
|
2423
2453
|
const historyRefreshBtn = recentCard.querySelector("#task-history-refresh-btn");
|
|
2424
2454
|
const recentTasksList = recentCard.querySelector("#recent-tasks-list");
|
|
2425
2455
|
const statRunning = statsCard.querySelector("#stat-running");
|
|
2426
2456
|
const statToday = statsCard.querySelector("#stat-today");
|
|
2427
2457
|
const statSaved = statsCard.querySelector("#stat-saved");
|
|
2428
2458
|
let tasks = [];
|
|
2459
|
+
let accountRows = [];
|
|
2429
2460
|
const activeRunIds = /* @__PURE__ */ new Set();
|
|
2430
2461
|
let unsubscribeActiveRuns = null;
|
|
2431
2462
|
const joinPath2 = (...parts) => {
|
|
2432
2463
|
if (typeof ctx2?.api?.pathJoin === "function") return ctx2.api.pathJoin(...parts);
|
|
2433
2464
|
return parts.filter(Boolean).join("/");
|
|
2434
2465
|
};
|
|
2435
|
-
const scheduleScript = joinPath2("apps", "webauto", "entry", "schedule.mjs");
|
|
2436
2466
|
const quotaScript = joinPath2("apps", "webauto", "entry", "lib", "quota-status.mjs");
|
|
2437
|
-
|
|
2438
|
-
|
|
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
|
+
}
|
|
2439
2508
|
function getTaskById(taskId) {
|
|
2440
2509
|
const id = String(taskId || "").trim();
|
|
2441
2510
|
if (!id) return null;
|
|
@@ -2453,13 +2522,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2453
2522
|
formTitle.textContent = "\u65B0\u5EFA\u4EFB\u52A1";
|
|
2454
2523
|
}
|
|
2455
2524
|
function updateTaskTypeOptions(preferredType = "") {
|
|
2456
|
-
const platform = platformSelect.value;
|
|
2525
|
+
const platform = normalizePlatform2(platformSelect.value);
|
|
2457
2526
|
const options = getTasksForPlatform(platform);
|
|
2458
2527
|
taskTypeSelect.innerHTML = options.map((item) => `<option value="${item.type}">${item.icon} ${item.label}</option>`).join("");
|
|
2459
2528
|
const target = String(preferredType || "").trim();
|
|
2460
2529
|
const matched = options.find((item) => item.type === target);
|
|
2461
2530
|
taskTypeSelect.value = matched?.type || options[0]?.type || "";
|
|
2462
2531
|
updatePlatformFields();
|
|
2532
|
+
void refreshPlatformAccountRows(platform).then(() => {
|
|
2533
|
+
updateProfileHint(platform);
|
|
2534
|
+
maybeAutofillProfile(platform);
|
|
2535
|
+
});
|
|
2463
2536
|
}
|
|
2464
2537
|
function updatePlatformFields() {
|
|
2465
2538
|
const taskType = String(taskTypeSelect.value || "").trim();
|
|
@@ -2467,9 +2540,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2467
2540
|
userIdWrap.style.display = isWeiboMonitor ? "" : "none";
|
|
2468
2541
|
}
|
|
2469
2542
|
function updateScheduleVisibility() {
|
|
2470
|
-
const
|
|
2471
|
-
|
|
2472
|
-
|
|
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
|
+
}
|
|
2473
2554
|
}
|
|
2474
2555
|
function updateLikeKeywordsState() {
|
|
2475
2556
|
likeKeywordsInput.disabled = !likesInput.checked;
|
|
@@ -2477,6 +2558,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2477
2558
|
function collectFormData() {
|
|
2478
2559
|
const maxRunsRaw = String(maxRunsInput.value || "").trim();
|
|
2479
2560
|
const maxRunsNum = maxRunsRaw ? Number(maxRunsRaw) : 0;
|
|
2561
|
+
const scheduleMode = scheduleTypeSelect.value;
|
|
2562
|
+
const periodicType = periodicTypeSelect.value;
|
|
2563
|
+
const runAtText = String(runAtInput.value || "").trim();
|
|
2480
2564
|
return {
|
|
2481
2565
|
id: String(editingIdInput.value || "").trim() || void 0,
|
|
2482
2566
|
name: String(nameInput.value || "").trim(),
|
|
@@ -2492,9 +2576,10 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2492
2576
|
collectBody: bodyInput.checked,
|
|
2493
2577
|
doLikes: likesInput.checked,
|
|
2494
2578
|
likeKeywords: String(likeKeywordsInput.value || "").trim(),
|
|
2495
|
-
|
|
2579
|
+
scheduleMode,
|
|
2580
|
+
periodicType,
|
|
2496
2581
|
intervalMinutes: Math.max(1, Number(intervalInput.value || 30) || 30),
|
|
2497
|
-
runAt: toIsoOrNull(
|
|
2582
|
+
runAt: toIsoOrNull(runAtText),
|
|
2498
2583
|
maxRuns: Number.isFinite(maxRunsNum) && maxRunsNum > 0 ? Math.max(1, Math.floor(maxRunsNum)) : null
|
|
2499
2584
|
};
|
|
2500
2585
|
}
|
|
@@ -2514,7 +2599,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2514
2599
|
bodyInput.checked = task.commandArgv?.["fetch-body"] !== false;
|
|
2515
2600
|
likesInput.checked = task.commandArgv?.["do-likes"] === true;
|
|
2516
2601
|
likeKeywordsInput.value = String(task.commandArgv?.["like-keywords"] || "").trim();
|
|
2517
|
-
|
|
2602
|
+
const uiSchedule = inferUiScheduleEditorState(task);
|
|
2603
|
+
scheduleTypeSelect.value = uiSchedule.mode;
|
|
2604
|
+
periodicTypeSelect.value = uiSchedule.periodicType;
|
|
2518
2605
|
intervalInput.value = String(task.intervalMinutes || 30);
|
|
2519
2606
|
runAtInput.value = toLocalDatetimeValue(task.runAt);
|
|
2520
2607
|
maxRunsInput.value = task.maxRuns ? String(task.maxRuns) : "";
|
|
@@ -2537,7 +2624,8 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2537
2624
|
bodyInput.checked = DEFAULT_FORM.collectBody;
|
|
2538
2625
|
likesInput.checked = DEFAULT_FORM.doLikes;
|
|
2539
2626
|
likeKeywordsInput.value = DEFAULT_FORM.likeKeywords;
|
|
2540
|
-
scheduleTypeSelect.value = DEFAULT_FORM.
|
|
2627
|
+
scheduleTypeSelect.value = DEFAULT_FORM.scheduleMode;
|
|
2628
|
+
periodicTypeSelect.value = DEFAULT_FORM.periodicType;
|
|
2541
2629
|
intervalInput.value = String(DEFAULT_FORM.intervalMinutes);
|
|
2542
2630
|
runAtInput.value = "";
|
|
2543
2631
|
maxRunsInput.value = "";
|
|
@@ -2571,19 +2659,29 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2571
2659
|
}
|
|
2572
2660
|
}
|
|
2573
2661
|
function renderRecentTasks() {
|
|
2574
|
-
const rows = sortedTasksByRecent()
|
|
2662
|
+
const rows = sortedTasksByRecent();
|
|
2575
2663
|
if (rows.length === 0) {
|
|
2576
2664
|
recentTasksList.innerHTML = '<div class="muted" style="font-size:12px;">\u6682\u65E0\u4EFB\u52A1</div>';
|
|
2577
2665
|
return;
|
|
2578
2666
|
}
|
|
2579
2667
|
recentTasksList.innerHTML = rows.map((task) => `
|
|
2580
|
-
<div class="task-row" style="display:flex;gap:var(--gap-sm);padding:var(--gap-xs)0;border-bottom:1px solid var(--border-subtle);align-items:center;">
|
|
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;">
|
|
2581
2669
|
<span style="flex:1;font-size:12px;">${task.name || task.id}</span>
|
|
2582
2670
|
<span style="font-size:11px;color:var(--text-tertiary);">${task.commandType}</span>
|
|
2583
2671
|
<span style="font-size:11px;color:${task.enabled ? "var(--accent-success)" : "var(--text-muted)"};">${task.enabled ? "\u542F\u7528" : "\u7981\u7528"}</span>
|
|
2584
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>
|
|
2585
2674
|
</div>
|
|
2586
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
|
+
});
|
|
2587
2685
|
recentTasksList.querySelectorAll(".edit-task-btn").forEach((btn) => {
|
|
2588
2686
|
btn.addEventListener("click", () => {
|
|
2589
2687
|
const taskId = btn.dataset.id || "";
|
|
@@ -2593,6 +2691,14 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2593
2691
|
applyTaskToForm(task, "edit");
|
|
2594
2692
|
});
|
|
2595
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
|
+
});
|
|
2596
2702
|
}
|
|
2597
2703
|
function updateStats() {
|
|
2598
2704
|
statSaved.textContent = String(tasks.length);
|
|
@@ -2600,21 +2706,27 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2600
2706
|
const totalRunCount = tasks.reduce((sum, row) => sum + (Number(row.runCount) || 0), 0);
|
|
2601
2707
|
statToday.textContent = String(totalRunCount);
|
|
2602
2708
|
}
|
|
2603
|
-
async function
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
timeoutMs
|
|
2609
|
-
});
|
|
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);
|
|
2610
2714
|
if (!ret?.ok) {
|
|
2611
|
-
const reason = String(ret?.error ||
|
|
2612
|
-
throw new Error(reason || "command failed");
|
|
2715
|
+
const reason = String(ret?.error || "schedule command failed").trim();
|
|
2716
|
+
throw new Error(reason || "schedule command failed");
|
|
2613
2717
|
}
|
|
2614
|
-
return ret
|
|
2718
|
+
return ret?.json ?? ret;
|
|
2615
2719
|
}
|
|
2616
|
-
async function
|
|
2617
|
-
|
|
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;
|
|
2618
2730
|
}
|
|
2619
2731
|
async function loadQuotaStatus() {
|
|
2620
2732
|
try {
|
|
@@ -2643,7 +2755,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2643
2755
|
}
|
|
2644
2756
|
async function loadTasks() {
|
|
2645
2757
|
try {
|
|
2646
|
-
const out = await
|
|
2758
|
+
const out = await invokeSchedule({ action: "list" });
|
|
2647
2759
|
tasks = parseTaskRows(out);
|
|
2648
2760
|
renderHistorySelect();
|
|
2649
2761
|
renderRecentTasks();
|
|
@@ -2654,7 +2766,6 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2654
2766
|
}
|
|
2655
2767
|
function buildCommandArgv(data) {
|
|
2656
2768
|
const argv = {
|
|
2657
|
-
profile: data.profileId,
|
|
2658
2769
|
keyword: data.keyword,
|
|
2659
2770
|
"max-notes": data.targetCount,
|
|
2660
2771
|
target: data.targetCount,
|
|
@@ -2664,36 +2775,70 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2664
2775
|
"do-likes": data.doLikes,
|
|
2665
2776
|
"like-keywords": data.likeKeywords
|
|
2666
2777
|
};
|
|
2778
|
+
const profileId = String(data.profileId || "").trim();
|
|
2779
|
+
if (profileId) argv.profile = profileId;
|
|
2667
2780
|
if (String(data.taskType || "").startsWith("weibo-")) {
|
|
2668
|
-
argv["task-type"] = commandTypeToWeiboTaskType(data.taskType);
|
|
2669
2781
|
if (data.userId) argv["user-id"] = data.userId;
|
|
2670
2782
|
}
|
|
2671
2783
|
return argv;
|
|
2672
2784
|
}
|
|
2673
|
-
function
|
|
2674
|
-
if (
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
const args = data.id ? ["update", data.id] : ["add"];
|
|
2682
|
-
args.push("--name", data.name || fallbackTaskName(data));
|
|
2683
|
-
args.push("--enabled", String(data.enabled));
|
|
2684
|
-
args.push("--command-type", data.taskType || "xhs-unified");
|
|
2685
|
-
args.push("--schedule-type", data.scheduleType);
|
|
2686
|
-
if (data.scheduleType === "interval") {
|
|
2687
|
-
args.push("--interval-minutes", String(data.intervalMinutes));
|
|
2688
|
-
} else {
|
|
2689
|
-
args.push("--run-at", String(data.runAt || ""));
|
|
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
|
+
};
|
|
2690
2793
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
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
|
+
};
|
|
2694
2839
|
}
|
|
2695
2840
|
async function runSavedTask(taskId, data) {
|
|
2696
|
-
const out = await
|
|
2841
|
+
const out = await invokeSchedule({ action: "run", taskId, timeoutMs: 0 });
|
|
2697
2842
|
const runId = String(
|
|
2698
2843
|
out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
|
|
2699
2844
|
).trim();
|
|
@@ -2709,23 +2854,30 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2709
2854
|
target: data.targetCount,
|
|
2710
2855
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2711
2856
|
};
|
|
2857
|
+
ctx2.activeRunId = runId || null;
|
|
2712
2858
|
}
|
|
2713
2859
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2714
2860
|
ctx2.setActiveTab(data.taskType === "xhs-unified" ? "dashboard" : "scheduler");
|
|
2715
2861
|
}
|
|
2716
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
|
+
}
|
|
2717
2870
|
async function saveTask(runImmediately = false) {
|
|
2718
2871
|
const data = collectFormData();
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
alert(invalidReason);
|
|
2872
|
+
if (runImmediately && data.scheduleMode === "immediate") {
|
|
2873
|
+
await runWithoutSave();
|
|
2722
2874
|
return;
|
|
2723
2875
|
}
|
|
2724
2876
|
saveBtn.disabled = true;
|
|
2725
2877
|
runBtn.disabled = true;
|
|
2726
2878
|
runEphemeralBtn.disabled = true;
|
|
2727
2879
|
try {
|
|
2728
|
-
const out = await
|
|
2880
|
+
const out = await invokeSchedule({ action: "save", payload: toSchedulePayload(data) });
|
|
2729
2881
|
const taskId = String(out?.task?.id || data.id || "").trim();
|
|
2730
2882
|
if (!taskId) {
|
|
2731
2883
|
throw new Error("task id missing after save");
|
|
@@ -2735,7 +2887,13 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2735
2887
|
await loadTasks();
|
|
2736
2888
|
historySelect.value = taskId;
|
|
2737
2889
|
if (runImmediately) {
|
|
2738
|
-
|
|
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
|
+
});
|
|
2739
2897
|
} else {
|
|
2740
2898
|
alert("\u4EFB\u52A1\u5DF2\u4FDD\u5B58");
|
|
2741
2899
|
}
|
|
@@ -2747,83 +2905,13 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2747
2905
|
runEphemeralBtn.disabled = false;
|
|
2748
2906
|
}
|
|
2749
2907
|
}
|
|
2750
|
-
function buildEphemeralRunSpec(data) {
|
|
2751
|
-
if (!data.profileId) return null;
|
|
2752
|
-
if (data.taskType === "xhs-unified") {
|
|
2753
|
-
if (!data.keyword) return null;
|
|
2754
|
-
return {
|
|
2755
|
-
title: `xhs: ${data.keyword}`,
|
|
2756
|
-
groupKey: "xhs-unified",
|
|
2757
|
-
args: [
|
|
2758
|
-
xhsScript,
|
|
2759
|
-
"--profile",
|
|
2760
|
-
data.profileId,
|
|
2761
|
-
"--keyword",
|
|
2762
|
-
data.keyword,
|
|
2763
|
-
"--target",
|
|
2764
|
-
String(data.targetCount),
|
|
2765
|
-
"--max-notes",
|
|
2766
|
-
String(data.targetCount),
|
|
2767
|
-
"--env",
|
|
2768
|
-
data.env,
|
|
2769
|
-
"--do-comments",
|
|
2770
|
-
String(data.collectComments),
|
|
2771
|
-
"--fetch-body",
|
|
2772
|
-
String(data.collectBody),
|
|
2773
|
-
"--do-likes",
|
|
2774
|
-
String(data.doLikes),
|
|
2775
|
-
"--like-keywords",
|
|
2776
|
-
data.likeKeywords
|
|
2777
|
-
]
|
|
2778
|
-
};
|
|
2779
|
-
}
|
|
2780
|
-
if (data.taskType === "weibo-search") {
|
|
2781
|
-
if (!data.keyword) return null;
|
|
2782
|
-
return {
|
|
2783
|
-
title: `weibo: ${data.keyword}`,
|
|
2784
|
-
groupKey: "weibo-search",
|
|
2785
|
-
args: [
|
|
2786
|
-
weiboScript,
|
|
2787
|
-
"search",
|
|
2788
|
-
"--profile",
|
|
2789
|
-
data.profileId,
|
|
2790
|
-
"--keyword",
|
|
2791
|
-
data.keyword,
|
|
2792
|
-
"--target",
|
|
2793
|
-
String(data.targetCount),
|
|
2794
|
-
"--env",
|
|
2795
|
-
data.env
|
|
2796
|
-
]
|
|
2797
|
-
};
|
|
2798
|
-
}
|
|
2799
|
-
return null;
|
|
2800
|
-
}
|
|
2801
2908
|
async function runWithoutSave() {
|
|
2802
2909
|
const data = collectFormData();
|
|
2803
|
-
if (!data.profileId) {
|
|
2804
|
-
alert("\u8BF7\u8F93\u5165 Profile ID");
|
|
2805
|
-
return;
|
|
2806
|
-
}
|
|
2807
|
-
if (isKeywordRequired(data.taskType) && !data.keyword) {
|
|
2808
|
-
alert("\u8BF7\u8F93\u5165\u5173\u952E\u8BCD");
|
|
2809
|
-
return;
|
|
2810
|
-
}
|
|
2811
|
-
if (data.taskType === "weibo-monitor" && !data.userId) {
|
|
2812
|
-
alert("\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id");
|
|
2813
|
-
return;
|
|
2814
|
-
}
|
|
2815
|
-
const spec = buildEphemeralRunSpec(data);
|
|
2816
|
-
if (!spec) {
|
|
2817
|
-
alert(`\u5F53\u524D\u4EFB\u52A1\u7C7B\u578B\u6682\u4E0D\u652F\u6301\u4EC5\u6267\u884C(\u4E0D\u4FDD\u5B58): ${data.taskType}`);
|
|
2818
|
-
return;
|
|
2819
|
-
}
|
|
2820
2910
|
runEphemeralBtn.disabled = true;
|
|
2821
2911
|
try {
|
|
2822
|
-
const ret = await
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
args: spec.args,
|
|
2826
|
-
groupKey: spec.groupKey
|
|
2912
|
+
const ret = await invokeTaskRunEphemeral({
|
|
2913
|
+
commandType: data.taskType,
|
|
2914
|
+
argv: buildCommandArgv(data)
|
|
2827
2915
|
});
|
|
2828
2916
|
const runId = String(ret?.runId || "").trim();
|
|
2829
2917
|
if (runId) {
|
|
@@ -2831,17 +2919,19 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2831
2919
|
updateStats();
|
|
2832
2920
|
}
|
|
2833
2921
|
if (typeof ctx2.setStatus === "function") {
|
|
2834
|
-
ctx2.setStatus(`started: ${
|
|
2922
|
+
ctx2.setStatus(`started: ${data.taskType}`);
|
|
2835
2923
|
}
|
|
2836
2924
|
if (data.taskType === "xhs-unified" && ctx2 && typeof ctx2 === "object") {
|
|
2925
|
+
const resolvedProfile = String(ret?.profile || data.profileId || "").trim();
|
|
2837
2926
|
ctx2.xhsCurrentRun = {
|
|
2838
2927
|
runId: runId || null,
|
|
2839
2928
|
taskId: null,
|
|
2840
|
-
profileId:
|
|
2929
|
+
profileId: resolvedProfile,
|
|
2841
2930
|
keyword: data.keyword,
|
|
2842
2931
|
target: data.targetCount,
|
|
2843
2932
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2844
2933
|
};
|
|
2934
|
+
ctx2.activeRunId = runId || null;
|
|
2845
2935
|
}
|
|
2846
2936
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2847
2937
|
ctx2.setActiveTab(data.taskType === "xhs-unified" ? "dashboard" : "scheduler");
|
|
@@ -2874,6 +2964,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2874
2964
|
});
|
|
2875
2965
|
taskTypeSelect.addEventListener("change", () => updatePlatformFields());
|
|
2876
2966
|
scheduleTypeSelect.addEventListener("change", () => updateScheduleVisibility());
|
|
2967
|
+
periodicTypeSelect.addEventListener("change", () => updateScheduleVisibility());
|
|
2877
2968
|
likesInput.addEventListener("change", () => updateLikeKeywordsState());
|
|
2878
2969
|
saveBtn.addEventListener("click", () => {
|
|
2879
2970
|
void saveTask(false);
|
|
@@ -2907,6 +2998,14 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2907
2998
|
}
|
|
2908
2999
|
applyTaskToForm(task, "clone");
|
|
2909
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
|
+
});
|
|
2910
3009
|
gotoSchedulerBtn.addEventListener("click", () => {
|
|
2911
3010
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2912
3011
|
ctx2.setActiveTab("scheduler");
|
|
@@ -2994,6 +3093,11 @@ function renderDashboard(root, ctx2) {
|
|
|
2994
3093
|
<div id="recent-errors-empty" class="muted" style="font-size: 12px;">\u6682\u65E0\u9519\u8BEF</div>
|
|
2995
3094
|
<ul id="recent-errors-list" style="margin: 6px 0 0 16px; padding: 0; font-size: 12px; line-height: 1.5; display:none;"></ul>
|
|
2996
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>
|
|
2997
3101
|
`;
|
|
2998
3102
|
runSummaryGrid.appendChild(runSummaryCard);
|
|
2999
3103
|
root.appendChild(runSummaryGrid);
|
|
@@ -3088,6 +3192,8 @@ function renderDashboard(root, ctx2) {
|
|
|
3088
3192
|
const errorCountText = root.querySelector("#error-count-text");
|
|
3089
3193
|
const recentErrorsEmpty = root.querySelector("#recent-errors-empty");
|
|
3090
3194
|
const recentErrorsList = root.querySelector("#recent-errors-list");
|
|
3195
|
+
const likedLinksEmpty = root.querySelector("#liked-links-empty");
|
|
3196
|
+
const likedLinksList = root.querySelector("#liked-links-list");
|
|
3091
3197
|
const logsContainer = root.querySelector("#logs-container");
|
|
3092
3198
|
const toggleLogsBtn = root.querySelector("#toggle-logs-btn");
|
|
3093
3199
|
const pauseBtn = root.querySelector("#pause-btn");
|
|
@@ -3104,23 +3210,175 @@ function renderDashboard(root, ctx2) {
|
|
|
3104
3210
|
let startTime = Date.now();
|
|
3105
3211
|
let stoppedAt = null;
|
|
3106
3212
|
let elapsedTimer = null;
|
|
3213
|
+
let statePollTimer = null;
|
|
3107
3214
|
let unsubscribeState = null;
|
|
3108
3215
|
let unsubscribeCmd = null;
|
|
3109
|
-
|
|
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();
|
|
3110
3220
|
let activeStatus = "";
|
|
3111
3221
|
let errorCountTotal = 0;
|
|
3112
3222
|
const recentErrors = [];
|
|
3223
|
+
const likedLinks = /* @__PURE__ */ new Map();
|
|
3113
3224
|
const maxLogs = 500;
|
|
3114
3225
|
const maxRecentErrors = 8;
|
|
3115
|
-
const
|
|
3226
|
+
const maxLikedLinks = 30;
|
|
3227
|
+
const initialTaskId = String(contextRun?.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3116
3228
|
if (initialTaskId) {
|
|
3117
3229
|
taskConfigId.textContent = initialTaskId;
|
|
3118
3230
|
}
|
|
3119
3231
|
const normalizeStatus = (value) => String(value || "").trim().toLowerCase();
|
|
3120
3232
|
const isRunningStatus = (value) => ["running", "queued", "pending", "starting"].includes(normalizeStatus(value));
|
|
3121
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
|
+
}
|
|
3122
3377
|
function renderRunSummary() {
|
|
3123
3378
|
runIdText.textContent = activeRunId || "-";
|
|
3379
|
+
if (ctx2 && typeof ctx2 === "object") {
|
|
3380
|
+
ctx2.activeRunId = activeRunId || null;
|
|
3381
|
+
}
|
|
3124
3382
|
errorCountText.textContent = String(errorCountTotal);
|
|
3125
3383
|
recentErrorsList.innerHTML = "";
|
|
3126
3384
|
if (recentErrors.length === 0) {
|
|
@@ -3132,18 +3390,35 @@ function renderDashboard(root, ctx2) {
|
|
|
3132
3390
|
recentErrorsList.style.display = "block";
|
|
3133
3391
|
recentErrors.forEach((item) => {
|
|
3134
3392
|
const li = document.createElement("li");
|
|
3135
|
-
|
|
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
|
+
}
|
|
3136
3409
|
recentErrorsList.appendChild(li);
|
|
3137
3410
|
});
|
|
3411
|
+
renderLikedLinks();
|
|
3138
3412
|
}
|
|
3139
|
-
function pushRecentError(message, source = "runtime") {
|
|
3413
|
+
function pushRecentError(message, source = "runtime", details = null) {
|
|
3140
3414
|
const msg = String(message || "").trim();
|
|
3141
3415
|
if (!msg) return;
|
|
3142
3416
|
errorCountTotal += 1;
|
|
3143
3417
|
recentErrors.push({
|
|
3144
3418
|
ts: (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }),
|
|
3145
3419
|
source: String(source || "runtime").trim() || "runtime",
|
|
3146
|
-
message: msg
|
|
3420
|
+
message: msg,
|
|
3421
|
+
details: normalizeDetails(details)
|
|
3147
3422
|
});
|
|
3148
3423
|
while (recentErrors.length > maxRecentErrors) recentErrors.shift();
|
|
3149
3424
|
renderRunSummary();
|
|
@@ -3165,6 +3440,19 @@ function renderDashboard(root, ctx2) {
|
|
|
3165
3440
|
clearInterval(elapsedTimer);
|
|
3166
3441
|
elapsedTimer = null;
|
|
3167
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
|
+
}
|
|
3168
3456
|
function addLog(line, type = "info") {
|
|
3169
3457
|
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
|
|
3170
3458
|
const logLine = createEl("div", { className: "log-line" });
|
|
@@ -3177,8 +3465,39 @@ function renderDashboard(root, ctx2) {
|
|
|
3177
3465
|
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
3178
3466
|
}
|
|
3179
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
|
+
}
|
|
3180
3494
|
function updateFromTaskState(state) {
|
|
3181
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
|
+
}
|
|
3182
3501
|
const progressObj = state.progress && typeof state.progress === "object" ? state.progress : null;
|
|
3183
3502
|
const processedRaw = progressObj?.processed ?? progressObj?.current ?? state.progress ?? state.collected ?? state.current ?? 0;
|
|
3184
3503
|
const totalRaw = progressObj?.total ?? state.total ?? state.target ?? state.maxNotes ?? 0;
|
|
@@ -3231,6 +3550,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3231
3550
|
if (state.profileId) {
|
|
3232
3551
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3233
3552
|
taskAccount.textContent = aliases[state.profileId] || state.profileId;
|
|
3553
|
+
activeProfileId = String(state.profileId || "").trim();
|
|
3234
3554
|
}
|
|
3235
3555
|
const taskId = String(state.taskId || state.scheduleTaskId || state.configTaskId || "").trim();
|
|
3236
3556
|
if (taskId) {
|
|
@@ -3272,26 +3592,32 @@ function renderDashboard(root, ctx2) {
|
|
|
3272
3592
|
}
|
|
3273
3593
|
}
|
|
3274
3594
|
if (state.error) {
|
|
3275
|
-
pushRecentError(String(state.error), "state");
|
|
3595
|
+
pushRecentError(String(state.error), "state", state);
|
|
3276
3596
|
}
|
|
3277
3597
|
}
|
|
3278
3598
|
function pickTaskFromList(tasks) {
|
|
3279
3599
|
const target = activeRunId;
|
|
3280
|
-
const running = tasks.find((item) => isRunningStatus(item?.status));
|
|
3281
3600
|
const sorted = [...tasks].sort((a, b) => {
|
|
3282
3601
|
const aTs = Number(a?.updatedAt ?? a?.completedAt ?? a?.startedAt ?? 0) || 0;
|
|
3283
3602
|
const bTs = Number(b?.updatedAt ?? b?.completedAt ?? b?.startedAt ?? 0) || 0;
|
|
3284
3603
|
return bTs - aTs;
|
|
3285
3604
|
});
|
|
3605
|
+
const running = sorted.find((item) => isRunningStatus(item?.status)) || null;
|
|
3286
3606
|
const latest = sorted[0] || null;
|
|
3607
|
+
const launchingFresh = Number.isFinite(contextStartedAtMs) && contextStartedAtMs > 0 && Date.now() - contextStartedAtMs < 12e4;
|
|
3287
3608
|
if (target) {
|
|
3288
3609
|
const matched = tasks.find((item) => String(item?.runId || "").trim() === target);
|
|
3289
3610
|
if (matched) {
|
|
3290
|
-
if (isRunningStatus(matched?.status)) return matched;
|
|
3291
|
-
if (running) return running;
|
|
3292
|
-
if (latest && String(latest?.runId || "").trim() !== target) return latest;
|
|
3293
3611
|
return matched;
|
|
3294
3612
|
}
|
|
3613
|
+
if (launchingFresh) {
|
|
3614
|
+
return null;
|
|
3615
|
+
}
|
|
3616
|
+
if (running) return running;
|
|
3617
|
+
return null;
|
|
3618
|
+
}
|
|
3619
|
+
if (launchingFresh) {
|
|
3620
|
+
return running || null;
|
|
3295
3621
|
}
|
|
3296
3622
|
return running || latest || null;
|
|
3297
3623
|
}
|
|
@@ -3302,29 +3628,14 @@ function renderDashboard(root, ctx2) {
|
|
|
3302
3628
|
currentPhase.textContent = "\u8FD0\u884C\u4E2D";
|
|
3303
3629
|
currentAction.textContent = "\u542F\u52A8 autoscript";
|
|
3304
3630
|
activeStatus = "running";
|
|
3305
|
-
statCollected.textContent = "0";
|
|
3306
|
-
statSuccess.textContent = "0";
|
|
3307
|
-
statFailed.textContent = "0";
|
|
3308
|
-
statRemaining.textContent = "0";
|
|
3309
|
-
progressPercent.textContent = "0%";
|
|
3310
|
-
progressBar.style.width = "0%";
|
|
3311
|
-
commentsCount = 0;
|
|
3312
|
-
likesCount = 0;
|
|
3313
|
-
likesSkippedCount = 0;
|
|
3314
|
-
likesAlreadyCount = 0;
|
|
3315
|
-
likesDedupCount = 0;
|
|
3316
|
-
statComments.textContent = `0\u6761`;
|
|
3317
|
-
statLikes.textContent = `0\u6B21 (\u8DF3\u8FC7:0, \u5DF2\u8D5E:0, \u53BB\u91CD:0)`;
|
|
3318
3631
|
const ts = Date.parse(String(payload.ts || "")) || Date.now();
|
|
3319
|
-
startTime = ts;
|
|
3320
|
-
stoppedAt = null;
|
|
3321
|
-
updateElapsed();
|
|
3322
|
-
startElapsedTimer();
|
|
3323
3632
|
if (payload.runId) {
|
|
3324
3633
|
activeRunId = String(payload.runId || "").trim() || activeRunId;
|
|
3325
3634
|
}
|
|
3635
|
+
resetDashboardForNewRun("\u65B0\u4EFB\u52A1\u542F\u52A8", ts);
|
|
3326
3636
|
if (payload.keyword) taskKeyword.textContent = String(payload.keyword);
|
|
3327
3637
|
if (payload.maxNotes) taskTarget.textContent = String(payload.maxNotes);
|
|
3638
|
+
if (payload.profileId) activeProfileId = String(payload.profileId || "").trim();
|
|
3328
3639
|
if (payload.taskId) {
|
|
3329
3640
|
const taskId = String(payload.taskId || "").trim();
|
|
3330
3641
|
if (taskId) {
|
|
@@ -3373,6 +3684,23 @@ function renderDashboard(root, ctx2) {
|
|
|
3373
3684
|
likesAlreadyCount = Math.max(0, likesAlreadyCount + already);
|
|
3374
3685
|
likesDedupCount = Math.max(0, likesDedupCount + dedup);
|
|
3375
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();
|
|
3376
3704
|
}
|
|
3377
3705
|
return;
|
|
3378
3706
|
}
|
|
@@ -3381,7 +3709,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3381
3709
|
statFailed.textContent = String(failed + 1);
|
|
3382
3710
|
const opId = String(payload?.operationId || "").trim();
|
|
3383
3711
|
const err = String(payload?.error || payload?.message || payload?.code || event).trim();
|
|
3384
|
-
pushRecentError(opId ? `${opId}: ${err}` : err, event);
|
|
3712
|
+
pushRecentError(opId ? `${opId}: ${err}` : err, event, payload);
|
|
3385
3713
|
return;
|
|
3386
3714
|
}
|
|
3387
3715
|
if (event === "xhs.unified.merged") {
|
|
@@ -3403,7 +3731,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3403
3731
|
currentPhase.textContent = reason && reason !== "script_failure" ? "\u5DF2\u7ED3\u675F" : "\u5931\u8D25";
|
|
3404
3732
|
currentAction.textContent = reason || "stop";
|
|
3405
3733
|
if (reason && !successReasons.has(reason)) {
|
|
3406
|
-
pushRecentError(`stop reason=${reason}`, event);
|
|
3734
|
+
pushRecentError(`stop reason=${reason}`, event, payload);
|
|
3407
3735
|
}
|
|
3408
3736
|
renderRunSummary();
|
|
3409
3737
|
}
|
|
@@ -3461,6 +3789,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3461
3789
|
if (profile?.profileId) {
|
|
3462
3790
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3463
3791
|
taskAccount.textContent = aliases[profile.profileId] || profile.profileId;
|
|
3792
|
+
activeProfileId = String(profile.profileId || "").trim();
|
|
3464
3793
|
}
|
|
3465
3794
|
const runId = String(profile?.runId || summary?.runId || "").trim();
|
|
3466
3795
|
if (runId) {
|
|
@@ -3536,7 +3865,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3536
3865
|
if (payload.action) addLog(String(payload.action), "info");
|
|
3537
3866
|
if (payload.error) {
|
|
3538
3867
|
addLog(String(payload.error), "error");
|
|
3539
|
-
pushRecentError(String(payload.error), "state");
|
|
3868
|
+
pushRecentError(String(payload.error), "state", payload);
|
|
3540
3869
|
}
|
|
3541
3870
|
}
|
|
3542
3871
|
});
|
|
@@ -3553,10 +3882,12 @@ function renderDashboard(root, ctx2) {
|
|
|
3553
3882
|
unsubscribeCmd = ctx2.api.onCmdEvent((evt) => {
|
|
3554
3883
|
if (paused) return;
|
|
3555
3884
|
const runId = String(evt?.runId || "").trim();
|
|
3556
|
-
|
|
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) {
|
|
3557
3888
|
activeRunId = runId;
|
|
3558
3889
|
activeStatus = "running";
|
|
3559
|
-
|
|
3890
|
+
resetDashboardForNewRun("\u8FDB\u7A0B\u542F\u52A8");
|
|
3560
3891
|
renderRunSummary();
|
|
3561
3892
|
}
|
|
3562
3893
|
if (activeRunId && runId && runId !== activeRunId) return;
|
|
@@ -3565,7 +3896,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3565
3896
|
parseLineEvent(String(evt.line || "").trim());
|
|
3566
3897
|
} else if (evt.type === "stderr") {
|
|
3567
3898
|
addLog(evt.line, "error");
|
|
3568
|
-
pushRecentError(String(evt.line || ""), "stderr");
|
|
3899
|
+
pushRecentError(String(evt.line || ""), "stderr", evt);
|
|
3569
3900
|
const failed = Number(statFailed.textContent || "0") || 0;
|
|
3570
3901
|
statFailed.textContent = String(failed + 1);
|
|
3571
3902
|
} else if (evt.type === "exit") {
|
|
@@ -3580,7 +3911,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3580
3911
|
stopElapsedTimer();
|
|
3581
3912
|
}
|
|
3582
3913
|
if (Number(evt.exitCode || 0) !== 0) {
|
|
3583
|
-
pushRecentError(`\u8FDB\u7A0B\u9000\u51FA code=${evt.exitCode ?? "null"}`, "exit");
|
|
3914
|
+
pushRecentError(`\u8FDB\u7A0B\u9000\u51FA code=${evt.exitCode ?? "null"}`, "exit", evt);
|
|
3584
3915
|
}
|
|
3585
3916
|
renderRunSummary();
|
|
3586
3917
|
}
|
|
@@ -3591,13 +3922,17 @@ function renderDashboard(root, ctx2) {
|
|
|
3591
3922
|
try {
|
|
3592
3923
|
const config = await ctx2.api.configLoadLast();
|
|
3593
3924
|
if (config) {
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
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) {
|
|
3597
3932
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3598
3933
|
taskAccount.textContent = aliases[config.lastProfileId] || config.lastProfileId;
|
|
3599
3934
|
}
|
|
3600
|
-
const taskId = String(config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3935
|
+
const taskId = String(contextRun?.taskId || config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3601
3936
|
if (taskId) {
|
|
3602
3937
|
taskConfigId.textContent = taskId;
|
|
3603
3938
|
}
|
|
@@ -3661,23 +3996,25 @@ function renderDashboard(root, ctx2) {
|
|
|
3661
3996
|
}
|
|
3662
3997
|
setTimeout(() => {
|
|
3663
3998
|
if (typeof ctx2.setActiveTab === "function") {
|
|
3664
|
-
ctx2.setActiveTab("
|
|
3999
|
+
ctx2.setActiveTab("tasks");
|
|
3665
4000
|
}
|
|
3666
4001
|
}, 1500);
|
|
3667
4002
|
}
|
|
3668
4003
|
};
|
|
3669
4004
|
backConfigBtn.onclick = () => {
|
|
3670
4005
|
if (typeof ctx2.setActiveTab === "function") {
|
|
3671
|
-
ctx2.setActiveTab("
|
|
4006
|
+
ctx2.setActiveTab("tasks");
|
|
3672
4007
|
}
|
|
3673
4008
|
};
|
|
3674
4009
|
renderRunSummary();
|
|
3675
4010
|
loadTaskInfo();
|
|
3676
4011
|
subscribeToUpdates();
|
|
3677
4012
|
fetchCurrentState();
|
|
4013
|
+
startStatePoll();
|
|
3678
4014
|
startElapsedTimer();
|
|
3679
4015
|
return () => {
|
|
3680
4016
|
stopElapsedTimer();
|
|
4017
|
+
stopStatePoll();
|
|
3681
4018
|
if (unsubscribeState) unsubscribeState();
|
|
3682
4019
|
if (unsubscribeCmd) unsubscribeCmd();
|
|
3683
4020
|
if (unsubscribeBus) unsubscribeBus();
|
|
@@ -3725,6 +4062,14 @@ function toTimestamp(value) {
|
|
|
3725
4062
|
if (!Number.isFinite(parsed)) return null;
|
|
3726
4063
|
return parsed;
|
|
3727
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
|
+
}
|
|
3728
4073
|
function renderAccountManager(root, ctx2) {
|
|
3729
4074
|
root.innerHTML = "";
|
|
3730
4075
|
const autoSyncTimers = /* @__PURE__ */ new Map();
|
|
@@ -3747,7 +4092,7 @@ function renderAccountManager(root, ctx2) {
|
|
|
3747
4092
|
</div>
|
|
3748
4093
|
<div class="env-item" id="env-firefox">
|
|
3749
4094
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
3750
|
-
<span
|
|
4095
|
+
<span>\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox Firefox\uFF09</span>
|
|
3751
4096
|
</div>
|
|
3752
4097
|
</div>
|
|
3753
4098
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
@@ -3790,15 +4135,18 @@ function renderAccountManager(root, ctx2) {
|
|
|
3790
4135
|
let busUnsubscribe = null;
|
|
3791
4136
|
async function checkEnvironment() {
|
|
3792
4137
|
try {
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
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);
|
|
3802
4150
|
} catch (err) {
|
|
3803
4151
|
console.error("Environment check failed:", err);
|
|
3804
4152
|
}
|
|
@@ -3812,6 +4160,18 @@ function renderAccountManager(root, ctx2) {
|
|
|
3812
4160
|
envCheckInFlight = false;
|
|
3813
4161
|
}
|
|
3814
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
|
+
}
|
|
3815
4175
|
function updateEnvItem(id, ok) {
|
|
3816
4176
|
const el = root.querySelector(`#${id}`);
|
|
3817
4177
|
if (!el) return;
|
|
@@ -3827,7 +4187,11 @@ function renderAccountManager(root, ctx2) {
|
|
|
3827
4187
|
platform: normalizePlatform(row.platform),
|
|
3828
4188
|
statusView: row.valid ? "valid" : row.status === "pending" ? "pending" : "expired",
|
|
3829
4189
|
lastCheckAt: toTimestamp(row.updatedAt)
|
|
3830
|
-
}))
|
|
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
|
+
});
|
|
3831
4195
|
renderAccountList();
|
|
3832
4196
|
} catch (err) {
|
|
3833
4197
|
console.error("Failed to load accounts:", err);
|
|
@@ -3861,11 +4225,11 @@ function renderAccountManager(root, ctx2) {
|
|
|
3861
4225
|
const nameDiv = createEl("div", { style: "min-width: 0; flex: 1;" }, [
|
|
3862
4226
|
createEl("div", { className: "account-name", style: "display: flex; gap: 6px; align-items: center;" }, [
|
|
3863
4227
|
createEl("span", { style: "font-size: 13px;" }, [platform.icon]),
|
|
3864
|
-
createEl("span", {}, [acc.alias || acc.name || acc.profileId]),
|
|
4228
|
+
createEl("span", {}, [acc.alias || acc.name || formatProfileTag2(acc.profileId)]),
|
|
3865
4229
|
createEl("span", { style: "font-size: 11px; color: var(--text-3);" }, [platform.label])
|
|
3866
4230
|
]),
|
|
3867
4231
|
createEl("div", { className: "account-alias", style: "font-size: 11px; color: var(--text-3);" }, [
|
|
3868
|
-
`profile: ${acc.profileId} \xB7 \u4E0A\u6B21\u68C0\u67E5: ${formatTs(acc.lastCheckAt)}`
|
|
4232
|
+
`profile: ${formatProfileTag2(acc.profileId)} (${acc.profileId}) \xB7 \u4E0A\u6B21\u68C0\u67E5: ${formatTs(acc.lastCheckAt)}`
|
|
3869
4233
|
])
|
|
3870
4234
|
]);
|
|
3871
4235
|
const statusBadge = createEl("span", {
|
|
@@ -4187,11 +4551,6 @@ Profile ID: ${acc.profileId}
|
|
|
4187
4551
|
}
|
|
4188
4552
|
|
|
4189
4553
|
// src/renderer/tabs-new/scheduler.mts
|
|
4190
|
-
function commandTypeToWeiboTaskType2(commandType) {
|
|
4191
|
-
if (commandType === "weibo-search") return "search";
|
|
4192
|
-
if (commandType === "weibo-monitor") return "monitor";
|
|
4193
|
-
return "timeline";
|
|
4194
|
-
}
|
|
4195
4554
|
function renderSchedulerPanel(root, ctx2) {
|
|
4196
4555
|
root.innerHTML = "";
|
|
4197
4556
|
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
@@ -4260,18 +4619,25 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4260
4619
|
<div>
|
|
4261
4620
|
<label>\u8C03\u5EA6\u7C7B\u578B</label>
|
|
4262
4621
|
<select id="scheduler-type" style="width: 140px;">
|
|
4263
|
-
<option value="
|
|
4264
|
-
<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>
|
|
4265
4631
|
<option value="daily">\u6BCF\u5929</option>
|
|
4266
4632
|
<option value="weekly">\u6BCF\u5468</option>
|
|
4267
4633
|
</select>
|
|
4268
4634
|
</div>
|
|
4269
|
-
<div id="scheduler-interval-wrap">
|
|
4635
|
+
<div id="scheduler-interval-wrap" style="display:none;">
|
|
4270
4636
|
<label>\u95F4\u9694\u5206\u949F</label>
|
|
4271
4637
|
<input id="scheduler-interval" type="number" min="1" value="30" style="width: 120px;" />
|
|
4272
4638
|
</div>
|
|
4273
4639
|
<div id="scheduler-runat-wrap" style="display:none;">
|
|
4274
|
-
<label>\
|
|
4640
|
+
<label>\u6267\u884C\u65F6\u95F4</label>
|
|
4275
4641
|
<input id="scheduler-runat" type="datetime-local" style="width: 220px;" />
|
|
4276
4642
|
</div>
|
|
4277
4643
|
<div>
|
|
@@ -4281,8 +4647,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4281
4647
|
</div>
|
|
4282
4648
|
<div class="row">
|
|
4283
4649
|
<div>
|
|
4284
|
-
<label>Profile</label>
|
|
4285
|
-
<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>
|
|
4286
4653
|
</div>
|
|
4287
4654
|
<div>
|
|
4288
4655
|
<label>\u5173\u952E\u8BCD</label>
|
|
@@ -4332,6 +4699,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4332
4699
|
</div>
|
|
4333
4700
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
4334
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>
|
|
4335
4703
|
<button id="scheduler-reset-btn" class="secondary" style="flex:1;">\u6E05\u7A7A\u8868\u5355</button>
|
|
4336
4704
|
</div>
|
|
4337
4705
|
`;
|
|
@@ -4360,12 +4728,15 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4360
4728
|
const nameInput = root.querySelector("#scheduler-name");
|
|
4361
4729
|
const enabledInput = root.querySelector("#scheduler-enabled");
|
|
4362
4730
|
const typeSelect = root.querySelector("#scheduler-type");
|
|
4731
|
+
const periodicTypeWrap = root.querySelector("#scheduler-periodic-type-wrap");
|
|
4732
|
+
const periodicTypeSelect = root.querySelector("#scheduler-periodic-type");
|
|
4363
4733
|
const intervalWrap = root.querySelector("#scheduler-interval-wrap");
|
|
4364
4734
|
const runAtWrap = root.querySelector("#scheduler-runat-wrap");
|
|
4365
4735
|
const intervalInput = root.querySelector("#scheduler-interval");
|
|
4366
4736
|
const runAtInput = root.querySelector("#scheduler-runat");
|
|
4367
4737
|
const maxRunsInput = root.querySelector("#scheduler-max-runs");
|
|
4368
4738
|
const profileInput = root.querySelector("#scheduler-profile");
|
|
4739
|
+
const profileHint = root.querySelector("#scheduler-profile-hint");
|
|
4369
4740
|
const keywordInput = root.querySelector("#scheduler-keyword");
|
|
4370
4741
|
const userIdWrap = root.querySelector("#scheduler-user-id-wrap");
|
|
4371
4742
|
const userIdInput = root.querySelector("#scheduler-user-id");
|
|
@@ -4377,8 +4748,10 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4377
4748
|
const dryRunInput = root.querySelector("#scheduler-dryrun");
|
|
4378
4749
|
const likeKeywordsInput = root.querySelector("#scheduler-like-keywords");
|
|
4379
4750
|
const saveBtn = root.querySelector("#scheduler-save-btn");
|
|
4751
|
+
const runNowBtn = root.querySelector("#scheduler-run-now-btn");
|
|
4380
4752
|
const resetBtn = root.querySelector("#scheduler-reset-btn");
|
|
4381
4753
|
let tasks = [];
|
|
4754
|
+
let accountRows = [];
|
|
4382
4755
|
let daemonRunId = "";
|
|
4383
4756
|
let unsubscribeCmd = null;
|
|
4384
4757
|
let pendingFocusTaskId = String(ctx2?.activeTaskConfigId || "").trim();
|
|
@@ -4395,14 +4768,21 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4395
4768
|
function openConfigTab(taskId) {
|
|
4396
4769
|
setActiveTaskContext(taskId);
|
|
4397
4770
|
if (typeof ctx2.setActiveTab === "function") {
|
|
4398
|
-
ctx2.setActiveTab("
|
|
4771
|
+
ctx2.setActiveTab("tasks");
|
|
4399
4772
|
}
|
|
4400
4773
|
}
|
|
4401
4774
|
function updateTypeFields() {
|
|
4402
|
-
const mode = typeSelect.value;
|
|
4403
|
-
const
|
|
4404
|
-
|
|
4405
|
-
|
|
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
|
+
}
|
|
4406
4786
|
}
|
|
4407
4787
|
function updateTaskTypeOptions() {
|
|
4408
4788
|
const platform = platformSelect.value;
|
|
@@ -4412,6 +4792,36 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4412
4792
|
taskTypeSelect.value = taskTypeSelect.options[0]?.value || "";
|
|
4413
4793
|
}
|
|
4414
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
|
+
}
|
|
4415
4825
|
}
|
|
4416
4826
|
function updatePlatformFields() {
|
|
4417
4827
|
const commandType = String(taskTypeSelect.value || "").trim();
|
|
@@ -4424,7 +4834,8 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4424
4834
|
editingIdInput.value = "";
|
|
4425
4835
|
nameInput.value = "";
|
|
4426
4836
|
enabledInput.checked = true;
|
|
4427
|
-
typeSelect.value = "
|
|
4837
|
+
typeSelect.value = "immediate";
|
|
4838
|
+
periodicTypeSelect.value = "interval";
|
|
4428
4839
|
intervalInput.value = "30";
|
|
4429
4840
|
runAtInput.value = "";
|
|
4430
4841
|
maxRunsInput.value = "";
|
|
@@ -4447,7 +4858,6 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4447
4858
|
const maxRuns = maxRunsRaw ? Math.max(1, Number(maxRunsRaw) || 1) : null;
|
|
4448
4859
|
const commandType = String(taskTypeSelect.value || "xhs-unified").trim();
|
|
4449
4860
|
const argv = {
|
|
4450
|
-
profile: profileInput.value.trim(),
|
|
4451
4861
|
keyword: keywordInput.value.trim(),
|
|
4452
4862
|
"max-notes": Number(maxNotesInput.value || 50) || 50,
|
|
4453
4863
|
env: envSelect.value,
|
|
@@ -4457,19 +4867,40 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4457
4867
|
headless: headlessInput.checked,
|
|
4458
4868
|
"dry-run": dryRunInput.checked
|
|
4459
4869
|
};
|
|
4870
|
+
const profileValue = profileInput.value.trim();
|
|
4871
|
+
if (profileValue) argv.profile = profileValue;
|
|
4460
4872
|
if (commandType.startsWith("weibo")) {
|
|
4461
|
-
argv["task-type"] = commandTypeToWeiboTaskType2(commandType);
|
|
4462
4873
|
argv["user-id"] = userIdInput.value.trim();
|
|
4463
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
|
+
}
|
|
4464
4895
|
return {
|
|
4465
4896
|
id: editingIdInput.value.trim(),
|
|
4466
4897
|
name: nameInput.value.trim(),
|
|
4467
4898
|
enabled: enabledInput.checked,
|
|
4468
4899
|
commandType,
|
|
4469
|
-
scheduleType
|
|
4900
|
+
scheduleType,
|
|
4470
4901
|
intervalMinutes: Number(intervalInput.value || 30) || 30,
|
|
4471
|
-
runAt
|
|
4472
|
-
maxRuns,
|
|
4902
|
+
runAt,
|
|
4903
|
+
maxRuns: maxRunsFinal,
|
|
4473
4904
|
argv
|
|
4474
4905
|
};
|
|
4475
4906
|
}
|
|
@@ -4482,7 +4913,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4482
4913
|
editingIdInput.value = task.id;
|
|
4483
4914
|
nameInput.value = task.name || "";
|
|
4484
4915
|
enabledInput.checked = task.enabled !== false;
|
|
4485
|
-
|
|
4916
|
+
const uiSchedule = inferUiScheduleEditorState(task);
|
|
4917
|
+
typeSelect.value = uiSchedule.mode;
|
|
4918
|
+
periodicTypeSelect.value = uiSchedule.periodicType;
|
|
4486
4919
|
intervalInput.value = String(task.intervalMinutes || 30);
|
|
4487
4920
|
runAtInput.value = toLocalDatetimeValue(task.runAt);
|
|
4488
4921
|
maxRunsInput.value = task.maxRuns ? String(task.maxRuns) : "";
|
|
@@ -4500,19 +4933,27 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4500
4933
|
updatePlatformFields();
|
|
4501
4934
|
updateTypeFields();
|
|
4502
4935
|
}
|
|
4503
|
-
async function
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
args: [script, ...args, "--json"],
|
|
4509
|
-
timeoutMs
|
|
4510
|
-
});
|
|
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);
|
|
4511
4941
|
if (!ret?.ok) {
|
|
4512
|
-
const reason = String(ret?.error ||
|
|
4942
|
+
const reason = String(ret?.error || "schedule command failed").trim();
|
|
4513
4943
|
throw new Error(reason || "schedule command failed");
|
|
4514
4944
|
}
|
|
4515
|
-
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;
|
|
4516
4957
|
}
|
|
4517
4958
|
function downloadJson(fileName, payload) {
|
|
4518
4959
|
const text = JSON.stringify(payload, null, 2);
|
|
@@ -4534,7 +4975,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4534
4975
|
const card = createEl("div", {
|
|
4535
4976
|
style: "border:1px solid var(--border); border-radius:10px; padding:10px; margin-bottom:10px; background: var(--panel-soft);"
|
|
4536
4977
|
});
|
|
4537
|
-
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)`;
|
|
4538
4979
|
const statusText = task.lastStatus ? `${task.lastStatus} / run=${task.runCount} / fail=${task.failCount}` : "never run";
|
|
4539
4980
|
const headRow = createEl("div", { style: "display:flex; justify-content:space-between; gap:8px; margin-bottom:6px;" });
|
|
4540
4981
|
headRow.appendChild(createEl("div", { style: "font-weight:600;" }, [task.name || task.id]));
|
|
@@ -4583,7 +5024,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4583
5024
|
runBtn.onclick = async () => {
|
|
4584
5025
|
try {
|
|
4585
5026
|
setActiveTaskContext(task.id);
|
|
4586
|
-
const out = await
|
|
5027
|
+
const out = await invokeSchedule({ action: "run", taskId: task.id, timeoutMs: 0 });
|
|
4587
5028
|
const runId = String(
|
|
4588
5029
|
out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
|
|
4589
5030
|
).trim();
|
|
@@ -4597,6 +5038,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4597
5038
|
target: Number(argv["max-notes"] || argv.target || 0) || 0,
|
|
4598
5039
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4599
5040
|
};
|
|
5041
|
+
ctx2.activeRunId = runId || null;
|
|
4600
5042
|
}
|
|
4601
5043
|
if (typeof ctx2.setStatus === "function") {
|
|
4602
5044
|
ctx2.setStatus(`running: ${task.id}`);
|
|
@@ -4611,7 +5053,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4611
5053
|
};
|
|
4612
5054
|
exportBtn.onclick = async () => {
|
|
4613
5055
|
try {
|
|
4614
|
-
const out = await
|
|
5056
|
+
const out = await invokeSchedule({ action: "export", taskId: task.id });
|
|
4615
5057
|
downloadJson(`${task.id}.json`, out);
|
|
4616
5058
|
} catch (err) {
|
|
4617
5059
|
alert(`\u5BFC\u51FA\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4620,7 +5062,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4620
5062
|
delBtn.onclick = async () => {
|
|
4621
5063
|
if (!confirm(`\u786E\u8BA4\u5220\u9664\u4EFB\u52A1 ${task.id} ?`)) return;
|
|
4622
5064
|
try {
|
|
4623
|
-
await
|
|
5065
|
+
await invokeSchedule({ action: "delete", taskId: task.id });
|
|
4624
5066
|
await refreshList();
|
|
4625
5067
|
} catch (err) {
|
|
4626
5068
|
alert(`\u5220\u9664\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4630,7 +5072,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4630
5072
|
}
|
|
4631
5073
|
}
|
|
4632
5074
|
async function refreshList() {
|
|
4633
|
-
const out = await
|
|
5075
|
+
const out = await invokeSchedule({ action: "list" });
|
|
4634
5076
|
tasks = parseTaskRows(out);
|
|
4635
5077
|
if (!pendingFocusTaskId) {
|
|
4636
5078
|
pendingFocusTaskId = String(ctx2?.activeTaskConfigId || "").trim();
|
|
@@ -4650,48 +5092,8 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4650
5092
|
}
|
|
4651
5093
|
async function saveTask() {
|
|
4652
5094
|
const payload = readFormAsPayload();
|
|
4653
|
-
if (!payload.name) {
|
|
4654
|
-
alert("\u4EFB\u52A1\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4655
|
-
return;
|
|
4656
|
-
}
|
|
4657
|
-
if (!payload.argv.profile && !payload.argv.profiles && !payload.argv.profilepool) {
|
|
4658
|
-
alert("profile/profiles/profilepool \u81F3\u5C11\u586B\u5199\u4E00\u4E2A");
|
|
4659
|
-
return;
|
|
4660
|
-
}
|
|
4661
|
-
const commandType = String(payload.commandType || "").trim();
|
|
4662
|
-
const keywordRequired = commandType === "xhs-unified" || commandType === "weibo-search" || commandType === "1688-search";
|
|
4663
|
-
if (keywordRequired && !payload.argv.keyword) {
|
|
4664
|
-
alert("\u5173\u952E\u8BCD\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4665
|
-
return;
|
|
4666
|
-
}
|
|
4667
|
-
if (commandType === "weibo-monitor" && !payload.argv["user-id"]) {
|
|
4668
|
-
alert("\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id");
|
|
4669
|
-
return;
|
|
4670
|
-
}
|
|
4671
|
-
const args = payload.id ? ["update", payload.id] : ["add"];
|
|
4672
|
-
args.push("--name", payload.name);
|
|
4673
|
-
args.push("--enabled", String(payload.enabled));
|
|
4674
|
-
args.push("--command-type", commandType || "xhs-unified");
|
|
4675
|
-
args.push("--schedule-type", payload.scheduleType);
|
|
4676
|
-
if (payload.scheduleType === "once") {
|
|
4677
|
-
if (!payload.runAt) {
|
|
4678
|
-
alert("\u4E00\u6B21\u6027\u4EFB\u52A1\u9700\u8981\u951A\u70B9\u65F6\u95F4");
|
|
4679
|
-
return;
|
|
4680
|
-
}
|
|
4681
|
-
args.push("--run-at", payload.runAt);
|
|
4682
|
-
} else if (payload.scheduleType === "daily" || payload.scheduleType === "weekly") {
|
|
4683
|
-
if (!payload.runAt) {
|
|
4684
|
-
alert(`${payload.scheduleType} \u4EFB\u52A1\u9700\u8981\u951A\u70B9\u65F6\u95F4`);
|
|
4685
|
-
return;
|
|
4686
|
-
}
|
|
4687
|
-
args.push("--run-at", payload.runAt);
|
|
4688
|
-
} else {
|
|
4689
|
-
args.push("--interval-minutes", String(Math.max(1, payload.intervalMinutes)));
|
|
4690
|
-
}
|
|
4691
|
-
args.push("--max-runs", payload.maxRuns === null ? "0" : String(payload.maxRuns));
|
|
4692
|
-
args.push("--argv-json", JSON.stringify(payload.argv));
|
|
4693
5095
|
try {
|
|
4694
|
-
const out = await
|
|
5096
|
+
const out = await invokeSchedule({ action: "save", payload });
|
|
4695
5097
|
const savedId = String(out?.task?.id || payload.id || "").trim();
|
|
4696
5098
|
pendingFocusTaskId = savedId;
|
|
4697
5099
|
if (savedId) setActiveTaskContext(savedId);
|
|
@@ -4700,9 +5102,44 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4700
5102
|
alert(`\u4FDD\u5B58\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4701
5103
|
}
|
|
4702
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
|
+
}
|
|
4703
5140
|
async function runDueNow() {
|
|
4704
5141
|
try {
|
|
4705
|
-
const out = await
|
|
5142
|
+
const out = await invokeSchedule({ action: "run-due", limit: 20, timeoutMs: 0 });
|
|
4706
5143
|
alert(`\u5230\u70B9\u4EFB\u52A1\u6267\u884C\u5B8C\u6210\uFF1Adue=${out.count || 0}, success=${out.success || 0}, failed=${out.failed || 0}`);
|
|
4707
5144
|
await refreshList();
|
|
4708
5145
|
} catch (err) {
|
|
@@ -4711,7 +5148,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4711
5148
|
}
|
|
4712
5149
|
async function exportAll() {
|
|
4713
5150
|
try {
|
|
4714
|
-
const out = await
|
|
5151
|
+
const out = await invokeSchedule({ action: "export" });
|
|
4715
5152
|
downloadJson("webauto-schedules.json", out);
|
|
4716
5153
|
} catch (err) {
|
|
4717
5154
|
alert(`\u5BFC\u51FA\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4726,7 +5163,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4726
5163
|
if (!file) return;
|
|
4727
5164
|
try {
|
|
4728
5165
|
const text = await file.text();
|
|
4729
|
-
await
|
|
5166
|
+
await invokeSchedule({ action: "import", payloadJson: text, mode: "merge" });
|
|
4730
5167
|
await refreshList();
|
|
4731
5168
|
} catch (err) {
|
|
4732
5169
|
alert(`\u5BFC\u5165\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4740,13 +5177,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4740
5177
|
return;
|
|
4741
5178
|
}
|
|
4742
5179
|
const interval = Math.max(5, Number(daemonIntervalInput.value || 30) || 30);
|
|
4743
|
-
const
|
|
4744
|
-
const ret = await ctx2.api.cmdSpawn({
|
|
4745
|
-
title: `schedule daemon ${interval}s`,
|
|
4746
|
-
cwd: "",
|
|
4747
|
-
args: [script, "daemon", "--interval-sec", String(interval), "--limit", "20", "--json"],
|
|
4748
|
-
groupKey: "scheduler"
|
|
4749
|
-
});
|
|
5180
|
+
const ret = await invokeSchedule({ action: "daemon-start", intervalSec: interval, limit: 20 });
|
|
4750
5181
|
daemonRunId = String(ret?.runId || "").trim();
|
|
4751
5182
|
setDaemonStatus(daemonRunId ? `daemon: \u8FD0\u884C\u4E2D (${daemonRunId})` : "daemon: \u542F\u52A8\u5931\u8D25");
|
|
4752
5183
|
}
|
|
@@ -4765,7 +5196,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4765
5196
|
platformSelect.addEventListener("change", updateTaskTypeOptions);
|
|
4766
5197
|
taskTypeSelect.addEventListener("change", updatePlatformFields);
|
|
4767
5198
|
typeSelect.addEventListener("change", updateTypeFields);
|
|
5199
|
+
periodicTypeSelect.addEventListener("change", updateTypeFields);
|
|
4768
5200
|
saveBtn.onclick = () => void saveTask();
|
|
5201
|
+
runNowBtn.onclick = () => void runNowFromForm();
|
|
4769
5202
|
resetBtn.onclick = () => resetForm();
|
|
4770
5203
|
refreshBtn.onclick = () => void refreshList();
|
|
4771
5204
|
runDueBtn.onclick = () => void runDueNow();
|
|
@@ -4817,6 +5250,8 @@ var tabs = [
|
|
|
4817
5250
|
var tabsEl = document.getElementById("tabs");
|
|
4818
5251
|
var contentEl = document.getElementById("content");
|
|
4819
5252
|
var statusEl = document.getElementById("status");
|
|
5253
|
+
var appTitleEl = document.getElementById("app-title");
|
|
5254
|
+
var appVersionEl = document.getElementById("app-version");
|
|
4820
5255
|
var activeTabCleanup = null;
|
|
4821
5256
|
var mutableApi = { ...window.api || {}, settings: null };
|
|
4822
5257
|
var tabIcons = {
|
|
@@ -4892,6 +5327,22 @@ function startDesktopHeartbeat() {
|
|
|
4892
5327
|
async function loadSettings() {
|
|
4893
5328
|
await ctx.refreshSettings();
|
|
4894
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
|
+
}
|
|
4895
5346
|
function focusTabButton(tabId) {
|
|
4896
5347
|
const button = tabsEl.querySelector(`[data-tab-id="${tabId}"]`);
|
|
4897
5348
|
button?.focus();
|
|
@@ -4999,6 +5450,7 @@ async function detectStartupTab() {
|
|
|
4999
5450
|
}
|
|
5000
5451
|
async function main() {
|
|
5001
5452
|
startDesktopHeartbeat();
|
|
5453
|
+
await applyVersionBadge();
|
|
5002
5454
|
await loadSettings();
|
|
5003
5455
|
installCmdEvents();
|
|
5004
5456
|
const startupTab = await detectStartupTab();
|