@web-auto/webauto 0.1.18 → 0.1.19

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 (65) hide show
  1. package/README.md +122 -53
  2. package/apps/desktop-console/dist/main/index.mjs +227 -12
  3. package/apps/desktop-console/dist/renderer/index.js +237 -8
  4. package/apps/desktop-console/entry/ui-cli.mjs +282 -16
  5. package/apps/desktop-console/entry/ui-console.mjs +46 -15
  6. package/apps/webauto/entry/account.mjs +126 -27
  7. package/apps/webauto/entry/lib/account-detect.mjs +399 -9
  8. package/apps/webauto/entry/lib/account-store.mjs +201 -109
  9. package/apps/webauto/entry/lib/iflow-reply.mjs +194 -0
  10. package/apps/webauto/entry/lib/profile-policy.mjs +48 -0
  11. package/apps/webauto/entry/lib/profilepool.mjs +12 -0
  12. package/apps/webauto/entry/lib/schedule-store.mjs +29 -2
  13. package/apps/webauto/entry/lib/session-init.mjs +227 -0
  14. package/apps/webauto/entry/lib/upgrade-check.mjs +269 -0
  15. package/apps/webauto/entry/lib/xhs-unified-blocks.mjs +160 -0
  16. package/apps/webauto/entry/lib/xhs-unified-output-blocks.mjs +83 -0
  17. package/apps/webauto/entry/lib/xhs-unified-plan-blocks.mjs +55 -0
  18. package/apps/webauto/entry/lib/xhs-unified-profile-blocks.mjs +542 -0
  19. package/apps/webauto/entry/lib/xhs-unified-runtime-blocks.mjs +436 -0
  20. package/apps/webauto/entry/profilepool.mjs +56 -9
  21. package/apps/webauto/entry/smart-reply-cli.mjs +267 -0
  22. package/apps/webauto/entry/weibo-unified.mjs +84 -11
  23. package/apps/webauto/entry/xhs-orchestrate.mjs +43 -1
  24. package/apps/webauto/entry/xhs-unified.mjs +92 -997
  25. package/bin/webauto.mjs +22 -4
  26. package/dist/modules/camo-backend/src/index.js +33 -0
  27. package/dist/modules/camo-backend/src/internal/BrowserSession.js +232 -49
  28. package/dist/modules/camo-backend/src/internal/engine-manager.js +14 -13
  29. package/dist/modules/camo-backend/src/internal/ws-server.js +16 -19
  30. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +38 -6
  31. package/dist/modules/workflow/blocks/EnsureSession.js +0 -8
  32. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +78 -6
  33. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +266 -192
  34. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +2 -0
  35. package/dist/modules/workflow/src/runner.js +2 -0
  36. package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +150 -37
  37. package/dist/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.js +491 -0
  38. package/modules/camo-backend/src/index.ts +31 -0
  39. package/modules/camo-backend/src/internal/BrowserSession.ts +224 -53
  40. package/modules/camo-backend/src/internal/engine-manager.ts +14 -15
  41. package/modules/camo-backend/src/internal/ws-server.ts +17 -17
  42. package/modules/camo-runtime/src/autoscript/action-providers/xhs/common.mjs +12 -2
  43. package/modules/camo-runtime/src/autoscript/action-providers/xhs/persistence.mjs +57 -0
  44. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +2475 -243
  45. package/modules/camo-runtime/src/autoscript/runtime.mjs +35 -30
  46. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +80 -443
  47. package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +39 -6
  48. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +206 -39
  49. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +0 -79
  50. package/modules/camo-runtime/src/container/runtime-core/operations/viewport.mjs +46 -0
  51. package/modules/camo-runtime/src/utils/browser-service.mjs +41 -6
  52. package/modules/camo-runtime/src/utils/js-policy.mjs +28 -0
  53. package/modules/workflow/blocks/EnsureSession.ts +0 -4
  54. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +81 -6
  55. package/modules/workflow/blocks/WeiboCollectSearchLinksBlock.ts +316 -0
  56. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +2 -0
  57. package/modules/workflow/src/runner.ts +2 -0
  58. package/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.ts +198 -53
  59. package/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.ts +706 -0
  60. package/package.json +2 -2
  61. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +0 -498
  62. package/modules/camo-runtime/src/autoscript/action-providers/xhs/detail.mjs +0 -181
  63. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +0 -691
  64. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +0 -388
  65. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +0 -135
