@viyzhu/boss-cli-fork 0.6.8

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.
Files changed (159) hide show
  1. package/AGENTS.md +30 -0
  2. package/LICENSE +674 -0
  3. package/README.md +223 -0
  4. package/dist/browser/browser_session.d.ts +27 -0
  5. package/dist/browser/browser_session.d.ts.map +1 -0
  6. package/dist/browser/browser_session.js +246 -0
  7. package/dist/browser/browser_session.js.map +1 -0
  8. package/dist/browser/cdp_browser.d.ts +50 -0
  9. package/dist/browser/cdp_browser.d.ts.map +1 -0
  10. package/dist/browser/cdp_browser.js +315 -0
  11. package/dist/browser/cdp_browser.js.map +1 -0
  12. package/dist/browser/human_delay.d.ts +117 -0
  13. package/dist/browser/human_delay.d.ts.map +1 -0
  14. package/dist/browser/human_delay.js +71 -0
  15. package/dist/browser/human_delay.js.map +1 -0
  16. package/dist/browser/index.d.ts +9 -0
  17. package/dist/browser/index.d.ts.map +1 -0
  18. package/dist/browser/index.js +9 -0
  19. package/dist/browser/index.js.map +1 -0
  20. package/dist/browser/timing.d.ts +7 -0
  21. package/dist/browser/timing.d.ts.map +1 -0
  22. package/dist/browser/timing.js +34 -0
  23. package/dist/browser/timing.js.map +1 -0
  24. package/dist/browser/viewport_temp.d.ts +15 -0
  25. package/dist/browser/viewport_temp.d.ts.map +1 -0
  26. package/dist/browser/viewport_temp.js +78 -0
  27. package/dist/browser/viewport_temp.js.map +1 -0
  28. package/dist/cli/banner.d.ts +2 -0
  29. package/dist/cli/banner.d.ts.map +1 -0
  30. package/dist/cli/banner.js +69 -0
  31. package/dist/cli/banner.js.map +1 -0
  32. package/dist/cli/cliRouter.d.ts +9 -0
  33. package/dist/cli/cliRouter.d.ts.map +1 -0
  34. package/dist/cli/cliRouter.js +604 -0
  35. package/dist/cli/cliRouter.js.map +1 -0
  36. package/dist/cli/index.d.ts +3 -0
  37. package/dist/cli/index.d.ts.map +1 -0
  38. package/dist/cli/index.js +34 -0
  39. package/dist/cli/index.js.map +1 -0
  40. package/dist/cli/version.d.ts +29 -0
  41. package/dist/cli/version.d.ts.map +1 -0
  42. package/dist/cli/version.js +180 -0
  43. package/dist/cli/version.js.map +1 -0
  44. package/dist/common/auth.d.ts +44 -0
  45. package/dist/common/auth.d.ts.map +1 -0
  46. package/dist/common/auth.js +168 -0
  47. package/dist/common/auth.js.map +1 -0
  48. package/dist/common/baidu_user_env.d.ts +7 -0
  49. package/dist/common/baidu_user_env.d.ts.map +1 -0
  50. package/dist/common/baidu_user_env.js +56 -0
  51. package/dist/common/baidu_user_env.js.map +1 -0
  52. package/dist/common/boss_availability.d.ts +5 -0
  53. package/dist/common/boss_availability.d.ts.map +1 -0
  54. package/dist/common/boss_availability.js +245 -0
  55. package/dist/common/boss_availability.js.map +1 -0
  56. package/dist/common/boss_modal.d.ts +14 -0
  57. package/dist/common/boss_modal.d.ts.map +1 -0
  58. package/dist/common/boss_modal.js +81 -0
  59. package/dist/common/boss_modal.js.map +1 -0
  60. package/dist/common/boss_page_guards.d.ts +14 -0
  61. package/dist/common/boss_page_guards.d.ts.map +1 -0
  62. package/dist/common/boss_page_guards.js +596 -0
  63. package/dist/common/boss_page_guards.js.map +1 -0
  64. package/dist/common/boss_paywall_popup.d.ts +28 -0
  65. package/dist/common/boss_paywall_popup.d.ts.map +1 -0
  66. package/dist/common/boss_paywall_popup.js +158 -0
  67. package/dist/common/boss_paywall_popup.js.map +1 -0
  68. package/dist/common/boss_session_lock.d.ts +2 -0
  69. package/dist/common/boss_session_lock.d.ts.map +1 -0
  70. package/dist/common/boss_session_lock.js +127 -0
  71. package/dist/common/boss_session_lock.js.map +1 -0
  72. package/dist/common/boss_session_page.d.ts +27 -0
  73. package/dist/common/boss_session_page.d.ts.map +1 -0
  74. package/dist/common/boss_session_page.js +173 -0
  75. package/dist/common/boss_session_page.js.map +1 -0
  76. package/dist/common/boss_sidebar_nav.d.ts +7 -0
  77. package/dist/common/boss_sidebar_nav.d.ts.map +1 -0
  78. package/dist/common/boss_sidebar_nav.js +53 -0
  79. package/dist/common/boss_sidebar_nav.js.map +1 -0
  80. package/dist/common/c_resume_capture.d.ts +19 -0
  81. package/dist/common/c_resume_capture.d.ts.map +1 -0
  82. package/dist/common/c_resume_capture.js +185 -0
  83. package/dist/common/c_resume_capture.js.map +1 -0
  84. package/dist/common/ensure_page.d.ts +9 -0
  85. package/dist/common/ensure_page.d.ts.map +1 -0
  86. package/dist/common/ensure_page.js +14 -0
  87. package/dist/common/ensure_page.js.map +1 -0
  88. package/dist/config.d.ts +22 -0
  89. package/dist/config.d.ts.map +1 -0
  90. package/dist/config.js +57 -0
  91. package/dist/config.js.map +1 -0
  92. package/dist/ocr/baidu_ocr.d.ts +13 -0
  93. package/dist/ocr/baidu_ocr.d.ts.map +1 -0
  94. package/dist/ocr/baidu_ocr.js +80 -0
  95. package/dist/ocr/baidu_ocr.js.map +1 -0
  96. package/dist/ocr/index.d.ts +3 -0
  97. package/dist/ocr/index.d.ts.map +1 -0
  98. package/dist/ocr/index.js +3 -0
  99. package/dist/ocr/index.js.map +1 -0
  100. package/dist/ocr/resume_ocr.d.ts +13 -0
  101. package/dist/ocr/resume_ocr.d.ts.map +1 -0
  102. package/dist/ocr/resume_ocr.js +38 -0
  103. package/dist/ocr/resume_ocr.js.map +1 -0
  104. package/dist/toolset/action.d.ts +12 -0
  105. package/dist/toolset/action.d.ts.map +1 -0
  106. package/dist/toolset/action.js +521 -0
  107. package/dist/toolset/action.js.map +1 -0
  108. package/dist/toolset/baidu_credentials.d.ts +5 -0
  109. package/dist/toolset/baidu_credentials.d.ts.map +1 -0
  110. package/dist/toolset/baidu_credentials.js +24 -0
  111. package/dist/toolset/baidu_credentials.js.map +1 -0
  112. package/dist/toolset/chat.d.ts +10 -0
  113. package/dist/toolset/chat.d.ts.map +1 -0
  114. package/dist/toolset/chat.js +763 -0
  115. package/dist/toolset/chat.js.map +1 -0
  116. package/dist/toolset/deep-search.d.ts +35 -0
  117. package/dist/toolset/deep-search.d.ts.map +1 -0
  118. package/dist/toolset/deep-search.js +1505 -0
  119. package/dist/toolset/deep-search.js.map +1 -0
  120. package/dist/toolset/greet.d.ts +6 -0
  121. package/dist/toolset/greet.d.ts.map +1 -0
  122. package/dist/toolset/greet.js +81 -0
  123. package/dist/toolset/greet.js.map +1 -0
  124. package/dist/toolset/index.d.ts +48 -0
  125. package/dist/toolset/index.d.ts.map +1 -0
  126. package/dist/toolset/index.js +71 -0
  127. package/dist/toolset/index.js.map +1 -0
  128. package/dist/toolset/jd.d.ts +10 -0
  129. package/dist/toolset/jd.d.ts.map +1 -0
  130. package/dist/toolset/jd.js +406 -0
  131. package/dist/toolset/jd.js.map +1 -0
  132. package/dist/toolset/list.d.ts +6 -0
  133. package/dist/toolset/list.d.ts.map +1 -0
  134. package/dist/toolset/list.js +146 -0
  135. package/dist/toolset/list.js.map +1 -0
  136. package/dist/toolset/login.d.ts +6 -0
  137. package/dist/toolset/login.d.ts.map +1 -0
  138. package/dist/toolset/login.js +76 -0
  139. package/dist/toolset/login.js.map +1 -0
  140. package/dist/toolset/normal-search.d.ts +12 -0
  141. package/dist/toolset/normal-search.d.ts.map +1 -0
  142. package/dist/toolset/normal-search.js +301 -0
  143. package/dist/toolset/normal-search.js.map +1 -0
  144. package/dist/toolset/preview.d.ts +5 -0
  145. package/dist/toolset/preview.d.ts.map +1 -0
  146. package/dist/toolset/preview.js +100 -0
  147. package/dist/toolset/preview.js.map +1 -0
  148. package/dist/toolset/recommend.d.ts +36 -0
  149. package/dist/toolset/recommend.d.ts.map +1 -0
  150. package/dist/toolset/recommend.js +428 -0
  151. package/dist/toolset/recommend.js.map +1 -0
  152. package/dist/toolset/send.d.ts +7 -0
  153. package/dist/toolset/send.d.ts.map +1 -0
  154. package/dist/toolset/send.js +52 -0
  155. package/dist/toolset/send.js.map +1 -0
  156. package/package.json +69 -0
  157. package/skills/boss-frontend-analysis/SKILL.md +58 -0
  158. package/skills/boss-frontend-analysis/agents/openai.yaml +4 -0
  159. package/skills/boss-frontend-analysis/scripts/capture_boss_frontend.mjs +604 -0
