@reconcrap/boss-recommend-mcp 0.1.3 → 0.1.4
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/package.json
CHANGED
|
@@ -20,6 +20,39 @@ function parsePositiveInteger(raw) {
|
|
|
20
20
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function sortSchoolSelection(values) {
|
|
24
|
+
const order = new Map(SCHOOL_TAG_OPTIONS.map((label, index) => [label, index]));
|
|
25
|
+
const unique = Array.from(new Set((values || []).filter((item) => order.has(item))));
|
|
26
|
+
if (!unique.length) return [];
|
|
27
|
+
if (unique.includes("不限")) {
|
|
28
|
+
return unique.length === 1
|
|
29
|
+
? ["不限"]
|
|
30
|
+
: unique.filter((item) => item !== "不限").sort((left, right) => order.get(left) - order.get(right));
|
|
31
|
+
}
|
|
32
|
+
return unique.sort((left, right) => order.get(left) - order.get(right));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseSchoolSelection(raw) {
|
|
36
|
+
const text = normalizeText(raw);
|
|
37
|
+
if (!text) return null;
|
|
38
|
+
if (text === "不限") return ["不限"];
|
|
39
|
+
|
|
40
|
+
const selected = [];
|
|
41
|
+
for (const chunk of text.split(/[,,、/|]/)) {
|
|
42
|
+
const value = normalizeText(chunk);
|
|
43
|
+
if (SCHOOL_TAG_OPTIONS.includes(value)) {
|
|
44
|
+
selected.push(value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const label of SCHOOL_TAG_OPTIONS) {
|
|
48
|
+
if (label !== "不限" && text.includes(label)) {
|
|
49
|
+
selected.push(label);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const normalized = sortSchoolSelection(selected);
|
|
53
|
+
return normalized.length ? normalized : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
23
56
|
function normalizeDegree(value) {
|
|
24
57
|
const normalized = normalizeText(value);
|
|
25
58
|
if (!normalized) return null;
|
|
@@ -72,7 +105,7 @@ function parseDegreeSelection(raw) {
|
|
|
72
105
|
|
|
73
106
|
function parseArgs(argv) {
|
|
74
107
|
const args = {
|
|
75
|
-
schoolTag: "不限",
|
|
108
|
+
schoolTag: ["不限"],
|
|
76
109
|
degree: ["不限"],
|
|
77
110
|
gender: "不限",
|
|
78
111
|
recentNotView: "不限",
|
|
@@ -91,7 +124,7 @@ function parseArgs(argv) {
|
|
|
91
124
|
const token = argv[index];
|
|
92
125
|
const next = argv[index + 1];
|
|
93
126
|
if (token === "--school-tag" && next) {
|
|
94
|
-
args.schoolTag = next;
|
|
127
|
+
args.schoolTag = parseSchoolSelection(next);
|
|
95
128
|
args.__provided.schoolTag = true;
|
|
96
129
|
index += 1;
|
|
97
130
|
} else if (token === "--degree" && next) {
|
|
@@ -134,7 +167,12 @@ async function promptValue(ask, question, validate, defaultValue) {
|
|
|
134
167
|
|
|
135
168
|
async function enrichArgsFromPrompt(args) {
|
|
136
169
|
if (!isInteractiveTTY() || args.help) return args;
|
|
137
|
-
const askTargets =
|
|
170
|
+
const askTargets =
|
|
171
|
+
Object.values(args.__provided || {}).some((item) => item === false)
|
|
172
|
+
|| !Array.isArray(args.schoolTag)
|
|
173
|
+
|| args.schoolTag.length === 0
|
|
174
|
+
|| !Array.isArray(args.degree)
|
|
175
|
+
|| args.degree.length === 0;
|
|
138
176
|
if (!askTargets) return args;
|
|
139
177
|
|
|
140
178
|
const rl = readline.createInterface({
|
|
@@ -144,11 +182,12 @@ async function enrichArgsFromPrompt(args) {
|
|
|
144
182
|
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
145
183
|
try {
|
|
146
184
|
if (!args.__provided.schoolTag) {
|
|
185
|
+
const current = Array.isArray(args.schoolTag) && args.schoolTag.length > 0 ? args.schoolTag.join("/") : "不限";
|
|
147
186
|
args.schoolTag = await promptValue(
|
|
148
187
|
ask,
|
|
149
|
-
|
|
150
|
-
(value) =>
|
|
151
|
-
args.schoolTag
|
|
188
|
+
`学校标签(可多选,逗号/斜杠分隔;${SCHOOL_TAG_OPTIONS.join("/")},默认: ${current}): `,
|
|
189
|
+
(value) => parseSchoolSelection(value),
|
|
190
|
+
Array.isArray(args.schoolTag) && args.schoolTag.length > 0 ? args.schoolTag : ["不限"]
|
|
152
191
|
);
|
|
153
192
|
}
|
|
154
193
|
if (!args.__provided.gender) {
|
|
@@ -159,6 +198,14 @@ async function enrichArgsFromPrompt(args) {
|
|
|
159
198
|
args.gender
|
|
160
199
|
);
|
|
161
200
|
}
|
|
201
|
+
if (!args.__provided.recentNotView) {
|
|
202
|
+
args.recentNotView = await promptValue(
|
|
203
|
+
ask,
|
|
204
|
+
`近14天已看过滤(${RECENT_NOT_VIEW_OPTIONS.join("/")},默认: ${args.recentNotView}): `,
|
|
205
|
+
(value) => RECENT_NOT_VIEW_OPTIONS.includes(value) ? value : null,
|
|
206
|
+
args.recentNotView
|
|
207
|
+
);
|
|
208
|
+
}
|
|
162
209
|
if (!args.__provided.degree || !Array.isArray(args.degree) || args.degree.length === 0) {
|
|
163
210
|
const current = Array.isArray(args.degree) && args.degree.length > 0 ? args.degree.join(",") : "不限";
|
|
164
211
|
args.degree = await promptValue(
|
|
@@ -168,14 +215,6 @@ async function enrichArgsFromPrompt(args) {
|
|
|
168
215
|
Array.isArray(args.degree) && args.degree.length > 0 ? args.degree : ["不限"]
|
|
169
216
|
);
|
|
170
217
|
}
|
|
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
218
|
if (!args.__provided.port) {
|
|
180
219
|
args.port = await promptValue(
|
|
181
220
|
ask,
|
|
@@ -687,21 +726,16 @@ class RecommendSearchCli {
|
|
|
687
726
|
if (option.alreadySelected) {
|
|
688
727
|
return;
|
|
689
728
|
}
|
|
690
|
-
await this.
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (
|
|
729
|
+
const domClick = await this.clickOptionBySelector(groupClass, label);
|
|
730
|
+
if (!domClick?.ok) {
|
|
731
|
+
throw new Error(domClick?.error || "OPTION_DOM_CLICK_FAILED");
|
|
732
|
+
}
|
|
733
|
+
if (await this.waitOptionSelected(groupClass, label, 10)) {
|
|
695
734
|
return;
|
|
696
735
|
}
|
|
697
736
|
|
|
698
|
-
|
|
699
|
-
if (!
|
|
700
|
-
throw new Error(fallback?.error || "OPTION_FALLBACK_CLICK_FAILED");
|
|
701
|
-
}
|
|
702
|
-
await sleep(humanDelay(220, 60));
|
|
703
|
-
afterClick = await this.getOptionInfo(groupClass, label);
|
|
704
|
-
if (!(afterClick?.ok && afterClick.alreadySelected)) {
|
|
737
|
+
await this.simulateHumanClick(option.x, option.y);
|
|
738
|
+
if (!(await this.waitOptionSelected(groupClass, label, 10))) {
|
|
705
739
|
throw new Error("OPTION_SELECTION_NOT_APPLIED");
|
|
706
740
|
}
|
|
707
741
|
}
|
|
@@ -767,6 +801,85 @@ class RecommendSearchCli {
|
|
|
767
801
|
})(${JSON.stringify(groupClass)}, ${JSON.stringify(label)})`);
|
|
768
802
|
}
|
|
769
803
|
|
|
804
|
+
async waitOptionSelected(groupClass, label, rounds = 8) {
|
|
805
|
+
for (let index = 0; index < rounds; index += 1) {
|
|
806
|
+
const state = await this.getOptionInfo(groupClass, label);
|
|
807
|
+
if (state?.ok && state.alreadySelected) {
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
await sleep(120 + index * 40);
|
|
811
|
+
}
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
async getSchoolFilterState() {
|
|
816
|
+
return this.evaluate(`(() => {
|
|
817
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
818
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
819
|
+
|| document.querySelector('iframe');
|
|
820
|
+
if (!frame || !frame.contentDocument) {
|
|
821
|
+
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
822
|
+
}
|
|
823
|
+
const doc = frame.contentDocument;
|
|
824
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
825
|
+
const groups = Array.from(doc.querySelectorAll('.check-box'));
|
|
826
|
+
const group = doc.querySelector('.check-box.school')
|
|
827
|
+
|| groups.find((item) => {
|
|
828
|
+
const set = new Set(
|
|
829
|
+
Array.from(item.querySelectorAll('.default.option, .options .option, .option'))
|
|
830
|
+
.map((node) => normalize(node.textContent))
|
|
831
|
+
.filter(Boolean)
|
|
832
|
+
);
|
|
833
|
+
return set.has('985') || set.has('211') || set.has('双一流院校');
|
|
834
|
+
});
|
|
835
|
+
if (!group) {
|
|
836
|
+
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
837
|
+
}
|
|
838
|
+
const labels = ${JSON.stringify(SCHOOL_TAG_OPTIONS)};
|
|
839
|
+
const activeLabels = labels.filter((label) => {
|
|
840
|
+
const node = Array.from(group.querySelectorAll('.options .option, .option'))
|
|
841
|
+
.find((item) => normalize(item.textContent) === normalize(label));
|
|
842
|
+
return Boolean(node && node.classList.contains('active'));
|
|
843
|
+
});
|
|
844
|
+
const defaultOption = group.querySelector('.default.option');
|
|
845
|
+
return {
|
|
846
|
+
ok: true,
|
|
847
|
+
defaultActive: Boolean(defaultOption && defaultOption.classList.contains('active')),
|
|
848
|
+
activeLabels
|
|
849
|
+
};
|
|
850
|
+
})()`);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async selectSchoolFilter(labels) {
|
|
854
|
+
const ensure = await this.ensureGroupReady("school");
|
|
855
|
+
if (!ensure?.ok) {
|
|
856
|
+
throw new Error(ensure?.error || "GROUP_NOT_FOUND");
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const targetLabels = Array.isArray(labels) && labels.length > 0 ? labels : ["不限"];
|
|
860
|
+
if (targetLabels.includes("不限")) {
|
|
861
|
+
await this.selectOption("school", "不限");
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const currentState = await this.getSchoolFilterState();
|
|
866
|
+
if (!currentState?.ok) {
|
|
867
|
+
throw new Error(currentState?.error || "SCHOOL_FILTER_STATE_FAILED");
|
|
868
|
+
}
|
|
869
|
+
const current = sortSchoolSelection(currentState.activeLabels || []);
|
|
870
|
+
const desired = sortSchoolSelection(targetLabels);
|
|
871
|
+
const same =
|
|
872
|
+
!currentState.defaultActive
|
|
873
|
+
&& current.length === desired.length
|
|
874
|
+
&& current.every((value, index) => value === desired[index]);
|
|
875
|
+
if (same) return;
|
|
876
|
+
|
|
877
|
+
await this.selectOption("school", "不限");
|
|
878
|
+
for (const label of desired) {
|
|
879
|
+
await this.selectOption("school", label);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
770
883
|
async getDegreeFilterState() {
|
|
771
884
|
return this.evaluate(`(() => {
|
|
772
885
|
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
@@ -884,11 +997,14 @@ class RecommendSearchCli {
|
|
|
884
997
|
console.log(JSON.stringify({
|
|
885
998
|
status: "COMPLETED",
|
|
886
999
|
result: {
|
|
887
|
-
usage: "node src/cli.js --school-tag 985 --degree 本科及以上 --gender 男 --recent-not-view 近14天没有 --port 9222"
|
|
1000
|
+
usage: "node src/cli.js --school-tag 985/211 --degree 本科及以上 --gender 男 --recent-not-view 近14天没有 --port 9222"
|
|
888
1001
|
}
|
|
889
1002
|
}));
|
|
890
1003
|
return;
|
|
891
1004
|
}
|
|
1005
|
+
if (!Array.isArray(this.args.schoolTag) || this.args.schoolTag.length === 0) {
|
|
1006
|
+
throw new Error("INVALID_SCHOOL_TAG_INPUT");
|
|
1007
|
+
}
|
|
892
1008
|
if (!Array.isArray(this.args.degree) || this.args.degree.length === 0) {
|
|
893
1009
|
throw new Error("INVALID_DEGREE_INPUT");
|
|
894
1010
|
}
|
|
@@ -901,10 +1017,10 @@ class RecommendSearchCli {
|
|
|
901
1017
|
}
|
|
902
1018
|
|
|
903
1019
|
await this.openFilterPanel();
|
|
904
|
-
await this.
|
|
905
|
-
await this.selectDegreeFilter(this.args.degree);
|
|
1020
|
+
await this.selectSchoolFilter(this.args.schoolTag);
|
|
906
1021
|
await this.selectOption("gender", this.args.gender);
|
|
907
1022
|
await this.selectOption("recentNotView", this.args.recentNotView);
|
|
1023
|
+
await this.selectDegreeFilter(this.args.degree);
|
|
908
1024
|
await this.closeFilterPanel();
|
|
909
1025
|
const candidateInfo = await this.waitForCandidateCountStable();
|
|
910
1026
|
|