@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.1
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 +86 -33
- package/package.json +62 -9
- package/skills/boss-chat/SKILL.md +5 -4
- package/skills/boss-recommend-pipeline/SKILL.md +21 -31
- package/skills/boss-recruit-pipeline/README.md +17 -0
- package/skills/boss-recruit-pipeline/SKILL.md +55 -0
- package/src/chat-mcp.js +1333 -0
- package/src/chat-runtime-config.js +559 -0
- package/src/cli.js +1254 -225
- package/src/core/browser/index.js +378 -0
- package/src/core/capture/index.js +298 -0
- package/src/core/cv-acquisition/index.js +219 -0
- package/src/core/greet-quota/index.js +54 -0
- package/src/core/infinite-list/index.js +459 -0
- package/src/core/reporting/legacy-csv.js +332 -0
- package/src/core/run/index.js +286 -0
- package/src/core/screening/index.js +1166 -0
- package/src/core/self-heal/index.js +848 -0
- package/src/domains/chat/cards.js +129 -0
- package/src/domains/chat/constants.js +183 -0
- package/src/domains/chat/detail.js +1369 -0
- package/src/domains/chat/index.js +7 -0
- package/src/domains/chat/jobs.js +334 -0
- package/src/domains/chat/page-guard.js +88 -0
- package/src/domains/chat/roots.js +56 -0
- package/src/domains/chat/run-service.js +1101 -0
- package/src/domains/recommend/actions.js +457 -0
- package/src/domains/recommend/cards.js +228 -0
- package/src/domains/recommend/constants.js +141 -0
- package/src/domains/recommend/detail.js +341 -0
- package/src/domains/recommend/filters.js +581 -0
- package/src/domains/recommend/index.js +10 -0
- package/src/domains/recommend/jobs.js +232 -0
- package/src/domains/recommend/refresh.js +204 -0
- package/src/domains/recommend/roots.js +78 -0
- package/src/domains/recommend/run-service.js +903 -0
- package/src/domains/recommend/scopes.js +245 -0
- package/src/domains/recruit/actions.js +277 -0
- package/src/domains/recruit/cards.js +66 -0
- package/src/domains/recruit/constants.js +130 -0
- package/src/domains/recruit/detail.js +414 -0
- package/src/domains/recruit/index.js +9 -0
- package/src/domains/recruit/instruction-parser.js +451 -0
- package/src/domains/recruit/refresh.js +40 -0
- package/src/domains/recruit/roots.js +67 -0
- package/src/domains/recruit/run-service.js +580 -0
- package/src/domains/recruit/search.js +1149 -0
- package/src/index.js +578 -419
- package/src/recommend-mcp.js +1257 -0
- package/src/recruit-mcp.js +1035 -0
- package/src/adapters.js +0 -3079
- package/src/boss-chat.js +0 -1037
- package/src/pipeline.js +0 -2249
- package/src/recommend-healing-config.js +0 -131
- package/src/recommend-healing-rules.json +0 -261
- package/src/self-heal.js +0 -2237
- package/src/test-adapters-runtime.js +0 -628
- package/src/test-boss-chat.js +0 -3196
- package/src/test-index-async.js +0 -498
- package/src/test-parser.js +0 -742
- package/src/test-pipeline.js +0 -2703
- package/src/test-run-state.js +0 -152
- package/src/test-self-heal.js +0 -224
- package/vendor/boss-chat-cli/README.md +0 -134
- package/vendor/boss-chat-cli/package.json +0 -53
- package/vendor/boss-chat-cli/src/app.js +0 -1501
- package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
- package/vendor/boss-chat-cli/src/cli.js +0 -1713
- package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
- package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
- package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
- package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
- package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
- package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
- package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
- package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
- package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
- package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
- package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
- package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
- package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -7072
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
- package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2423
- package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
- package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export const RECOMMEND_TARGET_URL = "https://www.zhipin.com/web/chat/recommend";
|
|
2
|
+
|
|
3
|
+
export const RECOMMEND_PAGE_SCOPE_DEFAULT = "recommend";
|
|
4
|
+
|
|
5
|
+
export const RECOMMEND_PAGE_SCOPE_STATUS = Object.freeze({
|
|
6
|
+
recommend: "0",
|
|
7
|
+
latest: "1",
|
|
8
|
+
featured: "3"
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const RECOMMEND_PAGE_SCOPE_LABELS = Object.freeze({
|
|
12
|
+
recommend: "推荐",
|
|
13
|
+
latest: "最新",
|
|
14
|
+
featured: "精选"
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const RECOMMEND_IFRAME_SELECTORS = Object.freeze([
|
|
18
|
+
'iframe[name="recommendFrame"]',
|
|
19
|
+
'iframe[src*="/web/frame/recommend/"]',
|
|
20
|
+
"iframe"
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const RECOMMEND_PAGE_SCOPE_TAB_SELECTOR = [
|
|
24
|
+
".tab-list .tab-item[data-status]",
|
|
25
|
+
".tab-wrap .tab-item[data-status]",
|
|
26
|
+
".tab-item[data-status]",
|
|
27
|
+
"[data-status]"
|
|
28
|
+
].join(", ");
|
|
29
|
+
|
|
30
|
+
export const RECOMMEND_FILTER_SELECTORS = Object.freeze({
|
|
31
|
+
trigger: ".filter-label-wrap",
|
|
32
|
+
panel: ".filter-panel",
|
|
33
|
+
groups: Object.freeze({
|
|
34
|
+
recentNotView: ".filter-panel .check-box.recentNotView",
|
|
35
|
+
degree: ".filter-panel .check-box.degree",
|
|
36
|
+
gender: ".filter-panel .check-box.gender",
|
|
37
|
+
school: ".filter-panel .check-box.school"
|
|
38
|
+
}),
|
|
39
|
+
option: ".default.option, .options .option, .option",
|
|
40
|
+
activeOption: ".default.option.active, .options .option.active, .option.active",
|
|
41
|
+
confirmButton: ".filter-panel .btn, .filter-panel button",
|
|
42
|
+
checkBox: ".filter-panel .check-box"
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const RECOMMEND_FILTER_GROUP_ORDER = Object.freeze([
|
|
46
|
+
"recentNotView",
|
|
47
|
+
"degree",
|
|
48
|
+
"gender",
|
|
49
|
+
"school"
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
export const RECOMMEND_RECENT_NOT_VIEW_LABEL = "近14天没有";
|
|
53
|
+
|
|
54
|
+
export const RECOMMEND_CARD_SELECTOR = [
|
|
55
|
+
".candidate-card-wrap .card-inner[data-geek]",
|
|
56
|
+
".candidate-card-wrap [data-geek]",
|
|
57
|
+
"li.geek-info-card a[data-geekid]",
|
|
58
|
+
"a[data-geekid]"
|
|
59
|
+
].join(", ");
|
|
60
|
+
|
|
61
|
+
export const RECOMMEND_END_REFRESH_SELECTOR = [
|
|
62
|
+
".btn",
|
|
63
|
+
"button",
|
|
64
|
+
'[role="button"]',
|
|
65
|
+
'[class*="refresh"]',
|
|
66
|
+
'[ka*="refresh"]',
|
|
67
|
+
"a"
|
|
68
|
+
].join(", ");
|
|
69
|
+
|
|
70
|
+
export const DETAIL_POPUP_SELECTORS = Object.freeze([
|
|
71
|
+
".dialog-wrap.active",
|
|
72
|
+
".boss-popup__wrapper",
|
|
73
|
+
".boss-popup_wrapper",
|
|
74
|
+
".boss-dialog_wrapper",
|
|
75
|
+
".boss-dialog",
|
|
76
|
+
".resume-item-detail",
|
|
77
|
+
".geek-detail-modal",
|
|
78
|
+
'[class*="popup"][class*="wrapper"]',
|
|
79
|
+
'[class*="dialog"][class*="wrapper"]'
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
export const DETAIL_RESUME_IFRAME_SELECTORS = Object.freeze([
|
|
83
|
+
'iframe[src*="/web/frame/c-resume/"]',
|
|
84
|
+
'iframe[name*="resume"]'
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
export const DETAIL_CLOSE_SELECTORS = Object.freeze([
|
|
88
|
+
".boss-popup__close",
|
|
89
|
+
".popup-close",
|
|
90
|
+
".modal-close",
|
|
91
|
+
".dialog-close",
|
|
92
|
+
".close-btn",
|
|
93
|
+
'button[aria-label*="关闭"]',
|
|
94
|
+
'button[title*="关闭"]',
|
|
95
|
+
".icon-close",
|
|
96
|
+
'[aria-label*="关闭"]',
|
|
97
|
+
'[title*="关闭"]',
|
|
98
|
+
'[class*="close"]'
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
export const DETAIL_NETWORK_PATTERNS = Object.freeze([
|
|
102
|
+
/\/wapi\/zpjob\/view\/geek\/info\b/i,
|
|
103
|
+
/\/wapi\/zpitem\/web\/boss\/[^?#]*\/geek\/info\b/i,
|
|
104
|
+
/\/boss\/[^?#]*\/geek\/info\b/i,
|
|
105
|
+
/\/geek\/info\b/i,
|
|
106
|
+
/\/web\/frame\/c-resume\//i,
|
|
107
|
+
/resume/i
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
export const FAVORITE_BUTTON_SELECTORS = Object.freeze([
|
|
111
|
+
".like-icon-and-text",
|
|
112
|
+
".resume-footer.item-operate [class*=\"collect\"]",
|
|
113
|
+
".resume-footer.item-operate [class*=\"favorite\"]",
|
|
114
|
+
".resume-footer.item-operate [class*=\"like\"]",
|
|
115
|
+
".resume-footer-wrap [class*=\"collect\"]",
|
|
116
|
+
".resume-footer-wrap [class*=\"favorite\"]",
|
|
117
|
+
".resume-footer-wrap [class*=\"like\"]",
|
|
118
|
+
".resume-footer [class*=\"collect\"]",
|
|
119
|
+
".resume-footer [class*=\"favorite\"]",
|
|
120
|
+
".resume-footer [class*=\"like\"]",
|
|
121
|
+
".resume-footer.item-operate button",
|
|
122
|
+
".resume-footer.item-operate .btn",
|
|
123
|
+
".resume-footer.item-operate span",
|
|
124
|
+
".resume-footer-wrap button",
|
|
125
|
+
".resume-footer-wrap .btn",
|
|
126
|
+
".resume-footer-wrap span",
|
|
127
|
+
".resume-footer button",
|
|
128
|
+
".resume-footer .btn",
|
|
129
|
+
".resume-footer span"
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
export const GREET_BUTTON_RECOMMEND_SELECTORS = Object.freeze([
|
|
133
|
+
"button.btn-v2.btn-sure-v2.btn-greet",
|
|
134
|
+
".resume-footer.item-operate button.btn-v2",
|
|
135
|
+
".resume-footer-wrap button.btn-v2",
|
|
136
|
+
".resume-footer.item-operate button",
|
|
137
|
+
".resume-footer-wrap button",
|
|
138
|
+
".resume-footer button",
|
|
139
|
+
"button[class*=\"greet\"]",
|
|
140
|
+
"button[class*=\"sure\"]"
|
|
141
|
+
]);
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clickNodeCenter,
|
|
3
|
+
clickPoint,
|
|
4
|
+
getFrameDocumentNodeId,
|
|
5
|
+
getNodeBox,
|
|
6
|
+
getOuterHTML,
|
|
7
|
+
pressKey,
|
|
8
|
+
querySelectorAll,
|
|
9
|
+
sleep
|
|
10
|
+
} from "../../core/browser/index.js";
|
|
11
|
+
import {
|
|
12
|
+
buildScreeningCandidateFromDetail,
|
|
13
|
+
htmlToText
|
|
14
|
+
} from "../../core/screening/index.js";
|
|
15
|
+
import {
|
|
16
|
+
DETAIL_CLOSE_SELECTORS,
|
|
17
|
+
DETAIL_NETWORK_PATTERNS,
|
|
18
|
+
DETAIL_POPUP_SELECTORS,
|
|
19
|
+
DETAIL_RESUME_IFRAME_SELECTORS
|
|
20
|
+
} from "./constants.js";
|
|
21
|
+
import {
|
|
22
|
+
getRecommendRoots,
|
|
23
|
+
queryFirstAcrossRoots
|
|
24
|
+
} from "./roots.js";
|
|
25
|
+
|
|
26
|
+
export function matchesRecommendDetailNetwork(url) {
|
|
27
|
+
return DETAIL_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createRecommendDetailNetworkRecorder(client) {
|
|
31
|
+
const events = [];
|
|
32
|
+
client.Network.responseReceived((event) => {
|
|
33
|
+
const url = event?.response?.url || "";
|
|
34
|
+
if (!matchesRecommendDetailNetwork(url)) return;
|
|
35
|
+
events.push({
|
|
36
|
+
requestId: event.requestId,
|
|
37
|
+
url,
|
|
38
|
+
status: event.response?.status,
|
|
39
|
+
mimeType: event.response?.mimeType,
|
|
40
|
+
type: event.type
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
if (typeof client.Network.loadingFinished === "function") {
|
|
44
|
+
client.Network.loadingFinished((event) => {
|
|
45
|
+
const found = events.find((item) => item.requestId === event.requestId);
|
|
46
|
+
if (!found) return;
|
|
47
|
+
found.loading_finished = true;
|
|
48
|
+
found.encodedDataLength = event.encodedDataLength;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (typeof client.Network.loadingFailed === "function") {
|
|
52
|
+
client.Network.loadingFailed((event) => {
|
|
53
|
+
const found = events.find((item) => item.requestId === event.requestId);
|
|
54
|
+
if (!found) return;
|
|
55
|
+
found.loading_failed = true;
|
|
56
|
+
found.loading_error = event.errorText || event.blockedReason || "Network loading failed";
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
events,
|
|
61
|
+
clear() {
|
|
62
|
+
events.length = 0;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function waitForRecommendDetailNetworkEvents(recorder, {
|
|
68
|
+
minCount = 1,
|
|
69
|
+
requireLoaded = true,
|
|
70
|
+
timeoutMs = 3500,
|
|
71
|
+
intervalMs = 100
|
|
72
|
+
} = {}) {
|
|
73
|
+
const started = Date.now();
|
|
74
|
+
const events = Array.isArray(recorder) ? recorder : recorder?.events || [];
|
|
75
|
+
let matching = [];
|
|
76
|
+
while (Date.now() - started <= timeoutMs) {
|
|
77
|
+
matching = events.filter((event) => (
|
|
78
|
+
!requireLoaded
|
|
79
|
+
|| event.loading_finished === true
|
|
80
|
+
|| event.loading_failed === true
|
|
81
|
+
));
|
|
82
|
+
if (matching.length >= minCount) {
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
elapsed_ms: Date.now() - started,
|
|
86
|
+
count: matching.length,
|
|
87
|
+
events: matching
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
await sleep(intervalMs);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
elapsed_ms: Date.now() - started,
|
|
95
|
+
count: matching.length,
|
|
96
|
+
events: matching,
|
|
97
|
+
total_event_count: events.length
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function readRecommendDetailNetworkBodies(client, events = [], {
|
|
102
|
+
limit = 10
|
|
103
|
+
} = {}) {
|
|
104
|
+
const bodies = [];
|
|
105
|
+
for (const event of events.slice(0, limit)) {
|
|
106
|
+
try {
|
|
107
|
+
const body = await client.Network.getResponseBody({ requestId: event.requestId });
|
|
108
|
+
bodies.push({
|
|
109
|
+
...event,
|
|
110
|
+
body,
|
|
111
|
+
body_length: String(body?.body || "").length
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
bodies.push({
|
|
115
|
+
...event,
|
|
116
|
+
body_error: error?.message || String(error)
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return bodies;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function waitForRecommendDetail(client, {
|
|
124
|
+
timeoutMs = 10000,
|
|
125
|
+
intervalMs = 250
|
|
126
|
+
} = {}) {
|
|
127
|
+
const started = Date.now();
|
|
128
|
+
let lastState = null;
|
|
129
|
+
while (Date.now() - started <= timeoutMs) {
|
|
130
|
+
const rootState = await getRecommendRoots(client);
|
|
131
|
+
const popup = await queryFirstAcrossRoots(client, rootState.roots, DETAIL_POPUP_SELECTORS);
|
|
132
|
+
const resumeIframe = await queryFirstAcrossRoots(client, rootState.roots, DETAIL_RESUME_IFRAME_SELECTORS);
|
|
133
|
+
lastState = {
|
|
134
|
+
iframe: rootState.iframe,
|
|
135
|
+
roots: rootState.roots,
|
|
136
|
+
popup,
|
|
137
|
+
resumeIframe
|
|
138
|
+
};
|
|
139
|
+
if (popup || resumeIframe) return lastState;
|
|
140
|
+
await sleep(intervalMs);
|
|
141
|
+
}
|
|
142
|
+
return lastState;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function readRecommendDetailHtml(client, detailState) {
|
|
146
|
+
let popupHTML = "";
|
|
147
|
+
let resumeHTML = "";
|
|
148
|
+
let resumeIframeDocumentNodeId = null;
|
|
149
|
+
|
|
150
|
+
if (detailState?.popup?.node_id) {
|
|
151
|
+
popupHTML = await getOuterHTML(client, detailState.popup.node_id);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (detailState?.resumeIframe?.node_id) {
|
|
155
|
+
resumeIframeDocumentNodeId = await getFrameDocumentNodeId(client, detailState.resumeIframe.node_id);
|
|
156
|
+
resumeHTML = await getOuterHTML(client, resumeIframeDocumentNodeId);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
popupHTML,
|
|
161
|
+
resumeHTML,
|
|
162
|
+
resumeIframeDocumentNodeId,
|
|
163
|
+
popupText: htmlToText(popupHTML),
|
|
164
|
+
resumeText: htmlToText(resumeHTML)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function openRecommendCardDetail(client, cardNodeId, {
|
|
169
|
+
timeoutMs = 12000,
|
|
170
|
+
scrollIntoView = true
|
|
171
|
+
} = {}) {
|
|
172
|
+
const cardBox = await clickNodeCenter(client, cardNodeId, { scrollIntoView });
|
|
173
|
+
const detailState = await waitForRecommendDetail(client, { timeoutMs });
|
|
174
|
+
if (!detailState?.popup && !detailState?.resumeIframe) {
|
|
175
|
+
throw new Error("Candidate detail did not open or no known detail selectors mounted");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
card_box: cardBox,
|
|
180
|
+
detail_state: detailState
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function closeRecommendDetail(client, {
|
|
185
|
+
attemptsLimit = 3
|
|
186
|
+
} = {}) {
|
|
187
|
+
const attempts = [];
|
|
188
|
+
for (let index = 0; index < attemptsLimit; index += 1) {
|
|
189
|
+
const existingState = await waitForRecommendDetail(client, { timeoutMs: 500 });
|
|
190
|
+
if (!existingState?.popup && !existingState?.resumeIframe) {
|
|
191
|
+
return {
|
|
192
|
+
closed: true,
|
|
193
|
+
attempts
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const rootState = await getRecommendRoots(client);
|
|
198
|
+
const closeTarget = await findVisibleCloseTarget(client, rootState.roots, DETAIL_CLOSE_SELECTORS);
|
|
199
|
+
if (closeTarget) {
|
|
200
|
+
try {
|
|
201
|
+
if (closeTarget.center) {
|
|
202
|
+
await clickPoint(client, closeTarget.center.x, closeTarget.center.y);
|
|
203
|
+
} else {
|
|
204
|
+
await clickNodeCenter(client, closeTarget.node_id);
|
|
205
|
+
}
|
|
206
|
+
attempts.push({
|
|
207
|
+
mode: "close-selector",
|
|
208
|
+
selector: closeTarget.selector,
|
|
209
|
+
root: closeTarget.root
|
|
210
|
+
});
|
|
211
|
+
} catch (error) {
|
|
212
|
+
attempts.push({
|
|
213
|
+
mode: "close-selector-error",
|
|
214
|
+
selector: closeTarget.selector,
|
|
215
|
+
root: closeTarget.root,
|
|
216
|
+
error: error?.message || String(error)
|
|
217
|
+
});
|
|
218
|
+
await pressEscape(client);
|
|
219
|
+
attempts.push({ mode: "Escape-after-close-selector-error" });
|
|
220
|
+
}
|
|
221
|
+
await sleep(700);
|
|
222
|
+
} else {
|
|
223
|
+
await pressEscape(client);
|
|
224
|
+
attempts.push({ mode: "Escape" });
|
|
225
|
+
await sleep(700);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let state = await waitForRecommendDetail(client, { timeoutMs: 1000 });
|
|
229
|
+
if (!state?.popup && !state?.resumeIframe) {
|
|
230
|
+
return {
|
|
231
|
+
closed: true,
|
|
232
|
+
attempts
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await pressEscape(client);
|
|
237
|
+
attempts.push({ mode: "Escape-fallback" });
|
|
238
|
+
await sleep(700);
|
|
239
|
+
|
|
240
|
+
state = await waitForRecommendDetail(client, { timeoutMs: 1000 });
|
|
241
|
+
if (!state?.popup && !state?.resumeIframe) {
|
|
242
|
+
return {
|
|
243
|
+
closed: true,
|
|
244
|
+
attempts
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
closed: false,
|
|
251
|
+
attempts
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function findVisibleCloseTarget(client, roots, selectors) {
|
|
256
|
+
let fallback = null;
|
|
257
|
+
for (const root of roots) {
|
|
258
|
+
if (!root?.nodeId) continue;
|
|
259
|
+
for (const selector of selectors) {
|
|
260
|
+
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
261
|
+
for (const nodeId of nodeIds) {
|
|
262
|
+
const target = {
|
|
263
|
+
root: root.name,
|
|
264
|
+
root_node_id: root.nodeId,
|
|
265
|
+
selector,
|
|
266
|
+
node_id: nodeId
|
|
267
|
+
};
|
|
268
|
+
if (!fallback) fallback = target;
|
|
269
|
+
try {
|
|
270
|
+
const box = await getNodeBox(client, nodeId);
|
|
271
|
+
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
272
|
+
return {
|
|
273
|
+
...target,
|
|
274
|
+
center: box.center,
|
|
275
|
+
rect: box.rect
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
} catch {}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return fallback;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function pressEscape(client) {
|
|
286
|
+
await pressKey(client, "Escape", {
|
|
287
|
+
code: "Escape",
|
|
288
|
+
windowsVirtualKeyCode: 27,
|
|
289
|
+
nativeVirtualKeyCode: 27
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export async function extractRecommendDetailCandidate(client, {
|
|
294
|
+
cardCandidate,
|
|
295
|
+
cardNodeId,
|
|
296
|
+
detailState,
|
|
297
|
+
networkEvents = [],
|
|
298
|
+
targetUrl = "",
|
|
299
|
+
closeDetail = true
|
|
300
|
+
} = {}) {
|
|
301
|
+
await sleep(1000);
|
|
302
|
+
const networkBodies = await readRecommendDetailNetworkBodies(client, networkEvents);
|
|
303
|
+
const detailHtml = await readRecommendDetailHtml(client, detailState);
|
|
304
|
+
const detailText = [
|
|
305
|
+
detailHtml.popupText,
|
|
306
|
+
detailHtml.resumeText
|
|
307
|
+
].filter(Boolean).join("\n\n");
|
|
308
|
+
|
|
309
|
+
const detailCandidateResult = buildScreeningCandidateFromDetail({
|
|
310
|
+
cardCandidate,
|
|
311
|
+
detailText,
|
|
312
|
+
networkBodies,
|
|
313
|
+
metadata: {
|
|
314
|
+
target_url: targetUrl,
|
|
315
|
+
card_node_id: cardNodeId,
|
|
316
|
+
detail_popup_selector: detailState?.popup?.selector || null,
|
|
317
|
+
detail_popup_root: detailState?.popup?.root || null,
|
|
318
|
+
resume_iframe_selector: detailState?.resumeIframe?.selector || null,
|
|
319
|
+
resume_iframe_root: detailState?.resumeIframe?.root || null,
|
|
320
|
+
resume_iframe_document_node_id: detailHtml.resumeIframeDocumentNodeId
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
let closeResult = null;
|
|
325
|
+
if (closeDetail) {
|
|
326
|
+
closeResult = await closeRecommendDetail(client);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
candidate: detailCandidateResult.candidate,
|
|
331
|
+
parsed_network_profiles: detailCandidateResult.parsed_network_profiles,
|
|
332
|
+
network_bodies: networkBodies,
|
|
333
|
+
detail: {
|
|
334
|
+
popup_text: detailHtml.popupText,
|
|
335
|
+
resume_text: detailHtml.resumeText,
|
|
336
|
+
popup_html_length: detailHtml.popupHTML.length,
|
|
337
|
+
resume_html_length: detailHtml.resumeHTML.length
|
|
338
|
+
},
|
|
339
|
+
close_result: closeResult
|
|
340
|
+
};
|
|
341
|
+
}
|