@viyzhu/boss-cli-fork 0.7.2-rc.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +85 -56
- package/LICENSE +674 -674
- package/README.md +284 -276
- package/dist/browser/cdp_browser.d.ts +170 -88
- package/dist/browser/cdp_browser.d.ts.map +1 -1
- package/dist/browser/cdp_browser.js +674 -400
- package/dist/browser/cdp_browser.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/cli/cliRouter.js +61 -61
- package/dist/common/auth.js +49 -49
- package/dist/common/boss_modal.js +42 -42
- package/dist/common/boss_page_guards.js +247 -247
- package/dist/common/boss_paywall_popup.js +87 -87
- package/dist/common/boss_session_page.d.ts.map +1 -1
- package/dist/common/boss_session_page.js +16 -15
- package/dist/common/boss_session_page.js.map +1 -1
- package/dist/common/boss_sidebar_nav.js +24 -24
- package/dist/common/c_resume_capture.js +61 -61
- package/dist/toolset/action.d.ts.map +1 -1
- package/dist/toolset/action.js +237 -233
- package/dist/toolset/action.js.map +1 -1
- package/dist/toolset/chat.js +437 -437
- package/dist/toolset/deep-search.js +570 -570
- package/dist/toolset/jd.js +132 -132
- package/dist/toolset/list.js +48 -48
- package/dist/toolset/normal-search.js +158 -158
- package/dist/toolset/preview.d.ts.map +1 -1
- package/dist/toolset/preview.js +3 -3
- package/dist/toolset/preview.js.map +1 -1
- package/dist/toolset/recommend.js +207 -207
- package/package.json +69 -69
- package/skills/boss-frontend-analysis/SKILL.md +58 -58
- package/skills/boss-frontend-analysis/agents/openai.yaml +4 -4
- package/skills/boss-frontend-analysis/scripts/capture_boss_frontend.mjs +604 -604
|
@@ -31,15 +31,15 @@ function assertStillOnAiForm(page) {
|
|
|
31
31
|
async function waitForAiFormReady(page) {
|
|
32
32
|
assertStillOnAiForm(page);
|
|
33
33
|
try {
|
|
34
|
-
await page.waitForFunction(`(() => {
|
|
35
|
-
const root = document.querySelector(".ai-form-left");
|
|
36
|
-
const submit = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
37
|
-
const selected = document.querySelector(".job-dropmenu-select .job-main-text");
|
|
38
|
-
if (!root || !submit || !selected) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
const text = (selected.textContent ?? "").replace(/\\s+/g, " ").trim();
|
|
42
|
-
return text.length > 0;
|
|
34
|
+
await page.waitForFunction(`(() => {
|
|
35
|
+
const root = document.querySelector(".ai-form-left");
|
|
36
|
+
const submit = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
37
|
+
const selected = document.querySelector(".job-dropmenu-select .job-main-text");
|
|
38
|
+
if (!root || !submit || !selected) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const text = (selected.textContent ?? "").replace(/\\s+/g, " ").trim();
|
|
42
|
+
return text.length > 0;
|
|
43
43
|
})()`, { timeout: 18_000 });
|
|
44
44
|
}
|
|
45
45
|
catch (e) {
|
|
@@ -64,22 +64,22 @@ async function ensureDeepSearchPageReady(page) {
|
|
|
64
64
|
}
|
|
65
65
|
async function clickAddConditionInSection(page, titleKeyword) {
|
|
66
66
|
const titleLiteral = JSON.stringify(titleKeyword);
|
|
67
|
-
const clicked = (await page.evaluate(`((titleKeyword) => {
|
|
68
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
69
|
-
function findFormSectionByTitle(kw) {
|
|
70
|
-
const h3s = Array.from(document.querySelectorAll(".form-content .form-content-title-h3"));
|
|
71
|
-
const h3 = h3s.find((el) => norm(el.textContent).includes(kw));
|
|
72
|
-
return h3 ? h3.closest(".form-content") : null;
|
|
73
|
-
}
|
|
74
|
-
const section = findFormSectionByTitle(titleKeyword);
|
|
75
|
-
if (!section) return false;
|
|
76
|
-
const header = section.querySelector(".form-content-header");
|
|
77
|
-
const titleBtn = header?.querySelector(".form-content-title-btn");
|
|
78
|
-
if (!(titleBtn instanceof HTMLElement)) return false;
|
|
79
|
-
if (!norm(titleBtn.textContent).includes("添加条件")) return false;
|
|
80
|
-
titleBtn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
81
|
-
titleBtn.click();
|
|
82
|
-
return true;
|
|
67
|
+
const clicked = (await page.evaluate(`((titleKeyword) => {
|
|
68
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
69
|
+
function findFormSectionByTitle(kw) {
|
|
70
|
+
const h3s = Array.from(document.querySelectorAll(".form-content .form-content-title-h3"));
|
|
71
|
+
const h3 = h3s.find((el) => norm(el.textContent).includes(kw));
|
|
72
|
+
return h3 ? h3.closest(".form-content") : null;
|
|
73
|
+
}
|
|
74
|
+
const section = findFormSectionByTitle(titleKeyword);
|
|
75
|
+
if (!section) return false;
|
|
76
|
+
const header = section.querySelector(".form-content-header");
|
|
77
|
+
const titleBtn = header?.querySelector(".form-content-title-btn");
|
|
78
|
+
if (!(titleBtn instanceof HTMLElement)) return false;
|
|
79
|
+
if (!norm(titleBtn.textContent).includes("添加条件")) return false;
|
|
80
|
+
titleBtn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
81
|
+
titleBtn.click();
|
|
82
|
+
return true;
|
|
83
83
|
})(${titleLiteral})`));
|
|
84
84
|
if (!clicked) {
|
|
85
85
|
throw new Error(`未找到「${titleKeyword}」区域的「添加条件」。`);
|
|
@@ -663,38 +663,38 @@ async function applyLinesToSection(page, titleKeyword, lines) {
|
|
|
663
663
|
async function ensureRequirementGroupRow(page, titleKeyword, rowIndex) {
|
|
664
664
|
const titleLiteral = JSON.stringify(titleKeyword);
|
|
665
665
|
for (let round = 0; round < 5; round++) {
|
|
666
|
-
const state = (await page.evaluate(`(() => {
|
|
667
|
-
const titleKeyword = ${titleLiteral};
|
|
668
|
-
const rowIndex = ${rowIndex};
|
|
669
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
670
|
-
function getGroup(title) {
|
|
671
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
672
|
-
const header = headers.find((el) => {
|
|
673
|
-
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
674
|
-
return h.includes(title);
|
|
675
|
-
});
|
|
676
|
-
if (!(header instanceof HTMLElement)) return null;
|
|
677
|
-
let node = header.nextElementSibling;
|
|
678
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
679
|
-
if (node.classList.contains("form-content-list")) return { header, list: node };
|
|
680
|
-
node = node.nextElementSibling;
|
|
681
|
-
}
|
|
682
|
-
return null;
|
|
683
|
-
}
|
|
684
|
-
const group = getGroup(titleKeyword);
|
|
685
|
-
if (!group) return { ok: false, reason: "missing_group", count: 0, x: 0, y: 0 };
|
|
686
|
-
const count = group.list.querySelectorAll(".form-content-list-item").length;
|
|
687
|
-
if (count > rowIndex) return { ok: true, count, x: 0, y: 0 };
|
|
688
|
-
const add = group.header.querySelector(".form-content-title-btn");
|
|
689
|
-
if (!(add instanceof HTMLElement)) return { ok: false, reason: "missing_add_button", count, x: 0, y: 0 };
|
|
690
|
-
const disabled =
|
|
691
|
-
add.getAttribute("aria-disabled") === "true" ||
|
|
692
|
-
add.classList.contains("disabled") ||
|
|
693
|
-
add.classList.contains("is-disabled");
|
|
694
|
-
if (disabled) return { ok: false, reason: "add_button_disabled", count, x: 0, y: 0 };
|
|
695
|
-
add.scrollIntoView({ block: "center", inline: "nearest" });
|
|
696
|
-
const rect = add.getBoundingClientRect();
|
|
697
|
-
return { ok: true, count, x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
666
|
+
const state = (await page.evaluate(`(() => {
|
|
667
|
+
const titleKeyword = ${titleLiteral};
|
|
668
|
+
const rowIndex = ${rowIndex};
|
|
669
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
670
|
+
function getGroup(title) {
|
|
671
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
672
|
+
const header = headers.find((el) => {
|
|
673
|
+
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
674
|
+
return h.includes(title);
|
|
675
|
+
});
|
|
676
|
+
if (!(header instanceof HTMLElement)) return null;
|
|
677
|
+
let node = header.nextElementSibling;
|
|
678
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
679
|
+
if (node.classList.contains("form-content-list")) return { header, list: node };
|
|
680
|
+
node = node.nextElementSibling;
|
|
681
|
+
}
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
const group = getGroup(titleKeyword);
|
|
685
|
+
if (!group) return { ok: false, reason: "missing_group", count: 0, x: 0, y: 0 };
|
|
686
|
+
const count = group.list.querySelectorAll(".form-content-list-item").length;
|
|
687
|
+
if (count > rowIndex) return { ok: true, count, x: 0, y: 0 };
|
|
688
|
+
const add = group.header.querySelector(".form-content-title-btn");
|
|
689
|
+
if (!(add instanceof HTMLElement)) return { ok: false, reason: "missing_add_button", count, x: 0, y: 0 };
|
|
690
|
+
const disabled =
|
|
691
|
+
add.getAttribute("aria-disabled") === "true" ||
|
|
692
|
+
add.classList.contains("disabled") ||
|
|
693
|
+
add.classList.contains("is-disabled");
|
|
694
|
+
if (disabled) return { ok: false, reason: "add_button_disabled", count, x: 0, y: 0 };
|
|
695
|
+
add.scrollIntoView({ block: "center", inline: "nearest" });
|
|
696
|
+
const rect = add.getBoundingClientRect();
|
|
697
|
+
return { ok: true, count, x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
698
698
|
})()`));
|
|
699
699
|
if (!state.ok) {
|
|
700
700
|
throw new Error(`未找到「${titleKeyword}」分组或添加条件按钮:${state.reason ?? 'unknown'}`);
|
|
@@ -706,24 +706,24 @@ async function ensureRequirementGroupRow(page, titleKeyword, rowIndex) {
|
|
|
706
706
|
await sleepRandom(80, 140);
|
|
707
707
|
await page.mouse.click(state.x, state.y);
|
|
708
708
|
try {
|
|
709
|
-
await page.waitForFunction(`(() => {
|
|
710
|
-
const titleKeyword = ${titleLiteral};
|
|
711
|
-
const rowIndex = ${rowIndex};
|
|
712
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
713
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
714
|
-
const header = headers.find((el) => {
|
|
715
|
-
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
716
|
-
return h.includes(titleKeyword);
|
|
717
|
-
});
|
|
718
|
-
if (!header) return false;
|
|
719
|
-
let node = header.nextElementSibling;
|
|
720
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
721
|
-
if (node.classList.contains("form-content-list")) {
|
|
722
|
-
return node.querySelectorAll(".form-content-list-item").length > rowIndex;
|
|
723
|
-
}
|
|
724
|
-
node = node.nextElementSibling;
|
|
725
|
-
}
|
|
726
|
-
return false;
|
|
709
|
+
await page.waitForFunction(`(() => {
|
|
710
|
+
const titleKeyword = ${titleLiteral};
|
|
711
|
+
const rowIndex = ${rowIndex};
|
|
712
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
713
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
714
|
+
const header = headers.find((el) => {
|
|
715
|
+
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
716
|
+
return h.includes(titleKeyword);
|
|
717
|
+
});
|
|
718
|
+
if (!header) return false;
|
|
719
|
+
let node = header.nextElementSibling;
|
|
720
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
721
|
+
if (node.classList.contains("form-content-list")) {
|
|
722
|
+
return node.querySelectorAll(".form-content-list-item").length > rowIndex;
|
|
723
|
+
}
|
|
724
|
+
node = node.nextElementSibling;
|
|
725
|
+
}
|
|
726
|
+
return false;
|
|
727
727
|
})()`, { timeout: 1600 });
|
|
728
728
|
return;
|
|
729
729
|
}
|
|
@@ -736,35 +736,35 @@ async function ensureRequirementGroupRow(page, titleKeyword, rowIndex) {
|
|
|
736
736
|
async function fillRequirementGroupRow(page, titleKeyword, rowIndex, value) {
|
|
737
737
|
const titleLiteral = JSON.stringify(titleKeyword);
|
|
738
738
|
const valueLiteral = JSON.stringify(value);
|
|
739
|
-
const clicked = (await page.evaluate(`(() => {
|
|
740
|
-
const titleKeyword = ${titleLiteral};
|
|
741
|
-
const rowIndex = ${rowIndex};
|
|
742
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
743
|
-
function getList(title) {
|
|
744
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
745
|
-
const header = headers.find((el) => {
|
|
746
|
-
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
747
|
-
return h.includes(title);
|
|
748
|
-
});
|
|
749
|
-
if (!header) return null;
|
|
750
|
-
let node = header.nextElementSibling;
|
|
751
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
752
|
-
if (node.classList.contains("form-content-list")) return node;
|
|
753
|
-
node = node.nextElementSibling;
|
|
754
|
-
}
|
|
755
|
-
return null;
|
|
756
|
-
}
|
|
757
|
-
const list = getList(titleKeyword);
|
|
758
|
-
const row = list?.querySelectorAll(".form-content-list-item")[rowIndex];
|
|
759
|
-
if (!(row instanceof HTMLElement)) return false;
|
|
760
|
-
const target =
|
|
761
|
-
row.querySelector('[contenteditable="true"].auto-resize-textarea, [contenteditable="true"]') ||
|
|
762
|
-
row.querySelector(".form-content-list-item-content, .form-content-list-item-title") ||
|
|
763
|
-
row;
|
|
764
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
765
|
-
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
766
|
-
target.click();
|
|
767
|
-
return true;
|
|
739
|
+
const clicked = (await page.evaluate(`(() => {
|
|
740
|
+
const titleKeyword = ${titleLiteral};
|
|
741
|
+
const rowIndex = ${rowIndex};
|
|
742
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
743
|
+
function getList(title) {
|
|
744
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
745
|
+
const header = headers.find((el) => {
|
|
746
|
+
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
747
|
+
return h.includes(title);
|
|
748
|
+
});
|
|
749
|
+
if (!header) return null;
|
|
750
|
+
let node = header.nextElementSibling;
|
|
751
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
752
|
+
if (node.classList.contains("form-content-list")) return node;
|
|
753
|
+
node = node.nextElementSibling;
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
const list = getList(titleKeyword);
|
|
758
|
+
const row = list?.querySelectorAll(".form-content-list-item")[rowIndex];
|
|
759
|
+
if (!(row instanceof HTMLElement)) return false;
|
|
760
|
+
const target =
|
|
761
|
+
row.querySelector('[contenteditable="true"].auto-resize-textarea, [contenteditable="true"]') ||
|
|
762
|
+
row.querySelector(".form-content-list-item-content, .form-content-list-item-title") ||
|
|
763
|
+
row;
|
|
764
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
765
|
+
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
766
|
+
target.click();
|
|
767
|
+
return true;
|
|
768
768
|
})()`));
|
|
769
769
|
if (!clicked) {
|
|
770
770
|
return false;
|
|
@@ -778,136 +778,136 @@ async function fillRequirementGroupRow(page, titleKeyword, rowIndex, value) {
|
|
|
778
778
|
await page.keyboard.type(value, { delay: 12 });
|
|
779
779
|
await page.keyboard.press('Tab');
|
|
780
780
|
await sleepRandom(120, 220);
|
|
781
|
-
const typedShown = (await page.evaluate(`(() => {
|
|
782
|
-
const titleKeyword = ${titleLiteral};
|
|
783
|
-
const rowIndex = ${rowIndex};
|
|
784
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
785
|
-
function getList(title) {
|
|
786
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
787
|
-
const header = headers.find((el) => {
|
|
788
|
-
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
789
|
-
return h.includes(title);
|
|
790
|
-
});
|
|
791
|
-
if (!header) return null;
|
|
792
|
-
let node = header.nextElementSibling;
|
|
793
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
794
|
-
if (node.classList.contains("form-content-list")) return node;
|
|
795
|
-
node = node.nextElementSibling;
|
|
796
|
-
}
|
|
797
|
-
return null;
|
|
798
|
-
}
|
|
799
|
-
const list = getList(titleKeyword);
|
|
800
|
-
const row = list?.querySelectorAll(".form-content-list-item")[rowIndex];
|
|
801
|
-
if (!(row instanceof HTMLElement)) return "";
|
|
802
|
-
const word = row.querySelector(".form-content-word");
|
|
803
|
-
const editable = row.querySelector('[contenteditable="true"]');
|
|
804
|
-
const rowInput = row.querySelector("textarea, input");
|
|
805
|
-
if (word && norm(word.textContent)) return norm(word.textContent);
|
|
806
|
-
if (editable && norm(editable.textContent)) return norm(editable.textContent);
|
|
807
|
-
if (rowInput && norm(rowInput.value)) return norm(rowInput.value);
|
|
808
|
-
return norm(row.textContent);
|
|
781
|
+
const typedShown = (await page.evaluate(`(() => {
|
|
782
|
+
const titleKeyword = ${titleLiteral};
|
|
783
|
+
const rowIndex = ${rowIndex};
|
|
784
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
785
|
+
function getList(title) {
|
|
786
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
787
|
+
const header = headers.find((el) => {
|
|
788
|
+
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
789
|
+
return h.includes(title);
|
|
790
|
+
});
|
|
791
|
+
if (!header) return null;
|
|
792
|
+
let node = header.nextElementSibling;
|
|
793
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
794
|
+
if (node.classList.contains("form-content-list")) return node;
|
|
795
|
+
node = node.nextElementSibling;
|
|
796
|
+
}
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
const list = getList(titleKeyword);
|
|
800
|
+
const row = list?.querySelectorAll(".form-content-list-item")[rowIndex];
|
|
801
|
+
if (!(row instanceof HTMLElement)) return "";
|
|
802
|
+
const word = row.querySelector(".form-content-word");
|
|
803
|
+
const editable = row.querySelector('[contenteditable="true"]');
|
|
804
|
+
const rowInput = row.querySelector("textarea, input");
|
|
805
|
+
if (word && norm(word.textContent)) return norm(word.textContent);
|
|
806
|
+
if (editable && norm(editable.textContent)) return norm(editable.textContent);
|
|
807
|
+
if (rowInput && norm(rowInput.value)) return norm(rowInput.value);
|
|
808
|
+
return norm(row.textContent);
|
|
809
809
|
})()`));
|
|
810
810
|
if (normFormText(typedShown) === normFormText(value)) {
|
|
811
811
|
return true;
|
|
812
812
|
}
|
|
813
|
-
const shown = (await page.evaluate(`(() => {
|
|
814
|
-
const titleKeyword = ${titleLiteral};
|
|
815
|
-
const rowIndex = ${rowIndex};
|
|
816
|
-
const value = ${valueLiteral};
|
|
817
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
818
|
-
function getList(title) {
|
|
819
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
820
|
-
const header = headers.find((el) => {
|
|
821
|
-
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
822
|
-
return h.includes(title);
|
|
823
|
-
});
|
|
824
|
-
if (!header) return null;
|
|
825
|
-
let node = header.nextElementSibling;
|
|
826
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
827
|
-
if (node.classList.contains("form-content-list")) return node;
|
|
828
|
-
node = node.nextElementSibling;
|
|
829
|
-
}
|
|
830
|
-
return null;
|
|
831
|
-
}
|
|
832
|
-
function setNativeValue(el, val) {
|
|
833
|
-
const tracker = el._valueTracker;
|
|
834
|
-
if (tracker && typeof tracker.setValue === "function") tracker.setValue("");
|
|
835
|
-
const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
836
|
-
const desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
837
|
-
if (desc?.set) desc.set.call(el, val);
|
|
838
|
-
else el.value = val;
|
|
839
|
-
}
|
|
840
|
-
function fire(el) {
|
|
841
|
-
try {
|
|
842
|
-
el.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true, data: value, inputType: "insertText" }));
|
|
843
|
-
} catch {
|
|
844
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
845
|
-
}
|
|
846
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
847
|
-
}
|
|
848
|
-
const list = getList(titleKeyword);
|
|
849
|
-
const row = list?.querySelectorAll(".form-content-list-item")[rowIndex];
|
|
850
|
-
if (!(row instanceof HTMLElement)) return "";
|
|
851
|
-
const editable = row.querySelector('[contenteditable="true"]');
|
|
852
|
-
let input = row.querySelector("textarea, input");
|
|
853
|
-
if (input instanceof HTMLInputElement && (input.type === "hidden" || input.type === "button" || input.type === "submit")) {
|
|
854
|
-
input = null;
|
|
855
|
-
}
|
|
856
|
-
if (editable instanceof HTMLElement) {
|
|
857
|
-
editable.focus();
|
|
858
|
-
editable.textContent = value;
|
|
859
|
-
fire(editable);
|
|
860
|
-
editable.blur();
|
|
861
|
-
} else if (input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement) {
|
|
862
|
-
input.focus();
|
|
863
|
-
setNativeValue(input, value);
|
|
864
|
-
fire(input);
|
|
865
|
-
input.blur();
|
|
866
|
-
} else {
|
|
867
|
-
const title = row.querySelector(".form-content-list-item-title");
|
|
868
|
-
if (!(title instanceof HTMLElement)) return "";
|
|
869
|
-
let word = title.querySelector(".form-content-word");
|
|
870
|
-
if (!(word instanceof HTMLElement)) {
|
|
871
|
-
word = document.createElement("div");
|
|
872
|
-
word.className = "form-content-word";
|
|
873
|
-
title.appendChild(word);
|
|
874
|
-
}
|
|
875
|
-
word.textContent = value;
|
|
876
|
-
title.classList.remove("error");
|
|
877
|
-
fire(word);
|
|
878
|
-
fire(title);
|
|
879
|
-
}
|
|
880
|
-
fire(row);
|
|
881
|
-
fire(list);
|
|
882
|
-
const word = row.querySelector(".form-content-word");
|
|
883
|
-
const shownEditable = row.querySelector('[contenteditable="true"]');
|
|
884
|
-
const rowInput = row.querySelector("textarea, input");
|
|
885
|
-
if (word && norm(word.textContent)) return norm(word.textContent);
|
|
886
|
-
if (shownEditable && norm(shownEditable.textContent)) return norm(shownEditable.textContent);
|
|
887
|
-
if (rowInput && norm(rowInput.value)) return norm(rowInput.value);
|
|
888
|
-
return norm(row.textContent);
|
|
813
|
+
const shown = (await page.evaluate(`(() => {
|
|
814
|
+
const titleKeyword = ${titleLiteral};
|
|
815
|
+
const rowIndex = ${rowIndex};
|
|
816
|
+
const value = ${valueLiteral};
|
|
817
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
818
|
+
function getList(title) {
|
|
819
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
820
|
+
const header = headers.find((el) => {
|
|
821
|
+
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
822
|
+
return h.includes(title);
|
|
823
|
+
});
|
|
824
|
+
if (!header) return null;
|
|
825
|
+
let node = header.nextElementSibling;
|
|
826
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
827
|
+
if (node.classList.contains("form-content-list")) return node;
|
|
828
|
+
node = node.nextElementSibling;
|
|
829
|
+
}
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
function setNativeValue(el, val) {
|
|
833
|
+
const tracker = el._valueTracker;
|
|
834
|
+
if (tracker && typeof tracker.setValue === "function") tracker.setValue("");
|
|
835
|
+
const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
836
|
+
const desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
837
|
+
if (desc?.set) desc.set.call(el, val);
|
|
838
|
+
else el.value = val;
|
|
839
|
+
}
|
|
840
|
+
function fire(el) {
|
|
841
|
+
try {
|
|
842
|
+
el.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true, data: value, inputType: "insertText" }));
|
|
843
|
+
} catch {
|
|
844
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
845
|
+
}
|
|
846
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
847
|
+
}
|
|
848
|
+
const list = getList(titleKeyword);
|
|
849
|
+
const row = list?.querySelectorAll(".form-content-list-item")[rowIndex];
|
|
850
|
+
if (!(row instanceof HTMLElement)) return "";
|
|
851
|
+
const editable = row.querySelector('[contenteditable="true"]');
|
|
852
|
+
let input = row.querySelector("textarea, input");
|
|
853
|
+
if (input instanceof HTMLInputElement && (input.type === "hidden" || input.type === "button" || input.type === "submit")) {
|
|
854
|
+
input = null;
|
|
855
|
+
}
|
|
856
|
+
if (editable instanceof HTMLElement) {
|
|
857
|
+
editable.focus();
|
|
858
|
+
editable.textContent = value;
|
|
859
|
+
fire(editable);
|
|
860
|
+
editable.blur();
|
|
861
|
+
} else if (input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement) {
|
|
862
|
+
input.focus();
|
|
863
|
+
setNativeValue(input, value);
|
|
864
|
+
fire(input);
|
|
865
|
+
input.blur();
|
|
866
|
+
} else {
|
|
867
|
+
const title = row.querySelector(".form-content-list-item-title");
|
|
868
|
+
if (!(title instanceof HTMLElement)) return "";
|
|
869
|
+
let word = title.querySelector(".form-content-word");
|
|
870
|
+
if (!(word instanceof HTMLElement)) {
|
|
871
|
+
word = document.createElement("div");
|
|
872
|
+
word.className = "form-content-word";
|
|
873
|
+
title.appendChild(word);
|
|
874
|
+
}
|
|
875
|
+
word.textContent = value;
|
|
876
|
+
title.classList.remove("error");
|
|
877
|
+
fire(word);
|
|
878
|
+
fire(title);
|
|
879
|
+
}
|
|
880
|
+
fire(row);
|
|
881
|
+
fire(list);
|
|
882
|
+
const word = row.querySelector(".form-content-word");
|
|
883
|
+
const shownEditable = row.querySelector('[contenteditable="true"]');
|
|
884
|
+
const rowInput = row.querySelector("textarea, input");
|
|
885
|
+
if (word && norm(word.textContent)) return norm(word.textContent);
|
|
886
|
+
if (shownEditable && norm(shownEditable.textContent)) return norm(shownEditable.textContent);
|
|
887
|
+
if (rowInput && norm(rowInput.value)) return norm(rowInput.value);
|
|
888
|
+
return norm(row.textContent);
|
|
889
889
|
})()`));
|
|
890
890
|
return normFormText(shown) === normFormText(value);
|
|
891
891
|
}
|
|
892
892
|
async function readRequirementGroupRowCount(page, titleKeyword) {
|
|
893
893
|
const titleLiteral = JSON.stringify(titleKeyword);
|
|
894
|
-
return (await page.evaluate(`(() => {
|
|
895
|
-
const titleKeyword = ${titleLiteral};
|
|
896
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
897
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
898
|
-
const header = headers.find((el) => {
|
|
899
|
-
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
900
|
-
return h.includes(titleKeyword);
|
|
901
|
-
});
|
|
902
|
-
if (!header) return -1;
|
|
903
|
-
let node = header.nextElementSibling;
|
|
904
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
905
|
-
if (node.classList.contains("form-content-list")) {
|
|
906
|
-
return node.querySelectorAll(".form-content-list-item").length;
|
|
907
|
-
}
|
|
908
|
-
node = node.nextElementSibling;
|
|
909
|
-
}
|
|
910
|
-
return -1;
|
|
894
|
+
return (await page.evaluate(`(() => {
|
|
895
|
+
const titleKeyword = ${titleLiteral};
|
|
896
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
897
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
898
|
+
const header = headers.find((el) => {
|
|
899
|
+
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
900
|
+
return h.includes(titleKeyword);
|
|
901
|
+
});
|
|
902
|
+
if (!header) return -1;
|
|
903
|
+
let node = header.nextElementSibling;
|
|
904
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
905
|
+
if (node.classList.contains("form-content-list")) {
|
|
906
|
+
return node.querySelectorAll(".form-content-list-item").length;
|
|
907
|
+
}
|
|
908
|
+
node = node.nextElementSibling;
|
|
909
|
+
}
|
|
910
|
+
return -1;
|
|
911
911
|
})()`));
|
|
912
912
|
}
|
|
913
913
|
async function removeRequirementGroupRowsAfter(page, titleKeyword, desiredCount) {
|
|
@@ -917,61 +917,61 @@ async function removeRequirementGroupRowsAfter(page, titleKeyword, desiredCount)
|
|
|
917
917
|
throw new Error(`未找到「${titleKeyword}」分组。`);
|
|
918
918
|
}
|
|
919
919
|
const targetIndex = count - 1;
|
|
920
|
-
const pos = (await page.evaluate(`(() => {
|
|
921
|
-
const titleKeyword = ${titleLiteral};
|
|
922
|
-
const targetIndex = ${targetIndex};
|
|
923
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
924
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
925
|
-
const header = headers.find((el) => {
|
|
926
|
-
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
927
|
-
return h.includes(titleKeyword);
|
|
928
|
-
});
|
|
929
|
-
if (!header) return null;
|
|
930
|
-
let node = header.nextElementSibling;
|
|
931
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
932
|
-
if (node.classList.contains("form-content-list")) {
|
|
933
|
-
const row = node.querySelectorAll(".form-content-list-item")[targetIndex];
|
|
934
|
-
const btn = row?.querySelector(".form-content-list-item-btn");
|
|
935
|
-
if (!(btn instanceof HTMLElement)) return null;
|
|
936
|
-
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
937
|
-
const rect = btn.getBoundingClientRect();
|
|
938
|
-
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
939
|
-
}
|
|
940
|
-
node = node.nextElementSibling;
|
|
941
|
-
}
|
|
942
|
-
return null;
|
|
920
|
+
const pos = (await page.evaluate(`(() => {
|
|
921
|
+
const titleKeyword = ${titleLiteral};
|
|
922
|
+
const targetIndex = ${targetIndex};
|
|
923
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
924
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
925
|
+
const header = headers.find((el) => {
|
|
926
|
+
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
927
|
+
return h.includes(titleKeyword);
|
|
928
|
+
});
|
|
929
|
+
if (!header) return null;
|
|
930
|
+
let node = header.nextElementSibling;
|
|
931
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
932
|
+
if (node.classList.contains("form-content-list")) {
|
|
933
|
+
const row = node.querySelectorAll(".form-content-list-item")[targetIndex];
|
|
934
|
+
const btn = row?.querySelector(".form-content-list-item-btn");
|
|
935
|
+
if (!(btn instanceof HTMLElement)) return null;
|
|
936
|
+
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
937
|
+
const rect = btn.getBoundingClientRect();
|
|
938
|
+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
939
|
+
}
|
|
940
|
+
node = node.nextElementSibling;
|
|
941
|
+
}
|
|
942
|
+
return null;
|
|
943
943
|
})()`));
|
|
944
944
|
if (!pos) {
|
|
945
945
|
throw new Error(`「${titleKeyword}」第 ${targetIndex + 1} 条条件未找到删除按钮。`);
|
|
946
946
|
}
|
|
947
947
|
await page.mouse.click(pos.x, pos.y);
|
|
948
|
-
await page.waitForFunction(`(() => {
|
|
949
|
-
const titleKeyword = ${titleLiteral};
|
|
950
|
-
const expectedCount = ${targetIndex};
|
|
951
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
952
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
953
|
-
const header = headers.find((el) => {
|
|
954
|
-
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
955
|
-
return h.includes(titleKeyword);
|
|
956
|
-
});
|
|
957
|
-
if (!header) return false;
|
|
958
|
-
let node = header.nextElementSibling;
|
|
959
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
960
|
-
if (node.classList.contains("form-content-list")) {
|
|
961
|
-
const items = Array.from(node.querySelectorAll(".form-content-list-item"));
|
|
962
|
-
if (items.length === expectedCount) return true;
|
|
963
|
-
if (expectedCount === 0 && items.length === 1) {
|
|
964
|
-
const item = items[0];
|
|
965
|
-
const word = norm(item.querySelector(".form-content-word")?.textContent);
|
|
966
|
-
const input = norm(item.querySelector("input, textarea")?.value);
|
|
967
|
-
const editable = norm(item.querySelector('[contenteditable="true"]')?.textContent);
|
|
968
|
-
return !word && !input && !editable;
|
|
969
|
-
}
|
|
970
|
-
return false;
|
|
971
|
-
}
|
|
972
|
-
node = node.nextElementSibling;
|
|
973
|
-
}
|
|
974
|
-
return false;
|
|
948
|
+
await page.waitForFunction(`(() => {
|
|
949
|
+
const titleKeyword = ${titleLiteral};
|
|
950
|
+
const expectedCount = ${targetIndex};
|
|
951
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
952
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
953
|
+
const header = headers.find((el) => {
|
|
954
|
+
const h = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
955
|
+
return h.includes(titleKeyword);
|
|
956
|
+
});
|
|
957
|
+
if (!header) return false;
|
|
958
|
+
let node = header.nextElementSibling;
|
|
959
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
960
|
+
if (node.classList.contains("form-content-list")) {
|
|
961
|
+
const items = Array.from(node.querySelectorAll(".form-content-list-item"));
|
|
962
|
+
if (items.length === expectedCount) return true;
|
|
963
|
+
if (expectedCount === 0 && items.length === 1) {
|
|
964
|
+
const item = items[0];
|
|
965
|
+
const word = norm(item.querySelector(".form-content-word")?.textContent);
|
|
966
|
+
const input = norm(item.querySelector("input, textarea")?.value);
|
|
967
|
+
const editable = norm(item.querySelector('[contenteditable="true"]')?.textContent);
|
|
968
|
+
return !word && !input && !editable;
|
|
969
|
+
}
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
node = node.nextElementSibling;
|
|
973
|
+
}
|
|
974
|
+
return false;
|
|
975
975
|
})()`, { timeout: 1800 });
|
|
976
976
|
}
|
|
977
977
|
}
|
|
@@ -1000,108 +1000,108 @@ async function applyAiFormRequirementLists(page, opts) {
|
|
|
1000
1000
|
}
|
|
1001
1001
|
}
|
|
1002
1002
|
async function readSearchFormSnapshot(page) {
|
|
1003
|
-
return (await page.evaluate(`(() => {
|
|
1004
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1005
|
-
function parseCount(text) {
|
|
1006
|
-
const m = String(text || "").match(/\\d+/);
|
|
1007
|
-
return m ? Number(m[0]) : null;
|
|
1008
|
-
}
|
|
1009
|
-
function getGroupList(titleKeyword) {
|
|
1010
|
-
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
1011
|
-
const header = headers.find((el) => {
|
|
1012
|
-
const title = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
1013
|
-
return title.includes(titleKeyword);
|
|
1014
|
-
});
|
|
1015
|
-
if (!header) return null;
|
|
1016
|
-
let node = header.nextElementSibling;
|
|
1017
|
-
while (node && !node.classList.contains("form-content-header")) {
|
|
1018
|
-
if (node.classList.contains("form-content-list")) return node;
|
|
1019
|
-
node = node.nextElementSibling;
|
|
1020
|
-
}
|
|
1021
|
-
return null;
|
|
1022
|
-
}
|
|
1023
|
-
function itemLineText(item, groupTitle) {
|
|
1024
|
-
const word = item.querySelector(".form-content-word");
|
|
1025
|
-
const w = word ? norm(word.textContent) : "";
|
|
1026
|
-
if (w) return w;
|
|
1027
|
-
const inp = item.querySelector("input, textarea");
|
|
1028
|
-
if (inp && norm(inp.value)) return norm(inp.value);
|
|
1029
|
-
const ce = item.querySelector("[contenteditable='true']");
|
|
1030
|
-
if (ce) return norm(ce.textContent);
|
|
1031
|
-
const titleEl = item.querySelector(".form-content-list-item-title");
|
|
1032
|
-
const title = titleEl ? norm(titleEl.textContent) : "";
|
|
1033
|
-
if (!title) return "";
|
|
1034
|
-
if (title.includes("请至少输入") || title.includes("可输入")) return "";
|
|
1035
|
-
if (groupTitle && title.includes(groupTitle)) return "";
|
|
1036
|
-
return title;
|
|
1037
|
-
}
|
|
1038
|
-
function readGroup(titleKeyword) {
|
|
1039
|
-
const list = getGroupList(titleKeyword);
|
|
1040
|
-
if (!list) return [];
|
|
1041
|
-
return Array.from(list.querySelectorAll(".form-content-list-item"))
|
|
1042
|
-
.map((item) => itemLineText(item, titleKeyword))
|
|
1043
|
-
.filter(Boolean);
|
|
1044
|
-
}
|
|
1045
|
-
const selectedJob = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
1046
|
-
const remainingCountText = norm(document.querySelector(".ai-form-match-footer-text-count")?.textContent);
|
|
1047
|
-
const btn = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
1048
|
-
const btnText = norm(btn?.textContent);
|
|
1049
|
-
let matchButtonDisabled = true;
|
|
1050
|
-
if (btn instanceof HTMLElement) {
|
|
1051
|
-
const cls = String(btn.className || "");
|
|
1052
|
-
const style = getComputedStyle(btn);
|
|
1053
|
-
matchButtonDisabled =
|
|
1054
|
-
btn.getAttribute("disabled") !== null ||
|
|
1055
|
-
/disabled|forbid|ban/i.test(cls) ||
|
|
1056
|
-
style.pointerEvents === "none" ||
|
|
1057
|
-
Number(style.opacity) < 0.3;
|
|
1058
|
-
}
|
|
1059
|
-
return {
|
|
1060
|
-
selectedJob,
|
|
1061
|
-
coreRequirements: readGroup("核心要求"),
|
|
1062
|
-
bonusRequirements: readGroup("加分项"),
|
|
1063
|
-
remainingCountText,
|
|
1064
|
-
remainingCount: parseCount(remainingCountText),
|
|
1065
|
-
matchButtonText: btnText || "未知",
|
|
1066
|
-
matchButtonDisabled,
|
|
1067
|
-
};
|
|
1003
|
+
return (await page.evaluate(`(() => {
|
|
1004
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1005
|
+
function parseCount(text) {
|
|
1006
|
+
const m = String(text || "").match(/\\d+/);
|
|
1007
|
+
return m ? Number(m[0]) : null;
|
|
1008
|
+
}
|
|
1009
|
+
function getGroupList(titleKeyword) {
|
|
1010
|
+
const headers = Array.from(document.querySelectorAll(".form-content-header"));
|
|
1011
|
+
const header = headers.find((el) => {
|
|
1012
|
+
const title = norm(el.querySelector(".form-content-title-h3")?.textContent);
|
|
1013
|
+
return title.includes(titleKeyword);
|
|
1014
|
+
});
|
|
1015
|
+
if (!header) return null;
|
|
1016
|
+
let node = header.nextElementSibling;
|
|
1017
|
+
while (node && !node.classList.contains("form-content-header")) {
|
|
1018
|
+
if (node.classList.contains("form-content-list")) return node;
|
|
1019
|
+
node = node.nextElementSibling;
|
|
1020
|
+
}
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
function itemLineText(item, groupTitle) {
|
|
1024
|
+
const word = item.querySelector(".form-content-word");
|
|
1025
|
+
const w = word ? norm(word.textContent) : "";
|
|
1026
|
+
if (w) return w;
|
|
1027
|
+
const inp = item.querySelector("input, textarea");
|
|
1028
|
+
if (inp && norm(inp.value)) return norm(inp.value);
|
|
1029
|
+
const ce = item.querySelector("[contenteditable='true']");
|
|
1030
|
+
if (ce) return norm(ce.textContent);
|
|
1031
|
+
const titleEl = item.querySelector(".form-content-list-item-title");
|
|
1032
|
+
const title = titleEl ? norm(titleEl.textContent) : "";
|
|
1033
|
+
if (!title) return "";
|
|
1034
|
+
if (title.includes("请至少输入") || title.includes("可输入")) return "";
|
|
1035
|
+
if (groupTitle && title.includes(groupTitle)) return "";
|
|
1036
|
+
return title;
|
|
1037
|
+
}
|
|
1038
|
+
function readGroup(titleKeyword) {
|
|
1039
|
+
const list = getGroupList(titleKeyword);
|
|
1040
|
+
if (!list) return [];
|
|
1041
|
+
return Array.from(list.querySelectorAll(".form-content-list-item"))
|
|
1042
|
+
.map((item) => itemLineText(item, titleKeyword))
|
|
1043
|
+
.filter(Boolean);
|
|
1044
|
+
}
|
|
1045
|
+
const selectedJob = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
1046
|
+
const remainingCountText = norm(document.querySelector(".ai-form-match-footer-text-count")?.textContent);
|
|
1047
|
+
const btn = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
1048
|
+
const btnText = norm(btn?.textContent);
|
|
1049
|
+
let matchButtonDisabled = true;
|
|
1050
|
+
if (btn instanceof HTMLElement) {
|
|
1051
|
+
const cls = String(btn.className || "");
|
|
1052
|
+
const style = getComputedStyle(btn);
|
|
1053
|
+
matchButtonDisabled =
|
|
1054
|
+
btn.getAttribute("disabled") !== null ||
|
|
1055
|
+
/disabled|forbid|ban/i.test(cls) ||
|
|
1056
|
+
style.pointerEvents === "none" ||
|
|
1057
|
+
Number(style.opacity) < 0.3;
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
selectedJob,
|
|
1061
|
+
coreRequirements: readGroup("核心要求"),
|
|
1062
|
+
bonusRequirements: readGroup("加分项"),
|
|
1063
|
+
remainingCountText,
|
|
1064
|
+
remainingCount: parseCount(remainingCountText),
|
|
1065
|
+
matchButtonText: btnText || "未知",
|
|
1066
|
+
matchButtonDisabled,
|
|
1067
|
+
};
|
|
1068
1068
|
})()`));
|
|
1069
1069
|
}
|
|
1070
1070
|
async function waitForAiFormJobDropdownReady(page) {
|
|
1071
|
-
await page.waitForFunction(`(() => {
|
|
1072
|
-
const input = Array.from(
|
|
1073
|
-
document.querySelectorAll(
|
|
1074
|
-
".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",
|
|
1075
|
-
),
|
|
1076
|
-
).find((el) => {
|
|
1077
|
-
if (!(el instanceof HTMLInputElement)) return false;
|
|
1078
|
-
const rect = el.getBoundingClientRect();
|
|
1079
|
-
const style = window.getComputedStyle(el);
|
|
1080
|
-
return rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden";
|
|
1081
|
-
});
|
|
1082
|
-
return !!input;
|
|
1071
|
+
await page.waitForFunction(`(() => {
|
|
1072
|
+
const input = Array.from(
|
|
1073
|
+
document.querySelectorAll(
|
|
1074
|
+
".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",
|
|
1075
|
+
),
|
|
1076
|
+
).find((el) => {
|
|
1077
|
+
if (!(el instanceof HTMLInputElement)) return false;
|
|
1078
|
+
const rect = el.getBoundingClientRect();
|
|
1079
|
+
const style = window.getComputedStyle(el);
|
|
1080
|
+
return rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden";
|
|
1081
|
+
});
|
|
1082
|
+
return !!input;
|
|
1083
1083
|
})()`, { timeout: 8_000 });
|
|
1084
1084
|
}
|
|
1085
1085
|
async function waitForAiFormJobSearchResults(page, keyword) {
|
|
1086
|
-
await page.waitForFunction(`((kw) => {
|
|
1087
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
|
|
1088
|
-
const rows = Array.from(
|
|
1089
|
-
document.querySelectorAll(
|
|
1090
|
-
".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",
|
|
1091
|
-
),
|
|
1092
|
-
);
|
|
1093
|
-
if (rows.length === 0) return false;
|
|
1094
|
-
return rows.some((el) => {
|
|
1095
|
-
const label = norm(el.querySelector(".job-option-text, .label")?.textContent || el.textContent || "");
|
|
1096
|
-
return label.includes(norm(kw));
|
|
1097
|
-
});
|
|
1086
|
+
await page.waitForFunction(`((kw) => {
|
|
1087
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
|
|
1088
|
+
const rows = Array.from(
|
|
1089
|
+
document.querySelectorAll(
|
|
1090
|
+
".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",
|
|
1091
|
+
),
|
|
1092
|
+
);
|
|
1093
|
+
if (rows.length === 0) return false;
|
|
1094
|
+
return rows.some((el) => {
|
|
1095
|
+
const label = norm(el.querySelector(".job-option-text, .label")?.textContent || el.textContent || "");
|
|
1096
|
+
return label.includes(norm(kw));
|
|
1097
|
+
});
|
|
1098
1098
|
})`, { timeout: 10_000 }, keyword);
|
|
1099
1099
|
}
|
|
1100
1100
|
async function waitForAiFormJobSelected(page, expectedLabel) {
|
|
1101
|
-
await page.waitForFunction(`((label) => {
|
|
1102
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1103
|
-
const selected = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
1104
|
-
return !!selected && selected === label;
|
|
1101
|
+
await page.waitForFunction(`((label) => {
|
|
1102
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1103
|
+
const selected = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
1104
|
+
return !!selected && selected === label;
|
|
1105
1105
|
})`, { timeout: 10_000 }, expectedLabel);
|
|
1106
1106
|
await ensureInDeepSearchPage(page);
|
|
1107
1107
|
}
|
|
@@ -1111,65 +1111,65 @@ export async function selectAiFormJob(page, keyword) {
|
|
|
1111
1111
|
throw new Error('岗位关键字不能为空。');
|
|
1112
1112
|
}
|
|
1113
1113
|
const kwLiteral = JSON.stringify(kw);
|
|
1114
|
-
const opened = (await page.evaluate(`(() => {
|
|
1115
|
-
const host = document.querySelector(".job-dropmenu-select");
|
|
1116
|
-
if (!(host instanceof HTMLElement)) return false;
|
|
1117
|
-
host.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1118
|
-
host.click();
|
|
1119
|
-
return true;
|
|
1114
|
+
const opened = (await page.evaluate(`(() => {
|
|
1115
|
+
const host = document.querySelector(".job-dropmenu-select");
|
|
1116
|
+
if (!(host instanceof HTMLElement)) return false;
|
|
1117
|
+
host.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1118
|
+
host.click();
|
|
1119
|
+
return true;
|
|
1120
1120
|
})()`));
|
|
1121
1121
|
if (!opened) {
|
|
1122
1122
|
throw new Error('未找到深度搜索页岗位下拉(.job-dropmenu-select)。');
|
|
1123
1123
|
}
|
|
1124
1124
|
await sleepRandom(JOB_SELECT_ACTION_GAP_MS.min, JOB_SELECT_ACTION_GAP_MS.max);
|
|
1125
1125
|
await waitForAiFormJobDropdownReady(page);
|
|
1126
|
-
const searched = (await page.evaluate(`(() => {
|
|
1127
|
-
const kw = ${kwLiteral};
|
|
1128
|
-
const inputs = Array.from(
|
|
1129
|
-
document.querySelectorAll(
|
|
1130
|
-
".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",
|
|
1131
|
-
),
|
|
1132
|
-
);
|
|
1133
|
-
const input = inputs.find((el) => {
|
|
1134
|
-
if (!(el instanceof HTMLInputElement)) return false;
|
|
1135
|
-
const r = el.getBoundingClientRect();
|
|
1136
|
-
return r.width > 0 && r.height > 0;
|
|
1137
|
-
});
|
|
1138
|
-
if (!input) return false;
|
|
1139
|
-
input.focus();
|
|
1140
|
-
input.value = kw;
|
|
1141
|
-
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1142
|
-
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1143
|
-
return true;
|
|
1126
|
+
const searched = (await page.evaluate(`(() => {
|
|
1127
|
+
const kw = ${kwLiteral};
|
|
1128
|
+
const inputs = Array.from(
|
|
1129
|
+
document.querySelectorAll(
|
|
1130
|
+
".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",
|
|
1131
|
+
),
|
|
1132
|
+
);
|
|
1133
|
+
const input = inputs.find((el) => {
|
|
1134
|
+
if (!(el instanceof HTMLInputElement)) return false;
|
|
1135
|
+
const r = el.getBoundingClientRect();
|
|
1136
|
+
return r.width > 0 && r.height > 0;
|
|
1137
|
+
});
|
|
1138
|
+
if (!input) return false;
|
|
1139
|
+
input.focus();
|
|
1140
|
+
input.value = kw;
|
|
1141
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1142
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1143
|
+
return true;
|
|
1144
1144
|
})()`));
|
|
1145
1145
|
if (searched) {
|
|
1146
1146
|
await sleepRandom(JOB_SEARCH_ACTION_GAP_MS.min, JOB_SEARCH_ACTION_GAP_MS.max);
|
|
1147
1147
|
await waitForAiFormJobSearchResults(page, kw);
|
|
1148
1148
|
}
|
|
1149
|
-
const picked = (await page.evaluate(`(() => {
|
|
1150
|
-
const kw = ${kwLiteral};
|
|
1151
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
|
|
1152
|
-
const rows = Array.from(
|
|
1153
|
-
document.querySelectorAll(
|
|
1154
|
-
".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",
|
|
1155
|
-
),
|
|
1156
|
-
);
|
|
1157
|
-
if (rows.length === 0) return { ok: false, reason: "empty" };
|
|
1158
|
-
const target = rows.find((el) => {
|
|
1159
|
-
const label = norm(
|
|
1160
|
-
el.querySelector(".job-option-text, .label")?.textContent || el.textContent || "",
|
|
1161
|
-
);
|
|
1162
|
-
return label.includes(norm(kw));
|
|
1163
|
-
});
|
|
1164
|
-
if (!(target instanceof HTMLElement)) return { ok: false, reason: "not_found" };
|
|
1165
|
-
const label = (
|
|
1166
|
-
target.querySelector(".job-option-text, .label")?.textContent ?? target.textContent ?? ""
|
|
1167
|
-
)
|
|
1168
|
-
.replace(/\\s+/g, " ")
|
|
1169
|
-
.trim();
|
|
1170
|
-
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1171
|
-
target.click();
|
|
1172
|
-
return { ok: true, label };
|
|
1149
|
+
const picked = (await page.evaluate(`(() => {
|
|
1150
|
+
const kw = ${kwLiteral};
|
|
1151
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
|
|
1152
|
+
const rows = Array.from(
|
|
1153
|
+
document.querySelectorAll(
|
|
1154
|
+
".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",
|
|
1155
|
+
),
|
|
1156
|
+
);
|
|
1157
|
+
if (rows.length === 0) return { ok: false, reason: "empty" };
|
|
1158
|
+
const target = rows.find((el) => {
|
|
1159
|
+
const label = norm(
|
|
1160
|
+
el.querySelector(".job-option-text, .label")?.textContent || el.textContent || "",
|
|
1161
|
+
);
|
|
1162
|
+
return label.includes(norm(kw));
|
|
1163
|
+
});
|
|
1164
|
+
if (!(target instanceof HTMLElement)) return { ok: false, reason: "not_found" };
|
|
1165
|
+
const label = (
|
|
1166
|
+
target.querySelector(".job-option-text, .label")?.textContent ?? target.textContent ?? ""
|
|
1167
|
+
)
|
|
1168
|
+
.replace(/\\s+/g, " ")
|
|
1169
|
+
.trim();
|
|
1170
|
+
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1171
|
+
target.click();
|
|
1172
|
+
return { ok: true, label };
|
|
1173
1173
|
})()`));
|
|
1174
1174
|
if (!picked.ok) {
|
|
1175
1175
|
throw new Error(`未找到匹配岗位「${kw}」。`);
|
|
@@ -1181,11 +1181,11 @@ export async function selectAiFormJob(page, keyword) {
|
|
|
1181
1181
|
}
|
|
1182
1182
|
/** 深度搜索页当前选中的岗位文案(无则「默认」) */
|
|
1183
1183
|
export async function readAiFormSelectedJobLabel(page) {
|
|
1184
|
-
return (await page.evaluate(`(() => {
|
|
1185
|
-
const t = (document.querySelector(".job-dropmenu-select .job-main-text")?.textContent ?? "")
|
|
1186
|
-
.replace(/\\s+/g, " ")
|
|
1187
|
-
.trim();
|
|
1188
|
-
return t.length > 0 ? t : "默认";
|
|
1184
|
+
return (await page.evaluate(`(() => {
|
|
1185
|
+
const t = (document.querySelector(".job-dropmenu-select .job-main-text")?.textContent ?? "")
|
|
1186
|
+
.replace(/\\s+/g, " ")
|
|
1187
|
+
.trim();
|
|
1188
|
+
return t.length > 0 ? t : "默认";
|
|
1189
1189
|
})()`));
|
|
1190
1190
|
}
|
|
1191
1191
|
/**
|
|
@@ -1194,61 +1194,61 @@ export async function readAiFormSelectedJobLabel(page) {
|
|
|
1194
1194
|
export async function openDeepSearchResumePreview(page, target) {
|
|
1195
1195
|
const raw = target.trim();
|
|
1196
1196
|
const targetLiteral = JSON.stringify(raw);
|
|
1197
|
-
const opened = (await page.evaluate(`(() => {
|
|
1198
|
-
const raw = ${targetLiteral};
|
|
1199
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1200
|
-
const allCards = Array.from(
|
|
1201
|
-
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1202
|
-
);
|
|
1203
|
-
if (allCards.length === 0) return false;
|
|
1204
|
-
const cards = allCards.filter((item) => {
|
|
1205
|
-
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1206
|
-
return !chatLabel.includes("继续沟通");
|
|
1207
|
-
});
|
|
1208
|
-
if (cards.length === 0) return false;
|
|
1209
|
-
const targetCard =
|
|
1210
|
-
cards.find((item) => {
|
|
1211
|
-
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1212
|
-
return name === raw || name.includes(raw);
|
|
1213
|
-
}) ?? null;
|
|
1214
|
-
if (!targetCard) return false;
|
|
1215
|
-
|
|
1216
|
-
function tryOpen(el) {
|
|
1217
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
1218
|
-
if (el.classList.contains("disabled")) return false;
|
|
1219
|
-
const st = window.getComputedStyle(el);
|
|
1220
|
-
if (st.pointerEvents === "none" || Number(st.opacity) < 0.3) return false;
|
|
1221
|
-
el.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1222
|
-
el.click();
|
|
1223
|
-
return true;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
const nameEl = targetCard.querySelector(".geek-name");
|
|
1227
|
-
if (nameEl instanceof HTMLElement) {
|
|
1228
|
-
nameEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1229
|
-
nameEl.click();
|
|
1230
|
-
return true;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
const resumeOnline = targetCard.querySelector("a.resume-btn-online");
|
|
1234
|
-
if (tryOpen(resumeOnline)) return true;
|
|
1235
|
-
const hrefResume = targetCard.querySelector('a[href*="c-resume"], a[href*="frame/c-resume"]');
|
|
1236
|
-
if (tryOpen(hrefResume)) return true;
|
|
1237
|
-
|
|
1238
|
-
const links = Array.from(targetCard.querySelectorAll("a, button, .btn")).filter((node) => {
|
|
1239
|
-
const t = norm(node.textContent);
|
|
1240
|
-
return /在线简历|查看简历|简历预览|预览/.test(t);
|
|
1241
|
-
});
|
|
1242
|
-
if (links.length > 0 && tryOpen(links[0])) return true;
|
|
1243
|
-
|
|
1244
|
-
const geekInfo = targetCard.querySelector(".geek-info, .geek-card-main, .card-content");
|
|
1245
|
-
if (geekInfo instanceof HTMLElement) {
|
|
1246
|
-
geekInfo.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1247
|
-
geekInfo.click();
|
|
1248
|
-
return true;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
return false;
|
|
1197
|
+
const opened = (await page.evaluate(`(() => {
|
|
1198
|
+
const raw = ${targetLiteral};
|
|
1199
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1200
|
+
const allCards = Array.from(
|
|
1201
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1202
|
+
);
|
|
1203
|
+
if (allCards.length === 0) return false;
|
|
1204
|
+
const cards = allCards.filter((item) => {
|
|
1205
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1206
|
+
return !chatLabel.includes("继续沟通");
|
|
1207
|
+
});
|
|
1208
|
+
if (cards.length === 0) return false;
|
|
1209
|
+
const targetCard =
|
|
1210
|
+
cards.find((item) => {
|
|
1211
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1212
|
+
return name === raw || name.includes(raw);
|
|
1213
|
+
}) ?? null;
|
|
1214
|
+
if (!targetCard) return false;
|
|
1215
|
+
|
|
1216
|
+
function tryOpen(el) {
|
|
1217
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
1218
|
+
if (el.classList.contains("disabled")) return false;
|
|
1219
|
+
const st = window.getComputedStyle(el);
|
|
1220
|
+
if (st.pointerEvents === "none" || Number(st.opacity) < 0.3) return false;
|
|
1221
|
+
el.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1222
|
+
el.click();
|
|
1223
|
+
return true;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const nameEl = targetCard.querySelector(".geek-name");
|
|
1227
|
+
if (nameEl instanceof HTMLElement) {
|
|
1228
|
+
nameEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1229
|
+
nameEl.click();
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const resumeOnline = targetCard.querySelector("a.resume-btn-online");
|
|
1234
|
+
if (tryOpen(resumeOnline)) return true;
|
|
1235
|
+
const hrefResume = targetCard.querySelector('a[href*="c-resume"], a[href*="frame/c-resume"]');
|
|
1236
|
+
if (tryOpen(hrefResume)) return true;
|
|
1237
|
+
|
|
1238
|
+
const links = Array.from(targetCard.querySelectorAll("a, button, .btn")).filter((node) => {
|
|
1239
|
+
const t = norm(node.textContent);
|
|
1240
|
+
return /在线简历|查看简历|简历预览|预览/.test(t);
|
|
1241
|
+
});
|
|
1242
|
+
if (links.length > 0 && tryOpen(links[0])) return true;
|
|
1243
|
+
|
|
1244
|
+
const geekInfo = targetCard.querySelector(".geek-info, .geek-card-main, .card-content");
|
|
1245
|
+
if (geekInfo instanceof HTMLElement) {
|
|
1246
|
+
geekInfo.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1247
|
+
geekInfo.click();
|
|
1248
|
+
return true;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
return false;
|
|
1252
1252
|
})()`));
|
|
1253
1253
|
if (opened) {
|
|
1254
1254
|
await sleepRandom(RESUME_PREVIEW_OPEN_GAP_MS.min, RESUME_PREVIEW_OPEN_GAP_MS.max);
|
|
@@ -1256,32 +1256,32 @@ export async function openDeepSearchResumePreview(page, target) {
|
|
|
1256
1256
|
return opened;
|
|
1257
1257
|
}
|
|
1258
1258
|
export async function readDeepSearchGeekList(page) {
|
|
1259
|
-
return (await page.evaluate(`(() => {
|
|
1260
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1261
|
-
const items = Array.from(
|
|
1262
|
-
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1263
|
-
);
|
|
1264
|
-
return items
|
|
1265
|
-
.map((item) => {
|
|
1266
|
-
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1267
|
-
if (chatLabel.includes("继续沟通")) {
|
|
1268
|
-
return null;
|
|
1269
|
-
}
|
|
1270
|
-
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1271
|
-
const splits = Array.from(item.querySelectorAll(".geek-exp .split"))
|
|
1272
|
-
.map((el) => norm(el.getAttribute("title") || el.textContent || ""))
|
|
1273
|
-
.filter(Boolean);
|
|
1274
|
-
const meta = splits.join(" · ");
|
|
1275
|
-
const work = norm(item.querySelector(".geek-works span")?.textContent);
|
|
1276
|
-
const edu = norm(item.querySelector(".geek-edus span")?.textContent);
|
|
1277
|
-
const recEl = item.querySelector(".geek-recommend-text");
|
|
1278
|
-
let reason = "";
|
|
1279
|
-
if (recEl) {
|
|
1280
|
-
reason = norm(recEl.textContent).replace(/^推荐理由\\s*/, "").trim();
|
|
1281
|
-
}
|
|
1282
|
-
return { name, meta, work, edu, reason };
|
|
1283
|
-
})
|
|
1284
|
-
.filter((x) => x !== null);
|
|
1259
|
+
return (await page.evaluate(`(() => {
|
|
1260
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1261
|
+
const items = Array.from(
|
|
1262
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1263
|
+
);
|
|
1264
|
+
return items
|
|
1265
|
+
.map((item) => {
|
|
1266
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1267
|
+
if (chatLabel.includes("继续沟通")) {
|
|
1268
|
+
return null;
|
|
1269
|
+
}
|
|
1270
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1271
|
+
const splits = Array.from(item.querySelectorAll(".geek-exp .split"))
|
|
1272
|
+
.map((el) => norm(el.getAttribute("title") || el.textContent || ""))
|
|
1273
|
+
.filter(Boolean);
|
|
1274
|
+
const meta = splits.join(" · ");
|
|
1275
|
+
const work = norm(item.querySelector(".geek-works span")?.textContent);
|
|
1276
|
+
const edu = norm(item.querySelector(".geek-edus span")?.textContent);
|
|
1277
|
+
const recEl = item.querySelector(".geek-recommend-text");
|
|
1278
|
+
let reason = "";
|
|
1279
|
+
if (recEl) {
|
|
1280
|
+
reason = norm(recEl.textContent).replace(/^推荐理由\\s*/, "").trim();
|
|
1281
|
+
}
|
|
1282
|
+
return { name, meta, work, edu, reason };
|
|
1283
|
+
})
|
|
1284
|
+
.filter((x) => x !== null);
|
|
1285
1285
|
})()`));
|
|
1286
1286
|
}
|
|
1287
1287
|
export function renderGeekListSection(title, items) {
|
|
@@ -1307,51 +1307,51 @@ export function renderGeekListSection(title, items) {
|
|
|
1307
1307
|
}
|
|
1308
1308
|
export async function clickGreetDeepSearch(page, target) {
|
|
1309
1309
|
const targetLiteral = JSON.stringify(target.trim());
|
|
1310
|
-
const result = (await page.evaluate(`(() => {
|
|
1311
|
-
const raw = ${targetLiteral};
|
|
1312
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1313
|
-
const allCards = Array.from(
|
|
1314
|
-
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1315
|
-
);
|
|
1316
|
-
if (allCards.length === 0) {
|
|
1317
|
-
return { kind: "empty" };
|
|
1318
|
-
}
|
|
1319
|
-
const cards = allCards.filter((item) => {
|
|
1320
|
-
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1321
|
-
return !chatLabel.includes("继续沟通");
|
|
1322
|
-
});
|
|
1323
|
-
if (cards.length === 0) {
|
|
1324
|
-
return { kind: "all_continue" };
|
|
1325
|
-
}
|
|
1326
|
-
const targetCard =
|
|
1327
|
-
cards.find((item) => {
|
|
1328
|
-
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1329
|
-
return name === raw || name.includes(raw);
|
|
1330
|
-
}) ?? null;
|
|
1331
|
-
if (!targetCard) {
|
|
1332
|
-
return { kind: "not_found", target: raw };
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
const name = norm(targetCard.querySelector(".geek-name")?.textContent);
|
|
1336
|
-
const btn =
|
|
1337
|
-
targetCard.querySelector(".geek-chat .btn-ai-v2") ||
|
|
1338
|
-
targetCard.querySelector(".geek-chat span.btn-ai-v2") ||
|
|
1339
|
-
targetCard.querySelector(".geek-chat span[class*='btn-ai']");
|
|
1340
|
-
if (!(btn instanceof HTMLElement)) {
|
|
1341
|
-
return { kind: "no_btn", name };
|
|
1342
|
-
}
|
|
1343
|
-
const label = norm(btn.textContent);
|
|
1344
|
-
if (!label.includes("打招呼")) {
|
|
1345
|
-
return { kind: "not_greet", name, label };
|
|
1346
|
-
}
|
|
1347
|
-
const cls = btn.className ?? "";
|
|
1348
|
-
const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
|
|
1349
|
-
if (disabled) {
|
|
1350
|
-
return { kind: "disabled", name };
|
|
1351
|
-
}
|
|
1352
|
-
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1353
|
-
btn.click();
|
|
1354
|
-
return { kind: "clicked", name };
|
|
1310
|
+
const result = (await page.evaluate(`(() => {
|
|
1311
|
+
const raw = ${targetLiteral};
|
|
1312
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1313
|
+
const allCards = Array.from(
|
|
1314
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1315
|
+
);
|
|
1316
|
+
if (allCards.length === 0) {
|
|
1317
|
+
return { kind: "empty" };
|
|
1318
|
+
}
|
|
1319
|
+
const cards = allCards.filter((item) => {
|
|
1320
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1321
|
+
return !chatLabel.includes("继续沟通");
|
|
1322
|
+
});
|
|
1323
|
+
if (cards.length === 0) {
|
|
1324
|
+
return { kind: "all_continue" };
|
|
1325
|
+
}
|
|
1326
|
+
const targetCard =
|
|
1327
|
+
cards.find((item) => {
|
|
1328
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1329
|
+
return name === raw || name.includes(raw);
|
|
1330
|
+
}) ?? null;
|
|
1331
|
+
if (!targetCard) {
|
|
1332
|
+
return { kind: "not_found", target: raw };
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
const name = norm(targetCard.querySelector(".geek-name")?.textContent);
|
|
1336
|
+
const btn =
|
|
1337
|
+
targetCard.querySelector(".geek-chat .btn-ai-v2") ||
|
|
1338
|
+
targetCard.querySelector(".geek-chat span.btn-ai-v2") ||
|
|
1339
|
+
targetCard.querySelector(".geek-chat span[class*='btn-ai']");
|
|
1340
|
+
if (!(btn instanceof HTMLElement)) {
|
|
1341
|
+
return { kind: "no_btn", name };
|
|
1342
|
+
}
|
|
1343
|
+
const label = norm(btn.textContent);
|
|
1344
|
+
if (!label.includes("打招呼")) {
|
|
1345
|
+
return { kind: "not_greet", name, label };
|
|
1346
|
+
}
|
|
1347
|
+
const cls = btn.className ?? "";
|
|
1348
|
+
const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
|
|
1349
|
+
if (disabled) {
|
|
1350
|
+
return { kind: "disabled", name };
|
|
1351
|
+
}
|
|
1352
|
+
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1353
|
+
btn.click();
|
|
1354
|
+
return { kind: "clicked", name };
|
|
1355
1355
|
})()`));
|
|
1356
1356
|
switch (result.kind) {
|
|
1357
1357
|
case 'empty':
|
|
@@ -1414,34 +1414,34 @@ async function triggerAiFormMatch(page) {
|
|
|
1414
1414
|
throw new Error(`今日匹配次数已用完:${before.remainingCountText || '0次'}`);
|
|
1415
1415
|
}
|
|
1416
1416
|
const beforeCountLiteral = JSON.stringify(before.remainingCountText);
|
|
1417
|
-
const clicked = (await page.evaluate(`(() => {
|
|
1418
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1419
|
-
const btn = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
1420
|
-
if (!(btn instanceof HTMLElement)) return { ok: false, reason: "missing_button" };
|
|
1421
|
-
const cls = String(btn.className || "");
|
|
1422
|
-
const style = getComputedStyle(btn);
|
|
1423
|
-
const disabled =
|
|
1424
|
-
btn.getAttribute("disabled") !== null ||
|
|
1425
|
-
/disabled|forbid|ban/i.test(cls) ||
|
|
1426
|
-
style.pointerEvents === "none" ||
|
|
1427
|
-
Number(style.opacity) < 0.3;
|
|
1428
|
-
if (disabled) return { ok: false, reason: "disabled", text: norm(btn.textContent) };
|
|
1429
|
-
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1430
|
-
btn.click();
|
|
1431
|
-
return { ok: true, text: norm(btn.textContent) };
|
|
1417
|
+
const clicked = (await page.evaluate(`(() => {
|
|
1418
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1419
|
+
const btn = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
1420
|
+
if (!(btn instanceof HTMLElement)) return { ok: false, reason: "missing_button" };
|
|
1421
|
+
const cls = String(btn.className || "");
|
|
1422
|
+
const style = getComputedStyle(btn);
|
|
1423
|
+
const disabled =
|
|
1424
|
+
btn.getAttribute("disabled") !== null ||
|
|
1425
|
+
/disabled|forbid|ban/i.test(cls) ||
|
|
1426
|
+
style.pointerEvents === "none" ||
|
|
1427
|
+
Number(style.opacity) < 0.3;
|
|
1428
|
+
if (disabled) return { ok: false, reason: "disabled", text: norm(btn.textContent) };
|
|
1429
|
+
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1430
|
+
btn.click();
|
|
1431
|
+
return { ok: true, text: norm(btn.textContent) };
|
|
1432
1432
|
})()`));
|
|
1433
1433
|
if (!clicked.ok) {
|
|
1434
1434
|
throw new Error(`无法点击「立即匹配」:${clicked.reason ?? 'unknown'}${clicked.text ? `(${clicked.text})` : ''}`);
|
|
1435
1435
|
}
|
|
1436
1436
|
await sleepRandom(900, 1400);
|
|
1437
|
-
await page.waitForFunction(`(() => {
|
|
1438
|
-
const beforeCount = ${beforeCountLiteral};
|
|
1439
|
-
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1440
|
-
const btnText = norm(document.querySelector(".ai-form-match-footer .btn-ai-match-v2")?.textContent);
|
|
1441
|
-
const countText = norm(document.querySelector(".ai-form-match-footer-text-count")?.textContent);
|
|
1442
|
-
const cards = document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item").length;
|
|
1443
|
-
const stillMatching = /停止匹配|匹配中|加载中|生成中/.test(btnText);
|
|
1444
|
-
return !stillMatching && cards > 0 && (!beforeCount || (countText && countText !== beforeCount));
|
|
1437
|
+
await page.waitForFunction(`(() => {
|
|
1438
|
+
const beforeCount = ${beforeCountLiteral};
|
|
1439
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1440
|
+
const btnText = norm(document.querySelector(".ai-form-match-footer .btn-ai-match-v2")?.textContent);
|
|
1441
|
+
const countText = norm(document.querySelector(".ai-form-match-footer-text-count")?.textContent);
|
|
1442
|
+
const cards = document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item").length;
|
|
1443
|
+
const stillMatching = /停止匹配|匹配中|加载中|生成中/.test(btnText);
|
|
1444
|
+
return !stillMatching && cards > 0 && (!beforeCount || (countText && countText !== beforeCount));
|
|
1445
1445
|
})()`, { timeout: 35_000 });
|
|
1446
1446
|
await ensureInDeepSearchPage(page);
|
|
1447
1447
|
}
|