@web-auto/webauto 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/apps/desktop-console/default-settings.json +2 -2
  2. package/apps/desktop-console/dist/main/index.mjs +915 -85
  3. package/apps/desktop-console/dist/main/preload.mjs +7 -0
  4. package/apps/desktop-console/dist/renderer/index.html +622 -50
  5. package/apps/desktop-console/dist/renderer/index.js +2415 -470
  6. package/apps/desktop-console/dist/renderer/run.mts +6 -5
  7. package/apps/desktop-console/entry/ui-cli.mjs +672 -0
  8. package/apps/desktop-console/entry/ui-console.mjs +416 -29
  9. package/apps/webauto/entry/account.mjs +89 -53
  10. package/apps/webauto/entry/browser-status.mjs +7 -10
  11. package/apps/webauto/entry/lib/account-detect.mjs +254 -28
  12. package/apps/webauto/entry/lib/account-store.mjs +219 -30
  13. package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
  14. package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
  15. package/apps/webauto/entry/lib/profilepool.mjs +14 -5
  16. package/apps/webauto/entry/lib/quota-status.mjs +23 -0
  17. package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
  18. package/apps/webauto/entry/profilepool.mjs +106 -17
  19. package/apps/webauto/entry/schedule.mjs +612 -0
  20. package/apps/webauto/entry/weibo-unified.mjs +134 -0
  21. package/apps/webauto/entry/xhs-install.mjs +236 -29
  22. package/apps/webauto/entry/xhs-status.mjs +5 -2
  23. package/apps/webauto/entry/xhs-unified.mjs +631 -98
  24. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
  25. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
  26. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
  27. package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
  28. package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
  29. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
  30. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
  31. package/bin/camoufox-cli.mjs +61 -0
  32. package/bin/webauto.mjs +301 -54
  33. package/dist/modules/camo-backend/src/index.js +49 -1
  34. package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
  35. package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
  36. package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
  37. package/dist/modules/collection-manager/bloom-filter.js +91 -0
  38. package/dist/modules/collection-manager/date-utils.js +275 -0
  39. package/dist/modules/collection-manager/index.js +258 -0
  40. package/dist/modules/collection-manager/storage.js +195 -0
  41. package/dist/modules/collection-manager/types.js +47 -0
  42. package/dist/modules/logging/src/index.js +1 -1
  43. package/dist/modules/process-registry/index.js +230 -0
  44. package/dist/modules/rate-limiter/index.js +242 -0
  45. package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
  46. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
  47. package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
  48. package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
  49. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
  50. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
  51. package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
  52. package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
  53. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
  54. package/dist/modules/workflow/config/workflowRegistry.js +2 -0
  55. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
  56. package/dist/modules/workflow/src/runner.js +6 -0
  57. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
  58. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
  59. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
  60. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
  61. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
  62. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
  63. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
  64. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
  65. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
  66. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
  67. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  68. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
  69. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  70. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
  71. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
  72. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
  73. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
  74. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
  75. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
  76. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
  77. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  78. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
  79. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  80. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
  81. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  82. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
  83. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  84. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
  85. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  86. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
  87. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  88. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
  89. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  90. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
  91. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  92. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
  93. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  94. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
  95. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  96. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
  97. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  98. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
  99. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  100. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
  101. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  102. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
  103. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  104. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
  105. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  106. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
  107. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  108. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
  109. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  110. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
  111. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  112. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
  113. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  114. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
  115. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  116. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
  117. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  118. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
  119. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  120. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
  121. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
  122. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
  123. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  124. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
  125. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
  126. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
  127. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  128. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
  129. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  130. package/dist/services/shared/serviceProcessLogger.js +1 -1
  131. package/dist/services/unified-api/server.js +105 -11
  132. package/modules/camo-backend/src/index.ts +46 -1
  133. package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
  134. package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
  135. package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
  136. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
  137. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
  138. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
  139. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
  140. package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
  141. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
  142. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
  143. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
  144. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
  145. package/modules/collection-manager/bloom-filter.ts +112 -0
  146. package/modules/collection-manager/date-utils.ts +316 -0
  147. package/modules/collection-manager/index.ts +309 -0
  148. package/modules/collection-manager/package.json +10 -0
  149. package/modules/collection-manager/storage.ts +174 -0
  150. package/modules/collection-manager/types.ts +156 -0
  151. package/modules/logging/src/index.ts +1 -1
  152. package/modules/process-registry/index.ts +284 -0
  153. package/modules/rate-limiter/index.ts +322 -0
  154. package/modules/state/src/paths.ts +9 -1
  155. package/modules/task-scheduler/index.ts +293 -0
  156. package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
  157. package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
  158. package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
  159. package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
  160. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
  161. package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
  162. package/modules/workflow/config/workflowRegistry.ts +2 -0
  163. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
  164. package/modules/workflow/src/runner.ts +6 -0
  165. package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
  166. package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
  167. package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
  168. package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
  169. package/package.json +14 -5
  170. package/scripts/postinstall-resources.mjs +62 -0
  171. package/scripts/test/run-coverage.mjs +76 -0
  172. package/scripts/weibo/search.ts +49 -0
  173. package/services/shared/serviceProcessLogger.ts +1 -1
  174. package/services/unified-api/server.ts +98 -12
