@joohw/boss-cli 0.1.9 → 0.2.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 (68) hide show
  1. package/dist/browser/chat.d.ts +1 -1
  2. package/dist/browser/chat.d.ts.map +1 -1
  3. package/dist/browser/chat.js +24 -2
  4. package/dist/browser/chat.js.map +1 -1
  5. package/dist/browser/human_delay.d.ts +2 -0
  6. package/dist/browser/human_delay.d.ts.map +1 -1
  7. package/dist/browser/human_delay.js +2 -0
  8. package/dist/browser/human_delay.js.map +1 -1
  9. package/dist/browser/index.d.ts +0 -2
  10. package/dist/browser/index.d.ts.map +1 -1
  11. package/dist/browser/index.js +0 -2
  12. package/dist/browser/index.js.map +1 -1
  13. package/dist/cli/banner.d.ts +1 -1
  14. package/dist/cli/banner.d.ts.map +1 -1
  15. package/dist/cli/banner.js +8 -39
  16. package/dist/cli/banner.js.map +1 -1
  17. package/dist/cli/cliRouter.d.ts.map +1 -1
  18. package/dist/cli/cliRouter.js +33 -23
  19. package/dist/cli/cliRouter.js.map +1 -1
  20. package/dist/common/auth.d.ts +38 -0
  21. package/dist/common/auth.d.ts.map +1 -0
  22. package/dist/common/auth.js +150 -0
  23. package/dist/common/auth.js.map +1 -0
  24. package/dist/common/boss_paywall_popup.d.ts +10 -3
  25. package/dist/common/boss_paywall_popup.d.ts.map +1 -1
  26. package/dist/common/boss_paywall_popup.js +42 -30
  27. package/dist/common/boss_paywall_popup.js.map +1 -1
  28. package/dist/common/boss_session_page.d.ts +11 -0
  29. package/dist/common/boss_session_page.d.ts.map +1 -0
  30. package/dist/common/boss_session_page.js +155 -0
  31. package/dist/common/boss_session_page.js.map +1 -0
  32. package/dist/common/c_resume_capture.d.ts +9 -2
  33. package/dist/common/c_resume_capture.d.ts.map +1 -1
  34. package/dist/common/c_resume_capture.js +71 -22
  35. package/dist/common/c_resume_capture.js.map +1 -1
  36. package/dist/toolset/action.js +2 -1
  37. package/dist/toolset/action.js.map +1 -1
  38. package/dist/toolset/chat.js +2 -1
  39. package/dist/toolset/chat.js.map +1 -1
  40. package/dist/toolset/deep-search.d.ts +7 -0
  41. package/dist/toolset/deep-search.d.ts.map +1 -1
  42. package/dist/toolset/deep-search.js +94 -76
  43. package/dist/toolset/deep-search.js.map +1 -1
  44. package/dist/toolset/greet.d.ts.map +1 -1
  45. package/dist/toolset/greet.js +18 -2
  46. package/dist/toolset/greet.js.map +1 -1
  47. package/dist/toolset/index.d.ts +1 -1
  48. package/dist/toolset/index.d.ts.map +1 -1
  49. package/dist/toolset/index.js +5 -4
  50. package/dist/toolset/index.js.map +1 -1
  51. package/dist/toolset/jd.d.ts.map +1 -1
  52. package/dist/toolset/jd.js +3 -1
  53. package/dist/toolset/jd.js.map +1 -1
  54. package/dist/toolset/list.d.ts.map +1 -1
  55. package/dist/toolset/list.js +3 -1
  56. package/dist/toolset/list.js.map +1 -1
  57. package/dist/toolset/preview.d.ts +6 -0
  58. package/dist/toolset/preview.d.ts.map +1 -0
  59. package/dist/toolset/preview.js +103 -0
  60. package/dist/toolset/preview.js.map +1 -0
  61. package/dist/toolset/recommend.d.ts +5 -4
  62. package/dist/toolset/recommend.d.ts.map +1 -1
  63. package/dist/toolset/recommend.js +15 -56
  64. package/dist/toolset/recommend.js.map +1 -1
  65. package/dist/toolset/send.d.ts.map +1 -1
  66. package/dist/toolset/send.js +3 -1
  67. package/dist/toolset/send.js.map +1 -1
  68. package/package.json +1 -1
