@harness.farm/social-cli 0.1.5 → 0.1.6
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/dist/cli.js +6 -0
- package/dist/scripts/zhipin-download-resumes.js +78 -68
- package/package.json +2 -3
package/dist/cli.js
CHANGED
|
@@ -42,6 +42,12 @@ if (!hasYaml && !tsAdapter) {
|
|
|
42
42
|
// Support both positional args and --key value flags
|
|
43
43
|
const args = parseArgs(rest);
|
|
44
44
|
// ── Run ───────────────────────────────────────────────────────────────────
|
|
45
|
+
// TypeScript sub-commands that bypass YAML/TS adapter dispatch
|
|
46
|
+
if (platform === 'zhipin' && command === 'resumes') {
|
|
47
|
+
const { downloadResumes } = await import('./scripts/zhipin-download-resumes.js');
|
|
48
|
+
await downloadResumes(args.positional[0]);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
45
51
|
if (hasYaml) {
|
|
46
52
|
// YAML adapter: pass positional args in order
|
|
47
53
|
runYamlCommand(yamlPath, command, args.positional).catch(err => {
|
|
@@ -7,13 +7,12 @@
|
|
|
7
7
|
* - 每份简历滚动截多张图,保存为 PNG 序列
|
|
8
8
|
*
|
|
9
9
|
* 用法:
|
|
10
|
+
* social-cli zhipin resumes [outputDir]
|
|
10
11
|
* npx tsx src/scripts/zhipin-download-resumes.ts [outputDir]
|
|
11
|
-
* npm run zhipin:resumes [-- ./my-resumes]
|
|
12
12
|
*/
|
|
13
13
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
14
14
|
import { resolve } from 'path';
|
|
15
15
|
import { connectTab, sleep } from '../browser/cdp.js';
|
|
16
|
-
const OUTPUT_DIR = resolve(process.argv[2] ?? './resumes');
|
|
17
16
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
18
17
|
async function scrollLoadAll(client) {
|
|
19
18
|
for (let i = 0; i < 8; i++) {
|
|
@@ -107,75 +106,86 @@ async function closeDialog(client) {
|
|
|
107
106
|
await sleep(600);
|
|
108
107
|
}
|
|
109
108
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
await
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
109
|
+
export async function downloadResumes(outDir) {
|
|
110
|
+
const OUTPUT_DIR = resolve(outDir ?? './resumes');
|
|
111
|
+
console.log(`\n📁 输出目录: ${OUTPUT_DIR}`);
|
|
112
|
+
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
113
|
+
const chatClient = await connectTab(9222, 0);
|
|
114
|
+
await chatClient.navigate('https://www.zhipin.com/web/chat/index', 4500);
|
|
115
|
+
console.log('\n⏳ 加载候选人列表...');
|
|
116
|
+
await scrollLoadAll(chatClient);
|
|
117
|
+
const candidates = await getCandidates(chatClient);
|
|
118
|
+
console.log(`✅ 找到 ${candidates.length} 位候选人\n`);
|
|
119
|
+
const summary = [];
|
|
120
|
+
for (const c of candidates) {
|
|
121
|
+
const label = `[${c.index + 1}/${candidates.length}] ${c.name} (${c.job})`;
|
|
122
|
+
console.log(`📄 ${label}`);
|
|
123
|
+
const dir = resolve(OUTPUT_DIR, `${String(c.index + 1).padStart(3, '0')}_${c.name}`);
|
|
124
|
+
mkdirSync(dir, { recursive: true });
|
|
125
|
+
try {
|
|
126
|
+
// 点击候选人
|
|
127
|
+
await chatClient.eval(`
|
|
128
|
+
(function(){
|
|
129
|
+
var item = document.querySelector('[data-id="${c.dataId}"]');
|
|
130
|
+
if(item) item.click();
|
|
131
|
+
})()
|
|
132
|
+
`);
|
|
133
|
+
await sleep(1800);
|
|
134
|
+
// 等待在线简历按钮出现
|
|
135
|
+
let btnReady = false;
|
|
136
|
+
for (let t = 0; t < 10; t++) {
|
|
137
|
+
const has = await chatClient.eval(`!!document.querySelector("a.resume-btn-online")`);
|
|
138
|
+
if (has) {
|
|
139
|
+
btnReady = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
await sleep(500);
|
|
140
143
|
}
|
|
141
|
-
|
|
144
|
+
if (!btnReady)
|
|
145
|
+
throw new Error('在线简历按钮未出现');
|
|
146
|
+
// 点击在线简历
|
|
147
|
+
await chatClient.eval(`document.querySelector("a.resume-btn-online").click()`);
|
|
148
|
+
await sleep(3500); // 等待 WASM 加载并渲染 Canvas
|
|
149
|
+
// 截图
|
|
150
|
+
const prefix = `resume`;
|
|
151
|
+
const screenshots = await screenshotResume(chatClient, dir, prefix);
|
|
152
|
+
summary.push({ name: c.name, job: c.job, uid: c.uid, screenshots });
|
|
153
|
+
console.log(` ✅ 截图 ${screenshots.length} 张 → ${dir}`);
|
|
154
|
+
// 关闭弹窗
|
|
155
|
+
await closeDialog(chatClient);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
159
|
+
console.log(` ❌ 失败: ${msg}`);
|
|
160
|
+
summary.push({ name: c.name, job: c.job, uid: c.uid, screenshots: [], error: msg });
|
|
161
|
+
// 尝试关闭弹窗后继续
|
|
162
|
+
await closeDialog(chatClient).catch(() => { });
|
|
142
163
|
}
|
|
143
|
-
|
|
144
|
-
throw new Error('在线简历按钮未出现');
|
|
145
|
-
// 点击在线简历
|
|
146
|
-
await chatClient.eval(`document.querySelector("a.resume-btn-online").click()`);
|
|
147
|
-
await sleep(3500); // 等待 WASM 加载并渲染 Canvas
|
|
148
|
-
// 截图
|
|
149
|
-
const prefix = `resume`;
|
|
150
|
-
const screenshots = await screenshotResume(chatClient, dir, prefix);
|
|
151
|
-
summary.push({ name: c.name, job: c.job, uid: c.uid, screenshots });
|
|
152
|
-
console.log(` ✅ 截图 ${screenshots.length} 张 → ${dir}`);
|
|
153
|
-
// 关闭弹窗
|
|
154
|
-
await closeDialog(chatClient);
|
|
164
|
+
await sleep(500);
|
|
155
165
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
// 保存汇总
|
|
167
|
+
const summaryPath = resolve(OUTPUT_DIR, '_summary.json');
|
|
168
|
+
writeFileSync(summaryPath, JSON.stringify({
|
|
169
|
+
total: candidates.length,
|
|
170
|
+
downloaded: summary.filter(s => s.screenshots.length > 0).length,
|
|
171
|
+
failed: summary.filter(s => !!s.error).length,
|
|
172
|
+
candidates: summary,
|
|
173
|
+
}, null, 2), 'utf-8');
|
|
174
|
+
console.log(`\n${'─'.repeat(50)}`);
|
|
175
|
+
console.log(`✅ 完成: ${summary.filter(s => s.screenshots.length > 0).length}/${candidates.length} 份简历`);
|
|
176
|
+
if (summary.some(s => s.error)) {
|
|
177
|
+
console.log(`❌ 失败: ${summary.filter(s => s.error).length} 份`);
|
|
178
|
+
summary.filter(s => s.error).forEach(s => console.log(` - ${s.name}: ${s.error}`));
|
|
162
179
|
}
|
|
163
|
-
|
|
180
|
+
console.log(`📄 汇总: ${summaryPath}`);
|
|
181
|
+
console.log(`📁 目录: ${OUTPUT_DIR}`);
|
|
182
|
+
chatClient.close();
|
|
164
183
|
}
|
|
165
|
-
//
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}, null, 2), 'utf-8');
|
|
173
|
-
console.log(`\n${'─'.repeat(50)}`);
|
|
174
|
-
console.log(`✅ 完成: ${summary.filter(s => s.screenshots.length > 0).length}/${candidates.length} 份简历`);
|
|
175
|
-
if (summary.some(s => s.error)) {
|
|
176
|
-
console.log(`❌ 失败: ${summary.filter(s => s.error).length} 份`);
|
|
177
|
-
summary.filter(s => s.error).forEach(s => console.log(` - ${s.name}: ${s.error}`));
|
|
184
|
+
// 直接执行时运行
|
|
185
|
+
const isDirectRun = process.argv[1]?.includes('zhipin-download-resumes');
|
|
186
|
+
if (isDirectRun) {
|
|
187
|
+
downloadResumes(process.argv[2]).catch(err => {
|
|
188
|
+
console.error('❌', err.message ?? err);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
});
|
|
178
191
|
}
|
|
179
|
-
console.log(`📄 汇总: ${summaryPath}`);
|
|
180
|
-
console.log(`📁 目录: ${OUTPUT_DIR}`);
|
|
181
|
-
chatClient.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness.farm/social-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "CDP-based social media automation CLI — X, 小红书, 抖音, B站, Temu",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
"bilibili": "tsx src/cli.ts bilibili",
|
|
24
24
|
"temu": "tsx src/cli.ts temu",
|
|
25
25
|
"zhipin": "tsx src/cli.ts zhipin",
|
|
26
|
-
"zhipin:resumes": "tsx src/
|
|
27
|
-
"zhipin:screen": "tsx src/scripts/zhipin-screen-resumes.ts"
|
|
26
|
+
"zhipin:resumes": "tsx src/cli.ts zhipin resumes"
|
|
28
27
|
},
|
|
29
28
|
"keywords": [
|
|
30
29
|
"social-media",
|