@@ -0,0 +1,612 @@
1
+ #!/usr/bin/env node
2
+ import minimist from 'minimist';
3
+ import fs from 'node:fs';
4
+ import {
5
+ acquireScheduleDaemonLease,
6
+ addScheduleTask,
7
+ claimScheduleTask,
8
+ exportScheduleTasks,
9
+ getScheduleTask,
10
+ getSchedulerPolicy,
11
+ importScheduleTasks,
12
+ listDueScheduleTasks,
13
+ listScheduleTasks,
14
+ markScheduleTaskResult,
15
+ normalizeSchedulerPolicy,
16
+ releaseScheduleDaemonLease,
17
+ releaseScheduleTaskClaim,
18
+ removeScheduleTask,
19
+ renewScheduleDaemonLease,
20
+ renewScheduleTaskClaim,
21
+ setSchedulerPolicy,
22
+ resolveSchedulesRoot,
23
+ updateScheduleTask,
24
+ } from './lib/schedule-store.mjs';
25
+
26
+ let xhsRunnerPromise = null;
27
+ let weiboRunnerPromise = null;
28
+
29
+ async function getXhsRunner() {
30
+ if (!xhsRunnerPromise) {
31
+ xhsRunnerPromise = import('./xhs-unified.mjs').then((mod) => mod.runUnified);
32
+ }
33
+ return xhsRunnerPromise;
34
+ }
35
+
36
+ async function getWeiboRunner() {
37
+ if (!weiboRunnerPromise) {
38
+ weiboRunnerPromise = import('./weibo-unified.mjs').then((mod) => mod.runWeiboUnified);
39
+ }
40
+ return weiboRunnerPromise;
41
+ }
42
+
43
+ function output(payload, jsonMode) {
44
+ if (jsonMode) {
45
+ console.log(JSON.stringify(payload));
46
+ return;
47
+ }
48
+ console.log(JSON.stringify(payload, null, 2));
49
+ }
50
+
51
+ function parseBoolean(value, fallback = false) {
52
+ if (value === undefined || value === null) return fallback;
53
+ if (typeof value === 'boolean') return value;
54
+ const text = String(value).trim().toLowerCase();
55
+ if (!text) return fallback;
56
+ if (text === '1' || text === 'true' || text === 'yes') return true;
57
+ if (text === '0' || text === 'false' || text === 'no') return false;
58
+ return fallback;
59
+ }
60
+
61
+ function parsePositiveInt(value, fallback) {
62
+ const num = Number(value);
63
+ if (!Number.isFinite(num)) return fallback;
64
+ return Math.max(1, Math.floor(num));
65
+ }
66
+
67
+ function parseJson(text, fallback = {}) {
68
+ if (text === undefined || text === null || text === '') return fallback;
69
+ const raw = String(text);
70
+ return JSON.parse(raw.replace(/^\uFEFF/, ''));
71
+ }
72
+
73
+ function safeReadJsonFile(filePath) {
74
+ const raw = fs.readFileSync(filePath, 'utf8');
75
+ return parseJson(raw, {});
76
+ }
77
+
78
+ function safeWriteJsonFile(filePath, payload) {
79
+ fs.mkdirSync(requirePathDir(filePath), { recursive: true });
80
+ fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
81
+ }
82
+
83
+ function requirePathDir(filePath) {
84
+ const path = String(filePath || '').trim();
85
+ if (!path) throw new Error('file path is required');
86
+ const idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
87
+ return idx > 0 ? path.slice(0, idx) : '.';
88
+ }
89
+
90
+ function pickArgvValue(argv, key) {
91
+ if (Object.prototype.hasOwnProperty.call(argv, key)) return argv[key];
92
+ return undefined;
93
+ }
94
+
95
+ function buildCommandArgv(argv) {
96
+ const payload = parseJson(argv['argv-json'], {});
97
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
98
+ throw new Error('--argv-json must be object JSON');
99
+ }
100
+ const out = { ...payload };
101
+ const keys = [
102
+ 'profile',
103
+ 'profiles',
104
+ 'profilepool',
105
+ 'keyword',
106
+ 'k',
107
+ 'max-notes',
108
+ 'target',
109
+ 'total-notes',
110
+ 'total-target',
111
+ 'parallel',
112
+ 'concurrency',
113
+ 'plan-only',
114
+ 'tab-count',
115
+ 'throttle',
116
+ 'note-interval',
117
+ 'env',
118
+ 'output-root',
119
+ 'dry-run',
120
+ 'do-comments',
121
+ 'persist-comments',
122
+ 'do-likes',
123
+ 'like-keywords',
124
+ 'max-likes',
125
+ 'match-mode',
126
+ 'match-min-hits',
127
+ 'match-keywords',
128
+ 'do-reply',
129
+ 'reply-text',
130
+ 'do-ocr',
131
+ 'ocr-command',
132
+ 'input-mode',
133
+ 'headless',
134
+ 'task-type',
135
+ 'taskType',
136
+ 'user-id',
137
+ 'userId',
138
+ ];
139
+ for (const key of keys) {
140
+ const value = pickArgvValue(argv, key);
141
+ if (value !== undefined) out[key] = value;
142
+ }
143
+ if (argv['no-dry-run'] === true) out['dry-run'] = false;
144
+ return out;
145
+ }
146
+
147
+ function parseTaskInput(argv, mode = 'add') {
148
+ const scheduleType = String(argv['schedule-type'] || argv.type || (mode === 'add' ? 'interval' : '')).trim();
149
+ const intervalMinutes = argv['interval-minutes'] ?? argv['every-minutes'];
150
+ const runAt = argv['run-at'] ?? argv.at;
151
+ const maxRuns = argv['max-runs'] ?? argv.maxRuns;
152
+ const enabled = argv.enabled;
153
+ const commandType = String(argv['command-type'] || 'xhs-unified').trim();
154
+ const commandArgv = buildCommandArgv(argv);
155
+ if (commandType.startsWith('weibo-')) {
156
+ const explicitTaskType = String(commandArgv['task-type'] || commandArgv.taskType || '').trim();
157
+ if (!explicitTaskType) {
158
+ if (commandType === 'weibo-search') commandArgv['task-type'] = 'search';
159
+ else if (commandType === 'weibo-monitor') commandArgv['task-type'] = 'monitor';
160
+ else commandArgv['task-type'] = 'timeline';
161
+ }
162
+ }
163
+ const patch = {
164
+ name: argv.name,
165
+ scheduleType: scheduleType || undefined,
166
+ intervalMinutes,
167
+ runAt,
168
+ maxRuns,
169
+ enabled: enabled === undefined ? undefined : parseBoolean(enabled, true),
170
+ commandType,
171
+ commandArgv,
172
+ };
173
+ return patch;
174
+ }
175
+
176
+ function createOwnerId(prefix = 'schedule') {
177
+ return `${prefix}-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
178
+ }
179
+
180
+ function parseLeaseMs(value, fallbackMs) {
181
+ if (value === undefined || value === null || value === '') return fallbackMs;
182
+ const seconds = Number(value);
183
+ if (!Number.isFinite(seconds) || seconds <= 0) return fallbackMs;
184
+ return Math.max(1_000, Math.floor(seconds * 1000));
185
+ }
186
+
187
+ function mergePolicy(base, override) {
188
+ const left = base && typeof base === 'object' ? base : {};
189
+ const right = override && typeof override === 'object' ? override : {};
190
+ return {
191
+ ...left,
192
+ ...right,
193
+ maxConcurrencyByPlatform: {
194
+ ...(left.maxConcurrencyByPlatform || {}),
195
+ ...(right.maxConcurrencyByPlatform || {}),
196
+ },
197
+ resourceMutex: {
198
+ ...(left.resourceMutex || {}),
199
+ ...(right.resourceMutex || {}),
200
+ },
201
+ };
202
+ }
203
+
204
+ function resolveRuntimePolicy(argv = {}) {
205
+ const basePolicy = getSchedulerPolicy();
206
+ const policyOverride = argv['policy-json'] !== undefined
207
+ ? parseJson(argv['policy-json'], {})
208
+ : {};
209
+ const merged = mergePolicy(basePolicy, policyOverride);
210
+ const concurrencyOverride = parsePositiveInt(argv['max-concurrency'] ?? argv.concurrency, Number(merged.maxConcurrency || 1));
211
+ merged.maxConcurrency = concurrencyOverride;
212
+ return normalizeSchedulerPolicy(merged);
213
+ }
214
+
215
+ async function withConsoleSilenced(enabled, fn) {
216
+ if (!enabled) return fn();
217
+ const originalLog = console.log;
218
+ const originalWarn = console.warn;
219
+ const originalInfo = console.info;
220
+ const originalError = console.error;
221
+ console.log = () => {};
222
+ console.warn = () => {};
223
+ console.info = () => {};
224
+ console.error = () => {};
225
+ try {
226
+ return await fn();
227
+ } finally {
228
+ console.log = originalLog;
229
+ console.warn = originalWarn;
230
+ console.info = originalInfo;
231
+ console.error = originalError;
232
+ }
233
+ }
234
+
235
+ async function executeTask(task, options = {}) {
236
+ const ownerId = String(options.ownerId || '').trim() || createOwnerId('runner');
237
+ const runToken = createOwnerId('claim');
238
+ const taskLeaseMs = parseLeaseMs(options.taskLeaseSec, 30 * 60 * 1000);
239
+ const policy = options.policy && typeof options.policy === 'object'
240
+ ? options.policy
241
+ : resolveRuntimePolicy({});
242
+ const claim = claimScheduleTask(task, {
243
+ ownerId,
244
+ runToken,
245
+ leaseMs: taskLeaseMs,
246
+ policy,
247
+ });
248
+ if (!claim.ok) {
249
+ return {
250
+ ok: false,
251
+ skipped: true,
252
+ taskId: task?.id || null,
253
+ name: task?.name || null,
254
+ reason: claim.reason || 'claim_failed',
255
+ details: claim.details || null,
256
+ };
257
+ }
258
+
259
+ const heartbeatMs = Math.max(5_000, Math.floor(taskLeaseMs / 3));
260
+ const heartbeat = setInterval(() => {
261
+ renewScheduleTaskClaim(task.id, { ownerId, runToken, leaseMs: taskLeaseMs });
262
+ }, heartbeatMs);
263
+ heartbeat.unref?.();
264
+ const startedAt = Date.now();
265
+ const quietExecutors = options.quietExecutors === true;
266
+ try {
267
+ const commandType = String(task?.commandType || 'xhs-unified').trim();
268
+ const result = await withConsoleSilenced(quietExecutors, async () => {
269
+ if (commandType === 'xhs-unified') {
270
+ const runUnified = await getXhsRunner();
271
+ return runUnified(task.commandArgv || {});
272
+ }
273
+ if (commandType.startsWith('weibo-')) {
274
+ const runWeiboUnified = await getWeiboRunner();
275
+ return runWeiboUnified(task.commandArgv || {});
276
+ }
277
+ if (commandType === '1688-search') {
278
+ throw new Error(`executor_not_implemented: ${commandType}`);
279
+ }
280
+ throw new Error(`unsupported commandType at executeTask: ${commandType}`);
281
+ });
282
+ const durationMs = Date.now() - startedAt;
283
+ const runResult = markScheduleTaskResult(task.id, {
284
+ status: 'success',
285
+ durationMs,
286
+ runId: result?.runId || null,
287
+ finishedAt: new Date().toISOString(),
288
+ });
289
+ return {
290
+ ok: true,
291
+ skipped: false,
292
+ taskId: task.id,
293
+ name: task.name,
294
+ durationMs,
295
+ runResult,
296
+ result: {
297
+ summaryPath: result?.summaryPath || null,
298
+ planOnly: result?.planOnly === true,
299
+ },
300
+ };
301
+ } catch (error) {
302
+ const durationMs = Date.now() - startedAt;
303
+ const message = error?.message || String(error);
304
+ const runResult = markScheduleTaskResult(task.id, {
305
+ status: 'failed',
306
+ error: message,
307
+ durationMs,
308
+ finishedAt: new Date().toISOString(),
309
+ });
310
+ return {
311
+ ok: false,
312
+ skipped: false,
313
+ taskId: task.id,
314
+ name: task.name,
315
+ durationMs,
316
+ error: message,
317
+ runResult,
318
+ };
319
+ } finally {
320
+ clearInterval(heartbeat);
321
+ releaseScheduleTaskClaim(task.id, { ownerId, runToken });
322
+ }
323
+ }
324
+
325
+ async function runDue(limit, options = {}) {
326
+ const dueTasks = listDueScheduleTasks(limit);
327
+ const maxConcurrency = Math.max(1, Math.min(
328
+ Number(options?.policy?.maxConcurrency) || 1,
329
+ dueTasks.length || 1,
330
+ ));
331
+ const queue = [...dueTasks];
332
+ const results = [];
333
+ async function worker() {
334
+ while (queue.length > 0) {
335
+ const task = queue.shift();
336
+ if (!task) break;
337
+ const item = await executeTask(task, options);
338
+ results.push(item);
339
+ }
340
+ }
341
+ await Promise.all(Array.from({ length: maxConcurrency }, () => worker()));
342
+ return {
343
+ count: dueTasks.length,
344
+ success: results.filter((item) => item.ok).length,
345
+ skipped: results.filter((item) => item.skipped === true).length,
346
+ failed: results.filter((item) => !item.ok && item.skipped !== true).length,
347
+ results,
348
+ };
349
+ }
350
+
351
+ function printHelp() {
352
+ console.log(`webauto schedule
353
+
354
+ Usage:
355
+ webauto schedule --help
356
+ webauto schedule list [--json]
357
+ webauto schedule get <taskId> [--json]
358
+ webauto schedule add [options]
359
+ webauto schedule update <taskId> [options]
360
+ webauto schedule delete <taskId> [--json]
361
+ webauto schedule import [--file <path> | --payload-json <json>] [--mode merge|replace] [--json]
362
+ webauto schedule export [taskId] [--file <path>] [--json]
363
+ webauto schedule policy [--json]
364
+ webauto schedule policy set [--file <path> | --payload-json <json>] [--json]
365
+ webauto schedule run <taskId> [--json]
366
+ webauto schedule run-due [--limit <n>] [--json]
367
+ webauto schedule daemon [--interval-sec <n>] [--limit <n>] [--once] [--json]
368
+
369
+ Task Options:
370
+ --name <text>
371
+ --enabled <true|false>
372
+ --schedule-type interval|once|daily|weekly
373
+ --interval-minutes <n> interval 模式每次触发间隔分钟
374
+ --run-at <iso> once/daily/weekly 模式锚点时间(ISO)
375
+ --max-runs <n> 最大执行次数(>0;为空=不限)
376
+ --command-type xhs-unified|weibo-timeline|weibo-search|weibo-monitor|1688-search
377
+ --argv-json <json> 透传给任务执行器的参数对象
378
+ --max-concurrency <n> 调度并发上限(默认来自 policy)
379
+ --task-lease-sec <n> 单任务 claim lease 秒数(默认 1800)
380
+ --daemon-lease-sec <n> daemon lease 秒数(默认 120)
381
+ --policy-json <json> 运行时策略覆盖(不落盘)
382
+
383
+ Common xhs argv shortcuts (optional):
384
+ --profile <id> --profiles <a,b> --profilepool <prefix>
385
+ --keyword <kw> --max-notes <n> --env <debug|prod>
386
+ --do-comments <bool> --do-likes <bool> --like-keywords <csv>
387
+ --dry-run <bool> --no-dry-run
388
+
389
+ Common weibo argv shortcuts (optional):
390
+ --task-type <timeline|search|monitor>
391
+ --user-id <id> monitor 任务必填
392
+ `);
393
+ }
394
+
395
+ async function cmdList(argv, jsonMode) {
396
+ const result = listScheduleTasks();
397
+ output({ ok: true, ...result }, jsonMode);
398
+ }
399
+
400
+ async function cmdGet(taskId, jsonMode) {
401
+ const task = getScheduleTask(taskId);
402
+ output({ ok: true, task }, jsonMode);
403
+ }
404
+
405
+ async function cmdAdd(argv, jsonMode) {
406
+ const input = parseTaskInput(argv, 'add');
407
+ const task = addScheduleTask(input);
408
+ output({ ok: true, task }, jsonMode);
409
+ }
410
+
411
+ async function cmdUpdate(taskId, argv, jsonMode) {
412
+ const patch = parseTaskInput(argv, 'update');
413
+ const task = updateScheduleTask(taskId, patch);
414
+ output({ ok: true, task }, jsonMode);
415
+ }
416
+
417
+ async function cmdDelete(taskId, jsonMode) {
418
+ const removed = removeScheduleTask(taskId);
419
+ output({ ok: true, removed }, jsonMode);
420
+ }
421
+
422
+ async function cmdImport(argv, jsonMode) {
423
+ const mode = String(argv.mode || 'merge').trim().toLowerCase();
424
+ const payloadText = argv['payload-json'];
425
+ const filePath = argv.file;
426
+ if (!payloadText && !filePath) {
427
+ throw new Error('import requires --file or --payload-json');
428
+ }
429
+ const payload = payloadText ? parseJson(payloadText, {}) : safeReadJsonFile(String(filePath || ''));
430
+ const result = importScheduleTasks(payload, { mode });
431
+ output({ ok: true, ...result }, jsonMode);
432
+ }
433
+
434
+ async function cmdExport(taskId, argv, jsonMode) {
435
+ const payload = exportScheduleTasks(taskId || null);
436
+ const filePath = String(argv.file || '').trim();
437
+ if (filePath) {
438
+ safeWriteJsonFile(filePath, payload);
439
+ output({ ok: true, filePath, count: payload.count }, jsonMode);
440
+ return;
441
+ }
442
+ output({ ok: true, ...payload }, jsonMode);
443
+ }
444
+
445
+ async function cmdPolicy(arg1, argv, jsonMode) {
446
+ const action = String(arg1 || '').trim().toLowerCase();
447
+ if (!action || action === 'get' || action === 'show') {
448
+ output({ ok: true, policy: getSchedulerPolicy() }, jsonMode);
449
+ return;
450
+ }
451
+ if (action === 'set') {
452
+ const payloadText = argv['payload-json'];
453
+ const filePath = argv.file;
454
+ if (!payloadText && !filePath) {
455
+ throw new Error('policy set requires --file or --payload-json');
456
+ }
457
+ const payload = payloadText ? parseJson(payloadText, {}) : safeReadJsonFile(String(filePath || ''));
458
+ const policy = setSchedulerPolicy(payload);
459
+ output({ ok: true, policy }, jsonMode);
460
+ return;
461
+ }
462
+ throw new Error(`unknown policy command: ${action}`);
463
+ }
464
+
465
+ async function cmdRun(taskId, argv, jsonMode) {
466
+ const task = getScheduleTask(taskId);
467
+ const policy = resolveRuntimePolicy(argv);
468
+ const result = await executeTask(task, {
469
+ quietExecutors: jsonMode,
470
+ ownerId: createOwnerId('run'),
471
+ taskLeaseSec: argv['task-lease-sec'],
472
+ policy,
473
+ });
474
+ output({ ok: result.ok, result }, jsonMode);
475
+ if (!result.ok || result.skipped) process.exitCode = 1;
476
+ }
477
+
478
+ async function cmdRunDue(argv, jsonMode) {
479
+ const limit = parsePositiveInt(argv.limit, 20);
480
+ const policy = resolveRuntimePolicy(argv);
481
+ const result = await runDue(limit, {
482
+ quietExecutors: jsonMode,
483
+ ownerId: createOwnerId('run-due'),
484
+ taskLeaseSec: argv['task-lease-sec'],
485
+ policy,
486
+ });
487
+ const ok = result.failed === 0;
488
+ output({ ok, ...result }, jsonMode);
489
+ if (!ok) process.exitCode = 1;
490
+ }
491
+
492
+ async function cmdDaemon(argv, jsonMode) {
493
+ const intervalSec = parsePositiveInt(argv['interval-sec'], 30);
494
+ const limit = parsePositiveInt(argv.limit, 20);
495
+ const runOnce = argv.once === true;
496
+ if (runOnce) {
497
+ const policy = resolveRuntimePolicy(argv);
498
+ const onceResult = await runDue(limit, {
499
+ quietExecutors: jsonMode,
500
+ ownerId: createOwnerId('daemon-once'),
501
+ taskLeaseSec: argv['task-lease-sec'],
502
+ policy,
503
+ });
504
+ const ok = onceResult.failed === 0;
505
+ output({ ok, mode: 'once', intervalSec, ...onceResult }, jsonMode);
506
+ if (!ok) process.exitCode = 1;
507
+ return;
508
+ }
509
+ const ownerId = createOwnerId('daemon');
510
+ const daemonLeaseMs = parseLeaseMs(argv['daemon-lease-sec'], 2 * 60 * 1000);
511
+ const daemonLease = acquireScheduleDaemonLease({
512
+ ownerId,
513
+ leaseMs: daemonLeaseMs,
514
+ });
515
+ if (!daemonLease.ok) {
516
+ output({
517
+ ok: false,
518
+ mode: 'daemon',
519
+ error: 'daemon_lease_busy',
520
+ lease: daemonLease.lease || null,
521
+ }, jsonMode);
522
+ process.exitCode = 1;
523
+ return;
524
+ }
525
+ const policy = resolveRuntimePolicy(argv);
526
+ output({
527
+ ok: true,
528
+ mode: 'daemon',
529
+ root: resolveSchedulesRoot(),
530
+ intervalSec,
531
+ limit,
532
+ ownerId,
533
+ policy,
534
+ startedAt: new Date().toISOString(),
535
+ }, jsonMode);
536
+ const leaseHeartbeatMs = Math.max(5_000, Math.floor(daemonLeaseMs / 3));
537
+ const leaseHeartbeat = setInterval(() => {
538
+ renewScheduleDaemonLease({ ownerId, leaseMs: daemonLeaseMs });
539
+ }, leaseHeartbeatMs);
540
+ leaseHeartbeat.unref?.();
541
+ const tick = async () => {
542
+ const result = await runDue(limit, {
543
+ quietExecutors: jsonMode,
544
+ ownerId,
545
+ taskLeaseSec: argv['task-lease-sec'],
546
+ policy,
547
+ });
548
+ const line = {
549
+ ts: new Date().toISOString(),
550
+ event: 'schedule.tick',
551
+ intervalSec,
552
+ limit,
553
+ dueCount: result.count,
554
+ success: result.success,
555
+ skipped: result.skipped,
556
+ failed: result.failed,
557
+ taskIds: result.results.map((item) => item.taskId),
558
+ };
559
+ console.log(JSON.stringify(line));
560
+ };
561
+ await tick();
562
+ const timer = setInterval(() => {
563
+ void tick();
564
+ }, intervalSec * 1000);
565
+ const shutdown = () => {
566
+ clearInterval(timer);
567
+ clearInterval(leaseHeartbeat);
568
+ releaseScheduleDaemonLease({ ownerId });
569
+ console.log(JSON.stringify({
570
+ ts: new Date().toISOString(),
571
+ event: 'schedule.stopped',
572
+ }));
573
+ process.exit(0);
574
+ };
575
+ process.on('SIGINT', shutdown);
576
+ process.on('SIGTERM', shutdown);
577
+ }
578
+
579
+ async function main() {
580
+ const argv = minimist(process.argv.slice(2), {
581
+ boolean: ['help', 'json', 'once'],
582
+ alias: { h: 'help' },
583
+ });
584
+ const cmd = String(argv._[0] || '').trim();
585
+ const arg1 = String(argv._[1] || '').trim();
586
+ const jsonMode = argv.json === true;
587
+
588
+ if (!cmd || cmd === 'help' || argv.help) {
589
+ printHelp();
590
+ return;
591
+ }
592
+
593
+ if (cmd === 'list') return cmdList(argv, jsonMode);
594
+ if (cmd === 'get') return cmdGet(arg1, jsonMode);
595
+ if (cmd === 'add') return cmdAdd(argv, jsonMode);
596
+ if (cmd === 'update') return cmdUpdate(arg1, argv, jsonMode);
597
+ if (cmd === 'delete' || cmd === 'remove' || cmd === 'rm') return cmdDelete(arg1, jsonMode);
598
+ if (cmd === 'import') return cmdImport(argv, jsonMode);
599
+ if (cmd === 'export') return cmdExport(arg1, argv, jsonMode);
600
+ if (cmd === 'policy') return cmdPolicy(arg1, argv, jsonMode);
601
+ if (cmd === 'run') return cmdRun(arg1, argv, jsonMode);
602
+ if (cmd === 'run-due') return cmdRunDue(argv, jsonMode);
603
+ if (cmd === 'daemon') return cmdDaemon(argv, jsonMode);
604
+
605
+ throw new Error(`unknown schedule command: ${cmd}`);
606
+ }
607
+
608
+ main().catch((error) => {
609
+ const message = error?.message || String(error);
610
+ console.error(message);
611
+ process.exit(1);
612
+ });