@reconcrap/boss-recommend-mcp 1.2.6 → 1.2.7

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.
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import process from "node:process";
3
3
  import readline from "node:readline";
4
+ import { createRequire } from "node:module";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import CDP from "chrome-remote-interface";
7
+ import {
8
+ buildFirstSelectorLookupExpression,
9
+ getRecommendSelectorRule
10
+ } from "../../../src/recommend-healing-config.js";
6
11
 
7
12
  const DEFAULT_PORT = 9222;
8
13
  const RECOMMEND_URL_FRAGMENT = "/web/chat/recommend";
@@ -14,6 +19,56 @@ const DEGREE_OPTIONS = ["不限", "初中及以下", "中专/中技", "高中",
14
19
  const DEGREE_ORDER = ["初中及以下", "中专/中技", "高中", "大专", "本科", "硕士", "博士"];
15
20
  const GENDER_OPTIONS = ["不限", "男", "女"];
16
21
  const RECENT_NOT_VIEW_OPTIONS = ["不限", "近14天没有"];
22
+ const require = createRequire(import.meta.url);
23
+ require("../../../src/recommend-healing-rules.json");
24
+ const RECOMMEND_IFRAME_SELECTORS = getRecommendSelectorRule(
25
+ ["top", "recommend_iframe"],
26
+ ['iframe[name="recommendFrame"]', 'iframe[src*="/web/frame/recommend/"]', "iframe"]
27
+ );
28
+ const FILTER_TRIGGER_SELECTORS = getRecommendSelectorRule(
29
+ ["frame", "filter_trigger"],
30
+ [".filter-label-wrap", ".recommend-filter.op-filter"]
31
+ );
32
+ const JOB_DROPDOWN_TRIGGER_SELECTORS = getRecommendSelectorRule(
33
+ ["frame", "job_dropdown_trigger"],
34
+ [
35
+ ".chat-job-select",
36
+ ".chat-job-selector",
37
+ ".job-selecter",
38
+ ".job-selector",
39
+ ".job-select-wrap",
40
+ ".job-select",
41
+ ".job-select-box",
42
+ ".job-wrap",
43
+ ".chat-job-name",
44
+ ".top-chat-search"
45
+ ]
46
+ );
47
+ const JOB_LIST_ITEM_SELECTORS = getRecommendSelectorRule(
48
+ ["frame", "job_list_items"],
49
+ [
50
+ ".ui-dropmenu-list .job-list .job-item",
51
+ ".job-selecter-options .job-list .job-item",
52
+ ".job-selector-options .job-list .job-item",
53
+ ".dropmenu-list .job-list .job-item",
54
+ ".job-list .job-item"
55
+ ]
56
+ );
57
+ const JOB_SELECTED_LABEL_SELECTORS = getRecommendSelectorRule(
58
+ ["frame", "job_selected_label"],
59
+ [".chat-job-name", ".job-selecter .label", ".job-selecter .job-name", ".job-select .label"]
60
+ );
61
+ const RECOMMEND_CARD_SELECTORS = getRecommendSelectorRule(["frame", "recommend_cards"], ["ul.card-list > li.card-item"]);
62
+ const FEATURED_CARD_SELECTORS = getRecommendSelectorRule(["frame", "featured_cards"], ["li.geek-info-card"]);
63
+ const LATEST_CARD_SELECTORS = getRecommendSelectorRule(["frame", "latest_cards"], [".candidate-card-wrap"]);
64
+ const RECOMMEND_TAB_SELECTORS = getRecommendSelectorRule(
65
+ ["frame", "tab_items"],
66
+ ["li.tab-item[data-status]", 'li[data-status][class*="tab"]']
67
+ );
68
+
69
+ function buildRecommendFrameExpression() {
70
+ return buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS);
71
+ }
17
72
 
18
73
  function normalizeText(value) {
19
74
  return String(value || "").replace(/\s+/g, " ").trim();
@@ -419,9 +474,7 @@ class RecommendSearchCli {
419
474
  title
420
475
  };
421
476
  }
