@reconcrap/boss-recommend-mcp 0.1.2 → 0.1.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/package.json
CHANGED
|
@@ -361,11 +361,10 @@ class RecommendSearchCli {
|
|
|
361
361
|
const rect = el.getBoundingClientRect();
|
|
362
362
|
return rect.width > 2 && rect.height > 2;
|
|
363
363
|
};
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return Boolean((school && degree && gender && recent) || isVisible(panel));
|
|
364
|
+
const groups = Array.from(doc.querySelectorAll('.check-box'));
|
|
365
|
+
const visibleGroups = groups.filter((group) => isVisible(group));
|
|
366
|
+
if (visibleGroups.length >= 2) return true;
|
|
367
|
+
return Boolean(isVisible(panel) && visibleGroups.length >= 1);
|
|
369
368
|
})()`);
|
|
370
369
|
return result === true;
|
|
371
370
|
}
|
|
@@ -522,11 +521,46 @@ class RecommendSearchCli {
|
|
|
522
521
|
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
523
522
|
}
|
|
524
523
|
const doc = frame.contentDocument;
|
|
525
|
-
const
|
|
524
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
525
|
+
const groupCandidates = Array.from(doc.querySelectorAll('.check-box'));
|
|
526
|
+
const getOptionSet = (group) => new Set(
|
|
527
|
+
Array.from(group.querySelectorAll('.default.option, .options .option, .option'))
|
|
528
|
+
.map((item) => normalize(item.textContent))
|
|
529
|
+
.filter(Boolean)
|
|
530
|
+
);
|
|
531
|
+
const findGroup = () => {
|
|
532
|
+
const direct = doc.querySelector('.check-box.' + groupClass);
|
|
533
|
+
if (direct) return direct;
|
|
534
|
+
if (groupClass === 'school') {
|
|
535
|
+
return groupCandidates.find((group) => {
|
|
536
|
+
const set = getOptionSet(group);
|
|
537
|
+
return set.has('985') || set.has('211') || set.has('双一流院校');
|
|
538
|
+
}) || null;
|
|
539
|
+
}
|
|
540
|
+
if (groupClass === 'degree') {
|
|
541
|
+
return groupCandidates.find((group) => {
|
|
542
|
+
const set = getOptionSet(group);
|
|
543
|
+
return set.has('大专') || set.has('本科') || set.has('硕士') || set.has('博士');
|
|
544
|
+
}) || null;
|
|
545
|
+
}
|
|
546
|
+
if (groupClass === 'gender') {
|
|
547
|
+
return groupCandidates.find((group) => {
|
|
548
|
+
const set = getOptionSet(group);
|
|
549
|
+
return set.has('男') || set.has('女');
|
|
550
|
+
}) || null;
|
|
551
|
+
}
|
|
552
|
+
if (groupClass === 'recentNotView') {
|
|
553
|
+
return groupCandidates.find((group) => {
|
|
554
|
+
const set = getOptionSet(group);
|
|
555
|
+
return set.has('近14天没有');
|
|
556
|
+
}) || null;
|
|
557
|
+
}
|
|
558
|
+
return null;
|
|
559
|
+
};
|
|
560
|
+
const group = findGroup();
|
|
526
561
|
if (!group) {
|
|
527
562
|
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
528
563
|
}
|
|
529
|
-
const normalize = (value) => String(value || '').replace(/\s+/g, '').trim();
|
|
530
564
|
const frameRect = frame.getBoundingClientRect();
|
|
531
565
|
const getPoint = (el) => {
|
|
532
566
|
const rect = el.getBoundingClientRect();
|
|
@@ -555,8 +589,98 @@ class RecommendSearchCli {
|
|
|
555
589
|
})(${JSON.stringify(groupClass)}, ${JSON.stringify(label)})`);
|
|
556
590
|
}
|
|
557
591
|
|
|
592
|
+
async ensureGroupReady(groupClass) {
|
|
593
|
+
return this.evaluate(`((groupClass) => {
|
|
594
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
595
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
596
|
+
|| document.querySelector('iframe');
|
|
597
|
+
if (!frame || !frame.contentDocument) {
|
|
598
|
+
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
599
|
+
}
|
|
600
|
+
const doc = frame.contentDocument;
|
|
601
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
602
|
+
const groupCandidates = Array.from(doc.querySelectorAll('.check-box'));
|
|
603
|
+
const getOptionSet = (group) => new Set(
|
|
604
|
+
Array.from(group.querySelectorAll('.default.option, .options .option, .option'))
|
|
605
|
+
.map((item) => normalize(item.textContent))
|
|
606
|
+
.filter(Boolean)
|
|
607
|
+
);
|
|
608
|
+
const findGroup = () => {
|
|
609
|
+
const direct = doc.querySelector('.check-box.' + groupClass);
|
|
610
|
+
if (direct) return direct;
|
|
611
|
+
if (groupClass === 'school') {
|
|
612
|
+
return groupCandidates.find((group) => {
|
|
613
|
+
const set = getOptionSet(group);
|
|
614
|
+
return set.has('985') || set.has('211') || set.has('双一流院校');
|
|
615
|
+
}) || null;
|
|
616
|
+
}
|
|
617
|
+
if (groupClass === 'degree') {
|
|
618
|
+
return groupCandidates.find((group) => {
|
|
619
|
+
const set = getOptionSet(group);
|
|
620
|
+
return set.has('大专') || set.has('本科') || set.has('硕士') || set.has('博士');
|
|
621
|
+
}) || null;
|
|
622
|
+
}
|
|
623
|
+
if (groupClass === 'gender') {
|
|
624
|
+
return groupCandidates.find((group) => {
|
|
625
|
+
const set = getOptionSet(group);
|
|
626
|
+
return set.has('男') || set.has('女');
|
|
627
|
+
}) || null;
|
|
628
|
+
}
|
|
629
|
+
if (groupClass === 'recentNotView') {
|
|
630
|
+
return groupCandidates.find((group) => {
|
|
631
|
+
const set = getOptionSet(group);
|
|
632
|
+
return set.has('近14天没有');
|
|
633
|
+
}) || null;
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const scrollGroupIntoView = (group) => {
|
|
639
|
+
try {
|
|
640
|
+
group.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
641
|
+
} catch {
|
|
642
|
+
try { group.scrollIntoView({ block: 'center' }); } catch {}
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
let group = findGroup();
|
|
647
|
+
if (group) {
|
|
648
|
+
scrollGroupIntoView(group);
|
|
649
|
+
return { ok: true, found: true, scrolled: false };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const topScroller = doc.querySelector('.recommend-filter.op-filter .filter-panel .top')
|
|
653
|
+
|| doc.querySelector('.recommend-filter.op-filter .top')
|
|
654
|
+
|| doc.querySelector('.recommend-filter.op-filter .filter-panel');
|
|
655
|
+
if (!topScroller) {
|
|
656
|
+
return { ok: false, error: 'FILTER_SCROLL_CONTAINER_NOT_FOUND' };
|
|
657
|
+
}
|
|
658
|
+
const maxScrollTop = Math.max(0, topScroller.scrollHeight - topScroller.clientHeight);
|
|
659
|
+
const steps = 14;
|
|
660
|
+
for (let index = 0; index <= steps; index += 1) {
|
|
661
|
+
const nextTop = maxScrollTop <= 0 ? 0 : Math.round((maxScrollTop * index) / steps);
|
|
662
|
+
topScroller.scrollTop = nextTop;
|
|
663
|
+
group = findGroup();
|
|
664
|
+
if (group) {
|
|
665
|
+
scrollGroupIntoView(group);
|
|
666
|
+
return { ok: true, found: true, scrolled: true, step: index };
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
670
|
+
})(${JSON.stringify(groupClass)})`);
|
|
671
|
+
}
|
|
672
|
+
|
|
558
673
|
async selectOption(groupClass, label) {
|
|
559
|
-
|
|
674
|
+
let option = await this.getOptionInfo(groupClass, label);
|
|
675
|
+
if (!option?.ok && option?.error === "GROUP_NOT_FOUND") {
|
|
676
|
+
await this.openFilterPanel();
|
|
677
|
+
const ensure = await this.ensureGroupReady(groupClass);
|
|
678
|
+
if (!ensure?.ok) {
|
|
679
|
+
throw new Error(ensure?.error || "GROUP_NOT_FOUND");
|
|
680
|
+
}
|
|
681
|
+
await sleep(humanDelay(180, 60));
|
|
682
|
+
option = await this.getOptionInfo(groupClass, label);
|
|
683
|
+
}
|
|
560
684
|
if (!option?.ok) {
|
|
561
685
|
throw new Error(option?.error || 'OPTION_NOT_FOUND');
|
|
562
686
|
}
|
|
@@ -565,6 +689,82 @@ class RecommendSearchCli {
|
|
|
565
689
|
}
|
|
566
690
|
await this.simulateHumanClick(option.x, option.y);
|
|
567
691
|
await sleep(humanDelay(300, 80));
|
|
692
|
+
|
|
693
|
+
let afterClick = await this.getOptionInfo(groupClass, label);
|
|
694
|
+
if (afterClick?.ok && afterClick.alreadySelected) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const fallback = await this.clickOptionBySelector(groupClass, label);
|
|
699
|
+
if (!fallback?.ok) {
|
|
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)) {
|
|
705
|
+
throw new Error("OPTION_SELECTION_NOT_APPLIED");
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async clickOptionBySelector(groupClass, label) {
|
|
710
|
+
return this.evaluate(`((groupClass, label) => {
|
|
711
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
712
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
713
|
+
|| document.querySelector('iframe');
|
|
714
|
+
if (!frame || !frame.contentDocument) {
|
|
715
|
+
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
716
|
+
}
|
|
717
|
+
const doc = frame.contentDocument;
|
|
718
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
719
|
+
const groupCandidates = Array.from(doc.querySelectorAll('.check-box'));
|
|
720
|
+
const getOptionSet = (group) => new Set(
|
|
721
|
+
Array.from(group.querySelectorAll('.default.option, .options .option, .option'))
|
|
722
|
+
.map((item) => normalize(item.textContent))
|
|
723
|
+
.filter(Boolean)
|
|
724
|
+
);
|
|
725
|
+
const findGroup = () => {
|
|
726
|
+
const direct = doc.querySelector('.check-box.' + groupClass);
|
|
727
|
+
if (direct) return direct;
|
|
728
|
+
if (groupClass === 'school') {
|
|
729
|
+
return groupCandidates.find((group) => {
|
|
730
|
+
const set = getOptionSet(group);
|
|
731
|
+
return set.has('985') || set.has('211') || set.has('双一流院校');
|
|
732
|
+
}) || null;
|
|
733
|
+
}
|
|
734
|
+
if (groupClass === 'degree') {
|
|
735
|
+
return groupCandidates.find((group) => {
|
|
736
|
+
const set = getOptionSet(group);
|
|
737
|
+
return set.has('大专') || set.has('本科') || set.has('硕士') || set.has('博士');
|
|
738
|
+
}) || null;
|
|
739
|
+
}
|
|
740
|
+
if (groupClass === 'gender') {
|
|
741
|
+
return groupCandidates.find((group) => {
|
|
742
|
+
const set = getOptionSet(group);
|
|
743
|
+
return set.has('男') || set.has('女');
|
|
744
|
+
}) || null;
|
|
745
|
+
}
|
|
746
|
+
if (groupClass === 'recentNotView') {
|
|
747
|
+
return groupCandidates.find((group) => {
|
|
748
|
+
const set = getOptionSet(group);
|
|
749
|
+
return set.has('近14天没有');
|
|
750
|
+
}) || null;
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
};
|
|
754
|
+
const group = findGroup();
|
|
755
|
+
if (!group) {
|
|
756
|
+
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
757
|
+
}
|
|
758
|
+
const options = Array.from(group.querySelectorAll('.options .option, .option'));
|
|
759
|
+
const target = label === '不限'
|
|
760
|
+
? (group.querySelector('.default.option') || options.find((item) => normalize(item.textContent) === '不限'))
|
|
761
|
+
: options.find((item) => normalize(item.textContent) === normalize(label));
|
|
762
|
+
if (!target) {
|
|
763
|
+
return { ok: false, error: 'OPTION_NOT_FOUND' };
|
|
764
|
+
}
|
|
765
|
+
target.click();
|
|
766
|
+
return { ok: true };
|
|
767
|
+
})(${JSON.stringify(groupClass)}, ${JSON.stringify(label)})`);
|
|
568
768
|
}
|
|
569
769
|
|
|
570
770
|
async getDegreeFilterState() {
|
|
@@ -576,11 +776,20 @@ class RecommendSearchCli {
|
|
|
576
776
|
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
577
777
|
}
|
|
578
778
|
const doc = frame.contentDocument;
|
|
579
|
-
const
|
|
779
|
+
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
780
|
+
const groups = Array.from(doc.querySelectorAll('.check-box'));
|
|
781
|
+
const group = doc.querySelector('.check-box.degree')
|
|
782
|
+
|| groups.find((item) => {
|
|
783
|
+
const set = new Set(
|
|
784
|
+
Array.from(item.querySelectorAll('.default.option, .options .option, .option'))
|
|
785
|
+
.map((node) => normalize(node.textContent))
|
|
786
|
+
.filter(Boolean)
|
|
787
|
+
);
|
|
788
|
+
return set.has('大专') || set.has('本科') || set.has('硕士') || set.has('博士');
|
|
789
|
+
});
|
|
580
790
|
if (!group) {
|
|
581
791
|
return { ok: false, error: 'GROUP_NOT_FOUND' };
|
|
582
792
|
}
|
|
583
|
-
const normalize = (value) => String(value || '').replace(/\\s+/g, '').trim();
|
|
584
793
|
const labels = ${JSON.stringify(DEGREE_OPTIONS)};
|
|
585
794
|
const activeLabels = labels.filter((label) => {
|
|
586
795
|
const node = Array.from(group.querySelectorAll('.options .option'))
|
|
@@ -597,6 +806,11 @@ class RecommendSearchCli {
|
|
|
597
806
|
}
|
|
598
807
|
|
|
599
808
|
async selectDegreeFilter(labels) {
|
|
809
|
+
const ensure = await this.ensureGroupReady("degree");
|
|
810
|
+
if (!ensure?.ok) {
|
|
811
|
+
throw new Error(ensure?.error || "GROUP_NOT_FOUND");
|
|
812
|
+
}
|
|
813
|
+
|
|
600
814
|
const targetLabels = Array.isArray(labels) && labels.length > 0 ? labels : ["不限"];
|
|
601
815
|
if (targetLabels.includes("不限")) {
|
|
602
816
|
await this.selectOption("degree", "不限");
|