@viyzhu/boss-cli-fork 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  }