@reconcrap/boss-recommend-mcp 0.1.0 → 0.1.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.
- package/README.md +3 -0
- package/package.json +1 -1
- package/skills/boss-recommend-pipeline/SKILL.md +1 -0
- package/src/adapters.js +11 -0
- package/src/index.js +17 -0
- package/src/parser.js +112 -0
- package/src/test-parser.js +50 -0
- package/src/test-pipeline.js +2 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +126 -2
- package/vendor/boss-recommend-search-cli/src/cli.js +222 -12
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ MCP 工具名:`run_recommend_pipeline`
|
|
|
20
20
|
|
|
21
21
|
- 页面目标固定为 `https://www.zhipin.com/web/chat/recommend`
|
|
22
22
|
- 支持推荐页原生筛选:学校标签 / 性别 / 近14天没有
|
|
23
|
+
- 支持推荐页原生筛选:学校标签 / 学历 / 性别 / 近14天没有
|
|
24
|
+
- 学历支持单选与多选语义:如“本科及以上”会展开为 `本科/硕士/博士`;如“大专、本科”只勾选这两项
|
|
23
25
|
- `post_action` 必须在每次完整运行开始时确认一次
|
|
24
26
|
- `target_count` 会在每次运行开始时询问一次(可留空,不设上限)
|
|
25
27
|
- 当 `post_action=greet` 时,必须在运行开始时确认 `max_greet_count`
|
|
@@ -102,6 +104,7 @@ node src/cli.js run --instruction-file request.txt --confirmation-file confirmat
|
|
|
102
104
|
},
|
|
103
105
|
"overrides": {
|
|
104
106
|
"school_tag": "211",
|
|
107
|
+
"degree": ["本科", "硕士", "博士"],
|
|
105
108
|
"gender": "女",
|
|
106
109
|
"recent_not_view": "近14天没有",
|
|
107
110
|
"criteria": "候选人需要有 AI Agent 或 MCP 工具开发经验",
|
package/package.json
CHANGED
|
@@ -50,6 +50,7 @@ description: "Use when users ask to run Boss recommend-page filtering and screen
|
|
|
50
50
|
- `max_greet_count_value` (integer)
|
|
51
51
|
- `overrides`
|
|
52
52
|
- `school_tag`
|
|
53
|
+
- `degree`(可传单值或数组;如“本科及以上”应展开为 `["本科","硕士","博士"]`)
|
|
53
54
|
- `gender`
|
|
54
55
|
- `recent_not_view`
|
|
55
56
|
- `criteria`
|
package/src/adapters.js
CHANGED
|
@@ -36,6 +36,15 @@ function parsePositiveInteger(raw) {
|
|
|
36
36
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function serializeDegreeSelection(value) {
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
const normalized = value.map((item) => String(item || "").trim()).filter(Boolean);
|
|
42
|
+
return normalized.length ? normalized.join(",") : "不限";
|
|
43
|
+
}
|
|
44
|
+
const normalized = String(value || "").trim();
|
|
45
|
+
return normalized || "不限";
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
function resolveScreenConfigPath(workspaceRoot) {
|
|
40
49
|
const envConfigPath = process.env.BOSS_RECOMMEND_SCREEN_CONFIG
|
|
41
50
|
? path.resolve(process.env.BOSS_RECOMMEND_SCREEN_CONFIG)
|
|
@@ -506,6 +515,8 @@ export async function runRecommendSearchCli({ workspaceRoot, searchParams }) {
|
|
|
506
515
|
cliPath,
|
|
507
516
|
"--school-tag",
|
|
508
517
|
searchParams.school_tag,
|
|
518
|
+
"--degree",
|
|
519
|
+
serializeDegreeSelection(searchParams.degree),
|
|
509
520
|
"--gender",
|
|
510
521
|
searchParams.gender,
|
|
511
522
|
"--recent-not-view",
|
package/src/index.js
CHANGED
|
@@ -64,6 +64,23 @@ function createToolSchema() {
|
|
|
64
64
|
type: "string",
|
|
65
65
|
enum: ["不限", "985", "211", "双一流院校", "留学", "国内外名校", "公办本科"]
|
|
66
66
|
},
|
|
67
|
+
degree: {
|
|
68
|
+
oneOf: [
|
|
69
|
+
{
|
|
70
|
+
type: "string",
|
|
71
|
+
enum: ["不限", "初中及以下", "中专/中技", "高中", "大专", "本科", "硕士", "博士"]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: "array",
|
|
75
|
+
items: {
|
|
76
|
+
type: "string",
|
|
77
|
+
enum: ["不限", "初中及以下", "中专/中技", "高中", "大专", "本科", "硕士", "博士"]
|
|
78
|
+
},
|
|
79
|
+
minItems: 1,
|
|
80
|
+
uniqueItems: true
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
},
|
|
67
84
|
gender: {
|
|
68
85
|
type: "string",
|
|
69
86
|
enum: ["不限", "男", "女"]
|
package/src/parser.js
CHANGED
|
@@ -7,6 +7,25 @@ const SCHOOL_TAG_OPTIONS = [
|
|
|
7
7
|
"国内外名校",
|
|
8
8
|
"公办本科"
|
|
9
9
|
];
|
|
10
|
+
const DEGREE_OPTIONS = [
|
|
11
|
+
"不限",
|
|
12
|
+
"初中及以下",
|
|
13
|
+
"中专/中技",
|
|
14
|
+
"高中",
|
|
15
|
+
"大专",
|
|
16
|
+
"本科",
|
|
17
|
+
"硕士",
|
|
18
|
+
"博士"
|
|
19
|
+
];
|
|
20
|
+
const DEGREE_ORDER = [
|
|
21
|
+
"初中及以下",
|
|
22
|
+
"中专/中技",
|
|
23
|
+
"高中",
|
|
24
|
+
"大专",
|
|
25
|
+
"本科",
|
|
26
|
+
"硕士",
|
|
27
|
+
"博士"
|
|
28
|
+
];
|
|
10
29
|
const GENDER_OPTIONS = ["不限", "男", "女"];
|
|
11
30
|
const RECENT_NOT_VIEW_OPTIONS = ["不限", "近14天没有"];
|
|
12
31
|
const POST_ACTION_OPTIONS = ["favorite", "greet"];
|
|
@@ -30,6 +49,15 @@ const SCHOOL_TAG_PATTERNS = [
|
|
|
30
49
|
{ label: "国内外名校", pattern: /国内外名校|名校/i },
|
|
31
50
|
{ label: "公办本科", pattern: /公办本科/i }
|
|
32
51
|
];
|
|
52
|
+
const DEGREE_PATTERNS = [
|
|
53
|
+
{ label: "初中及以下", pattern: /初中及以下|初中以下/i },
|
|
54
|
+
{ label: "中专/中技", pattern: /中专\s*\/\s*中技|中专中技|中专|中技/i },
|
|
55
|
+
{ label: "高中", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}高中/i },
|
|
56
|
+
{ label: "大专", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:大专|专科)/i },
|
|
57
|
+
{ label: "本科", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:本科|学士)/i },
|
|
58
|
+
{ label: "硕士", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:硕士|研究生)/i },
|
|
59
|
+
{ label: "博士", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}博士/i }
|
|
60
|
+
];
|
|
33
61
|
const GENDER_PATTERNS = [
|
|
34
62
|
{ label: "男", pattern: /(?:性别|候选人|人选)?[^。;;\n]{0,8}(?:男生|男性|男)/i },
|
|
35
63
|
{ label: "女", pattern: /(?:性别|候选人|人选)?[^。;;\n]{0,8}(?:女生|女性|女)/i }
|
|
@@ -57,6 +85,7 @@ const MAX_GREET_COUNT_PATTERNS = [
|
|
|
57
85
|
];
|
|
58
86
|
const FILTER_CLAUSE_PATTERNS = [
|
|
59
87
|
/学校标签|院校标签|985|211|双一流|留学|国内外名校|公办本科/i,
|
|
88
|
+
/学历|学位|教育|初中及以下|中专|中技|高中|大专|专科|本科|硕士|研究生|博士/i,
|
|
60
89
|
/性别|男生|女生|男性|女性|男\b|女\b/i,
|
|
61
90
|
/近?14天(?:内)?没有|近?14天(?:内)?没看过|近?14天(?:内)?未查看|过滤[^。;;\n]{0,12}14天|排除[^。;;\n]{0,12}14天/i,
|
|
62
91
|
/目标(?:处理|筛选)?(?:人数|数量)?|至少(?:处理|筛选)|(?:处理|筛选)\s*\d+\s*(?:位|人)/i,
|
|
@@ -89,6 +118,75 @@ function normalizeSchoolTag(value) {
|
|
|
89
118
|
return null;
|
|
90
119
|
}
|
|
91
120
|
|
|
121
|
+
function normalizeDegree(value) {
|
|
122
|
+
const normalized = normalizeText(value);
|
|
123
|
+
if (!normalized) return null;
|
|
124
|
+
if (normalized === "专科") return "大专";
|
|
125
|
+
if (normalized === "研究生") return "硕士";
|
|
126
|
+
if (normalized === "中专" || normalized === "中技" || normalized === "中专中技") return "中专/中技";
|
|
127
|
+
return DEGREE_OPTIONS.includes(normalized) ? normalized : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function sortDegreeSelections(values) {
|
|
131
|
+
return uniqueList(values).sort((left, right) => {
|
|
132
|
+
const leftIndex = DEGREE_ORDER.indexOf(left);
|
|
133
|
+
const rightIndex = DEGREE_ORDER.indexOf(right);
|
|
134
|
+
return leftIndex - rightIndex;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function expandDegreeAtOrAbove(value) {
|
|
139
|
+
const normalized = normalizeDegree(value);
|
|
140
|
+
if (!normalized || normalized === "不限") return [];
|
|
141
|
+
const startIndex = DEGREE_ORDER.indexOf(normalized);
|
|
142
|
+
if (startIndex === -1) return [];
|
|
143
|
+
return DEGREE_ORDER.slice(startIndex);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseDegreeSelectionsFromText(text) {
|
|
147
|
+
const normalizedText = normalizeText(text);
|
|
148
|
+
if (!normalizedText) return [];
|
|
149
|
+
if (/(?:学历|学位|教育)[^。;;\n]{0,6}不限|不限[^。;;\n]{0,6}(?:学历|学位|教育)/i.test(normalizedText)) {
|
|
150
|
+
return ["不限"];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const selected = [];
|
|
154
|
+
const atOrAbovePattern = /(初中及以下|中专\/中技|中专中技|中专|中技|高中|大专|专科|本科|硕士|研究生|博士)\s*(?:及|或)?以上/g;
|
|
155
|
+
let match;
|
|
156
|
+
while ((match = atOrAbovePattern.exec(normalizedText)) !== null) {
|
|
157
|
+
selected.push(...expandDegreeAtOrAbove(match[1]));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
for (const { label, pattern } of DEGREE_PATTERNS) {
|
|
161
|
+
if (pattern.test(normalizedText)) {
|
|
162
|
+
selected.push(label);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return sortDegreeSelections(selected);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeDegreeSelections(input) {
|
|
169
|
+
if (Array.isArray(input)) {
|
|
170
|
+
const normalized = sortDegreeSelections(input.map((item) => normalizeDegree(item)).filter(Boolean));
|
|
171
|
+
if (!normalized.length) return null;
|
|
172
|
+
return normalized.includes("不限") ? ["不限"] : normalized;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const text = normalizeText(input);
|
|
176
|
+
if (!text) return null;
|
|
177
|
+
if (text.includes("以上")) {
|
|
178
|
+
const fromText = parseDegreeSelectionsFromText(text);
|
|
179
|
+
if (fromText.length) return fromText;
|
|
180
|
+
}
|
|
181
|
+
const parts = text.split(/[,,、/|]/).map((item) => normalizeDegree(item)).filter(Boolean);
|
|
182
|
+
if (parts.length) {
|
|
183
|
+
const normalized = sortDegreeSelections(parts);
|
|
184
|
+
return normalized.includes("不限") ? ["不限"] : normalized;
|
|
185
|
+
}
|
|
186
|
+
const single = normalizeDegree(text);
|
|
187
|
+
return single ? [single] : null;
|
|
188
|
+
}
|
|
189
|
+
|
|
92
190
|
function normalizeGender(value) {
|
|
93
191
|
const normalized = normalizeText(value);
|
|
94
192
|
if (!normalized) return null;
|
|
@@ -142,6 +240,10 @@ function extractGender(text) {
|
|
|
142
240
|
return null;
|
|
143
241
|
}
|
|
144
242
|
|
|
243
|
+
function extractDegrees(text) {
|
|
244
|
+
return parseDegreeSelectionsFromText(text);
|
|
245
|
+
}
|
|
246
|
+
|
|
145
247
|
function extractRecentNotView(text) {
|
|
146
248
|
for (const pattern of RECENT_NOT_VIEW_NEGATIVE_PATTERNS) {
|
|
147
249
|
if (pattern.test(text)) {
|
|
@@ -298,13 +400,22 @@ function collectSuspiciousFields({ detectedSchoolTags }) {
|
|
|
298
400
|
export function parseRecommendInstruction({ instruction, confirmation, overrides }) {
|
|
299
401
|
const text = normalizeText(instruction);
|
|
300
402
|
const detectedSchoolTags = extractSchoolTags(text);
|
|
403
|
+
const detectedDegrees = extractDegrees(text);
|
|
301
404
|
const overrideSchoolTag = normalizeSchoolTag(overrides?.school_tag);
|
|
405
|
+
const overrideDegrees = normalizeDegreeSelections(overrides?.degree);
|
|
302
406
|
const overrideGender = normalizeGender(overrides?.gender);
|
|
303
407
|
const overrideRecentNotView = normalizeRecentNotView(overrides?.recent_not_view);
|
|
304
408
|
const overrideCriteria = overrides?.criteria;
|
|
305
409
|
|
|
306
410
|
const searchParams = {
|
|
307
411
|
school_tag: overrideSchoolTag || detectedSchoolTags[0] || "不限",
|
|
412
|
+
degree: (
|
|
413
|
+
(Array.isArray(overrideDegrees) && overrideDegrees.length > 0
|
|
414
|
+
? overrideDegrees
|
|
415
|
+
: Array.isArray(detectedDegrees) && detectedDegrees.length > 0
|
|
416
|
+
? detectedDegrees
|
|
417
|
+
: ["不限"])
|
|
418
|
+
),
|
|
308
419
|
gender: overrideGender || extractGender(text) || "不限",
|
|
309
420
|
recent_not_view: overrideRecentNotView || extractRecentNotView(text) || "不限"
|
|
310
421
|
};
|
|
@@ -415,6 +526,7 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
415
526
|
}
|
|
416
527
|
|
|
417
528
|
export {
|
|
529
|
+
DEGREE_OPTIONS,
|
|
418
530
|
GENDER_OPTIONS,
|
|
419
531
|
POST_ACTION_LABELS,
|
|
420
532
|
POST_ACTION_OPTIONS,
|
package/src/test-parser.js
CHANGED
|
@@ -9,6 +9,7 @@ function testNeedConfirmationIncludesPostAction() {
|
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
assert.equal(result.searchParams.school_tag, "985");
|
|
12
|
+
assert.deepEqual(result.searchParams.degree, ["不限"]);
|
|
12
13
|
assert.equal(result.searchParams.gender, "男");
|
|
13
14
|
assert.equal(result.searchParams.recent_not_view, "近14天没有");
|
|
14
15
|
assert.equal(result.screenParams.criteria, "有大模型平台经验");
|
|
@@ -33,12 +34,14 @@ function testConfirmedPostActionAndOverrides() {
|
|
|
33
34
|
},
|
|
34
35
|
overrides: {
|
|
35
36
|
school_tag: "211",
|
|
37
|
+
degree: "本科",
|
|
36
38
|
recent_not_view: "近14天没有",
|
|
37
39
|
target_count: 12
|
|
38
40
|
}
|
|
39
41
|
});
|
|
40
42
|
|
|
41
43
|
assert.equal(result.searchParams.school_tag, "211");
|
|
44
|
+
assert.deepEqual(result.searchParams.degree, ["本科"]);
|
|
42
45
|
assert.equal(result.searchParams.gender, "女");
|
|
43
46
|
assert.equal(result.searchParams.recent_not_view, "近14天没有");
|
|
44
47
|
assert.equal(result.screenParams.criteria, "有多模态经历");
|
|
@@ -65,10 +68,53 @@ function testMultipleSchoolTagsMarkedSuspicious() {
|
|
|
65
68
|
});
|
|
66
69
|
|
|
67
70
|
assert.equal(result.searchParams.school_tag, "985");
|
|
71
|
+
assert.deepEqual(result.searchParams.degree, ["不限"]);
|
|
68
72
|
assert.equal(result.suspicious_fields.length, 1);
|
|
69
73
|
assert.equal(result.suspicious_fields[0].field, "school_tag");
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
function testDegreeCanBeExtracted() {
|
|
77
|
+
const result = parseRecommendInstruction({
|
|
78
|
+
instruction: "推荐页筛选本科女生,近14天没有,有大模型项目经验",
|
|
79
|
+
confirmation: null,
|
|
80
|
+
overrides: null
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
assert.deepEqual(result.searchParams.degree, ["本科"]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function testDegreeAtOrAboveExpansion() {
|
|
87
|
+
const result = parseRecommendInstruction({
|
|
88
|
+
instruction: "推荐页筛选大专及以上,近14天没有,有Agent经验",
|
|
89
|
+
confirmation: null,
|
|
90
|
+
overrides: null
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
assert.deepEqual(result.searchParams.degree, ["大专", "本科", "硕士", "博士"]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function testDegreeExplicitListOnly() {
|
|
97
|
+
const result = parseRecommendInstruction({
|
|
98
|
+
instruction: "推荐页筛选大专、本科,近14天没有,有Agent经验",
|
|
99
|
+
confirmation: null,
|
|
100
|
+
overrides: null
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.deepEqual(result.searchParams.degree, ["大专", "本科"]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function testDegreeOverrideCanBeArray() {
|
|
107
|
+
const result = parseRecommendInstruction({
|
|
108
|
+
instruction: "推荐页筛选本科,近14天没有,有Agent经验",
|
|
109
|
+
confirmation: null,
|
|
110
|
+
overrides: {
|
|
111
|
+
degree: ["大专", "本科"]
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
assert.deepEqual(result.searchParams.degree, ["大专", "本科"]);
|
|
116
|
+
}
|
|
117
|
+
|
|
72
118
|
function testCriteriaCanBeProvidedViaOverrides() {
|
|
73
119
|
const result = parseRecommendInstruction({
|
|
74
120
|
instruction: "推荐页筛选211女生",
|
|
@@ -190,6 +236,10 @@ function main() {
|
|
|
190
236
|
testNeedConfirmationIncludesPostAction();
|
|
191
237
|
testConfirmedPostActionAndOverrides();
|
|
192
238
|
testMultipleSchoolTagsMarkedSuspicious();
|
|
239
|
+
testDegreeCanBeExtracted();
|
|
240
|
+
testDegreeAtOrAboveExpansion();
|
|
241
|
+
testDegreeExplicitListOnly();
|
|
242
|
+
testDegreeOverrideCanBeArray();
|
|
193
243
|
testCriteriaCanBeProvidedViaOverrides();
|
|
194
244
|
testMissingCriteriaTriggersNeedInput();
|
|
195
245
|
testMcpMentionShouldStayInCriteria();
|
package/src/test-pipeline.js
CHANGED
|
@@ -5,6 +5,7 @@ function createParsed(overrides = {}) {
|
|
|
5
5
|
return {
|
|
6
6
|
searchParams: {
|
|
7
7
|
school_tag: "985",
|
|
8
|
+
degree: ["本科"],
|
|
8
9
|
gender: "男",
|
|
9
10
|
recent_not_view: "近14天没有"
|
|
10
11
|
},
|
|
@@ -180,6 +181,7 @@ async function testCompletedPipeline() {
|
|
|
180
181
|
assert.equal(result.result.processed_count, 10);
|
|
181
182
|
assert.equal(result.result.passed_count, 3);
|
|
182
183
|
assert.equal(result.result.post_action, "favorite");
|
|
184
|
+
assert.deepEqual(result.result.applied_filters.degree, ["本科"]);
|
|
183
185
|
assert.equal(calls[0].type, "search");
|
|
184
186
|
assert.equal(calls[1].type, "screen");
|
|
185
187
|
}
|
|
@@ -53,7 +53,18 @@ function parseArgs(argv) {
|
|
|
53
53
|
output: path.resolve(process.cwd(), `筛选结果_${Date.now()}.csv`),
|
|
54
54
|
postAction: null,
|
|
55
55
|
postActionConfirmed: null,
|
|
56
|
-
help: false
|
|
56
|
+
help: false,
|
|
57
|
+
__provided: {
|
|
58
|
+
baseUrl: false,
|
|
59
|
+
apiKey: false,
|
|
60
|
+
model: false,
|
|
61
|
+
criteria: false,
|
|
62
|
+
targetCount: false,
|
|
63
|
+
maxGreetCount: false,
|
|
64
|
+
port: false,
|
|
65
|
+
postAction: false,
|
|
66
|
+
postActionConfirmed: false
|
|
67
|
+
}
|
|
57
68
|
};
|
|
58
69
|
|
|
59
70
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -61,12 +72,15 @@ function parseArgs(argv) {
|
|
|
61
72
|
const next = argv[index + 1];
|
|
62
73
|
if (token === "--baseurl" && next) {
|
|
63
74
|
parsed.baseUrl = next;
|
|
75
|
+
parsed.__provided.baseUrl = true;
|
|
64
76
|
index += 1;
|
|
65
77
|
} else if (token === "--apikey" && next) {
|
|
66
78
|
parsed.apiKey = next;
|
|
79
|
+
parsed.__provided.apiKey = true;
|
|
67
80
|
index += 1;
|
|
68
81
|
} else if (token === "--model" && next) {
|
|
69
82
|
parsed.model = next;
|
|
83
|
+
parsed.__provided.model = true;
|
|
70
84
|
index += 1;
|
|
71
85
|
} else if (token === "--openai-organization" && next) {
|
|
72
86
|
parsed.openaiOrganization = next;
|
|
@@ -76,24 +90,30 @@ function parseArgs(argv) {
|
|
|
76
90
|
index += 1;
|
|
77
91
|
} else if (token === "--criteria" && next) {
|
|
78
92
|
parsed.criteria = next;
|
|
93
|
+
parsed.__provided.criteria = true;
|
|
79
94
|
index += 1;
|
|
80
95
|
} else if (token === "--targetCount" && next) {
|
|
81
96
|
parsed.targetCount = parsePositiveInteger(next);
|
|
97
|
+
parsed.__provided.targetCount = true;
|
|
82
98
|
index += 1;
|
|
83
99
|
} else if (token === "--max-greet-count" && next) {
|
|
84
100
|
parsed.maxGreetCount = parsePositiveInteger(next);
|
|
101
|
+
parsed.__provided.maxGreetCount = true;
|
|
85
102
|
index += 1;
|
|
86
103
|
} else if (token === "--port" && next) {
|
|
87
104
|
parsed.port = parsePositiveInteger(next) || DEFAULT_PORT;
|
|
105
|
+
parsed.__provided.port = true;
|
|
88
106
|
index += 1;
|
|
89
107
|
} else if (token === "--output" && next) {
|
|
90
108
|
parsed.output = path.resolve(next);
|
|
91
109
|
index += 1;
|
|
92
110
|
} else if (token === "--post-action" && next) {
|
|
93
111
|
parsed.postAction = normalizePostAction(next);
|
|
112
|
+
parsed.__provided.postAction = true;
|
|
94
113
|
index += 1;
|
|
95
114
|
} else if (token === "--post-action-confirmed" && next) {
|
|
96
115
|
parsed.postActionConfirmed = parseBoolean(next);
|
|
116
|
+
parsed.__provided.postActionConfirmed = true;
|
|
97
117
|
index += 1;
|
|
98
118
|
} else if (token === "--help" || token === "-h") {
|
|
99
119
|
parsed.help = true;
|
|
@@ -103,6 +123,109 @@ function parseArgs(argv) {
|
|
|
103
123
|
return parsed;
|
|
104
124
|
}
|
|
105
125
|
|
|
126
|
+
function isInteractiveTTY() {
|
|
127
|
+
return Boolean(process.stdin?.isTTY && process.stdout?.isTTY);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function askWithValidation(ask, question, validate, options = {}) {
|
|
131
|
+
const { allowEmpty = false, defaultValue = undefined } = options;
|
|
132
|
+
while (true) {
|
|
133
|
+
const answer = normalizeText(await ask(question));
|
|
134
|
+
if (!answer) {
|
|
135
|
+
if (defaultValue !== undefined) return defaultValue;
|
|
136
|
+
if (allowEmpty) return null;
|
|
137
|
+
}
|
|
138
|
+
const validated = validate(answer);
|
|
139
|
+
if (validated !== null && validated !== undefined) return validated;
|
|
140
|
+
console.error("输入无效,请重试。");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function promptMissingInputs(args) {
|
|
145
|
+
if (!isInteractiveTTY() || args.help) return args;
|
|
146
|
+
|
|
147
|
+
if (args.__provided.postAction && args.postAction && args.postActionConfirmed === null) {
|
|
148
|
+
args.postActionConfirmed = true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const rl = readline.createInterface({
|
|
152
|
+
input: process.stdin,
|
|
153
|
+
output: process.stdout
|
|
154
|
+
});
|
|
155
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
156
|
+
try {
|
|
157
|
+
if (!normalizeText(args.criteria)) {
|
|
158
|
+
args.criteria = await askWithValidation(
|
|
159
|
+
ask,
|
|
160
|
+
"请输入筛选标准(--criteria): ",
|
|
161
|
+
(value) => normalizeText(value) || null
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (!normalizeText(args.baseUrl)) {
|
|
165
|
+
args.baseUrl = await askWithValidation(
|
|
166
|
+
ask,
|
|
167
|
+
"请输入模型接口 baseUrl(--baseurl,例如 https://api.openai.com/v1): ",
|
|
168
|
+
(value) => normalizeText(value) || null
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (!normalizeText(args.apiKey)) {
|
|
172
|
+
args.apiKey = await askWithValidation(
|
|
173
|
+
ask,
|
|
174
|
+
"请输入模型接口 apiKey(--apikey): ",
|
|
175
|
+
(value) => normalizeText(value) || null
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (!normalizeText(args.model)) {
|
|
179
|
+
args.model = await askWithValidation(
|
|
180
|
+
ask,
|
|
181
|
+
"请输入模型名(--model): ",
|
|
182
|
+
(value) => normalizeText(value) || null
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (args.targetCount === null) {
|
|
186
|
+
const targetCount = await askWithValidation(
|
|
187
|
+
ask,
|
|
188
|
+
"请输入目标筛选人数(--targetCount,可留空表示不设上限): ",
|
|
189
|
+
(value) => parsePositiveInteger(value),
|
|
190
|
+
{ allowEmpty: true }
|
|
191
|
+
);
|
|
192
|
+
if (Number.isInteger(targetCount) && targetCount > 0) {
|
|
193
|
+
args.targetCount = targetCount;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!(args.postActionConfirmed === true && args.postAction)) {
|
|
197
|
+
args.postAction = await askWithValidation(
|
|
198
|
+
ask,
|
|
199
|
+
"本次通过人选统一执行什么动作?请输入 1(收藏) 或 2(直接沟通): ",
|
|
200
|
+
(value) => {
|
|
201
|
+
if (value === "1") return "favorite";
|
|
202
|
+
if (value === "2") return "greet";
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
args.postActionConfirmed = true;
|
|
207
|
+
}
|
|
208
|
+
if (args.postAction === "greet" && !(Number.isInteger(args.maxGreetCount) && args.maxGreetCount > 0)) {
|
|
209
|
+
args.maxGreetCount = await askWithValidation(
|
|
210
|
+
ask,
|
|
211
|
+
"本次最多打招呼多少位候选人?请输入正整数(--max-greet-count): ",
|
|
212
|
+
(value) => parsePositiveInteger(value)
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (!args.__provided.port) {
|
|
216
|
+
args.port = await askWithValidation(
|
|
217
|
+
ask,
|
|
218
|
+
`Chrome 调试端口(--port,默认: ${args.port}): `,
|
|
219
|
+
(value) => parsePositiveInteger(value),
|
|
220
|
+
{ defaultValue: args.port }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return args;
|
|
224
|
+
} finally {
|
|
225
|
+
rl.close();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
106
229
|
function sleep(ms) {
|
|
107
230
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
108
231
|
}
|
|
@@ -1605,7 +1728,8 @@ async function main() {
|
|
|
1605
1728
|
return;
|
|
1606
1729
|
}
|
|
1607
1730
|
|
|
1608
|
-
const
|
|
1731
|
+
const finalArgs = await promptMissingInputs(args);
|
|
1732
|
+
const cli = new RecommendScreenCli(finalArgs);
|
|
1609
1733
|
const result = await cli.run();
|
|
1610
1734
|
console.log(JSON.stringify(result));
|
|
1611
1735
|
}
|
|
@@ -1,22 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from "node:process";
|
|
3
|
+
import readline from "node:readline";
|
|
3
4
|
import CDP from "chrome-remote-interface";
|
|
4
5
|
|
|
5
6
|
const DEFAULT_PORT = 9222;
|
|
6
7
|
const RECOMMEND_URL_FRAGMENT = "/web/chat/recommend";
|
|
8
|
+
const SCHOOL_TAG_OPTIONS = ["不限", "985", "211", "双一流院校", "留学", "国内外名校", "公办本科"];
|
|
9
|
+
const DEGREE_OPTIONS = ["不限", "初中及以下", "中专/中技", "高中", "大专", "本科", "硕士", "博士"];
|
|
10
|
+
const DEGREE_ORDER = ["初中及以下", "中专/中技", "高中", "大专", "本科", "硕士", "博士"];
|
|
11
|
+
const GENDER_OPTIONS = ["不限", "男", "女"];
|
|
12
|
+
const RECENT_NOT_VIEW_OPTIONS = ["不限", "近14天没有"];
|
|
13
|
+
|
|
14
|
+
function normalizeText(value) {
|
|
15
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
16
|
+
}
|
|
7
17
|
|
|
8
18
|
function parsePositiveInteger(raw) {
|
|
9
19
|
const value = Number.parseInt(String(raw || ""), 10);
|
|
10
20
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
11
21
|
}
|
|
12
22
|
|
|
23
|
+
function normalizeDegree(value) {
|
|
24
|
+
const normalized = normalizeText(value);
|
|
25
|
+
if (!normalized) return null;
|
|
26
|
+
if (normalized === "专科") return "大专";
|
|
27
|
+
if (normalized === "研究生") return "硕士";
|
|
28
|
+
if (normalized === "中专" || normalized === "中技" || normalized === "中专中技") return "中专/中技";
|
|
29
|
+
return DEGREE_OPTIONS.includes(normalized) ? normalized : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sortDegreeSelection(values) {
|
|
33
|
+
return Array.from(new Set(values.filter(Boolean))).sort((left, right) => DEGREE_ORDER.indexOf(left) - DEGREE_ORDER.indexOf(right));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function expandDegreeAtOrAbove(value) {
|
|
37
|
+
const normalized = normalizeDegree(value);
|
|
38
|
+
if (!normalized || normalized === "不限") return [];
|
|
39
|
+
const index = DEGREE_ORDER.indexOf(normalized);
|
|
40
|
+
if (index === -1) return [];
|
|
41
|
+
return DEGREE_ORDER.slice(index);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseDegreeSelection(raw) {
|
|
45
|
+
const text = normalizeText(raw);
|
|
46
|
+
if (!text) return null;
|
|
47
|
+
if (text === "不限") return ["不限"];
|
|
48
|
+
if (/不限/.test(text) && !/(初中|中专|中技|高中|大专|专科|本科|硕士|研究生|博士)/.test(text)) {
|
|
49
|
+
return ["不限"];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const selected = [];
|
|
53
|
+
const atOrAbovePattern = /(初中及以下|中专\/中技|中专中技|中专|中技|高中|大专|专科|本科|硕士|研究生|博士)\s*(?:及|或)?以上/g;
|
|
54
|
+
let match;
|
|
55
|
+
while ((match = atOrAbovePattern.exec(text)) !== null) {
|
|
56
|
+
selected.push(...expandDegreeAtOrAbove(match[1]));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const chunks = text.split(/[,,、/|]/).map((item) => normalizeDegree(item)).filter(Boolean);
|
|
60
|
+
selected.push(...chunks);
|
|
61
|
+
|
|
62
|
+
for (const label of DEGREE_OPTIONS) {
|
|
63
|
+
if (label === "不限") continue;
|
|
64
|
+
if (text.includes(label)) {
|
|
65
|
+
selected.push(label);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const normalized = sortDegreeSelection(selected);
|
|
70
|
+
return normalized.length ? normalized : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
13
73
|
function parseArgs(argv) {
|
|
14
74
|
const args = {
|
|
15
75
|
schoolTag: "不限",
|
|
76
|
+
degree: ["不限"],
|
|
16
77
|
gender: "不限",
|
|
17
78
|
recentNotView: "不限",
|
|
18
79
|
port: DEFAULT_PORT,
|
|
19
|
-
help: false
|
|
80
|
+
help: false,
|
|
81
|
+
__provided: {
|
|
82
|
+
schoolTag: false,
|
|
83
|
+
degree: false,
|
|
84
|
+
gender: false,
|
|
85
|
+
recentNotView: false,
|
|
86
|
+
port: false
|
|
87
|
+
}
|
|
20
88
|
};
|
|
21
89
|
|
|
22
90
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -24,15 +92,23 @@ function parseArgs(argv) {
|
|
|
24
92
|
const next = argv[index + 1];
|
|
25
93
|
if (token === "--school-tag" && next) {
|
|
26
94
|
args.schoolTag = next;
|
|
95
|
+
args.__provided.schoolTag = true;
|
|
96
|
+
index += 1;
|
|
97
|
+
} else if (token === "--degree" && next) {
|
|
98
|
+
args.degree = parseDegreeSelection(next);
|
|
99
|
+
args.__provided.degree = true;
|
|
27
100
|
index += 1;
|
|
28
101
|
} else if (token === "--gender" && next) {
|
|
29
102
|
args.gender = next;
|
|
103
|
+
args.__provided.gender = true;
|
|
30
104
|
index += 1;
|
|
31
105
|
} else if (token === "--recent-not-view" && next) {
|
|
32
106
|
args.recentNotView = next;
|
|
107
|
+
args.__provided.recentNotView = true;
|
|
33
108
|
index += 1;
|
|
34
109
|
} else if (token === "--port" && next) {
|
|
35
110
|
args.port = parsePositiveInteger(next) || DEFAULT_PORT;
|
|
111
|
+
args.__provided.port = true;
|
|
36
112
|
index += 1;
|
|
37
113
|
} else if (token === "--help" || token === "-h") {
|
|
38
114
|
args.help = true;
|
|
@@ -42,6 +118,78 @@ function parseArgs(argv) {
|
|
|
42
118
|
return args;
|
|
43
119
|
}
|
|
44
120
|
|
|
121
|
+
function isInteractiveTTY() {
|
|
122
|
+
return Boolean(process.stdin?.isTTY && process.stdout?.isTTY);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function promptValue(ask, question, validate, defaultValue) {
|
|
126
|
+
while (true) {
|
|
127
|
+
const answer = normalizeText(await ask(question));
|
|
128
|
+
if (!answer && defaultValue !== undefined) return defaultValue;
|
|
129
|
+
const validated = validate(answer);
|
|
130
|
+
if (validated !== null && validated !== undefined) return validated;
|
|
131
|
+
console.error("输入无效,请重试。");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function enrichArgsFromPrompt(args) {
|
|
136
|
+
if (!isInteractiveTTY() || args.help) return args;
|
|
137
|
+
const askTargets = Object.values(args.__provided || {}).some((item) => item === false) || !Array.isArray(args.degree) || args.degree.length === 0;
|
|
138
|
+
if (!askTargets) return args;
|
|
139
|
+
|
|
140
|
+
const rl = readline.createInterface({
|
|
141
|
+
input: process.stdin,
|
|
142
|
+
output: process.stdout
|
|
143
|
+
});
|
|
144
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
145
|
+
try {
|
|
146
|
+
if (!args.__provided.schoolTag) {
|
|
147
|
+
args.schoolTag = await promptValue(
|
|
148
|
+
ask,
|
|
149
|
+
`学校标签(${SCHOOL_TAG_OPTIONS.join("/")},默认: ${args.schoolTag}): `,
|
|
150
|
+
(value) => SCHOOL_TAG_OPTIONS.includes(value) ? value : null,
|
|
151
|
+
args.schoolTag
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (!args.__provided.gender) {
|
|
155
|
+
args.gender = await promptValue(
|
|
156
|
+
ask,
|
|
157
|
+
`性别(${GENDER_OPTIONS.join("/")},默认: ${args.gender}): `,
|
|
158
|
+
(value) => GENDER_OPTIONS.includes(value) ? value : null,
|
|
159
|
+
args.gender
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (!args.__provided.degree || !Array.isArray(args.degree) || args.degree.length === 0) {
|
|
163
|
+
const current = Array.isArray(args.degree) && args.degree.length > 0 ? args.degree.join(",") : "不限";
|
|
164
|
+
args.degree = await promptValue(
|
|
165
|
+
ask,
|
|
166
|
+
`学历(可多选逗号分隔,支持“本科及以上”;默认: ${current}): `,
|
|
167
|
+
(value) => parseDegreeSelection(value),
|
|
168
|
+
Array.isArray(args.degree) && args.degree.length > 0 ? args.degree : ["不限"]
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (!args.__provided.recentNotView) {
|
|
172
|
+
args.recentNotView = await promptValue(
|
|
173
|
+
ask,
|
|
174
|
+
`近14天已看过滤(${RECENT_NOT_VIEW_OPTIONS.join("/")},默认: ${args.recentNotView}): `,
|
|
175
|
+
(value) => RECENT_NOT_VIEW_OPTIONS.includes(value) ? value : null,
|
|
176
|
+
args.recentNotView
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (!args.__provided.port) {
|
|
180
|
+
args.port = await promptValue(
|
|
181
|
+
ask,
|
|
182
|
+
`Chrome 调试端口(默认: ${args.port}): `,
|
|
183
|
+
(value) => parsePositiveInteger(value),
|
|
184
|
+
args.port
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return args;
|
|
188
|
+
} finally {
|
|
189
|
+
rl.close();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
45
193
|
function sleep(ms) {
|
|
46
194
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
195
|
}
|
|
@@ -66,10 +214,6 @@ function generateBezierPath(start, end, steps = 18) {
|
|
|
66
214
|
return path;
|
|
67
215
|
}
|
|
68
216
|
|
|
69
|
-
function normalizeText(value) {
|
|
70
|
-
return String(value || "").replace(/\s+/g, " ").trim();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
217
|
class RecommendSearchCli {
|
|
74
218
|
constructor(args) {
|
|
75
219
|
this.args = args;
|
|
@@ -218,9 +362,10 @@ class RecommendSearchCli {
|
|
|
218
362
|
return rect.width > 2 && rect.height > 2;
|
|
219
363
|
};
|
|
220
364
|
const school = doc.querySelector('.check-box.school');
|
|
365
|
+
const degree = doc.querySelector('.check-box.degree');
|
|
221
366
|
const gender = doc.querySelector('.check-box.gender');
|
|
222
367
|
const recent = doc.querySelector('.check-box.recentNotView');
|
|
223
|
-
return Boolean((school && gender && recent) || isVisible(panel));
|
|
368
|
+
return Boolean((school && degree && gender && recent) || isVisible(panel));
|
|
224
369
|
})()`);
|
|
225
370
|
return result === true;
|
|
226
371
|
}
|
|
@@ -399,10 +544,11 @@ class RecommendSearchCli {
|
|
|
399
544
|
if (!target) {
|
|
400
545
|
return { ok: false, error: 'OPTION_NOT_FOUND', activeText };
|
|
401
546
|
}
|
|
547
|
+
const targetActive = target.classList.contains('active');
|
|
402
548
|
return {
|
|
403
549
|
ok: true,
|
|
404
550
|
activeText,
|
|
405
|
-
alreadySelected: activeText === normalize(label),
|
|
551
|
+
alreadySelected: targetActive || activeText === normalize(label),
|
|
406
552
|
x: getPoint(target).x,
|
|
407
553
|
y: getPoint(target).y
|
|
408
554
|
};
|
|
@@ -421,6 +567,60 @@ class RecommendSearchCli {
|
|
|
421
567
|
await sleep(humanDelay(300, 80));
|
|
422
568
|
}
|
|
423
569
|
|
|
570
|
+
async getDegreeFilterState() {
|
|
571
|
+
return this.evaluate(`(() => {
|
|
572
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
573
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
574
|
+
|| document.querySelector('iframe');
|
|
575
|
+
if (!frame || !frame.contentDocument) {
|
|
576
|
+
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
577
|
+
}
|
|
578
|
+
const doc = frame.contentDocument;
|
|
579
|
+
const group = doc.querySelector('.check-box.degree');
|
|
580
|
+
if (!group) {
|
|
581
|
+
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
582
|
+
}
|
|
583
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
584
|
+
const labels = ${JSON.stringify(DEGREE_OPTIONS)};
|
|
585
|
+
const activeLabels = labels.filter((label) => {
|
|
586
|
+
const node = Array.from(group.querySelectorAll('.options .option'))
|
|
587
|
+
.find((item) => normalize(item.textContent) === normalize(label));
|
|
588
|
+
return Boolean(node && node.classList.contains('active'));
|
|
589
|
+
});
|
|
590
|
+
const defaultOption = group.querySelector('.default.option');
|
|
591
|
+
return {
|
|
592
|
+
ok: true,
|
|
593
|
+
defaultActive: Boolean(defaultOption && defaultOption.classList.contains('active')),
|
|
594
|
+
activeLabels
|
|
595
|
+
};
|
|
596
|
+
})()`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async selectDegreeFilter(labels) {
|
|
600
|
+
const targetLabels = Array.isArray(labels) && labels.length > 0 ? labels : ["不限"];
|
|
601
|
+
if (targetLabels.includes("不限")) {
|
|
602
|
+
await this.selectOption("degree", "不限");
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const currentState = await this.getDegreeFilterState();
|
|
607
|
+
if (!currentState?.ok) {
|
|
608
|
+
throw new Error(currentState?.error || "DEGREE_FILTER_STATE_FAILED");
|
|
609
|
+
}
|
|
610
|
+
const current = sortDegreeSelection(currentState.activeLabels || []);
|
|
611
|
+
const desired = sortDegreeSelection(targetLabels);
|
|
612
|
+
const same =
|
|
613
|
+
!currentState.defaultActive
|
|
614
|
+
&& current.length === desired.length
|
|
615
|
+
&& current.every((value, index) => value === desired[index]);
|
|
616
|
+
if (same) return;
|
|
617
|
+
|
|
618
|
+
await this.selectOption("degree", "不限");
|
|
619
|
+
for (const label of desired) {
|
|
620
|
+
await this.selectOption("degree", label);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
424
624
|
async countCandidates() {
|
|
425
625
|
return this.evaluate(`(() => {
|
|
426
626
|
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
@@ -470,11 +670,14 @@ class RecommendSearchCli {
|
|
|
470
670
|
console.log(JSON.stringify({
|
|
471
671
|
status: "COMPLETED",
|
|
472
672
|
result: {
|
|
473
|
-
usage: "node src/cli.js --school-tag 985 --gender 男 --recent-not-view 近14天没有 --port 9222"
|
|
673
|
+
usage: "node src/cli.js --school-tag 985 --degree 本科及以上 --gender 男 --recent-not-view 近14天没有 --port 9222"
|
|
474
674
|
}
|
|
475
675
|
}));
|
|
476
676
|
return;
|
|
477
677
|
}
|
|
678
|
+
if (!Array.isArray(this.args.degree) || this.args.degree.length === 0) {
|
|
679
|
+
throw new Error("INVALID_DEGREE_INPUT");
|
|
680
|
+
}
|
|
478
681
|
|
|
479
682
|
await this.connect();
|
|
480
683
|
try {
|
|
@@ -485,6 +688,7 @@ class RecommendSearchCli {
|
|
|
485
688
|
|
|
486
689
|
await this.openFilterPanel();
|
|
487
690
|
await this.selectOption("school", this.args.schoolTag);
|
|
691
|
+
await this.selectDegreeFilter(this.args.degree);
|
|
488
692
|
await this.selectOption("gender", this.args.gender);
|
|
489
693
|
await this.selectOption("recentNotView", this.args.recentNotView);
|
|
490
694
|
await this.closeFilterPanel();
|
|
@@ -495,6 +699,7 @@ class RecommendSearchCli {
|
|
|
495
699
|
result: {
|
|
496
700
|
applied_filters: {
|
|
497
701
|
school_tag: this.args.schoolTag,
|
|
702
|
+
degree: this.args.degree,
|
|
498
703
|
gender: this.args.gender,
|
|
499
704
|
recent_not_view: this.args.recentNotView
|
|
500
705
|
},
|
|
@@ -511,9 +716,14 @@ class RecommendSearchCli {
|
|
|
511
716
|
}
|
|
512
717
|
}
|
|
513
718
|
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
719
|
+
async function main() {
|
|
720
|
+
const args = parseArgs(process.argv.slice(2));
|
|
721
|
+
const finalArgs = await enrichArgsFromPrompt(args);
|
|
722
|
+
const cli = new RecommendSearchCli(finalArgs);
|
|
723
|
+
await cli.run();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
main().catch((error) => {
|
|
517
727
|
console.log(JSON.stringify({
|
|
518
728
|
status: "FAILED",
|
|
519
729
|
error: {
|
|
@@ -523,4 +733,4 @@ cli.run().catch((error) => {
|
|
|
523
733
|
}
|
|
524
734
|
}));
|
|
525
735
|
process.exitCode = 1;
|
|
526
|
-
});
|
|
736
|
+
});
|