@@ -1,388 +0,0 @@
1
- export function buildSubmitSearchScript(params = {}) {
2
- const keyword = String(params.keyword || '').trim();
3
- const method = String(params.method || params.submitMethod || 'click').trim().toLowerCase();
4
- const actionDelayMinMs = Math.max(20, Number(params.actionDelayMinMs ?? 180) || 180);
5
- const actionDelayMaxMs = Math.max(actionDelayMinMs, Number(params.actionDelayMaxMs ?? 620) || 620);
6
- const settleMinMs = Math.max(60, Number(params.settleMinMs ?? 1200) || 1200);
7
- const settleMaxMs = Math.max(settleMinMs, Number(params.settleMaxMs ?? 2600) || 2600);
8
- return `(async () => {
9
- const state = window.__camoXhsState || (window.__camoXhsState = {});
10
- const metrics = state.metrics && typeof state.metrics === 'object' ? state.metrics : {};
11
- state.metrics = metrics;
12
- metrics.searchCount = Number(metrics.searchCount || 0) + 1;
13
- metrics.lastSearchAt = new Date().toISOString();
14
- const actionTrace = [];
15
- const pushTrace = (payload) => {
16
- actionTrace.push({
17
- ts: new Date().toISOString(),
18
- ...payload,
19
- });
20
- };
21
- const input = document.querySelector('#search-input, input.search-input');
22
- if (!(input instanceof HTMLInputElement)) {
23
- throw new Error('SEARCH_INPUT_NOT_FOUND');
24
- }
25
- const randomBetween = (min, max) => {
26
- const lo = Math.max(0, Math.floor(Number(min) || 0));
27
- const hi = Math.max(lo, Math.floor(Number(max) || 0));
28
- if (hi <= lo) return lo;
29
- return lo + Math.floor(Math.random() * (hi - lo + 1));
30
- };
31
- const waitRandom = async (min, max, stage) => {
32
- const waitMs = randomBetween(min, max);
33
- pushTrace({ kind: 'wait', stage, waitMs });
34
- await new Promise((resolve) => setTimeout(resolve, waitMs));
35
- return waitMs;
36
- };
37
- const targetKeyword = ${JSON.stringify(keyword)};
38
- if (targetKeyword && input.value !== targetKeyword) {
39
- input.focus();
40
- input.value = targetKeyword;
41
- input.dispatchEvent(new Event('input', { bubbles: true }));
42
- input.dispatchEvent(new Event('change', { bubbles: true }));
43
- }
44
- const requestedMethod = ${JSON.stringify(method)};
45
- const normalizedMethod = ['click', 'enter', 'form'].includes(requestedMethod) ? requestedMethod : 'click';
46
- const beforeUrl = window.location.href;
47
- input.focus();
48
- const candidates = ['.input-button .search-icon', '.input-button', 'button.min-width-search-icon'];
49
- let methodUsed = normalizedMethod;
50
- let clickedSelector = null;
51
- const form = input.closest('form');
52
- if (normalizedMethod === 'click') {
53
- let clicked = false;
54
- for (const selector of candidates) {
55
- const button = document.querySelector(selector);
56
- if (!button) continue;
57
- if (button instanceof HTMLElement) {
58
- pushTrace({ kind: 'scroll', stage: 'submit_search', selector, via: 'scrollIntoView' });
59
- button.scrollIntoView({ behavior: 'auto', block: 'center' });
60
- }
61
- await waitRandom(${actionDelayMinMs}, ${actionDelayMaxMs}, 'submit_pre_click');
62
- pushTrace({ kind: 'click', stage: 'submit_search', selector });
63
- button.click();
64
- clickedSelector = selector;
65
- clicked = true;
66
- break;
67
- }
68
- if (!clicked) {
69
- methodUsed = 'form';
70
- }
71
- }
72
- if (methodUsed === 'enter') {
73
- await waitRandom(${actionDelayMinMs}, ${actionDelayMaxMs}, 'submit_pre_enter');
74
- const enterEvent = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true };
75
- pushTrace({ kind: 'key', stage: 'submit_search', key: 'Enter' });
76
- input.dispatchEvent(new KeyboardEvent('keydown', enterEvent));
77
- input.dispatchEvent(new KeyboardEvent('keypress', enterEvent));
78
- input.dispatchEvent(new KeyboardEvent('keyup', enterEvent));
79
- } else if (methodUsed === 'form') {
80
- await waitRandom(${actionDelayMinMs}, ${actionDelayMaxMs}, 'submit_pre_form');
81
- if (form) {
82
- pushTrace({ kind: 'submit', stage: 'submit_search', via: 'form' });
83
- if (typeof form.requestSubmit === 'function') form.requestSubmit();
84
- else form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
85
- } else {
86
- methodUsed = 'enter';
87
- const enterEvent = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true };
88
- pushTrace({ kind: 'key', stage: 'submit_search', key: 'Enter', fallback: true });
89
- input.dispatchEvent(new KeyboardEvent('keydown', enterEvent));
90
- input.dispatchEvent(new KeyboardEvent('keypress', enterEvent));
91
- input.dispatchEvent(new KeyboardEvent('keyup', enterEvent));
92
- }
93
- }
94
- await waitRandom(${settleMinMs}, ${settleMaxMs}, 'submit_settle');
95
- return {
96
- submitted: true,
97
- via: clickedSelector || methodUsed,
98
- beforeUrl,
99
- afterUrl: window.location.href,
100
- method: methodUsed,
101
- searchCount: metrics.searchCount,
102
- actionTrace,
103
- };
104
- })()`;
105
- }
106
-
107
- export function buildOpenDetailScript(params = {}) {
108
- const mode = String(params.mode || 'first').trim().toLowerCase();
109
- const maxNotes = Math.max(1, Number(params.maxNotes ?? params.limit ?? 20) || 20);
110
- const keyword = String(params.keyword || '').trim();
111
- const resume = params.resume !== false;
112
- const incrementalMax = params.incrementalMax !== false;
113
- const excludeNoteIds = Array.isArray(params.excludeNoteIds)
114
- ? params.excludeNoteIds.map((item) => String(item || '').trim()).filter(Boolean)
115
- : [];
116
- const seedCollectCount = Math.max(0, Number(params.seedCollectCount || 0) || 0);
117
- const seedCollectMaxRounds = Math.max(0, Number(params.seedCollectMaxRounds || 0) || 0);
118
- const seedCollectStep = Math.max(120, Number(params.seedCollectStep || 360) || 360);
119
- const seedCollectSettleMs = Math.max(100, Number(params.seedCollectSettleMs || 260) || 260);
120
- const seedResetToTop = params.seedResetToTop !== false;
121
- const nextSeekRounds = Math.max(0, Number(params.nextSeekRounds || 8) || 8);
122
- const nextSeekStep = Math.max(0, Number(params.nextSeekStep || 0) || 0);
123
- const nextSeekSettleMs = Math.max(120, Number(params.nextSeekSettleMs || 320) || 320);
124
- const preClickDelayMinMs = Math.max(60, Number(params.preClickDelayMinMs ?? 220) || 220);
125
- const preClickDelayMaxMs = Math.max(preClickDelayMinMs, Number(params.preClickDelayMaxMs ?? 700) || 700);
126
- const pollDelayMinMs = Math.max(80, Number(params.pollDelayMinMs ?? 130) || 130);
127
- const pollDelayMaxMs = Math.max(pollDelayMinMs, Number(params.pollDelayMaxMs ?? 320) || 320);
128
- const postOpenDelayMinMs = Math.max(120, Number(params.postOpenDelayMinMs ?? 420) || 420);
129
- const postOpenDelayMaxMs = Math.max(postOpenDelayMinMs, Number(params.postOpenDelayMaxMs ?? 1100) || 1100);
130
-
131
- return `(async () => {
132
- const STATE_KEY = '__camoXhsState';
133
- const randomBetween = (min, max) => {
134
- const lo = Math.max(0, Math.floor(Number(min) || 0));
135
- const hi = Math.max(lo, Math.floor(Number(max) || 0));
136
- if (hi <= lo) return lo;
137
- return lo + Math.floor(Math.random() * (hi - lo + 1));
138
- };
139
- const normalizeVisited = (value) => {
140
- if (!Array.isArray(value)) return [];
141
- return value
142
- .map((item) => String(item || '').trim())
143
- .filter(Boolean);
144
- };
145
- const mergeVisited = (a, b) => Array.from(new Set([
146
- ...normalizeVisited(a),
147
- ...normalizeVisited(b),
148
- ]));
149
- const loadState = () => {
150
- const inMemory = window.__camoXhsState && typeof window.__camoXhsState === 'object' ? window.__camoXhsState : {};
151
- try {
152
- const stored = localStorage.getItem(STATE_KEY);
153
- if (!stored) return { ...inMemory };
154
- const parsed = JSON.parse(stored);
155
- if (!parsed || typeof parsed !== 'object') return { ...inMemory };
156
- const merged = { ...inMemory, ...parsed };
157
- merged.visitedNoteIds = mergeVisited(parsed.visitedNoteIds, inMemory.visitedNoteIds);
158
- return merged;
159
- } catch {
160
- return { ...inMemory };
161
- }
162
- };
163
- const saveState = (nextState) => {
164
- window.__camoXhsState = nextState;
165
- try { localStorage.setItem(STATE_KEY, JSON.stringify(nextState)); } catch {}
166
- };
167
-
168
- const state = loadState();
169
- const actionTrace = [];
170
- const pushTrace = (payload) => {
171
- actionTrace.push({
172
- ts: new Date().toISOString(),
173
- ...payload,
174
- });
175
- };
176
- if (!Array.isArray(state.visitedNoteIds)) state.visitedNoteIds = [];
177
- const requestedKeyword = ${JSON.stringify(keyword)};
178
- const mode = ${JSON.stringify(mode)};
179
- const previousKeyword = String(state.keyword || '').trim();
180
- const keywordChanged = Boolean(requestedKeyword && previousKeyword && requestedKeyword !== previousKeyword);
181
- if (mode === 'first') {
182
- if (!${resume ? 'true' : 'false'} || keywordChanged) {
183
- state.visitedNoteIds = [];
184
- }
185
- } else if (keywordChanged) {
186
- state.visitedNoteIds = [];
187
- }
188
- const requestedMaxNotes = Number(${maxNotes});
189
- if (mode === 'first') {
190
- if (${incrementalMax ? 'true' : 'false'} && ${resume ? 'true' : 'false'} && !keywordChanged) {
191
- state.maxNotes = Number(state.visitedNoteIds.length || 0) + requestedMaxNotes;
192
- } else {
193
- state.maxNotes = requestedMaxNotes;
194
- }
195
- } else if (!Number.isFinite(Number(state.maxNotes)) || Number(state.maxNotes) <= 0) {
196
- state.maxNotes = requestedMaxNotes;
197
- }
198
- if (requestedKeyword) state.keyword = requestedKeyword;
199
-
200
- if (mode === 'next' && state.visitedNoteIds.length >= state.maxNotes) {
201
- throw new Error('AUTOSCRIPT_DONE_MAX_NOTES');
202
- }
203
-
204
- const excludedNoteIds = new Set(${JSON.stringify(excludeNoteIds)});
205
- const mapNodes = () => Array.from(document.querySelectorAll('.note-item'))
206
- .map((item, index) => {
207
- const cover = item.querySelector('a.cover');
208
- if (!cover) return null;
209
- const href = String(cover.getAttribute('href') || '').trim();
210
- const lastSegment = href.split('/').filter(Boolean).pop() || '';
211
- const normalized = lastSegment.split('?')[0].split('#')[0];
212
- const noteId = normalized || ('idx_' + index);
213
- return { cover, href, noteId };
214
- })
215
- .filter(Boolean);
216
- let nodes = mapNodes();
217
- const seedCollectedSet = new Set();
218
- const seedCollectEnabled = mode === 'first'
219
- && Number(${seedCollectCount}) > 0
220
- && Number(${seedCollectMaxRounds}) > 0;
221
- if (seedCollectEnabled) {
222
- const collectVisible = () => {
223
- for (const row of mapNodes()) {
224
- if (!row || !row.noteId) continue;
225
- seedCollectedSet.add(row.noteId);
226
- }
227
- };
228
- collectVisible();
229
- const maxRounds = Number(${seedCollectMaxRounds});
230
- const targetCount = Number(${seedCollectCount});
231
- for (let round = 0; round < maxRounds && seedCollectedSet.size < targetCount; round += 1) {
232
- pushTrace({
233
- kind: 'scroll',
234
- stage: 'seed_collect',
235
- round: round + 1,
236
- deltaY: Number(${seedCollectStep}),
237
- });
238
- window.scrollBy({ top: Number(${seedCollectStep}), left: 0, behavior: 'auto' });
239
- await new Promise((resolve) => setTimeout(resolve, Number(${seedCollectSettleMs})));
240
- collectVisible();
241
- }
242
- if (${seedResetToTop ? 'true' : 'false'}) {
243
- pushTrace({
244
- kind: 'scroll',
245
- stage: 'seed_collect',
246
- round: 'reset_to_top',
247
- toTop: true,
248
- });
249
- window.scrollTo({ top: 0, behavior: 'auto' });
250
- await new Promise((resolve) => setTimeout(resolve, Number(${seedCollectSettleMs})));
251
- }
252
- nodes = mapNodes();
253
- }
254
- if (nodes.length === 0) throw new Error('NO_SEARCH_RESULT_ITEM');
255
-
256
- const isEligible = (row) => (
257
- row
258
- && row.noteId
259
- && !excludedNoteIds.has(row.noteId)
260
- && !state.visitedNoteIds.includes(row.noteId)
261
- );
262
- const resolveSeekStep = () => {
263
- const configured = Number(${nextSeekStep});
264
- if (Number.isFinite(configured) && configured > 0) return configured;
265
- const viewportHeight = Math.max(
266
- Number(window.innerHeight || 0) || 0,
267
- Number(document.documentElement?.clientHeight || 0) || 0,
268
- );
269
- return Math.max(240, Math.floor(viewportHeight * 0.9));
270
- };
271
- const seekStep = resolveSeekStep();
272
-
273
- let next = nodes.find((row) => isEligible(row));
274
- if (!next) {
275
- let stagnantRounds = 0;
276
- for (let round = 0; !next && round < Number(${nextSeekRounds}); round += 1) {
277
- const beforeTop = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
278
- pushTrace({
279
- kind: 'scroll',
280
- stage: 'seek_next_detail',
281
- round: round + 1,
282
- deltaY: seekStep,
283
- });
284
- window.scrollBy({ top: seekStep, left: 0, behavior: 'auto' });
285
- await new Promise((resolve) => setTimeout(resolve, Number(${nextSeekSettleMs})));
286
- nodes = mapNodes();
287
- next = nodes.find((row) => isEligible(row));
288
- const afterTop = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
289
- if (Math.abs(afterTop - beforeTop) < 2) stagnantRounds += 1;
290
- else stagnantRounds = 0;
291
- if (stagnantRounds >= 2) break;
292
- }
293
- }
294
- if (!next) throw new Error('AUTOSCRIPT_DONE_NO_MORE_NOTES');
295
-
296
- const detailSelectors = [
297
- '.note-detail-mask',
298
- '.note-detail-page',
299
- '.note-detail-dialog',
300
- '.note-detail-mask .detail-container',
301
- '.note-detail-mask .media-container',
302
- '.note-detail-mask .note-scroller',
303
- '.note-detail-mask .note-content',
304
- '.note-detail-mask .interaction-container',
305
- '.note-detail-mask .comments-container',
306
- ];
307
- const searchSelectors = [
308
- '.note-item',
309
- '.search-result-list',
310
- '#search-input',
311
- '.feeds-page',
312
- ];
313
- const isVisible = (node) => {
314
- if (!node || !(node instanceof HTMLElement)) return false;
315
- const style = window.getComputedStyle(node);
316
- if (!style) return false;
317
- if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') === 0) return false;
318
- const rect = node.getBoundingClientRect();
319
- return rect.width > 1 && rect.height > 1;
320
- };
321
- const hasVisible = (selectors) => selectors.some((selector) => isVisible(document.querySelector(selector)));
322
- const isDetailReady = () => {
323
- const detailVisible = hasVisible(detailSelectors);
324
- if (!detailVisible) return false;
325
- const href = String(window.location.href || '');
326
- const isDetailUrl = href.includes('/explore/') && !href.includes('/search_result');
327
- if (isDetailUrl) return true;
328
- const searchVisible = hasVisible(searchSelectors);
329
- return !searchVisible;
330
- };
331
-
332
- pushTrace({
333
- kind: 'scroll',
334
- stage: 'open_detail',
335
- noteId: next.noteId,
336
- via: 'scrollIntoView',
337
- });
338
- next.cover.scrollIntoView({ behavior: 'auto', block: 'center' });
339
- const preClickDelay = randomBetween(${preClickDelayMinMs}, ${preClickDelayMaxMs});
340
- pushTrace({ kind: 'wait', stage: 'open_detail_pre_click', noteId: next.noteId, waitMs: preClickDelay });
341
- await new Promise((resolve) => setTimeout(resolve, preClickDelay));
342
- const beforeUrl = window.location.href;
343
- pushTrace({
344
- kind: 'click',
345
- stage: 'open_detail',
346
- noteId: next.noteId,
347
- selector: 'a.cover',
348
- });
349
- next.cover.click();
350
-
351
- let detailReady = false;
352
- for (let i = 0; i < 60; i += 1) {
353
- if (isDetailReady()) {
354
- detailReady = true;
355
- break;
356
- }
357
- const pollDelay = randomBetween(${pollDelayMinMs}, ${pollDelayMaxMs});
358
- await new Promise((resolve) => setTimeout(resolve, pollDelay));
359
- }
360
- if (!detailReady) {
361
- throw new Error('DETAIL_OPEN_TIMEOUT');
362
- }
363
- const postOpenDelay = randomBetween(${postOpenDelayMinMs}, ${postOpenDelayMaxMs});
364
- pushTrace({ kind: 'wait', stage: 'open_detail_post_open', noteId: next.noteId, waitMs: postOpenDelay });
365
- await new Promise((resolve) => setTimeout(resolve, postOpenDelay));
366
- const afterUrl = window.location.href;
367
-
368
- if (!state.visitedNoteIds.includes(next.noteId)) state.visitedNoteIds.push(next.noteId);
369
- state.currentNoteId = next.noteId;
370
- state.currentHref = next.href;
371
- state.lastListUrl = beforeUrl;
372
- saveState(state);
373
- return {
374
- opened: true,
375
- source: mode === 'next' ? 'open_next_detail' : 'open_first_detail',
376
- noteId: next.noteId,
377
- visited: state.visitedNoteIds.length,
378
- maxNotes: state.maxNotes,
379
- openByClick: true,
380
- beforeUrl,
381
- afterUrl,
382
- excludedCount: excludedNoteIds.size,
383
- seedCollectedCount: seedCollectedSet.size,
384
- seedCollectedNoteIds: Array.from(seedCollectedSet),
385
- actionTrace,
386
- };
387
- })()`;
388
- }
@@ -1,135 +0,0 @@
1
- function asBoolLiteral(value) {
2
- return value ? 'true' : 'false';
3
- }
4
-
5
- export function buildSelectorScrollIntoViewScript({ selector, highlight }) {
6
- const selectorLiteral = JSON.stringify(selector);
7
- const highlightLiteral = asBoolLiteral(highlight);
8
- return `(async () => {
9
- const el = document.querySelector(${selectorLiteral});
10
- if (!el) throw new Error('Element not found: ' + ${selectorLiteral});
11
- const restoreOutline = el instanceof HTMLElement ? el.style.outline : '';
12
- if (${highlightLiteral} && el instanceof HTMLElement) {
13
- el.style.outline = '2px solid #ff4d4f';
14
- }
15
- el.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'nearest' });
16
- await new Promise((r) => setTimeout(r, 120));
17
- if (${highlightLiteral} && el instanceof HTMLElement) {
18
- el.style.outline = restoreOutline;
19
- }
20
- return { ok: true, selector: ${selectorLiteral} };
21
- })()`;
22
- }
23
-
24
- export function buildSelectorClickScript({ selector, highlight }) {
25
- const selectorLiteral = JSON.stringify(selector);
26
- const highlightLiteral = asBoolLiteral(highlight);
27
- return `(async () => {
28
- const el = document.querySelector(${selectorLiteral});
29
- if (!el) throw new Error('Element not found: ' + ${selectorLiteral});
30
- const restoreOutline = el instanceof HTMLElement ? el.style.outline : '';
31
- if (${highlightLiteral} && el instanceof HTMLElement) {
32
- el.style.outline = '2px solid #ff4d4f';
33
- }
34
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
35
- await new Promise((r) => setTimeout(r, 150));
36
- if (el instanceof HTMLElement) {
37
- try { el.focus({ preventScroll: true }); } catch {}
38
- const common = { bubbles: true, cancelable: true, view: window };
39
- try {
40
- if (typeof PointerEvent === 'function') {
41
- el.dispatchEvent(new PointerEvent('pointerdown', { ...common, pointerType: 'mouse', button: 0 }));
42
- el.dispatchEvent(new PointerEvent('pointerup', { ...common, pointerType: 'mouse', button: 0 }));
43
- }
44
- } catch {}
45
- try { el.dispatchEvent(new MouseEvent('mousedown', { ...common, button: 0 })); } catch {}
46
- try { el.dispatchEvent(new MouseEvent('mouseup', { ...common, button: 0 })); } catch {}
47
- }
48
- if (typeof el.click === 'function') el.click();
49
- else el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window, button: 0 }));
50
- if (${highlightLiteral} && el instanceof HTMLElement) {
51
- setTimeout(() => { el.style.outline = restoreOutline; }, 260);
52
- }
53
- return { ok: true, selector: ${selectorLiteral}, action: 'click', highlight: ${highlightLiteral} };
54
- })()`;
55
- }
56
-
57
- export function buildSelectorTypeScript({ selector, highlight, text }) {
58
- const selectorLiteral = JSON.stringify(selector);
59
- const highlightLiteral = asBoolLiteral(highlight);
60
- const textLiteral = JSON.stringify(String(text || ''));
61
- const textLength = String(text || '').length;
62
-
63
- return `(async () => {
64
- const el = document.querySelector(${selectorLiteral});
65
- if (!el) throw new Error('Element not found: ' + ${selectorLiteral});
66
- const restoreOutline = el instanceof HTMLElement ? el.style.outline : '';
67
- if (${highlightLiteral} && el instanceof HTMLElement) {
68
- el.style.outline = '2px solid #ff4d4f';
69
- }
70
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
71
- await new Promise((r) => setTimeout(r, 150));
72
- if (el instanceof HTMLElement) {
73
- try { el.focus({ preventScroll: true }); } catch {}
74
- if (typeof el.click === 'function') el.click();
75
- }
76
- const value = ${textLiteral};
77
- const fireInputEvent = (target, name, init) => {
78
- try {
79
- if (typeof InputEvent === 'function') {
80
- target.dispatchEvent(new InputEvent(name, init));
81
- return;
82
- }
83
- } catch {}
84
- target.dispatchEvent(new Event(name, { bubbles: true, cancelable: init?.cancelable === true }));
85
- };
86
- const assignControlValue = (target, next) => {
87
- if (target instanceof HTMLInputElement) {
88
- const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
89
- if (setter) setter.call(target, next);
90
- else target.value = next;
91
- if (typeof target.setSelectionRange === 'function') {
92
- const cursor = String(next).length;
93
- try { target.setSelectionRange(cursor, cursor); } catch {}
94
- }
95
- return true;
96
- }
97
- if (target instanceof HTMLTextAreaElement) {
98
- const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
99
- if (setter) setter.call(target, next);
100
- else target.value = next;
101
- if (typeof target.setSelectionRange === 'function') {
102
- const cursor = String(next).length;
103
- try { target.setSelectionRange(cursor, cursor); } catch {}
104
- }
105
- return true;
106
- }
107
- return false;
108
- };
109
- const editableAssigned = assignControlValue(el, value);
110
- if (!editableAssigned) {
111
- if (el instanceof HTMLElement && el.isContentEditable) {
112
- el.textContent = value;
113
- } else {
114
- throw new Error('Element not editable: ' + ${selectorLiteral});
115
- }
116
- }
117
- fireInputEvent(el, 'beforeinput', {
118
- bubbles: true,
119
- cancelable: true,
120
- data: value,
121
- inputType: 'insertText',
122
- });
123
- fireInputEvent(el, 'input', {
124
- bubbles: true,
125
- cancelable: false,
126
- data: value,
127
- inputType: 'insertText',
128
- });
129
- el.dispatchEvent(new Event('change', { bubbles: true }));
130
- if (${highlightLiteral} && el instanceof HTMLElement) {
131
- setTimeout(() => { el.style.outline = restoreOutline; }, 260);
132
- }
133
- return { ok: true, selector: ${selectorLiteral}, action: 'type', length: ${textLength}, highlight: ${highlightLiteral} };
134
- })()`;
135
- }