@joohw/boss-cli 0.3.5 → 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 (113) hide show
  1. package/README.md +11 -14
  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 +10 -10
  8. package/dist/cli/cliRouter.js.map +1 -1
  9. package/dist/common/auth.js +49 -49
  10. package/dist/common/boss_paywall_popup.js +87 -87
  11. package/dist/common/boss_session_lock.d.ts +2 -0
  12. package/dist/common/boss_session_lock.d.ts.map +1 -0
  13. package/dist/common/boss_session_lock.js +127 -0
  14. package/dist/common/boss_session_lock.js.map +1 -0
  15. package/dist/common/boss_session_page.d.ts +2 -2
  16. package/dist/common/boss_session_page.d.ts.map +1 -1
  17. package/dist/common/boss_session_page.js +66 -74
  18. package/dist/common/boss_session_page.js.map +1 -1
  19. package/dist/common/boss_sidebar_nav.js +25 -26
  20. package/dist/common/boss_sidebar_nav.js.map +1 -1
  21. package/dist/common/c_resume_capture.d.ts +1 -0
  22. package/dist/common/c_resume_capture.d.ts.map +1 -1
  23. package/dist/common/c_resume_capture.js +69 -32
  24. package/dist/common/c_resume_capture.js.map +1 -1
  25. package/dist/ocr/resume_ocr.d.ts.map +1 -1
  26. package/dist/ocr/resume_ocr.js +4 -1
  27. package/dist/ocr/resume_ocr.js.map +1 -1
  28. package/dist/toolset/action.d.ts.map +1 -1
  29. package/dist/toolset/action.js +11 -7
  30. package/dist/toolset/action.js.map +1 -1
  31. package/dist/toolset/chat.d.ts.map +1 -1
  32. package/dist/toolset/chat.js +246 -230
  33. package/dist/toolset/chat.js.map +1 -1
  34. package/dist/toolset/deep-search.d.ts +1 -1
  35. package/dist/toolset/deep-search.d.ts.map +1 -1
  36. package/dist/toolset/deep-search.js +291 -281
  37. package/dist/toolset/deep-search.js.map +1 -1
  38. package/dist/toolset/greet.d.ts.map +1 -1
  39. package/dist/toolset/greet.js +2 -7
  40. package/dist/toolset/greet.js.map +1 -1
  41. package/dist/toolset/jd.d.ts.map +1 -1
  42. package/dist/toolset/jd.js +134 -142
  43. package/dist/toolset/jd.js.map +1 -1
  44. package/dist/toolset/list.d.ts.map +1 -1
  45. package/dist/toolset/list.js +52 -45
  46. package/dist/toolset/list.js.map +1 -1
  47. package/dist/toolset/preview.d.ts.map +1 -1
  48. package/dist/toolset/preview.js +7 -9
  49. package/dist/toolset/preview.js.map +1 -1
  50. package/dist/toolset/recommend.d.ts +1 -1
  51. package/dist/toolset/recommend.d.ts.map +1 -1
  52. package/dist/toolset/recommend.js +223 -210
  53. package/dist/toolset/recommend.js.map +1 -1
  54. package/dist/toolset/send.d.ts.map +1 -1
  55. package/dist/toolset/send.js +6 -9
  56. package/dist/toolset/send.js.map +1 -1
  57. package/package.json +65 -63
  58. package/dist/browser/auth.d.ts +0 -33
  59. package/dist/browser/auth.d.ts.map +0 -1
  60. package/dist/browser/auth.js +0 -137
  61. package/dist/browser/auth.js.map +0 -1
  62. package/dist/browser/c_resume_capture.d.ts +0 -11
  63. package/dist/browser/c_resume_capture.d.ts.map +0 -1
  64. package/dist/browser/c_resume_capture.js +0 -76
  65. package/dist/browser/c_resume_capture.js.map +0 -1
  66. package/dist/browser/chat.d.ts +0 -7
  67. package/dist/browser/chat.d.ts.map +0 -1
  68. package/dist/browser/chat.js +0 -155
  69. package/dist/browser/chat.js.map +0 -1
  70. package/dist/browser/withLoggedInPage.d.ts +0 -7
  71. package/dist/browser/withLoggedInPage.d.ts.map +0 -1
  72. package/dist/browser/withLoggedInPage.js +0 -19
  73. package/dist/browser/withLoggedInPage.js.map +0 -1
  74. package/dist/facebook-cli/index.d.ts +0 -2
  75. package/dist/facebook-cli/index.d.ts.map +0 -1
  76. package/dist/facebook-cli/index.js +0 -2
  77. package/dist/facebook-cli/index.js.map +0 -1
  78. package/dist/toolset/c_resume_capture.d.ts +0 -11
  79. package/dist/toolset/c_resume_capture.d.ts.map +0 -1
  80. package/dist/toolset/c_resume_capture.js +0 -76
  81. package/dist/toolset/c_resume_capture.js.map +0 -1
  82. package/dist/toolset/chat_action.d.ts +0 -7
  83. package/dist/toolset/chat_action.d.ts.map +0 -1
  84. package/dist/toolset/chat_action.js +0 -355
  85. package/dist/toolset/chat_action.js.map +0 -1
  86. package/dist/toolset/list_candidates.d.ts +0 -4
  87. package/dist/toolset/list_candidates.d.ts.map +0 -1
  88. package/dist/toolset/list_candidates.js +0 -120
  89. package/dist/toolset/list_candidates.js.map +0 -1
  90. package/dist/toolset/list_positions.d.ts +0 -9
  91. package/dist/toolset/list_positions.d.ts.map +0 -1
  92. package/dist/toolset/list_positions.js +0 -41
  93. package/dist/toolset/list_positions.js.map +0 -1
  94. package/dist/toolset/open_chat.d.ts +0 -3
  95. package/dist/toolset/open_chat.d.ts.map +0 -1
  96. package/dist/toolset/open_chat.js +0 -423
  97. package/dist/toolset/open_chat.js.map +0 -1
  98. package/dist/toolset/recommend_greet.d.ts +0 -2
  99. package/dist/toolset/recommend_greet.d.ts.map +0 -1
  100. package/dist/toolset/recommend_greet.js +0 -27
  101. package/dist/toolset/recommend_greet.js.map +0 -1
  102. package/dist/toolset/search.d.ts +0 -24
  103. package/dist/toolset/search.d.ts.map +0 -1
  104. package/dist/toolset/search.js +0 -682
  105. package/dist/toolset/search.js.map +0 -1
  106. package/dist/toolset/send_message.d.ts +0 -12
  107. package/dist/toolset/send_message.d.ts.map +0 -1
  108. package/dist/toolset/send_message.js +0 -214
  109. package/dist/toolset/send_message.js.map +0 -1
  110. package/dist/toolset/skill.d.ts +0 -13
  111. package/dist/toolset/skill.d.ts.map +0 -1
  112. package/dist/toolset/skill.js +0 -85
  113. 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,226 +640,255 @@ 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
  /**
770
- * 在深度搜索(aiform)主文档中按姓名或序号打开在线简历预览(与 {@link clickGreetDeepSearch} 同一卡片集合,排除「继续沟通」)。
803
+ * 在深度搜索(aiform)主文档中按姓名打开在线简历预览(与 {@link clickGreetDeepSearch} 同一卡片集合,排除「继续沟通」)。
771
804
  */
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
-
788
- const idxNum = Number.parseInt(raw, 10);
789
- var targetCard = null;
790
- if (Number.isFinite(idxNum) && idxNum >= 1 && idxNum <= cards.length) {
791
- targetCard = cards[idxNum - 1];
792
- } else {
793
- targetCard =
794
- cards.find((item) => {
795
- const name = norm(item.querySelector(".geek-name")?.textContent);
796
- return name === raw || name.includes(raw);
797
- }) ?? null;
798
- }
799
- if (!targetCard) return false;
800
-
801
- function tryOpen(el) {
802
- if (!(el instanceof HTMLElement)) return false;
803
- if (el.classList.contains("disabled")) return false;
804
- const st = window.getComputedStyle(el);
805
- if (st.pointerEvents === "none" || Number(st.opacity) < 0.3) return false;
806
- el.scrollIntoView({ block: "center", inline: "nearest" });
807
- el.click();
808
- return true;
809
- }
810
-
811
- const nameEl = targetCard.querySelector(".geek-name");
812
- if (nameEl instanceof HTMLElement) {
813
- nameEl.scrollIntoView({ block: "center", inline: "nearest" });
814
- nameEl.click();
815
- return true;
816
- }
817
-
818
- const resumeOnline = targetCard.querySelector("a.resume-btn-online");
819
- if (tryOpen(resumeOnline)) return true;
820
- const hrefResume = targetCard.querySelector('a[href*="c-resume"], a[href*="frame/c-resume"]');
821
- if (tryOpen(hrefResume)) return true;
822
-
823
- const links = Array.from(targetCard.querySelectorAll("a, button, .btn")).filter((node) => {
824
- const t = norm(node.textContent);
825
- return /在线简历|查看简历|简历预览|预览/.test(t);
826
- });
827
- if (links.length > 0 && tryOpen(links[0])) return true;
828
-
829
- const geekInfo = targetCard.querySelector(".geek-info, .geek-card-main, .card-content");
830
- if (geekInfo instanceof HTMLElement) {
831
- geekInfo.scrollIntoView({ block: "center", inline: "nearest" });
832
- geekInfo.click();
833
- return true;
834
- }
835
-
836
- 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;
837
863
  })()`));
838
864
  }
839
865
  export async function readDeepSearchGeekList(page) {
840
- return (await page.evaluate(`(() => {
841
- const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
842
- const items = Array.from(
843
- document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
844
- );
845
- return items
846
- .map((item) => {
847
- const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
848
- if (chatLabel.includes("继续沟通")) {
849
- return null;
850
- }
851
- const name = norm(item.querySelector(".geek-name")?.textContent);
852
- const splits = Array.from(item.querySelectorAll(".geek-exp .split"))
853
- .map((el) => norm(el.getAttribute("title") || el.textContent || ""))
854
- .filter(Boolean);
855
- const meta = splits.join(" · ");
856
- const work = norm(item.querySelector(".geek-works span")?.textContent);
857
- const edu = norm(item.querySelector(".geek-edus span")?.textContent);
858
- const recEl = item.querySelector(".geek-recommend-text");
859
- let reason = "";
860
- if (recEl) {
861
- reason = norm(recEl.textContent).replace(/^推荐理由\\s*/, "").trim();
862
- }
863
- return { name, meta, work, edu, reason };
864
- })
865
- .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);
866
892
  })()`));
867
893
  }
868
894
  export function renderGeekListSection(title, items) {
@@ -888,58 +914,51 @@ export function renderGeekListSection(title, items) {
888
914
  }
889
915
  export async function clickGreetDeepSearch(page, target) {
890
916
  const targetLiteral = JSON.stringify(target.trim());
891
- const result = (await page.evaluate(`(() => {
892
- const raw = ${targetLiteral};
893
- const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
894
- const allCards = Array.from(
895
- document.querySelectorAll(".geeks-box .geek-card-item, .geek-card-list .geek-card-item"),
896
- );
897
- if (allCards.length === 0) {
898
- return { kind: "empty" };
899
- }
900
- const cards = allCards.filter((item) => {
901
- const chatLabel = norm(item.querySelector(".geek-chat")?.textContent);
902
- return !chatLabel.includes("继续沟通");
903
- });
904
- if (cards.length === 0) {
905
- return { kind: "all_continue" };
906
- }
907
-
908
- const idxNum = Number.parseInt(raw, 10);
909
- let targetCard = null;
910
- if (Number.isFinite(idxNum) && idxNum >= 1 && idxNum <= cards.length) {
911
- targetCard = cards[idxNum - 1];
912
- } else {
913
- targetCard =
914
- cards.find((item) => {
915
- const name = norm(item.querySelector(".geek-name")?.textContent);
916
- return name === raw || name.includes(raw);
917
- }) ?? null;
918
- }
919
- if (!targetCard) {
920
- return { kind: "not_found", target: raw };
921
- }
922
-
923
- const name = norm(targetCard.querySelector(".geek-name")?.textContent);
924
- const btn =
925
- targetCard.querySelector(".geek-chat .btn-ai-v2") ||
926
- targetCard.querySelector(".geek-chat span.btn-ai-v2") ||
927
- targetCard.querySelector(".geek-chat span[class*='btn-ai']");
928
- if (!(btn instanceof HTMLElement)) {
929
- return { kind: "no_btn", name };
930
- }
931
- const label = norm(btn.textContent);
932
- if (!label.includes("打招呼")) {
933
- return { kind: "not_greet", name, label };
934
- }
935
- const cls = btn.className ?? "";
936
- const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
937
- if (disabled) {
938
- return { kind: "disabled", name };
939
- }
940
- btn.scrollIntoView({ block: "center", inline: "nearest" });
941
- btn.click();
942
- 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 };
943
962
  })()`));
944
963
  switch (result.kind) {
945
964
  case 'empty':
@@ -947,7 +966,7 @@ export async function clickGreetDeepSearch(page, target) {
947
966
  case 'all_continue':
948
967
  throw new Error('当前列表均为「继续沟通」状态,已无待打招呼人选(与 boss deep-search 列表展示一致)。');
949
968
  case 'not_found':
950
- throw new Error(`未在可打招呼的深度搜索列表中找到目标:${result.target}(「继续沟通」人选已排除,请用 boss deep-search 核对序号与姓名)。`);
969
+ throw new Error(`未在可打招呼的深度搜索列表中找到目标:${result.target}(「继续沟通」人选已排除,请用 boss deep-search 核对姓名)。`);
951
970
  case 'no_btn':
952
971
  throw new Error(`候选人 ${result.name} 缺少「打招呼」按钮,无法执行。`);
953
972
  case 'not_greet':
@@ -987,7 +1006,6 @@ export async function runBossSearchSet(opts) {
987
1006
  const currentUrl = page.url();
988
1007
  if (!isBossChatAiFormUrl(currentUrl)) {
989
1008
  await clickBossSidebarMenuToPath(page, '深度搜索', '/web/chat/aiform');
990
- await sleepRandom(AI_FORM_SETTLE_MS.min, AI_FORM_SETTLE_MS.max);
991
1009
  }
992
1010
  if (!isBossChatAiFormUrl(page.url())) {
993
1011
  throw new Error('通过侧边栏“深度搜索”进入页面失败,请确认已登录并可访问 /web/chat/aiform。');
@@ -997,7 +1015,7 @@ export async function runBossSearchSet(opts) {
997
1015
  await selectAiFormJob(page, jobKeyword);
998
1016
  await ensureInDeepSearchPage(page);
999
1017
  if (hasFormEdit) {
1000
- await sleepRandom(500, 900);
1018
+ await ensureInDeepSearchPage(page);
1001
1019
  }
1002
1020
  }
1003
1021
  if (hasFormEdit) {
@@ -1013,9 +1031,6 @@ export async function runBossSearchSet(opts) {
1013
1031
  }
1014
1032
  catch (e) {
1015
1033
  const message = e instanceof Error ? e.message : String(e);
1016
- if (e instanceof Error && e.message.includes('浏览器会话尚未初始化')) {
1017
- throw new Error(createWaitManualLoginRequiredText('设置深度搜索条件'));
1018
- }
1019
1034
  console.error(`[boss-cli] boss_search_set error: ${message}`);
1020
1035
  throw new Error(`设置深度搜索条件失败:${message}`);
1021
1036
  }
@@ -1027,7 +1042,6 @@ export async function runBossSearch(opts = {}) {
1027
1042
  const currentUrl = page.url();
1028
1043
  if (!isBossChatAiFormUrl(currentUrl)) {
1029
1044
  await clickBossSidebarMenuToPath(page, '深度搜索', '/web/chat/aiform');
1030
- await sleepRandom(AI_FORM_SETTLE_MS.min, AI_FORM_SETTLE_MS.max);
1031
1045
  }
1032
1046
  if (!isBossChatAiFormUrl(page.url())) {
1033
1047
  throw new Error('通过侧边栏“深度搜索”进入页面失败,请确认已登录并可访问 /web/chat/aiform。');
@@ -1036,7 +1050,6 @@ export async function runBossSearch(opts = {}) {
1036
1050
  if (jobKeyword) {
1037
1051
  await selectAiFormJob(page, jobKeyword);
1038
1052
  await ensureInDeepSearchPage(page);
1039
- await sleepRandom(600, 1200);
1040
1053
  }
1041
1054
  const geeks = await readDeepSearchGeekList(page);
1042
1055
  const title = jobKeyword
@@ -1047,9 +1060,6 @@ export async function runBossSearch(opts = {}) {
1047
1060
  }
1048
1061
  catch (e) {
1049
1062
  const message = e instanceof Error ? e.message : String(e);
1050
- if (e instanceof Error && e.message.includes('浏览器会话尚未初始化')) {
1051
- throw new Error(createWaitManualLoginRequiredText('读取深度搜索列表'));
1052
- }
1053
1063
  console.error(`[boss-cli] boss_search error: ${message}`);
1054
1064
  throw new Error(`读取深度搜索列表失败:${message}`);
1055
1065
  }