@reconcrap/boss-recommend-mcp 2.0.53 → 2.0.55
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 +0 -0
- package/config/screening-config.example.json +1 -1
- package/package.json +120 -120
- package/src/cli.js +3121 -3121
- package/src/core/run/index.js +310 -310
- package/src/domains/chat/constants.js +229 -220
- package/src/domains/chat/detail.js +1686 -1653
- package/src/domains/chat/run-service.js +2039 -1979
- package/src/domains/common/account-rights-panel.js +314 -0
- package/src/domains/recommend/detail.js +546 -531
- package/src/domains/recommend/run-service.js +1245 -1180
- package/src/domains/recruit/detail.js +15 -0
- package/src/domains/recruit/run-service.js +78 -1
- package/src/recommend-mcp.js +1701 -1701
- package/src/run-state.js +358 -358
|
@@ -3,26 +3,30 @@ import {
|
|
|
3
3
|
clickPoint,
|
|
4
4
|
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
5
|
getFrameDocumentNodeId,
|
|
6
|
-
getNodeBox,
|
|
7
|
-
getOuterHTML,
|
|
8
|
-
pressKey,
|
|
9
|
-
querySelectorAll,
|
|
10
|
-
sleep
|
|
11
|
-
} from "../../core/browser/index.js";
|
|
6
|
+
getNodeBox,
|
|
7
|
+
getOuterHTML,
|
|
8
|
+
pressKey,
|
|
9
|
+
querySelectorAll,
|
|
10
|
+
sleep
|
|
11
|
+
} from "../../core/browser/index.js";
|
|
12
12
|
import { candidateKeyFromProfile } from "../../core/infinite-list/index.js";
|
|
13
13
|
import {
|
|
14
14
|
buildScreeningCandidateFromDetail,
|
|
15
15
|
htmlToText
|
|
16
16
|
} from "../../core/screening/index.js";
|
|
17
17
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
closeBossAccountRightsBlockingPanel,
|
|
19
|
+
findBossAccountRightsBlockingPanel
|
|
20
|
+
} from "../common/account-rights-panel.js";
|
|
21
|
+
import {
|
|
22
|
+
DETAIL_CLOSE_SELECTORS,
|
|
23
|
+
DETAIL_NETWORK_PATTERNS,
|
|
24
|
+
DETAIL_POPUP_SELECTORS,
|
|
25
|
+
DETAIL_RESUME_IFRAME_SELECTORS
|
|
26
|
+
} from "./constants.js";
|
|
27
|
+
import {
|
|
28
|
+
getRecommendRoots
|
|
29
|
+
} from "./roots.js";
|
|
26
30
|
import {
|
|
27
31
|
findRecommendCardNodeIds,
|
|
28
32
|
readRecommendCardCandidate
|
|
@@ -38,149 +42,160 @@ const DETAIL_OUTSIDE_CLOSE_BOUNDARY_SELECTORS = Object.freeze([
|
|
|
38
42
|
]);
|
|
39
43
|
|
|
40
44
|
export function matchesRecommendDetailNetwork(url) {
|
|
41
|
-
return DETAIL_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
|
|
42
|
-
}
|
|
43
|
-
|
|
45
|
+
return DETAIL_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
export function createRecommendDetailNetworkRecorder(client) {
|
|
45
|
-
const events = [];
|
|
46
|
-
client.Network.responseReceived((event) => {
|
|
47
|
-
const url = event?.response?.url || "";
|
|
48
|
-
if (!matchesRecommendDetailNetwork(url)) return;
|
|
49
|
-
events.push({
|
|
50
|
-
requestId: event.requestId,
|
|
51
|
-
url,
|
|
52
|
-
status: event.response?.status,
|
|
53
|
-
mimeType: event.response?.mimeType,
|
|
54
|
-
type: event.type
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
if (typeof client.Network.loadingFinished === "function") {
|
|
58
|
-
client.Network.loadingFinished((event) => {
|
|
59
|
-
const found = events.find((item) => item.requestId === event.requestId);
|
|
60
|
-
if (!found) return;
|
|
61
|
-
found.loading_finished = true;
|
|
62
|
-
found.encodedDataLength = event.encodedDataLength;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
if (typeof client.Network.loadingFailed === "function") {
|
|
66
|
-
client.Network.loadingFailed((event) => {
|
|
67
|
-
const found = events.find((item) => item.requestId === event.requestId);
|
|
68
|
-
if (!found) return;
|
|
69
|
-
found.loading_failed = true;
|
|
70
|
-
found.loading_error = event.errorText || event.blockedReason || "Network loading failed";
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
events,
|
|
75
|
-
clear() {
|
|
76
|
-
events.length = 0;
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function waitForRecommendDetailNetworkEvents(recorder, {
|
|
82
|
-
minCount = 1,
|
|
83
|
-
requireLoaded = true,
|
|
84
|
-
timeoutMs = 3500,
|
|
85
|
-
intervalMs = 100
|
|
86
|
-
} = {}) {
|
|
87
|
-
const started = Date.now();
|
|
88
|
-
const events = Array.isArray(recorder) ? recorder : recorder?.events || [];
|
|
89
|
-
let matching = [];
|
|
90
|
-
while (Date.now() - started <= timeoutMs) {
|
|
91
|
-
matching = events.filter((event) => (
|
|
92
|
-
!requireLoaded
|
|
93
|
-
|| event.loading_finished === true
|
|
94
|
-
|| event.loading_failed === true
|
|
95
|
-
));
|
|
96
|
-
if (matching.length >= minCount) {
|
|
97
|
-
return {
|
|
98
|
-
ok: true,
|
|
99
|
-
elapsed_ms: Date.now() - started,
|
|
100
|
-
count: matching.length,
|
|
101
|
-
events: matching
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
await sleep(intervalMs);
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
ok: false,
|
|
108
|
-
elapsed_ms: Date.now() - started,
|
|
109
|
-
count: matching.length,
|
|
110
|
-
events: matching,
|
|
111
|
-
total_event_count: events.length
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export async function readRecommendDetailNetworkBodies(client, events = [], {
|
|
116
|
-
limit = 10
|
|
117
|
-
} = {}) {
|
|
118
|
-
const bodies = [];
|
|
119
|
-
for (const event of events.slice(0, limit)) {
|
|
120
|
-
try {
|
|
121
|
-
const body = await client.Network.getResponseBody({ requestId: event.requestId });
|
|
122
|
-
bodies.push({
|
|
123
|
-
...event,
|
|
124
|
-
body,
|
|
125
|
-
body_length: String(body?.body || "").length
|
|
126
|
-
});
|
|
127
|
-
} catch (error) {
|
|
128
|
-
bodies.push({
|
|
129
|
-
...event,
|
|
130
|
-
body_error: error?.message || String(error)
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return bodies;
|
|
49
|
+
const events = [];
|
|
50
|
+
client.Network.responseReceived((event) => {
|
|
51
|
+
const url = event?.response?.url || "";
|
|
52
|
+
if (!matchesRecommendDetailNetwork(url)) return;
|
|
53
|
+
events.push({
|
|
54
|
+
requestId: event.requestId,
|
|
55
|
+
url,
|
|
56
|
+
status: event.response?.status,
|
|
57
|
+
mimeType: event.response?.mimeType,
|
|
58
|
+
type: event.type
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
if (typeof client.Network.loadingFinished === "function") {
|
|
62
|
+
client.Network.loadingFinished((event) => {
|
|
63
|
+
const found = events.find((item) => item.requestId === event.requestId);
|
|
64
|
+
if (!found) return;
|
|
65
|
+
found.loading_finished = true;
|
|
66
|
+
found.encodedDataLength = event.encodedDataLength;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (typeof client.Network.loadingFailed === "function") {
|
|
70
|
+
client.Network.loadingFailed((event) => {
|
|
71
|
+
const found = events.find((item) => item.requestId === event.requestId);
|
|
72
|
+
if (!found) return;
|
|
73
|
+
found.loading_failed = true;
|
|
74
|
+
found.loading_error = event.errorText || event.blockedReason || "Network loading failed";
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
events,
|
|
79
|
+
clear() {
|
|
80
|
+
events.length = 0;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
135
83
|
}
|
|
136
84
|
|
|
137
|
-
export async function
|
|
138
|
-
|
|
139
|
-
intervalMs = 250
|
|
140
|
-
} = {}) {
|
|
141
|
-
const started = Date.now();
|
|
142
|
-
let lastState = null;
|
|
143
|
-
while (Date.now() - started <= timeoutMs) {
|
|
144
|
-
lastState = await readRecommendDetailState(client);
|
|
145
|
-
if (lastState?.popup || lastState?.resumeIframe) return lastState;
|
|
146
|
-
await sleep(intervalMs);
|
|
147
|
-
}
|
|
148
|
-
return lastState;
|
|
85
|
+
export async function findRecommendBlockingPanel(client, options = {}) {
|
|
86
|
+
return findBossAccountRightsBlockingPanel(client, options);
|
|
149
87
|
}
|
|
150
88
|
|
|
151
|
-
async function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
iframe: rootState.iframe,
|
|
157
|
-
roots: rootState.roots,
|
|
158
|
-
popup,
|
|
159
|
-
resumeIframe
|
|
160
|
-
};
|
|
89
|
+
export async function closeRecommendBlockingPanels(client, options = {}) {
|
|
90
|
+
return closeBossAccountRightsBlockingPanel(client, {
|
|
91
|
+
resolveRoots: getRecommendRoots,
|
|
92
|
+
...options
|
|
93
|
+
});
|
|
161
94
|
}
|
|
162
95
|
|
|
96
|
+
export async function waitForRecommendDetailNetworkEvents(recorder, {
|
|
97
|
+
minCount = 1,
|
|
98
|
+
requireLoaded = true,
|
|
99
|
+
timeoutMs = 3500,
|
|
100
|
+
intervalMs = 100
|
|
101
|
+
} = {}) {
|
|
102
|
+
const started = Date.now();
|
|
103
|
+
const events = Array.isArray(recorder) ? recorder : recorder?.events || [];
|
|
104
|
+
let matching = [];
|
|
105
|
+
while (Date.now() - started <= timeoutMs) {
|
|
106
|
+
matching = events.filter((event) => (
|
|
107
|
+
!requireLoaded
|
|
108
|
+
|| event.loading_finished === true
|
|
109
|
+
|| event.loading_failed === true
|
|
110
|
+
));
|
|
111
|
+
if (matching.length >= minCount) {
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
elapsed_ms: Date.now() - started,
|
|
115
|
+
count: matching.length,
|
|
116
|
+
events: matching
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
await sleep(intervalMs);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
elapsed_ms: Date.now() - started,
|
|
124
|
+
count: matching.length,
|
|
125
|
+
events: matching,
|
|
126
|
+
total_event_count: events.length
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function readRecommendDetailNetworkBodies(client, events = [], {
|
|
131
|
+
limit = 10
|
|
132
|
+
} = {}) {
|
|
133
|
+
const bodies = [];
|
|
134
|
+
for (const event of events.slice(0, limit)) {
|
|
135
|
+
try {
|
|
136
|
+
const body = await client.Network.getResponseBody({ requestId: event.requestId });
|
|
137
|
+
bodies.push({
|
|
138
|
+
...event,
|
|
139
|
+
body,
|
|
140
|
+
body_length: String(body?.body || "").length
|
|
141
|
+
});
|
|
142
|
+
} catch (error) {
|
|
143
|
+
bodies.push({
|
|
144
|
+
...event,
|
|
145
|
+
body_error: error?.message || String(error)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return bodies;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function waitForRecommendDetail(client, {
|
|
153
|
+
timeoutMs = 10000,
|
|
154
|
+
intervalMs = 250
|
|
155
|
+
} = {}) {
|
|
156
|
+
const started = Date.now();
|
|
157
|
+
let lastState = null;
|
|
158
|
+
while (Date.now() - started <= timeoutMs) {
|
|
159
|
+
lastState = await readRecommendDetailState(client);
|
|
160
|
+
if (lastState?.popup || lastState?.resumeIframe) return lastState;
|
|
161
|
+
await sleep(intervalMs);
|
|
162
|
+
}
|
|
163
|
+
return lastState;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function readRecommendDetailState(client) {
|
|
167
|
+
const rootState = await getRecommendRoots(client);
|
|
168
|
+
const popup = await findVisibleDetailTarget(client, rootState.roots, DETAIL_POPUP_SELECTORS);
|
|
169
|
+
const resumeIframe = await findVisibleDetailTarget(client, rootState.roots, DETAIL_RESUME_IFRAME_SELECTORS);
|
|
170
|
+
return {
|
|
171
|
+
iframe: rootState.iframe,
|
|
172
|
+
roots: rootState.roots,
|
|
173
|
+
popup,
|
|
174
|
+
resumeIframe
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
163
178
|
export async function waitForRecommendDetailClosed(client, {
|
|
164
179
|
timeoutMs = 4000,
|
|
165
180
|
intervalMs = 250
|
|
166
181
|
} = {}) {
|
|
167
|
-
const started = Date.now();
|
|
168
|
-
let lastState = null;
|
|
169
|
-
while (Date.now() - started <= timeoutMs) {
|
|
170
|
-
lastState = await readRecommendDetailState(client);
|
|
171
|
-
if (!lastState?.popup && !lastState?.resumeIframe) {
|
|
172
|
-
return {
|
|
173
|
-
closed: true,
|
|
174
|
-
elapsed_ms: Date.now() - started,
|
|
175
|
-
state: lastState
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
await sleep(intervalMs);
|
|
179
|
-
}
|
|
180
|
-
return {
|
|
181
|
-
closed: false,
|
|
182
|
-
elapsed_ms: Date.now() - started,
|
|
183
|
-
state: lastState
|
|
182
|
+
const started = Date.now();
|
|
183
|
+
let lastState = null;
|
|
184
|
+
while (Date.now() - started <= timeoutMs) {
|
|
185
|
+
lastState = await readRecommendDetailState(client);
|
|
186
|
+
if (!lastState?.popup && !lastState?.resumeIframe) {
|
|
187
|
+
return {
|
|
188
|
+
closed: true,
|
|
189
|
+
elapsed_ms: Date.now() - started,
|
|
190
|
+
state: lastState
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
await sleep(intervalMs);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
closed: false,
|
|
197
|
+
elapsed_ms: Date.now() - started,
|
|
198
|
+
state: lastState
|
|
184
199
|
};
|
|
185
200
|
}
|
|
186
201
|
|
|
@@ -242,74 +257,74 @@ async function verifyRecommendDetailStillOpen(client, {
|
|
|
242
257
|
async function findVisibleDetailTarget(client, roots, selectors) {
|
|
243
258
|
for (const root of roots) {
|
|
244
259
|
if (!root?.nodeId) continue;
|
|
245
|
-
for (const selector of selectors) {
|
|
246
|
-
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
247
|
-
for (const nodeId of nodeIds) {
|
|
248
|
-
try {
|
|
249
|
-
const box = await getNodeBox(client, nodeId);
|
|
250
|
-
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
251
|
-
return {
|
|
252
|
-
root: root.name,
|
|
253
|
-
root_node_id: root.nodeId,
|
|
254
|
-
selector,
|
|
255
|
-
node_id: nodeId,
|
|
256
|
-
center: box.center,
|
|
257
|
-
rect: box.rect
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
} catch {}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export async function readRecommendDetailHtml(client, detailState) {
|
|
268
|
-
let popupHTML = "";
|
|
269
|
-
let resumeHTML = "";
|
|
270
|
-
let resumeIframeDocumentNodeId = null;
|
|
271
|
-
const errors = [];
|
|
272
|
-
|
|
273
|
-
if (detailState?.popup?.node_id) {
|
|
274
|
-
try {
|
|
275
|
-
popupHTML = await getOuterHTML(client, detailState.popup.node_id);
|
|
276
|
-
} catch (error) {
|
|
277
|
-
errors.push({
|
|
278
|
-
source: "popup",
|
|
279
|
-
node_id: detailState.popup.node_id,
|
|
280
|
-
stale_node: isStaleRecommendNodeError(error),
|
|
281
|
-
error: error?.message || String(error)
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (detailState?.resumeIframe?.node_id) {
|
|
287
|
-
try {
|
|
288
|
-
resumeIframeDocumentNodeId = await getFrameDocumentNodeId(client, detailState.resumeIframe.node_id);
|
|
289
|
-
resumeHTML = await getOuterHTML(client, resumeIframeDocumentNodeId);
|
|
290
|
-
} catch (error) {
|
|
291
|
-
errors.push({
|
|
292
|
-
source: "resume_iframe",
|
|
293
|
-
node_id: detailState.resumeIframe.node_id,
|
|
294
|
-
document_node_id: resumeIframeDocumentNodeId,
|
|
295
|
-
stale_node: isStaleRecommendNodeError(error),
|
|
296
|
-
error: error?.message || String(error)
|
|
297
|
-
});
|
|
298
|
-
resumeIframeDocumentNodeId = null;
|
|
299
|
-
resumeHTML = "";
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
popupHTML,
|
|
305
|
-
resumeHTML,
|
|
306
|
-
resumeIframeDocumentNodeId,
|
|
307
|
-
popupText: htmlToText(popupHTML),
|
|
308
|
-
resumeText: htmlToText(resumeHTML),
|
|
309
|
-
errors
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
|
|
260
|
+
for (const selector of selectors) {
|
|
261
|
+
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
262
|
+
for (const nodeId of nodeIds) {
|
|
263
|
+
try {
|
|
264
|
+
const box = await getNodeBox(client, nodeId);
|
|
265
|
+
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
266
|
+
return {
|
|
267
|
+
root: root.name,
|
|
268
|
+
root_node_id: root.nodeId,
|
|
269
|
+
selector,
|
|
270
|
+
node_id: nodeId,
|
|
271
|
+
center: box.center,
|
|
272
|
+
rect: box.rect
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export async function readRecommendDetailHtml(client, detailState) {
|
|
283
|
+
let popupHTML = "";
|
|
284
|
+
let resumeHTML = "";
|
|
285
|
+
let resumeIframeDocumentNodeId = null;
|
|
286
|
+
const errors = [];
|
|
287
|
+
|
|
288
|
+
if (detailState?.popup?.node_id) {
|
|
289
|
+
try {
|
|
290
|
+
popupHTML = await getOuterHTML(client, detailState.popup.node_id);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
errors.push({
|
|
293
|
+
source: "popup",
|
|
294
|
+
node_id: detailState.popup.node_id,
|
|
295
|
+
stale_node: isStaleRecommendNodeError(error),
|
|
296
|
+
error: error?.message || String(error)
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (detailState?.resumeIframe?.node_id) {
|
|
302
|
+
try {
|
|
303
|
+
resumeIframeDocumentNodeId = await getFrameDocumentNodeId(client, detailState.resumeIframe.node_id);
|
|
304
|
+
resumeHTML = await getOuterHTML(client, resumeIframeDocumentNodeId);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
errors.push({
|
|
307
|
+
source: "resume_iframe",
|
|
308
|
+
node_id: detailState.resumeIframe.node_id,
|
|
309
|
+
document_node_id: resumeIframeDocumentNodeId,
|
|
310
|
+
stale_node: isStaleRecommendNodeError(error),
|
|
311
|
+
error: error?.message || String(error)
|
|
312
|
+
});
|
|
313
|
+
resumeIframeDocumentNodeId = null;
|
|
314
|
+
resumeHTML = "";
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
popupHTML,
|
|
320
|
+
resumeHTML,
|
|
321
|
+
resumeIframeDocumentNodeId,
|
|
322
|
+
popupText: htmlToText(popupHTML),
|
|
323
|
+
resumeText: htmlToText(resumeHTML),
|
|
324
|
+
errors
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
313
328
|
export function isStaleRecommendNodeError(error) {
|
|
314
329
|
const message = String(error?.message || error || "");
|
|
315
330
|
return /Could not find node with given id|No node with given id|Node is detached|Cannot find node|Could not compute box model/i.test(message);
|
|
@@ -321,138 +336,138 @@ export function isRecommendDetailOpenMissError(error) {
|
|
|
321
336
|
}
|
|
322
337
|
|
|
323
338
|
export async function findRecommendCardNodeForCandidateKey(client, {
|
|
324
|
-
candidateKey = "",
|
|
325
|
-
rootState = null,
|
|
326
|
-
targetUrl = "",
|
|
327
|
-
source = "recommend-run-card-retry",
|
|
328
|
-
timeoutMs = 5000,
|
|
329
|
-
intervalMs = 250
|
|
330
|
-
} = {}) {
|
|
331
|
-
if (!candidateKey) {
|
|
332
|
-
return {
|
|
333
|
-
ok: false,
|
|
334
|
-
reason: "candidate_key_required"
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const started = Date.now();
|
|
339
|
-
let lastError = null;
|
|
340
|
-
let lastCardCount = 0;
|
|
341
|
-
while (Date.now() - started <= timeoutMs) {
|
|
342
|
-
const currentRootState = rootState?.iframe?.documentNodeId
|
|
343
|
-
? rootState
|
|
344
|
-
: await getRecommendRoots(client);
|
|
345
|
-
const frameNodeId = currentRootState?.iframe?.documentNodeId;
|
|
346
|
-
if (!frameNodeId) {
|
|
347
|
-
return {
|
|
348
|
-
ok: false,
|
|
349
|
-
reason: "recommend_frame_not_found"
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const nodeIds = await findRecommendCardNodeIds(client, frameNodeId);
|
|
354
|
-
lastCardCount = nodeIds.length;
|
|
355
|
-
for (let visibleIndex = 0; visibleIndex < nodeIds.length; visibleIndex += 1) {
|
|
356
|
-
const nodeId = nodeIds[visibleIndex];
|
|
357
|
-
try {
|
|
358
|
-
const candidate = await readRecommendCardCandidate(client, nodeId, {
|
|
359
|
-
targetUrl,
|
|
360
|
-
source,
|
|
361
|
-
metadata: {
|
|
362
|
-
visible_index: visibleIndex,
|
|
363
|
-
retry_reason: "stale_detail_node"
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
const key = candidateKeyFromProfile(candidate, {
|
|
367
|
-
nodeId,
|
|
368
|
-
visibleIndex,
|
|
369
|
-
attributes: candidate?.attributes || candidate?.metadata?.attributes || {}
|
|
370
|
-
});
|
|
371
|
-
if (key === candidateKey) {
|
|
372
|
-
return {
|
|
373
|
-
ok: true,
|
|
374
|
-
node_id: nodeId,
|
|
375
|
-
visible_index: visibleIndex,
|
|
376
|
-
candidate,
|
|
377
|
-
key,
|
|
378
|
-
root_state: currentRootState,
|
|
379
|
-
card_count: nodeIds.length
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
} catch (error) {
|
|
383
|
-
lastError = error;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (intervalMs > 0) await sleep(intervalMs);
|
|
388
|
-
rootState = null;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
return {
|
|
392
|
-
ok: false,
|
|
393
|
-
reason: "candidate_key_not_mounted",
|
|
394
|
-
candidate_key: candidateKey,
|
|
395
|
-
last_card_count: lastCardCount,
|
|
396
|
-
error: lastError?.message || null
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
export async function openRecommendCardDetail(client, cardNodeId, {
|
|
401
|
-
timeoutMs = 12000,
|
|
402
|
-
scrollIntoView = true
|
|
403
|
-
} = {}) {
|
|
404
|
-
const started = Date.now();
|
|
405
|
-
const clickStarted = Date.now();
|
|
406
|
-
const cardBox = await clickNodeCenter(client, cardNodeId, { scrollIntoView });
|
|
407
|
-
const candidateClickMs = Date.now() - clickStarted;
|
|
408
|
-
const detailStarted = Date.now();
|
|
409
|
-
const detailState = await waitForRecommendDetail(client, { timeoutMs });
|
|
410
|
-
const detailOpenMs = Date.now() - detailStarted;
|
|
411
|
-
if (!detailState?.popup && !detailState?.resumeIframe) {
|
|
412
|
-
throw new Error("Candidate detail did not open or no known detail selectors mounted");
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
return {
|
|
416
|
-
card_box: cardBox,
|
|
417
|
-
detail_state: detailState,
|
|
418
|
-
timings: {
|
|
419
|
-
candidate_click_ms: candidateClickMs,
|
|
420
|
-
detail_open_ms: detailOpenMs,
|
|
421
|
-
open_total_ms: Date.now() - started
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
427
|
-
cardNodeId,
|
|
428
|
-
candidateKey = "",
|
|
429
|
-
cardCandidate = null,
|
|
430
|
-
rootState = null,
|
|
431
|
-
targetUrl = "",
|
|
432
|
-
timeoutMs = 12000,
|
|
433
|
-
scrollIntoView = true,
|
|
434
|
-
retryTimeoutMs = 5000,
|
|
435
|
-
retryIntervalMs = 250,
|
|
436
|
-
maxAttempts = 2
|
|
437
|
-
} = {}) {
|
|
438
|
-
let currentNodeId = cardNodeId;
|
|
439
|
-
let currentCandidate = cardCandidate;
|
|
440
|
-
let currentRootState = rootState;
|
|
441
|
-
const attempts = [];
|
|
442
|
-
const limit = Math.max(1, Number(maxAttempts) || 1);
|
|
443
|
-
|
|
444
|
-
for (let attemptIndex = 0; attemptIndex < limit; attemptIndex += 1) {
|
|
445
|
-
try {
|
|
446
|
-
const opened = await openRecommendCardDetail(client, currentNodeId, {
|
|
447
|
-
timeoutMs,
|
|
448
|
-
scrollIntoView
|
|
449
|
-
});
|
|
450
|
-
return {
|
|
451
|
-
...opened,
|
|
452
|
-
card_node_id: currentNodeId,
|
|
453
|
-
card_candidate: currentCandidate,
|
|
454
|
-
retry_attempts: attempts
|
|
455
|
-
};
|
|
339
|
+
candidateKey = "",
|
|
340
|
+
rootState = null,
|
|
341
|
+
targetUrl = "",
|
|
342
|
+
source = "recommend-run-card-retry",
|
|
343
|
+
timeoutMs = 5000,
|
|
344
|
+
intervalMs = 250
|
|
345
|
+
} = {}) {
|
|
346
|
+
if (!candidateKey) {
|
|
347
|
+
return {
|
|
348
|
+
ok: false,
|
|
349
|
+
reason: "candidate_key_required"
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const started = Date.now();
|
|
354
|
+
let lastError = null;
|
|
355
|
+
let lastCardCount = 0;
|
|
356
|
+
while (Date.now() - started <= timeoutMs) {
|
|
357
|
+
const currentRootState = rootState?.iframe?.documentNodeId
|
|
358
|
+
? rootState
|
|
359
|
+
: await getRecommendRoots(client);
|
|
360
|
+
const frameNodeId = currentRootState?.iframe?.documentNodeId;
|
|
361
|
+
if (!frameNodeId) {
|
|
362
|
+
return {
|
|
363
|
+
ok: false,
|
|
364
|
+
reason: "recommend_frame_not_found"
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const nodeIds = await findRecommendCardNodeIds(client, frameNodeId);
|
|
369
|
+
lastCardCount = nodeIds.length;
|
|
370
|
+
for (let visibleIndex = 0; visibleIndex < nodeIds.length; visibleIndex += 1) {
|
|
371
|
+
const nodeId = nodeIds[visibleIndex];
|
|
372
|
+
try {
|
|
373
|
+
const candidate = await readRecommendCardCandidate(client, nodeId, {
|
|
374
|
+
targetUrl,
|
|
375
|
+
source,
|
|
376
|
+
metadata: {
|
|
377
|
+
visible_index: visibleIndex,
|
|
378
|
+
retry_reason: "stale_detail_node"
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
const key = candidateKeyFromProfile(candidate, {
|
|
382
|
+
nodeId,
|
|
383
|
+
visibleIndex,
|
|
384
|
+
attributes: candidate?.attributes || candidate?.metadata?.attributes || {}
|
|
385
|
+
});
|
|
386
|
+
if (key === candidateKey) {
|
|
387
|
+
return {
|
|
388
|
+
ok: true,
|
|
389
|
+
node_id: nodeId,
|
|
390
|
+
visible_index: visibleIndex,
|
|
391
|
+
candidate,
|
|
392
|
+
key,
|
|
393
|
+
root_state: currentRootState,
|
|
394
|
+
card_count: nodeIds.length
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
} catch (error) {
|
|
398
|
+
lastError = error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (intervalMs > 0) await sleep(intervalMs);
|
|
403
|
+
rootState = null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
ok: false,
|
|
408
|
+
reason: "candidate_key_not_mounted",
|
|
409
|
+
candidate_key: candidateKey,
|
|
410
|
+
last_card_count: lastCardCount,
|
|
411
|
+
error: lastError?.message || null
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export async function openRecommendCardDetail(client, cardNodeId, {
|
|
416
|
+
timeoutMs = 12000,
|
|
417
|
+
scrollIntoView = true
|
|
418
|
+
} = {}) {
|
|
419
|
+
const started = Date.now();
|
|
420
|
+
const clickStarted = Date.now();
|
|
421
|
+
const cardBox = await clickNodeCenter(client, cardNodeId, { scrollIntoView });
|
|
422
|
+
const candidateClickMs = Date.now() - clickStarted;
|
|
423
|
+
const detailStarted = Date.now();
|
|
424
|
+
const detailState = await waitForRecommendDetail(client, { timeoutMs });
|
|
425
|
+
const detailOpenMs = Date.now() - detailStarted;
|
|
426
|
+
if (!detailState?.popup && !detailState?.resumeIframe) {
|
|
427
|
+
throw new Error("Candidate detail did not open or no known detail selectors mounted");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
card_box: cardBox,
|
|
432
|
+
detail_state: detailState,
|
|
433
|
+
timings: {
|
|
434
|
+
candidate_click_ms: candidateClickMs,
|
|
435
|
+
detail_open_ms: detailOpenMs,
|
|
436
|
+
open_total_ms: Date.now() - started
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
442
|
+
cardNodeId,
|
|
443
|
+
candidateKey = "",
|
|
444
|
+
cardCandidate = null,
|
|
445
|
+
rootState = null,
|
|
446
|
+
targetUrl = "",
|
|
447
|
+
timeoutMs = 12000,
|
|
448
|
+
scrollIntoView = true,
|
|
449
|
+
retryTimeoutMs = 5000,
|
|
450
|
+
retryIntervalMs = 250,
|
|
451
|
+
maxAttempts = 2
|
|
452
|
+
} = {}) {
|
|
453
|
+
let currentNodeId = cardNodeId;
|
|
454
|
+
let currentCandidate = cardCandidate;
|
|
455
|
+
let currentRootState = rootState;
|
|
456
|
+
const attempts = [];
|
|
457
|
+
const limit = Math.max(1, Number(maxAttempts) || 1);
|
|
458
|
+
|
|
459
|
+
for (let attemptIndex = 0; attemptIndex < limit; attemptIndex += 1) {
|
|
460
|
+
try {
|
|
461
|
+
const opened = await openRecommendCardDetail(client, currentNodeId, {
|
|
462
|
+
timeoutMs,
|
|
463
|
+
scrollIntoView
|
|
464
|
+
});
|
|
465
|
+
return {
|
|
466
|
+
...opened,
|
|
467
|
+
card_node_id: currentNodeId,
|
|
468
|
+
card_candidate: currentCandidate,
|
|
469
|
+
retry_attempts: attempts
|
|
470
|
+
};
|
|
456
471
|
} catch (error) {
|
|
457
472
|
const stale = isStaleRecommendNodeError(error);
|
|
458
473
|
const detailOpenMiss = isRecommendDetailOpenMissError(error);
|
|
@@ -467,88 +482,88 @@ export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
|
467
482
|
error.recommend_detail_open_attempts = attempts;
|
|
468
483
|
throw error;
|
|
469
484
|
}
|
|
470
|
-
|
|
471
|
-
const resolved = await findRecommendCardNodeForCandidateKey(client, {
|
|
472
|
-
candidateKey,
|
|
473
|
-
rootState: currentRootState,
|
|
474
|
-
targetUrl,
|
|
475
|
-
timeoutMs: retryTimeoutMs,
|
|
476
|
-
intervalMs: retryIntervalMs
|
|
477
|
-
});
|
|
478
|
-
attempts[attempts.length - 1].refresh_lookup = {
|
|
479
|
-
ok: Boolean(resolved.ok),
|
|
480
|
-
node_id: resolved.node_id || null,
|
|
481
|
-
visible_index: resolved.visible_index ?? null,
|
|
482
|
-
card_count: resolved.card_count || resolved.last_card_count || 0,
|
|
483
|
-
reason: resolved.reason || null,
|
|
484
|
-
error: resolved.error || null
|
|
485
|
-
};
|
|
486
|
-
if (!resolved.ok || !resolved.node_id) {
|
|
487
|
-
error.recommend_detail_open_attempts = attempts;
|
|
488
|
-
throw error;
|
|
489
|
-
}
|
|
490
|
-
currentNodeId = resolved.node_id;
|
|
491
|
-
currentCandidate = resolved.candidate || currentCandidate;
|
|
492
|
-
currentRootState = resolved.root_state || null;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
throw new Error("Recommend detail retry exhausted");
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
export async function closeRecommendDetail(client, {
|
|
500
|
-
attemptsLimit = 4,
|
|
501
|
-
closeWaitMs = 5000,
|
|
502
|
-
escapeWaitMs = 3500
|
|
503
|
-
} = {}) {
|
|
504
|
-
const attempts = [];
|
|
505
|
-
for (let index = 0; index < attemptsLimit; index += 1) {
|
|
506
|
-
const existingState = await waitForRecommendDetail(client, { timeoutMs: 500 });
|
|
507
|
-
if (!existingState?.popup && !existingState?.resumeIframe) {
|
|
508
|
-
return {
|
|
509
|
-
closed: true,
|
|
510
|
-
attempts
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const rootState = await getRecommendRoots(client);
|
|
515
|
-
const closeTarget = await findVisibleCloseTarget(client, rootState.roots, DETAIL_CLOSE_SELECTORS);
|
|
516
|
-
if (closeTarget) {
|
|
517
|
-
try {
|
|
485
|
+
|
|
486
|
+
const resolved = await findRecommendCardNodeForCandidateKey(client, {
|
|
487
|
+
candidateKey,
|
|
488
|
+
rootState: currentRootState,
|
|
489
|
+
targetUrl,
|
|
490
|
+
timeoutMs: retryTimeoutMs,
|
|
491
|
+
intervalMs: retryIntervalMs
|
|
492
|
+
});
|
|
493
|
+
attempts[attempts.length - 1].refresh_lookup = {
|
|
494
|
+
ok: Boolean(resolved.ok),
|
|
495
|
+
node_id: resolved.node_id || null,
|
|
496
|
+
visible_index: resolved.visible_index ?? null,
|
|
497
|
+
card_count: resolved.card_count || resolved.last_card_count || 0,
|
|
498
|
+
reason: resolved.reason || null,
|
|
499
|
+
error: resolved.error || null
|
|
500
|
+
};
|
|
501
|
+
if (!resolved.ok || !resolved.node_id) {
|
|
502
|
+
error.recommend_detail_open_attempts = attempts;
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
currentNodeId = resolved.node_id;
|
|
506
|
+
currentCandidate = resolved.candidate || currentCandidate;
|
|
507
|
+
currentRootState = resolved.root_state || null;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
throw new Error("Recommend detail retry exhausted");
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export async function closeRecommendDetail(client, {
|
|
515
|
+
attemptsLimit = 4,
|
|
516
|
+
closeWaitMs = 5000,
|
|
517
|
+
escapeWaitMs = 3500
|
|
518
|
+
} = {}) {
|
|
519
|
+
const attempts = [];
|
|
520
|
+
for (let index = 0; index < attemptsLimit; index += 1) {
|
|
521
|
+
const existingState = await waitForRecommendDetail(client, { timeoutMs: 500 });
|
|
522
|
+
if (!existingState?.popup && !existingState?.resumeIframe) {
|
|
523
|
+
return {
|
|
524
|
+
closed: true,
|
|
525
|
+
attempts
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const rootState = await getRecommendRoots(client);
|
|
530
|
+
const closeTarget = await findVisibleCloseTarget(client, rootState.roots, DETAIL_CLOSE_SELECTORS);
|
|
531
|
+
if (closeTarget) {
|
|
532
|
+
try {
|
|
518
533
|
if (closeTarget.center) {
|
|
519
534
|
await clickPoint(client, closeTarget.center.x, closeTarget.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
520
535
|
} else {
|
|
521
536
|
await clickNodeCenter(client, closeTarget.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
522
537
|
}
|
|
523
|
-
attempts.push({
|
|
524
|
-
mode: "close-selector",
|
|
525
|
-
selector: closeTarget.selector,
|
|
526
|
-
root: closeTarget.root
|
|
527
|
-
});
|
|
528
|
-
} catch (error) {
|
|
529
|
-
attempts.push({
|
|
530
|
-
mode: "close-selector-error",
|
|
531
|
-
selector: closeTarget.selector,
|
|
532
|
-
root: closeTarget.root,
|
|
533
|
-
error: error?.message || String(error)
|
|
534
|
-
});
|
|
535
|
-
await pressEscape(client);
|
|
536
|
-
attempts.push({ mode: "Escape-after-close-selector-error" });
|
|
537
|
-
}
|
|
538
|
-
} else {
|
|
539
|
-
await pressEscape(client);
|
|
540
|
-
attempts.push({ mode: "Escape" });
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const closedAfterClick = await waitForRecommendDetailClosed(client, {
|
|
544
|
-
timeoutMs: closeWaitMs,
|
|
545
|
-
intervalMs: 250
|
|
546
|
-
});
|
|
547
|
-
attempts.push({
|
|
548
|
-
mode: "wait-closed-after-primary",
|
|
549
|
-
closed: closedAfterClick.closed,
|
|
550
|
-
elapsed_ms: closedAfterClick.elapsed_ms
|
|
551
|
-
});
|
|
538
|
+
attempts.push({
|
|
539
|
+
mode: "close-selector",
|
|
540
|
+
selector: closeTarget.selector,
|
|
541
|
+
root: closeTarget.root
|
|
542
|
+
});
|
|
543
|
+
} catch (error) {
|
|
544
|
+
attempts.push({
|
|
545
|
+
mode: "close-selector-error",
|
|
546
|
+
selector: closeTarget.selector,
|
|
547
|
+
root: closeTarget.root,
|
|
548
|
+
error: error?.message || String(error)
|
|
549
|
+
});
|
|
550
|
+
await pressEscape(client);
|
|
551
|
+
attempts.push({ mode: "Escape-after-close-selector-error" });
|
|
552
|
+
}
|
|
553
|
+
} else {
|
|
554
|
+
await pressEscape(client);
|
|
555
|
+
attempts.push({ mode: "Escape" });
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const closedAfterClick = await waitForRecommendDetailClosed(client, {
|
|
559
|
+
timeoutMs: closeWaitMs,
|
|
560
|
+
intervalMs: 250
|
|
561
|
+
});
|
|
562
|
+
attempts.push({
|
|
563
|
+
mode: "wait-closed-after-primary",
|
|
564
|
+
closed: closedAfterClick.closed,
|
|
565
|
+
elapsed_ms: closedAfterClick.elapsed_ms
|
|
566
|
+
});
|
|
552
567
|
if (closedAfterClick.closed) {
|
|
553
568
|
return {
|
|
554
569
|
closed: true,
|
|
@@ -578,22 +593,22 @@ export async function closeRecommendDetail(client, {
|
|
|
578
593
|
|
|
579
594
|
await pressEscape(client);
|
|
580
595
|
attempts.push({ mode: "Escape-fallback" });
|
|
581
|
-
|
|
582
|
-
const closedAfterEscape = await waitForRecommendDetailClosed(client, {
|
|
583
|
-
timeoutMs: escapeWaitMs,
|
|
584
|
-
intervalMs: 250
|
|
585
|
-
});
|
|
586
|
-
attempts.push({
|
|
587
|
-
mode: "wait-closed-after-escape",
|
|
588
|
-
closed: closedAfterEscape.closed,
|
|
589
|
-
elapsed_ms: closedAfterEscape.elapsed_ms
|
|
590
|
-
});
|
|
591
|
-
if (closedAfterEscape.closed) {
|
|
592
|
-
return {
|
|
593
|
-
closed: true,
|
|
594
|
-
attempts
|
|
595
|
-
};
|
|
596
|
-
}
|
|
596
|
+
|
|
597
|
+
const closedAfterEscape = await waitForRecommendDetailClosed(client, {
|
|
598
|
+
timeoutMs: escapeWaitMs,
|
|
599
|
+
intervalMs: 250
|
|
600
|
+
});
|
|
601
|
+
attempts.push({
|
|
602
|
+
mode: "wait-closed-after-escape",
|
|
603
|
+
closed: closedAfterEscape.closed,
|
|
604
|
+
elapsed_ms: closedAfterEscape.elapsed_ms
|
|
605
|
+
});
|
|
606
|
+
if (closedAfterEscape.closed) {
|
|
607
|
+
return {
|
|
608
|
+
closed: true,
|
|
609
|
+
attempts
|
|
610
|
+
};
|
|
611
|
+
}
|
|
597
612
|
}
|
|
598
613
|
|
|
599
614
|
const verification = await verifyRecommendDetailStillOpen(client);
|
|
@@ -621,37 +636,37 @@ export async function closeRecommendDetail(client, {
|
|
|
621
636
|
verification
|
|
622
637
|
};
|
|
623
638
|
}
|
|
624
|
-
|
|
625
|
-
async function findVisibleCloseTarget(client, roots, selectors) {
|
|
626
|
-
let fallback = null;
|
|
627
|
-
for (const root of roots) {
|
|
628
|
-
if (!root?.nodeId) continue;
|
|
629
|
-
for (const selector of selectors) {
|
|
630
|
-
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
631
|
-
for (const nodeId of nodeIds) {
|
|
632
|
-
const target = {
|
|
633
|
-
root: root.name,
|
|
634
|
-
root_node_id: root.nodeId,
|
|
635
|
-
selector,
|
|
636
|
-
node_id: nodeId
|
|
637
|
-
};
|
|
638
|
-
if (!fallback) fallback = target;
|
|
639
|
-
try {
|
|
640
|
-
const box = await getNodeBox(client, nodeId);
|
|
641
|
-
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
642
|
-
return {
|
|
643
|
-
...target,
|
|
644
|
-
center: box.center,
|
|
645
|
-
rect: box.rect
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
} catch {}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return fallback;
|
|
653
|
-
}
|
|
654
|
-
|
|
639
|
+
|
|
640
|
+
async function findVisibleCloseTarget(client, roots, selectors) {
|
|
641
|
+
let fallback = null;
|
|
642
|
+
for (const root of roots) {
|
|
643
|
+
if (!root?.nodeId) continue;
|
|
644
|
+
for (const selector of selectors) {
|
|
645
|
+
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
646
|
+
for (const nodeId of nodeIds) {
|
|
647
|
+
const target = {
|
|
648
|
+
root: root.name,
|
|
649
|
+
root_node_id: root.nodeId,
|
|
650
|
+
selector,
|
|
651
|
+
node_id: nodeId
|
|
652
|
+
};
|
|
653
|
+
if (!fallback) fallback = target;
|
|
654
|
+
try {
|
|
655
|
+
const box = await getNodeBox(client, nodeId);
|
|
656
|
+
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
657
|
+
return {
|
|
658
|
+
...target,
|
|
659
|
+
center: box.center,
|
|
660
|
+
rect: box.rect
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
} catch {}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return fallback;
|
|
668
|
+
}
|
|
669
|
+
|
|
655
670
|
async function pressEscape(client) {
|
|
656
671
|
await pressKey(client, "Escape", {
|
|
657
672
|
code: "Escape",
|
|
@@ -753,64 +768,64 @@ async function clickOutsideRecommendDetail(client, detailState) {
|
|
|
753
768
|
}
|
|
754
769
|
|
|
755
770
|
export async function extractRecommendDetailCandidate(client, {
|
|
756
|
-
cardCandidate,
|
|
757
|
-
cardNodeId,
|
|
758
|
-
detailState,
|
|
759
|
-
networkEvents = [],
|
|
760
|
-
targetUrl = "",
|
|
761
|
-
closeDetail = true,
|
|
762
|
-
networkParseRetryMs = 1800,
|
|
763
|
-
networkParseIntervalMs = 250
|
|
764
|
-
} = {}) {
|
|
765
|
-
const detailHtml = await readRecommendDetailHtml(client, detailState);
|
|
766
|
-
const detailText = [
|
|
767
|
-
detailHtml.popupText,
|
|
768
|
-
detailHtml.resumeText
|
|
769
|
-
].filter(Boolean).join("\n\n");
|
|
770
|
-
|
|
771
|
-
const parseStarted = Date.now();
|
|
772
|
-
let networkBodies = [];
|
|
773
|
-
let detailCandidateResult = null;
|
|
774
|
-
do {
|
|
775
|
-
networkBodies = await readRecommendDetailNetworkBodies(client, networkEvents);
|
|
776
|
-
detailCandidateResult = buildScreeningCandidateFromDetail({
|
|
777
|
-
cardCandidate,
|
|
778
|
-
detailText,
|
|
779
|
-
networkBodies,
|
|
780
|
-
metadata: {
|
|
781
|
-
target_url: targetUrl,
|
|
782
|
-
card_node_id: cardNodeId,
|
|
783
|
-
detail_popup_selector: detailState?.popup?.selector || null,
|
|
784
|
-
detail_popup_root: detailState?.popup?.root || null,
|
|
785
|
-
resume_iframe_selector: detailState?.resumeIframe?.selector || null,
|
|
786
|
-
resume_iframe_root: detailState?.resumeIframe?.root || null,
|
|
787
|
-
resume_iframe_document_node_id: detailHtml.resumeIframeDocumentNodeId,
|
|
788
|
-
detail_html_errors: detailHtml.errors || []
|
|
789
|
-
}
|
|
790
|
-
});
|
|
791
|
-
if (detailCandidateResult.parsed_network_profiles.some((item) => item.ok)) break;
|
|
792
|
-
if (Date.now() - parseStarted >= Math.max(0, Number(networkParseRetryMs) || 0)) break;
|
|
793
|
-
await sleep(Math.max(50, Number(networkParseIntervalMs) || 250));
|
|
794
|
-
} while (true);
|
|
795
|
-
|
|
796
|
-
let closeResult = null;
|
|
797
|
-
if (closeDetail) {
|
|
798
|
-
closeResult = await closeRecommendDetail(client);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
return {
|
|
802
|
-
candidate: detailCandidateResult.candidate,
|
|
803
|
-
parsed_network_profiles: detailCandidateResult.parsed_network_profiles,
|
|
804
|
-
network_bodies: networkBodies,
|
|
805
|
-
network_parse_retry_elapsed_ms: Date.now() - parseStarted,
|
|
806
|
-
network_event_count: networkEvents.length,
|
|
807
|
-
detail: {
|
|
808
|
-
popup_text: detailHtml.popupText,
|
|
809
|
-
resume_text: detailHtml.resumeText,
|
|
810
|
-
popup_html_length: detailHtml.popupHTML.length,
|
|
811
|
-
resume_html_length: detailHtml.resumeHTML.length,
|
|
812
|
-
html_errors: detailHtml.errors || []
|
|
813
|
-
},
|
|
814
|
-
close_result: closeResult
|
|
815
|
-
};
|
|
816
|
-
}
|
|
771
|
+
cardCandidate,
|
|
772
|
+
cardNodeId,
|
|
773
|
+
detailState,
|
|
774
|
+
networkEvents = [],
|
|
775
|
+
targetUrl = "",
|
|
776
|
+
closeDetail = true,
|
|
777
|
+
networkParseRetryMs = 1800,
|
|
778
|
+
networkParseIntervalMs = 250
|
|
779
|
+
} = {}) {
|
|
780
|
+
const detailHtml = await readRecommendDetailHtml(client, detailState);
|
|
781
|
+
const detailText = [
|
|
782
|
+
detailHtml.popupText,
|
|
783
|
+
detailHtml.resumeText
|
|
784
|
+
].filter(Boolean).join("\n\n");
|
|
785
|
+
|
|
786
|
+
const parseStarted = Date.now();
|
|
787
|
+
let networkBodies = [];
|
|
788
|
+
let detailCandidateResult = null;
|
|
789
|
+
do {
|
|
790
|
+
networkBodies = await readRecommendDetailNetworkBodies(client, networkEvents);
|
|
791
|
+
detailCandidateResult = buildScreeningCandidateFromDetail({
|
|
792
|
+
cardCandidate,
|
|
793
|
+
detailText,
|
|
794
|
+
networkBodies,
|
|
795
|
+
metadata: {
|
|
796
|
+
target_url: targetUrl,
|
|
797
|
+
card_node_id: cardNodeId,
|
|
798
|
+
detail_popup_selector: detailState?.popup?.selector || null,
|
|
799
|
+
detail_popup_root: detailState?.popup?.root || null,
|
|
800
|
+
resume_iframe_selector: detailState?.resumeIframe?.selector || null,
|
|
801
|
+
resume_iframe_root: detailState?.resumeIframe?.root || null,
|
|
802
|
+
resume_iframe_document_node_id: detailHtml.resumeIframeDocumentNodeId,
|
|
803
|
+
detail_html_errors: detailHtml.errors || []
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
if (detailCandidateResult.parsed_network_profiles.some((item) => item.ok)) break;
|
|
807
|
+
if (Date.now() - parseStarted >= Math.max(0, Number(networkParseRetryMs) || 0)) break;
|
|
808
|
+
await sleep(Math.max(50, Number(networkParseIntervalMs) || 250));
|
|
809
|
+
} while (true);
|
|
810
|
+
|
|
811
|
+
let closeResult = null;
|
|
812
|
+
if (closeDetail) {
|
|
813
|
+
closeResult = await closeRecommendDetail(client);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return {
|
|
817
|
+
candidate: detailCandidateResult.candidate,
|
|
818
|
+
parsed_network_profiles: detailCandidateResult.parsed_network_profiles,
|
|
819
|
+
network_bodies: networkBodies,
|
|
820
|
+
network_parse_retry_elapsed_ms: Date.now() - parseStarted,
|
|
821
|
+
network_event_count: networkEvents.length,
|
|
822
|
+
detail: {
|
|
823
|
+
popup_text: detailHtml.popupText,
|
|
824
|
+
resume_text: detailHtml.resumeText,
|
|
825
|
+
popup_html_length: detailHtml.popupHTML.length,
|
|
826
|
+
resume_html_length: detailHtml.resumeHTML.length,
|
|
827
|
+
html_errors: detailHtml.errors || []
|
|
828
|
+
},
|
|
829
|
+
close_result: closeResult
|
|
830
|
+
};
|
|
831
|
+
}
|