@reconcrap/boss-recruit-mcp 1.0.0
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 +82 -0
- package/bin/boss-recruit-mcp.js +2 -0
- package/config/screening-config.example.json +7 -0
- package/package.json +42 -0
- package/skills/boss-recruit-pipeline/README.md +20 -0
- package/skills/boss-recruit-pipeline/SKILL.md +83 -0
- package/src/adapters.js +352 -0
- package/src/cli.js +112 -0
- package/src/index.js +210 -0
- package/src/parser.js +221 -0
- package/src/pipeline.js +215 -0
- package/src/test-parser.js +79 -0
- package/vendor/boss-screen-cli/boss-screen-cli.cjs +1646 -0
- package/vendor/boss-screen-cli/favorite-calibration.json +9 -0
- package/vendor/boss-search-cli/src/boss-searcher.js +667 -0
- package/vendor/boss-search-cli/src/chrome-connector.js +79 -0
- package/vendor/boss-search-cli/src/cli.js +132 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import CDP from 'chrome-remote-interface';
|
|
2
|
+
|
|
3
|
+
export class ChromeConnector {
|
|
4
|
+
constructor(port = 9222) {
|
|
5
|
+
this.port = port;
|
|
6
|
+
this.client = null;
|
|
7
|
+
this.DOM = null;
|
|
8
|
+
this.Runtime = null;
|
|
9
|
+
this.Page = null;
|
|
10
|
+
this.Input = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async connect() {
|
|
14
|
+
try {
|
|
15
|
+
console.log(`正在连接远程Chrome (端口: ${this.port})...`);
|
|
16
|
+
this.client = await CDP({ port: this.port });
|
|
17
|
+
const { DOM, Runtime, Page, Input } = this.client;
|
|
18
|
+
this.DOM = DOM;
|
|
19
|
+
this.Runtime = Runtime;
|
|
20
|
+
this.Page = Page;
|
|
21
|
+
this.Input = Input;
|
|
22
|
+
|
|
23
|
+
await Promise.all([
|
|
24
|
+
DOM.enable(),
|
|
25
|
+
Runtime.enable(),
|
|
26
|
+
Page.enable()
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
console.log('✅ 成功连接到远程Chrome');
|
|
30
|
+
return true;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('❌ 连接远程Chrome失败:', error.message);
|
|
33
|
+
console.error('请确保Chrome已通过以下命令启动:');
|
|
34
|
+
console.error('chrome.exe --remote-debugging-port=9222');
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async evaluate(expression) {
|
|
40
|
+
const result = await this.Runtime.evaluate({
|
|
41
|
+
expression,
|
|
42
|
+
returnByValue: true,
|
|
43
|
+
awaitPromise: true
|
|
44
|
+
});
|
|
45
|
+
if (result.exceptionDetails) {
|
|
46
|
+
throw new Error(result.exceptionDetails.exception.description);
|
|
47
|
+
}
|
|
48
|
+
return result.result.value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getDocument() {
|
|
52
|
+
const { root } = await this.DOM.getDocument();
|
|
53
|
+
return root;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async clickElement(selector) {
|
|
57
|
+
await this.evaluate(`
|
|
58
|
+
(async () => {
|
|
59
|
+
const el = document.querySelector('${selector}');
|
|
60
|
+
if (el) {
|
|
61
|
+
el.click();
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
})()
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async sleep(ms) {
|
|
70
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async disconnect() {
|
|
74
|
+
if (this.client) {
|
|
75
|
+
await this.client.close();
|
|
76
|
+
console.log('已断开与Chrome的连接');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { BossSearcher } from './boss-searcher.js';
|
|
3
|
+
|
|
4
|
+
class BossSearchCLI {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.searcher = new BossSearcher(9222);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async run() {
|
|
10
|
+
console.log('========================================');
|
|
11
|
+
console.log(' Boss直聘搜索自动化工具');
|
|
12
|
+
console.log('========================================\n');
|
|
13
|
+
|
|
14
|
+
const args = this.parseArgs();
|
|
15
|
+
|
|
16
|
+
const connected = await this.searcher.connect();
|
|
17
|
+
if (!connected) {
|
|
18
|
+
console.log('\n请确保Chrome已通过以下命令启动:');
|
|
19
|
+
console.log('chrome.exe --remote-debugging-port=9222');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await this.searcher.sleep(500);
|
|
25
|
+
|
|
26
|
+
console.log('🔄 刷新iframe清除现有搜索条件...');
|
|
27
|
+
await this.searcher.refreshIframe();
|
|
28
|
+
console.log('');
|
|
29
|
+
|
|
30
|
+
// 第一步:必须先选择"不限职位",否则后续设置的城市会被重置
|
|
31
|
+
console.log('💼 选择"不限职位"...');
|
|
32
|
+
await this.searcher.setJobTitle('不限职位');
|
|
33
|
+
await this.searcher.sleep(500);
|
|
34
|
+
console.log('');
|
|
35
|
+
|
|
36
|
+
// 第二步:设置其他过滤条件(城市、学历、院校等)
|
|
37
|
+
if (args.city) {
|
|
38
|
+
await this.searcher.setCity(args.city);
|
|
39
|
+
await this.searcher.sleep(500);
|
|
40
|
+
console.log('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await this.searchWithConfig(args);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('❌ 执行出错:', error);
|
|
46
|
+
} finally {
|
|
47
|
+
await this.searcher.disconnect();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
parseArgs() {
|
|
52
|
+
const args = {
|
|
53
|
+
keywords: '',
|
|
54
|
+
degree: '不限',
|
|
55
|
+
schools: [],
|
|
56
|
+
city: null,
|
|
57
|
+
experience: '不限',
|
|
58
|
+
ageMin: null,
|
|
59
|
+
ageMax: null
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const schoolMap = {
|
|
63
|
+
'211': '211院校',
|
|
64
|
+
'985': '985院校',
|
|
65
|
+
'qs100': 'QS 100',
|
|
66
|
+
'qs500': 'QS 500',
|
|
67
|
+
'双一流': '双一流院校',
|
|
68
|
+
'留学生': '留学生',
|
|
69
|
+
'统招': '统招本科'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const argv = process.argv.slice(2);
|
|
73
|
+
for (let i = 0; i < argv.length; i++) {
|
|
74
|
+
const arg = argv[i];
|
|
75
|
+
if (arg === '--keywords' || arg === '-k') {
|
|
76
|
+
args.keywords = argv[++i];
|
|
77
|
+
} else if (arg === '--degree' || arg === '-d') {
|
|
78
|
+
args.degree = argv[++i];
|
|
79
|
+
} else if (arg === '--schools' || arg === '-s') {
|
|
80
|
+
const schools = argv[++i].split(',');
|
|
81
|
+
args.schools = schools.map(function(s) {
|
|
82
|
+
const normalized = s.trim().toLowerCase();
|
|
83
|
+
return schoolMap[normalized] || s;
|
|
84
|
+
});
|
|
85
|
+
} else if (arg === '--city' || arg === '-c') {
|
|
86
|
+
args.city = argv[++i];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!args.keywords) {
|
|
91
|
+
console.log('⚠️ 未指定搜索关键词,使用默认值');
|
|
92
|
+
args.keywords = '算法工程师';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return args;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async searchWithConfig(config) {
|
|
99
|
+
console.log('📋 搜索配置:');
|
|
100
|
+
console.log(' 关键词:', config.keywords);
|
|
101
|
+
console.log(' 学历要求:', config.degree);
|
|
102
|
+
if (config.schools.length > 0) {
|
|
103
|
+
console.log(' 院校要求:', config.schools.join(', '));
|
|
104
|
+
}
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
await this.searcher.sleep(500);
|
|
108
|
+
|
|
109
|
+
if (config.degree && config.degree !== '不限') {
|
|
110
|
+
await this.searcher.setDegree(config.degree);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (config.schools.length > 0) {
|
|
114
|
+
await this.searcher.setSchoolRequirements(config.schools);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (config.keywords) {
|
|
118
|
+
await this.searcher.setKeywords(config.keywords);
|
|
119
|
+
console.log(' [DEBUG] 关键词设置完成,等待500ms让下拉提示消失...');
|
|
120
|
+
await this.searcher.sleep(500);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await this.searcher.clickSearch();
|
|
124
|
+
await this.searcher.sleep(2000);
|
|
125
|
+
await this.searcher.getResults();
|
|
126
|
+
|
|
127
|
+
console.log('\n✅ 搜索完成!');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const cli = new BossSearchCLI();
|
|
132
|
+
cli.run();
|