@joohw/boss-cli 0.4.0 → 0.4.1

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