@web-auto/camo 0.1.14 → 0.1.15
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 +19 -37
- package/package.json +1 -1
- package/src/autoscript/action-providers/index.mjs +3 -6
- package/src/autoscript/runtime.mjs +13 -12
- package/src/commands/autoscript.mjs +1 -104
- package/src/container/runtime-core/checkpoint.mjs +21 -7
- package/src/container/runtime-core/validation.mjs +2 -2
- package/src/utils/help.mjs +0 -1
- package/src/autoscript/action-providers/xhs/comments.mjs +0 -412
- package/src/autoscript/action-providers/xhs/common.mjs +0 -77
- package/src/autoscript/action-providers/xhs/detail.mjs +0 -181
- package/src/autoscript/action-providers/xhs/interaction.mjs +0 -466
- package/src/autoscript/action-providers/xhs/like-rules.mjs +0 -57
- package/src/autoscript/action-providers/xhs/persistence.mjs +0 -167
- package/src/autoscript/action-providers/xhs/search.mjs +0 -174
- package/src/autoscript/action-providers/xhs.mjs +0 -133
- package/src/autoscript/xhs-unified-template.mjs +0 -934
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
export function buildSubmitSearchScript(params = {}) {
|
|
2
|
-
const keyword = String(params.keyword || '').trim();
|
|
3
|
-
return `(async () => {
|
|
4
|
-
const state = window.__camoXhsState || (window.__camoXhsState = {});
|
|
5
|
-
const metrics = state.metrics && typeof state.metrics === 'object' ? state.metrics : {};
|
|
6
|
-
state.metrics = metrics;
|
|
7
|
-
metrics.searchCount = Number(metrics.searchCount || 0) + 1;
|
|
8
|
-
metrics.lastSearchAt = new Date().toISOString();
|
|
9
|
-
const input = document.querySelector('#search-input, input.search-input');
|
|
10
|
-
if (!(input instanceof HTMLInputElement)) {
|
|
11
|
-
throw new Error('SEARCH_INPUT_NOT_FOUND');
|
|
12
|
-
}
|
|
13
|
-
const targetKeyword = ${JSON.stringify(keyword)};
|
|
14
|
-
if (targetKeyword && input.value !== targetKeyword) {
|
|
15
|
-
input.focus();
|
|
16
|
-
input.value = targetKeyword;
|
|
17
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
18
|
-
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
19
|
-
}
|
|
20
|
-
const enterEvent = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true };
|
|
21
|
-
const beforeUrl = window.location.href;
|
|
22
|
-
input.focus();
|
|
23
|
-
input.dispatchEvent(new KeyboardEvent('keydown', enterEvent));
|
|
24
|
-
input.dispatchEvent(new KeyboardEvent('keypress', enterEvent));
|
|
25
|
-
input.dispatchEvent(new KeyboardEvent('keyup', enterEvent));
|
|
26
|
-
const candidates = ['.input-button .search-icon', '.input-button', 'button.min-width-search-icon'];
|
|
27
|
-
let clickedSelector = null;
|
|
28
|
-
for (const selector of candidates) {
|
|
29
|
-
const button = document.querySelector(selector);
|
|
30
|
-
if (!button) continue;
|
|
31
|
-
if (button instanceof HTMLElement) button.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
32
|
-
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
33
|
-
button.click();
|
|
34
|
-
clickedSelector = selector;
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
const form = input.closest('form');
|
|
38
|
-
if (form) {
|
|
39
|
-
if (typeof form.requestSubmit === 'function') form.requestSubmit();
|
|
40
|
-
else form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
|
41
|
-
}
|
|
42
|
-
await new Promise((resolve) => setTimeout(resolve, 320));
|
|
43
|
-
return {
|
|
44
|
-
submitted: true,
|
|
45
|
-
via: clickedSelector || 'enter_or_form_submit',
|
|
46
|
-
beforeUrl,
|
|
47
|
-
afterUrl: window.location.href,
|
|
48
|
-
searchCount: metrics.searchCount,
|
|
49
|
-
};
|
|
50
|
-
})()`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function buildOpenDetailScript(params = {}) {
|
|
54
|
-
const mode = String(params.mode || 'first').trim().toLowerCase();
|
|
55
|
-
const maxNotes = Math.max(1, Number(params.maxNotes ?? params.limit ?? 20) || 20);
|
|
56
|
-
const keyword = String(params.keyword || '').trim();
|
|
57
|
-
|
|
58
|
-
return `(async () => {
|
|
59
|
-
const STATE_KEY = '__camoXhsState';
|
|
60
|
-
const normalizeVisited = (value) => {
|
|
61
|
-
if (!Array.isArray(value)) return [];
|
|
62
|
-
return value
|
|
63
|
-
.map((item) => String(item || '').trim())
|
|
64
|
-
.filter(Boolean);
|
|
65
|
-
};
|
|
66
|
-
const mergeVisited = (a, b) => Array.from(new Set([
|
|
67
|
-
...normalizeVisited(a),
|
|
68
|
-
...normalizeVisited(b),
|
|
69
|
-
]));
|
|
70
|
-
const loadState = () => {
|
|
71
|
-
const inMemory = window.__camoXhsState && typeof window.__camoXhsState === 'object' ? window.__camoXhsState : {};
|
|
72
|
-
try {
|
|
73
|
-
const stored = localStorage.getItem(STATE_KEY);
|
|
74
|
-
if (!stored) return { ...inMemory };
|
|
75
|
-
const parsed = JSON.parse(stored);
|
|
76
|
-
if (!parsed || typeof parsed !== 'object') return { ...inMemory };
|
|
77
|
-
const merged = { ...inMemory, ...parsed };
|
|
78
|
-
merged.visitedNoteIds = mergeVisited(parsed.visitedNoteIds, inMemory.visitedNoteIds);
|
|
79
|
-
return merged;
|
|
80
|
-
} catch {
|
|
81
|
-
return { ...inMemory };
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
const saveState = (nextState) => {
|
|
85
|
-
window.__camoXhsState = nextState;
|
|
86
|
-
try { localStorage.setItem(STATE_KEY, JSON.stringify(nextState)); } catch {}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const state = loadState();
|
|
90
|
-
if (!Array.isArray(state.visitedNoteIds)) state.visitedNoteIds = [];
|
|
91
|
-
state.maxNotes = Number(${maxNotes});
|
|
92
|
-
if (${JSON.stringify(keyword)}) state.keyword = ${JSON.stringify(keyword)};
|
|
93
|
-
|
|
94
|
-
if (${JSON.stringify(mode)} === 'next' && state.visitedNoteIds.length >= state.maxNotes) {
|
|
95
|
-
throw new Error('AUTOSCRIPT_DONE_MAX_NOTES');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const nodes = Array.from(document.querySelectorAll('.note-item'))
|
|
99
|
-
.map((item, index) => {
|
|
100
|
-
const cover = item.querySelector('a.cover');
|
|
101
|
-
if (!cover) return null;
|
|
102
|
-
const href = String(cover.getAttribute('href') || '').trim();
|
|
103
|
-
const noteId = href.split('/').filter(Boolean).pop() || ('idx_' + index);
|
|
104
|
-
return { cover, href, noteId };
|
|
105
|
-
})
|
|
106
|
-
.filter(Boolean);
|
|
107
|
-
if (nodes.length === 0) throw new Error('NO_SEARCH_RESULT_ITEM');
|
|
108
|
-
|
|
109
|
-
let next = null;
|
|
110
|
-
if (${JSON.stringify(mode)} === 'next') {
|
|
111
|
-
next = nodes.find((row) => !state.visitedNoteIds.includes(row.noteId));
|
|
112
|
-
if (!next) throw new Error('AUTOSCRIPT_DONE_NO_MORE_NOTES');
|
|
113
|
-
} else {
|
|
114
|
-
next = nodes.find((row) => !state.visitedNoteIds.includes(row.noteId)) || nodes[0];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const detailSelectors = [
|
|
118
|
-
'.note-detail-mask',
|
|
119
|
-
'.note-detail-page',
|
|
120
|
-
'.note-detail-dialog',
|
|
121
|
-
'.note-detail-mask .detail-container',
|
|
122
|
-
'.note-detail-mask .media-container',
|
|
123
|
-
'.note-detail-mask .note-scroller',
|
|
124
|
-
'.note-detail-mask .note-content',
|
|
125
|
-
'.note-detail-mask .interaction-container',
|
|
126
|
-
'.note-detail-mask .comments-container',
|
|
127
|
-
];
|
|
128
|
-
const isVisible = (node) => {
|
|
129
|
-
if (!node || !(node instanceof HTMLElement)) return false;
|
|
130
|
-
const style = window.getComputedStyle(node);
|
|
131
|
-
if (!style) return false;
|
|
132
|
-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') === 0) return false;
|
|
133
|
-
const rect = node.getBoundingClientRect();
|
|
134
|
-
return rect.width > 1 && rect.height > 1;
|
|
135
|
-
};
|
|
136
|
-
const isDetailReady = () => detailSelectors.some((selector) => isVisible(document.querySelector(selector)));
|
|
137
|
-
|
|
138
|
-
next.cover.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
139
|
-
await new Promise((resolve) => setTimeout(resolve, 140));
|
|
140
|
-
const beforeUrl = window.location.href;
|
|
141
|
-
next.cover.click();
|
|
142
|
-
|
|
143
|
-
let detailReady = false;
|
|
144
|
-
for (let i = 0; i < 60; i += 1) {
|
|
145
|
-
if (isDetailReady()) {
|
|
146
|
-
detailReady = true;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
150
|
-
}
|
|
151
|
-
if (!detailReady) {
|
|
152
|
-
throw new Error('DETAIL_OPEN_TIMEOUT');
|
|
153
|
-
}
|
|
154
|
-
await new Promise((resolve) => setTimeout(resolve, 220));
|
|
155
|
-
const afterUrl = window.location.href;
|
|
156
|
-
|
|
157
|
-
if (!state.visitedNoteIds.includes(next.noteId)) state.visitedNoteIds.push(next.noteId);
|
|
158
|
-
state.currentNoteId = next.noteId;
|
|
159
|
-
state.currentHref = next.href;
|
|
160
|
-
state.lastListUrl = beforeUrl;
|
|
161
|
-
saveState(state);
|
|
162
|
-
return {
|
|
163
|
-
opened: true,
|
|
164
|
-
source: ${JSON.stringify(mode)} === 'next' ? 'open_next_detail' : 'open_first_detail',
|
|
165
|
-
noteId: next.noteId,
|
|
166
|
-
visited: state.visitedNoteIds.length,
|
|
167
|
-
maxNotes: state.maxNotes,
|
|
168
|
-
openByClick: true,
|
|
169
|
-
beforeUrl,
|
|
170
|
-
afterUrl,
|
|
171
|
-
};
|
|
172
|
-
})()`;
|
|
173
|
-
}
|
|
174
|
-
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { asErrorPayload } from '../../container/runtime-core/utils.mjs';
|
|
2
|
-
import {
|
|
3
|
-
createEvaluateHandler,
|
|
4
|
-
extractEvaluateResultData,
|
|
5
|
-
evaluateWithScript,
|
|
6
|
-
runEvaluateScript,
|
|
7
|
-
} from './xhs/common.mjs';
|
|
8
|
-
import { buildCommentsHarvestScript, buildCommentMatchScript } from './xhs/comments.mjs';
|
|
9
|
-
import {
|
|
10
|
-
buildCloseDetailScript,
|
|
11
|
-
buildDetailHarvestScript,
|
|
12
|
-
buildExpandRepliesScript,
|
|
13
|
-
} from './xhs/detail.mjs';
|
|
14
|
-
import {
|
|
15
|
-
buildCommentReplyScript,
|
|
16
|
-
executeCommentLikeOperation,
|
|
17
|
-
} from './xhs/interaction.mjs';
|
|
18
|
-
import {
|
|
19
|
-
mergeCommentsJsonl,
|
|
20
|
-
resolveXhsOutputContext,
|
|
21
|
-
} from './xhs/persistence.mjs';
|
|
22
|
-
import { buildOpenDetailScript, buildSubmitSearchScript } from './xhs/search.mjs';
|
|
23
|
-
|
|
24
|
-
function buildReadStateScript() {
|
|
25
|
-
return `(() => {
|
|
26
|
-
const state = window.__camoXhsState || {};
|
|
27
|
-
return {
|
|
28
|
-
keyword: state.keyword || null,
|
|
29
|
-
currentNoteId: state.currentNoteId || null,
|
|
30
|
-
lastCommentsHarvest: state.lastCommentsHarvest && typeof state.lastCommentsHarvest === 'object'
|
|
31
|
-
? state.lastCommentsHarvest
|
|
32
|
-
: null,
|
|
33
|
-
};
|
|
34
|
-
})()`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function readXhsRuntimeState(profileId) {
|
|
38
|
-
try {
|
|
39
|
-
const payload = await runEvaluateScript({
|
|
40
|
-
profileId,
|
|
41
|
-
script: buildReadStateScript(),
|
|
42
|
-
highlight: false,
|
|
43
|
-
});
|
|
44
|
-
return extractEvaluateResultData(payload) || {};
|
|
45
|
-
} catch {
|
|
46
|
-
return {};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function handleRaiseError({ params }) {
|
|
51
|
-
const code = String(params.code || params.message || 'AUTOSCRIPT_ABORT').trim();
|
|
52
|
-
return asErrorPayload('OPERATION_FAILED', code || 'AUTOSCRIPT_ABORT');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function executeCommentsHarvestOperation({ profileId, params = {} }) {
|
|
56
|
-
const script = buildCommentsHarvestScript(params);
|
|
57
|
-
const highlight = params.highlight !== false;
|
|
58
|
-
const operationResult = await evaluateWithScript({
|
|
59
|
-
profileId,
|
|
60
|
-
script,
|
|
61
|
-
message: 'xhs_comments_harvest done',
|
|
62
|
-
highlight,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const payload = extractEvaluateResultData(operationResult.data) || {};
|
|
66
|
-
const shouldPersistComments = params.persistComments === true || params.persistCollectedComments === true;
|
|
67
|
-
const includeComments = params.includeComments !== false;
|
|
68
|
-
const comments = Array.isArray(payload.comments) ? payload.comments : [];
|
|
69
|
-
|
|
70
|
-
if (!shouldPersistComments || !includeComments || comments.length === 0) {
|
|
71
|
-
return {
|
|
72
|
-
...operationResult,
|
|
73
|
-
data: {
|
|
74
|
-
...payload,
|
|
75
|
-
commentsPath: null,
|
|
76
|
-
commentsAdded: 0,
|
|
77
|
-
commentsTotal: Number(payload.collected || comments.length || 0),
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const state = await readXhsRuntimeState(profileId);
|
|
83
|
-
const output = resolveXhsOutputContext({
|
|
84
|
-
params,
|
|
85
|
-
state,
|
|
86
|
-
noteId: payload.noteId || state.currentNoteId || params.noteId,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const merged = await mergeCommentsJsonl({
|
|
90
|
-
filePath: output.commentsPath,
|
|
91
|
-
noteId: output.noteId,
|
|
92
|
-
comments,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
ok: true,
|
|
97
|
-
code: 'OPERATION_DONE',
|
|
98
|
-
message: 'xhs_comments_harvest done',
|
|
99
|
-
data: {
|
|
100
|
-
...payload,
|
|
101
|
-
commentsPath: merged.filePath,
|
|
102
|
-
commentsAdded: merged.added,
|
|
103
|
-
commentsTotal: merged.total,
|
|
104
|
-
outputNoteDir: output.noteDir,
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const XHS_ACTION_HANDLERS = {
|
|
110
|
-
raise_error: handleRaiseError,
|
|
111
|
-
xhs_submit_search: createEvaluateHandler('xhs_submit_search done', buildSubmitSearchScript),
|
|
112
|
-
xhs_open_detail: createEvaluateHandler('xhs_open_detail done', buildOpenDetailScript),
|
|
113
|
-
xhs_detail_harvest: createEvaluateHandler('xhs_detail_harvest done', buildDetailHarvestScript),
|
|
114
|
-
xhs_expand_replies: createEvaluateHandler('xhs_expand_replies done', buildExpandRepliesScript),
|
|
115
|
-
xhs_comments_harvest: executeCommentsHarvestOperation,
|
|
116
|
-
xhs_comment_match: createEvaluateHandler('xhs_comment_match done', buildCommentMatchScript),
|
|
117
|
-
xhs_comment_like: executeCommentLikeOperation,
|
|
118
|
-
xhs_comment_reply: createEvaluateHandler('xhs_comment_reply done', buildCommentReplyScript),
|
|
119
|
-
xhs_close_detail: createEvaluateHandler('xhs_close_detail done', buildCloseDetailScript),
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export function isXhsAutoscriptAction(action) {
|
|
123
|
-
const normalized = String(action || '').trim();
|
|
124
|
-
return normalized === 'raise_error' || normalized.startsWith('xhs_');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export async function executeXhsAutoscriptOperation({ profileId, action, params = {} }) {
|
|
128
|
-
const handler = XHS_ACTION_HANDLERS[action];
|
|
129
|
-
if (!handler) {
|
|
130
|
-
return asErrorPayload('UNSUPPORTED_OPERATION', `Unsupported xhs operation: ${action}`);
|
|
131
|
-
}
|
|
132
|
-
return handler({ profileId, params });
|
|
133
|
-
}
|