@@ -0,0 +1,763 @@
1
+ import { CHAT_HISTORY_DIALOG_WAIT_MS, CHAT_HISTORY_TAB_SWITCH_MS, OPEN_CHAT_AFTER_ROW_CLICK_MS, OPEN_CHAT_SCROLL_GAP_MS, sleepRandom, } from '../browser/index.js';
2
+ import { isBossChatIndexUrl } from '../common/auth.js';
3
+ import { ensureChatListReady } from './list.js';
4
+ function chatRoleTag(from) {
5
+ switch (from) {
6
+ case 'friend':
7
+ return '[candidate]';
8
+ case 'myself':
9
+ return '[you]';
10
+ case 'system':
11
+ return '[system]';
12
+ default:
13
+ return '[unknown]';
14
+ }
15
+ }
16
+ async function waitForChatHistoryPanelReady(page, selectedTab) {
17
+ const selectedTabLiteral = JSON.stringify(selectedTab ?? null);
18
+ await page.waitForFunction(`(() => {
19
+ const tabLabel = ${selectedTabLiteral};
20
+ function norm(v) {
21
+ return (v ?? "").replace(/\\s+/g, " ").trim();
22
+ }
23
+ function isVisible(el) {
24
+ if (!el) return false;
25
+ const rect = el.getBoundingClientRect();
26
+ const style = window.getComputedStyle(el);
27
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
28
+ }
29
+ const root = document.querySelector(".chat-history-process");
30
+ if (!isVisible(root)) return false;
31
+ if (tabLabel) {
32
+ const selected = root.querySelector(".tab-hd span.selected");
33
+ if (norm(selected?.textContent) !== tabLabel) return false;
34
+ }
35
+ return !!root.querySelector(".record");
36
+ })()`, { timeout: 10_000 });
37
+ }
38
+ /**
39
+ * 打开「沟通记录」弹窗,依次读取「同事沟通」「我的沟通」列表,关闭弹窗。
40
+ * 未找到入口时返回 null(视为暂无同事沟通记录,不输出该段)。
41
+ */
42
+ async function fetchColleagueChatHistorySection(page) {
43
+ const clicked = (await page.evaluate(`(() => {
44
+ function norm(v) {
45
+ return (v ?? "").replace(/\\s+/g, " ").trim();
46
+ }
47
+ const tooltips = Array.from(document.querySelectorAll(".chat-tooltip-custom"));
48
+ for (const el of tooltips) {
49
+ if (!norm(el.textContent).includes("沟通记录")) continue;
50
+ const host = el.closest("span.icon") ?? el.closest("span") ?? el.parentElement;
51
+ if (host) {
52
+ host.click();
53
+ return true;
54
+ }
55
+ el.click();
56
+ return true;
57
+ }
58
+ const uses = Array.from(document.querySelectorAll("use"));
59
+ for (const u of uses) {
60
+ const h =
61
+ u.getAttribute("href") ||
62
+ u.getAttributeNS("http://www.w3.org/1999/xlink", "href") ||
63
+ "";
64
+ if (h.includes("icon-chat-history")) {
65
+ const p = u.closest("span.icon") ?? u.parentElement?.parentElement;
66
+ if (p) {
67
+ p.click();
68
+ return true;
69
+ }
70
+ }
71
+ }
72
+ return false;
73
+ })()`));
74
+ if (!clicked) {
75
+ return null;
76
+ }
77
+ await sleepRandom(CHAT_HISTORY_DIALOG_WAIT_MS.min, CHAT_HISTORY_DIALOG_WAIT_MS.max);
78
+ try {
79
+ await waitForChatHistoryPanelReady(page);
80
+ }
81
+ catch {
82
+ return '(已点击「沟通记录」,但弹窗未在预期时间内出现。)';
83
+ }
84
+ const scrapeRows = () => page.evaluate(`(() => {
85
+ function norm(v) {
86
+ return (v ?? "").replace(/\\s+/g, " ").trim();
87
+ }
88
+ const root = document.querySelector(".chat-history-process");
89
+ if (!root) return [];
90
+ return Array.from(root.querySelectorAll(".record li"))
91
+ .map((li) => ({
92
+ action: norm(li.querySelector(".action")?.textContent),
93
+ operat: norm(li.querySelector(".operat")?.textContent),
94
+ }))
95
+ .filter((x) => x.action || x.operat);
96
+ })()`);
97
+ const clickTab = (label) => {
98
+ const labelLiteral = JSON.stringify(label);
99
+ return page.evaluate(`(() => {
100
+ const lab = ${labelLiteral};
101
+ function norm(v) {
102
+ return (v ?? "").replace(/\\s+/g, " ").trim();
103
+ }
104
+ const root = document.querySelector(".chat-history-process");
105
+ if (!root) return;
106
+ const spans = Array.from(root.querySelectorAll(".tab-hd span"));
107
+ const sp = spans.find((s) => norm(s.textContent) === lab);
108
+ if (sp && !sp.classList.contains("selected")) {
109
+ sp.click();
110
+ }
111
+ })()`);
112
+ };
113
+ await clickTab('同事沟通');
114
+ await sleepRandom(CHAT_HISTORY_TAB_SWITCH_MS.min, CHAT_HISTORY_TAB_SWITCH_MS.max);
115
+ await waitForChatHistoryPanelReady(page, '同事沟通');
116
+ const rowsColleague = await scrapeRows();
117
+ await clickTab('我的沟通');
118
+ await sleepRandom(CHAT_HISTORY_TAB_SWITCH_MS.min, CHAT_HISTORY_TAB_SWITCH_MS.max);
119
+ await waitForChatHistoryPanelReady(page, '我的沟通');
120
+ const rowsMine = await scrapeRows();
121
+ const fmt = (label, rows) => {
122
+ const lines = [`[${label}]`];
123
+ if (rows.length === 0) {
124
+ lines.push('(暂无)');
125
+ return lines;
126
+ }
127
+ rows.forEach((r, i) => {
128
+ const line = [r.action, r.operat].filter(Boolean).join(' | ');
129
+ lines.push(`${i + 1}. ${line}`);
130
+ });
131
+ return lines;
132
+ };
133
+ const parts = [];
134
+ parts.push(...fmt('同事沟通', rowsColleague));
135
+ parts.push('');
136
+ parts.push(...fmt('我的沟通', rowsMine));
137
+ await closeChatHistoryPopup(page);
138
+ return parts.join('\n');
139
+ }
140
+ export async function runGetCommunicationHistory(page) {
141
+ const currentUrl = page.url();
142
+ if (!isBossChatIndexUrl(currentUrl)) {
143
+ throw new Error('请先进入聊天列表页(/web/chat/index)并打开候选人聊天。');
144
+ }
145
+ const inCandidateChat = await page.$('.base-info-single-container');
146
+ if (!inCandidateChat) {
147
+ throw new Error('请先打开候选人聊天详情页,再执行“沟通记录”操作。');
148
+ }
149
+ let historyBlock = null;
150
+ historyBlock = await fetchColleagueChatHistorySection(page);
151
+ if (historyBlock === null) {
152
+ return '未找到「沟通记录」入口,或当前候选人暂无可读取记录。';
153
+ }
154
+ return ['同事/我的沟通记录:', '', historyBlock].join('\n');
155
+ }
156
+ /** 关闭「沟通记录」弹层(优先点 Boss 提供的 popup 关闭钮) */
157
+ async function closeChatHistoryPopup(page) {
158
+ try {
159
+ const selectors = [
160
+ '.boss-popup__wrapper.chat-history .boss-popup__close',
161
+ '.boss-dialog__wrapper.chat-history .boss-popup__close',
162
+ '.boss-popup__wrapper.boss-dialog.chat-history .boss-popup__close',
163
+ ];
164
+ let btn = null;
165
+ for (const sel of selectors) {
166
+ btn = await page.$(sel);
167
+ if (btn)
168
+ break;
169
+ }
170
+ if (btn) {
171
+ await btn.click();
172
+ }
173
+ else {
174
+ await page.evaluate(`(() => {
175
+ const root =
176
+ document.querySelector(".boss-popup__wrapper.chat-history") ||
177
+ document.querySelector(".boss-dialog__wrapper.chat-history");
178
+ const c = root?.querySelector(".boss-popup__close") ?? document.querySelector(".boss-popup__close");
179
+ if (c) {
180
+ c.click();
181
+ }
182
+ })()`);
183
+ }
184
+ await page.waitForFunction(`(() => {
185
+ const roots = Array.from(document.querySelectorAll(".boss-popup__wrapper.chat-history, .boss-dialog__wrapper.chat-history"));
186
+ return roots.every((el) => {
187
+ if (!(el instanceof HTMLElement)) return true;
188
+ const st = window.getComputedStyle(el);
189
+ const r = el.getBoundingClientRect();
190
+ return st.display === "none" || st.visibility === "hidden" || r.width <= 0 || r.height <= 0;
191
+ });
192
+ })()`, { timeout: 3_000 }).catch(() => { });
193
+ const popupWrap = await page.$('.boss-popup__wrapper.chat-history, .boss-dialog__wrapper.chat-history');
194
+ if (popupWrap) {
195
+ await page.evaluate(`(() => {
196
+ const root =
197
+ document.querySelector(".boss-popup__wrapper.chat-history") ||
198
+ document.querySelector(".boss-dialog__wrapper.chat-history");
199
+ const c = root?.querySelector(".boss-popup__close") ?? document.querySelector(".boss-popup__close");
200
+ c?.click();
201
+ })()`);
202
+ await page.waitForFunction(`(() => {
203
+ const roots = Array.from(document.querySelectorAll(".boss-popup__wrapper.chat-history, .boss-dialog__wrapper.chat-history"));
204
+ return roots.every((el) => {
205
+ if (!(el instanceof HTMLElement)) return true;
206
+ const st = window.getComputedStyle(el);
207
+ const r = el.getBoundingClientRect();
208
+ return st.display === "none" || st.visibility === "hidden" || r.width <= 0 || r.height <= 0;
209
+ });
210
+ })()`, { timeout: 2_500 }).catch(() => { });
211
+ }
212
+ }
213
+ catch (err) {
214
+ const msg = err instanceof Error ? err.message : String(err);
215
+ throw new Error(`关闭「沟通记录」弹层失败:${msg}`);
216
+ }
217
+ }
218
+ async function fetchCandidateSummary(page, expectedName, exactMatch) {
219
+ const targetNameLiteral = JSON.stringify(expectedName);
220
+ const exactMatchLiteral = JSON.stringify(exactMatch);
221
+ const scraped = (await page.evaluate(`(() => {
222
+ const targetName = ${targetNameLiteral};
223
+ const exact = ${exactMatchLiteral};
224
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
225
+ const matches = (value) => exact ? value === targetName : value.includes(targetName);
226
+ const isVisible = (el) => {
227
+ if (!el) return false;
228
+ const rect = el.getBoundingClientRect();
229
+ const style = window.getComputedStyle(el);
230
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
231
+ };
232
+ const roots = Array.from(document.querySelectorAll(".base-info-single-container")).filter(isVisible);
233
+ const root = roots.find((el) => matches(norm(el.querySelector(".name-box")?.textContent)));
234
+ if (!root) {
235
+ const visibleNames = roots.map((el) => norm(el.querySelector(".name-box")?.textContent)).filter(Boolean).join(", ");
236
+ throw new Error("未找到目标候选人详情容器:" + targetName + ";当前可见详情:" + (visibleNames || "空"));
237
+ }
238
+ const name = norm(root.querySelector(".name-box")?.textContent);
239
+ const active = norm(root.querySelector(".high-light-orange.active-time span")?.textContent);
240
+ const basicFacts = Array.from(root.querySelectorAll(".base-info-single-detial > div"))
241
+ .filter((el) => !el.classList.contains("name-contet") && !el.classList.contains("active-time"))
242
+ .map((el) => norm(el.textContent))
243
+ .filter((v) => v.length > 0);
244
+ const recentExperience = Array.from(
245
+ root.querySelectorAll(".experience-content.detail-list .work-content .value"),
246
+ )
247
+ .map((el) => norm(el.textContent))
248
+ .filter((v) => v.length > 0);
249
+ const communicationPosition = norm(
250
+ root.querySelector(".position-item .position-name")?.textContent,
251
+ );
252
+ const expectation = norm(root.querySelector(".position-item.expect .value.job")?.textContent);
253
+ const remark = norm(
254
+ root.querySelector(".label-remark-content .remark span:last-child")?.textContent,
255
+ );
256
+ return {
257
+ name,
258
+ active,
259
+ basicFacts,
260
+ recentExperience,
261
+ communicationPosition,
262
+ expectation,
263
+ remark,
264
+ };
265
+ })()`));
266
+ return scraped;
267
+ }
268
+ async function waitForOpenedCandidateChat(page, expectedName) {
269
+ const expectedNameLiteral = JSON.stringify(expectedName);
270
+ await page.waitForFunction(`(() => {
271
+ const name = ${expectedNameLiteral};
272
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
273
+ const isVisible = (el) => {
274
+ if (!el) return false;
275
+ const rect = el.getBoundingClientRect();
276
+ const style = window.getComputedStyle(el);
277
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
278
+ };
279
+ const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
280
+ const selectedName = norm(selected?.querySelector(".geek-name")?.textContent);
281
+ const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
282
+ const detailName = norm(root?.querySelector(".name-box")?.textContent);
283
+ return selectedName === name && detailName === name;
284
+ })()`, { timeout: 15_000 });
285
+ await page.waitForFunction(`(() => {
286
+ const isVisible = (el) => {
287
+ if (!el) return false;
288
+ const rect = el.getBoundingClientRect();
289
+ const style = window.getComputedStyle(el);
290
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
291
+ };
292
+ const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
293
+ const list = lists[lists.length - 1];
294
+ if (!list) return false;
295
+ const items = list.querySelectorAll(".message-item");
296
+ if (!items || items.length === 0) return false;
297
+ return Array.from(items).some((item) => {
298
+ const txt =
299
+ item.querySelector(".item-friend .text span")?.textContent ??
300
+ item.querySelector(".item-myself .text span")?.textContent ??
301
+ item.querySelector(".item-system .message-card-top-title")?.textContent ??
302
+ "";
303
+ return txt.replace(/\\s+/g, " ").trim().length > 0;
304
+ });
305
+ })()`, { timeout: 20_000 });
306
+ }
307
+ async function scrapeCurrentChatMessages(page) {
308
+ return (await page.evaluate(`(() => {
309
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
310
+ const isVisible = (el) => {
311
+ if (!el) return false;
312
+ const rect = el.getBoundingClientRect();
313
+ const style = window.getComputedStyle(el);
314
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
315
+ };
316
+ function isBossPriorityUpsellSystemText(text) {
317
+ return norm(text).indexOf("优先提醒") !== -1;
318
+ }
319
+ const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
320
+ const list = lists[lists.length - 1];
321
+ if (!list) {
322
+ throw new Error("未找到可见聊天消息列表(.chat-message-list)。");
323
+ }
324
+ const items = Array.from(list.querySelectorAll(".message-item"));
325
+ let currentTime = "";
326
+ const messages = [];
327
+ let hasFriendResumeAttachment = false;
328
+ for (const item of items) {
329
+ const timeNode = item.querySelector(".message-time .time");
330
+ if (timeNode) {
331
+ const t = norm(timeNode.textContent);
332
+ if (t) currentTime = t;
333
+ }
334
+ const friendRoot = item.querySelector(".item-friend");
335
+ let friendText = "";
336
+ if (friendRoot) {
337
+ friendText = norm(friendRoot.querySelector(".text > span")?.textContent);
338
+ if (!friendText) {
339
+ const resumeIcon = friendRoot.querySelector(".resume-icon");
340
+ const title = norm(friendRoot.querySelector(".message-card-top-title")?.textContent);
341
+ const cardBtn = norm(friendRoot.querySelector(".message-card-buttons .card-btn")?.textContent);
342
+ if (resumeIcon) hasFriendResumeAttachment = true;
343
+ if (title || cardBtn) {
344
+ const parts = [];
345
+ if (title) parts.push(title);
346
+ if (cardBtn) parts.push(cardBtn);
347
+ friendText = parts.length ? parts.join(" · ") : "";
348
+ }
349
+ if (!friendText) friendText = norm(friendRoot.querySelector(".text")?.textContent);
350
+ }
351
+ }
352
+ const myselfText = norm(item.querySelector(".item-myself .text span")?.textContent);
353
+ const systemText =
354
+ norm(item.querySelector(".item-system .message-card-top-title")?.textContent) ||
355
+ norm(item.querySelector(".item-system .text span")?.textContent);
356
+ if (friendText) {
357
+ messages.push({ text: friendText, time: currentTime, from: "friend" });
358
+ } else if (myselfText) {
359
+ messages.push({ text: myselfText, time: currentTime, from: "myself" });
360
+ } else if (systemText) {
361
+ if (!isBossPriorityUpsellSystemText(systemText)) {
362
+ messages.push({ text: systemText, time: currentTime, from: "system" });
363
+ }
364
+ }
365
+ }
366
+ return { messages, hasFriendResumeAttachment };
367
+ })()`));
368
+ }
369
+ async function renderOpenedCandidateChat(page, foundName) {
370
+ await waitForOpenedCandidateChat(page, foundName);
371
+ const scraped = await scrapeCurrentChatMessages(page);
372
+ const detailLines = scraped.messages.map((m) => {
373
+ const tag = chatRoleTag(m.from);
374
+ const timePart = m.time ? ` ${m.time}` : '';
375
+ return `${tag}${timePart} ${m.text}`.trimEnd();
376
+ });
377
+ const resumeStatus = scraped.hasFriendResumeAttachment ? '已获取' : '未获取';
378
+ const summary = await fetchCandidateSummary(page, foundName, true);
379
+ const out = [
380
+ `成功进入候选人聊天:${foundName}`,
381
+ `简历获取状态: ${resumeStatus}`,
382
+ ];
383
+ const summaryLines = [];
384
+ const summaryName = summary.name || foundName;
385
+ summaryLines.push(`姓名: ${summaryName}`);
386
+ if (summary.active) {
387
+ summaryLines.push(`活跃状态: ${summary.active}`);
388
+ }
389
+ if (summary.basicFacts.length > 0) {
390
+ summaryLines.push(`基本信息: ${summary.basicFacts.join(' / ')}`);
391
+ }
392
+ if (summary.communicationPosition) {
393
+ summaryLines.push(`沟通职位: ${summary.communicationPosition}`);
394
+ }
395
+ if (summary.expectation) {
396
+ summaryLines.push(`期望: ${summary.expectation}`);
397
+ }
398
+ if (summary.recentExperience.length > 0) {
399
+ summaryLines.push('近期经历:');
400
+ summary.recentExperience.forEach((it, idx) => {
401
+ summaryLines.push(`${idx + 1}. ${it}`);
402
+ });
403
+ }
404
+ if (summaryLines.length > 0) {
405
+ out.push('', '人才摘要:', '', ...summaryLines);
406
+ }
407
+ if (summary.remark) {
408
+ out.push('', `备注: ${summary.remark}`);
409
+ }
410
+ out.push('', '完整聊天消息:');
411
+ if (detailLines.length > 0) {
412
+ out.push('', ...detailLines);
413
+ }
414
+ else {
415
+ out.push('', '(暂无)');
416
+ }
417
+ return out.join('\n');
418
+ }
419
+ export async function runOpenCandidateChatByIndex(page, params) {
420
+ if (!Number.isInteger(params.index) || params.index < 1) {
421
+ throw new Error(`聊天列表序号必须是从 1 开始的整数,当前值:${params.index}`);
422
+ }
423
+ const filter = params.filter ?? 'all';
424
+ const expectedName = params.expectedName?.trim() ?? '';
425
+ const exact = params.exact === true;
426
+ await ensureChatListReady(page, filter);
427
+ if (!isBossChatIndexUrl(page.url())) {
428
+ throw new Error('当前不在沟通列表页(/web/chat/index),无法打开候选人聊天。');
429
+ }
430
+ const rowInfo = (await page.evaluate(`((rowIndex) => {
431
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
432
+ const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
433
+ const total = wraps.length;
434
+ const wrap = wraps[rowIndex - 1];
435
+ if (!wrap) return { total, name: "", job: "", message: "", time: "", x: 0, y: 0 };
436
+ const row = wrap.querySelector(".geek-item") || wrap;
437
+ row.scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
438
+ const rect = row.getBoundingClientRect();
439
+ return {
440
+ total,
441
+ name: norm(wrap.querySelector(".geek-name")?.textContent),
442
+ job: norm(wrap.querySelector(".source-job")?.textContent),
443
+ message: norm(wrap.querySelector(".push-text")?.textContent),
444
+ time: norm(wrap.querySelector(".time")?.textContent),
445
+ x: rect.left + rect.width / 2,
446
+ y: rect.top + rect.height / 2,
447
+ };
448
+ })(${JSON.stringify(params.index)})`));
449
+ if (!rowInfo.name) {
450
+ throw new Error(`聊天列表序号 ${params.index} 不存在;当前${filter === 'unread' ? '未读' : '全部'}列表共 ${rowInfo.total} 条。`);
451
+ }
452
+ if (expectedName) {
453
+ const matched = exact ? rowInfo.name === expectedName : rowInfo.name.includes(expectedName);
454
+ if (!matched) {
455
+ throw new Error(`聊天列表序号 ${params.index} 的候选人是「${rowInfo.name}」,与指定姓名「${expectedName}」不匹配。`);
456
+ }
457
+ }
458
+ await page.mouse.click(rowInfo.x, rowInfo.y, { delay: 40 });
459
+ await sleepRandom(OPEN_CHAT_AFTER_ROW_CLICK_MS.min, OPEN_CHAT_AFTER_ROW_CLICK_MS.max);
460
+ return renderOpenedCandidateChat(page, rowInfo.name);
461
+ }
462
+ export async function runOpenCandidateChat(page, candidateName, exact = true) {
463
+ const targetName = candidateName.trim();
464
+ try {
465
+ await ensureChatListReady(page);
466
+ if (!isBossChatIndexUrl(page.url())) {
467
+ throw new Error('当前不在沟通列表页(/web/chat/index),无法打开候选人聊天。');
468
+ }
469
+ const norm = (v) => (v ?? '').replace(/\s+/g, ' ').trim();
470
+ const matcher = (value) => exact ? value === targetName : value.includes(targetName);
471
+ let targetWrap = null;
472
+ let foundName = '';
473
+ const maxScrollRounds = 40;
474
+ for (let round = 0; round < maxScrollRounds && !targetWrap; round++) {
475
+ const wraps = await page.$$('.geek-item-wrap');
476
+ for (const wrap of wraps) {
477
+ const nameText = await wrap
478
+ .$eval('.geek-name', (el) => (el.textContent ?? '').trim())
479
+ .catch(() => '');
480
+ const candidate = norm(nameText);
481
+ if (!candidate)
482
+ continue;
483
+ if (matcher(candidate)) {
484
+ targetWrap = wrap;
485
+ foundName = candidate;
486
+ break;
487
+ }
488
+ }
489
+ if (targetWrap)
490
+ break;
491
+ const scrollState = (await page.evaluate(`(() => {
492
+ const first = document.querySelector(".geek-item-wrap");
493
+ if (!first) return { moved: false, atEnd: true };
494
+ let node = first.parentElement;
495
+ let scroller = null;
496
+ while (node) {
497
+ const style = window.getComputedStyle(node);
498
+ const overflowY = style.overflowY;
499
+ const canScroll =
500
+ (overflowY === "auto" || overflowY === "scroll") &&
501
+ node.scrollHeight > node.clientHeight;
502
+ if (canScroll) {
503
+ scroller = node;
504
+ break;
505
+ }
506
+ node = node.parentElement;
507
+ }
508
+ if (!scroller) return { moved: false, atEnd: true };
509
+ const prev = scroller.scrollTop;
510
+ const step = Math.max(160, Math.floor(scroller.clientHeight * 0.8));
511
+ scroller.scrollTop = Math.min(scroller.scrollTop + step, scroller.scrollHeight);
512
+ const moved = scroller.scrollTop !== prev;
513
+ const atEnd = scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2;
514
+ return { moved, atEnd };
515
+ })()`));
516
+ if (!scrollState.moved || scrollState.atEnd) {
517
+ break;
518
+ }
519
+ await sleepRandom(OPEN_CHAT_SCROLL_GAP_MS.min, OPEN_CHAT_SCROLL_GAP_MS.max);
520
+ }
521
+ if (!targetWrap) {
522
+ throw new Error(`未在聊天列表中找到候选人:${targetName}`);
523
+ }
524
+ const clickNameLiteral = JSON.stringify(foundName || targetName);
525
+ const clickExactLiteral = JSON.stringify(exact);
526
+ const scrolledToTarget = (await page.evaluate(`(() => {
527
+ const targetName = ${clickNameLiteral};
528
+ const exactMatch = ${clickExactLiteral};
529
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
530
+ const matches = (value) => exactMatch ? value === targetName : value.includes(targetName);
531
+ const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
532
+ const wrap = wraps.find((el) => matches(norm(el.querySelector(".geek-name")?.textContent)));
533
+ if (!wrap) return false;
534
+ const row = wrap.querySelector(".geek-item") || wrap;
535
+ let node = row.parentElement;
536
+ let scroller = null;
537
+ while (node) {
538
+ const style = window.getComputedStyle(node);
539
+ const overflowY = style.overflowY;
540
+ const canScroll =
541
+ (overflowY === "auto" || overflowY === "scroll") &&
542
+ node.scrollHeight > node.clientHeight;
543
+ if (canScroll) {
544
+ scroller = node;
545
+ break;
546
+ }
547
+ node = node.parentElement;
548
+ }
549
+ if (scroller) {
550
+ const rowRect = row.getBoundingClientRect();
551
+ const scrollerRect = scroller.getBoundingClientRect();
552
+ scroller.scrollTop += rowRect.top - scrollerRect.top - (scroller.clientHeight - rowRect.height) / 2;
553
+ } else {
554
+ row.scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
555
+ }
556
+ return true;
557
+ })()`));
558
+ if (!scrolledToTarget) {
559
+ throw new Error(`未能重新定位候选人行:${foundName || targetName}`);
560
+ }
561
+ await sleepRandom(120, 220);
562
+ const clickPoint = (await page.evaluate(`(() => {
563
+ const targetName = ${clickNameLiteral};
564
+ const exactMatch = ${clickExactLiteral};
565
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
566
+ const matches = (value) => exactMatch ? value === targetName : value.includes(targetName);
567
+ const wraps = Array.from(document.querySelectorAll(".geek-item-wrap"));
568
+ const wrap = wraps.find((el) => matches(norm(el.querySelector(".geek-name")?.textContent)));
569
+ if (!wrap) return null;
570
+ const row = wrap.querySelector(".geek-item") || wrap;
571
+ const rect = row.getBoundingClientRect();
572
+ return {
573
+ x: rect.left + rect.width / 2,
574
+ y: rect.top + rect.height / 2,
575
+ };
576
+ })()`));
577
+ if (!clickPoint) {
578
+ throw new Error(`未能重新定位候选人行:${foundName || targetName}`);
579
+ }
580
+ await page.mouse.click(clickPoint.x, clickPoint.y, { delay: 40 });
581
+ await sleepRandom(OPEN_CHAT_AFTER_ROW_CLICK_MS.min, OPEN_CHAT_AFTER_ROW_CLICK_MS.max);
582
+ try {
583
+ const expectedNameLiteral = JSON.stringify(foundName || targetName);
584
+ const exactLiteral = JSON.stringify(exact);
585
+ await page.waitForFunction(`(() => {
586
+ const name = ${expectedNameLiteral};
587
+ const exactMatch = ${exactLiteral};
588
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
589
+ const matches = (value) => exactMatch ? value === name : value.includes(name);
590
+ const isVisible = (el) => {
591
+ if (!el) return false;
592
+ const rect = el.getBoundingClientRect();
593
+ const style = window.getComputedStyle(el);
594
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
595
+ };
596
+ const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
597
+ const selectedName = norm(selected?.querySelector(".geek-name")?.textContent);
598
+ const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
599
+ const detailName = norm(root?.querySelector(".name-box")?.textContent);
600
+ return matches(selectedName) && matches(detailName);
601
+ })()`, { timeout: 15_000 });
602
+ await page.waitForFunction(`(() => {
603
+ const isVisible = (el) => {
604
+ if (!el) return false;
605
+ const rect = el.getBoundingClientRect();
606
+ const style = window.getComputedStyle(el);
607
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
608
+ };
609
+ const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
610
+ const list = lists[lists.length - 1];
611
+ if (!list) return false;
612
+ const items = list.querySelectorAll(".message-item");
613
+ if (!items || items.length === 0) return false;
614
+ const hasText = Array.from(items).some((item) => {
615
+ const txt =
616
+ item.querySelector(".item-friend .text span")?.textContent ??
617
+ item.querySelector(".item-myself .text span")?.textContent ??
618
+ item.querySelector(".item-system .message-card-top-title")?.textContent ??
619
+ "";
620
+ return txt.replace(/\\s+/g, " ").trim().length > 0;
621
+ });
622
+ return hasText;
623
+ })()`, { timeout: 20_000 });
624
+ }
625
+ catch {
626
+ const state = (await page.evaluate(`(() => {
627
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
628
+ const isVisible = (el) => {
629
+ if (!el) return false;
630
+ const rect = el.getBoundingClientRect();
631
+ const style = window.getComputedStyle(el);
632
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
633
+ };
634
+ const selected = Array.from(document.querySelectorAll(".geek-item.selected")).find(isVisible);
635
+ const root = Array.from(document.querySelectorAll(".base-info-single-container")).find(isVisible);
636
+ return {
637
+ selectedName: norm(selected?.querySelector(".geek-name")?.textContent),
638
+ detailName: norm(root?.querySelector(".name-box")?.textContent),
639
+ };
640
+ })()`));
641
+ throw new Error(`已尝试点击 ${foundName},但未检测到对应聊天详情面板(selected=${state.selectedName || '空'},detail=${state.detailName || '空'})。`);
642
+ }
643
+ let fullMessages = [];
644
+ let hasFriendResumeAttachment = false;
645
+ const scraped = (await page.evaluate(`(() => {
646
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
647
+ const isVisible = (el) => {
648
+ if (!el) return false;
649
+ const rect = el.getBoundingClientRect();
650
+ const style = window.getComputedStyle(el);
651
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
652
+ };
653
+ /** Boss 系统里的「消息优先提醒」增值服务条,对业务无意义,过滤掉 */
654
+ function isBossPriorityUpsellSystemText(text) {
655
+ return norm(text).indexOf("优先提醒") !== -1;
656
+ }
657
+ const lists = Array.from(document.querySelectorAll(".chat-message-list")).filter(isVisible);
658
+ const list = lists[lists.length - 1];
659
+ if (!list) {
660
+ throw new Error("未找到可见聊天消息列表(.chat-message-list)。");
661
+ }
662
+ const items = Array.from(list.querySelectorAll(".message-item"));
663
+ let currentTime = "";
664
+ const messages = [];
665
+ let hasFriendResumeAttachment = false;
666
+ for (const item of items) {
667
+ const timeNode = item.querySelector(".message-time .time");
668
+ if (timeNode) {
669
+ const t = norm(timeNode.textContent);
670
+ if (t) currentTime = t;
671
+ }
672
+ const friendRoot = item.querySelector(".item-friend");
673
+ let friendText = "";
674
+ if (friendRoot) {
675
+ friendText = norm(friendRoot.querySelector(".text > span")?.textContent);
676
+ if (!friendText) {
677
+ const resumeIcon = friendRoot.querySelector(".resume-icon");
678
+ const title = norm(friendRoot.querySelector(".message-card-top-title")?.textContent);
679
+ const cardBtn = norm(friendRoot.querySelector(".message-card-buttons .card-btn")?.textContent);
680
+ if (resumeIcon) hasFriendResumeAttachment = true;
681
+ if (title || cardBtn) {
682
+ const parts = [];
683
+ if (title) parts.push(title);
684
+ if (cardBtn) parts.push(cardBtn);
685
+ friendText = parts.length ? parts.join(" · ") : "";
686
+ }
687
+ if (!friendText) friendText = norm(friendRoot.querySelector(".text")?.textContent);
688
+ }
689
+ }
690
+ const myselfText = norm(item.querySelector(".item-myself .text span")?.textContent);
691
+ const systemText =
692
+ norm(item.querySelector(".item-system .message-card-top-title")?.textContent) ||
693
+ norm(item.querySelector(".item-system .text span")?.textContent);
694
+ if (friendText) {
695
+ messages.push({ text: friendText, time: currentTime, from: "friend" });
696
+ } else if (myselfText) {
697
+ messages.push({ text: myselfText, time: currentTime, from: "myself" });
698
+ } else if (systemText) {
699
+ if (!isBossPriorityUpsellSystemText(systemText)) {
700
+ messages.push({ text: systemText, time: currentTime, from: "system" });
701
+ }
702
+ }
703
+ }
704
+ return { messages, hasFriendResumeAttachment };
705
+ })()`));
706
+ fullMessages = scraped.messages;
707
+ hasFriendResumeAttachment = scraped.hasFriendResumeAttachment;
708
+ const detailLines = fullMessages.map((m) => {
709
+ const tag = chatRoleTag(m.from);
710
+ const timePart = m.time ? ` ${m.time}` : '';
711
+ return `${tag}${timePart} ${m.text}`.trimEnd();
712
+ });
713
+ const resumeStatus = hasFriendResumeAttachment ? '已获取' : '未获取';
714
+ const summary = await fetchCandidateSummary(page, foundName || targetName, exact);
715
+ const out = [
716
+ `成功进入候选人聊天:${foundName}`,
717
+ `简历获取状态: ${resumeStatus}`,
718
+ ];
719
+ const summaryLines = [];
720
+ const summaryName = summary.name || foundName || targetName;
721
+ summaryLines.push(`姓名: ${summaryName}`);
722
+ if (summary.active) {
723
+ summaryLines.push(`活跃状态: ${summary.active}`);
724
+ }
725
+ if (summary.basicFacts.length > 0) {
726
+ summaryLines.push(`基本信息: ${summary.basicFacts.join(' / ')}`);
727
+ }
728
+ if (summary.communicationPosition) {
729
+ summaryLines.push(`沟通职位: ${summary.communicationPosition}`);
730
+ }
731
+ if (summary.expectation) {
732
+ summaryLines.push(`期望: ${summary.expectation}`);
733
+ }
734
+ if (summary.recentExperience.length > 0) {
735
+ summaryLines.push('近期经历:');
736
+ summary.recentExperience.forEach((it, idx) => {
737
+ summaryLines.push(`${idx + 1}. ${it}`);
738
+ });
739
+ }
740
+ if (summaryLines.length > 0) {
741
+ out.push('', '人才摘要:', '', ...summaryLines);
742
+ }
743
+ if (summary.remark) {
744
+ out.push('', `备注: ${summary.remark}`);
745
+ }
746
+ out.push('', '完整聊天消息:');
747
+ if (detailLines.length > 0) {
748
+ out.push('', ...detailLines);
749
+ }
750
+ else {
751
+ out.push('', '(暂无)');
752
+ }
753
+ return out.join('\n');
754
+ }
755
+ catch (e) {
756
+ const message = e instanceof Error ? e.message : String(e);
757
+ if (e instanceof Error) {
758
+ throw e;
759
+ }
760
+ throw new Error(`打开候选人聊天失败:${message}`);
761
+ }
762
+ }
763
+ //# sourceMappingURL=chat.js.map