@reconcrap/boss-recommend-mcp 0.1.2 → 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,
|
|
@@ -361,11 +400,10 @@ class RecommendSearchCli {
|
|
|
361
400
|
const rect = el.getBoundingClientRect();
|
|
362
401
|
return rect.width > 2 && rect.height > 2;
|
|
363
402
|
};
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return Boolean((school && degree && gender && recent) || isVisible(panel));
|
|
403
|
+
const groups = Array.from(doc.querySelectorAll('.check-box'));
|
|
404
|
+
const visibleGroups = groups.filter((group) => isVisible(group));
|
|
405
|
+
if (visibleGroups.length >= 2) return true;
|
|
406
|
+
return Boolean(isVisible(panel) && visibleGroups.length >= 1);
|
|
369
407
|
})()`);
|
|
370
408
|
return result === true;
|
|
371
409
|
}
|
|
@@ -522,11 +560,46 @@ class RecommendSearchCli {
|
|
|
522
560
|
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
523
561
|
}
|
|
524
562
|
const doc = frame.contentDocument;
|
|
525
|
-
const
|
|
563
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
564
|
+
const groupCandidates = Array.from(doc.querySelectorAll('.check-box'));
|
|
565
|
+
const getOptionSet = (group) => new Set(
|
|
566
|
+
Array.from(group.querySelectorAll('.default.option, .options .option, .option'))
|
|
567
|
+
.map((item) => normalize(item.textContent))
|
|
568
|
+
.filter(Boolean)
|
|
569
|
+
);
|
|
570
|
+
const findGroup = () => {
|
|
571
|
+
const direct = doc.querySelector('.check-box.' + groupClass);
|
|
572
|
+
if (direct) return direct;
|
|
573
|
+
if (groupClass === 'school') {
|
|
574
|
+
return groupCandidates.find((group) => {
|
|
575
|
+
const set = getOptionSet(group);
|
|
576
|
+
return set.has('985') || set.has('211') || set.has('双一流院校');
|
|
577
|
+
}) || null;
|
|
578
|
+
}
|
|
579
|
+
if (groupClass === 'degree') {
|
|
580
|
+
return groupCandidates.find((group) => {
|
|
581
|
+
const set = getOptionSet(group);
|
|
582
|
+
return set.has('大专') || set.has('本科') || set.has('硕士') || set.has('博士');
|
|
583
|
+
}) || null;
|
|
584
|
+
}
|
|
585
|
+
if (groupClass === 'gender') {
|
|
586
|
+
return groupCandidates.find((group) => {
|
|
587
|
+
const set = getOptionSet(group);
|
|
588
|
+
return set.has('男') || set.has('女');
|
|
589
|
+
}) || null;
|
|
590
|
+
}
|
|
591
|
+
if (groupClass === 'recentNotView') {
|
|
592
|
+
return groupCandidates.find((group) => {
|
|
593
|
+
const set = getOptionSet(group);
|
|
594
|
+
return set.has('近14天没有');
|
|
595
|
+
}) || null;
|
|
596
|
+
}
|
|
597
|
+
return null;
|
|
598
|
+
};
|
|
599
|
+
const group = findGroup();
|
|
526
600
|
if (!group) {
|
|
527
601
|
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
528
602
|
}
|
|
529
|
-
const normalize = (value) => String(value || '').replace(/\s+/g, '').trim();
|
|
530
603
|
const frameRect = frame.getBoundingClientRect();
|
|
531
604
|
const getPoint = (el) => {
|
|
532
605
|
const rect = el.getBoundingClientRect();
|
|
@@ -555,19 +628,191 @@ class RecommendSearchCli {
|
|
|
555
628
|
})(${JSON.stringify(groupClass)}, ${JSON.stringify(label)})`);
|
|
556
629
|
}
|
|
557
630
|
|
|
631
|
+
async ensureGroupReady(groupClass) {
|
|
632
|
+
return this.evaluate(`((groupClass) => {
|
|
633
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
634
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
635
|
+
|| document.querySelector('iframe');
|
|
636
|
+
if (!frame || !frame.contentDocument) {
|
|
637
|
+
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
638
|
+
}
|
|
639
|
+
const doc = frame.contentDocument;
|
|
640
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
641
|
+
const groupCandidates = Array.from(doc.querySelectorAll('.check-box'));
|
|
642
|
+
const getOptionSet = (group) => new Set(
|
|
643
|
+
Array.from(group.querySelectorAll('.default.option, .options .option, .option'))
|
|
644
|
+
.map((item) => normalize(item.textContent))
|
|
645
|
+
.filter(Boolean)
|
|
646
|
+
);
|
|
647
|
+
const findGroup = () => {
|
|
648
|
+
const direct = doc.querySelector('.check-box.' + groupClass);
|
|
649
|
+
if (direct) return direct;
|
|
650
|
+
if (groupClass === 'school') {
|
|
651
|
+
return groupCandidates.find((group) => {
|
|
652
|
+
const set = getOptionSet(group);
|
|
653
|
+
return set.has('985') || set.has('211') || set.has('双一流院校');
|
|
654
|
+
}) || null;
|
|
655
|
+
}
|
|
656
|
+
if (groupClass === 'degree') {
|
|
657
|
+
return groupCandidates.find((group) => {
|
|
658
|
+
const set = getOptionSet(group);
|
|
659
|
+
return set.has('大专') || set.has('本科') || set.has('硕士') || set.has('博士');
|
|
660
|
+
}) || null;
|
|
661
|
+
}
|
|
662
|
+
if (groupClass === 'gender') {
|
|
663
|
+
return groupCandidates.find((group) => {
|
|
664
|
+
const set = getOptionSet(group);
|
|
665
|
+
return set.has('男') || set.has('女');
|
|
666
|
+
}) || null;
|
|
667
|
+
}
|
|
668
|
+
if (groupClass === 'recentNotView') {
|
|
669
|
+
return groupCandidates.find((group) => {
|
|
670
|
+
const set = getOptionSet(group);
|
|
671
|
+
return set.has('近14天没有');
|
|
672
|
+
}) || null;
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
const scrollGroupIntoView = (group) => {
|
|
678
|
+
try {
|
|
679
|
+
group.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
680
|
+
} catch {
|
|
681
|
+
try { group.scrollIntoView({ block: 'center' }); } catch {}
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
let group = findGroup();
|
|
686
|
+
if (group) {
|
|
687
|
+
scrollGroupIntoView(group);
|
|
688
|
+
return { ok: true, found: true, scrolled: false };
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const topScroller = doc.querySelector('.recommend-filter.op-filter .filter-panel .top')
|
|
692
|
+
|| doc.querySelector('.recommend-filter.op-filter .top')
|
|
693
|
+
|| doc.querySelector('.recommend-filter.op-filter .filter-panel');
|
|
694
|
+
if (!topScroller) {
|
|
695
|
+
return { ok: false, error: 'FILTER_SCROLL_CONTAINER_NOT_FOUND' };
|
|
696
|
+
}
|
|
697
|
+
const maxScrollTop = Math.max(0, topScroller.scrollHeight - topScroller.clientHeight);
|
|
698
|
+
const steps = 14;
|
|
699
|
+
for (let index = 0; index <= steps; index += 1) {
|
|
700
|
+
const nextTop = maxScrollTop <= 0 ? 0 : Math.round((maxScrollTop * index) / steps);
|
|
701
|
+
topScroller.scrollTop = nextTop;
|
|
702
|
+
group = findGroup();
|
|
703
|
+
if (group) {
|
|
704
|
+
scrollGroupIntoView(group);
|
|
705
|
+
return { ok: true, found: true, scrolled: true, step: index };
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
709
|
+
})(${JSON.stringify(groupClass)})`);
|
|
710
|
+
}
|
|
711
|
+
|
|
558
712
|
async selectOption(groupClass, label) {
|
|
559
|
-
|
|
713
|
+
let option = await this.getOptionInfo(groupClass, label);
|
|
714
|
+
if (!option?.ok && option?.error === "GROUP_NOT_FOUND") {
|
|
715
|
+
await this.openFilterPanel();
|
|
716
|
+
const ensure = await this.ensureGroupReady(groupClass);
|
|
717
|
+
if (!ensure?.ok) {
|
|
718
|
+
throw new Error(ensure?.error || "GROUP_NOT_FOUND");
|
|
719
|
+
}
|
|
720
|
+
await sleep(humanDelay(180, 60));
|
|
721
|
+
option = await this.getOptionInfo(groupClass, label);
|
|
722
|
+
}
|
|
560
723
|
if (!option?.ok) {
|
|
561
724
|
throw new Error(option?.error || 'OPTION_NOT_FOUND');
|
|
562
725
|
}
|
|
563
726
|
if (option.alreadySelected) {
|
|
564
727
|
return;
|
|
565
728
|
}
|
|
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)) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
566
737
|
await this.simulateHumanClick(option.x, option.y);
|
|
567
|
-
await
|
|
738
|
+
if (!(await this.waitOptionSelected(groupClass, label, 10))) {
|
|
739
|
+
throw new Error("OPTION_SELECTION_NOT_APPLIED");
|
|
740
|
+
}
|
|
568
741
|
}
|
|
569
742
|
|
|
570
|
-
async
|
|
743
|
+
async clickOptionBySelector(groupClass, label) {
|
|
744
|
+
return this.evaluate(`((groupClass, label) => {
|
|
745
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
746
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
747
|
+
|| document.querySelector('iframe');
|
|
748
|
+
if (!frame || !frame.contentDocument) {
|
|
749
|
+
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
750
|
+
}
|
|
751
|
+
const doc = frame.contentDocument;
|
|
752
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
753
|
+
const groupCandidates = Array.from(doc.querySelectorAll('.check-box'));
|
|
754
|
+
const getOptionSet = (group) => new Set(
|
|
755
|
+
Array.from(group.querySelectorAll('.default.option, .options .option, .option'))
|
|
756
|
+
.map((item) => normalize(item.textContent))
|
|
757
|
+
.filter(Boolean)
|
|
758
|
+
);
|
|
759
|
+
const findGroup = () => {
|
|
760
|
+
const direct = doc.querySelector('.check-box.' + groupClass);
|
|
761
|
+
if (direct) return direct;
|
|
762
|
+
if (groupClass === 'school') {
|
|
763
|
+
return groupCandidates.find((group) => {
|
|
764
|
+
const set = getOptionSet(group);
|
|
765
|
+
return set.has('985') || set.has('211') || set.has('双一流院校');
|
|
766
|
+
}) || null;
|
|
767
|
+
}
|
|
768
|
+
if (groupClass === 'degree') {
|
|
769
|
+
return groupCandidates.find((group) => {
|
|
770
|
+
const set = getOptionSet(group);
|
|
771
|
+
return set.has('大专') || set.has('本科') || set.has('硕士') || set.has('博士');
|
|
772
|
+
}) || null;
|
|
773
|
+
}
|
|
774
|
+
if (groupClass === 'gender') {
|
|
775
|
+
return groupCandidates.find((group) => {
|
|
776
|
+
const set = getOptionSet(group);
|
|
777
|
+
return set.has('男') || set.has('女');
|
|
778
|
+
}) || null;
|
|
779
|
+
}
|
|
780
|
+
if (groupClass === 'recentNotView') {
|
|
781
|
+
return groupCandidates.find((group) => {
|
|
782
|
+
const set = getOptionSet(group);
|
|
783
|
+
return set.has('近14天没有');
|
|
784
|
+
}) || null;
|
|
785
|
+
}
|
|
786
|
+
return null;
|
|
787
|
+
};
|
|
788
|
+
const group = findGroup();
|
|
789
|
+
if (!group) {
|
|
790
|
+
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
791
|
+
}
|
|
792
|
+
const options = Array.from(group.querySelectorAll('.options .option, .option'));
|
|
793
|
+
const target = label === '不限'
|
|
794
|
+
? (group.querySelector('.default.option') || options.find((item) => normalize(item.textContent) === '不限'))
|
|
795
|
+
: options.find((item) => normalize(item.textContent) === normalize(label));
|
|
796
|
+
if (!target) {
|
|
797
|
+
return { ok: false, error: 'OPTION_NOT_FOUND' };
|
|
798
|
+
}
|
|
799
|
+
target.click();
|
|
800
|
+
return { ok: true };
|
|
801
|
+
})(${JSON.stringify(groupClass)}, ${JSON.stringify(label)})`);
|
|
802
|
+
}
|
|
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() {
|
|
571
816
|
return this.evaluate(`(() => {
|
|
572
817
|
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
573
818
|
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
@@ -576,11 +821,88 @@ class RecommendSearchCli {
|
|
|
576
821
|
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
577
822
|
}
|
|
578
823
|
const doc = frame.contentDocument;
|
|
579
|
-
const
|
|
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
|
+
});
|
|
580
835
|
if (!group) {
|
|
581
836
|
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
582
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
|
+
|
|
883
|
+
async getDegreeFilterState() {
|
|
884
|
+
return this.evaluate(`(() => {
|
|
885
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
886
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
887
|
+
|| document.querySelector('iframe');
|
|
888
|
+
if (!frame || !frame.contentDocument) {
|
|
889
|
+
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
890
|
+
}
|
|
891
|
+
const doc = frame.contentDocument;
|
|
583
892
|
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
893
|
+
const groups = Array.from(doc.querySelectorAll('.check-box'));
|
|
894
|
+
const group = doc.querySelector('.check-box.degree')
|
|
895
|
+
|| groups.find((item) => {
|
|
896
|
+
const set = new Set(
|
|
897
|
+
Array.from(item.querySelectorAll('.default.option, .options .option, .option'))
|
|
898
|
+
.map((node) => normalize(node.textContent))
|
|
899
|
+
.filter(Boolean)
|
|
900
|
+
);
|
|
901
|
+
return set.has('大专') || set.has('本科') || set.has('硕士') || set.has('博士');
|
|
902
|
+
});
|
|
903
|
+
if (!group) {
|
|
904
|
+
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
905
|
+
}
|
|
584
906
|
const labels = ${JSON.stringify(DEGREE_OPTIONS)};
|
|
585
907
|
const activeLabels = labels.filter((label) => {
|
|
586
908
|
const node = Array.from(group.querySelectorAll('.options .option'))
|
|
@@ -597,6 +919,11 @@ class RecommendSearchCli {
|
|
|
597
919
|
}
|
|
598
920
|
|
|
599
921
|
async selectDegreeFilter(labels) {
|
|
922
|
+
const ensure = await this.ensureGroupReady("degree");
|
|
923
|
+
if (!ensure?.ok) {
|
|
924
|
+
throw new Error(ensure?.error || "GROUP_NOT_FOUND");
|
|
925
|
+
}
|
|
926
|
+
|
|
600
927
|
const targetLabels = Array.isArray(labels) && labels.length > 0 ? labels : ["不限"];
|
|
601
928
|
if (targetLabels.includes("不限")) {
|
|
602
929
|
await this.selectOption("degree", "不限");
|
|
@@ -670,11 +997,14 @@ class RecommendSearchCli {
|
|
|
670
997
|
console.log(JSON.stringify({
|
|
671
998
|
status: "COMPLETED",
|
|
672
999
|
result: {
|
|
673
|
-
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"
|
|
674
1001
|
}
|
|
675
1002
|
}));
|
|
676
1003
|
return;
|
|
677
1004
|
}
|
|
1005
|
+
if (!Array.isArray(this.args.schoolTag) || this.args.schoolTag.length === 0) {
|
|
1006
|
+
throw new Error("INVALID_SCHOOL_TAG_INPUT");
|
|
1007
|
+
}
|
|
678
1008
|
if (!Array.isArray(this.args.degree) || this.args.degree.length === 0) {
|
|
679
1009
|
throw new Error("INVALID_DEGREE_INPUT");
|
|
680
1010
|
}
|
|
@@ -687,10 +1017,10 @@ class RecommendSearchCli {
|
|
|
687
1017
|
}
|
|
688
1018
|
|
|
689
1019
|
await this.openFilterPanel();
|
|
690
|
-
await this.
|
|
691
|
-
await this.selectDegreeFilter(this.args.degree);
|
|
1020
|
+
await this.selectSchoolFilter(this.args.schoolTag);
|
|
692
1021
|
await this.selectOption("gender", this.args.gender);
|
|
693
1022
|
await this.selectOption("recentNotView", this.args.recentNotView);
|
|
1023
|
+
await this.selectDegreeFilter(this.args.degree);
|
|
694
1024
|
await this.closeFilterPanel();
|
|
695
1025
|
const candidateInfo = await this.waitForCandidateCountStable();
|
|
696
1026
|
|