@joohw/boss-cli 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -20
- package/dist/browser/auth.d.ts +0 -1
- package/dist/browser/auth.d.ts.map +1 -1
- package/dist/browser/auth.js +3 -19
- package/dist/browser/auth.js.map +1 -1
- package/dist/browser/browser_session.d.ts +4 -0
- package/dist/browser/browser_session.d.ts.map +1 -1
- package/dist/browser/browser_session.js +65 -14
- package/dist/browser/browser_session.js.map +1 -1
- package/dist/browser/cdp_browser.d.ts +9 -0
- package/dist/browser/cdp_browser.d.ts.map +1 -1
- package/dist/browser/cdp_browser.js +243 -11
- package/dist/browser/cdp_browser.js.map +1 -1
- package/dist/browser/chat.d.ts +2 -2
- package/dist/browser/chat.d.ts.map +1 -1
- package/dist/browser/chat.js +50 -18
- package/dist/browser/chat.js.map +1 -1
- package/dist/browser/human_delay.d.ts +88 -0
- package/dist/browser/human_delay.d.ts.map +1 -0
- package/dist/browser/human_delay.js +52 -0
- package/dist/browser/human_delay.js.map +1 -0
- package/dist/browser/index.d.ts +3 -2
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +3 -2
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/timing.d.ts +7 -0
- package/dist/browser/timing.d.ts.map +1 -0
- package/dist/browser/timing.js +34 -0
- package/dist/browser/timing.js.map +1 -0
- package/dist/cli/banner.d.ts.map +1 -1
- package/dist/cli/banner.js +43 -9
- package/dist/cli/banner.js.map +1 -1
- package/dist/cli/cliRouter.d.ts.map +1 -1
- package/dist/cli/cliRouter.js +130 -62
- package/dist/cli/cliRouter.js.map +1 -1
- package/dist/cli/index.js +14 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -1
- package/dist/ocr/baidu_ocr.d.ts +11 -0
- package/dist/ocr/baidu_ocr.d.ts.map +1 -0
- package/dist/ocr/baidu_ocr.js +76 -0
- package/dist/ocr/baidu_ocr.js.map +1 -0
- package/dist/ocr/index.d.ts +3 -0
- package/dist/ocr/index.d.ts.map +1 -0
- package/dist/ocr/index.js +3 -0
- package/dist/ocr/index.js.map +1 -0
- package/dist/ocr/resume_ocr.d.ts +13 -0
- package/dist/ocr/resume_ocr.d.ts.map +1 -0
- package/dist/ocr/resume_ocr.js +35 -0
- package/dist/ocr/resume_ocr.js.map +1 -0
- package/dist/toolset/action.d.ts +7 -0
- package/dist/toolset/action.d.ts.map +1 -0
- package/dist/toolset/action.js +433 -0
- package/dist/toolset/action.js.map +1 -0
- package/dist/toolset/chat.d.ts +4 -0
- package/dist/toolset/chat.d.ts.map +1 -0
- package/dist/toolset/chat.js +434 -0
- package/dist/toolset/chat.js.map +1 -0
- package/dist/toolset/chat_action.d.ts +7 -0
- package/dist/toolset/chat_action.d.ts.map +1 -0
- package/dist/toolset/chat_action.js +355 -0
- package/dist/toolset/chat_action.js.map +1 -0
- package/dist/toolset/index.d.ts +18 -1
- package/dist/toolset/index.d.ts.map +1 -1
- package/dist/toolset/index.js +27 -6
- package/dist/toolset/index.js.map +1 -1
- package/dist/toolset/jd.d.ts +10 -0
- package/dist/toolset/jd.d.ts.map +1 -0
- package/dist/toolset/jd.js +418 -0
- package/dist/toolset/jd.js.map +1 -0
- package/dist/toolset/list.d.ts +4 -0
- package/dist/toolset/list.d.ts.map +1 -0
- package/dist/toolset/list.js +156 -0
- package/dist/toolset/list.js.map +1 -0
- package/dist/toolset/list_candidates.d.ts.map +1 -1
- package/dist/toolset/list_candidates.js +12 -19
- package/dist/toolset/list_candidates.js.map +1 -1
- package/dist/toolset/login.d.ts.map +1 -1
- package/dist/toolset/login.js +7 -4
- package/dist/toolset/login.js.map +1 -1
- package/dist/toolset/open_chat.d.ts.map +1 -1
- package/dist/toolset/open_chat.js +266 -24
- package/dist/toolset/open_chat.js.map +1 -1
- package/dist/toolset/search.d.ts +2 -0
- package/dist/toolset/search.d.ts.map +1 -0
- package/dist/toolset/search.js +174 -0
- package/dist/toolset/search.js.map +1 -0
- package/dist/toolset/send.d.ts +12 -0
- package/dist/toolset/send.d.ts.map +1 -0
- package/dist/toolset/send.js +214 -0
- package/dist/toolset/send.js.map +1 -0
- package/dist/toolset/send_message.d.ts +11 -1
- package/dist/toolset/send_message.d.ts.map +1 -1
- package/dist/toolset/send_message.js +120 -21
- package/dist/toolset/send_message.js.map +1 -1
- package/dist/toolset/skill.d.ts +13 -0
- package/dist/toolset/skill.d.ts.map +1 -0
- package/dist/toolset/skill.js +85 -0
- package/dist/toolset/skill.js.map +1 -0
- package/package.json +5 -2
- package/skills/README.md +11 -0
- package/skills/boss-cli/SKILL.md +58 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { createWaitManualLoginRequiredText, sleepRandom, withChatPage, } from '../browser/index.js';
|
|
2
|
+
const BOSS_CHAT_AI_FORM_URL = 'https://www.zhipin.com/web/chat/aiform';
|
|
3
|
+
const AI_FORM_SETTLE_MS = { min: 1600, max: 2600 };
|
|
4
|
+
function isBossChatAiFormUrl(url) {
|
|
5
|
+
try {
|
|
6
|
+
const u = new URL(url);
|
|
7
|
+
if (!u.hostname.includes('zhipin.com')) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const p = u.pathname.replace(/\/+$/, '') || '/';
|
|
11
|
+
return p === '/web/chat/aiform';
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function clickSidebarMenuToPath(page, menuLabel, targetPath) {
|
|
18
|
+
const labelLiteral = JSON.stringify(menuLabel);
|
|
19
|
+
const pathLiteral = JSON.stringify(targetPath);
|
|
20
|
+
const clicked = (await page.evaluate(`(() => {
|
|
21
|
+
const label = ${labelLiteral};
|
|
22
|
+
const path = ${pathLiteral};
|
|
23
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, "");
|
|
24
|
+
const links = Array.from(document.querySelectorAll(".menu-list a"));
|
|
25
|
+
const target = links.find((a) => {
|
|
26
|
+
const href = a.getAttribute("href") ?? "";
|
|
27
|
+
if (href.includes(path)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const text = norm(a.querySelector(".menu-item-content span")?.textContent ?? a.textContent);
|
|
31
|
+
return text.includes(label);
|
|
32
|
+
});
|
|
33
|
+
if (!(target instanceof HTMLElement)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
37
|
+
target.click();
|
|
38
|
+
return true;
|
|
39
|
+
})()`));
|
|
40
|
+
if (!clicked) {
|
|
41
|
+
throw new Error(`未找到侧边栏菜单“${menuLabel}”,无法跳转到 ${targetPath}。`);
|
|
42
|
+
}
|
|
43
|
+
await page.waitForFunction(`(() => {
|
|
44
|
+
const path = ${pathLiteral};
|
|
45
|
+
try {
|
|
46
|
+
const p = window.location.pathname.replace(/\\/+$/g, "") || "/";
|
|
47
|
+
return p === path;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
})()`, { timeout: 15_000 });
|
|
52
|
+
}
|
|
53
|
+
async function waitForAiFormReady(page) {
|
|
54
|
+
await page.waitForFunction(`(() => {
|
|
55
|
+
const root = document.querySelector(".ai-form-left");
|
|
56
|
+
const submit = document.querySelector(".ai-form-match-footer .btn-ai-match-v2");
|
|
57
|
+
const selected = document.querySelector(".job-dropmenu-select .job-main-text");
|
|
58
|
+
if (!root || !submit || !selected) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const text = (selected.textContent ?? "").replace(/\\s+/g, " ").trim();
|
|
62
|
+
return text.length > 0;
|
|
63
|
+
})()`, { timeout: 15_000 });
|
|
64
|
+
}
|
|
65
|
+
async function ensureInDeepSearchPage(page) {
|
|
66
|
+
if (!isBossChatAiFormUrl(page.url())) {
|
|
67
|
+
throw new Error('当前不在深度搜索页(/web/chat/aiform),请先进入后再执行 search。');
|
|
68
|
+
}
|
|
69
|
+
await waitForAiFormReady(page);
|
|
70
|
+
}
|
|
71
|
+
async function readSearchFormSnapshot(page) {
|
|
72
|
+
return (await page.evaluate(`(() => {
|
|
73
|
+
const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
|
|
74
|
+
const selectedJob = norm(document.querySelector(".job-dropmenu-select .job-main-text")?.textContent);
|
|
75
|
+
const sections = Array.from(document.querySelectorAll(".form-content"));
|
|
76
|
+
const coreRequirements = [];
|
|
77
|
+
const bonusRequirements = [];
|
|
78
|
+
for (const section of sections) {
|
|
79
|
+
const title = norm(section.querySelector(".form-content-header .form-content-title-h3")?.textContent);
|
|
80
|
+
const words = Array.from(section.querySelectorAll(".form-content-list-item .form-content-word"))
|
|
81
|
+
.map((el) => norm(el.textContent))
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
if (title.includes("核心要求")) {
|
|
84
|
+
coreRequirements.push(...words);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (title.includes("加分项")) {
|
|
88
|
+
bonusRequirements.push(...words);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const remainingCountText = norm(document.querySelector(".ai-form-match-footer-text-count")?.textContent);
|
|
92
|
+
return {
|
|
93
|
+
selectedJob,
|
|
94
|
+
coreRequirements,
|
|
95
|
+
bonusRequirements,
|
|
96
|
+
remainingCountText,
|
|
97
|
+
};
|
|
98
|
+
})()`));
|
|
99
|
+
}
|
|
100
|
+
async function clickMatchNow(page) {
|
|
101
|
+
const clicked = (await page.evaluate(`(() => {
|
|
102
|
+
function norm(v) {
|
|
103
|
+
return (v ?? "").replace(/\\s+/g, "").trim();
|
|
104
|
+
}
|
|
105
|
+
function isVisible(el) {
|
|
106
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
107
|
+
const st = window.getComputedStyle(el);
|
|
108
|
+
if (st.display === "none" || st.visibility === "hidden") return false;
|
|
109
|
+
const r = el.getBoundingClientRect();
|
|
110
|
+
return r.width > 0 && r.height > 0;
|
|
111
|
+
}
|
|
112
|
+
const buttons = Array.from(
|
|
113
|
+
document.querySelectorAll(".ai-form-match-footer .btn-ai-match-v2, .ai-form-match-footer-btn .btn-ai-common, .ai-form-match-footer-btn .light-flow-btn-content"),
|
|
114
|
+
).filter((el) => isVisible(el));
|
|
115
|
+
if (buttons.length === 0) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const preferred = buttons.find((el) => norm(el.textContent).includes("立即匹配")) ?? buttons[0];
|
|
119
|
+
if (!(preferred instanceof HTMLElement)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
preferred.scrollIntoView({ block: "center", inline: "nearest" });
|
|
123
|
+
preferred.click();
|
|
124
|
+
return true;
|
|
125
|
+
})()`));
|
|
126
|
+
if (!clicked) {
|
|
127
|
+
throw new Error('未找到“立即匹配”按钮,无法执行深度搜索。');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function renderSearchResultText(before, after) {
|
|
131
|
+
const core = before.coreRequirements.length > 0 ? before.coreRequirements.join('|') : '(空)';
|
|
132
|
+
const bonus = before.bonusRequirements.length > 0 ? before.bonusRequirements.join('|') : '(空)';
|
|
133
|
+
const remain = before.remainingCountText || '未知';
|
|
134
|
+
const remainAfter = after.remainingCountText || '未知';
|
|
135
|
+
const remainLine = remain === remainAfter ? `今日匹配剩余:${remain}` : `今日匹配剩余:${remain} -> ${remainAfter}`;
|
|
136
|
+
return [
|
|
137
|
+
'已进入深度搜索并触发“立即匹配”。',
|
|
138
|
+
`职位:${before.selectedJob || '未知职位'}`,
|
|
139
|
+
`核心要求(${before.coreRequirements.length}):${core}`,
|
|
140
|
+
`加分项(${before.bonusRequirements.length}):${bonus}`,
|
|
141
|
+
remainLine,
|
|
142
|
+
`来源页面:${BOSS_CHAT_AI_FORM_URL}`,
|
|
143
|
+
].join('\n');
|
|
144
|
+
}
|
|
145
|
+
export async function runBossSearch() {
|
|
146
|
+
try {
|
|
147
|
+
return await withChatPage(async (page) => {
|
|
148
|
+
const currentUrl = page.url();
|
|
149
|
+
if (!isBossChatAiFormUrl(currentUrl)) {
|
|
150
|
+
await clickSidebarMenuToPath(page, '深度搜索', '/web/chat/aiform');
|
|
151
|
+
await sleepRandom(AI_FORM_SETTLE_MS.min, AI_FORM_SETTLE_MS.max);
|
|
152
|
+
}
|
|
153
|
+
if (!isBossChatAiFormUrl(page.url())) {
|
|
154
|
+
throw new Error('通过侧边栏“深度搜索”进入页面失败,请确认已登录并可访问 /web/chat/aiform。');
|
|
155
|
+
}
|
|
156
|
+
await ensureInDeepSearchPage(page);
|
|
157
|
+
const before = await readSearchFormSnapshot(page);
|
|
158
|
+
await clickMatchNow(page);
|
|
159
|
+
await sleepRandom(1200, 1800);
|
|
160
|
+
await ensureInDeepSearchPage(page);
|
|
161
|
+
const after = await readSearchFormSnapshot(page);
|
|
162
|
+
return renderSearchResultText(before, after);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
167
|
+
if (e instanceof Error && e.message.includes('浏览器会话尚未初始化')) {
|
|
168
|
+
throw new Error(createWaitManualLoginRequiredText('执行深度搜索'));
|
|
169
|
+
}
|
|
170
|
+
console.error(`[boss-cli] boss_search error: ${message}`);
|
|
171
|
+
throw new Error(`执行深度搜索失败:${message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/toolset/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** CLI `--action` 与内部逻辑共用 */
|
|
2
|
+
export type SendAction = 'request-resume' | 'agree-resume'
|
|
3
|
+
/** 在「附件简历」卡片上点「拒绝」 */
|
|
4
|
+
| 'confuse-resume';
|
|
5
|
+
export type SendChatMessageOptions = {
|
|
6
|
+
text?: string;
|
|
7
|
+
/** 可与 text 同次执行;若有先发消息,再 action,中间有默认随机间隔 */
|
|
8
|
+
action?: SendAction;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
};
|
|
11
|
+
export declare function runSendChatMessage(options: SendChatMessageOptions): Promise<string>;
|
|
12
|
+
//# sourceMappingURL=send.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/toolset/send.ts"],"names":[],"mappings":"AAcA,6BAA6B;AAC7B,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,cAAc;AAChB,sBAAsB;GACpB,gBAAgB,CAAC;AAErB,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAmKF,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgFzF"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createWaitManualLoginRequiredText, isBossChatIndexUrl, randomIntInclusive, SEND_AFTER_ENTER_MS, SEND_BEFORE_RESUME_MS, SEND_INPUT_CLICK_MS, SEND_TYPING_GAP_MS, sleepRandom, typeTextWithRandomKeyDelay, withChatPage, } from '../browser/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 对方「附件简历」确认卡片上点击「同意」或「拒绝」。
|
|
4
|
+
* 对应按钮 disabled 时视为已处理。
|
|
5
|
+
*/
|
|
6
|
+
async function runIncomingResumeCardAction(page, which) {
|
|
7
|
+
const currentUrl = page.url();
|
|
8
|
+
if (!isBossChatIndexUrl(currentUrl)) {
|
|
9
|
+
throw new Error('请先进入聊天列表页(/web/chat/index)并打开候选人聊天。');
|
|
10
|
+
}
|
|
11
|
+
const inCandidateChat = await page.$('.base-info-single-container');
|
|
12
|
+
if (!inCandidateChat) {
|
|
13
|
+
throw new Error('请先打开候选人聊天详情页,再操作附件简历卡片。');
|
|
14
|
+
}
|
|
15
|
+
await sleepRandom(200, 550);
|
|
16
|
+
const result = (await page.evaluate((w) => {
|
|
17
|
+
function norm(v) {
|
|
18
|
+
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
19
|
+
}
|
|
20
|
+
function isDisabledBtn(el) {
|
|
21
|
+
const cls = el.className ?? "";
|
|
22
|
+
if (/disabled|forbid|ban/i.test(cls))
|
|
23
|
+
return true;
|
|
24
|
+
if (el.getAttribute("disabled") !== null)
|
|
25
|
+
return true;
|
|
26
|
+
const st = window.getComputedStyle(el);
|
|
27
|
+
return st.pointerEvents === "none" || Number(st.opacity) < 0.35;
|
|
28
|
+
}
|
|
29
|
+
function matchesLabel(t, mode) {
|
|
30
|
+
if (mode === "agree") {
|
|
31
|
+
return t === "同意" || t.indexOf("同意") === 0;
|
|
32
|
+
}
|
|
33
|
+
return t === "拒绝" || t.indexOf("拒绝") === 0;
|
|
34
|
+
}
|
|
35
|
+
const items = Array.from(document.querySelectorAll(".chat-message-list .message-item"));
|
|
36
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
37
|
+
const friend = items[i].querySelector(".item-friend");
|
|
38
|
+
if (!friend)
|
|
39
|
+
continue;
|
|
40
|
+
const title = norm(friend.querySelector(".message-card-top-title")?.textContent);
|
|
41
|
+
if (!title || title.indexOf("附件简历") === -1)
|
|
42
|
+
continue;
|
|
43
|
+
const buttons = friend.querySelectorAll(".message-card-buttons .card-btn");
|
|
44
|
+
for (let j = 0; j < buttons.length; j++) {
|
|
45
|
+
const btn = buttons[j];
|
|
46
|
+
const t = norm(btn.textContent);
|
|
47
|
+
if (!matchesLabel(t, w))
|
|
48
|
+
continue;
|
|
49
|
+
if (isDisabledBtn(btn)) {
|
|
50
|
+
return { kind: "already_handled", which: w };
|
|
51
|
+
}
|
|
52
|
+
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
53
|
+
btn.click();
|
|
54
|
+
return { kind: "clicked", which: w };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { kind: "not_found", which: w };
|
|
58
|
+
}, which));
|
|
59
|
+
if (result.kind === 'not_found') {
|
|
60
|
+
throw new Error('未找到待处理的「对方发送附件简历」确认卡片(标题需含「附件简历」)。');
|
|
61
|
+
}
|
|
62
|
+
if (result.kind === 'already_handled') {
|
|
63
|
+
return result.which === 'agree'
|
|
64
|
+
? '对方发来的附件简历请求已处理(同意按钮已禁用)。'
|
|
65
|
+
: '对方发来的附件简历请求已处理(拒绝按钮已禁用)。';
|
|
66
|
+
}
|
|
67
|
+
await sleepRandom(350, 900);
|
|
68
|
+
return result.which === 'agree'
|
|
69
|
+
? '已点击「同意」,接受对方发送的附件简历。'
|
|
70
|
+
: '已点击「拒绝」,拒绝接收对方附件简历。';
|
|
71
|
+
}
|
|
72
|
+
async function runRequestOfflineResume(page) {
|
|
73
|
+
try {
|
|
74
|
+
const currentUrl = page.url();
|
|
75
|
+
if (!isBossChatIndexUrl(currentUrl)) {
|
|
76
|
+
throw new Error('请先进入聊天列表页(/web/chat/index)并打开候选人聊天。');
|
|
77
|
+
}
|
|
78
|
+
const inCandidateChat = await page.$('.base-info-single-container');
|
|
79
|
+
if (!inCandidateChat) {
|
|
80
|
+
throw new Error('请先打开候选人聊天详情页,再执行索取离线简历。');
|
|
81
|
+
}
|
|
82
|
+
const hasFriendResumeAttachment = (await page.evaluate(`(() => {
|
|
83
|
+
const items = Array.from(document.querySelectorAll(".chat-message-list .message-item"));
|
|
84
|
+
return items.some((item) => !!item.querySelector(".item-friend .resume-icon"));
|
|
85
|
+
})()`));
|
|
86
|
+
if (hasFriendResumeAttachment) {
|
|
87
|
+
return '历史消息中已有候选人发来的附件简历,已跳过索取离线简历。';
|
|
88
|
+
}
|
|
89
|
+
const availability = (await page.evaluate(`(() => {
|
|
90
|
+
const items = Array.from(document.querySelectorAll(".operate-icon-item"));
|
|
91
|
+
const target = items.find((el) => {
|
|
92
|
+
const text = (el.querySelector(".operate-btn")?.textContent ?? "").replace(/\\s+/g, "");
|
|
93
|
+
return text.includes("求简历");
|
|
94
|
+
});
|
|
95
|
+
if (!target) return { found: false, available: false };
|
|
96
|
+
const btn = target.querySelector(".operate-btn");
|
|
97
|
+
const className = [target.className ?? "", btn?.className ?? ""].join(" ");
|
|
98
|
+
const disabled = /disabled|forbid|ban/.test(className);
|
|
99
|
+
return { found: true, available: !disabled };
|
|
100
|
+
})()`));
|
|
101
|
+
if (!availability.found || !availability.available) {
|
|
102
|
+
throw new Error('当前不可索取离线简历。通常需要双方至少各发送过一条消息后,才会变为可索取状态。');
|
|
103
|
+
}
|
|
104
|
+
await sleepRandom(280, 720);
|
|
105
|
+
const clicked = (await page.evaluate(`(() => {
|
|
106
|
+
const items = Array.from(document.querySelectorAll(".operate-icon-item"));
|
|
107
|
+
const target = items.find((el) => {
|
|
108
|
+
const text = (el.querySelector(".operate-btn")?.textContent ?? "").replace(/\\s+/g, "");
|
|
109
|
+
return text.includes("求简历");
|
|
110
|
+
});
|
|
111
|
+
if (!target) return false;
|
|
112
|
+
const host = target;
|
|
113
|
+
host.scrollIntoView({ block: "center", inline: "nearest" });
|
|
114
|
+
const btn = target.querySelector(".operate-btn");
|
|
115
|
+
(btn || host).click();
|
|
116
|
+
return true;
|
|
117
|
+
})()`));
|
|
118
|
+
if (!clicked) {
|
|
119
|
+
throw new Error('未找到“求简历”按钮,无法执行索取。');
|
|
120
|
+
}
|
|
121
|
+
await sleepRandom(400, 980);
|
|
122
|
+
const confirmed = (await page.evaluate(`(() => {
|
|
123
|
+
const confirms = Array.from(document.querySelectorAll(".exchange-tooltip .boss-btn-primary.boss-btn"));
|
|
124
|
+
const visible = confirms.find((el) => {
|
|
125
|
+
const style = window.getComputedStyle(el);
|
|
126
|
+
return style.display !== "none" && style.visibility !== "hidden";
|
|
127
|
+
});
|
|
128
|
+
if (!visible) return false;
|
|
129
|
+
visible.click();
|
|
130
|
+
return true;
|
|
131
|
+
})()`));
|
|
132
|
+
if (!confirmed) {
|
|
133
|
+
throw new Error('已点击“求简历”,但未找到确认按钮(确定)。');
|
|
134
|
+
}
|
|
135
|
+
return '已发起索取离线简历请求。';
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
139
|
+
if (e instanceof Error) {
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`索取离线简历失败:${message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export async function runSendChatMessage(options) {
|
|
146
|
+
const messageText = (options.text ?? '').trim();
|
|
147
|
+
const action = options.action;
|
|
148
|
+
const signal = options.signal;
|
|
149
|
+
if (!messageText && !action) {
|
|
150
|
+
throw new Error('请指定 --text 或 --action 至少其一。');
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
return await withChatPage(async (page) => {
|
|
154
|
+
const currentUrl = page.url();
|
|
155
|
+
if (!isBossChatIndexUrl(currentUrl)) {
|
|
156
|
+
throw new Error('请先进入聊天列表页(/web/chat/index)并打开候选人聊天。');
|
|
157
|
+
}
|
|
158
|
+
const lines = [];
|
|
159
|
+
if (messageText) {
|
|
160
|
+
const input = await page.$('#boss-chat-editor-input');
|
|
161
|
+
if (!input) {
|
|
162
|
+
throw new Error('未找到聊天输入框(#boss-chat-editor-input)。');
|
|
163
|
+
}
|
|
164
|
+
await input.click({
|
|
165
|
+
delay: randomIntInclusive(SEND_INPUT_CLICK_MS.min, SEND_INPUT_CLICK_MS.max),
|
|
166
|
+
});
|
|
167
|
+
await sleepRandom(60, 220, signal);
|
|
168
|
+
await page.keyboard.down('Control');
|
|
169
|
+
await page.keyboard.press('KeyA');
|
|
170
|
+
await page.keyboard.up('Control');
|
|
171
|
+
await sleepRandom(45, 180, signal);
|
|
172
|
+
await page.keyboard.press('Backspace');
|
|
173
|
+
await sleepRandom(80, 260, signal);
|
|
174
|
+
await typeTextWithRandomKeyDelay(page, messageText, SEND_TYPING_GAP_MS.min, SEND_TYPING_GAP_MS.max, signal);
|
|
175
|
+
await sleepRandom(120, 420, signal);
|
|
176
|
+
await page.keyboard.press('Enter');
|
|
177
|
+
await sleepRandom(SEND_AFTER_ENTER_MS.min, SEND_AFTER_ENTER_MS.max, signal);
|
|
178
|
+
lines.push(`已发送消息:${messageText}`);
|
|
179
|
+
}
|
|
180
|
+
if (action) {
|
|
181
|
+
if (lines.length > 0) {
|
|
182
|
+
await sleepRandom(SEND_BEFORE_RESUME_MS.min, SEND_BEFORE_RESUME_MS.max, signal);
|
|
183
|
+
}
|
|
184
|
+
switch (action) {
|
|
185
|
+
case 'request-resume':
|
|
186
|
+
lines.push(await runRequestOfflineResume(page));
|
|
187
|
+
break;
|
|
188
|
+
case 'agree-resume':
|
|
189
|
+
lines.push(await runIncomingResumeCardAction(page, 'agree'));
|
|
190
|
+
break;
|
|
191
|
+
case 'confuse-resume':
|
|
192
|
+
lines.push(await runIncomingResumeCardAction(page, 'refuse'));
|
|
193
|
+
break;
|
|
194
|
+
default: {
|
|
195
|
+
const _x = action;
|
|
196
|
+
throw new Error(`未知的 action: ${String(_x)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return lines.join('\n\n');
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
205
|
+
if (e instanceof Error) {
|
|
206
|
+
if (e.message.includes('浏览器会话尚未初始化')) {
|
|
207
|
+
throw new Error(createWaitManualLoginRequiredText('发送消息'));
|
|
208
|
+
}
|
|
209
|
+
throw e;
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`发送消息失败:${message}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/toolset/send.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/** CLI `--action` 与内部逻辑共用 */
|
|
2
|
+
export type SendAction = 'request-resume' | 'agree-resume'
|
|
3
|
+
/** 在「附件简历」卡片上点「拒绝」 */
|
|
4
|
+
| 'confuse-resume';
|
|
5
|
+
export type SendChatMessageOptions = {
|
|
6
|
+
text?: string;
|
|
7
|
+
/** 可与 text 同次执行;若有先发消息,再 action,中间有默认随机间隔 */
|
|
8
|
+
action?: SendAction;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
};
|
|
11
|
+
export declare function runSendChatMessage(options: SendChatMessageOptions): Promise<string>;
|
|
2
12
|
//# sourceMappingURL=send_message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send_message.d.ts","sourceRoot":"","sources":["../../src/toolset/send_message.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"send_message.d.ts","sourceRoot":"","sources":["../../src/toolset/send_message.ts"],"names":[],"mappings":"AAcA,6BAA6B;AAC7B,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,cAAc;AAChB,sBAAsB;GACpB,gBAAgB,CAAC;AAErB,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAmKF,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgFzF"}
|
|
@@ -1,4 +1,74 @@
|
|
|
1
|
-
import { createWaitManualLoginRequiredText, isBossChatIndexUrl,
|
|
1
|
+
import { createWaitManualLoginRequiredText, isBossChatIndexUrl, randomIntInclusive, SEND_AFTER_ENTER_MS, SEND_BEFORE_RESUME_MS, SEND_INPUT_CLICK_MS, SEND_TYPING_GAP_MS, sleepRandom, typeTextWithRandomKeyDelay, withChatPage, } from '../browser/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 对方「附件简历」确认卡片上点击「同意」或「拒绝」。
|
|
4
|
+
* 对应按钮 disabled 时视为已处理。
|
|
5
|
+
*/
|
|
6
|
+
async function runIncomingResumeCardAction(page, which) {
|
|
7
|
+
const currentUrl = page.url();
|
|
8
|
+
if (!isBossChatIndexUrl(currentUrl)) {
|
|
9
|
+
throw new Error('请先进入聊天列表页(/web/chat/index)并打开候选人聊天。');
|
|
10
|
+
}
|
|
11
|
+
const inCandidateChat = await page.$('.base-info-single-container');
|
|
12
|
+
if (!inCandidateChat) {
|
|
13
|
+
throw new Error('请先打开候选人聊天详情页,再操作附件简历卡片。');
|
|
14
|
+
}
|
|
15
|
+
await sleepRandom(200, 550);
|
|
16
|
+
const result = (await page.evaluate((w) => {
|
|
17
|
+
function norm(v) {
|
|
18
|
+
return (v ?? "").replace(/\\s+/g, " ").trim();
|
|
19
|
+
}
|
|
20
|
+
function isDisabledBtn(el) {
|
|
21
|
+
const cls = el.className ?? "";
|
|
22
|
+
if (/disabled|forbid|ban/i.test(cls))
|
|
23
|
+
return true;
|
|
24
|
+
if (el.getAttribute("disabled") !== null)
|
|
25
|
+
return true;
|
|
26
|
+
const st = window.getComputedStyle(el);
|
|
27
|
+
return st.pointerEvents === "none" || Number(st.opacity) < 0.35;
|
|
28
|
+
}
|
|
29
|
+
function matchesLabel(t, mode) {
|
|
30
|
+
if (mode === "agree") {
|
|
31
|
+
return t === "同意" || t.indexOf("同意") === 0;
|
|
32
|
+
}
|
|
33
|
+
return t === "拒绝" || t.indexOf("拒绝") === 0;
|
|
34
|
+
}
|
|
35
|
+
const items = Array.from(document.querySelectorAll(".chat-message-list .message-item"));
|
|
36
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
37
|
+
const friend = items[i].querySelector(".item-friend");
|
|
38
|
+
if (!friend)
|
|
39
|
+
continue;
|
|
40
|
+
const title = norm(friend.querySelector(".message-card-top-title")?.textContent);
|
|
41
|
+
if (!title || title.indexOf("附件简历") === -1)
|
|
42
|
+
continue;
|
|
43
|
+
const buttons = friend.querySelectorAll(".message-card-buttons .card-btn");
|
|
44
|
+
for (let j = 0; j < buttons.length; j++) {
|
|
45
|
+
const btn = buttons[j];
|
|
46
|
+
const t = norm(btn.textContent);
|
|
47
|
+
if (!matchesLabel(t, w))
|
|
48
|
+
continue;
|
|
49
|
+
if (isDisabledBtn(btn)) {
|
|
50
|
+
return { kind: "already_handled", which: w };
|
|
51
|
+
}
|
|
52
|
+
btn.scrollIntoView({ block: "center", inline: "nearest" });
|
|
53
|
+
btn.click();
|
|
54
|
+
return { kind: "clicked", which: w };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { kind: "not_found", which: w };
|
|
58
|
+
}, which));
|
|
59
|
+
if (result.kind === 'not_found') {
|
|
60
|
+
throw new Error('未找到待处理的「对方发送附件简历」确认卡片(标题需含「附件简历」)。');
|
|
61
|
+
}
|
|
62
|
+
if (result.kind === 'already_handled') {
|
|
63
|
+
return result.which === 'agree'
|
|
64
|
+
? '对方发来的附件简历请求已处理(同意按钮已禁用)。'
|
|
65
|
+
: '对方发来的附件简历请求已处理(拒绝按钮已禁用)。';
|
|
66
|
+
}
|
|
67
|
+
await sleepRandom(350, 900);
|
|
68
|
+
return result.which === 'agree'
|
|
69
|
+
? '已点击「同意」,接受对方发送的附件简历。'
|
|
70
|
+
: '已点击「拒绝」,拒绝接收对方附件简历。';
|
|
71
|
+
}
|
|
2
72
|
async function runRequestOfflineResume(page) {
|
|
3
73
|
try {
|
|
4
74
|
const currentUrl = page.url();
|
|
@@ -31,6 +101,7 @@ async function runRequestOfflineResume(page) {
|
|
|
31
101
|
if (!availability.found || !availability.available) {
|
|
32
102
|
throw new Error('当前不可索取离线简历。通常需要双方至少各发送过一条消息后,才会变为可索取状态。');
|
|
33
103
|
}
|
|
104
|
+
await sleepRandom(280, 720);
|
|
34
105
|
const clicked = (await page.evaluate(`(() => {
|
|
35
106
|
const items = Array.from(document.querySelectorAll(".operate-icon-item"));
|
|
36
107
|
const target = items.find((el) => {
|
|
@@ -47,6 +118,7 @@ async function runRequestOfflineResume(page) {
|
|
|
47
118
|
if (!clicked) {
|
|
48
119
|
throw new Error('未找到“求简历”按钮,无法执行索取。');
|
|
49
120
|
}
|
|
121
|
+
await sleepRandom(400, 980);
|
|
50
122
|
const confirmed = (await page.evaluate(`(() => {
|
|
51
123
|
const confirms = Array.from(document.querySelectorAll(".exchange-tooltip .boss-btn-primary.boss-btn"));
|
|
52
124
|
const visible = confirms.find((el) => {
|
|
@@ -70,10 +142,12 @@ async function runRequestOfflineResume(page) {
|
|
|
70
142
|
throw new Error(`索取离线简历失败:${message}`);
|
|
71
143
|
}
|
|
72
144
|
}
|
|
73
|
-
export async function runSendChatMessage(
|
|
74
|
-
const messageText = text.trim();
|
|
75
|
-
|
|
76
|
-
|
|
145
|
+
export async function runSendChatMessage(options) {
|
|
146
|
+
const messageText = (options.text ?? '').trim();
|
|
147
|
+
const action = options.action;
|
|
148
|
+
const signal = options.signal;
|
|
149
|
+
if (!messageText && !action) {
|
|
150
|
+
throw new Error('请指定 --text 或 --action 至少其一。');
|
|
77
151
|
}
|
|
78
152
|
try {
|
|
79
153
|
return await withChatPage(async (page) => {
|
|
@@ -81,28 +155,53 @@ export async function runSendChatMessage(text, alsoRequestResume = false, signal
|
|
|
81
155
|
if (!isBossChatIndexUrl(currentUrl)) {
|
|
82
156
|
throw new Error('请先进入聊天列表页(/web/chat/index)并打开候选人聊天。');
|
|
83
157
|
}
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
|
|
158
|
+
const lines = [];
|
|
159
|
+
if (messageText) {
|
|
160
|
+
const input = await page.$('#boss-chat-editor-input');
|
|
161
|
+
if (!input) {
|
|
162
|
+
throw new Error('未找到聊天输入框(#boss-chat-editor-input)。');
|
|
163
|
+
}
|
|
164
|
+
await input.click({
|
|
165
|
+
delay: randomIntInclusive(SEND_INPUT_CLICK_MS.min, SEND_INPUT_CLICK_MS.max),
|
|
166
|
+
});
|
|
167
|
+
await sleepRandom(60, 220, signal);
|
|
168
|
+
await page.keyboard.down('Control');
|
|
169
|
+
await page.keyboard.press('KeyA');
|
|
170
|
+
await page.keyboard.up('Control');
|
|
171
|
+
await sleepRandom(45, 180, signal);
|
|
172
|
+
await page.keyboard.press('Backspace');
|
|
173
|
+
await sleepRandom(80, 260, signal);
|
|
174
|
+
await typeTextWithRandomKeyDelay(page, messageText, SEND_TYPING_GAP_MS.min, SEND_TYPING_GAP_MS.max, signal);
|
|
175
|
+
await sleepRandom(120, 420, signal);
|
|
176
|
+
await page.keyboard.press('Enter');
|
|
177
|
+
await sleepRandom(SEND_AFTER_ENTER_MS.min, SEND_AFTER_ENTER_MS.max, signal);
|
|
178
|
+
lines.push(`已发送消息:${messageText}`);
|
|
87
179
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
180
|
+
if (action) {
|
|
181
|
+
if (lines.length > 0) {
|
|
182
|
+
await sleepRandom(SEND_BEFORE_RESUME_MS.min, SEND_BEFORE_RESUME_MS.max, signal);
|
|
183
|
+
}
|
|
184
|
+
switch (action) {
|
|
185
|
+
case 'request-resume':
|
|
186
|
+
lines.push(await runRequestOfflineResume(page));
|
|
187
|
+
break;
|
|
188
|
+
case 'agree-resume':
|
|
189
|
+
lines.push(await runIncomingResumeCardAction(page, 'agree'));
|
|
190
|
+
break;
|
|
191
|
+
case 'confuse-resume':
|
|
192
|
+
lines.push(await runIncomingResumeCardAction(page, 'refuse'));
|
|
193
|
+
break;
|
|
194
|
+
default: {
|
|
195
|
+
const _x = action;
|
|
196
|
+
throw new Error(`未知的 action: ${String(_x)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
97
199
|
}
|
|
98
|
-
|
|
99
|
-
const resumeLine = await runRequestOfflineResume(page);
|
|
100
|
-
return [`已发送消息:${messageText}`, resumeLine].filter(Boolean).join('\n\n');
|
|
200
|
+
return lines.join('\n\n');
|
|
101
201
|
});
|
|
102
202
|
}
|
|
103
203
|
catch (e) {
|
|
104
204
|
const message = e instanceof Error ? e.message : String(e);
|
|
105
|
-
console.error(`[boss-cli] send_chat_message error: ${message}`);
|
|
106
205
|
if (e instanceof Error) {
|
|
107
206
|
if (e.message.includes('浏览器会话尚未初始化')) {
|
|
108
207
|
throw new Error(createWaitManualLoginRequiredText('发送消息'));
|