@reconcrap/boss-recommend-mcp 2.0.45 → 2.0.47
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/bin/boss-recommend-mcp.js +4 -4
- package/config/screening-config.example.json +27 -27
- package/package.json +1 -1
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +39 -39
- package/skills/boss-chat/SKILL.md +93 -93
- package/skills/boss-recommend-pipeline/README.md +12 -12
- package/skills/boss-recommend-pipeline/SKILL.md +180 -180
- package/skills/boss-recruit-pipeline/README.md +17 -17
- package/skills/boss-recruit-pipeline/SKILL.md +58 -58
- package/src/chat-mcp.js +1780 -1780
- package/src/chat-runtime-config.js +749 -749
- package/src/cli.js +3054 -3054
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +1453 -1446
- package/src/core/capture/index.js +1201 -1201
- package/src/core/cv-acquisition/index.js +238 -238
- package/src/core/cv-capture-target/index.js +299 -299
- package/src/core/greet-quota/index.js +54 -54
- package/src/core/infinite-list/index.js +1326 -1326
- package/src/core/reporting/legacy-csv.js +341 -341
- package/src/core/run/timing.js +33 -33
- package/src/core/screening/index.js +50 -3
- package/src/core/self-heal/index.js +973 -973
- package/src/core/self-heal/viewport.js +564 -564
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +221 -221
- package/src/domains/chat/detail.js +1668 -1661
- package/src/domains/chat/index.js +7 -7
- package/src/domains/chat/jobs.js +592 -588
- package/src/domains/chat/page-guard.js +98 -98
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1977 -1955
- package/src/domains/recommend/actions.js +457 -457
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/constants.js +165 -165
- package/src/domains/recommend/detail.js +36 -28
- package/src/domains/recommend/filters.js +610 -581
- package/src/domains/recommend/index.js +10 -10
- package/src/domains/recommend/jobs.js +316 -263
- package/src/domains/recommend/refresh.js +472 -472
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +75 -35
- package/src/domains/recommend/scopes.js +246 -245
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +167 -167
- package/src/domains/recruit/detail.js +461 -460
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +451 -451
- package/src/domains/recruit/refresh.js +44 -44
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1207 -1161
- package/src/domains/recruit/search.js +1202 -1149
- package/src/recommend-mcp.js +22 -22
- package/src/recruit-mcp.js +1338 -1338
|
@@ -1,243 +1,243 @@
|
|
|
1
|
-
import {
|
|
2
|
-
clickNodeCenter,
|
|
3
|
-
getAttributesMap,
|
|
4
|
-
getNodeBox,
|
|
5
|
-
getOuterHTML,
|
|
6
|
-
querySelectorAll,
|
|
7
|
-
sleep
|
|
8
|
-
} from "../../core/browser/index.js";
|
|
9
|
-
import {
|
|
10
|
-
mergeBossCandidateCardFields,
|
|
11
|
-
parseBossCandidateCardFieldsFromHtml
|
|
12
|
-
} from "../../core/boss-cards/index.js";
|
|
13
|
-
import {
|
|
14
|
-
htmlToText,
|
|
15
|
-
normalizeCandidateFromHtml,
|
|
16
|
-
normalizeText
|
|
17
|
-
} from "../../core/screening/index.js";
|
|
18
|
-
import {
|
|
19
|
-
RECOMMEND_CARD_SELECTOR,
|
|
20
|
-
RECOMMEND_END_REFRESH_SELECTOR
|
|
21
|
-
} from "./constants.js";
|
|
22
|
-
|
|
23
|
-
function uniqueNodeIds(nodeIds = []) {
|
|
24
|
-
return Array.from(new Set(nodeIds.filter(Boolean)));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function normalizeRefreshButtonLabel(outerHTML = "") {
|
|
28
|
-
return normalizeText(htmlToText(outerHTML)).replace(/\s+/g, "");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function parseRecommendCardFieldsFromHtml(html = "") {
|
|
32
|
-
return parseBossCandidateCardFieldsFromHtml(html);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function enrichRecommendCardCandidate(candidate, outerHTML = "") {
|
|
36
|
-
return mergeBossCandidateCardFields(candidate, outerHTML, {
|
|
37
|
-
metadataKey: "recommend_card_fields"
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isRefreshButtonLabel(label = "") {
|
|
42
|
-
const normalized = String(label || "").trim();
|
|
43
|
-
if (!normalized || normalized.length > 80) return false;
|
|
44
|
-
return /刷新|refresh/i.test(normalized);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function refreshButtonRank(candidate) {
|
|
48
|
-
const label = String(candidate.label || "").toLowerCase();
|
|
49
|
-
if (label === "刷新" || label === "refresh") return 0;
|
|
50
|
-
if (/^刷新$|^refresh$/i.test(label)) return 0;
|
|
51
|
-
if (/刷新/.test(label) || /refresh/i.test(label)) return 1;
|
|
52
|
-
return 2;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function searchTextNodeIds(client, query, {
|
|
56
|
-
maxResults = 200
|
|
57
|
-
} = {}) {
|
|
58
|
-
if (typeof client?.DOM?.performSearch !== "function") return [];
|
|
59
|
-
const search = await client.DOM.performSearch({
|
|
60
|
-
query,
|
|
61
|
-
includeUserAgentShadowDOM: false
|
|
62
|
-
});
|
|
63
|
-
const searchId = search.searchId;
|
|
64
|
-
const resultCount = Math.min(search.resultCount || 0, maxResults);
|
|
65
|
-
if (!searchId || resultCount <= 0) return [];
|
|
66
|
-
try {
|
|
67
|
-
const results = await client.DOM.getSearchResults({
|
|
68
|
-
searchId,
|
|
69
|
-
fromIndex: 0,
|
|
70
|
-
toIndex: resultCount
|
|
71
|
-
});
|
|
72
|
-
return results.nodeIds || [];
|
|
73
|
-
} finally {
|
|
74
|
-
await client.DOM.discardSearchResults({ searchId });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function findRecommendCardNodeIds(client, frameNodeId, {
|
|
79
|
-
selector = RECOMMEND_CARD_SELECTOR
|
|
80
|
-
} = {}) {
|
|
81
|
-
return querySelectorAll(client, frameNodeId, selector);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export async function waitForRecommendCardNodeIds(client, frameNodeId, {
|
|
85
|
-
selector = RECOMMEND_CARD_SELECTOR,
|
|
86
|
-
timeoutMs = 10000,
|
|
87
|
-
intervalMs = 300
|
|
88
|
-
} = {}) {
|
|
89
|
-
const started = Date.now();
|
|
90
|
-
let nodeIds = [];
|
|
91
|
-
while (Date.now() - started <= timeoutMs) {
|
|
92
|
-
nodeIds = await findRecommendCardNodeIds(client, frameNodeId, { selector });
|
|
93
|
-
if (nodeIds.length) return nodeIds;
|
|
94
|
-
await sleep(intervalMs);
|
|
95
|
-
}
|
|
96
|
-
return nodeIds;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export async function readRecommendCardCandidate(client, cardNodeId, {
|
|
100
|
-
targetUrl = "",
|
|
101
|
-
source = "recommend-domain-card",
|
|
102
|
-
metadata = {}
|
|
103
|
-
} = {}) {
|
|
104
|
-
const [attributes, outerHTML] = await Promise.all([
|
|
105
|
-
getAttributesMap(client, cardNodeId),
|
|
106
|
-
getOuterHTML(client, cardNodeId)
|
|
107
|
-
]);
|
|
108
|
-
const candidate = normalizeCandidateFromHtml({
|
|
109
|
-
domain: "recommend",
|
|
110
|
-
source,
|
|
111
|
-
html: outerHTML,
|
|
112
|
-
attributes,
|
|
113
|
-
metadata: {
|
|
114
|
-
target_url: targetUrl,
|
|
115
|
-
card_node_id: cardNodeId,
|
|
116
|
-
...metadata
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
return enrichRecommendCardCandidate(candidate, outerHTML);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export async function readFirstRecommendCardCandidate(client, frameNodeId, options = {}) {
|
|
123
|
-
const cardNodeIds = await findRecommendCardNodeIds(client, frameNodeId, options);
|
|
124
|
-
if (!cardNodeIds.length) {
|
|
125
|
-
throw new Error("No recommend candidate cards found");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const candidate = await readRecommendCardCandidate(client, cardNodeIds[0], options);
|
|
129
|
-
return {
|
|
130
|
-
card_count: cardNodeIds.length,
|
|
131
|
-
first_card_node_id: cardNodeIds[0],
|
|
132
|
-
card_node_ids: cardNodeIds,
|
|
133
|
-
candidate
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export async function findRecommendEndRefreshButtons(client, frameNodeId, {
|
|
138
|
-
selector = RECOMMEND_END_REFRESH_SELECTOR,
|
|
139
|
-
maxCandidates = 1200
|
|
140
|
-
} = {}) {
|
|
141
|
-
const textNodeIds = [
|
|
142
|
-
...await searchTextNodeIds(client, "刷新", { maxResults: 200 }),
|
|
143
|
-
...await searchTextNodeIds(client, "refresh", { maxResults: 50 })
|
|
144
|
-
];
|
|
145
|
-
const selectorNodeIds = textNodeIds.length
|
|
146
|
-
? await querySelectorAll(client, frameNodeId, selector)
|
|
147
|
-
: [];
|
|
148
|
-
const nodeIds = uniqueNodeIds([...textNodeIds, ...selectorNodeIds]).slice(0, maxCandidates);
|
|
149
|
-
const candidates = [];
|
|
150
|
-
for (let index = 0; index < nodeIds.length; index += 1) {
|
|
151
|
-
const nodeId = nodeIds[index];
|
|
152
|
-
let outerHTML = "";
|
|
153
|
-
try {
|
|
154
|
-
outerHTML = await getOuterHTML(client, nodeId);
|
|
155
|
-
} catch {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
const label = normalizeRefreshButtonLabel(outerHTML);
|
|
159
|
-
if (!isRefreshButtonLabel(label)) continue;
|
|
160
|
-
|
|
161
|
-
let box = null;
|
|
162
|
-
try {
|
|
163
|
-
box = await getNodeBox(client, nodeId);
|
|
164
|
-
} catch {
|
|
165
|
-
// Some text matches can be hidden or stale. Keep the label out of the click set.
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
candidates.push({
|
|
169
|
-
node_id: nodeId,
|
|
170
|
-
index,
|
|
171
|
-
label,
|
|
172
|
-
box,
|
|
173
|
-
rank: refreshButtonRank({ label })
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return candidates.sort((left, right) => {
|
|
178
|
-
const rankDiff = left.rank - right.rank;
|
|
179
|
-
if (rankDiff !== 0) return rankDiff;
|
|
180
|
-
return (right.box?.rect?.y || 0) - (left.box?.rect?.y || 0);
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export async function clickRecommendEndRefreshButton(client, frameNodeId, {
|
|
185
|
-
settleMs = 5000
|
|
186
|
-
} = {}) {
|
|
187
|
-
const beforeCardCount = (await findRecommendCardNodeIds(client, frameNodeId)).length;
|
|
188
|
-
const candidates = await findRecommendEndRefreshButtons(client, frameNodeId);
|
|
189
|
-
if (!candidates.length) {
|
|
190
|
-
return {
|
|
191
|
-
ok: false,
|
|
192
|
-
method: "end_refresh_button",
|
|
193
|
-
reason: "refresh_button_not_found",
|
|
194
|
-
before_card_count: beforeCardCount,
|
|
195
|
-
candidates: []
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const attempts = [];
|
|
200
|
-
for (const candidate of candidates) {
|
|
201
|
-
try {
|
|
202
|
-
const box = await clickNodeCenter(client, candidate.node_id, { scrollIntoView: true });
|
|
203
|
-
if (settleMs > 0) await sleep(settleMs);
|
|
204
|
-
const afterCardCount = (await findRecommendCardNodeIds(client, frameNodeId)).length;
|
|
205
|
-
return {
|
|
206
|
-
ok: true,
|
|
207
|
-
method: "end_refresh_button",
|
|
208
|
-
clicked: true,
|
|
209
|
-
node_id: candidate.node_id,
|
|
210
|
-
label: candidate.label,
|
|
211
|
-
box,
|
|
212
|
-
before_card_count: beforeCardCount,
|
|
213
|
-
after_card_count: afterCardCount,
|
|
214
|
-
settle_ms: settleMs,
|
|
215
|
-
candidates: candidates.map((item) => ({
|
|
216
|
-
node_id: item.node_id,
|
|
217
|
-
label: item.label,
|
|
218
|
-
y: item.box?.rect?.y || null
|
|
219
|
-
})).slice(0, 10),
|
|
220
|
-
attempts
|
|
221
|
-
};
|
|
222
|
-
} catch (error) {
|
|
223
|
-
attempts.push({
|
|
224
|
-
node_id: candidate.node_id,
|
|
225
|
-
label: candidate.label,
|
|
226
|
-
error: error?.message || String(error)
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
ok: false,
|
|
233
|
-
method: "end_refresh_button",
|
|
234
|
-
reason: "refresh_button_click_failed",
|
|
235
|
-
before_card_count: beforeCardCount,
|
|
236
|
-
attempts,
|
|
237
|
-
candidates: candidates.map((item) => ({
|
|
238
|
-
node_id: item.node_id,
|
|
239
|
-
label: item.label,
|
|
240
|
-
y: item.box?.rect?.y || null
|
|
241
|
-
})).slice(0, 10)
|
|
242
|
-
};
|
|
243
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
clickNodeCenter,
|
|
3
|
+
getAttributesMap,
|
|
4
|
+
getNodeBox,
|
|
5
|
+
getOuterHTML,
|
|
6
|
+
querySelectorAll,
|
|
7
|
+
sleep
|
|
8
|
+
} from "../../core/browser/index.js";
|
|
9
|
+
import {
|
|
10
|
+
mergeBossCandidateCardFields,
|
|
11
|
+
parseBossCandidateCardFieldsFromHtml
|
|
12
|
+
} from "../../core/boss-cards/index.js";
|
|
13
|
+
import {
|
|
14
|
+
htmlToText,
|
|
15
|
+
normalizeCandidateFromHtml,
|
|
16
|
+
normalizeText
|
|
17
|
+
} from "../../core/screening/index.js";
|
|
18
|
+
import {
|
|
19
|
+
RECOMMEND_CARD_SELECTOR,
|
|
20
|
+
RECOMMEND_END_REFRESH_SELECTOR
|
|
21
|
+
} from "./constants.js";
|
|
22
|
+
|
|
23
|
+
function uniqueNodeIds(nodeIds = []) {
|
|
24
|
+
return Array.from(new Set(nodeIds.filter(Boolean)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeRefreshButtonLabel(outerHTML = "") {
|
|
28
|
+
return normalizeText(htmlToText(outerHTML)).replace(/\s+/g, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function parseRecommendCardFieldsFromHtml(html = "") {
|
|
32
|
+
return parseBossCandidateCardFieldsFromHtml(html);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function enrichRecommendCardCandidate(candidate, outerHTML = "") {
|
|
36
|
+
return mergeBossCandidateCardFields(candidate, outerHTML, {
|
|
37
|
+
metadataKey: "recommend_card_fields"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isRefreshButtonLabel(label = "") {
|
|
42
|
+
const normalized = String(label || "").trim();
|
|
43
|
+
if (!normalized || normalized.length > 80) return false;
|
|
44
|
+
return /刷新|refresh/i.test(normalized);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function refreshButtonRank(candidate) {
|
|
48
|
+
const label = String(candidate.label || "").toLowerCase();
|
|
49
|
+
if (label === "刷新" || label === "refresh") return 0;
|
|
50
|
+
if (/^刷新$|^refresh$/i.test(label)) return 0;
|
|
51
|
+
if (/刷新/.test(label) || /refresh/i.test(label)) return 1;
|
|
52
|
+
return 2;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function searchTextNodeIds(client, query, {
|
|
56
|
+
maxResults = 200
|
|
57
|
+
} = {}) {
|
|
58
|
+
if (typeof client?.DOM?.performSearch !== "function") return [];
|
|
59
|
+
const search = await client.DOM.performSearch({
|
|
60
|
+
query,
|
|
61
|
+
includeUserAgentShadowDOM: false
|
|
62
|
+
});
|
|
63
|
+
const searchId = search.searchId;
|
|
64
|
+
const resultCount = Math.min(search.resultCount || 0, maxResults);
|
|
65
|
+
if (!searchId || resultCount <= 0) return [];
|
|
66
|
+
try {
|
|
67
|
+
const results = await client.DOM.getSearchResults({
|
|
68
|
+
searchId,
|
|
69
|
+
fromIndex: 0,
|
|
70
|
+
toIndex: resultCount
|
|
71
|
+
});
|
|
72
|
+
return results.nodeIds || [];
|
|
73
|
+
} finally {
|
|
74
|
+
await client.DOM.discardSearchResults({ searchId });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function findRecommendCardNodeIds(client, frameNodeId, {
|
|
79
|
+
selector = RECOMMEND_CARD_SELECTOR
|
|
80
|
+
} = {}) {
|
|
81
|
+
return querySelectorAll(client, frameNodeId, selector);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function waitForRecommendCardNodeIds(client, frameNodeId, {
|
|
85
|
+
selector = RECOMMEND_CARD_SELECTOR,
|
|
86
|
+
timeoutMs = 10000,
|
|
87
|
+
intervalMs = 300
|
|
88
|
+
} = {}) {
|
|
89
|
+
const started = Date.now();
|
|
90
|
+
let nodeIds = [];
|
|
91
|
+
while (Date.now() - started <= timeoutMs) {
|
|
92
|
+
nodeIds = await findRecommendCardNodeIds(client, frameNodeId, { selector });
|
|
93
|
+
if (nodeIds.length) return nodeIds;
|
|
94
|
+
await sleep(intervalMs);
|
|
95
|
+
}
|
|
96
|
+
return nodeIds;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function readRecommendCardCandidate(client, cardNodeId, {
|
|
100
|
+
targetUrl = "",
|
|
101
|
+
source = "recommend-domain-card",
|
|
102
|
+
metadata = {}
|
|
103
|
+
} = {}) {
|
|
104
|
+
const [attributes, outerHTML] = await Promise.all([
|
|
105
|
+
getAttributesMap(client, cardNodeId),
|
|
106
|
+
getOuterHTML(client, cardNodeId)
|
|
107
|
+
]);
|
|
108
|
+
const candidate = normalizeCandidateFromHtml({
|
|
109
|
+
domain: "recommend",
|
|
110
|
+
source,
|
|
111
|
+
html: outerHTML,
|
|
112
|
+
attributes,
|
|
113
|
+
metadata: {
|
|
114
|
+
target_url: targetUrl,
|
|
115
|
+
card_node_id: cardNodeId,
|
|
116
|
+
...metadata
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return enrichRecommendCardCandidate(candidate, outerHTML);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function readFirstRecommendCardCandidate(client, frameNodeId, options = {}) {
|
|
123
|
+
const cardNodeIds = await findRecommendCardNodeIds(client, frameNodeId, options);
|
|
124
|
+
if (!cardNodeIds.length) {
|
|
125
|
+
throw new Error("No recommend candidate cards found");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const candidate = await readRecommendCardCandidate(client, cardNodeIds[0], options);
|
|
129
|
+
return {
|
|
130
|
+
card_count: cardNodeIds.length,
|
|
131
|
+
first_card_node_id: cardNodeIds[0],
|
|
132
|
+
card_node_ids: cardNodeIds,
|
|
133
|
+
candidate
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function findRecommendEndRefreshButtons(client, frameNodeId, {
|
|
138
|
+
selector = RECOMMEND_END_REFRESH_SELECTOR,
|
|
139
|
+
maxCandidates = 1200
|
|
140
|
+
} = {}) {
|
|
141
|
+
const textNodeIds = [
|
|
142
|
+
...await searchTextNodeIds(client, "刷新", { maxResults: 200 }),
|
|
143
|
+
...await searchTextNodeIds(client, "refresh", { maxResults: 50 })
|
|
144
|
+
];
|
|
145
|
+
const selectorNodeIds = textNodeIds.length
|
|
146
|
+
? await querySelectorAll(client, frameNodeId, selector)
|
|
147
|
+
: [];
|
|
148
|
+
const nodeIds = uniqueNodeIds([...textNodeIds, ...selectorNodeIds]).slice(0, maxCandidates);
|
|
149
|
+
const candidates = [];
|
|
150
|
+
for (let index = 0; index < nodeIds.length; index += 1) {
|
|
151
|
+
const nodeId = nodeIds[index];
|
|
152
|
+
let outerHTML = "";
|
|
153
|
+
try {
|
|
154
|
+
outerHTML = await getOuterHTML(client, nodeId);
|
|
155
|
+
} catch {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const label = normalizeRefreshButtonLabel(outerHTML);
|
|
159
|
+
if (!isRefreshButtonLabel(label)) continue;
|
|
160
|
+
|
|
161
|
+
let box = null;
|
|
162
|
+
try {
|
|
163
|
+
box = await getNodeBox(client, nodeId);
|
|
164
|
+
} catch {
|
|
165
|
+
// Some text matches can be hidden or stale. Keep the label out of the click set.
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
candidates.push({
|
|
169
|
+
node_id: nodeId,
|
|
170
|
+
index,
|
|
171
|
+
label,
|
|
172
|
+
box,
|
|
173
|
+
rank: refreshButtonRank({ label })
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return candidates.sort((left, right) => {
|
|
178
|
+
const rankDiff = left.rank - right.rank;
|
|
179
|
+
if (rankDiff !== 0) return rankDiff;
|
|
180
|
+
return (right.box?.rect?.y || 0) - (left.box?.rect?.y || 0);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function clickRecommendEndRefreshButton(client, frameNodeId, {
|
|
185
|
+
settleMs = 5000
|
|
186
|
+
} = {}) {
|
|
187
|
+
const beforeCardCount = (await findRecommendCardNodeIds(client, frameNodeId)).length;
|
|
188
|
+
const candidates = await findRecommendEndRefreshButtons(client, frameNodeId);
|
|
189
|
+
if (!candidates.length) {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
method: "end_refresh_button",
|
|
193
|
+
reason: "refresh_button_not_found",
|
|
194
|
+
before_card_count: beforeCardCount,
|
|
195
|
+
candidates: []
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const attempts = [];
|
|
200
|
+
for (const candidate of candidates) {
|
|
201
|
+
try {
|
|
202
|
+
const box = await clickNodeCenter(client, candidate.node_id, { scrollIntoView: true });
|
|
203
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
204
|
+
const afterCardCount = (await findRecommendCardNodeIds(client, frameNodeId)).length;
|
|
205
|
+
return {
|
|
206
|
+
ok: true,
|
|
207
|
+
method: "end_refresh_button",
|
|
208
|
+
clicked: true,
|
|
209
|
+
node_id: candidate.node_id,
|
|
210
|
+
label: candidate.label,
|
|
211
|
+
box,
|
|
212
|
+
before_card_count: beforeCardCount,
|
|
213
|
+
after_card_count: afterCardCount,
|
|
214
|
+
settle_ms: settleMs,
|
|
215
|
+
candidates: candidates.map((item) => ({
|
|
216
|
+
node_id: item.node_id,
|
|
217
|
+
label: item.label,
|
|
218
|
+
y: item.box?.rect?.y || null
|
|
219
|
+
})).slice(0, 10),
|
|
220
|
+
attempts
|
|
221
|
+
};
|
|
222
|
+
} catch (error) {
|
|
223
|
+
attempts.push({
|
|
224
|
+
node_id: candidate.node_id,
|
|
225
|
+
label: candidate.label,
|
|
226
|
+
error: error?.message || String(error)
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
method: "end_refresh_button",
|
|
234
|
+
reason: "refresh_button_click_failed",
|
|
235
|
+
before_card_count: beforeCardCount,
|
|
236
|
+
attempts,
|
|
237
|
+
candidates: candidates.map((item) => ({
|
|
238
|
+
node_id: item.node_id,
|
|
239
|
+
label: item.label,
|
|
240
|
+
y: item.box?.rect?.y || null
|
|
241
|
+
})).slice(0, 10)
|
|
242
|
+
};
|
|
243
|
+
}
|