@joohw/boss-cli 0.4.0 → 0.5.0
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 +170 -172
- 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.d.ts.map +1 -1
- package/dist/cli/cliRouter.js +56 -43
- package/dist/cli/cliRouter.js.map +1 -1
- package/dist/cli/version.d.ts +22 -0
- package/dist/cli/version.d.ts.map +1 -1
- package/dist/cli/version.js +127 -6
- package/dist/cli/version.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.map +1 -1
- package/dist/common/boss_session_lock.js +7 -1
- package/dist/common/boss_session_lock.js.map +1 -1
- 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 +18 -29
- 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.map +1 -1
- package/dist/toolset/deep-search.js +289 -265
- package/dist/toolset/deep-search.js.map +1 -1
- package/dist/toolset/greet.d.ts.map +1 -1
- package/dist/toolset/greet.js +1 -6
- package/dist/toolset/greet.js.map +1 -1
- package/dist/toolset/jd.d.ts.map +1 -1
- package/dist/toolset/jd.js +132 -136
- 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 +6 -8
- package/dist/toolset/preview.js.map +1 -1
- package/dist/toolset/recommend.d.ts.map +1 -1
- package/dist/toolset/recommend.js +222 -196
- 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 -65
- 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,127 +640,163 @@ 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
|
/**
|
|
@@ -772,90 +805,90 @@ export async function readAiFormSelectedJobLabel(page) {
|
|
|
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
|
-
const targetCard =
|
|
788
|
-
cards.find((item) => {
|
|
789
|
-
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
790
|
-
return name === raw || name.includes(raw);
|
|
791
|
-
}) ?? null;
|
|
792
|
-
if (!targetCard) return false;
|
|
793
|
-
|
|
794
|
-
function tryOpen(el) {
|
|
795
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
796
|
-
if (el.classList.contains("disabled")) return false;
|
|
797
|
-
const st = window.getComputedStyle(el);
|
|
798
|
-
if (st.pointerEvents === "none" || Number(st.opacity) < 0.3) return false;
|
|
799
|
-
el.scrollIntoView({ block: "center", inline: "nearest" });
|
|
800
|
-
el.click();
|
|
801
|
-
return true;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
const nameEl = targetCard.querySelector(".geek-name");
|
|
805
|
-
if (nameEl instanceof HTMLElement) {
|
|
806
|
-
nameEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
807
|
-
nameEl.click();
|
|
808
|
-
return true;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
const resumeOnline = targetCard.querySelector("a.resume-btn-online");
|
|
812
|
-
if (tryOpen(resumeOnline)) return true;
|
|
813
|
-
const hrefResume = targetCard.querySelector('a[href*="c-resume"], a[href*="frame/c-resume"]');
|
|
814
|
-
if (tryOpen(hrefResume)) return true;
|
|
815
|
-
|
|
816
|
-
const links = Array.from(targetCard.querySelectorAll("a, button, .btn")).filter((node) => {
|
|
817
|
-
const t = norm(node.textContent);
|
|
818
|
-
return /在线简历|查看简历|简历预览|预览/.test(t);
|
|
819
|
-
});
|
|
820
|
-
if (links.length > 0 && tryOpen(links[0])) return true;
|
|
821
|
-
|
|
822
|
-
const geekInfo = targetCard.querySelector(".geek-info, .geek-card-main, .card-content");
|
|
823
|
-
if (geekInfo instanceof HTMLElement) {
|
|
824
|
-
geekInfo.scrollIntoView({ block: "center", inline: "nearest" });
|
|
825
|
-
geekInfo.click();
|
|
826
|
-
return true;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
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;
|
|
830
863
|
})()`));
|
|
831
864
|
}
|
|
832
865
|
export async function readDeepSearchGeekList(page) {
|
|
833
|
-
return (await page.evaluate(`(() => {
|
|
834
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
835
|
-
const items = Array.from(
|
|
836
|
-
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
837
|
-
);
|
|
838
|
-
return items
|
|
839
|
-
.map((item) => {
|
|
840
|
-
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
841
|
-
if (chatLabel.includes("继续沟通")) {
|
|
842
|
-
return null;
|
|
843
|
-
}
|
|
844
|
-
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
845
|
-
const splits = Array.from(item.querySelectorAll(".geek-exp .split"))
|
|
846
|
-
.map((el) => norm(el.getAttribute("title") || el.textContent || ""))
|
|
847
|
-
.filter(Boolean);
|
|
848
|
-
const meta = splits.join(" · ");
|
|
849
|
-
const work = norm(item.querySelector(".geek-works span")?.textContent);
|
|
850
|
-
const edu = norm(item.querySelector(".geek-edus span")?.textContent);
|
|
851
|
-
const recEl = item.querySelector(".geek-recommend-text");
|
|
852
|
-
let reason = "";
|
|
853
|
-
if (recEl) {
|
|
854
|
-
reason = norm(recEl.textContent).replace(/^推荐理由\\s*/, "").trim();
|
|
855
|
-
}
|
|
856
|
-
return { name, meta, work, edu, reason };
|
|
857
|
-
})
|
|
858
|
-
.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);
|
|
859
892
|
})()`));
|
|
860
893
|
}
|
|
861
894
|
export function renderGeekListSection(title, items) {
|
|
@@ -881,51 +914,51 @@ export function renderGeekListSection(title, items) {
|
|
|
881
914
|
}
|
|
882
915
|
export async function clickGreetDeepSearch(page, target) {
|
|
883
916
|
const targetLiteral = JSON.stringify(target.trim());
|
|
884
|
-
const result = (await page.evaluate(`(() => {
|
|
885
|
-
const raw = ${targetLiteral};
|
|
886
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
887
|
-
const allCards = Array.from(
|
|
888
|
-
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
889
|
-
);
|
|
890
|
-
if (allCards.length === 0) {
|
|
891
|
-
return { kind: "empty" };
|
|
892
|
-
}
|
|
893
|
-
const cards = allCards.filter((item) => {
|
|
894
|
-
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
895
|
-
return !chatLabel.includes("继续沟通");
|
|
896
|
-
});
|
|
897
|
-
if (cards.length === 0) {
|
|
898
|
-
return { kind: "all_continue" };
|
|
899
|
-
}
|
|
900
|
-
const targetCard =
|
|
901
|
-
cards.find((item) => {
|
|
902
|
-
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
903
|
-
return name === raw || name.includes(raw);
|
|
904
|
-
}) ?? null;
|
|
905
|
-
if (!targetCard) {
|
|
906
|
-
return { kind: "not_found", target: raw };
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const name = norm(targetCard.querySelector(".geek-name")?.textContent);
|
|
910
|
-
const btn =
|
|
911
|
-
targetCard.querySelector(".geek-chat .btn-ai-v2") ||
|
|
912
|
-
targetCard.querySelector(".geek-chat span.btn-ai-v2") ||
|
|
913
|
-
targetCard.querySelector(".geek-chat span[class*='btn-ai']");
|
|
914
|
-
if (!(btn instanceof HTMLElement)) {
|
|
915
|
-
return { kind: "no_btn", name };
|
|
916
|
-
}
|
|
917
|
-
const label = norm(btn.textContent);
|
|
918
|
-
if (!label.includes("打招呼")) {
|
|
919
|
-
return { kind: "not_greet", name, label };
|
|
920
|
-
}
|
|
921
|
-
const cls = btn.className ?? "";
|
|
922
|
-
const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
|
|
923
|
-
if (disabled) {
|
|
924
|
-
return { kind: "disabled", name };
|
|
925
|
-
}
|
|
926
|
-
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
927
|
-
btn.click();
|
|
928
|
-
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 };
|
|
929
962
|
})()`));
|
|
930
963
|
switch (result.kind) {
|
|
931
964
|
case 'empty':
|
|
@@ -973,7 +1006,6 @@ export async function runBossSearchSet(opts) {
|
|
|
973
1006
|
const currentUrl = page.url();
|
|
974
1007
|
if (!isBossChatAiFormUrl(currentUrl)) {
|
|
975
1008
|
await clickBossSidebarMenuToPath(page, '深度搜索', '/web/chat/aiform');
|
|
976
|
-
await sleepRandom(AI_FORM_SETTLE_MS.min, AI_FORM_SETTLE_MS.max);
|
|
977
1009
|
}
|
|
978
1010
|
if (!isBossChatAiFormUrl(page.url())) {
|
|
979
1011
|
throw new Error('通过侧边栏“深度搜索”进入页面失败,请确认已登录并可访问 /web/chat/aiform。');
|
|
@@ -983,7 +1015,7 @@ export async function runBossSearchSet(opts) {
|
|
|
983
1015
|
await selectAiFormJob(page, jobKeyword);
|
|
984
1016
|
await ensureInDeepSearchPage(page);
|
|
985
1017
|
if (hasFormEdit) {
|
|
986
|
-
await
|
|
1018
|
+
await ensureInDeepSearchPage(page);
|
|
987
1019
|
}
|
|
988
1020
|
}
|
|
989
1021
|
if (hasFormEdit) {
|
|
@@ -999,9 +1031,6 @@ export async function runBossSearchSet(opts) {
|
|
|
999
1031
|
}
|
|
1000
1032
|
catch (e) {
|
|
1001
1033
|
const message = e instanceof Error ? e.message : String(e);
|
|
1002
|
-
if (e instanceof Error && e.message.includes('浏览器会话尚未初始化')) {
|
|
1003
|
-
throw new Error(createWaitManualLoginRequiredText('设置深度搜索条件'));
|
|
1004
|
-
}
|
|
1005
1034
|
console.error(`[boss-cli] boss_search_set error: ${message}`);
|
|
1006
1035
|
throw new Error(`设置深度搜索条件失败:${message}`);
|
|
1007
1036
|
}
|
|
@@ -1013,7 +1042,6 @@ export async function runBossSearch(opts = {}) {
|
|
|
1013
1042
|
const currentUrl = page.url();
|
|
1014
1043
|
if (!isBossChatAiFormUrl(currentUrl)) {
|
|
1015
1044
|
await clickBossSidebarMenuToPath(page, '深度搜索', '/web/chat/aiform');
|
|
1016
|
-
await sleepRandom(AI_FORM_SETTLE_MS.min, AI_FORM_SETTLE_MS.max);
|
|
1017
1045
|
}
|
|
1018
1046
|
if (!isBossChatAiFormUrl(page.url())) {
|
|
1019
1047
|
throw new Error('通过侧边栏“深度搜索”进入页面失败,请确认已登录并可访问 /web/chat/aiform。');
|
|
@@ -1022,7 +1050,6 @@ export async function runBossSearch(opts = {}) {
|
|
|
1022
1050
|
if (jobKeyword) {
|
|
1023
1051
|
await selectAiFormJob(page, jobKeyword);
|
|
1024
1052
|
await ensureInDeepSearchPage(page);
|
|
1025
|
-
await sleepRandom(600, 1200);
|
|
1026
1053
|
}
|
|
1027
1054
|
const geeks = await readDeepSearchGeekList(page);
|
|
1028
1055
|
const title = jobKeyword
|
|
@@ -1033,9 +1060,6 @@ export async function runBossSearch(opts = {}) {
|
|
|
1033
1060
|
}
|
|
1034
1061
|
catch (e) {
|
|
1035
1062
|
const message = e instanceof Error ? e.message : String(e);
|
|
1036
|
-
if (e instanceof Error && e.message.includes('浏览器会话尚未初始化')) {
|
|
1037
|
-
throw new Error(createWaitManualLoginRequiredText('读取深度搜索列表'));
|
|
1038
|
-
}
|
|
1039
1063
|
console.error(`[boss-cli] boss_search error: ${message}`);
|
|
1040
1064
|
throw new Error(`读取深度搜索列表失败:${message}`);
|
|
1041
1065
|
}
|