@jackwener/opencli 0.6.2 β 0.6.3
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/cli-manifest.json +39 -0
- package/dist/clis/boss/detail.d.ts +1 -0
- package/dist/clis/boss/detail.js +104 -0
- package/dist/clis/boss/search.js +2 -1
- package/package.json +1 -1
- package/src/clis/boss/detail.ts +115 -0
- package/src/clis/boss/search.ts +2 -1
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ npm install -g @jackwener/opencli@latest
|
|
|
139
139
|
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `following` `followers` `notifications` `post` `reply` `delete` `like` | π Browser |
|
|
140
140
|
| **reddit** | `hot` `frontpage` `search` `subreddit` | π Browser |
|
|
141
141
|
| **weibo** | `hot` | π Browser |
|
|
142
|
-
| **boss** | `search` | π Browser |
|
|
142
|
+
| **boss** | `search` `detail` | π Browser |
|
|
143
143
|
| **coupang** | `search` `add-to-cart` | π Browser |
|
|
144
144
|
| **youtube** | `search` | π Browser |
|
|
145
145
|
| **yahoo-finance** | `quote` | π Browser |
|
package/README.zh-CN.md
CHANGED
|
@@ -138,7 +138,7 @@ npm install -g @jackwener/opencli@latest
|
|
|
138
138
|
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `following` `followers` `notifications` `post` `reply` `delete` `like` | π ζ΅θ§ε¨ |
|
|
139
139
|
| **reddit** | `hot` `frontpage` `search` `subreddit` | π ζ΅θ§ε¨ |
|
|
140
140
|
| **weibo** | `hot` | π ζ΅θ§ε¨ |
|
|
141
|
-
| **boss** | `search` | π ζ΅θ§ε¨ |
|
|
141
|
+
| **boss** | `search` `detail` | π ζ΅θ§ε¨ |
|
|
142
142
|
| **coupang** | `search` `add-to-cart` | π ζ΅θ§ε¨ |
|
|
143
143
|
| **youtube** | `search` | π ζ΅θ§ε¨ |
|
|
144
144
|
| **yahoo-finance** | `quote` | π ζ΅θ§ε¨ |
|
package/dist/cli-manifest.json
CHANGED
|
@@ -392,6 +392,44 @@
|
|
|
392
392
|
"url"
|
|
393
393
|
]
|
|
394
394
|
},
|
|
395
|
+
{
|
|
396
|
+
"site": "boss",
|
|
397
|
+
"name": "detail",
|
|
398
|
+
"description": "BOSSη΄θζ₯ηθδ½θ―¦ζ
",
|
|
399
|
+
"strategy": "cookie",
|
|
400
|
+
"browser": true,
|
|
401
|
+
"args": [
|
|
402
|
+
{
|
|
403
|
+
"name": "security_id",
|
|
404
|
+
"type": "str",
|
|
405
|
+
"required": true,
|
|
406
|
+
"help": "Security ID from search results (securityId field)"
|
|
407
|
+
}
|
|
408
|
+
],
|
|
409
|
+
"type": "ts",
|
|
410
|
+
"modulePath": "boss/detail.js",
|
|
411
|
+
"domain": "www.zhipin.com",
|
|
412
|
+
"columns": [
|
|
413
|
+
"name",
|
|
414
|
+
"salary",
|
|
415
|
+
"experience",
|
|
416
|
+
"degree",
|
|
417
|
+
"city",
|
|
418
|
+
"district",
|
|
419
|
+
"description",
|
|
420
|
+
"skills",
|
|
421
|
+
"welfare",
|
|
422
|
+
"boss_name",
|
|
423
|
+
"boss_title",
|
|
424
|
+
"active_time",
|
|
425
|
+
"company",
|
|
426
|
+
"industry",
|
|
427
|
+
"scale",
|
|
428
|
+
"stage",
|
|
429
|
+
"address",
|
|
430
|
+
"url"
|
|
431
|
+
]
|
|
432
|
+
},
|
|
395
433
|
{
|
|
396
434
|
"site": "boss",
|
|
397
435
|
"name": "search",
|
|
@@ -467,6 +505,7 @@
|
|
|
467
505
|
"degree",
|
|
468
506
|
"skills",
|
|
469
507
|
"boss",
|
|
508
|
+
"security_id",
|
|
470
509
|
"url"
|
|
471
510
|
]
|
|
472
511
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOSSη΄θ job detail β fetch full job posting details via browser cookie API.
|
|
3
|
+
*
|
|
4
|
+
* Uses securityId from search results to call the detail API.
|
|
5
|
+
* Returns: job description, skills, welfare, boss info, company info, address.
|
|
6
|
+
*/
|
|
7
|
+
import { cli, Strategy } from '../../registry.js';
|
|
8
|
+
cli({
|
|
9
|
+
site: 'boss',
|
|
10
|
+
name: 'detail',
|
|
11
|
+
description: 'BOSSη΄θζ₯ηθδ½θ―¦ζ
',
|
|
12
|
+
domain: 'www.zhipin.com',
|
|
13
|
+
strategy: Strategy.COOKIE,
|
|
14
|
+
browser: true,
|
|
15
|
+
args: [
|
|
16
|
+
{ name: 'security_id', required: true, help: 'Security ID from search results (securityId field)' },
|
|
17
|
+
],
|
|
18
|
+
columns: [
|
|
19
|
+
'name', 'salary', 'experience', 'degree', 'city', 'district',
|
|
20
|
+
'description', 'skills', 'welfare',
|
|
21
|
+
'boss_name', 'boss_title', 'active_time',
|
|
22
|
+
'company', 'industry', 'scale', 'stage',
|
|
23
|
+
'address', 'url',
|
|
24
|
+
],
|
|
25
|
+
func: async (page, kwargs) => {
|
|
26
|
+
if (!page)
|
|
27
|
+
throw new Error('Browser page required');
|
|
28
|
+
const securityId = kwargs.security_id;
|
|
29
|
+
// Navigate to zhipin.com first to establish cookie context (referrer + cookies)
|
|
30
|
+
await page.goto('https://www.zhipin.com/web/geek/job');
|
|
31
|
+
await page.wait({ time: 1 });
|
|
32
|
+
const targetUrl = `https://www.zhipin.com/wapi/zpgeek/job/detail.json?securityId=${encodeURIComponent(securityId)}`;
|
|
33
|
+
if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
|
|
34
|
+
console.error(`[opencli:boss] Fetching job detail...`);
|
|
35
|
+
}
|
|
36
|
+
const evaluateScript = `
|
|
37
|
+
async () => {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const xhr = new window.XMLHttpRequest();
|
|
40
|
+
xhr.open('GET', ${JSON.stringify(targetUrl)}, true);
|
|
41
|
+
xhr.withCredentials = true;
|
|
42
|
+
xhr.timeout = 15000;
|
|
43
|
+
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
|
|
44
|
+
xhr.onload = () => {
|
|
45
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
46
|
+
try {
|
|
47
|
+
resolve(JSON.parse(xhr.responseText));
|
|
48
|
+
} catch (e) {
|
|
49
|
+
reject(new Error('Failed to parse JSON. Raw (200 chars): ' + xhr.responseText.substring(0, 200)));
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
reject(new Error('XHR HTTP Status: ' + xhr.status));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
xhr.onerror = () => reject(new Error('XHR Network Error'));
|
|
56
|
+
xhr.ontimeout = () => reject(new Error('XHR Timeout'));
|
|
57
|
+
xhr.send();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
let data;
|
|
62
|
+
try {
|
|
63
|
+
data = await page.evaluate(evaluateScript);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
throw new Error('API evaluate failed: ' + e.message);
|
|
67
|
+
}
|
|
68
|
+
if (data.code !== 0) {
|
|
69
|
+
if (data.code === 37) {
|
|
70
|
+
throw new Error('Cookie ε·²θΏζοΌθ―·ε¨ε½ε Chrome ζ΅θ§ε¨δΈιζ°η»ε½ BOSS η΄θγ');
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`BOSS API error: ${data.message || 'Unknown'} (code=${data.code})`);
|
|
73
|
+
}
|
|
74
|
+
const zpData = data.zpData || {};
|
|
75
|
+
const jobInfo = zpData.jobInfo || {};
|
|
76
|
+
const bossInfo = zpData.bossInfo || {};
|
|
77
|
+
const brandComInfo = zpData.brandComInfo || {};
|
|
78
|
+
if (!jobInfo.jobName) {
|
|
79
|
+
throw new Error('θ―₯θδ½δΏ‘ζ―δΈεε¨ζε·²δΈζΆ');
|
|
80
|
+
}
|
|
81
|
+
return [{
|
|
82
|
+
name: jobInfo.jobName || '',
|
|
83
|
+
salary: jobInfo.salaryDesc || '',
|
|
84
|
+
experience: jobInfo.experienceName || '',
|
|
85
|
+
degree: jobInfo.degreeName || '',
|
|
86
|
+
city: jobInfo.locationName || '',
|
|
87
|
+
district: [jobInfo.areaDistrict, jobInfo.businessDistrict].filter(Boolean).join('Β·'),
|
|
88
|
+
description: jobInfo.postDescription || '',
|
|
89
|
+
skills: (jobInfo.showSkills || []).join(', '),
|
|
90
|
+
welfare: (brandComInfo.labels || []).join(', '),
|
|
91
|
+
boss_name: bossInfo.name || '',
|
|
92
|
+
boss_title: bossInfo.title || '',
|
|
93
|
+
active_time: bossInfo.activeTimeDesc || '',
|
|
94
|
+
company: brandComInfo.brandName || bossInfo.brandName || '',
|
|
95
|
+
industry: brandComInfo.industryName || '',
|
|
96
|
+
scale: brandComInfo.scaleName || '',
|
|
97
|
+
stage: brandComInfo.stageName || '',
|
|
98
|
+
address: jobInfo.address || '',
|
|
99
|
+
url: jobInfo.encryptId
|
|
100
|
+
? 'https://www.zhipin.com/job_detail/' + jobInfo.encryptId + '.html'
|
|
101
|
+
: '',
|
|
102
|
+
}];
|
|
103
|
+
},
|
|
104
|
+
});
|
package/dist/clis/boss/search.js
CHANGED
|
@@ -78,7 +78,7 @@ cli({
|
|
|
78
78
|
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
79
79
|
{ name: 'limit', type: 'int', default: 15, help: 'Number of results' },
|
|
80
80
|
],
|
|
81
|
-
columns: ['name', 'salary', 'company', 'area', 'experience', 'degree', 'skills', 'boss', 'url'],
|
|
81
|
+
columns: ['name', 'salary', 'company', 'area', 'experience', 'degree', 'skills', 'boss', 'security_id', 'url'],
|
|
82
82
|
func: async (page, kwargs) => {
|
|
83
83
|
if (!page)
|
|
84
84
|
throw new Error('Browser page required');
|
|
@@ -180,6 +180,7 @@ cli({
|
|
|
180
180
|
degree: j.jobDegree,
|
|
181
181
|
skills: (j.skills || []).join(','),
|
|
182
182
|
boss: j.bossName + ' Β· ' + j.bossTitle,
|
|
183
|
+
security_id: j.securityId || '',
|
|
183
184
|
url: 'https://www.zhipin.com/job_detail/' + j.encryptJobId + '.html',
|
|
184
185
|
});
|
|
185
186
|
addedInBatch++;
|
package/package.json
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOSSη΄θ job detail β fetch full job posting details via browser cookie API.
|
|
3
|
+
*
|
|
4
|
+
* Uses securityId from search results to call the detail API.
|
|
5
|
+
* Returns: job description, skills, welfare, boss info, company info, address.
|
|
6
|
+
*/
|
|
7
|
+
import { cli, Strategy } from '../../registry.js';
|
|
8
|
+
import type { IPage } from '../../types.js';
|
|
9
|
+
|
|
10
|
+
cli({
|
|
11
|
+
site: 'boss',
|
|
12
|
+
name: 'detail',
|
|
13
|
+
description: 'BOSSη΄θζ₯ηθδ½θ―¦ζ
',
|
|
14
|
+
domain: 'www.zhipin.com',
|
|
15
|
+
strategy: Strategy.COOKIE,
|
|
16
|
+
|
|
17
|
+
browser: true,
|
|
18
|
+
args: [
|
|
19
|
+
{ name: 'security_id', required: true, help: 'Security ID from search results (securityId field)' },
|
|
20
|
+
],
|
|
21
|
+
columns: [
|
|
22
|
+
'name', 'salary', 'experience', 'degree', 'city', 'district',
|
|
23
|
+
'description', 'skills', 'welfare',
|
|
24
|
+
'boss_name', 'boss_title', 'active_time',
|
|
25
|
+
'company', 'industry', 'scale', 'stage',
|
|
26
|
+
'address', 'url',
|
|
27
|
+
],
|
|
28
|
+
func: async (page: IPage | null, kwargs) => {
|
|
29
|
+
if (!page) throw new Error('Browser page required');
|
|
30
|
+
|
|
31
|
+
const securityId = kwargs.security_id;
|
|
32
|
+
|
|
33
|
+
// Navigate to zhipin.com first to establish cookie context (referrer + cookies)
|
|
34
|
+
await page.goto('https://www.zhipin.com/web/geek/job');
|
|
35
|
+
await page.wait({ time: 1 });
|
|
36
|
+
|
|
37
|
+
const targetUrl = `https://www.zhipin.com/wapi/zpgeek/job/detail.json?securityId=${encodeURIComponent(securityId)}`;
|
|
38
|
+
|
|
39
|
+
if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
|
|
40
|
+
console.error(`[opencli:boss] Fetching job detail...`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const evaluateScript = `
|
|
44
|
+
async () => {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const xhr = new window.XMLHttpRequest();
|
|
47
|
+
xhr.open('GET', ${JSON.stringify(targetUrl)}, true);
|
|
48
|
+
xhr.withCredentials = true;
|
|
49
|
+
xhr.timeout = 15000;
|
|
50
|
+
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
|
|
51
|
+
xhr.onload = () => {
|
|
52
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
53
|
+
try {
|
|
54
|
+
resolve(JSON.parse(xhr.responseText));
|
|
55
|
+
} catch (e) {
|
|
56
|
+
reject(new Error('Failed to parse JSON. Raw (200 chars): ' + xhr.responseText.substring(0, 200)));
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
reject(new Error('XHR HTTP Status: ' + xhr.status));
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
xhr.onerror = () => reject(new Error('XHR Network Error'));
|
|
63
|
+
xhr.ontimeout = () => reject(new Error('XHR Timeout'));
|
|
64
|
+
xhr.send();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
let data: any;
|
|
70
|
+
try {
|
|
71
|
+
data = await page.evaluate(evaluateScript);
|
|
72
|
+
} catch (e: any) {
|
|
73
|
+
throw new Error('API evaluate failed: ' + e.message);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (data.code !== 0) {
|
|
77
|
+
if (data.code === 37) {
|
|
78
|
+
throw new Error('Cookie ε·²θΏζοΌθ―·ε¨ε½ε Chrome ζ΅θ§ε¨δΈιζ°η»ε½ BOSS η΄θγ');
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`BOSS API error: ${data.message || 'Unknown'} (code=${data.code})`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const zpData = data.zpData || {};
|
|
84
|
+
const jobInfo = zpData.jobInfo || {};
|
|
85
|
+
const bossInfo = zpData.bossInfo || {};
|
|
86
|
+
const brandComInfo = zpData.brandComInfo || {};
|
|
87
|
+
|
|
88
|
+
if (!jobInfo.jobName) {
|
|
89
|
+
throw new Error('θ―₯θδ½δΏ‘ζ―δΈεε¨ζε·²δΈζΆ');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return [{
|
|
93
|
+
name: jobInfo.jobName || '',
|
|
94
|
+
salary: jobInfo.salaryDesc || '',
|
|
95
|
+
experience: jobInfo.experienceName || '',
|
|
96
|
+
degree: jobInfo.degreeName || '',
|
|
97
|
+
city: jobInfo.locationName || '',
|
|
98
|
+
district: [jobInfo.areaDistrict, jobInfo.businessDistrict].filter(Boolean).join('Β·'),
|
|
99
|
+
description: jobInfo.postDescription || '',
|
|
100
|
+
skills: (jobInfo.showSkills || []).join(', '),
|
|
101
|
+
welfare: (brandComInfo.labels || []).join(', '),
|
|
102
|
+
boss_name: bossInfo.name || '',
|
|
103
|
+
boss_title: bossInfo.title || '',
|
|
104
|
+
active_time: bossInfo.activeTimeDesc || '',
|
|
105
|
+
company: brandComInfo.brandName || bossInfo.brandName || '',
|
|
106
|
+
industry: brandComInfo.industryName || '',
|
|
107
|
+
scale: brandComInfo.scaleName || '',
|
|
108
|
+
stage: brandComInfo.stageName || '',
|
|
109
|
+
address: jobInfo.address || '',
|
|
110
|
+
url: jobInfo.encryptId
|
|
111
|
+
? 'https://www.zhipin.com/job_detail/' + jobInfo.encryptId + '.html'
|
|
112
|
+
: '',
|
|
113
|
+
}];
|
|
114
|
+
},
|
|
115
|
+
});
|
package/src/clis/boss/search.ts
CHANGED
|
@@ -81,7 +81,7 @@ cli({
|
|
|
81
81
|
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
82
82
|
{ name: 'limit', type: 'int', default: 15, help: 'Number of results' },
|
|
83
83
|
],
|
|
84
|
-
columns: ['name', 'salary', 'company', 'area', 'experience', 'degree', 'skills', 'boss', 'url'],
|
|
84
|
+
columns: ['name', 'salary', 'company', 'area', 'experience', 'degree', 'skills', 'boss', 'security_id', 'url'],
|
|
85
85
|
func: async (page: IPage | null, kwargs) => {
|
|
86
86
|
if (!page) throw new Error('Browser page required');
|
|
87
87
|
|
|
@@ -191,6 +191,7 @@ cli({
|
|
|
191
191
|
degree: j.jobDegree,
|
|
192
192
|
skills: (j.skills || []).join(','),
|
|
193
193
|
boss: j.bossName + ' Β· ' + j.bossTitle,
|
|
194
|
+
security_id: j.securityId || '',
|
|
194
195
|
url: 'https://www.zhipin.com/job_detail/' + j.encryptJobId + '.html',
|
|
195
196
|
});
|
|
196
197
|
addedInBatch++;
|