422
- const frame = document.querySelector('iframe[name="recommendFrame"]')
423
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
424
- || document.querySelector('iframe');
477
+ const frame = ${buildRecommendFrameExpression()};
425
478
  if (!frame || !frame.contentDocument) {
426
479
  return { ok: false, error: 'NO_RECOMMEND_IFRAME', currentUrl, title };
427
480
  }
@@ -438,14 +491,16 @@ class RecommendSearchCli {
438
491
 
439
492
  async getFilterEntryPoint() {
440
493
  return this.evaluate(`(() => {
441
- const frame = document.querySelector('iframe[name="recommendFrame"]')
442
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
443
- || document.querySelector('iframe');
494
+ const frame = ${buildRecommendFrameExpression()};
444
495
  if (!frame || !frame.contentDocument) {
445
496
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
446
497
  }
447
498
  const doc = frame.contentDocument;
448
- const el = doc.querySelector('.filter-label-wrap') || doc.querySelector('.recommend-filter.op-filter');
499
+ const el = ${JSON.stringify(FILTER_TRIGGER_SELECTORS)}
500
+ .map((selector) => {
501
+ try { return doc.querySelector(selector); } catch { return null; }
502
+ })
503
+ .find((node) => node) || null;
449
504
  if (!el) {
450
505
  return { ok: false, error: 'FILTER_TRIGGER_NOT_FOUND' };
451
506
  }
@@ -461,9 +516,7 @@ class RecommendSearchCli {
461
516
 
462
517
  async getJobListState() {
463
518
  return this.evaluate(`(() => {
464
- const frame = document.querySelector('iframe[name="recommendFrame"]')
465
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
466
- || document.querySelector('iframe');
519
+ const frame = ${buildRecommendFrameExpression()};
467
520
  if (!frame || !frame.contentDocument) {
468
521
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
469
522
  }
@@ -491,13 +544,10 @@ class RecommendSearchCli {
491
544
  return rect.width > 2 && rect.height > 2;
492
545
  };
493
546
 
494
- const items = Array.from(doc.querySelectorAll([
495
- '.ui-dropmenu-list .job-list .job-item',
496
- '.job-selecter-options .job-list .job-item',
497
- '.job-selector-options .job-list .job-item',
498
- '.dropmenu-list .job-list .job-item',
499
- '.job-list .job-item'
500
- ].join(',')));
547
+ const items = ${JSON.stringify(JOB_LIST_ITEM_SELECTORS)}
548
+ .flatMap((selector) => {
549
+ try { return Array.from(doc.querySelectorAll(selector)); } catch { return []; }
550
+ });
501
551
  const jobs = [];
502
552
  const seen = new Set();
503
553
  for (const item of items) {
@@ -516,7 +566,11 @@ class RecommendSearchCli {
516
566
  });
517
567
  }
518
568
 
519
- const selectedLabelNode = doc.querySelector('.chat-job-name, .job-selecter .label, .job-selecter .job-name, .job-select .label');
569
+ const selectedLabelNode = ${JSON.stringify(JOB_SELECTED_LABEL_SELECTORS)}
570
+ .map((selector) => {
571
+ try { return doc.querySelector(selector); } catch { return null; }
572
+ })
573
+ .find((node) => node) || null;
520
574
  return {
521
575
  ok: true,
522
576
  jobs,
@@ -530,25 +584,12 @@ class RecommendSearchCli {
530
584
 
531
585
  async clickJobDropdownTriggerBySelector() {
532
586
  return this.evaluate(`(() => {
533
- const frame = document.querySelector('iframe[name="recommendFrame"]')
534
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
535
- || document.querySelector('iframe');
587
+ const frame = ${buildRecommendFrameExpression()};
536
588
  if (!frame || !frame.contentDocument) {
537
589
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
538
590
  }
539
591
  const doc = frame.contentDocument;
540
- const selectors = [
541
- '.chat-job-select',
542
- '.chat-job-selector',
543
- '.job-selecter',
544
- '.job-selector',
545
- '.job-select-wrap',
546
- '.job-select',
547
- '.job-select-box',
548
- '.job-wrap',
549
- '.chat-job-name',
550
- '.top-chat-search'
551
- ];
592
+ const selectors = ${JSON.stringify(JOB_DROPDOWN_TRIGGER_SELECTORS)};
552
593
  const isVisible = (el) => {
553
594
  if (!el) return false;
554
595
  const style = getComputedStyle(el);
@@ -615,9 +656,7 @@ class RecommendSearchCli {
615
656
 
616
657
  async clickJobBySelector(job) {
617
658
  return this.evaluate(`((job) => {
618
- const frame = document.querySelector('iframe[name="recommendFrame"]')
619
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
620
- || document.querySelector('iframe');
659
+ const frame = ${buildRecommendFrameExpression()};
621
660
  if (!frame || !frame.contentDocument) {
622
661
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
623
662
  }
@@ -635,13 +674,10 @@ class RecommendSearchCli {
635
674
  .trim();
636
675
  return strippedSingle || byGap;
637
676
  };
638
- const items = Array.from(doc.querySelectorAll([
639
- '.ui-dropmenu-list .job-list .job-item',
640
- '.job-selecter-options .job-list .job-item',
641
- '.job-selector-options .job-list .job-item',
642
- '.dropmenu-list .job-list .job-item',
643
- '.job-list .job-item'
644
- ].join(',')));
677
+ const items = ${JSON.stringify(JOB_LIST_ITEM_SELECTORS)}
678
+ .flatMap((selector) => {
679
+ try { return Array.from(doc.querySelectorAll(selector)); } catch { return []; }
680
+ });
645
681
  const target = items.find((item) => {
646
682
  const value = normalize(item.getAttribute('value') || item.dataset?.value || '');
647
683
  const label = normalize(item.querySelector('.label')?.textContent || item.textContent || '');
@@ -1439,20 +1475,30 @@ class RecommendSearchCli {
1439
1475
 
1440
1476
  async countCandidates() {
1441
1477
  return this.evaluate(`(() => {
1442
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1443
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1444
- || document.querySelector('iframe');
1478
+ const frame = ${buildRecommendFrameExpression()};
1445
1479
  if (!frame || !frame.contentDocument) {
1446
1480
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1447
1481
  }
1448
1482
  const doc = frame.contentDocument;
1449
- const cards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item'));
1483
+ const cards = ${JSON.stringify(RECOMMEND_CARD_SELECTORS)}
1484
+ .flatMap((selector) => {
1485
+ try { return Array.from(doc.querySelectorAll(selector)); } catch { return []; }
1486
+ });
1450
1487
  const recommendCandidates = cards.filter((card) => card.querySelector('.card-inner[data-geekid]'));
1451
- const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card'));
1488
+ const featuredCards = ${JSON.stringify(FEATURED_CARD_SELECTORS)}
1489
+ .flatMap((selector) => {
1490
+ try { return Array.from(doc.querySelectorAll(selector)); } catch { return []; }
1491
+ });
1452
1492
  const featuredCandidates = featuredCards.filter((card) => card.querySelector('a[data-geekid]'));
1453
- const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap'));
1493
+ const latestCards = ${JSON.stringify(LATEST_CARD_SELECTORS)}
1494
+ .flatMap((selector) => {
1495
+ try { return Array.from(doc.querySelectorAll(selector)); } catch { return []; }
1496
+ });
1454
1497
  const latestCandidates = latestCards.filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
1455
- const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
1498
+ const tabs = ${JSON.stringify(RECOMMEND_TAB_SELECTORS)}
1499
+ .flatMap((selector) => {
1500
+ try { return Array.from(doc.querySelectorAll(selector)); } catch { return []; }
1501
+ });
1456
1502
  const activeTab = tabs.find((node) => {
1457
1503
  const className = String(node.className || '');
1458
1504
  const selected = String(node.getAttribute('aria-selected') || '').toLowerCase() === 'true';
@@ -1648,3 +1694,5 @@ export {
1648
1694
  normalizeJobTitle,
1649
1695
  parseArgs
1650
1696
  };
1697
+
1698
+