@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.
- package/README.md +122 -53
- package/apps/desktop-console/dist/main/index.mjs +227 -12
- package/apps/desktop-console/dist/renderer/index.js +237 -8
- package/apps/desktop-console/entry/ui-cli.mjs +282 -16
- package/apps/desktop-console/entry/ui-console.mjs +46 -15
- package/apps/webauto/entry/account.mjs +126 -27
- package/apps/webauto/entry/lib/account-detect.mjs +399 -9
- package/apps/webauto/entry/lib/account-store.mjs +201 -109
- package/apps/webauto/entry/lib/iflow-reply.mjs +194 -0
- package/apps/webauto/entry/lib/profile-policy.mjs +48 -0
- package/apps/webauto/entry/lib/profilepool.mjs +12 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +29 -2
- package/apps/webauto/entry/lib/session-init.mjs +227 -0
- package/apps/webauto/entry/lib/upgrade-check.mjs +269 -0
- package/apps/webauto/entry/lib/xhs-unified-blocks.mjs +160 -0
- package/apps/webauto/entry/lib/xhs-unified-output-blocks.mjs +83 -0
- package/apps/webauto/entry/lib/xhs-unified-plan-blocks.mjs +55 -0
- package/apps/webauto/entry/lib/xhs-unified-profile-blocks.mjs +542 -0
- package/apps/webauto/entry/lib/xhs-unified-runtime-blocks.mjs +436 -0
- package/apps/webauto/entry/profilepool.mjs +56 -9
- package/apps/webauto/entry/smart-reply-cli.mjs +267 -0
- package/apps/webauto/entry/weibo-unified.mjs +84 -11
- package/apps/webauto/entry/xhs-orchestrate.mjs +43 -1
- package/apps/webauto/entry/xhs-unified.mjs +92 -997
- package/bin/webauto.mjs +22 -4
- package/dist/modules/camo-backend/src/index.js +33 -0
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +232 -49
- package/dist/modules/camo-backend/src/internal/engine-manager.js +14 -13
- package/dist/modules/camo-backend/src/internal/ws-server.js +16 -19
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +38 -6
- package/dist/modules/workflow/blocks/EnsureSession.js +0 -8
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +78 -6
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +266 -192
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +2 -0
- package/dist/modules/workflow/src/runner.js +2 -0
- package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +150 -37
- package/dist/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.js +491 -0
- package/modules/camo-backend/src/index.ts +31 -0
- package/modules/camo-backend/src/internal/BrowserSession.ts +224 -53
- package/modules/camo-backend/src/internal/engine-manager.ts +14 -15
- package/modules/camo-backend/src/internal/ws-server.ts +17 -17
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/common.mjs +12 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/persistence.mjs +57 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +2475 -243
- package/modules/camo-runtime/src/autoscript/runtime.mjs +35 -30
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +80 -443
- package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +39 -6
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +206 -39
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +0 -79
- package/modules/camo-runtime/src/container/runtime-core/operations/viewport.mjs +46 -0
- package/modules/camo-runtime/src/utils/browser-service.mjs +41 -6
- package/modules/camo-runtime/src/utils/js-policy.mjs +28 -0
- package/modules/workflow/blocks/EnsureSession.ts +0 -4
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +81 -6
- package/modules/workflow/blocks/WeiboCollectSearchLinksBlock.ts +316 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +2 -0
- package/modules/workflow/src/runner.ts +2 -0
- package/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.ts +198 -53
- package/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.ts +706 -0
- package/package.json +2 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +0 -498
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/detail.mjs +0 -181
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +0 -691
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +0 -388
- package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +0 -135
|
@@ -39,433 +39,14 @@ function splitCsv(value) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function pickCloseDependency(options) {
|
|
42
|
+
if (options.doReply) return 'comment_reply';
|
|
43
|
+
if (options.doLikes) return 'comment_like';
|
|
44
|
+
if (options.matchGateEnabled) return 'comment_match_gate';
|
|
45
|
+
if (options.commentsHarvestEnabled) return 'comments_harvest';
|
|
42
46
|
if (options.detailHarvestEnabled) return 'detail_harvest';
|
|
43
47
|
return 'open_first_detail';
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
function buildOpenFirstDetailScript(maxNotes, keyword) {
|
|
47
|
-
return `(async () => {
|
|
48
|
-
const STATE_KEY = '__camoXhsState';
|
|
49
|
-
const loadState = () => {
|
|
50
|
-
const inMemory = window.__camoXhsState && typeof window.__camoXhsState === 'object'
|
|
51
|
-
? window.__camoXhsState
|
|
52
|
-
: {};
|
|
53
|
-
try {
|
|
54
|
-
const stored = localStorage.getItem(STATE_KEY);
|
|
55
|
-
if (!stored) return { ...inMemory };
|
|
56
|
-
const parsed = JSON.parse(stored);
|
|
57
|
-
if (!parsed || typeof parsed !== 'object') return { ...inMemory };
|
|
58
|
-
return { ...parsed, ...inMemory };
|
|
59
|
-
} catch {
|
|
60
|
-
return { ...inMemory };
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
const saveState = (nextState) => {
|
|
64
|
-
window.__camoXhsState = nextState;
|
|
65
|
-
try {
|
|
66
|
-
localStorage.setItem(STATE_KEY, JSON.stringify(nextState));
|
|
67
|
-
} catch {}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const state = loadState();
|
|
71
|
-
if (!Array.isArray(state.visitedNoteIds)) state.visitedNoteIds = [];
|
|
72
|
-
state.maxNotes = Number(${maxNotes});
|
|
73
|
-
state.keyword = ${JSON.stringify(keyword)};
|
|
74
|
-
|
|
75
|
-
const nodes = Array.from(document.querySelectorAll('.note-item'))
|
|
76
|
-
.map((item, index) => {
|
|
77
|
-
const cover = item.querySelector('a.cover');
|
|
78
|
-
if (!cover) return null;
|
|
79
|
-
const href = String(cover.getAttribute('href') || '').trim();
|
|
80
|
-
const lastSegment = href.split('/').filter(Boolean).pop() || '';
|
|
81
|
-
const normalized = lastSegment.split('?')[0].split('#')[0];
|
|
82
|
-
const noteId = normalized || ('idx_' + index);
|
|
83
|
-
return { item, cover, href, noteId };
|
|
84
|
-
})
|
|
85
|
-
.filter(Boolean);
|
|
86
|
-
|
|
87
|
-
if (nodes.length === 0) {
|
|
88
|
-
throw new Error('NO_SEARCH_RESULT_ITEM');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const next = nodes.find((row) => !state.visitedNoteIds.includes(row.noteId)) || nodes[0];
|
|
92
|
-
next.cover.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
93
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
94
|
-
next.cover.click();
|
|
95
|
-
if (!state.visitedNoteIds.includes(next.noteId)) state.visitedNoteIds.push(next.noteId);
|
|
96
|
-
state.currentNoteId = next.noteId;
|
|
97
|
-
state.currentHref = next.href;
|
|
98
|
-
saveState(state);
|
|
99
|
-
return {
|
|
100
|
-
opened: true,
|
|
101
|
-
source: 'open_first_detail',
|
|
102
|
-
noteId: next.noteId,
|
|
103
|
-
visited: state.visitedNoteIds.length,
|
|
104
|
-
maxNotes: state.maxNotes,
|
|
105
|
-
};
|
|
106
|
-
})()`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function buildOpenNextDetailScript(maxNotes) {
|
|
110
|
-
return `(async () => {
|
|
111
|
-
const STATE_KEY = '__camoXhsState';
|
|
112
|
-
const loadState = () => {
|
|
113
|
-
const inMemory = window.__camoXhsState && typeof window.__camoXhsState === 'object'
|
|
114
|
-
? window.__camoXhsState
|
|
115
|
-
: {};
|
|
116
|
-
try {
|
|
117
|
-
const stored = localStorage.getItem(STATE_KEY);
|
|
118
|
-
if (!stored) return { ...inMemory };
|
|
119
|
-
const parsed = JSON.parse(stored);
|
|
120
|
-
if (!parsed || typeof parsed !== 'object') return { ...inMemory };
|
|
121
|
-
return { ...parsed, ...inMemory };
|
|
122
|
-
} catch {
|
|
123
|
-
return { ...inMemory };
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const saveState = (nextState) => {
|
|
127
|
-
window.__camoXhsState = nextState;
|
|
128
|
-
try {
|
|
129
|
-
localStorage.setItem(STATE_KEY, JSON.stringify(nextState));
|
|
130
|
-
} catch {}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const state = loadState();
|
|
134
|
-
if (!Array.isArray(state.visitedNoteIds)) state.visitedNoteIds = [];
|
|
135
|
-
state.maxNotes = Number(${maxNotes});
|
|
136
|
-
|
|
137
|
-
if (state.visitedNoteIds.length >= state.maxNotes) {
|
|
138
|
-
throw new Error('AUTOSCRIPT_DONE_MAX_NOTES');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const nodes = Array.from(document.querySelectorAll('.note-item'))
|
|
142
|
-
.map((item, index) => {
|
|
143
|
-
const cover = item.querySelector('a.cover');
|
|
144
|
-
if (!cover) return null;
|
|
145
|
-
const href = String(cover.getAttribute('href') || '').trim();
|
|
146
|
-
const lastSegment = href.split('/').filter(Boolean).pop() || '';
|
|
147
|
-
const normalized = lastSegment.split('?')[0].split('#')[0];
|
|
148
|
-
const noteId = normalized || ('idx_' + index);
|
|
149
|
-
return { item, cover, href, noteId };
|
|
150
|
-
})
|
|
151
|
-
.filter(Boolean);
|
|
152
|
-
|
|
153
|
-
const next = nodes.find((row) => !state.visitedNoteIds.includes(row.noteId));
|
|
154
|
-
if (!next) {
|
|
155
|
-
throw new Error('AUTOSCRIPT_DONE_NO_MORE_NOTES');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
next.cover.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
159
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
160
|
-
next.cover.click();
|
|
161
|
-
state.visitedNoteIds.push(next.noteId);
|
|
162
|
-
state.currentNoteId = next.noteId;
|
|
163
|
-
state.currentHref = next.href;
|
|
164
|
-
saveState(state);
|
|
165
|
-
return {
|
|
166
|
-
opened: true,
|
|
167
|
-
source: 'open_next_detail',
|
|
168
|
-
noteId: next.noteId,
|
|
169
|
-
visited: state.visitedNoteIds.length,
|
|
170
|
-
maxNotes: state.maxNotes,
|
|
171
|
-
};
|
|
172
|
-
})()`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function buildSubmitSearchScript(keyword) {
|
|
176
|
-
return `(async () => {
|
|
177
|
-
const input = document.querySelector('#search-input, input.search-input');
|
|
178
|
-
if (!(input instanceof HTMLInputElement)) {
|
|
179
|
-
throw new Error('SEARCH_INPUT_NOT_FOUND');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const targetKeyword = ${JSON.stringify(keyword)};
|
|
183
|
-
if (targetKeyword && input.value !== targetKeyword) {
|
|
184
|
-
input.focus();
|
|
185
|
-
input.value = targetKeyword;
|
|
186
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
187
|
-
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const enterEvent = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true };
|
|
191
|
-
const beforeUrl = window.location.href;
|
|
192
|
-
input.focus();
|
|
193
|
-
input.dispatchEvent(new KeyboardEvent('keydown', enterEvent));
|
|
194
|
-
input.dispatchEvent(new KeyboardEvent('keypress', enterEvent));
|
|
195
|
-
input.dispatchEvent(new KeyboardEvent('keyup', enterEvent));
|
|
196
|
-
|
|
197
|
-
const candidates = [
|
|
198
|
-
'.input-button .search-icon',
|
|
199
|
-
'.input-button',
|
|
200
|
-
'button.min-width-search-icon',
|
|
201
|
-
];
|
|
202
|
-
let clickedSelector = null;
|
|
203
|
-
for (const selector of candidates) {
|
|
204
|
-
const button = document.querySelector(selector);
|
|
205
|
-
if (!button) continue;
|
|
206
|
-
if (button instanceof HTMLElement) {
|
|
207
|
-
button.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
208
|
-
}
|
|
209
|
-
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
210
|
-
button.click();
|
|
211
|
-
clickedSelector = selector;
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const form = input.closest('form');
|
|
216
|
-
if (form) {
|
|
217
|
-
if (typeof form.requestSubmit === 'function') form.requestSubmit();
|
|
218
|
-
else form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
await new Promise((resolve) => setTimeout(resolve, 320));
|
|
222
|
-
return {
|
|
223
|
-
submitted: true,
|
|
224
|
-
via: clickedSelector || 'enter_or_form_submit',
|
|
225
|
-
beforeUrl,
|
|
226
|
-
afterUrl: window.location.href,
|
|
227
|
-
};
|
|
228
|
-
})()`;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function buildDetailHarvestScript() {
|
|
232
|
-
return `(async () => {
|
|
233
|
-
const state = window.__camoXhsState || (window.__camoXhsState = {});
|
|
234
|
-
const scroller = document.querySelector('.note-scroller')
|
|
235
|
-
|| document.querySelector('.comments-el')
|
|
236
|
-
|| document.scrollingElement
|
|
237
|
-
|| document.documentElement;
|
|
238
|
-
for (let i = 0; i < 3; i += 1) {
|
|
239
|
-
scroller.scrollBy({ top: 360, behavior: 'auto' });
|
|
240
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
241
|
-
}
|
|
242
|
-
const title = (document.querySelector('.note-title') || {}).textContent || '';
|
|
243
|
-
const content = (document.querySelector('.note-content') || {}).textContent || '';
|
|
244
|
-
state.lastDetail = {
|
|
245
|
-
title: String(title).trim().slice(0, 200),
|
|
246
|
-
contentLength: String(content).trim().length,
|
|
247
|
-
capturedAt: new Date().toISOString(),
|
|
248
|
-
};
|
|
249
|
-
return { harvested: true, detail: state.lastDetail };
|
|
250
|
-
})()`;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function buildExpandRepliesScript() {
|
|
254
|
-
return `(async () => {
|
|
255
|
-
const buttons = Array.from(document.querySelectorAll('.show-more, .reply-expand, [class*="expand"]'));
|
|
256
|
-
let clicked = 0;
|
|
257
|
-
for (const button of buttons.slice(0, 8)) {
|
|
258
|
-
if (!(button instanceof HTMLElement)) continue;
|
|
259
|
-
const text = (button.textContent || '').trim();
|
|
260
|
-
if (!text) continue;
|
|
261
|
-
button.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
262
|
-
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
263
|
-
button.click();
|
|
264
|
-
clicked += 1;
|
|
265
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
266
|
-
}
|
|
267
|
-
return { expanded: clicked, scanned: buttons.length };
|
|
268
|
-
})()`;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function buildCommentsHarvestScript() {
|
|
272
|
-
return `(async () => {
|
|
273
|
-
const state = window.__camoXhsState || (window.__camoXhsState = {});
|
|
274
|
-
const comments = Array.from(document.querySelectorAll('.comment-item'))
|
|
275
|
-
.map((item, index) => {
|
|
276
|
-
const textNode = item.querySelector('.content, .comment-content, p');
|
|
277
|
-
const likeNode = item.querySelector('.like-wrapper');
|
|
278
|
-
return {
|
|
279
|
-
index,
|
|
280
|
-
text: String((textNode && textNode.textContent) || '').trim(),
|
|
281
|
-
liked: Boolean(likeNode && /like-active/.test(String(likeNode.className || ''))),
|
|
282
|
-
};
|
|
283
|
-
})
|
|
284
|
-
.filter((row) => row.text);
|
|
285
|
-
|
|
286
|
-
state.currentComments = comments;
|
|
287
|
-
state.commentsCollectedAt = new Date().toISOString();
|
|
288
|
-
return {
|
|
289
|
-
collected: comments.length,
|
|
290
|
-
firstComment: comments[0] || null,
|
|
291
|
-
};
|
|
292
|
-
})()`;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function buildCommentMatchScript(matchKeywords, matchMode, matchMinHits) {
|
|
296
|
-
return `(async () => {
|
|
297
|
-
const state = window.__camoXhsState || (window.__camoXhsState = {});
|
|
298
|
-
const rows = Array.isArray(state.currentComments) ? state.currentComments : [];
|
|
299
|
-
const keywords = ${JSON.stringify(matchKeywords)};
|
|
300
|
-
const mode = ${JSON.stringify(matchMode)};
|
|
301
|
-
const minHits = Number(${matchMinHits});
|
|
302
|
-
|
|
303
|
-
const normalize = (value) => String(value || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
|
304
|
-
const tokens = keywords.map((item) => normalize(item)).filter(Boolean);
|
|
305
|
-
const matches = [];
|
|
306
|
-
for (const row of rows) {
|
|
307
|
-
const text = normalize(row.text);
|
|
308
|
-
if (!text || tokens.length === 0) continue;
|
|
309
|
-
const hits = tokens.filter((token) => text.includes(token));
|
|
310
|
-
if (mode === 'all' && hits.length < tokens.length) continue;
|
|
311
|
-
if (mode === 'atLeast' && hits.length < Math.max(1, minHits)) continue;
|
|
312
|
-
if (mode !== 'all' && mode !== 'atLeast' && hits.length === 0) continue;
|
|
313
|
-
matches.push({ index: row.index, hits });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
state.matchedComments = matches;
|
|
317
|
-
state.matchRule = { tokens, mode, minHits };
|
|
318
|
-
return {
|
|
319
|
-
matchCount: matches.length,
|
|
320
|
-
mode,
|
|
321
|
-
minHits: Math.max(1, minHits),
|
|
322
|
-
};
|
|
323
|
-
})()`;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function buildCommentLikeScript(likeKeywords, maxLikesPerRound) {
|
|
327
|
-
return `(async () => {
|
|
328
|
-
const state = window.__camoXhsState || (window.__camoXhsState = {});
|
|
329
|
-
const keywords = ${JSON.stringify(likeKeywords)};
|
|
330
|
-
const maxLikes = Number(${maxLikesPerRound});
|
|
331
|
-
const maxLikesLimit = maxLikes > 0 ? maxLikes : Number.POSITIVE_INFINITY;
|
|
332
|
-
const nodes = Array.from(document.querySelectorAll('.comment-item'));
|
|
333
|
-
const matches = Array.isArray(state.matchedComments) ? state.matchedComments : [];
|
|
334
|
-
const targetIndexes = new Set(matches.map((row) => Number(row.index)));
|
|
335
|
-
|
|
336
|
-
let likedCount = 0;
|
|
337
|
-
let skippedActive = 0;
|
|
338
|
-
for (let idx = 0; idx < nodes.length; idx += 1) {
|
|
339
|
-
if (likedCount >= maxLikesLimit) break;
|
|
340
|
-
if (targetIndexes.size > 0 && !targetIndexes.has(idx)) continue;
|
|
341
|
-
const item = nodes[idx];
|
|
342
|
-
const text = String((item.querySelector('.content, .comment-content, p') || {}).textContent || '').trim();
|
|
343
|
-
if (!text) continue;
|
|
344
|
-
if (keywords.length > 0) {
|
|
345
|
-
const lower = text.toLowerCase();
|
|
346
|
-
const hit = keywords.some((token) => lower.includes(String(token).toLowerCase()));
|
|
347
|
-
if (!hit) continue;
|
|
348
|
-
}
|
|
349
|
-
const likeWrapper = item.querySelector('.like-wrapper');
|
|
350
|
-
if (!likeWrapper) continue;
|
|
351
|
-
if (/like-active/.test(String(likeWrapper.className || ''))) {
|
|
352
|
-
skippedActive += 1;
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
likeWrapper.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
356
|
-
await new Promise((resolve) => setTimeout(resolve, 90));
|
|
357
|
-
likeWrapper.click();
|
|
358
|
-
likedCount += 1;
|
|
359
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
state.lastLike = { likedCount, skippedActive, at: new Date().toISOString() };
|
|
363
|
-
return state.lastLike;
|
|
364
|
-
})()`;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function buildCommentReplyScript(replyText) {
|
|
368
|
-
return `(async () => {
|
|
369
|
-
const state = window.__camoXhsState || (window.__camoXhsState = {});
|
|
370
|
-
const replyText = ${JSON.stringify(replyText)};
|
|
371
|
-
const matches = Array.isArray(state.matchedComments) ? state.matchedComments : [];
|
|
372
|
-
if (matches.length === 0) {
|
|
373
|
-
return { typed: false, reason: 'no_match' };
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const index = Number(matches[0].index);
|
|
377
|
-
const nodes = Array.from(document.querySelectorAll('.comment-item'));
|
|
378
|
-
const target = nodes[index];
|
|
379
|
-
if (!target) {
|
|
380
|
-
return { typed: false, reason: 'match_not_visible', index };
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
target.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
384
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
385
|
-
target.click();
|
|
386
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
387
|
-
|
|
388
|
-
const input = document.querySelector('textarea, input[placeholder*="说点"], [contenteditable="true"]');
|
|
389
|
-
if (!input) {
|
|
390
|
-
return { typed: false, reason: 'reply_input_not_found', index };
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (input instanceof HTMLTextAreaElement || input instanceof HTMLInputElement) {
|
|
394
|
-
input.focus();
|
|
395
|
-
input.value = replyText;
|
|
396
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
397
|
-
} else {
|
|
398
|
-
input.focus();
|
|
399
|
-
input.textContent = replyText;
|
|
400
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
404
|
-
const sendButton = Array.from(document.querySelectorAll('button'))
|
|
405
|
-
.find((button) => /发送|回复/.test(String(button.textContent || '').trim()));
|
|
406
|
-
if (sendButton) {
|
|
407
|
-
sendButton.click();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
state.lastReply = { typed: true, index, at: new Date().toISOString() };
|
|
411
|
-
return state.lastReply;
|
|
412
|
-
})()`;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function buildCloseDetailScript() {
|
|
416
|
-
return `(async () => {
|
|
417
|
-
const modalSelectors = [
|
|
418
|
-
'.note-detail-mask',
|
|
419
|
-
'.note-detail',
|
|
420
|
-
'.detail-container',
|
|
421
|
-
'.media-container',
|
|
422
|
-
];
|
|
423
|
-
const isModalVisible = () => modalSelectors.some((selector) => {
|
|
424
|
-
const node = document.querySelector(selector);
|
|
425
|
-
if (!node || !(node instanceof HTMLElement)) return false;
|
|
426
|
-
const style = window.getComputedStyle(node);
|
|
427
|
-
if (!style) return false;
|
|
428
|
-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') === 0) {
|
|
429
|
-
return false;
|
|
430
|
-
}
|
|
431
|
-
const rect = node.getBoundingClientRect();
|
|
432
|
-
return rect.width > 1 && rect.height > 1;
|
|
433
|
-
});
|
|
434
|
-
const waitForClosed = async () => {
|
|
435
|
-
for (let i = 0; i < 30; i += 1) {
|
|
436
|
-
if (!isModalVisible()) return true;
|
|
437
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
438
|
-
}
|
|
439
|
-
return !isModalVisible();
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
const selectors = [
|
|
443
|
-
'.note-detail-mask .close-box',
|
|
444
|
-
'.note-detail-mask .close-circle',
|
|
445
|
-
'a[href*="/explore?channel_id=homefeed_recommend"]',
|
|
446
|
-
];
|
|
447
|
-
for (const selector of selectors) {
|
|
448
|
-
const target = document.querySelector(selector);
|
|
449
|
-
if (!target) continue;
|
|
450
|
-
target.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
451
|
-
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
452
|
-
target.click();
|
|
453
|
-
return { closed: await waitForClosed(), via: selector };
|
|
454
|
-
}
|
|
455
|
-
if (window.history.length > 1) {
|
|
456
|
-
window.history.back();
|
|
457
|
-
return { closed: await waitForClosed(), via: 'history.back' };
|
|
458
|
-
}
|
|
459
|
-
return { closed: false, via: null, modalVisible: isModalVisible() };
|
|
460
|
-
})()`;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function buildAbortScript(code) {
|
|
464
|
-
return `(async () => {
|
|
465
|
-
throw new Error(${JSON.stringify(code)});
|
|
466
|
-
})()`;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
50
|
export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
470
51
|
const profileId = toTrimmedString(rawOptions.profileId, 'xiaohongshu-batch-1');
|
|
471
52
|
const keyword = toTrimmedString(rawOptions.keyword, '手机膜');
|
|
@@ -504,8 +85,11 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
504
85
|
const replyText = toTrimmedString(rawOptions.replyText, '感谢分享,已关注');
|
|
505
86
|
const sharedHarvestPath = toTrimmedString(rawOptions.sharedHarvestPath, '');
|
|
506
87
|
const searchSerialKey = toTrimmedString(rawOptions.searchSerialKey, `${env}:${keyword}`);
|
|
507
|
-
const seedCollectCount = toNonNegativeInt(rawOptions.seedCollectCount,
|
|
508
|
-
const seedCollectMaxRounds = toNonNegativeInt(
|
|
88
|
+
const seedCollectCount = toNonNegativeInt(rawOptions.seedCollectCount, maxNotes);
|
|
89
|
+
const seedCollectMaxRounds = toNonNegativeInt(
|
|
90
|
+
rawOptions.seedCollectMaxRounds,
|
|
91
|
+
Math.max(6, Math.ceil(maxNotes / 2)),
|
|
92
|
+
);
|
|
509
93
|
|
|
510
94
|
const doHomepage = toBoolean(rawOptions.doHomepage, true);
|
|
511
95
|
const doImages = toBoolean(rawOptions.doImages, false);
|
|
@@ -514,17 +98,24 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
514
98
|
const doReply = toBoolean(rawOptions.doReply, false);
|
|
515
99
|
const doOcr = toBoolean(rawOptions.doOcr, false);
|
|
516
100
|
const persistComments = toBoolean(rawOptions.persistComments, true);
|
|
101
|
+
const stage = toTrimmedString(rawOptions.stage, 'full').toLowerCase();
|
|
102
|
+
const stageLinksEnabled = toBoolean(rawOptions.stageLinksEnabled, true);
|
|
103
|
+
const stageContentEnabled = toBoolean(rawOptions.stageContentEnabled, true);
|
|
104
|
+
const stageLikeEnabled = toBoolean(rawOptions.stageLikeEnabled, doLikes);
|
|
105
|
+
const stageReplyEnabled = toBoolean(rawOptions.stageReplyEnabled, doReply);
|
|
517
106
|
|
|
518
107
|
const matchKeywords = splitCsv(rawOptions.matchKeywords || keyword);
|
|
519
108
|
const likeKeywordsSeed = splitCsv(rawOptions.likeKeywords || '');
|
|
520
109
|
const likeKeywords = likeKeywordsSeed.length > 0 ? likeKeywordsSeed : matchKeywords;
|
|
521
110
|
|
|
522
|
-
const
|
|
523
|
-
const
|
|
524
|
-
const
|
|
111
|
+
const detailLoopEnabled = stageContentEnabled || stageLikeEnabled || stageReplyEnabled;
|
|
112
|
+
const detailHarvestEnabled = detailLoopEnabled && (doHomepage || doImages || doComments || doOcr);
|
|
113
|
+
const commentsHarvestEnabled = detailLoopEnabled && (doComments || stageLikeEnabled || stageReplyEnabled);
|
|
114
|
+
const matchGateEnabled = stageLikeEnabled || stageReplyEnabled;
|
|
115
|
+
const collectLinksTimeoutMs = Math.max(180000, maxNotes * 6000);
|
|
525
116
|
const closeDependsOn = pickCloseDependency({
|
|
526
|
-
doReply,
|
|
527
|
-
doLikes,
|
|
117
|
+
doReply: stageReplyEnabled,
|
|
118
|
+
doLikes: stageLikeEnabled,
|
|
528
119
|
matchGateEnabled,
|
|
529
120
|
commentsHarvestEnabled,
|
|
530
121
|
detailHarvestEnabled,
|
|
@@ -591,9 +182,14 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
591
182
|
doHomepage,
|
|
592
183
|
doImages,
|
|
593
184
|
doComments,
|
|
594
|
-
doLikes,
|
|
595
|
-
doReply,
|
|
185
|
+
doLikes: stageLikeEnabled,
|
|
186
|
+
doReply: stageReplyEnabled,
|
|
596
187
|
doOcr,
|
|
188
|
+
stage,
|
|
189
|
+
stageLinksEnabled,
|
|
190
|
+
stageContentEnabled,
|
|
191
|
+
stageLikeEnabled,
|
|
192
|
+
stageReplyEnabled,
|
|
597
193
|
persistComments,
|
|
598
194
|
matchMode,
|
|
599
195
|
matchMinHits,
|
|
@@ -604,6 +200,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
604
200
|
searchSerialKey,
|
|
605
201
|
seedCollectCount,
|
|
606
202
|
seedCollectMaxRounds,
|
|
203
|
+
collectOpenLinksOnly: stageLinksEnabled && !detailLoopEnabled,
|
|
607
204
|
notes: [
|
|
608
205
|
'open_next_detail intentionally stops script by throwing AUTOSCRIPT_DONE_* when exhausted.',
|
|
609
206
|
'dev mode uses deterministic no-recovery policy (checkpoint recovery disabled).',
|
|
@@ -617,10 +214,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
617
214
|
{ id: 'search_result_item', selector: '.note-item', events: ['appear', 'exist', 'change'] },
|
|
618
215
|
{
|
|
619
216
|
id: 'detail_modal',
|
|
620
|
-
selector: '
|
|
621
|
-
visible: false,
|
|
622
|
-
pageUrlIncludes: ['/explore/'],
|
|
623
|
-
pageUrlExcludes: ['/search_result'],
|
|
217
|
+
selector: '.note-detail-mask, .note-detail-page, .note-detail-dialog',
|
|
624
218
|
events: ['appear', 'exist', 'disappear'],
|
|
625
219
|
},
|
|
626
220
|
{ id: 'detail_comment_item', selector: '.comment-item, [class*="comment-item"]', events: ['appear', 'exist', 'change'] },
|
|
@@ -705,8 +299,34 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
705
299
|
recovery,
|
|
706
300
|
},
|
|
707
301
|
},
|
|
302
|
+
{
|
|
303
|
+
id: 'collect_links',
|
|
304
|
+
enabled: stageLinksEnabled,
|
|
305
|
+
action: 'xhs_open_detail',
|
|
306
|
+
params: {
|
|
307
|
+
mode: 'collect',
|
|
308
|
+
maxNotes,
|
|
309
|
+
env,
|
|
310
|
+
keyword,
|
|
311
|
+
outputRoot,
|
|
312
|
+
resume,
|
|
313
|
+
incrementalMax,
|
|
314
|
+
sharedHarvestPath,
|
|
315
|
+
seedCollectCount,
|
|
316
|
+
seedCollectMaxRounds,
|
|
317
|
+
collectOpenLinksOnly: stageLinksEnabled && !detailLoopEnabled,
|
|
318
|
+
},
|
|
319
|
+
trigger: 'search_result_item.exist',
|
|
320
|
+
dependsOn: ['ensure_tab_pool'],
|
|
321
|
+
once: true,
|
|
322
|
+
timeoutMs: collectLinksTimeoutMs,
|
|
323
|
+
retry: { attempts: 1, backoffMs: 0 },
|
|
324
|
+
impact: 'script',
|
|
325
|
+
onFailure: 'stop_all',
|
|
326
|
+
},
|
|
708
327
|
{
|
|
709
328
|
id: 'open_first_detail',
|
|
329
|
+
enabled: detailLoopEnabled,
|
|
710
330
|
action: 'xhs_open_detail',
|
|
711
331
|
params: {
|
|
712
332
|
mode: 'first',
|
|
@@ -725,10 +345,11 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
725
345
|
postOpenDelayMaxMs: openDetailPostOpenMaxMs,
|
|
726
346
|
},
|
|
727
347
|
trigger: 'search_result_item.exist',
|
|
728
|
-
dependsOn: ['submit_search'],
|
|
348
|
+
dependsOn: [stageLinksEnabled ? 'collect_links' : 'submit_search'],
|
|
729
349
|
once: true,
|
|
730
350
|
timeoutMs: 90000,
|
|
731
|
-
onFailure: '
|
|
351
|
+
onFailure: 'stop_all',
|
|
352
|
+
impact: 'script',
|
|
732
353
|
validation: { mode: 'none' },
|
|
733
354
|
checkpoint: {
|
|
734
355
|
containerId: 'xiaohongshu_search.search_result_item',
|
|
@@ -736,6 +357,18 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
736
357
|
recovery,
|
|
737
358
|
},
|
|
738
359
|
},
|
|
360
|
+
{
|
|
361
|
+
id: 'finish_after_collect_links',
|
|
362
|
+
enabled: stageLinksEnabled && !detailLoopEnabled,
|
|
363
|
+
action: 'raise_error',
|
|
364
|
+
params: { code: 'AUTOSCRIPT_DONE_LINKS_COLLECTED' },
|
|
365
|
+
trigger: 'search_result_item.exist',
|
|
366
|
+
dependsOn: ['collect_links'],
|
|
367
|
+
once: true,
|
|
368
|
+
retry: { attempts: 1, backoffMs: 0 },
|
|
369
|
+
impact: 'script',
|
|
370
|
+
onFailure: 'stop_all',
|
|
371
|
+
},
|
|
739
372
|
{
|
|
740
373
|
id: 'detail_harvest',
|
|
741
374
|
enabled: detailHarvestEnabled,
|
|
@@ -835,14 +468,14 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
835
468
|
action: 'xhs_comment_match',
|
|
836
469
|
params: { keywords: matchKeywords, mode: matchMode, minHits: matchMinHits },
|
|
837
470
|
trigger: 'detail_modal.exist',
|
|
838
|
-
dependsOn: [
|
|
471
|
+
dependsOn: ['comments_harvest'],
|
|
839
472
|
once: false,
|
|
840
473
|
oncePerAppear: true,
|
|
841
474
|
pacing: { operationMinIntervalMs: 2400, eventCooldownMs: 1200, jitterMs: 160 },
|
|
842
475
|
},
|
|
843
476
|
{
|
|
844
477
|
id: 'comment_like',
|
|
845
|
-
enabled:
|
|
478
|
+
enabled: stageLikeEnabled,
|
|
846
479
|
action: 'xhs_comment_like',
|
|
847
480
|
params: {
|
|
848
481
|
env,
|
|
@@ -865,11 +498,11 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
865
498
|
},
|
|
866
499
|
{
|
|
867
500
|
id: 'comment_reply',
|
|
868
|
-
enabled:
|
|
501
|
+
enabled: stageReplyEnabled,
|
|
869
502
|
action: 'xhs_comment_reply',
|
|
870
503
|
params: { replyText },
|
|
871
504
|
trigger: 'detail_modal.exist',
|
|
872
|
-
dependsOn: ['comment_match_gate'],
|
|
505
|
+
dependsOn: [stageLikeEnabled ? 'comment_like' : 'comment_match_gate'],
|
|
873
506
|
once: false,
|
|
874
507
|
oncePerAppear: true,
|
|
875
508
|
timeoutMs: 90000,
|
|
@@ -880,6 +513,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
880
513
|
},
|
|
881
514
|
{
|
|
882
515
|
id: 'close_detail',
|
|
516
|
+
enabled: detailLoopEnabled,
|
|
883
517
|
action: 'xhs_close_detail',
|
|
884
518
|
params: {},
|
|
885
519
|
trigger: 'detail_modal.exist',
|
|
@@ -896,6 +530,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
896
530
|
},
|
|
897
531
|
{
|
|
898
532
|
id: 'wait_between_notes',
|
|
533
|
+
enabled: detailLoopEnabled,
|
|
899
534
|
action: 'wait',
|
|
900
535
|
params: { ms: noteIntervalMs },
|
|
901
536
|
trigger: 'search_result_item.exist',
|
|
@@ -909,6 +544,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
909
544
|
},
|
|
910
545
|
{
|
|
911
546
|
id: 'switch_tab_round_robin',
|
|
547
|
+
enabled: detailLoopEnabled,
|
|
912
548
|
action: 'tab_pool_switch_next',
|
|
913
549
|
params: { settleMs: 450 },
|
|
914
550
|
trigger: 'search_result_item.exist',
|
|
@@ -922,6 +558,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
922
558
|
},
|
|
923
559
|
{
|
|
924
560
|
id: 'open_next_detail',
|
|
561
|
+
enabled: detailLoopEnabled,
|
|
925
562
|
action: 'xhs_open_detail',
|
|
926
563
|
params: {
|
|
927
564
|
mode: 'next',
|
|
@@ -989,7 +626,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
989
626
|
normalizeTabs: false,
|
|
990
627
|
},
|
|
991
628
|
trigger: 'search_result_item.exist',
|
|
992
|
-
dependsOn: ['
|
|
629
|
+
dependsOn: ['submit_search'],
|
|
993
630
|
once: true,
|
|
994
631
|
timeoutMs: 180000,
|
|
995
632
|
retry: { attempts: 2, backoffMs: 500 },
|