@joohw/boss-cli 0.3.5 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -14
- package/dist/browser/agent_operating_indicator.js +51 -51
- package/dist/browser/human_delay.d.ts +2 -0
- package/dist/browser/human_delay.d.ts.map +1 -1
- package/dist/browser/human_delay.js +4 -0
- package/dist/browser/human_delay.js.map +1 -1
- package/dist/cli/cliRouter.js +10 -10
- package/dist/cli/cliRouter.js.map +1 -1
- package/dist/common/auth.js +49 -49
- package/dist/common/boss_paywall_popup.js +87 -87
- package/dist/common/boss_session_lock.d.ts +2 -0
- package/dist/common/boss_session_lock.d.ts.map +1 -0
- package/dist/common/boss_session_lock.js +127 -0
- package/dist/common/boss_session_lock.js.map +1 -0
- package/dist/common/boss_session_page.d.ts +2 -2
- package/dist/common/boss_session_page.d.ts.map +1 -1
- package/dist/common/boss_session_page.js +66 -74
- package/dist/common/boss_session_page.js.map +1 -1
- package/dist/common/boss_sidebar_nav.js +25 -26
- package/dist/common/boss_sidebar_nav.js.map +1 -1
- package/dist/common/c_resume_capture.d.ts +1 -0
- package/dist/common/c_resume_capture.d.ts.map +1 -1
- package/dist/common/c_resume_capture.js +69 -32
- package/dist/common/c_resume_capture.js.map +1 -1
- package/dist/ocr/resume_ocr.d.ts.map +1 -1
- package/dist/ocr/resume_ocr.js +4 -1
- package/dist/ocr/resume_ocr.js.map +1 -1
- package/dist/toolset/action.d.ts.map +1 -1
- package/dist/toolset/action.js +11 -7
- package/dist/toolset/action.js.map +1 -1
- package/dist/toolset/chat.d.ts.map +1 -1
- package/dist/toolset/chat.js +246 -230
- package/dist/toolset/chat.js.map +1 -1
- package/dist/toolset/deep-search.d.ts +1 -1
- package/dist/toolset/deep-search.d.ts.map +1 -1
- package/dist/toolset/deep-search.js +291 -281
- package/dist/toolset/deep-search.js.map +1 -1
- package/dist/toolset/greet.d.ts.map +1 -1
- package/dist/toolset/greet.js +2 -7
- package/dist/toolset/greet.js.map +1 -1
- package/dist/toolset/jd.d.ts.map +1 -1
- package/dist/toolset/jd.js +134 -142
- package/dist/toolset/jd.js.map +1 -1
- package/dist/toolset/list.d.ts.map +1 -1
- package/dist/toolset/list.js +52 -45
- package/dist/toolset/list.js.map +1 -1
- package/dist/toolset/preview.d.ts.map +1 -1
- package/dist/toolset/preview.js +7 -9
- package/dist/toolset/preview.js.map +1 -1
- package/dist/toolset/recommend.d.ts +1 -1
- package/dist/toolset/recommend.d.ts.map +1 -1
- package/dist/toolset/recommend.js +223 -210
- package/dist/toolset/recommend.js.map +1 -1
- package/dist/toolset/send.d.ts.map +1 -1
- package/dist/toolset/send.js +6 -9
- package/dist/toolset/send.js.map +1 -1
- package/package.json +65 -63
- package/dist/browser/auth.d.ts +0 -33
- package/dist/browser/auth.d.ts.map +0 -1
- package/dist/browser/auth.js +0 -137
- package/dist/browser/auth.js.map +0 -1
- package/dist/browser/c_resume_capture.d.ts +0 -11
- package/dist/browser/c_resume_capture.d.ts.map +0 -1
- package/dist/browser/c_resume_capture.js +0 -76
- package/dist/browser/c_resume_capture.js.map +0 -1
- package/dist/browser/chat.d.ts +0 -7
- package/dist/browser/chat.d.ts.map +0 -1
- package/dist/browser/chat.js +0 -155
- package/dist/browser/chat.js.map +0 -1
- package/dist/browser/withLoggedInPage.d.ts +0 -7
- package/dist/browser/withLoggedInPage.d.ts.map +0 -1
- package/dist/browser/withLoggedInPage.js +0 -19
- package/dist/browser/withLoggedInPage.js.map +0 -1
- package/dist/facebook-cli/index.d.ts +0 -2
- package/dist/facebook-cli/index.d.ts.map +0 -1
- package/dist/facebook-cli/index.js +0 -2
- package/dist/facebook-cli/index.js.map +0 -1
- package/dist/toolset/c_resume_capture.d.ts +0 -11
- package/dist/toolset/c_resume_capture.d.ts.map +0 -1
- package/dist/toolset/c_resume_capture.js +0 -76
- package/dist/toolset/c_resume_capture.js.map +0 -1
- package/dist/toolset/chat_action.d.ts +0 -7
- package/dist/toolset/chat_action.d.ts.map +0 -1
- package/dist/toolset/chat_action.js +0 -355
- package/dist/toolset/chat_action.js.map +0 -1
- package/dist/toolset/list_candidates.d.ts +0 -4
- package/dist/toolset/list_candidates.d.ts.map +0 -1
- package/dist/toolset/list_candidates.js +0 -120
- package/dist/toolset/list_candidates.js.map +0 -1
- package/dist/toolset/list_positions.d.ts +0 -9
- package/dist/toolset/list_positions.d.ts.map +0 -1
- package/dist/toolset/list_positions.js +0 -41
- package/dist/toolset/list_positions.js.map +0 -1
- package/dist/toolset/open_chat.d.ts +0 -3
- package/dist/toolset/open_chat.d.ts.map +0 -1
- package/dist/toolset/open_chat.js +0 -423
- package/dist/toolset/open_chat.js.map +0 -1
- package/dist/toolset/recommend_greet.d.ts +0 -2
- package/dist/toolset/recommend_greet.d.ts.map +0 -1
- package/dist/toolset/recommend_greet.js +0 -27
- package/dist/toolset/recommend_greet.js.map +0 -1
- package/dist/toolset/search.d.ts +0 -24
- package/dist/toolset/search.d.ts.map +0 -1
- package/dist/toolset/search.js +0 -682
- package/dist/toolset/search.js.map +0 -1
- package/dist/toolset/send_message.d.ts +0 -12
- package/dist/toolset/send_message.d.ts.map +0 -1
- package/dist/toolset/send_message.js +0 -214
- package/dist/toolset/send_message.js.map +0 -1
- package/dist/toolset/skill.d.ts +0 -13
- package/dist/toolset/skill.d.ts.map +0 -1
- package/dist/toolset/skill.js +0 -85
- package/dist/toolset/skill.js.map +0 -1
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { sleepRandom } from '../browser/index.js';
|
|
3
|
-
import { createWaitManualLoginRequiredText } from '../common/auth.js';
|
|
1
|
+
import { selectAllModifierKey, sleepRandom } from '../browser/index.js';
|
|
4
2
|
import { withBossSessionPage } from '../common/boss_session_page.js';
|
|
5
3
|
import { clickBossSidebarMenuToPath } from '../common/boss_sidebar_nav.js';
|
|
6
4
|
const BOSS_CHAT_AI_FORM_URL = 'https://www.zhipin.com/web/chat/aiform';
|
|
7
|
-
const AI_FORM_SETTLE_MS = { min: 1600, max: 2600 };
|
|
8
5
|
export function isBossChatAiFormUrl(url) {
|
|
9
6
|
try {
|
|
10
7
|
const u = new URL(url);
|
|
@@ -19,15 +16,15 @@ export function isBossChatAiFormUrl(url) {
|
|
|
19
16
|
}
|
|
20
17
|
}
|
|
21
18
|
async function waitForAiFormReady(page) {
|
|
22
|
-
await page.waitForFunction(`(() => {
|
|
23
|
-
const root = document.querySelector(".ai-form-left");
|
|
24
|
-
const submit = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
25
|
-
const selected = document.querySelector(".job-dropmenu-select .job-main-text");
|
|
26
|
-
if (!root || !submit || !selected) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
const text = (selected.textContent ?? "").replace(/\\s+/g, " ").trim();
|
|
30
|
-
return text.length > 0;
|
|
19
|
+
await page.waitForFunction(`(() => {
|
|
20
|
+
const root = document.querySelector(".ai-form-left");
|
|
21
|
+
const submit = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
22
|
+
const selected = document.querySelector(".job-dropmenu-select .job-main-text");
|
|
23
|
+
if (!root || !submit || !selected) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const text = (selected.textContent ?? "").replace(/\\s+/g, " ").trim();
|
|
27
|
+
return text.length > 0;
|
|
31
28
|
})()`, { timeout: 15_000 });
|
|
32
29
|
}
|
|
33
30
|
export async function ensureInDeepSearchPage(page) {
|
|
@@ -38,22 +35,22 @@ export async function ensureInDeepSearchPage(page) {
|
|
|
38
35
|
}
|
|
39
36
|
async function clickAddConditionInSection(page, titleKeyword) {
|
|
40
37
|
const titleLiteral = JSON.stringify(titleKeyword);
|
|
41
|
-
const clicked = (await page.evaluate(`((titleKeyword) => {
|
|
42
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
43
|
-
function findFormSectionByTitle(kw) {
|
|
44
|
-
const h3s = Array.from(document.querySelectorAll(".form-content .form-content-title-h3"));
|
|
45
|
-
const h3 = h3s.find((el) => norm(el.textContent).includes(kw));
|
|
46
|
-
return h3 ? h3.closest(".form-content") : null;
|
|
47
|
-
}
|
|
48
|
-
const section = findFormSectionByTitle(titleKeyword);
|
|
49
|
-
if (!section) return false;
|
|
50
|
-
const header = section.querySelector(".form-content-header");
|
|
51
|
-
const titleBtn = header?.querySelector(".form-content-title-btn");
|
|
52
|
-
if (!(titleBtn instanceof HTMLElement)) return false;
|
|
53
|
-
if (!norm(titleBtn.textContent).includes("添加条件")) return false;
|
|
54
|
-
titleBtn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
55
|
-
titleBtn.click();
|
|
56
|
-
return true;
|
|
38
|
+
const clicked = (await page.evaluate(`((titleKeyword) => {
|
|
39
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
40
|
+
function findFormSectionByTitle(kw) {
|
|
41
|
+
const h3s = Array.from(document.querySelectorAll(".form-content .form-content-title-h3"));
|
|
42
|
+
const h3 = h3s.find((el) => norm(el.textContent).includes(kw));
|
|
43
|
+
return h3 ? h3.closest(".form-content") : null;
|
|
44
|
+
}
|
|
45
|
+
const section = findFormSectionByTitle(titleKeyword);
|
|
46
|
+
if (!section) return false;
|
|
47
|
+
const header = section.querySelector(".form-content-header");
|
|
48
|
+
const titleBtn = header?.querySelector(".form-content-title-btn");
|
|
49
|
+
if (!(titleBtn instanceof HTMLElement)) return false;
|
|
50
|
+
if (!norm(titleBtn.textContent).includes("添加条件")) return false;
|
|
51
|
+
titleBtn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
52
|
+
titleBtn.click();
|
|
53
|
+
return true;
|
|
57
54
|
})(${titleLiteral})`));
|
|
58
55
|
if (!clicked) {
|
|
59
56
|
throw new Error(`未找到「${titleKeyword}」区域的「添加条件」。`);
|
|
@@ -598,7 +595,7 @@ async function fillRowAtIndexInSection(page, titleKeyword, rowIndex, text) {
|
|
|
598
595
|
}, titleKeyword, clickedIndex);
|
|
599
596
|
await sleepRandom(60, 100);
|
|
600
597
|
}
|
|
601
|
-
const selectAllMod =
|
|
598
|
+
const selectAllMod = selectAllModifierKey();
|
|
602
599
|
await page.keyboard.down(selectAllMod);
|
|
603
600
|
await page.keyboard.press('KeyA');
|
|
604
601
|
await page.keyboard.up(selectAllMod);
|
|
@@ -643,226 +640,255 @@ async function applyAiFormRequirementLists(page, opts) {
|
|
|
643
640
|
}
|
|
644
641
|
}
|
|
645
642
|
async function readSearchFormSnapshot(page) {
|
|
646
|
-
return (await page.evaluate(`(() => {
|
|
647
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
648
|
-
function itemLineText(item) {
|
|
649
|
-
const word = item.querySelector(".form-content-word");
|
|
650
|
-
const w = word ? norm(word.textContent) : "";
|
|
651
|
-
if (w) return w;
|
|
652
|
-
const inp = item.querySelector("input, textarea");
|
|
653
|
-
if (inp && norm(inp.value)) return norm(inp.value);
|
|
654
|
-
const ce = item.querySelector("[contenteditable='true']");
|
|
655
|
-
if (ce) return norm(ce.textContent);
|
|
656
|
-
const titleEl = item.querySelector(".form-content-list-item-title");
|
|
657
|
-
if (titleEl) return norm(titleEl.textContent);
|
|
658
|
-
return "";
|
|
659
|
-
}
|
|
660
|
-
const selectedJob = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
661
|
-
const sections = Array.from(document.querySelectorAll(".form-content"));
|
|
662
|
-
const coreRequirements = [];
|
|
663
|
-
const bonusRequirements = [];
|
|
664
|
-
for (const section of sections) {
|
|
665
|
-
const title = norm(section.querySelector(".form-content-header .form-content-title-h3")?.textContent);
|
|
666
|
-
const items = section.querySelectorAll(".form-content-list-item");
|
|
667
|
-
const words = Array.from(items)
|
|
668
|
-
.map((item) => itemLineText(item))
|
|
669
|
-
.filter(Boolean);
|
|
670
|
-
if (title.includes("核心要求")) {
|
|
671
|
-
coreRequirements.push(...words);
|
|
672
|
-
continue;
|
|
673
|
-
}
|
|
674
|
-
if (title.includes("加分项")) {
|
|
675
|
-
bonusRequirements.push(...words);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
const remainingCountText = norm(document.querySelector(".ai-form-match-footer-text-count")?.textContent);
|
|
679
|
-
return {
|
|
680
|
-
selectedJob,
|
|
681
|
-
coreRequirements,
|
|
682
|
-
bonusRequirements,
|
|
683
|
-
remainingCountText,
|
|
684
|
-
};
|
|
643
|
+
return (await page.evaluate(`(() => {
|
|
644
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
645
|
+
function itemLineText(item) {
|
|
646
|
+
const word = item.querySelector(".form-content-word");
|
|
647
|
+
const w = word ? norm(word.textContent) : "";
|
|
648
|
+
if (w) return w;
|
|
649
|
+
const inp = item.querySelector("input, textarea");
|
|
650
|
+
if (inp && norm(inp.value)) return norm(inp.value);
|
|
651
|
+
const ce = item.querySelector("[contenteditable='true']");
|
|
652
|
+
if (ce) return norm(ce.textContent);
|
|
653
|
+
const titleEl = item.querySelector(".form-content-list-item-title");
|
|
654
|
+
if (titleEl) return norm(titleEl.textContent);
|
|
655
|
+
return "";
|
|
656
|
+
}
|
|
657
|
+
const selectedJob = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
658
|
+
const sections = Array.from(document.querySelectorAll(".form-content"));
|
|
659
|
+
const coreRequirements = [];
|
|
660
|
+
const bonusRequirements = [];
|
|
661
|
+
for (const section of sections) {
|
|
662
|
+
const title = norm(section.querySelector(".form-content-header .form-content-title-h3")?.textContent);
|
|
663
|
+
const items = section.querySelectorAll(".form-content-list-item");
|
|
664
|
+
const words = Array.from(items)
|
|
665
|
+
.map((item) => itemLineText(item))
|
|
666
|
+
.filter(Boolean);
|
|
667
|
+
if (title.includes("核心要求")) {
|
|
668
|
+
coreRequirements.push(...words);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (title.includes("加分项")) {
|
|
672
|
+
bonusRequirements.push(...words);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const remainingCountText = norm(document.querySelector(".ai-form-match-footer-text-count")?.textContent);
|
|
676
|
+
return {
|
|
677
|
+
selectedJob,
|
|
678
|
+
coreRequirements,
|
|
679
|
+
bonusRequirements,
|
|
680
|
+
remainingCountText,
|
|
681
|
+
};
|
|
685
682
|
})()`));
|
|
686
683
|
}
|
|
684
|
+
async function waitForAiFormJobDropdownReady(page) {
|
|
685
|
+
await page.waitForFunction(`(() => {
|
|
686
|
+
const input = Array.from(
|
|
687
|
+
document.querySelectorAll(
|
|
688
|
+
".ui-dropmenu-list input[type='text'], .ui-dropmenu-list input, .job-dropmenu-options .chat-job-search, .job-dropmenu-popover .chat-job-search, .top-chat-search .chat-job-search, input.chat-job-search",
|
|
689
|
+
),
|
|
690
|
+
).find((el) => {
|
|
691
|
+
if (!(el instanceof HTMLInputElement)) return false;
|
|
692
|
+
const rect = el.getBoundingClientRect();
|
|
693
|
+
const style = window.getComputedStyle(el);
|
|
694
|
+
return rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden";
|
|
695
|
+
});
|
|
696
|
+
return !!input;
|
|
697
|
+
})()`, { timeout: 6_000 });
|
|
698
|
+
}
|
|
699
|
+
async function waitForAiFormJobSearchResults(page, keyword) {
|
|
700
|
+
await page.waitForFunction(`((kw) => {
|
|
701
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
|
|
702
|
+
const rows = Array.from(
|
|
703
|
+
document.querySelectorAll(
|
|
704
|
+
".job-dropmenu-list .job-dropmenu-item, .job-dropmenu-options .job-list .job-item, .job-dropmenu-popover .job-list .job-item, .job-dropmenu-options .job-item",
|
|
705
|
+
),
|
|
706
|
+
);
|
|
707
|
+
if (rows.length === 0) return false;
|
|
708
|
+
return rows.some((el) => {
|
|
709
|
+
const label = norm(el.querySelector(".job-option-text, .label")?.textContent || el.textContent || "");
|
|
710
|
+
return label.includes(norm(kw));
|
|
711
|
+
});
|
|
712
|
+
})`, { timeout: 8_000 }, keyword);
|
|
713
|
+
}
|
|
714
|
+
async function waitForAiFormJobSelected(page, expectedLabel) {
|
|
715
|
+
await page.waitForFunction(`((label) => {
|
|
716
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
717
|
+
const selected = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
718
|
+
return !!selected && selected === label;
|
|
719
|
+
})`, { timeout: 8_000 }, expectedLabel);
|
|
720
|
+
await ensureInDeepSearchPage(page);
|
|
721
|
+
}
|
|
687
722
|
export async function selectAiFormJob(page, keyword) {
|
|
688
723
|
const kw = keyword.trim();
|
|
689
724
|
if (!kw) {
|
|
690
725
|
throw new Error('岗位关键字不能为空。');
|
|
691
726
|
}
|
|
692
727
|
const kwLiteral = JSON.stringify(kw);
|
|
693
|
-
const opened = (await page.evaluate(`(() => {
|
|
694
|
-
const host = document.querySelector(".job-dropmenu-select");
|
|
695
|
-
if (!(host instanceof HTMLElement)) return false;
|
|
696
|
-
host.scrollIntoView({ block: "center", inline: "nearest" });
|
|
697
|
-
host.click();
|
|
698
|
-
return true;
|
|
728
|
+
const opened = (await page.evaluate(`(() => {
|
|
729
|
+
const host = document.querySelector(".job-dropmenu-select");
|
|
730
|
+
if (!(host instanceof HTMLElement)) return false;
|
|
731
|
+
host.scrollIntoView({ block: "center", inline: "nearest" });
|
|
732
|
+
host.click();
|
|
733
|
+
return true;
|
|
699
734
|
})()`));
|
|
700
735
|
if (!opened) {
|
|
701
736
|
throw new Error('未找到深度搜索页岗位下拉(.job-dropmenu-select)。');
|
|
702
737
|
}
|
|
703
|
-
await
|
|
704
|
-
const searched = (await page.evaluate(`(() => {
|
|
705
|
-
const kw = ${kwLiteral};
|
|
706
|
-
const inputs = Array.from(
|
|
707
|
-
document.querySelectorAll(
|
|
708
|
-
".ui-dropmenu-list input[type='text'], .ui-dropmenu-list input, .job-dropmenu-options .chat-job-search, .job-dropmenu-popover .chat-job-search, .top-chat-search .chat-job-search, input.chat-job-search",
|
|
709
|
-
),
|
|
710
|
-
);
|
|
711
|
-
const input = inputs.find((el) => {
|
|
712
|
-
if (!(el instanceof HTMLInputElement)) return false;
|
|
713
|
-
const r = el.getBoundingClientRect();
|
|
714
|
-
return r.width > 0 && r.height > 0;
|
|
715
|
-
});
|
|
716
|
-
if (!input) return false;
|
|
717
|
-
input.focus();
|
|
718
|
-
input.value = kw;
|
|
719
|
-
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
720
|
-
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
721
|
-
return true;
|
|
738
|
+
await waitForAiFormJobDropdownReady(page);
|
|
739
|
+
const searched = (await page.evaluate(`(() => {
|
|
740
|
+
const kw = ${kwLiteral};
|
|
741
|
+
const inputs = Array.from(
|
|
742
|
+
document.querySelectorAll(
|
|
743
|
+
".ui-dropmenu-list input[type='text'], .ui-dropmenu-list input, .job-dropmenu-options .chat-job-search, .job-dropmenu-popover .chat-job-search, .top-chat-search .chat-job-search, input.chat-job-search",
|
|
744
|
+
),
|
|
745
|
+
);
|
|
746
|
+
const input = inputs.find((el) => {
|
|
747
|
+
if (!(el instanceof HTMLInputElement)) return false;
|
|
748
|
+
const r = el.getBoundingClientRect();
|
|
749
|
+
return r.width > 0 && r.height > 0;
|
|
750
|
+
});
|
|
751
|
+
if (!input) return false;
|
|
752
|
+
input.focus();
|
|
753
|
+
input.value = kw;
|
|
754
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
755
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
756
|
+
return true;
|
|
722
757
|
})()`));
|
|
723
758
|
if (searched) {
|
|
724
|
-
await
|
|
759
|
+
await waitForAiFormJobSearchResults(page, kw);
|
|
725
760
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
)
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
751
|
-
target.click();
|
|
752
|
-
return { ok: true, label };
|
|
761
|
+
const picked = (await page.evaluate(`(() => {
|
|
762
|
+
const kw = ${kwLiteral};
|
|
763
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
|
|
764
|
+
const rows = Array.from(
|
|
765
|
+
document.querySelectorAll(
|
|
766
|
+
".job-dropmenu-list .job-dropmenu-item, .job-dropmenu-options .job-list .job-item, .job-dropmenu-popover .job-list .job-item, .job-dropmenu-options .job-item",
|
|
767
|
+
),
|
|
768
|
+
);
|
|
769
|
+
if (rows.length === 0) return { ok: false, reason: "empty" };
|
|
770
|
+
const target = rows.find((el) => {
|
|
771
|
+
const label = norm(
|
|
772
|
+
el.querySelector(".job-option-text, .label")?.textContent || el.textContent || "",
|
|
773
|
+
);
|
|
774
|
+
return label.includes(norm(kw));
|
|
775
|
+
});
|
|
776
|
+
if (!(target instanceof HTMLElement)) return { ok: false, reason: "not_found" };
|
|
777
|
+
const label = (
|
|
778
|
+
target.querySelector(".job-option-text, .label")?.textContent ?? target.textContent ?? ""
|
|
779
|
+
)
|
|
780
|
+
.replace(/\\s+/g, " ")
|
|
781
|
+
.trim();
|
|
782
|
+
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
783
|
+
target.click();
|
|
784
|
+
return { ok: true, label };
|
|
753
785
|
})()`));
|
|
754
786
|
if (!picked.ok) {
|
|
755
787
|
throw new Error(`未找到匹配岗位「${kw}」。`);
|
|
756
788
|
}
|
|
757
|
-
|
|
758
|
-
|
|
789
|
+
const label = picked.label ?? kw;
|
|
790
|
+
await waitForAiFormJobSelected(page, label);
|
|
791
|
+
return label;
|
|
759
792
|
}
|
|
760
793
|
/** 深度搜索页当前选中的岗位文案(无则「默认」) */
|
|
761
794
|
export async function readAiFormSelectedJobLabel(page) {
|
|
762
|
-
return (await page.evaluate(`(() => {
|
|
763
|
-
const t = (document.querySelector(".job-dropmenu-select .job-main-text")?.textContent ?? "")
|
|
764
|
-
.replace(/\\s+/g, " ")
|
|
765
|
-
.trim();
|
|
766
|
-
return t.length > 0 ? t : "默认";
|
|
795
|
+
return (await page.evaluate(`(() => {
|
|
796
|
+
const t = (document.querySelector(".job-dropmenu-select .job-main-text")?.textContent ?? "")
|
|
797
|
+
.replace(/\\s+/g, " ")
|
|
798
|
+
.trim();
|
|
799
|
+
return t.length > 0 ? t : "默认";
|
|
767
800
|
})()`));
|
|
768
801
|
}
|
|
769
802
|
/**
|
|
770
|
-
* 在深度搜索(aiform
|
|
803
|
+
* 在深度搜索(aiform)主文档中按姓名打开在线简历预览(与 {@link clickGreetDeepSearch} 同一卡片集合,排除「继续沟通」)。
|
|
771
804
|
*/
|
|
772
805
|
export async function openDeepSearchResumePreview(page, target) {
|
|
773
806
|
const raw = target.trim();
|
|
774
807
|
const targetLiteral = JSON.stringify(raw);
|
|
775
|
-
return (await page.evaluate(`(() => {
|
|
776
|
-
const raw = ${targetLiteral};
|
|
777
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
778
|
-
const allCards = Array.from(
|
|
779
|
-
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
780
|
-
);
|
|
781
|
-
if (allCards.length === 0) return false;
|
|
782
|
-
const cards = allCards.filter((item) => {
|
|
783
|
-
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
784
|
-
return !chatLabel.includes("继续沟通");
|
|
785
|
-
});
|
|
786
|
-
if (cards.length === 0) return false;
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
return true;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
const
|
|
812
|
-
if (
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if (geekInfo instanceof HTMLElement) {
|
|
831
|
-
geekInfo.scrollIntoView({ block: "center", inline: "nearest" });
|
|
832
|
-
geekInfo.click();
|
|
833
|
-
return true;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
return false;
|
|
808
|
+
return (await page.evaluate(`(() => {
|
|
809
|
+
const raw = ${targetLiteral};
|
|
810
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
811
|
+
const allCards = Array.from(
|
|
812
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
813
|
+
);
|
|
814
|
+
if (allCards.length === 0) return false;
|
|
815
|
+
const cards = allCards.filter((item) => {
|
|
816
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
817
|
+
return !chatLabel.includes("继续沟通");
|
|
818
|
+
});
|
|
819
|
+
if (cards.length === 0) return false;
|
|
820
|
+
const targetCard =
|
|
821
|
+
cards.find((item) => {
|
|
822
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
823
|
+
return name === raw || name.includes(raw);
|
|
824
|
+
}) ?? null;
|
|
825
|
+
if (!targetCard) return false;
|
|
826
|
+
|
|
827
|
+
function tryOpen(el) {
|
|
828
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
829
|
+
if (el.classList.contains("disabled")) return false;
|
|
830
|
+
const st = window.getComputedStyle(el);
|
|
831
|
+
if (st.pointerEvents === "none" || Number(st.opacity) < 0.3) return false;
|
|
832
|
+
el.scrollIntoView({ block: "center", inline: "nearest" });
|
|
833
|
+
el.click();
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const nameEl = targetCard.querySelector(".geek-name");
|
|
838
|
+
if (nameEl instanceof HTMLElement) {
|
|
839
|
+
nameEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
840
|
+
nameEl.click();
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const resumeOnline = targetCard.querySelector("a.resume-btn-online");
|
|
845
|
+
if (tryOpen(resumeOnline)) return true;
|
|
846
|
+
const hrefResume = targetCard.querySelector('a[href*="c-resume"], a[href*="frame/c-resume"]');
|
|
847
|
+
if (tryOpen(hrefResume)) return true;
|
|
848
|
+
|
|
849
|
+
const links = Array.from(targetCard.querySelectorAll("a, button, .btn")).filter((node) => {
|
|
850
|
+
const t = norm(node.textContent);
|
|
851
|
+
return /在线简历|查看简历|简历预览|预览/.test(t);
|
|
852
|
+
});
|
|
853
|
+
if (links.length > 0 && tryOpen(links[0])) return true;
|
|
854
|
+
|
|
855
|
+
const geekInfo = targetCard.querySelector(".geek-info, .geek-card-main, .card-content");
|
|
856
|
+
if (geekInfo instanceof HTMLElement) {
|
|
857
|
+
geekInfo.scrollIntoView({ block: "center", inline: "nearest" });
|
|
858
|
+
geekInfo.click();
|
|
859
|
+
return true;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return false;
|
|
837
863
|
})()`));
|
|
838
864
|
}
|
|
839
865
|
export async function readDeepSearchGeekList(page) {
|
|
840
|
-
return (await page.evaluate(`(() => {
|
|
841
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
842
|
-
const items = Array.from(
|
|
843
|
-
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
844
|
-
);
|
|
845
|
-
return items
|
|
846
|
-
.map((item) => {
|
|
847
|
-
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
848
|
-
if (chatLabel.includes("继续沟通")) {
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
851
|
-
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
852
|
-
const splits = Array.from(item.querySelectorAll(".geek-exp .split"))
|
|
853
|
-
.map((el) => norm(el.getAttribute("title") || el.textContent || ""))
|
|
854
|
-
.filter(Boolean);
|
|
855
|
-
const meta = splits.join(" · ");
|
|
856
|
-
const work = norm(item.querySelector(".geek-works span")?.textContent);
|
|
857
|
-
const edu = norm(item.querySelector(".geek-edus span")?.textContent);
|
|
858
|
-
const recEl = item.querySelector(".geek-recommend-text");
|
|
859
|
-
let reason = "";
|
|
860
|
-
if (recEl) {
|
|
861
|
-
reason = norm(recEl.textContent).replace(/^推荐理由\\s*/, "").trim();
|
|
862
|
-
}
|
|
863
|
-
return { name, meta, work, edu, reason };
|
|
864
|
-
})
|
|
865
|
-
.filter((x) => x !== null);
|
|
866
|
+
return (await page.evaluate(`(() => {
|
|
867
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
868
|
+
const items = Array.from(
|
|
869
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
870
|
+
);
|
|
871
|
+
return items
|
|
872
|
+
.map((item) => {
|
|
873
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
874
|
+
if (chatLabel.includes("继续沟通")) {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
878
|
+
const splits = Array.from(item.querySelectorAll(".geek-exp .split"))
|
|
879
|
+
.map((el) => norm(el.getAttribute("title") || el.textContent || ""))
|
|
880
|
+
.filter(Boolean);
|
|
881
|
+
const meta = splits.join(" · ");
|
|
882
|
+
const work = norm(item.querySelector(".geek-works span")?.textContent);
|
|
883
|
+
const edu = norm(item.querySelector(".geek-edus span")?.textContent);
|
|
884
|
+
const recEl = item.querySelector(".geek-recommend-text");
|
|
885
|
+
let reason = "";
|
|
886
|
+
if (recEl) {
|
|
887
|
+
reason = norm(recEl.textContent).replace(/^推荐理由\\s*/, "").trim();
|
|
888
|
+
}
|
|
889
|
+
return { name, meta, work, edu, reason };
|
|
890
|
+
})
|
|
891
|
+
.filter((x) => x !== null);
|
|
866
892
|
})()`));
|
|
867
893
|
}
|
|
868
894
|
export function renderGeekListSection(title, items) {
|
|
@@ -888,58 +914,51 @@ export function renderGeekListSection(title, items) {
|
|
|
888
914
|
}
|
|
889
915
|
export async function clickGreetDeepSearch(page, target) {
|
|
890
916
|
const targetLiteral = JSON.stringify(target.trim());
|
|
891
|
-
const result = (await page.evaluate(`(() => {
|
|
892
|
-
const raw = ${targetLiteral};
|
|
893
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
894
|
-
const allCards = Array.from(
|
|
895
|
-
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
896
|
-
);
|
|
897
|
-
if (allCards.length === 0) {
|
|
898
|
-
return { kind: "empty" };
|
|
899
|
-
}
|
|
900
|
-
const cards = allCards.filter((item) => {
|
|
901
|
-
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
902
|
-
return !chatLabel.includes("继续沟通");
|
|
903
|
-
});
|
|
904
|
-
if (cards.length === 0) {
|
|
905
|
-
return { kind: "all_continue" };
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
|
|
937
|
-
if (disabled) {
|
|
938
|
-
return { kind: "disabled", name };
|
|
939
|
-
}
|
|
940
|
-
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
941
|
-
btn.click();
|
|
942
|
-
return { kind: "clicked", name };
|
|
917
|
+
const result = (await page.evaluate(`(() => {
|
|
918
|
+
const raw = ${targetLiteral};
|
|
919
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
920
|
+
const allCards = Array.from(
|
|
921
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
922
|
+
);
|
|
923
|
+
if (allCards.length === 0) {
|
|
924
|
+
return { kind: "empty" };
|
|
925
|
+
}
|
|
926
|
+
const cards = allCards.filter((item) => {
|
|
927
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
928
|
+
return !chatLabel.includes("继续沟通");
|
|
929
|
+
});
|
|
930
|
+
if (cards.length === 0) {
|
|
931
|
+
return { kind: "all_continue" };
|
|
932
|
+
}
|
|
933
|
+
const targetCard =
|
|
934
|
+
cards.find((item) => {
|
|
935
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
936
|
+
return name === raw || name.includes(raw);
|
|
937
|
+
}) ?? null;
|
|
938
|
+
if (!targetCard) {
|
|
939
|
+
return { kind: "not_found", target: raw };
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const name = norm(targetCard.querySelector(".geek-name")?.textContent);
|
|
943
|
+
const btn =
|
|
944
|
+
targetCard.querySelector(".geek-chat .btn-ai-v2") ||
|
|
945
|
+
targetCard.querySelector(".geek-chat span.btn-ai-v2") ||
|
|
946
|
+
targetCard.querySelector(".geek-chat span[class*='btn-ai']");
|
|
947
|
+
if (!(btn instanceof HTMLElement)) {
|
|
948
|
+
return { kind: "no_btn", name };
|
|
949
|
+
}
|
|
950
|
+
const label = norm(btn.textContent);
|
|
951
|
+
if (!label.includes("打招呼")) {
|
|
952
|
+
return { kind: "not_greet", name, label };
|
|
953
|
+
}
|
|
954
|
+
const cls = btn.className ?? "";
|
|
955
|
+
const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
|
|
956
|
+
if (disabled) {
|
|
957
|
+
return { kind: "disabled", name };
|
|
958
|
+
}
|
|
959
|
+
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
960
|
+
btn.click();
|
|
961
|
+
return { kind: "clicked", name };
|
|
943
962
|
})()`));
|
|
944
963
|
switch (result.kind) {
|
|
945
964
|
case 'empty':
|
|
@@ -947,7 +966,7 @@ export async function clickGreetDeepSearch(page, target) {
|
|
|
947
966
|
case 'all_continue':
|
|
948
967
|
throw new Error('当前列表均为「继续沟通」状态,已无待打招呼人选(与 boss deep-search 列表展示一致)。');
|
|
949
968
|
case 'not_found':
|
|
950
|
-
throw new Error(`未在可打招呼的深度搜索列表中找到目标:${result.target}(「继续沟通」人选已排除,请用 boss deep-search
|
|
969
|
+
throw new Error(`未在可打招呼的深度搜索列表中找到目标:${result.target}(「继续沟通」人选已排除,请用 boss deep-search 核对姓名)。`);
|
|
951
970
|
case 'no_btn':
|
|
952
971
|
throw new Error(`候选人 ${result.name} 缺少「打招呼」按钮,无法执行。`);
|
|
953
972
|
case 'not_greet':
|
|
@@ -987,7 +1006,6 @@ export async function runBossSearchSet(opts) {
|
|
|
987
1006
|
const currentUrl = page.url();
|
|
988
1007
|
if (!isBossChatAiFormUrl(currentUrl)) {
|
|
989
1008
|
await clickBossSidebarMenuToPath(page, '深度搜索', '/web/chat/aiform');
|
|
990
|
-
await sleepRandom(AI_FORM_SETTLE_MS.min, AI_FORM_SETTLE_MS.max);
|
|
991
1009
|
}
|
|
992
1010
|
if (!isBossChatAiFormUrl(page.url())) {
|
|
993
1011
|
throw new Error('通过侧边栏“深度搜索”进入页面失败,请确认已登录并可访问 /web/chat/aiform。');
|
|
@@ -997,7 +1015,7 @@ export async function runBossSearchSet(opts) {
|
|
|
997
1015
|
await selectAiFormJob(page, jobKeyword);
|
|
998
1016
|
await ensureInDeepSearchPage(page);
|
|
999
1017
|
if (hasFormEdit) {
|
|
1000
|
-
await
|
|
1018
|
+
await ensureInDeepSearchPage(page);
|
|
1001
1019
|
}
|
|
1002
1020
|
}
|
|
1003
1021
|
if (hasFormEdit) {
|
|
@@ -1013,9 +1031,6 @@ export async function runBossSearchSet(opts) {
|
|
|
1013
1031
|
}
|
|
1014
1032
|
catch (e) {
|
|
1015
1033
|
const message = e instanceof Error ? e.message : String(e);
|
|
1016
|
-
if (e instanceof Error && e.message.includes('浏览器会话尚未初始化')) {
|
|
1017
|
-
throw new Error(createWaitManualLoginRequiredText('设置深度搜索条件'));
|
|
1018
|
-
}
|
|
1019
1034
|
console.error(`[boss-cli] boss_search_set error: ${message}`);
|
|
1020
1035
|
throw new Error(`设置深度搜索条件失败:${message}`);
|
|
1021
1036
|
}
|
|
@@ -1027,7 +1042,6 @@ export async function runBossSearch(opts = {}) {
|
|
|
1027
1042
|
const currentUrl = page.url();
|
|
1028
1043
|
if (!isBossChatAiFormUrl(currentUrl)) {
|
|
1029
1044
|
await clickBossSidebarMenuToPath(page, '深度搜索', '/web/chat/aiform');
|
|
1030
|
-
await sleepRandom(AI_FORM_SETTLE_MS.min, AI_FORM_SETTLE_MS.max);
|
|
1031
1045
|
}
|
|
1032
1046
|
if (!isBossChatAiFormUrl(page.url())) {
|
|
1033
1047
|
throw new Error('通过侧边栏“深度搜索”进入页面失败,请确认已登录并可访问 /web/chat/aiform。');
|
|
@@ -1036,7 +1050,6 @@ export async function runBossSearch(opts = {}) {
|
|
|
1036
1050
|
if (jobKeyword) {
|
|
1037
1051
|
await selectAiFormJob(page, jobKeyword);
|
|
1038
1052
|
await ensureInDeepSearchPage(page);
|
|
1039
|
-
await sleepRandom(600, 1200);
|
|
1040
1053
|
}
|
|
1041
1054
|
const geeks = await readDeepSearchGeekList(page);
|
|
1042
1055
|
const title = jobKeyword
|
|
@@ -1047,9 +1060,6 @@ export async function runBossSearch(opts = {}) {
|
|
|
1047
1060
|
}
|
|
1048
1061
|
catch (e) {
|
|
1049
1062
|
const message = e instanceof Error ? e.message : String(e);
|
|
1050
|
-
if (e instanceof Error && e.message.includes('浏览器会话尚未初始化')) {
|
|
1051
|
-
throw new Error(createWaitManualLoginRequiredText('读取深度搜索列表'));
|
|
1052
|
-
}
|
|
1053
1063
|
console.error(`[boss-cli] boss_search error: ${message}`);
|
|
1054
1064
|
throw new Error(`读取深度搜索列表失败:${message}`);
|
|
1055
1065
|
}
|