@viyzhu/boss-cli-fork 0.7.3 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +119 -85
- package/CHANGELOG.md +57 -0
- package/LICENSE +674 -674
- package/README.md +346 -284
- package/dist/browser/cdp_browser.d.ts +170 -170
- package/dist/browser/cdp_browser.js +674 -674
- package/dist/cli/cliRouter.d.ts.map +1 -1
- package/dist/cli/cliRouter.js +119 -67
- package/dist/cli/cliRouter.js.map +1 -1
- 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.js +14 -14
- package/dist/common/boss_sidebar_nav.d.ts.map +1 -1
- package/dist/common/boss_sidebar_nav.js +38 -29
- package/dist/common/boss_sidebar_nav.js.map +1 -1
- package/dist/common/c_resume_capture.js +61 -61
- package/dist/toolset/action.d.ts.map +1 -1
- package/dist/toolset/action.js +238 -233
- package/dist/toolset/action.js.map +1 -1
- package/dist/toolset/chat.js +437 -437
- package/dist/toolset/deep-search.js +574 -572
- package/dist/toolset/deep-search.js.map +1 -1
- package/dist/toolset/index.d.ts +4 -1
- package/dist/toolset/index.d.ts.map +1 -1
- package/dist/toolset/index.js +3 -2
- package/dist/toolset/index.js.map +1 -1
- package/dist/toolset/jd.js +132 -132
- package/dist/toolset/list.js +48 -48
- package/dist/toolset/normal-search.d.ts +167 -1
- package/dist/toolset/normal-search.d.ts.map +1 -1
- package/dist/toolset/normal-search.js +1276 -165
- package/dist/toolset/normal-search.js.map +1 -1
- package/dist/toolset/recommend.js +211 -209
- package/dist/toolset/recommend.js.map +1 -1
- package/package.json +70 -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,109 +1000,111 @@ 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
|
-
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1086
|
+
// 关键词内联,不能走入参:字符串 pageFunction 收不到,判据恒为真、等待变空转。详见 AGENTS.md。
|
|
1087
|
+
await page.waitForFunction(`(() => {
|
|
1088
|
+
const kw = ${JSON.stringify(keyword)};
|
|
1089
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
|
|
1090
|
+
const rows = Array.from(
|
|
1091
|
+
document.querySelectorAll(
|
|
1092
|
+
".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",
|
|
1093
|
+
),
|
|
1094
|
+
);
|
|
1095
|
+
if (rows.length === 0) return false;
|
|
1096
|
+
return rows.some((el) => {
|
|
1097
|
+
const label = norm(el.querySelector(".job-option-text, .label")?.textContent || el.textContent || "");
|
|
1098
|
+
return label.includes(norm(kw));
|
|
1099
|
+
});
|
|
1100
|
+
})()`, { timeout: 10_000 });
|
|
1099
1101
|
}
|
|
1100
1102
|
async function waitForAiFormJobSelected(page, expectedLabel) {
|
|
1101
|
-
await page.waitForFunction(`((
|
|
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 ===
|
|
1105
|
-
})`, { timeout: 10_000 }
|
|
1103
|
+
await page.waitForFunction(`(() => {
|
|
1104
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1105
|
+
const selected = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
1106
|
+
return !!selected && selected === ${JSON.stringify(expectedLabel)};
|
|
1107
|
+
})()`, { timeout: 10_000 });
|
|
1106
1108
|
await ensureInDeepSearchPage(page);
|
|
1107
1109
|
}
|
|
1108
1110
|
export async function selectAiFormJob(page, keyword) {
|
|
@@ -1111,65 +1113,65 @@ export async function selectAiFormJob(page, keyword) {
|
|
|
1111
1113
|
throw new Error('岗位关键字不能为空。');
|
|
1112
1114
|
}
|
|
1113
1115
|
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;
|
|
1116
|
+
const opened = (await page.evaluate(`(() => {
|
|
1117
|
+
const host = document.querySelector(".job-dropmenu-select");
|
|
1118
|
+
if (!(host instanceof HTMLElement)) return false;
|
|
1119
|
+
host.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1120
|
+
host.click();
|
|
1121
|
+
return true;
|
|
1120
1122
|
})()`));
|
|
1121
1123
|
if (!opened) {
|
|
1122
1124
|
throw new Error('未找到深度搜索页岗位下拉(.job-dropmenu-select)。');
|
|
1123
1125
|
}
|
|
1124
1126
|
await sleepRandom(JOB_SELECT_ACTION_GAP_MS.min, JOB_SELECT_ACTION_GAP_MS.max);
|
|
1125
1127
|
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;
|
|
1128
|
+
const searched = (await page.evaluate(`(() => {
|
|
1129
|
+
const kw = ${kwLiteral};
|
|
1130
|
+
const inputs = Array.from(
|
|
1131
|
+
document.querySelectorAll(
|
|
1132
|
+
".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",
|
|
1133
|
+
),
|
|
1134
|
+
);
|
|
1135
|
+
const input = inputs.find((el) => {
|
|
1136
|
+
if (!(el instanceof HTMLInputElement)) return false;
|
|
1137
|
+
const r = el.getBoundingClientRect();
|
|
1138
|
+
return r.width > 0 && r.height > 0;
|
|
1139
|
+
});
|
|
1140
|
+
if (!input) return false;
|
|
1141
|
+
input.focus();
|
|
1142
|
+
input.value = kw;
|
|
1143
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1144
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1145
|
+
return true;
|
|
1144
1146
|
})()`));
|
|
1145
1147
|
if (searched) {
|
|
1146
1148
|
await sleepRandom(JOB_SEARCH_ACTION_GAP_MS.min, JOB_SEARCH_ACTION_GAP_MS.max);
|
|
1147
1149
|
await waitForAiFormJobSearchResults(page, kw);
|
|
1148
1150
|
}
|
|
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 };
|
|
1151
|
+
const picked = (await page.evaluate(`(() => {
|
|
1152
|
+
const kw = ${kwLiteral};
|
|
1153
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
|
|
1154
|
+
const rows = Array.from(
|
|
1155
|
+
document.querySelectorAll(
|
|
1156
|
+
".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",
|
|
1157
|
+
),
|
|
1158
|
+
);
|
|
1159
|
+
if (rows.length === 0) return { ok: false, reason: "empty" };
|
|
1160
|
+
const target = rows.find((el) => {
|
|
1161
|
+
const label = norm(
|
|
1162
|
+
el.querySelector(".job-option-text, .label")?.textContent || el.textContent || "",
|
|
1163
|
+
);
|
|
1164
|
+
return label.includes(norm(kw));
|
|
1165
|
+
});
|
|
1166
|
+
if (!(target instanceof HTMLElement)) return { ok: false, reason: "not_found" };
|
|
1167
|
+
const label = (
|
|
1168
|
+
target.querySelector(".job-option-text, .label")?.textContent ?? target.textContent ?? ""
|
|
1169
|
+
)
|
|
1170
|
+
.replace(/\\s+/g, " ")
|
|
1171
|
+
.trim();
|
|
1172
|
+
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1173
|
+
target.click();
|
|
1174
|
+
return { ok: true, label };
|
|
1173
1175
|
})()`));
|
|
1174
1176
|
if (!picked.ok) {
|
|
1175
1177
|
throw new Error(`未找到匹配岗位「${kw}」。`);
|
|
@@ -1181,11 +1183,11 @@ export async function selectAiFormJob(page, keyword) {
|
|
|
1181
1183
|
}
|
|
1182
1184
|
/** 深度搜索页当前选中的岗位文案(无则「默认」) */
|
|
1183
1185
|
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 : "默认";
|
|
1186
|
+
return (await page.evaluate(`(() => {
|
|
1187
|
+
const t = (document.querySelector(".job-dropmenu-select .job-main-text")?.textContent ?? "")
|
|
1188
|
+
.replace(/\\s+/g, " ")
|
|
1189
|
+
.trim();
|
|
1190
|
+
return t.length > 0 ? t : "默认";
|
|
1189
1191
|
})()`));
|
|
1190
1192
|
}
|
|
1191
1193
|
/**
|
|
@@ -1194,61 +1196,61 @@ export async function readAiFormSelectedJobLabel(page) {
|
|
|
1194
1196
|
export async function openDeepSearchResumePreview(page, target) {
|
|
1195
1197
|
const raw = target.trim();
|
|
1196
1198
|
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;
|
|
1199
|
+
const opened = (await page.evaluate(`(() => {
|
|
1200
|
+
const raw = ${targetLiteral};
|
|
1201
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1202
|
+
const allCards = Array.from(
|
|
1203
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1204
|
+
);
|
|
1205
|
+
if (allCards.length === 0) return false;
|
|
1206
|
+
const cards = allCards.filter((item) => {
|
|
1207
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1208
|
+
return !chatLabel.includes("继续沟通");
|
|
1209
|
+
});
|
|
1210
|
+
if (cards.length === 0) return false;
|
|
1211
|
+
const targetCard =
|
|
1212
|
+
cards.find((item) => {
|
|
1213
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1214
|
+
return name === raw || name.includes(raw);
|
|
1215
|
+
}) ?? null;
|
|
1216
|
+
if (!targetCard) return false;
|
|
1217
|
+
|
|
1218
|
+
function tryOpen(el) {
|
|
1219
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
1220
|
+
if (el.classList.contains("disabled")) return false;
|
|
1221
|
+
const st = window.getComputedStyle(el);
|
|
1222
|
+
if (st.pointerEvents === "none" || Number(st.opacity) < 0.3) return false;
|
|
1223
|
+
el.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1224
|
+
el.click();
|
|
1225
|
+
return true;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
const nameEl = targetCard.querySelector(".geek-name");
|
|
1229
|
+
if (nameEl instanceof HTMLElement) {
|
|
1230
|
+
nameEl.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1231
|
+
nameEl.click();
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const resumeOnline = targetCard.querySelector("a.resume-btn-online");
|
|
1236
|
+
if (tryOpen(resumeOnline)) return true;
|
|
1237
|
+
const hrefResume = targetCard.querySelector('a[href*="c-resume"], a[href*="frame/c-resume"]');
|
|
1238
|
+
if (tryOpen(hrefResume)) return true;
|
|
1239
|
+
|
|
1240
|
+
const links = Array.from(targetCard.querySelectorAll("a, button, .btn")).filter((node) => {
|
|
1241
|
+
const t = norm(node.textContent);
|
|
1242
|
+
return /在线简历|查看简历|简历预览|预览/.test(t);
|
|
1243
|
+
});
|
|
1244
|
+
if (links.length > 0 && tryOpen(links[0])) return true;
|
|
1245
|
+
|
|
1246
|
+
const geekInfo = targetCard.querySelector(".geek-info, .geek-card-main, .card-content");
|
|
1247
|
+
if (geekInfo instanceof HTMLElement) {
|
|
1248
|
+
geekInfo.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1249
|
+
geekInfo.click();
|
|
1250
|
+
return true;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
return false;
|
|
1252
1254
|
})()`));
|
|
1253
1255
|
if (opened) {
|
|
1254
1256
|
await sleepRandom(RESUME_PREVIEW_OPEN_GAP_MS.min, RESUME_PREVIEW_OPEN_GAP_MS.max);
|
|
@@ -1256,32 +1258,32 @@ export async function openDeepSearchResumePreview(page, target) {
|
|
|
1256
1258
|
return opened;
|
|
1257
1259
|
}
|
|
1258
1260
|
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);
|
|
1261
|
+
return (await page.evaluate(`(() => {
|
|
1262
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1263
|
+
const items = Array.from(
|
|
1264
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1265
|
+
);
|
|
1266
|
+
return items
|
|
1267
|
+
.map((item) => {
|
|
1268
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1269
|
+
if (chatLabel.includes("继续沟通")) {
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1273
|
+
const splits = Array.from(item.querySelectorAll(".geek-exp .split"))
|
|
1274
|
+
.map((el) => norm(el.getAttribute("title") || el.textContent || ""))
|
|
1275
|
+
.filter(Boolean);
|
|
1276
|
+
const meta = splits.join(" · ");
|
|
1277
|
+
const work = norm(item.querySelector(".geek-works span")?.textContent);
|
|
1278
|
+
const edu = norm(item.querySelector(".geek-edus span")?.textContent);
|
|
1279
|
+
const recEl = item.querySelector(".geek-recommend-text");
|
|
1280
|
+
let reason = "";
|
|
1281
|
+
if (recEl) {
|
|
1282
|
+
reason = norm(recEl.textContent).replace(/^推荐理由\\s*/, "").trim();
|
|
1283
|
+
}
|
|
1284
|
+
return { name, meta, work, edu, reason };
|
|
1285
|
+
})
|
|
1286
|
+
.filter((x) => x !== null);
|
|
1285
1287
|
})()`));
|
|
1286
1288
|
}
|
|
1287
1289
|
export function renderGeekListSection(title, items) {
|
|
@@ -1307,51 +1309,51 @@ export function renderGeekListSection(title, items) {
|
|
|
1307
1309
|
}
|
|
1308
1310
|
export async function clickGreetDeepSearch(page, target) {
|
|
1309
1311
|
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 };
|
|
1312
|
+
const result = (await page.evaluate(`(() => {
|
|
1313
|
+
const raw = ${targetLiteral};
|
|
1314
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1315
|
+
const allCards = Array.from(
|
|
1316
|
+
document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
|
|
1317
|
+
);
|
|
1318
|
+
if (allCards.length === 0) {
|
|
1319
|
+
return { kind: "empty" };
|
|
1320
|
+
}
|
|
1321
|
+
const cards = allCards.filter((item) => {
|
|
1322
|
+
const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
|
|
1323
|
+
return !chatLabel.includes("继续沟通");
|
|
1324
|
+
});
|
|
1325
|
+
if (cards.length === 0) {
|
|
1326
|
+
return { kind: "all_continue" };
|
|
1327
|
+
}
|
|
1328
|
+
const targetCard =
|
|
1329
|
+
cards.find((item) => {
|
|
1330
|
+
const name = norm(item.querySelector(".geek-name")?.textContent);
|
|
1331
|
+
return name === raw || name.includes(raw);
|
|
1332
|
+
}) ?? null;
|
|
1333
|
+
if (!targetCard) {
|
|
1334
|
+
return { kind: "not_found", target: raw };
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const name = norm(targetCard.querySelector(".geek-name")?.textContent);
|
|
1338
|
+
const btn =
|
|
1339
|
+
targetCard.querySelector(".geek-chat .btn-ai-v2") ||
|
|
1340
|
+
targetCard.querySelector(".geek-chat span.btn-ai-v2") ||
|
|
1341
|
+
targetCard.querySelector(".geek-chat span[class*='btn-ai']");
|
|
1342
|
+
if (!(btn instanceof HTMLElement)) {
|
|
1343
|
+
return { kind: "no_btn", name };
|
|
1344
|
+
}
|
|
1345
|
+
const label = norm(btn.textContent);
|
|
1346
|
+
if (!label.includes("打招呼")) {
|
|
1347
|
+
return { kind: "not_greet", name, label };
|
|
1348
|
+
}
|
|
1349
|
+
const cls = btn.className ?? "";
|
|
1350
|
+
const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
|
|
1351
|
+
if (disabled) {
|
|
1352
|
+
return { kind: "disabled", name };
|
|
1353
|
+
}
|
|
1354
|
+
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1355
|
+
btn.click();
|
|
1356
|
+
return { kind: "clicked", name };
|
|
1355
1357
|
})()`));
|
|
1356
1358
|
switch (result.kind) {
|
|
1357
1359
|
case 'empty':
|
|
@@ -1414,34 +1416,34 @@ async function triggerAiFormMatch(page) {
|
|
|
1414
1416
|
throw new Error(`今日匹配次数已用完:${before.remainingCountText || '0次'}`);
|
|
1415
1417
|
}
|
|
1416
1418
|
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) };
|
|
1419
|
+
const clicked = (await page.evaluate(`(() => {
|
|
1420
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1421
|
+
const btn = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
1422
|
+
if (!(btn instanceof HTMLElement)) return { ok: false, reason: "missing_button" };
|
|
1423
|
+
const cls = String(btn.className || "");
|
|
1424
|
+
const style = getComputedStyle(btn);
|
|
1425
|
+
const disabled =
|
|
1426
|
+
btn.getAttribute("disabled") !== null ||
|
|
1427
|
+
/disabled|forbid|ban/i.test(cls) ||
|
|
1428
|
+
style.pointerEvents === "none" ||
|
|
1429
|
+
Number(style.opacity) < 0.3;
|
|
1430
|
+
if (disabled) return { ok: false, reason: "disabled", text: norm(btn.textContent) };
|
|
1431
|
+
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1432
|
+
btn.click();
|
|
1433
|
+
return { ok: true, text: norm(btn.textContent) };
|
|
1432
1434
|
})()`));
|
|
1433
1435
|
if (!clicked.ok) {
|
|
1434
1436
|
throw new Error(`无法点击「立即匹配」:${clicked.reason ?? 'unknown'}${clicked.text ? `(${clicked.text})` : ''}`);
|
|
1435
1437
|
}
|
|
1436
1438
|
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));
|
|
1439
|
+
await page.waitForFunction(`(() => {
|
|
1440
|
+
const beforeCount = ${beforeCountLiteral};
|
|
1441
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
1442
|
+
const btnText = norm(document.querySelector(".ai-form-match-footer .btn-ai-match-v2")?.textContent);
|
|
1443
|
+
const countText = norm(document.querySelector(".ai-form-match-footer-text-count")?.textContent);
|
|
1444
|
+
const cards = document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item").length;
|
|
1445
|
+
const stillMatching = /停止匹配|匹配中|加载中|生成中/.test(btnText);
|
|
1446
|
+
return !stillMatching && cards > 0 && (!beforeCount || (countText && countText !== beforeCount));
|
|
1445
1447
|
})()`, { timeout: 35_000 });
|
|
1446
1448
|
await ensureInDeepSearchPage(page);
|
|
1447
1449
|
}
|