@@ -0,0 +1,150 @@
1
+ import { PROBE_LOGIN_POLL_MS } from '../browser/human_delay.js';
2
+ import { sleepRandom } from '../browser/timing.js';
3
+ /** Boss 直聘首页 */
4
+ export const BOSS_ZHIPIN_HOME = 'https://www.zhipin.com/';
5
+ /** 默认落地页(杭州 SEO 首页);CLI 等未配置环境变量时使用 */
6
+ export const BOSS_DEFAULT_LANDING_URL = 'https://www.zhipin.com/hangzhou/?seoRefer=index';
7
+ /** 沟通页(登录成功后的典型落地页之一) */
8
+ export const BOSS_CHAT_INDEX_URL = 'https://www.zhipin.com/web/chat/index';
9
+ /** 尚未有可用的浏览器会话时的提示文本(供工具抛错复用)。 */
10
+ export function createWaitManualLoginRequiredText(action) {
11
+ return `浏览器尚未初始化,无法${action}。请先运行 boss login 并在浏览器中完成登录。`;
12
+ }
13
+ /** 当前 URL 是否属于 Boss 直聘站点(hostname 含 `zhipin.com`);`about:blank` / 空 / 非法视为否 */
14
+ export function isBossZhipinSiteUrl(url) {
15
+ if (!url || url === 'about:blank') {
16
+ return false;
17
+ }
18
+ try {
19
+ const u = new URL(url);
20
+ return u.hostname.includes('zhipin.com');
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ /** 当前 URL 是否为沟通页 `/web/chat/index`(允许带 query) */
27
+ export function isBossChatIndexUrl(url) {
28
+ try {
29
+ const u = new URL(url);
30
+ if (!u.hostname.includes('zhipin.com')) {
31
+ return false;
32
+ }
33
+ const p = u.pathname.replace(/\/+$/, '') || '/';
34
+ return p === '/web/chat/index';
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ /** 未登录时常见跳转:如 `https://www.zhipin.com/web/user/?ka=bticket` */
41
+ export function isWebUserLoginUrl(url) {
42
+ try {
43
+ const u = new URL(url);
44
+ if (!u.hostname.includes('zhipin.com')) {
45
+ return false;
46
+ }
47
+ return u.pathname.includes('/web/user/');
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ /**
54
+ * 必须在页面里执行的探测脚本(纯字符串)。
55
+ * `tsx`/esbuild 转译 `page.evaluate(() => { ... })` 时可能注入 `__name(...)`,序列化到浏览器后会报 `__name is not defined`。
56
+ */
57
+ const PROBE_LOGGED_IN_SIGNALS_SCRIPT = `(() => {
58
+ function isNick(t) {
59
+ const s = t.replace(/\\s+/g, " ").trim();
60
+ if (s.length < 2 || s.length > 64) return false;
61
+ return !/^(我要登录|登录|注册|登录\\/注册)$/u.test(s);
62
+ }
63
+ var hasNickname = false;
64
+ var nickSelectors = [
65
+ ".user-name",
66
+ "span.user-name",
67
+ "[class*='user-name']",
68
+ ".label-name",
69
+ ".nav-user .name",
70
+ ".nav-user-name",
71
+ "a.nav-user",
72
+ ".header-nav [class*='name']",
73
+ ];
74
+ var si, i, els, el, t;
75
+ for (si = 0; si < nickSelectors.length; si++) {
76
+ els = document.querySelectorAll(nickSelectors[si]);
77
+ for (i = 0; i < els.length; i++) {
78
+ el = els[i];
79
+ t = (el.textContent || "").trim();
80
+ if (isNick(t)) {
81
+ hasNickname = true;
82
+ break;
83
+ }
84
+ }
85
+ if (hasNickname) break;
86
+ }
87
+ var navRoots = [];
88
+ var navSels = ["header", ".nav-header", ".nav-wrap", ".top-header", "#header", ".header", ".navbar"];
89
+ var j, ne;
90
+ for (j = 0; j < navSels.length; j++) {
91
+ ne = document.querySelector(navSels[j]);
92
+ if (ne instanceof HTMLElement) navRoots.push(ne);
93
+ }
94
+ var navLoginCta = false;
95
+ for (i = 0; i < navRoots.length; i++) {
96
+ if (/\\b我要登录\\b/u.test(navRoots[i].innerText || "")) {
97
+ navLoginCta = true;
98
+ break;
99
+ }
100
+ }
101
+ var bodyText = document.body instanceof HTMLElement ? document.body.innerText || "" : "";
102
+ var hasLogoutHint =
103
+ /\\b退出登录\\b/u.test(bodyText) ||
104
+ !!document.querySelector("a[href*='logout'], a[href*='signout'], [data-url*='logout']");
105
+ return { hasNickname: hasNickname, navLoginCta: navLoginCta, hasLogoutHint: hasLogoutHint };
106
+ })()`;
107
+ async function probeLoggedInSignals(page) {
108
+ return (await page.evaluate(PROBE_LOGGED_IN_SIGNALS_SCRIPT));
109
+ }
110
+ /**
111
+ * 根据当前页判断是否已登录(不导航)。
112
+ *
113
+ * **已登录(true)**:检测到顶栏昵称、或「退出登录」/logout 等信号。
114
+ * 会短轮询等待 SPA 渲染,避免 `goto` 后立即读静态 HTML 误判。
115
+ *
116
+ * **未登录(false)**:`/web/user/` 登录流 URL、顶栏出现「我要登录」入口、且轮询结束仍无昵称/退出类信号。
117
+ */
118
+ export async function probeLoggedInFromPage(page) {
119
+ const url = page.url();
120
+ if (!url || url === 'about:blank') {
121
+ return { loggedIn: false, url: url || '' };
122
+ }
123
+ if (isWebUserLoginUrl(url)) {
124
+ return { loggedIn: false, url };
125
+ }
126
+ const maxAttempts = 25;
127
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
128
+ const s = await probeLoggedInSignals(page);
129
+ if (s.hasNickname || s.hasLogoutHint) {
130
+ return { loggedIn: true, url };
131
+ }
132
+ if (s.navLoginCta) {
133
+ return { loggedIn: false, url };
134
+ }
135
+ if (attempt < maxAttempts - 1) {
136
+ await sleepRandom(PROBE_LOGIN_POLL_MS.min, PROBE_LOGIN_POLL_MS.max);
137
+ }
138
+ }
139
+ return { loggedIn: false, url };
140
+ }
141
+ /** 沟通页且已登录(与 {@link probeLoggedInFromPage} 一致)。 */
142
+ export async function probeBossChatIndexLoggedIn(page) {
143
+ const url = page.url();
144
+ if (!isBossChatIndexUrl(url)) {
145
+ return false;
146
+ }
147
+ const { loggedIn } = await probeLoggedInFromPage(page);
148
+ return loggedIn;
149
+ }
150
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/common/auth.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,23 @@
1
1
  import type { Page } from 'puppeteer-core';
2
2
  /**
3
- * 轮询直到出现 c-resume iframe、或出现付费墙、或超时。付费墙出现时不必再等满 {@link ONLINE_RESUME_IFRAME_WAIT_MAX_MS}。
3
+ * 轮询直到出现 c-resume iframe、或出现付费墙、或超时。
4
+ * c-resume 可能在主文档,也可能在 `recommendFrame` 等子 frame 内(推荐预览),故遍历 {@link Page.frames}。
4
5
  */
5
6
  export declare function waitForCResumeIframeOrPaywall(page: Page, timeoutMs?: number): Promise<'iframe' | 'paywall' | 'neither'>;
7
+ /**
8
+ * 打招呼等操作后,轮询主文档是否出现付费弹层(与 {@link describeBossPaywallPopupIfPresent} 判定一致)。
9
+ * 命中则返回 true;超时未出现则返回 false。
10
+ */
11
+ export declare function waitForBossPaywallPopup(page: Page, timeoutMs?: number): Promise<boolean>;
6
12
  /**
7
13
  * 若当前存在 VIP/付费类弹层(判定规则与 {@link describeBossPaywallPopupIfPresent} 一致),
8
14
  * 则点击关闭按钮以恢复页面可操作状态。返回是否执行了关闭。
9
15
  */
10
16
  export declare function closeBossPaywallPopupIfPresent(page: Page): Promise<boolean>;
11
17
  /**
12
- * 检测 Boss 页面上是否出现 VIP/付费购买类弹层(如点击「在线简历」或推荐预览后拦截权益时)。
18
+ * 检测 Boss 页面上是否出现 VIP/付费购买类弹层(如点击「在线简历」、推荐预览或打招呼后拦截权益时)。
13
19
  * 命中则返回简短中文说明,供与「未出现 c-resume iframe」类错误拼接。
20
+ * @param purpose `greet` 时将「查看在线简历」类措辞改为适合打招呼的说明。
14
21
  */
15
- export declare function describeBossPaywallPopupIfPresent(page: Page): Promise<string | null>;
22
+ export declare function describeBossPaywallPopupIfPresent(page: Page, purpose?: 'resume' | 'greet'): Promise<string | null>;
16
23
  //# sourceMappingURL=boss_paywall_popup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"boss_paywall_popup.d.ts","sourceRoot":"","sources":["../../src/common/boss_paywall_popup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAsC3C;;GAEG;AACH,wBAAsB,6BAA6B,CACjD,IAAI,EAAE,IAAI,EACV,SAAS,GAAE,MAAyC,GACnD,OAAO,CAAC,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC,CAkB3C;AAED;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAoCjF;AAED;;;GAGG;AACH,wBAAsB,iCAAiC,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmC1F"}
1
+ {"version":3,"file":"boss_paywall_popup.d.ts","sourceRoot":"","sources":["../../src/common/boss_paywall_popup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAgC3C;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,IAAI,EAAE,IAAI,EACV,SAAS,GAAE,MAAyC,GACnD,OAAO,CAAC,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC,CAkB3C;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,IAAI,EACV,SAAS,GAAE,MAAkC,GAC5C,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAoCjF;AAED;;;;GAIG;AACH,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,QAAQ,GAAG,OAAkB,GACrC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuCxB"}
@@ -1,14 +1,8 @@
1
- import { ONLINE_RESUME_IFRAME_WAIT_MAX_MS } from '../browser/human_delay.js';
1
+ import { GREET_PAYWALL_WAIT_MAX_MS, ONLINE_RESUME_IFRAME_WAIT_MAX_MS } from '../browser/human_delay.js';
2
2
  import { sleepRandom } from '../browser/timing.js';
3
- /** {@link describeBossPaywallPopupIfPresent} 中付费层判定一致;先匹配到 c-resume iframe 则返回 iframe。 */
4
- const WAIT_FOR_IFRAME_OR_PAYWALL_SCRIPT = `(() => {
5
- const iframe = document.querySelector(
6
- 'iframe[src*="c-resume"], iframe[src*="frame/c-resume"]',
7
- );
8
- if (iframe instanceof HTMLElement) {
9
- const r = iframe.getBoundingClientRect();
10
- if (r.width > 8 && r.height > 8) return "iframe";
11
- }
3
+ import { frameHasVisibleCResumeIframe } from './c_resume_capture.js';
4
+ /** 付费墙仅在主文档轮询(遮罩层挂在顶层);与 {@link describeBossPaywallPopupIfPresent} 判定一致 */
5
+ const HAS_PAYWALL_SCRIPT = `(() => {
12
6
  const roots = Array.from(
13
7
  document.querySelectorAll(".boss-popup__wrapper, .boss-dialog__wrapper"),
14
8
  );
@@ -29,32 +23,45 @@ const WAIT_FOR_IFRAME_OR_PAYWALL_SCRIPT = `(() => {
29
23
  text,
30
24
  );
31
25
  if (!hasVipUi && !hasPayText) continue;
32
- return "paywall";
26
+ return true;
33
27
  }
34
- return "";
28
+ return false;
35
29
  })()`;
36
30
  /**
37
- * 轮询直到出现 c-resume iframe、或出现付费墙、或超时。付费墙出现时不必再等满 {@link ONLINE_RESUME_IFRAME_WAIT_MAX_MS}。
31
+ * 轮询直到出现 c-resume iframe、或出现付费墙、或超时。
32
+ * c-resume 可能在主文档,也可能在 `recommendFrame` 等子 frame 内(推荐预览),故遍历 {@link Page.frames}。
38
33
  */
39
34
  export async function waitForCResumeIframeOrPaywall(page, timeoutMs = ONLINE_RESUME_IFRAME_WAIT_MAX_MS) {
40
- try {
41
- const handle = await page.waitForFunction(WAIT_FOR_IFRAME_OR_PAYWALL_SCRIPT, {
42
- timeout: timeoutMs,
43
- polling: 200,
44
- });
45
- const v = (await handle.jsonValue());
46
- if (v === 'iframe' || v === 'paywall') {
47
- return v;
35
+ const deadline = Date.now() + timeoutMs;
36
+ while (Date.now() < deadline) {
37
+ const paywall = (await page.evaluate(HAS_PAYWALL_SCRIPT));
38
+ if (paywall) {
39
+ return 'paywall';
48
40
  }
49
- return 'neither';
41
+ const frames = page.frames();
42
+ for (const frame of frames) {
43
+ if (await frameHasVisibleCResumeIframe(frame)) {
44
+ return 'iframe';
45
+ }
46
+ }
47
+ await sleepRandom(160, 240);
50
48
  }
51
- catch (e) {
52
- const msg = e instanceof Error ? e.message : String(e);
53
- if (/timeout|Timeout|exceeded|Waiting failed/i.test(msg)) {
54
- return 'neither';
49
+ return 'neither';
50
+ }
51
+ /**
52
+ * 打招呼等操作后,轮询主文档是否出现付费弹层(与 {@link describeBossPaywallPopupIfPresent} 判定一致)。
53
+ * 命中则返回 true;超时未出现则返回 false。
54
+ */
55
+ export async function waitForBossPaywallPopup(page, timeoutMs = GREET_PAYWALL_WAIT_MAX_MS) {
56
+ const deadline = Date.now() + timeoutMs;
57
+ while (Date.now() < deadline) {
58
+ const paywall = (await page.evaluate(HAS_PAYWALL_SCRIPT));
59
+ if (paywall) {
60
+ return true;
55
61
  }
56
- throw e;
62
+ await sleepRandom(160, 240);
57
63
  }
64
+ return false;
58
65
  }
59
66
  /**
60
67
  * 若当前存在 VIP/付费类弹层(判定规则与 {@link describeBossPaywallPopupIfPresent} 一致),
@@ -98,11 +105,12 @@ export async function closeBossPaywallPopupIfPresent(page) {
98
105
  return closed;
99
106
  }
100
107
  /**
101
- * 检测 Boss 页面上是否出现 VIP/付费购买类弹层(如点击「在线简历」或推荐预览后拦截权益时)。
108
+ * 检测 Boss 页面上是否出现 VIP/付费购买类弹层(如点击「在线简历」、推荐预览或打招呼后拦截权益时)。
102
109
  * 命中则返回简短中文说明,供与「未出现 c-resume iframe」类错误拼接。
110
+ * @param purpose `greet` 时将「查看在线简历」类措辞改为适合打招呼的说明。
103
111
  */
104
- export async function describeBossPaywallPopupIfPresent(page) {
105
- return (await page.evaluate(`(() => {
112
+ export async function describeBossPaywallPopupIfPresent(page, purpose = 'resume') {
113
+ const msg = (await page.evaluate(`(() => {
106
114
  const roots = Array.from(
107
115
  document.querySelectorAll(".boss-popup__wrapper, .boss-dialog__wrapper"),
108
116
  );
@@ -136,5 +144,9 @@ export async function describeBossPaywallPopupIfPresent(page) {
136
144
  }
137
145
  return null;
138
146
  })()`));
147
+ if (!msg || purpose === 'resume') {
148
+ return msg;
149
+ }
150
+ return msg.replace(/后才能查看在线简历/g, '后才能完成打招呼');
139
151
  }
140
152
  //# sourceMappingURL=boss_paywall_popup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"boss_paywall_popup.js","sourceRoot":"","sources":["../../src/common/boss_paywall_popup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,4FAA4F;AAC5F,MAAM,iCAAiC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+BrC,CAAC;AAEN;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,IAAU,EACV,YAAoB,gCAAgC;IAEpD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,iCAAiC,EAAE;YAC3E,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,GAAG;SACb,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,EAAE,CAAY,CAAC;QAChD,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACtC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,IAAI,0CAA0C,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACzD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,IAAU;IAC7D,MAAM,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8B/B,CAAC,CAAY,CAAC;IACnB,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iCAAiC,CAAC,IAAU;IAChE,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCvB,CAAC,CAAkB,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"boss_paywall_popup.js","sourceRoot":"","sources":["../../src/common/boss_paywall_popup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AACxG,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAErE,2EAA2E;AAC3E,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;KAwBtB,CAAC;AAEN;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,IAAU,EACV,YAAoB,gCAAgC;IAEpD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAY,CAAC;QACrE,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,MAAM,4BAA4B,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC9C,OAAO,QAAQ,CAAC;YAClB,CAAC;QACH,CAAC;QAED,MAAM,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,IAAU,EACV,YAAoB,yBAAyB;IAE7C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAY,CAAC;QACrE,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,IAAU;IAC7D,MAAM,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8B/B,CAAC,CAAY,CAAC;IACnB,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACrD,IAAU,EACV,UAA8B,QAAQ;IAEtC,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiC5B,CAAC,CAAkB,CAAC;IACzB,IAAI,CAAC,GAAG,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Boss B 端「已登录主壳」会话:选页、必要时进入沟通页、侧栏 `.menu-list` 探测与稳定检测,
3
+ * 再执行 {@link withBossSessionPage} 回调。与 `src/toolset/chat.ts`(按姓名打开会话等业务)无关。
4
+ */
5
+ import type { Page } from 'puppeteer-core';
6
+ /**
7
+ * 在已连接浏览器、且当前页为 Boss 已登录主壳(含侧栏 `.menu-list` 稳定)的前提下执行回调。
8
+ * 若当前不在 zhipin.com 则先进入沟通页 `/web/chat/index`,再校验侧栏;已在 Boss 站内(如推荐页)则不强制跳转。
9
+ */
10
+ export declare function withBossSessionPage<T>(callback: (page: Page) => Promise<T>): Promise<T>;
11
+ //# sourceMappingURL=boss_session_page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boss_session_page.d.ts","sourceRoot":"","sources":["../../src/common/boss_session_page.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAwIpD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAuD7F"}
@@ -0,0 +1,155 @@
1
+ import { BOSS_CHAT_INDEX_URL, isBossZhipinSiteUrl } from './auth.js';
2
+ import { hideAgentOperatingIndicator, showAgentOperatingIndicator, } from '../browser/agent_operating_indicator.js';
3
+ import { ensureBrowserSession, getBrowserRef, getPageRef, setSessionPage, } from '../browser/browser_session.js';
4
+ import { CONTEXT_DESTROY_RETRY_MS } from '../browser/human_delay.js';
5
+ import { sleepRandom } from '../browser/timing.js';
6
+ const SHOULD_DISABLE_JS = process.env.BOSS_BROWSER_DISABLE_JS === 'true' || process.env.BOSS_BROWSER_DISABLE_JS === '1';
7
+ /** 设为 `1` / `true` 时不注入顶栏滚动提示(调试或截图对比用)。 */
8
+ const SKIP_AGENT_OPERATING_OVERLAY = process.env.BOSS_CLI_NO_AGENT_OVERLAY === '1' ||
9
+ process.env.BOSS_CLI_NO_AGENT_OVERLAY === 'true';
10
+ /** Boss 为 SPA:`load` 后侧栏可能尚未挂载,需单独等待 `.menu-list` 出现 */
11
+ const MENU_LIST_MOUNT_TIMEOUT_MS = 30_000;
12
+ async function pickExistingPage(browser) {
13
+ const pages = (await browser.pages()).filter((p) => !p.isClosed());
14
+ if (pages.length === 0)
15
+ return null;
16
+ const urls = await Promise.all(pages.map((p) => {
17
+ try {
18
+ return p.url();
19
+ }
20
+ catch {
21
+ return '';
22
+ }
23
+ }));
24
+ const zhipin = pages.find((p, i) => {
25
+ const u = urls[i] ?? '';
26
+ return u.length > 0 && u !== 'about:blank' && u.includes('zhipin.com');
27
+ });
28
+ if (zhipin)
29
+ return zhipin;
30
+ const nonBlank = pages.find((p, i) => {
31
+ const u = urls[i] ?? '';
32
+ return u.length > 0 && u !== 'about:blank';
33
+ });
34
+ return nonBlank ?? null;
35
+ }
36
+ function normalizeMenuText(raw) {
37
+ return (raw ?? '').replace(/\s+/g, ' ').trim();
38
+ }
39
+ async function readMenuListSnapshot(page) {
40
+ return (await page.evaluate(`(() => {
41
+ const root = document.querySelector(".menu-list");
42
+ if (!root) {
43
+ return { exists: false, signature: "" };
44
+ }
45
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
46
+ const links = Array.from(root.querySelectorAll("dl > dt > a"));
47
+ const entries = links.map((a) => {
48
+ const href = a.getAttribute("href") ?? "";
49
+ const labelNode = a.querySelector(".menu-item-content span");
50
+ const label = norm(labelNode?.textContent || a.textContent || "");
51
+ return label + "::" + href;
52
+ });
53
+ return { exists: true, signature: entries.join("|") };
54
+ })()`));
55
+ }
56
+ /** 仅当当前页不在 Boss 站点(非 zhipin.com / 空白等)时进入沟通页,再交由 {@link ensureMenuListStableAfterLoad} 查 `.menu-list` */
57
+ async function ensureBossZhipinLandingBeforeMenuList(page) {
58
+ if (isBossZhipinSiteUrl(page.url())) {
59
+ return;
60
+ }
61
+ await page.goto(BOSS_CHAT_INDEX_URL, { waitUntil: 'load', timeout: 60_000 });
62
+ }
63
+ async function ensureMenuListStableAfterLoad(page) {
64
+ await page.waitForFunction(`(() => document.readyState === "complete" || document.readyState === "interactive")()`, { timeout: 12_000 });
65
+ try {
66
+ await page.waitForFunction(`(() => !!document.querySelector(".menu-list"))()`, { timeout: MENU_LIST_MOUNT_TIMEOUT_MS });
67
+ }
68
+ catch (e) {
69
+ const err = e instanceof Error ? e : new Error(String(e));
70
+ const timedOut = err.name === 'TimeoutError' || /timeout|waiting failed/i.test(err.message);
71
+ if (timedOut) {
72
+ throw new Error(`在 ${MENU_LIST_MOUNT_TIMEOUT_MS / 1000}s 内未出现侧栏 .menu-list(页面或仍在加载,或未登录无法进入主壳)。`);
73
+ }
74
+ throw e;
75
+ }
76
+ const first = await readMenuListSnapshot(page);
77
+ if (!first.exists) {
78
+ throw new Error('当前页面可能未登录或未进入 Boss 主界面。');
79
+ }
80
+ if (!normalizeMenuText(first.signature)) {
81
+ throw new Error('检测到 .menu-list 但菜单内容为空,当前页面状态异常。');
82
+ }
83
+ const stableWindowMs = 3_000;
84
+ const pollMs = 300;
85
+ const deadline = Date.now() + stableWindowMs;
86
+ const expected = first.signature;
87
+ while (Date.now() < deadline) {
88
+ await sleepRandom(pollMs, pollMs);
89
+ const snap = await readMenuListSnapshot(page);
90
+ if (!snap.exists) {
91
+ throw new Error('页面中的 .menu-list 在 3 秒稳定检测内消失,疑似未登录或页面仍在跳转。');
92
+ }
93
+ if (snap.signature !== expected) {
94
+ throw new Error('页面中的 .menu-list 在 3 秒稳定检测内发生变化,疑似页面仍在重定向或刷新。');
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * 在已连接浏览器、且当前页为 Boss 已登录主壳(含侧栏 `.menu-list` 稳定)的前提下执行回调。
100
+ * 若当前不在 zhipin.com 则先进入沟通页 `/web/chat/index`,再校验侧栏;已在 Boss 站内(如推荐页)则不强制跳转。
101
+ */
102
+ export async function withBossSessionPage(callback) {
103
+ const isContextDestroyed = (e) => {
104
+ const msg = e instanceof Error ? e.message : String(e);
105
+ return (msg.includes('Execution context was destroyed') ||
106
+ msg.includes('Cannot find context with specified id') ||
107
+ msg.includes('Most likely because of a navigation'));
108
+ };
109
+ const maxAttempts = 2;
110
+ let lastErr;
111
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
112
+ try {
113
+ await ensureBrowserSession();
114
+ const browser = getBrowserRef();
115
+ if (!browser) {
116
+ throw new Error('无法获取浏览器实例。');
117
+ }
118
+ let page = getPageRef();
119
+ if (!page || page.isClosed()) {
120
+ page = (await pickExistingPage(browser)) ?? (await browser.newPage());
121
+ }
122
+ setSessionPage(page);
123
+ await page.bringToFront();
124
+ await ensureBossZhipinLandingBeforeMenuList(page);
125
+ if (SHOULD_DISABLE_JS) {
126
+ await page.setJavaScriptEnabled(false);
127
+ }
128
+ await ensureMenuListStableAfterLoad(page);
129
+ if (!SHOULD_DISABLE_JS && !SKIP_AGENT_OPERATING_OVERLAY) {
130
+ await showAgentOperatingIndicator(page).catch(() => {
131
+ /* 注入失败不阻断业务 */
132
+ });
133
+ }
134
+ try {
135
+ return await callback(page);
136
+ }
137
+ finally {
138
+ if (!SHOULD_DISABLE_JS && !SKIP_AGENT_OPERATING_OVERLAY) {
139
+ await hideAgentOperatingIndicator(page);
140
+ }
141
+ }
142
+ }
143
+ catch (e) {
144
+ lastErr = e;
145
+ if (attempt < maxAttempts - 1 && isContextDestroyed(e)) {
146
+ // Boss 页面偶发跳转/重渲染会销毁执行上下文;短暂等待并重试一次即可。
147
+ await sleepRandom(CONTEXT_DESTROY_RETRY_MS.min, CONTEXT_DESTROY_RETRY_MS.max);
148
+ continue;
149
+ }
150
+ throw e;
151
+ }
152
+ }
153
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
154
+ }
155
+ //# sourceMappingURL=boss_session_page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boss_session_page.js","sourceRoot":"","sources":["../../src/common/boss_session_page.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,15 @@
1
- import type { Page } from 'puppeteer-core';
1
+ import type { ElementHandle, Frame, Page } from 'puppeteer-core';
2
+ /** 在线简历 iframe:`src` 常为相对路径 `/web/frame/c-resume/...`,故用子串匹配 */
3
+ export declare const C_RESUME_IFRAME_SELECTOR: "iframe[src*=\"c-resume\"], iframe[src*=\"frame/c-resume\"]";
4
+ export declare function frameHasVisibleCResumeIframe(frame: Frame): Promise<boolean>;
2
5
  /** 截图文件名安全段(在线简历 / 推荐预览共用) */
3
6
  export declare function safeResumeScreenshotFileBase(name: string): string;
4
- /** 关闭含 `c-resume` iframe 的弹层(聊天「在线简历」与推荐「预览」共用)。 */
7
+ /** 关闭含 `c-resume` iframe 的弹层(聊天「在线简历」与推荐「预览」共用)。含 `.boss-popup__close`、`.btn-quxiao`(取消)等。会在主文档与各子 frame 中尝试。 */
5
8
  export declare function closeCResumePanel(page: Page): Promise<void>;
9
+ /**
10
+ * 在任意 frame(含主 frame、`recommendFrame` 等)中查找已挂载且尺寸可见的 c-resume iframe。
11
+ */
12
+ export declare function findVisibleCResumeIframeHandle(page: Page): Promise<ElementHandle<Element> | null>;
6
13
  /**
7
14
  * 在已出现 `c-resume` iframe 的页面上,对 iframe 整框截图并关闭弹层。
8
15
  * `preOpenViewport` 为打开弹层前的视口快照,请用 `snapshotBossPageViewport(page)`(`page.viewport()` 常为 null 时勿直接用默认尺寸)。
@@ -1 +1 @@
1
- {"version":3,"file":"c_resume_capture.d.ts","sourceRoot":"","sources":["../../src/common/c_resume_capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAI3C,8BAA8B;AAC9B,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,oDAAoD;AACpD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BjE;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,IAAI,EACV,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EACtD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAmClB"}
1
+ {"version":3,"file":"c_resume_capture.d.ts","sourceRoot":"","sources":["../../src/common/c_resume_capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAIjE,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,EACnC,4DAAiE,CAAC;AAqCpE,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAMjF;AAED,8BAA8B;AAC9B,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,iHAAiH;AACjH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAajE;AAED;;GAEG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAevG;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,IAAI,EACV,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EACtD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAmClB"}
@@ -1,41 +1,90 @@
1
1
  import { sleepRandom } from '../browser/timing.js';
2
2
  import { resumeHeight, setTempHeight } from '../browser/viewport_temp.js';
3
+ /** 在线简历 iframe:`src` 常为相对路径 `/web/frame/c-resume/...`,故用子串匹配 */
4
+ export const C_RESUME_IFRAME_SELECTOR = 'iframe[src*="c-resume"], iframe[src*="frame/c-resume"]';
5
+ const CLOSE_C_RESUME_PANEL_SCRIPT = `(() => {
6
+ const sel = ${JSON.stringify(C_RESUME_IFRAME_SELECTOR)};
7
+ const wraps = Array.from(document.querySelectorAll('.boss-popup__wrapper'));
8
+ for (var wi = 0; wi < wraps.length; wi++) {
9
+ var w = wraps[wi];
10
+ if (w.querySelector(sel)) {
11
+ var c = w.querySelector('.boss-popup__close') || w.querySelector('.btn-quxiao');
12
+ if (c) {
13
+ c.click();
14
+ return true;
15
+ }
16
+ }
17
+ }
18
+ var iframe = document.querySelector(sel);
19
+ var node = iframe ? iframe.parentElement : null;
20
+ for (var i = 0; i < 12 && node; i++) {
21
+ var closeBtn = node.querySelector(
22
+ '.boss-popup__close, .drawer-close, .icon-close, .btn-quxiao',
23
+ );
24
+ if (closeBtn) {
25
+ closeBtn.click();
26
+ return true;
27
+ }
28
+ node = node.parentElement;
29
+ }
30
+ return false;
31
+ })()`;
32
+ const VISIBLE_C_RESUME_IN_FRAME_SCRIPT = `(() => {
33
+ var iframe = document.querySelector(${JSON.stringify(C_RESUME_IFRAME_SELECTOR)});
34
+ if (!(iframe instanceof HTMLElement)) return false;
35
+ var r = iframe.getBoundingClientRect();
36
+ return r.width > 8 && r.height > 8;
37
+ })()`;
38
+ export async function frameHasVisibleCResumeIframe(frame) {
39
+ try {
40
+ return (await frame.evaluate(VISIBLE_C_RESUME_IN_FRAME_SCRIPT));
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
3
46
  /** 截图文件名安全段(在线简历 / 推荐预览共用) */
4
47
  export function safeResumeScreenshotFileBase(name) {
5
48
  const t = name.replace(/[/\\?%*:|"<>]/g, '_').trim().slice(0, 64);
6
49
  return t.length > 0 ? t : 'candidate';
7
50
  }
8
- /** 关闭含 `c-resume` iframe 的弹层(聊天「在线简历」与推荐「预览」共用)。 */
51
+ /** 关闭含 `c-resume` iframe 的弹层(聊天「在线简历」与推荐「预览」共用)。含 `.boss-popup__close`、`.btn-quxiao`(取消)等。会在主文档与各子 frame 中尝试。 */
9
52
  export async function closeCResumePanel(page) {
10
53
  try {
11
- await page.evaluate(() => {
12
- const wraps = Array.from(document.querySelectorAll('.boss-popup__wrapper'));
13
- for (const w of wraps) {
14
- if (w.querySelector('iframe[src*="c-resume"], iframe[src*="frame/c-resume"]')) {
15
- const c = w.querySelector('.boss-popup__close');
16
- if (c) {
17
- c.click();
18
- return;
19
- }
20
- }
54
+ for (const frame of page.frames()) {
55
+ try {
56
+ await frame.evaluate(CLOSE_C_RESUME_PANEL_SCRIPT);
21
57
  }
22
- const iframe = document.querySelector('iframe[src*="c-resume"], iframe[src*="frame/c-resume"]');
23
- let node = iframe?.parentElement ?? null;
24
- for (let i = 0; i < 12 && node; i++) {
25
- const c = node.querySelector('.boss-popup__close, .drawer-close, .icon-close');
26
- if (c) {
27
- c.click();
28
- return;
29
- }
30
- node = node.parentElement;
58
+ catch {
59
+ /* detached / 无权限 */
31
60
  }
32
- });
61
+ }
33
62
  await sleepRandom(200, 450);
34
63
  }
35
64
  catch {
36
65
  /* ignore */
37
66
  }
38
67
  }
68
+ /**
69
+ * 在任意 frame(含主 frame、`recommendFrame` 等)中查找已挂载且尺寸可见的 c-resume iframe。
70
+ */
71
+ export async function findVisibleCResumeIframeHandle(page) {
72
+ for (const frame of page.frames()) {
73
+ try {
74
+ if (!(await frameHasVisibleCResumeIframe(frame))) {
75
+ continue;
76
+ }
77
+ const h = await frame.$(C_RESUME_IFRAME_SELECTOR);
78
+ if (h) {
79
+ return h;
80
+ }
81
+ }
82
+ catch {
83
+ /* detached */
84
+ }
85
+ }
86
+ return null;
87
+ }
39
88
  /**
40
89
  * 在已出现 `c-resume` iframe 的页面上,对 iframe 整框截图并关闭弹层。
41
90
  * `preOpenViewport` 为打开弹层前的视口快照,请用 `snapshotBossPageViewport(page)`(`page.viewport()` 常为 null 时勿直接用默认尺寸)。
@@ -44,7 +93,7 @@ export async function captureCResumeIframeToFile(page, preOpenViewport, absPath)
44
93
  try {
45
94
  await setTempHeight(page, preOpenViewport);
46
95
  await sleepRandom(100, 320);
47
- const iframe = await page.$('iframe[src*="c-resume"], iframe[src*="frame/c-resume"]');
96
+ const iframe = await findVisibleCResumeIframeHandle(page);
48
97
  if (!iframe) {
49
98
  return false;
50
99
  }