@viyzhu/boss-cli-fork 0.7.2-rc.1 → 0.7.3
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/AGENTS.md +85 -56
- package/LICENSE +674 -674
- package/README.md +284 -276
- package/dist/browser/cdp_browser.d.ts +170 -88
- package/dist/browser/cdp_browser.d.ts.map +1 -1
- package/dist/browser/cdp_browser.js +674 -400
- package/dist/browser/cdp_browser.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/cli/cliRouter.js +61 -61
- package/dist/common/auth.js +49 -49
- package/dist/common/boss_modal.js +42 -42
- package/dist/common/boss_page_guards.js +247 -247
- package/dist/common/boss_paywall_popup.js +87 -87
- package/dist/common/boss_session_page.d.ts.map +1 -1
- package/dist/common/boss_session_page.js +16 -15
- package/dist/common/boss_session_page.js.map +1 -1
- package/dist/common/boss_sidebar_nav.js +24 -24
- package/dist/common/c_resume_capture.js +61 -61
- package/dist/toolset/action.d.ts.map +1 -1
- package/dist/toolset/action.js +237 -233
- package/dist/toolset/action.js.map +1 -1
- package/dist/toolset/chat.js +437 -437
- package/dist/toolset/deep-search.js +570 -570
- package/dist/toolset/jd.js +132 -132
- package/dist/toolset/list.js +48 -48
- package/dist/toolset/normal-search.js +158 -158
- package/dist/toolset/preview.d.ts.map +1 -1
- package/dist/toolset/preview.js +3 -3
- package/dist/toolset/preview.js.map +1 -1
- package/dist/toolset/recommend.js +207 -207
- package/package.json +69 -69
- package/skills/boss-frontend-analysis/SKILL.md +58 -58
- package/skills/boss-frontend-analysis/agents/openai.yaml +4 -4
- package/skills/boss-frontend-analysis/scripts/capture_boss_frontend.mjs +604 -604
package/dist/toolset/chat.js
CHANGED
|
@@ -7,21 +7,21 @@ import { ensureChatListReady } from './list.js';
|
|
|
7
7
|
* 因此不能只认 `auto/scroll`——那样会判定不到滚动容器,导致只能扫到首屏 40 条会话
|
|
8
8
|
* (实测该账号实际有 380 条)。改为「非 visible 且真的可滚」来识别。
|
|
9
9
|
*/
|
|
10
|
-
const CHAT_LIST_SCROLLER_JS = `
|
|
11
|
-
const first = document.querySelector(".geek-item-wrap");
|
|
12
|
-
let scroller = null;
|
|
13
|
-
let node = first ? first.parentElement : null;
|
|
14
|
-
while (node) {
|
|
15
|
-
const overflowY = window.getComputedStyle(node).overflowY;
|
|
16
|
-
if (
|
|
17
|
-
overflowY !== "visible" &&
|
|
18
|
-
node.scrollHeight > node.clientHeight + 2 &&
|
|
19
|
-
node.clientHeight > 100
|
|
20
|
-
) {
|
|
21
|
-
scroller = node;
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
node = node.parentElement;
|
|
10
|
+
const CHAT_LIST_SCROLLER_JS = `
|
|
11
|
+
const first = document.querySelector(".geek-item-wrap");
|
|
12
|
+
let scroller = null;
|
|
13
|
+
let node = first ? first.parentElement : null;
|
|
14
|
+
while (node) {
|
|
15
|
+
const overflowY = window.getComputedStyle(node).overflowY;
|
|
16
|
+
if (
|
|
17
|
+
overflowY !== "visible" &&
|
|
18
|
+
node.scrollHeight > node.clientHeight + 2 &&
|
|
19
|
+
node.clientHeight > 100
|
|
20
|
+
) {
|
|
21
|
+
scroller = node;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
node = node.parentElement;
|
|
25
25
|
}`;
|
|
26
26
|
function chatRoleTag(from) {
|
|
27
27
|
switch (from) {
|
|
@@ -37,24 +37,24 @@ function chatRoleTag(from) {
|
|
|
37
37
|
}
|
|
38
38
|
async function waitForChatHistoryPanelReady(page, selectedTab) {
|
|
39
39
|
const selectedTabLiteral = JSON.stringify(selectedTab ?? null);
|
|
40
|
-
await page.waitForFunction(`(() => {
|
|
41
|
-
const tabLabel = ${selectedTabLiteral};
|
|
42
|
-
function norm(v) {
|
|
43
|
-
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
44
|
-
}
|
|
45
|
-
function isVisible(el) {
|
|
46
|
-
if (!el) return false;
|
|
47
|
-
const rect = el.getBoundingClientRect();
|
|
48
|
-
const style = window.getComputedStyle(el);
|
|
49
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
50
|
-
}
|
|
51
|
-
const root = document.querySelector(".chat-history-process");
|
|
52
|
-
if (!isVisible(root)) return false;
|
|
53
|
-
if (tabLabel) {
|
|
54
|
-
const selected = root.querySelector(".tab-hd span.selected");
|
|
55
|
-
if (norm(selected?.textContent) !== tabLabel) return false;
|
|
56
|
-
}
|
|
57
|
-
return !!root.querySelector(".record");
|
|
40
|
+
await page.waitForFunction(`(() => {
|
|
41
|
+
const tabLabel = ${selectedTabLiteral};
|
|
42
|
+
function norm(v) {
|
|
43
|
+
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
44
|
+
}
|
|
45
|
+
function isVisible(el) {
|
|
46
|
+
if (!el) return false;
|
|
47
|
+
const rect = el.getBoundingClientRect();
|
|
48
|
+
const style = window.getComputedStyle(el);
|
|
49
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
50
|
+
}
|
|
51
|
+
const root = document.querySelector(".chat-history-process");
|
|
52
|
+
if (!isVisible(root)) return false;
|
|
53
|
+
if (tabLabel) {
|
|
54
|
+
const selected = root.querySelector(".tab-hd span.selected");
|
|
55
|
+
if (norm(selected?.textContent) !== tabLabel) return false;
|
|
56
|
+
}
|
|
57
|
+
return !!root.querySelector(".record");
|
|
58
58
|
})()`, { timeout: 10_000 });
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
@@ -62,36 +62,36 @@ async function waitForChatHistoryPanelReady(page, selectedTab) {
|
|
|
62
62
|
* 未找到入口时返回 null(视为暂无同事沟通记录,不输出该段)。
|
|
63
63
|
*/
|
|
64
64
|
async function fetchColleagueChatHistorySection(page) {
|
|
65
|
-
const clicked = (await page.evaluate(`(() => {
|
|
66
|
-
function norm(v) {
|
|
67
|
-
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
68
|
-
}
|
|
69
|
-
const tooltips = Array.from(document.querySelectorAll(".chat-tooltip-custom"));
|
|
70
|
-
for (const el of tooltips) {
|
|
71
|
-
if (!norm(el.textContent).includes("沟通记录")) continue;
|
|
72
|
-
const host = el.closest("span.icon") ?? el.closest("span") ?? el.parentElement;
|
|
73
|
-
if (host) {
|
|
74
|
-
host.click();
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
el.click();
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
const uses = Array.from(document.querySelectorAll("use"));
|
|
81
|
-
for (const u of uses) {
|
|
82
|
-
const h =
|
|
83
|
-
u.getAttribute("href") ||
|
|
84
|
-
u.getAttributeNS("http://www.w3.org/1999/xlink", "href") ||
|
|
85
|
-
"";
|
|
86
|
-
if (h.includes("icon-chat-history")) {
|
|
87
|
-
const p = u.closest("span.icon") ?? u.parentElement?.parentElement;
|
|
88
|
-
if (p) {
|
|
89
|
-
p.click();
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return false;
|
|
65
|
+
const clicked = (await page.evaluate(`(() => {
|
|
66
|
+
function norm(v) {
|
|
67
|
+
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
68
|
+
}
|
|
69
|
+
const tooltips = Array.from(document.querySelectorAll(".chat-tooltip-custom"));
|
|
70
|
+
for (const el of tooltips) {
|
|
71
|
+
if (!norm(el.textContent).includes("沟通记录")) continue;
|
|
72
|
+
const host = el.closest("span.icon") ?? el.closest("span") ?? el.parentElement;
|
|
73
|
+
if (host) {
|
|
74
|
+
host.click();
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
el.click();
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
const uses = Array.from(document.querySelectorAll("use"));
|
|
81
|
+
for (const u of uses) {
|
|
82
|
+
const h =
|
|
83
|
+
u.getAttribute("href") ||
|
|
84
|
+
u.getAttributeNS("http://www.w3.org/1999/xlink", "href") ||
|
|
85
|
+
"";
|
|
86
|
+
if (h.includes("icon-chat-history")) {
|
|
87
|
+
const p = u.closest("span.icon") ?? u.parentElement?.parentElement;
|
|
88
|
+
if (p) {
|
|
89
|
+
p.click();
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
95
|
})()`));
|
|
96
96
|
if (!clicked) {
|
|
97
97
|
return null;
|
|
@@ -103,33 +103,33 @@ async function fetchColleagueChatHistorySection(page) {
|
|
|
103
103
|
catch {
|
|
104
104
|
return '(已点击「沟通记录」,但弹窗未在预期时间内出现。)';
|
|
105
105
|
}
|
|
106
|
-
const scrapeRows = () => page.evaluate(`(() => {
|
|
107
|
-
function norm(v) {
|
|
108
|
-
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
109
|
-
}
|
|
110
|
-
const root = document.querySelector(".chat-history-process");
|
|
111
|
-
if (!root) return [];
|
|
112
|
-
return Array.from(root.querySelectorAll(".record li"))
|
|
113
|
-
.map((li) => ({
|
|
114
|
-
action: norm(li.querySelector(".action")?.textContent),
|
|
115
|
-
operat: norm(li.querySelector(".operat")?.textContent),
|
|
116
|
-
}))
|
|
117
|
-
.filter((x) => x.action || x.operat);
|
|
106
|
+
const scrapeRows = () => page.evaluate(`(() => {
|
|
107
|
+
function norm(v) {
|
|
108
|
+
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
109
|
+
}
|
|
110
|
+
const root = document.querySelector(".chat-history-process");
|
|
111
|
+
if (!root) return [];
|
|
112
|
+
return Array.from(root.querySelectorAll(".record li"))
|
|
113
|
+
.map((li) => ({
|
|
114
|
+
action: norm(li.querySelector(".action")?.textContent),
|
|
115
|
+
operat: norm(li.querySelector(".operat")?.textContent),
|
|
116
|
+
}))
|
|
117
|
+
.filter((x) => x.action || x.operat);
|
|
118
118
|
})()`);
|
|
119
119
|
const clickTab = (label) => {
|
|
120
120
|
const labelLiteral = JSON.stringify(label);
|
|
121
|
-
return page.evaluate(`(() => {
|
|
122
|
-
const lab = ${labelLiteral};
|
|
123
|
-
function norm(v) {
|
|
124
|
-
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
125
|
-
}
|
|
126
|
-
const root = document.querySelector(".chat-history-process");
|
|
127
|
-
if (!root) return;
|
|
128
|
-
const spans = Array.from(root.querySelectorAll(".tab-hd span"));
|
|
129
|
-
const sp = spans.find((s) => norm(s.textContent) === lab);
|
|
130
|
-
if (sp && !sp.classList.contains("selected")) {
|
|
131
|
-
sp.click();
|
|
132
|
-
}
|
|
121
|
+
return page.evaluate(`(() => {
|
|
122
|
+
const lab = ${labelLiteral};
|
|
123
|
+
function norm(v) {
|
|
124
|
+
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
125
|
+
}
|
|
126
|
+
const root = document.querySelector(".chat-history-process");
|
|
127
|
+
if (!root) return;
|
|
128
|
+
const spans = Array.from(root.querySelectorAll(".tab-hd span"));
|
|
129
|
+
const sp = spans.find((s) => norm(s.textContent) === lab);
|
|
130
|
+
if (sp && !sp.classList.contains("selected")) {
|
|
131
|
+
sp.click();
|
|
132
|
+
}
|
|
133
133
|
})()`);
|
|
134
134
|
};
|
|
135
135
|
await clickTab('同事沟通');
|
|
@@ -193,42 +193,42 @@ async function closeChatHistoryPopup(page) {
|
|
|
193
193
|
await btn.click();
|
|
194
194
|
}
|
|
195
195
|
else {
|
|
196
|
-
await page.evaluate(`(() => {
|
|
197
|
-
const root =
|
|
198
|
-
document.querySelector(".boss-popup__wrapper.chat-history") ||
|
|
199
|
-
document.querySelector(".boss-dialog__wrapper.chat-history");
|
|
200
|
-
const c = root?.querySelector(".boss-popup__close") ?? document.querySelector(".boss-popup__close");
|
|
201
|
-
if (c) {
|
|
202
|
-
c.click();
|
|
203
|
-
}
|
|
196
|
+
await page.evaluate(`(() => {
|
|
197
|
+
const root =
|
|
198
|
+
document.querySelector(".boss-popup__wrapper.chat-history") ||
|
|
199
|
+
document.querySelector(".boss-dialog__wrapper.chat-history");
|
|
200
|
+
const c = root?.querySelector(".boss-popup__close") ?? document.querySelector(".boss-popup__close");
|
|
201
|
+
if (c) {
|
|
202
|
+
c.click();
|
|
203
|
+
}
|
|
204
204
|
})()`);
|
|
205
205
|
}
|
|
206
|
-
await page.waitForFunction(`(() => {
|
|
207
|
-
const roots = Array.from(document.querySelectorAll(".boss-popup__wrapper.chat-history, .boss-dialog__wrapper.chat-history"));
|
|
208
|
-
return roots.every((el) => {
|
|
209
|
-
if (!(el instanceof HTMLElement)) return true;
|
|
210
|
-
const st = window.getComputedStyle(el);
|
|
211
|
-
const r = el.getBoundingClientRect();
|
|
212
|
-
return st.display === "none" || st.visibility === "hidden" || r.width <= 0 || r.height <= 0;
|
|
213
|
-
});
|
|
206
|
+
await page.waitForFunction(`(() => {
|
|
207
|
+
const roots = Array.from(document.querySelectorAll(".boss-popup__wrapper.chat-history, .boss-dialog__wrapper.chat-history"));
|
|
208
|
+
return roots.every((el) => {
|
|
209
|
+
if (!(el instanceof HTMLElement)) return true;
|
|
210
|
+
const st = window.getComputedStyle(el);
|
|
211
|
+
const r = el.getBoundingClientRect();
|
|
212
|
+
return st.display === "none" || st.visibility === "hidden" || r.width <= 0 || r.height <= 0;
|
|
213
|
+
});
|
|
214
214
|
})()`, { timeout: 3_000 }).catch(() => { });
|
|
215
215
|
const popupWrap = await page.$('.boss-popup__wrapper.chat-history, .boss-dialog__wrapper.chat-history');
|
|
216
216
|
if (popupWrap) {
|
|
217
|
-
await page.evaluate(`(() => {
|
|
218
|
-
const root =
|
|
219
|
-
document.querySelector(".boss-popup__wrapper.chat-history") ||
|
|
220
|
-
document.querySelector(".boss-dialog__wrapper.chat-history");
|
|
221
|
-
const c = root?.querySelector(".boss-popup__close") ?? document.querySelector(".boss-popup__close");
|
|
222
|
-
c?.click();
|
|
217
|
+
await page.evaluate(`(() => {
|
|
218
|
+
const root =
|
|
219
|
+
document.querySelector(".boss-popup__wrapper.chat-history") ||
|
|
220
|
+
document.querySelector(".boss-dialog__wrapper.chat-history");
|
|
221
|
+
const c = root?.querySelector(".boss-popup__close") ?? document.querySelector(".boss-popup__close");
|
|
222
|
+
c?.click();
|
|
223
223
|
})()`);
|
|
224
|
-
await page.waitForFunction(`(() => {
|
|
225
|
-
const roots = Array.from(document.querySelectorAll(".boss-popup__wrapper.chat-history, .boss-dialog__wrapper.chat-history"));
|
|
226
|
-
return roots.every((el) => {
|
|
227
|
-
if (!(el instanceof HTMLElement)) return true;
|
|
228
|
-
const st = window.getComputedStyle(el);
|
|
229
|
-
const r = el.getBoundingClientRect();
|
|
230
|
-
return st.display === "none" || st.visibility === "hidden" || r.width <= 0 || r.height <= 0;
|
|
231
|
-
});
|
|
224
|
+
await page.waitForFunction(`(() => {
|
|
225
|
+
const roots = Array.from(document.querySelectorAll(".boss-popup__wrapper.chat-history, .boss-dialog__wrapper.chat-history"));
|
|
226
|
+
return roots.every((el) => {
|
|
227
|
+
if (!(el instanceof HTMLElement)) return true;
|
|
228
|
+
const st = window.getComputedStyle(el);
|
|
229
|
+
const r = el.getBoundingClientRect();
|
|
230
|
+
return st.display === "none" || st.visibility === "hidden" || r.width <= 0 || r.height <= 0;
|
|
231
|
+
});
|
|
232
232
|
})()`, { timeout: 2_500 }).catch(() => { });
|
|
233
233
|
}
|
|
234
234
|
}
|
|
@@ -240,152 +240,152 @@ async function closeChatHistoryPopup(page) {
|
|
|
240
240
|
async function fetchCandidateSummary(page, expectedName, exactMatch) {
|
|
241
241
|
const targetNameLiteral = JSON.stringify(expectedName);
|
|
242
242
|
const exactMatchLiteral = JSON.stringify(exactMatch);
|
|
243
|
-
const scraped = (await page.evaluate(`(() => {
|
|
244
|
-
const targetName = ${targetNameLiteral};
|
|
245
|
-
const exact = ${exactMatchLiteral};
|
|
246
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
247
|
-
const matches = (value) => exact ? value === targetName : value.includes(targetName);
|
|
248
|
-
const isVisible = (el) => {
|
|
249
|
-
if (!el) return false;
|
|
250
|
-
const rect = el.getBoundingClientRect();
|
|
251
|
-
const style = window.getComputedStyle(el);
|
|
252
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
253
|
-
};
|
|
254
|
-
const roots = Array.from(document.querySelectorAll(".base-info-single-container")).filter(isVisible);
|
|
255
|
-
const root = roots.find((el) => matches(norm(el.querySelector(".name-box")?.textContent)));
|
|
256
|
-
if (!root) {
|
|
257
|
-
const visibleNames = roots.map((el) => norm(el.querySelector(".name-box")?.textContent)).filter(Boolean).join(", ");
|
|
258
|
-
throw new Error("未找到目标候选人详情容器:" + targetName + ";当前可见详情:" + (visibleNames || "空"));
|
|
259
|
-
}
|
|
260
|
-
const name = norm(root.querySelector(".name-box")?.textContent);
|
|
261
|
-
const active = norm(root.querySelector(".high-light-orange.active-time span")?.textContent);
|
|
262
|
-
const basicFacts = Array.from(root.querySelectorAll(".base-info-single-detial > div"))
|
|
263
|
-
.filter((el) => !el.classList.contains("name-contet") && !el.classList.contains("active-time"))
|
|
264
|
-
.map((el) => norm(el.textContent))
|
|
265
|
-
.filter((v) => v.length > 0);
|
|
266
|
-
const recentExperience = Array.from(
|
|
267
|
-
root.querySelectorAll(".experience-content.detail-list .work-content .value"),
|
|
268
|
-
)
|
|
269
|
-
.map((el) => norm(el.textContent))
|
|
270
|
-
.filter((v) => v.length > 0);
|
|
271
|
-
const communicationPosition = norm(
|
|
272
|
-
root.querySelector(".position-item .position-name")?.textContent,
|
|
273
|
-
);
|
|
274
|
-
const expectation = norm(root.querySelector(".position-item.expect .value.job")?.textContent);
|
|
275
|
-
const remark = norm(
|
|
276
|
-
root.querySelector(".label-remark-content .remark span:last-child")?.textContent,
|
|
277
|
-
);
|
|
278
|
-
return {
|
|
279
|
-
name,
|
|
280
|
-
active,
|
|
281
|
-
basicFacts,
|
|
282
|
-
recentExperience,
|
|
283
|
-
communicationPosition,
|
|
284
|
-
expectation,
|
|
285
|
-
remark,
|
|
286
|
-
};
|
|
243
|
+
const scraped = (await page.evaluate(`(() => {
|
|
244
|
+
const targetName = ${targetNameLiteral};
|
|
245
|
+
const exact = ${exactMatchLiteral};
|
|
246
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
247
|
+
const matches = (value) => exact ? value === targetName : value.includes(targetName);
|
|
248
|
+
const isVisible = (el) => {
|
|
249
|
+
if (!el) return false;
|
|
250
|
+
const rect = el.getBoundingClientRect();
|
|
251
|
+
const style = window.getComputedStyle(el);
|
|
252
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
253
|
+
};
|
|
254
|
+
const roots = Array.from(document.querySelectorAll(".base-info-single-container")).filter(isVisible);
|
|
255
|
+
const root = roots.find((el) => matches(norm(el.querySelector(".name-box")?.textContent)));
|
|
256
|
+
if (!root) {
|
|
257
|
+
const visibleNames = roots.map((el) => norm(el.querySelector(".name-box")?.textContent)).filter(Boolean).join(", ");
|
|
258
|
+
throw new Error("未找到目标候选人详情容器:" + targetName + ";当前可见详情:" + (visibleNames || "空"));
|
|
259
|
+
}
|
|
260
|
+
const name = norm(root.querySelector(".name-box")?.textContent);
|
|
261
|
+
const active = norm(root.querySelector(".high-light-orange.active-time span")?.textContent);
|
|
262
|
+
const basicFacts = Array.from(root.querySelectorAll(".base-info-single-detial > div"))
|
|
263
|
+
.filter((el) => !el.classList.contains("name-contet") && !el.classList.contains("active-time"))
|
|
264
|
+
.map((el) => norm(el.textContent))
|
|
265
|
+
.filter((v) => v.length > 0);
|
|
266
|
+
const recentExperience = Array.from(
|
|
267
|
+
root.querySelectorAll(".experience-content.detail-list .work-content .value"),
|
|
268
|
+
)
|
|
269
|
+
.map((el) => norm(el.textContent))
|
|
270
|
+
.filter((v) => v.length > 0);
|
|
271
|
+
const communicationPosition = norm(
|
|
272
|
+
root.querySelector(".position-item .position-name")?.textContent,
|
|
273
|
+
);
|
|
274
|
+
const expectation = norm(root.querySelector(".position-item.expect .value.job")?.textContent);
|
|
275
|
+
const remark = norm(
|
|
276
|
+
root.querySelector(".label-remark-content .remark span:last-child")?.textContent,
|
|
277
|
+
);
|
|
278
|
+
return {
|
|
279
|
+
name,
|
|
280
|
+
active,
|
|
281
|
+
basicFacts,
|
|
282
|
+
recentExperience,
|
|
283
|
+
communicationPosition,
|
|
284
|
+
expectation,
|
|
285
|
+
remark,
|
|
286
|
+
};
|
|
287
287
|
})()`));
|
|
288
288
|
return scraped;
|
|
289
289
|
}
|
|
290
290
|
async function waitForOpenedCandidateChat(page, expectedName) {
|
|
291
291
|
const expectedNameLiteral = JSON.stringify(expectedName);
|
|
292
|
-
await page.waitForFunction(`(() => {
|
|
293
|
-
const name = ${expectedNameLiteral};
|
|
294
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
295
|
-
const isVisible = (el) => {
|
|
296
|
-
if (!el) return false;
|
|
297
|
-
const rect = el.getBoundingClientRect();
|
|
298
|
-
const style = window.getComputedStyle(el);
|
|
299
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
300
|
-
};
|
|
301
|
-
const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
|
|
302
|
-
const selectedName = norm(selected?.querySelector(".geek-name")?.textContent);
|
|
303
|
-
const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
|
|
304
|
-
const detailName = norm(root?.querySelector(".name-box")?.textContent);
|
|
305
|
-
return selectedName === name && detailName === name;
|
|
292
|
+
await page.waitForFunction(`(() => {
|
|
293
|
+
const name = ${expectedNameLiteral};
|
|
294
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
295
|
+
const isVisible = (el) => {
|
|
296
|
+
if (!el) return false;
|
|
297
|
+
const rect = el.getBoundingClientRect();
|
|
298
|
+
const style = window.getComputedStyle(el);
|
|
299
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
300
|
+
};
|
|
301
|
+
const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
|
|
302
|
+
const selectedName = norm(selected?.querySelector(".geek-name")?.textContent);
|
|
303
|
+
const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
|
|
304
|
+
const detailName = norm(root?.querySelector(".name-box")?.textContent);
|
|
305
|
+
return selectedName === name && detailName === name;
|
|
306
306
|
})()`, { timeout: 15_000 });
|
|
307
|
-
await page.waitForFunction(`(() => {
|
|
308
|
-
const isVisible = (el) => {
|
|
309
|
-
if (!el) return false;
|
|
310
|
-
const rect = el.getBoundingClientRect();
|
|
311
|
-
const style = window.getComputedStyle(el);
|
|
312
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
313
|
-
};
|
|
314
|
-
const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
|
|
315
|
-
const list = lists[lists.length - 1];
|
|
316
|
-
if (!list) return false;
|
|
317
|
-
const items = list.querySelectorAll(".message-item");
|
|
318
|
-
if (!items || items.length === 0) return false;
|
|
319
|
-
return Array.from(items).some((item) => {
|
|
320
|
-
const txt =
|
|
321
|
-
item.querySelector(".item-friend .text span")?.textContent ??
|
|
322
|
-
item.querySelector(".item-myself .text span")?.textContent ??
|
|
323
|
-
item.querySelector(".item-system .message-card-top-title")?.textContent ??
|
|
324
|
-
"";
|
|
325
|
-
return txt.replace(/\\s+/g, " ").trim().length > 0;
|
|
326
|
-
});
|
|
307
|
+
await page.waitForFunction(`(() => {
|
|
308
|
+
const isVisible = (el) => {
|
|
309
|
+
if (!el) return false;
|
|
310
|
+
const rect = el.getBoundingClientRect();
|
|
311
|
+
const style = window.getComputedStyle(el);
|
|
312
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
313
|
+
};
|
|
314
|
+
const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
|
|
315
|
+
const list = lists[lists.length - 1];
|
|
316
|
+
if (!list) return false;
|
|
317
|
+
const items = list.querySelectorAll(".message-item");
|
|
318
|
+
if (!items || items.length === 0) return false;
|
|
319
|
+
return Array.from(items).some((item) => {
|
|
320
|
+
const txt =
|
|
321
|
+
item.querySelector(".item-friend .text span")?.textContent ??
|
|
322
|
+
item.querySelector(".item-myself .text span")?.textContent ??
|
|
323
|
+
item.querySelector(".item-system .message-card-top-title")?.textContent ??
|
|
324
|
+
"";
|
|
325
|
+
return txt.replace(/\\s+/g, " ").trim().length > 0;
|
|
326
|
+
});
|
|
327
327
|
})()`, { timeout: 20_000 });
|
|
328
328
|
}
|
|
329
329
|
async function scrapeCurrentChatMessages(page) {
|
|
330
|
-
return (await page.evaluate(`(() => {
|
|
331
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
332
|
-
const isVisible = (el) => {
|
|
333
|
-
if (!el) return false;
|
|
334
|
-
const rect = el.getBoundingClientRect();
|
|
335
|
-
const style = window.getComputedStyle(el);
|
|
336
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
337
|
-
};
|
|
338
|
-
function isBossPriorityUpsellSystemText(text) {
|
|
339
|
-
return norm(text).indexOf("优先提醒") !== -1;
|
|
340
|
-
}
|
|
341
|
-
const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
|
|
342
|
-
const list = lists[lists.length - 1];
|
|
343
|
-
if (!list) {
|
|
344
|
-
throw new Error("未找到可见聊天消息列表(.chat-message-list)。");
|
|
345
|
-
}
|
|
346
|
-
const items = Array.from(list.querySelectorAll(".message-item"));
|
|
347
|
-
let currentTime = "";
|
|
348
|
-
const messages = [];
|
|
349
|
-
let hasFriendResumeAttachment = false;
|
|
350
|
-
for (const item of items) {
|
|
351
|
-
const timeNode = item.querySelector(".message-time .time");
|
|
352
|
-
if (timeNode) {
|
|
353
|
-
const t = norm(timeNode.textContent);
|
|
354
|
-
if (t) currentTime = t;
|
|
355
|
-
}
|
|
356
|
-
const friendRoot = item.querySelector(".item-friend");
|
|
357
|
-
let friendText = "";
|
|
358
|
-
if (friendRoot) {
|
|
359
|
-
friendText = norm(friendRoot.querySelector(".text > span")?.textContent);
|
|
360
|
-
if (!friendText) {
|
|
361
|
-
const resumeIcon = friendRoot.querySelector(".resume-icon");
|
|
362
|
-
const title = norm(friendRoot.querySelector(".message-card-top-title")?.textContent);
|
|
363
|
-
const cardBtn = norm(friendRoot.querySelector(".message-card-buttons .card-btn")?.textContent);
|
|
364
|
-
if (resumeIcon) hasFriendResumeAttachment = true;
|
|
365
|
-
if (title || cardBtn) {
|
|
366
|
-
const parts = [];
|
|
367
|
-
if (title) parts.push(title);
|
|
368
|
-
if (cardBtn) parts.push(cardBtn);
|
|
369
|
-
friendText = parts.length ? parts.join(" · ") : "";
|
|
370
|
-
}
|
|
371
|
-
if (!friendText) friendText = norm(friendRoot.querySelector(".text")?.textContent);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const myselfText = norm(item.querySelector(".item-myself .text span")?.textContent);
|
|
375
|
-
const systemText =
|
|
376
|
-
norm(item.querySelector(".item-system .message-card-top-title")?.textContent) ||
|
|
377
|
-
norm(item.querySelector(".item-system .text span")?.textContent);
|
|
378
|
-
if (friendText) {
|
|
379
|
-
messages.push({ text: friendText, time: currentTime, from: "friend" });
|
|
380
|
-
} else if (myselfText) {
|
|
381
|
-
messages.push({ text: myselfText, time: currentTime, from: "myself" });
|
|
382
|
-
} else if (systemText) {
|
|
383
|
-
if (!isBossPriorityUpsellSystemText(systemText)) {
|
|
384
|
-
messages.push({ text: systemText, time: currentTime, from: "system" });
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return { messages, hasFriendResumeAttachment };
|
|
330
|
+
return (await page.evaluate(`(() => {
|
|
331
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
332
|
+
const isVisible = (el) => {
|
|
333
|
+
if (!el) return false;
|
|
334
|
+
const rect = el.getBoundingClientRect();
|
|
335
|
+
const style = window.getComputedStyle(el);
|
|
336
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
337
|
+
};
|
|
338
|
+
function isBossPriorityUpsellSystemText(text) {
|
|
339
|
+
return norm(text).indexOf("优先提醒") !== -1;
|
|
340
|
+
}
|
|
341
|
+
const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
|
|
342
|
+
const list = lists[lists.length - 1];
|
|
343
|
+
if (!list) {
|
|
344
|
+
throw new Error("未找到可见聊天消息列表(.chat-message-list)。");
|
|
345
|
+
}
|
|
346
|
+
const items = Array.from(list.querySelectorAll(".message-item"));
|
|
347
|
+
let currentTime = "";
|
|
348
|
+
const messages = [];
|
|
349
|
+
let hasFriendResumeAttachment = false;
|
|
350
|
+
for (const item of items) {
|
|
351
|
+
const timeNode = item.querySelector(".message-time .time");
|
|
352
|
+
if (timeNode) {
|
|
353
|
+
const t = norm(timeNode.textContent);
|
|
354
|
+
if (t) currentTime = t;
|
|
355
|
+
}
|
|
356
|
+
const friendRoot = item.querySelector(".item-friend");
|
|
357
|
+
let friendText = "";
|
|
358
|
+
if (friendRoot) {
|
|
359
|
+
friendText = norm(friendRoot.querySelector(".text > span")?.textContent);
|
|
360
|
+
if (!friendText) {
|
|
361
|
+
const resumeIcon = friendRoot.querySelector(".resume-icon");
|
|
362
|
+
const title = norm(friendRoot.querySelector(".message-card-top-title")?.textContent);
|
|
363
|
+
const cardBtn = norm(friendRoot.querySelector(".message-card-buttons .card-btn")?.textContent);
|
|
364
|
+
if (resumeIcon) hasFriendResumeAttachment = true;
|
|
365
|
+
if (title || cardBtn) {
|
|
366
|
+
const parts = [];
|
|
367
|
+
if (title) parts.push(title);
|
|
368
|
+
if (cardBtn) parts.push(cardBtn);
|
|
369
|
+
friendText = parts.length ? parts.join(" · ") : "";
|
|
370
|
+
}
|
|
371
|
+
if (!friendText) friendText = norm(friendRoot.querySelector(".text")?.textContent);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const myselfText = norm(item.querySelector(".item-myself .text span")?.textContent);
|
|
375
|
+
const systemText =
|
|
376
|
+
norm(item.querySelector(".item-system .message-card-top-title")?.textContent) ||
|
|
377
|
+
norm(item.querySelector(".item-system .text span")?.textContent);
|
|
378
|
+
if (friendText) {
|
|
379
|
+
messages.push({ text: friendText, time: currentTime, from: "friend" });
|
|
380
|
+
} else if (myselfText) {
|
|
381
|
+
messages.push({ text: myselfText, time: currentTime, from: "myself" });
|
|
382
|
+
} else if (systemText) {
|
|
383
|
+
if (!isBossPriorityUpsellSystemText(systemText)) {
|
|
384
|
+
messages.push({ text: systemText, time: currentTime, from: "system" });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return { messages, hasFriendResumeAttachment };
|
|
389
389
|
})()`));
|
|
390
390
|
}
|
|
391
391
|
async function renderOpenedCandidateChat(page, foundName) {
|
|
@@ -449,24 +449,24 @@ export async function runOpenCandidateChatByIndex(page, params) {
|
|
|
449
449
|
if (!isBossChatIndexUrl(page.url())) {
|
|
450
450
|
throw new Error('当前不在沟通列表页(/web/chat/index),无法打开候选人聊天。');
|
|
451
451
|
}
|
|
452
|
-
const rowInfo = (await page.evaluate(`((rowIndex) => {
|
|
453
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
454
|
-
const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
|
|
455
|
-
const total = wraps.length;
|
|
456
|
-
const wrap = wraps[rowIndex - 1];
|
|
457
|
-
if (!wrap) return { total, name: "", job: "", message: "", time: "", x: 0, y: 0 };
|
|
458
|
-
const row = wrap.querySelector(".geek-item") || wrap;
|
|
459
|
-
row.scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
|
|
460
|
-
const rect = row.getBoundingClientRect();
|
|
461
|
-
return {
|
|
462
|
-
total,
|
|
463
|
-
name: norm(wrap.querySelector(".geek-name")?.textContent),
|
|
464
|
-
job: norm(wrap.querySelector(".source-job")?.textContent),
|
|
465
|
-
message: norm(wrap.querySelector(".push-text")?.textContent),
|
|
466
|
-
time: norm(wrap.querySelector(".time")?.textContent),
|
|
467
|
-
x: rect.left + rect.width / 2,
|
|
468
|
-
y: rect.top + rect.height / 2,
|
|
469
|
-
};
|
|
452
|
+
const rowInfo = (await page.evaluate(`((rowIndex) => {
|
|
453
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
454
|
+
const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
|
|
455
|
+
const total = wraps.length;
|
|
456
|
+
const wrap = wraps[rowIndex - 1];
|
|
457
|
+
if (!wrap) return { total, name: "", job: "", message: "", time: "", x: 0, y: 0 };
|
|
458
|
+
const row = wrap.querySelector(".geek-item") || wrap;
|
|
459
|
+
row.scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
|
|
460
|
+
const rect = row.getBoundingClientRect();
|
|
461
|
+
return {
|
|
462
|
+
total,
|
|
463
|
+
name: norm(wrap.querySelector(".geek-name")?.textContent),
|
|
464
|
+
job: norm(wrap.querySelector(".source-job")?.textContent),
|
|
465
|
+
message: norm(wrap.querySelector(".push-text")?.textContent),
|
|
466
|
+
time: norm(wrap.querySelector(".time")?.textContent),
|
|
467
|
+
x: rect.left + rect.width / 2,
|
|
468
|
+
y: rect.top + rect.height / 2,
|
|
469
|
+
};
|
|
470
470
|
})(${JSON.stringify(params.index)})`));
|
|
471
471
|
if (!rowInfo.name) {
|
|
472
472
|
throw new Error(`聊天列表序号 ${params.index} 不存在;当前${filter === 'unread' ? '未读' : '全部'}列表共 ${rowInfo.total} 条。`);
|
|
@@ -512,14 +512,14 @@ export async function runOpenCandidateChat(page, candidateName, exact = true) {
|
|
|
512
512
|
}
|
|
513
513
|
if (targetWrap)
|
|
514
514
|
break;
|
|
515
|
-
const scrollState = (await page.evaluate(`(() => {${CHAT_LIST_SCROLLER_JS}
|
|
516
|
-
if (!scroller) return { moved: false, atEnd: true, noScroller: true };
|
|
517
|
-
const prev = scroller.scrollTop;
|
|
518
|
-
const step = Math.max(160, Math.floor(scroller.clientHeight * 0.8));
|
|
519
|
-
scroller.scrollTop = Math.min(scroller.scrollTop + step, scroller.scrollHeight);
|
|
520
|
-
const moved = scroller.scrollTop !== prev;
|
|
521
|
-
const atEnd = scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2;
|
|
522
|
-
return { moved, atEnd };
|
|
515
|
+
const scrollState = (await page.evaluate(`(() => {${CHAT_LIST_SCROLLER_JS}
|
|
516
|
+
if (!scroller) return { moved: false, atEnd: true, noScroller: true };
|
|
517
|
+
const prev = scroller.scrollTop;
|
|
518
|
+
const step = Math.max(160, Math.floor(scroller.clientHeight * 0.8));
|
|
519
|
+
scroller.scrollTop = Math.min(scroller.scrollTop + step, scroller.scrollHeight);
|
|
520
|
+
const moved = scroller.scrollTop !== prev;
|
|
521
|
+
const atEnd = scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2;
|
|
522
|
+
return { moved, atEnd };
|
|
523
523
|
})()`));
|
|
524
524
|
if (scrollState.noScroller) {
|
|
525
525
|
break;
|
|
@@ -544,57 +544,57 @@ export async function runOpenCandidateChat(page, candidateName, exact = true) {
|
|
|
544
544
|
}
|
|
545
545
|
const clickNameLiteral = JSON.stringify(foundName || targetName);
|
|
546
546
|
const clickExactLiteral = JSON.stringify(exact);
|
|
547
|
-
const scrolledToTarget = (await page.evaluate(`(() => {
|
|
548
|
-
const targetName = ${clickNameLiteral};
|
|
549
|
-
const exactMatch = ${clickExactLiteral};
|
|
550
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
551
|
-
const matches = (value) => exactMatch ? value === targetName : value.includes(targetName);
|
|
552
|
-
const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
|
|
553
|
-
const wrap = wraps.find((el) => matches(norm(el.querySelector(".geek-name")?.textContent)));
|
|
554
|
-
if (!wrap) return false;
|
|
555
|
-
const row = wrap.querySelector(".geek-item") || wrap;
|
|
556
|
-
let node = row.parentElement;
|
|
557
|
-
let scroller = null;
|
|
558
|
-
while (node) {
|
|
559
|
-
// 同 CHAT_LIST_SCROLLER_JS:Boss 列表容器是 overflow-y:hidden + 自绘滚动条
|
|
560
|
-
const overflowY = window.getComputedStyle(node).overflowY;
|
|
561
|
-
if (
|
|
562
|
-
overflowY !== "visible" &&
|
|
563
|
-
node.scrollHeight > node.clientHeight + 2 &&
|
|
564
|
-
node.clientHeight > 100
|
|
565
|
-
) {
|
|
566
|
-
scroller = node;
|
|
567
|
-
break;
|
|
568
|
-
}
|
|
569
|
-
node = node.parentElement;
|
|
570
|
-
}
|
|
571
|
-
if (scroller) {
|
|
572
|
-
const rowRect = row.getBoundingClientRect();
|
|
573
|
-
const scrollerRect = scroller.getBoundingClientRect();
|
|
574
|
-
scroller.scrollTop += rowRect.top - scrollerRect.top - (scroller.clientHeight - rowRect.height) / 2;
|
|
575
|
-
} else {
|
|
576
|
-
row.scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
|
|
577
|
-
}
|
|
578
|
-
return true;
|
|
547
|
+
const scrolledToTarget = (await page.evaluate(`(() => {
|
|
548
|
+
const targetName = ${clickNameLiteral};
|
|
549
|
+
const exactMatch = ${clickExactLiteral};
|
|
550
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
551
|
+
const matches = (value) => exactMatch ? value === targetName : value.includes(targetName);
|
|
552
|
+
const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
|
|
553
|
+
const wrap = wraps.find((el) => matches(norm(el.querySelector(".geek-name")?.textContent)));
|
|
554
|
+
if (!wrap) return false;
|
|
555
|
+
const row = wrap.querySelector(".geek-item") || wrap;
|
|
556
|
+
let node = row.parentElement;
|
|
557
|
+
let scroller = null;
|
|
558
|
+
while (node) {
|
|
559
|
+
// 同 CHAT_LIST_SCROLLER_JS:Boss 列表容器是 overflow-y:hidden + 自绘滚动条
|
|
560
|
+
const overflowY = window.getComputedStyle(node).overflowY;
|
|
561
|
+
if (
|
|
562
|
+
overflowY !== "visible" &&
|
|
563
|
+
node.scrollHeight > node.clientHeight + 2 &&
|
|
564
|
+
node.clientHeight > 100
|
|
565
|
+
) {
|
|
566
|
+
scroller = node;
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
node = node.parentElement;
|
|
570
|
+
}
|
|
571
|
+
if (scroller) {
|
|
572
|
+
const rowRect = row.getBoundingClientRect();
|
|
573
|
+
const scrollerRect = scroller.getBoundingClientRect();
|
|
574
|
+
scroller.scrollTop += rowRect.top - scrollerRect.top - (scroller.clientHeight - rowRect.height) / 2;
|
|
575
|
+
} else {
|
|
576
|
+
row.scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
|
|
577
|
+
}
|
|
578
|
+
return true;
|
|
579
579
|
})()`));
|
|
580
580
|
if (!scrolledToTarget) {
|
|
581
581
|
throw new Error(`未能重新定位候选人行:${foundName || targetName}`);
|
|
582
582
|
}
|
|
583
583
|
await sleepRandom(120, 220);
|
|
584
|
-
const clickPoint = (await page.evaluate(`(() => {
|
|
585
|
-
const targetName = ${clickNameLiteral};
|
|
586
|
-
const exactMatch = ${clickExactLiteral};
|
|
587
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
588
|
-
const matches = (value) => exactMatch ? value === targetName : value.includes(targetName);
|
|
589
|
-
const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
|
|
590
|
-
const wrap = wraps.find((el) => matches(norm(el.querySelector(".geek-name")?.textContent)));
|
|
591
|
-
if (!wrap) return null;
|
|
592
|
-
const row = wrap.querySelector(".geek-item") || wrap;
|
|
593
|
-
const rect = row.getBoundingClientRect();
|
|
594
|
-
return {
|
|
595
|
-
x: rect.left + rect.width / 2,
|
|
596
|
-
y: rect.top + rect.height / 2,
|
|
597
|
-
};
|
|
584
|
+
const clickPoint = (await page.evaluate(`(() => {
|
|
585
|
+
const targetName = ${clickNameLiteral};
|
|
586
|
+
const exactMatch = ${clickExactLiteral};
|
|
587
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
588
|
+
const matches = (value) => exactMatch ? value === targetName : value.includes(targetName);
|
|
589
|
+
const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
|
|
590
|
+
const wrap = wraps.find((el) => matches(norm(el.querySelector(".geek-name")?.textContent)));
|
|
591
|
+
if (!wrap) return null;
|
|
592
|
+
const row = wrap.querySelector(".geek-item") || wrap;
|
|
593
|
+
const rect = row.getBoundingClientRect();
|
|
594
|
+
return {
|
|
595
|
+
x: rect.left + rect.width / 2,
|
|
596
|
+
y: rect.top + rect.height / 2,
|
|
597
|
+
};
|
|
598
598
|
})()`));
|
|
599
599
|
if (!clickPoint) {
|
|
600
600
|
throw new Error(`未能重新定位候选人行:${foundName || targetName}`);
|
|
@@ -604,126 +604,126 @@ export async function runOpenCandidateChat(page, candidateName, exact = true) {
|
|
|
604
604
|
try {
|
|
605
605
|
const expectedNameLiteral = JSON.stringify(foundName || targetName);
|
|
606
606
|
const exactLiteral = JSON.stringify(exact);
|
|
607
|
-
await page.waitForFunction(`(() => {
|
|
608
|
-
const name = ${expectedNameLiteral};
|
|
609
|
-
const exactMatch = ${exactLiteral};
|
|
610
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
611
|
-
const matches = (value) => exactMatch ? value === name : value.includes(name);
|
|
612
|
-
const isVisible = (el) => {
|
|
613
|
-
if (!el) return false;
|
|
614
|
-
const rect = el.getBoundingClientRect();
|
|
615
|
-
const style = window.getComputedStyle(el);
|
|
616
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
617
|
-
};
|
|
618
|
-
const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
|
|
619
|
-
const selectedName = norm(selected?.querySelector(".geek-name")?.textContent);
|
|
620
|
-
const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
|
|
621
|
-
const detailName = norm(root?.querySelector(".name-box")?.textContent);
|
|
622
|
-
return matches(selectedName) && matches(detailName);
|
|
607
|
+
await page.waitForFunction(`(() => {
|
|
608
|
+
const name = ${expectedNameLiteral};
|
|
609
|
+
const exactMatch = ${exactLiteral};
|
|
610
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
611
|
+
const matches = (value) => exactMatch ? value === name : value.includes(name);
|
|
612
|
+
const isVisible = (el) => {
|
|
613
|
+
if (!el) return false;
|
|
614
|
+
const rect = el.getBoundingClientRect();
|
|
615
|
+
const style = window.getComputedStyle(el);
|
|
616
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
617
|
+
};
|
|
618
|
+
const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
|
|
619
|
+
const selectedName = norm(selected?.querySelector(".geek-name")?.textContent);
|
|
620
|
+
const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
|
|
621
|
+
const detailName = norm(root?.querySelector(".name-box")?.textContent);
|
|
622
|
+
return matches(selectedName) && matches(detailName);
|
|
623
623
|
})()`, { timeout: 15_000 });
|
|
624
|
-
await page.waitForFunction(`(() => {
|
|
625
|
-
const isVisible = (el) => {
|
|
626
|
-
if (!el) return false;
|
|
627
|
-
const rect = el.getBoundingClientRect();
|
|
628
|
-
const style = window.getComputedStyle(el);
|
|
629
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
630
|
-
};
|
|
631
|
-
const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
|
|
632
|
-
const list = lists[lists.length - 1];
|
|
633
|
-
if (!list) return false;
|
|
634
|
-
const items = list.querySelectorAll(".message-item");
|
|
635
|
-
if (!items || items.length === 0) return false;
|
|
636
|
-
const hasText = Array.from(items).some((item) => {
|
|
637
|
-
const txt =
|
|
638
|
-
item.querySelector(".item-friend .text span")?.textContent ??
|
|
639
|
-
item.querySelector(".item-myself .text span")?.textContent ??
|
|
640
|
-
item.querySelector(".item-system .message-card-top-title")?.textContent ??
|
|
641
|
-
"";
|
|
642
|
-
return txt.replace(/\\s+/g, " ").trim().length > 0;
|
|
643
|
-
});
|
|
644
|
-
return hasText;
|
|
624
|
+
await page.waitForFunction(`(() => {
|
|
625
|
+
const isVisible = (el) => {
|
|
626
|
+
if (!el) return false;
|
|
627
|
+
const rect = el.getBoundingClientRect();
|
|
628
|
+
const style = window.getComputedStyle(el);
|
|
629
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
630
|
+
};
|
|
631
|
+
const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
|
|
632
|
+
const list = lists[lists.length - 1];
|
|
633
|
+
if (!list) return false;
|
|
634
|
+
const items = list.querySelectorAll(".message-item");
|
|
635
|
+
if (!items || items.length === 0) return false;
|
|
636
|
+
const hasText = Array.from(items).some((item) => {
|
|
637
|
+
const txt =
|
|
638
|
+
item.querySelector(".item-friend .text span")?.textContent ??
|
|
639
|
+
item.querySelector(".item-myself .text span")?.textContent ??
|
|
640
|
+
item.querySelector(".item-system .message-card-top-title")?.textContent ??
|
|
641
|
+
"";
|
|
642
|
+
return txt.replace(/\\s+/g, " ").trim().length > 0;
|
|
643
|
+
});
|
|
644
|
+
return hasText;
|
|
645
645
|
})()`, { timeout: 20_000 });
|
|
646
646
|
}
|
|
647
647
|
catch {
|
|
648
|
-
const state = (await page.evaluate(`(() => {
|
|
649
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
650
|
-
const isVisible = (el) => {
|
|
651
|
-
if (!el) return false;
|
|
652
|
-
const rect = el.getBoundingClientRect();
|
|
653
|
-
const style = window.getComputedStyle(el);
|
|
654
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
655
|
-
};
|
|
656
|
-
const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
|
|
657
|
-
const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
|
|
658
|
-
return {
|
|
659
|
-
selectedName: norm(selected?.querySelector(".geek-name")?.textContent),
|
|
660
|
-
detailName: norm(root?.querySelector(".name-box")?.textContent),
|
|
661
|
-
};
|
|
648
|
+
const state = (await page.evaluate(`(() => {
|
|
649
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
650
|
+
const isVisible = (el) => {
|
|
651
|
+
if (!el) return false;
|
|
652
|
+
const rect = el.getBoundingClientRect();
|
|
653
|
+
const style = window.getComputedStyle(el);
|
|
654
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
655
|
+
};
|
|
656
|
+
const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
|
|
657
|
+
const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
|
|
658
|
+
return {
|
|
659
|
+
selectedName: norm(selected?.querySelector(".geek-name")?.textContent),
|
|
660
|
+
detailName: norm(root?.querySelector(".name-box")?.textContent),
|
|
661
|
+
};
|
|
662
662
|
})()`));
|
|
663
663
|
throw new Error(`已尝试点击 ${foundName},但未检测到对应聊天详情面板(selected=${state.selectedName || '空'},detail=${state.detailName || '空'})。`);
|
|
664
664
|
}
|
|
665
665
|
let fullMessages = [];
|
|
666
666
|
let hasFriendResumeAttachment = false;
|
|
667
|
-
const scraped = (await page.evaluate(`(() => {
|
|
668
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
669
|
-
const isVisible = (el) => {
|
|
670
|
-
if (!el) return false;
|
|
671
|
-
const rect = el.getBoundingClientRect();
|
|
672
|
-
const style = window.getComputedStyle(el);
|
|
673
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
674
|
-
};
|
|
675
|
-
/** Boss 系统里的「消息优先提醒」增值服务条,对业务无意义,过滤掉 */
|
|
676
|
-
function isBossPriorityUpsellSystemText(text) {
|
|
677
|
-
return norm(text).indexOf("优先提醒") !== -1;
|
|
678
|
-
}
|
|
679
|
-
const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
|
|
680
|
-
const list = lists[lists.length - 1];
|
|
681
|
-
if (!list) {
|
|
682
|
-
throw new Error("未找到可见聊天消息列表(.chat-message-list)。");
|
|
683
|
-
}
|
|
684
|
-
const items = Array.from(list.querySelectorAll(".message-item"));
|
|
685
|
-
let currentTime = "";
|
|
686
|
-
const messages = [];
|
|
687
|
-
let hasFriendResumeAttachment = false;
|
|
688
|
-
for (const item of items) {
|
|
689
|
-
const timeNode = item.querySelector(".message-time .time");
|
|
690
|
-
if (timeNode) {
|
|
691
|
-
const t = norm(timeNode.textContent);
|
|
692
|
-
if (t) currentTime = t;
|
|
693
|
-
}
|
|
694
|
-
const friendRoot = item.querySelector(".item-friend");
|
|
695
|
-
let friendText = "";
|
|
696
|
-
if (friendRoot) {
|
|
697
|
-
friendText = norm(friendRoot.querySelector(".text > span")?.textContent);
|
|
698
|
-
if (!friendText) {
|
|
699
|
-
const resumeIcon = friendRoot.querySelector(".resume-icon");
|
|
700
|
-
const title = norm(friendRoot.querySelector(".message-card-top-title")?.textContent);
|
|
701
|
-
const cardBtn = norm(friendRoot.querySelector(".message-card-buttons .card-btn")?.textContent);
|
|
702
|
-
if (resumeIcon) hasFriendResumeAttachment = true;
|
|
703
|
-
if (title || cardBtn) {
|
|
704
|
-
const parts = [];
|
|
705
|
-
if (title) parts.push(title);
|
|
706
|
-
if (cardBtn) parts.push(cardBtn);
|
|
707
|
-
friendText = parts.length ? parts.join(" · ") : "";
|
|
708
|
-
}
|
|
709
|
-
if (!friendText) friendText = norm(friendRoot.querySelector(".text")?.textContent);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
const myselfText = norm(item.querySelector(".item-myself .text span")?.textContent);
|
|
713
|
-
const systemText =
|
|
714
|
-
norm(item.querySelector(".item-system .message-card-top-title")?.textContent) ||
|
|
715
|
-
norm(item.querySelector(".item-system .text span")?.textContent);
|
|
716
|
-
if (friendText) {
|
|
717
|
-
messages.push({ text: friendText, time: currentTime, from: "friend" });
|
|
718
|
-
} else if (myselfText) {
|
|
719
|
-
messages.push({ text: myselfText, time: currentTime, from: "myself" });
|
|
720
|
-
} else if (systemText) {
|
|
721
|
-
if (!isBossPriorityUpsellSystemText(systemText)) {
|
|
722
|
-
messages.push({ text: systemText, time: currentTime, from: "system" });
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
return { messages, hasFriendResumeAttachment };
|
|
667
|
+
const scraped = (await page.evaluate(`(() => {
|
|
668
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
669
|
+
const isVisible = (el) => {
|
|
670
|
+
if (!el) return false;
|
|
671
|
+
const rect = el.getBoundingClientRect();
|
|
672
|
+
const style = window.getComputedStyle(el);
|
|
673
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
674
|
+
};
|
|
675
|
+
/** Boss 系统里的「消息优先提醒」增值服务条,对业务无意义,过滤掉 */
|
|
676
|
+
function isBossPriorityUpsellSystemText(text) {
|
|
677
|
+
return norm(text).indexOf("优先提醒") !== -1;
|
|
678
|
+
}
|
|
679
|
+
const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
|
|
680
|
+
const list = lists[lists.length - 1];
|
|
681
|
+
if (!list) {
|
|
682
|
+
throw new Error("未找到可见聊天消息列表(.chat-message-list)。");
|
|
683
|
+
}
|
|
684
|
+
const items = Array.from(list.querySelectorAll(".message-item"));
|
|
685
|
+
let currentTime = "";
|
|
686
|
+
const messages = [];
|
|
687
|
+
let hasFriendResumeAttachment = false;
|
|
688
|
+
for (const item of items) {
|
|
689
|
+
const timeNode = item.querySelector(".message-time .time");
|
|
690
|
+
if (timeNode) {
|
|
691
|
+
const t = norm(timeNode.textContent);
|
|
692
|
+
if (t) currentTime = t;
|
|
693
|
+
}
|
|
694
|
+
const friendRoot = item.querySelector(".item-friend");
|
|
695
|
+
let friendText = "";
|
|
696
|
+
if (friendRoot) {
|
|
697
|
+
friendText = norm(friendRoot.querySelector(".text > span")?.textContent);
|
|
698
|
+
if (!friendText) {
|
|
699
|
+
const resumeIcon = friendRoot.querySelector(".resume-icon");
|
|
700
|
+
const title = norm(friendRoot.querySelector(".message-card-top-title")?.textContent);
|
|
701
|
+
const cardBtn = norm(friendRoot.querySelector(".message-card-buttons .card-btn")?.textContent);
|
|
702
|
+
if (resumeIcon) hasFriendResumeAttachment = true;
|
|
703
|
+
if (title || cardBtn) {
|
|
704
|
+
const parts = [];
|
|
705
|
+
if (title) parts.push(title);
|
|
706
|
+
if (cardBtn) parts.push(cardBtn);
|
|
707
|
+
friendText = parts.length ? parts.join(" · ") : "";
|
|
708
|
+
}
|
|
709
|
+
if (!friendText) friendText = norm(friendRoot.querySelector(".text")?.textContent);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const myselfText = norm(item.querySelector(".item-myself .text span")?.textContent);
|
|
713
|
+
const systemText =
|
|
714
|
+
norm(item.querySelector(".item-system .message-card-top-title")?.textContent) ||
|
|
715
|
+
norm(item.querySelector(".item-system .text span")?.textContent);
|
|
716
|
+
if (friendText) {
|
|
717
|
+
messages.push({ text: friendText, time: currentTime, from: "friend" });
|
|
718
|
+
} else if (myselfText) {
|
|
719
|
+
messages.push({ text: myselfText, time: currentTime, from: "myself" });
|
|
720
|
+
} else if (systemText) {
|
|
721
|
+
if (!isBossPriorityUpsellSystemText(systemText)) {
|
|
722
|
+
messages.push({ text: systemText, time: currentTime, from: "system" });
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return { messages, hasFriendResumeAttachment };
|
|
727
727
|
})()`));
|
|
728
728
|
fullMessages = scraped.messages;
|
|
729
729
|
hasFriendResumeAttachment = scraped.hasFriendResumeAttachment;
|