@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.
@@ -24,6 +24,204 @@ const PAGE_SCOPE_TAB_STATUS = {
24
24
  const BOTTOM_HINT_KEYWORDS = ["没有更多", "已显示全部", "已经到底", "暂无更多", "推荐完了", "没有更多人选"];
25
25
  const LOAD_MORE_HINT_KEYWORDS = ["滚动加载更多", "下滑加载更多", "继续下滑", "继续滑动", "滑动加载", "正在加载", "加载中"];
26
26
 
27
+ function getHealingRulesPath() {
28
+ const fromEnv = normalizeText(process.env.BOSS_RECOMMEND_HEALING_RULES_FILE || "");
29
+ return fromEnv
30
+ ? path.resolve(fromEnv)
31
+ : path.resolve(__dirname, "..", "..", "src", "recommend-healing-rules.json");
32
+ }
33
+
34
+ function loadHealingRules() {
35
+ try {
36
+ return JSON.parse(fs.readFileSync(getHealingRulesPath(), "utf8"));
37
+ } catch {
38
+ return {};
39
+ }
40
+ }
41
+
42
+ function getHealingValue(root, pathParts, fallback) {
43
+ let current = root;
44
+ for (const part of pathParts) {
45
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
46
+ current = undefined;
47
+ break;
48
+ }
49
+ current = current[part];
50
+ }
51
+ if (Array.isArray(current) && current.length > 0) {
52
+ return current.map((item) => String(item));
53
+ }
54
+ if (current && typeof current === "object" && !Array.isArray(current)) {
55
+ return JSON.parse(JSON.stringify(current));
56
+ }
57
+ if (typeof current === "string") return current;
58
+ return fallback;
59
+ }
60
+
61
+ function compilePatternList(patterns = []) {
62
+ return (Array.isArray(patterns) ? patterns : [])
63
+ .map((pattern) => {
64
+ try {
65
+ return new RegExp(String(pattern), "i");
66
+ } catch {
67
+ return null;
68
+ }
69
+ })
70
+ .filter(Boolean);
71
+ }
72
+
73
+ function firstMatchingPattern(text, patterns = []) {
74
+ const normalized = String(text || "");
75
+ for (const pattern of compilePatternList(patterns)) {
76
+ if (pattern.test(normalized)) return pattern.source;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ function buildFirstSelectorLookupExpression(selectors = [], rootExpr = "document") {
82
+ return `(() => {
83
+ const selectors = ${JSON.stringify(selectors)};
84
+ for (const selector of selectors) {
85
+ try {
86
+ const node = ${rootExpr}.querySelector(selector);
87
+ if (node) return node;
88
+ } catch {}
89
+ }
90
+ return null;
91
+ })()`;
92
+ }
93
+
94
+ function buildSelectorCollectionExpression(selectors = [], rootExpr = "document") {
95
+ return `(() => {
96
+ const selectors = ${JSON.stringify(selectors)};
97
+ const nodes = [];
98
+ for (const selector of selectors) {
99
+ try {
100
+ nodes.push(...Array.from(${rootExpr}.querySelectorAll(selector)));
101
+ } catch {}
102
+ }
103
+ return Array.from(new Set(nodes));
104
+ })()`;
105
+ }
106
+
107
+ const HEALING_RULES = loadHealingRules();
108
+ const RECOMMEND_IFRAME_SELECTORS = getHealingValue(
109
+ HEALING_RULES,
110
+ ["selectors", "top", "recommend_iframe"],
111
+ ['iframe[name="recommendFrame"]', 'iframe[src*="/web/frame/recommend/"]', "iframe"]
112
+ );
113
+ const RECOMMEND_CARD_SELECTORS = getHealingValue(HEALING_RULES, ["selectors", "frame", "recommend_cards"], ["ul.card-list > li.card-item"]);
114
+ const FEATURED_CARD_SELECTORS = getHealingValue(HEALING_RULES, ["selectors", "frame", "featured_cards"], ["li.geek-info-card"]);
115
+ const LATEST_CARD_SELECTORS = getHealingValue(HEALING_RULES, ["selectors", "frame", "latest_cards"], [".candidate-card-wrap"]);
116
+ const RECOMMEND_TAB_SELECTORS = getHealingValue(
117
+ HEALING_RULES,
118
+ ["selectors", "frame", "tab_items"],
119
+ ["li.tab-item[data-status]", 'li[data-status][class*="tab"]']
120
+ );
121
+ const DETAIL_POPUP_SELECTORS = getHealingValue(
122
+ HEALING_RULES,
123
+ ["selectors", "detail", "popup"],
124
+ [
125
+ ".boss-popup__wrapper",
126
+ ".boss-popup_wrapper",
127
+ ".boss-dialog_wrapper",
128
+ ".dialog-wrap.active",
129
+ ".boss-dialog",
130
+ ".geek-detail-modal",
131
+ ".resume-item-detail"
132
+ ]
133
+ );
134
+ const DETAIL_RESUME_IFRAME_SELECTORS = getHealingValue(
135
+ HEALING_RULES,
136
+ ["selectors", "detail", "resume_iframe"],
137
+ ['iframe[src*="/web/frame/c-resume/"]', 'iframe[name*="resume"]']
138
+ );
139
+ const DETAIL_CLOSE_SELECTORS = getHealingValue(
140
+ HEALING_RULES,
141
+ ["selectors", "detail", "close_button"],
142
+ [
143
+ ".boss-popup__close",
144
+ ".popup-close",
145
+ ".modal-close",
146
+ ".dialog-close",
147
+ ".close-btn",
148
+ 'button[aria-label*="关闭"]',
149
+ 'button[title*="关闭"]',
150
+ ".icon-close"
151
+ ]
152
+ );
153
+ const FAVORITE_BUTTON_SELECTORS = getHealingValue(
154
+ HEALING_RULES,
155
+ ["selectors", "detail", "favorite_button"],
156
+ [".like-icon-and-text"]
157
+ );
158
+ const GREET_BUTTON_RECOMMEND_SELECTORS = getHealingValue(
159
+ HEALING_RULES,
160
+ ["selectors", "detail", "greet_button_recommend"],
161
+ [
162
+ "button.btn-v2.btn-sure-v2.btn-greet",
163
+ ".resume-footer.item-operate button.btn-v2",
164
+ ".resume-footer-wrap button.btn-v2",
165
+ ".resume-footer.item-operate button",
166
+ ".resume-footer-wrap button"
167
+ ]
168
+ );
169
+ const GREET_BUTTON_FEATURED_SELECTORS = getHealingValue(
170
+ HEALING_RULES,
171
+ ["selectors", "detail", "greet_button_featured"],
172
+ [
173
+ "button.btn-v2.position-rights.btn-sure-v2",
174
+ "button.btn-v2.btn-sure-v2.position-rights",
175
+ ".resume-footer.item-operate button.btn-v2",
176
+ ".resume-footer-wrap button.btn-v2",
177
+ ".resume-footer.item-operate button",
178
+ ".resume-footer-wrap button"
179
+ ]
180
+ );
181
+ const REFRESH_FINISHED_WRAP_SELECTORS = getHealingValue(HEALING_RULES, ["selectors", "frame", "refresh_finished_wrap"], [".finished-wrap"]);
182
+ const REFRESH_BUTTON_SELECTORS = getHealingValue(
183
+ HEALING_RULES,
184
+ ["selectors", "frame", "refresh_button"],
185
+ [".finished-wrap .btn.btn-refresh", ".finished-wrap .btn-refresh", ".no-data-refresh .btn-refresh"]
186
+ );
187
+ const RESUME_INFO_URL_PATTERNS = getHealingValue(
188
+ HEALING_RULES,
189
+ ["network", "resume", "info_url_patterns"],
190
+ [
191
+ "\\/wapi\\/zpjob\\/view\\/geek\\/info\\b",
192
+ "\\/wapi\\/zpitem\\/web\\/boss\\/[^?#]*\\/geek\\/info\\b",
193
+ "\\/boss\\/[^?#]*\\/geek\\/info\\b",
194
+ "\\/geek\\/info\\b",
195
+ "[?&](?:geekid|geek_id|encryptgeekid|encryptjid|jid|securityid)="
196
+ ]
197
+ );
198
+ const RESUME_RELATED_KEYWORDS = getHealingValue(
199
+ HEALING_RULES,
200
+ ["network", "resume", "related_keywords"],
201
+ ["geek", "resume", "candidate", "friend"]
202
+ );
203
+ const FAVORITE_ADD_PATTERNS = getHealingValue(
204
+ HEALING_RULES,
205
+ ["network", "favorite", "add_patterns"],
206
+ [
207
+ "\\/add(?:\\/|$)|[?&](?:action|op|operation|type)=add\\b|[?&](?:status|p3|favorite|collect|interested)=1\\b",
208
+ "(?:^|[_\\W])(add|favorite|collect|interest(?:ed)?)(?:$|[_\\W])"
209
+ ]
210
+ );
211
+ const FAVORITE_REMOVE_PATTERNS = getHealingValue(
212
+ HEALING_RULES,
213
+ ["network", "favorite", "remove_patterns"],
214
+ [
215
+ "\\/del(?:\\/|$)|[?&](?:action|op|operation|type)=del\\b|[?&](?:status|p3|favorite|collect|interested)=0\\b",
216
+ "(?:^|[_\\W])(del|delete|remove|cancel|unfavorite|uncollect|uninterest)(?:$|[_\\W])"
217
+ ]
218
+ );
219
+ const FAVORITE_ACTIONLOG_NAME = getHealingValue(
220
+ HEALING_RULES,
221
+ ["network", "favorite", "actionlog_action_name"],
222
+ "star-interest-click"
223
+ );
224
+
27
225
  function classifyFinishedWrapState(finishedWrapText, refreshButtonVisible = false) {
28
226
  const normalizedText = normalizeText(finishedWrapText);
29
227
  const matchedBottomKeyword = BOTTOM_HINT_KEYWORDS.find((keyword) => normalizedText.includes(keyword)) || null;
@@ -292,10 +490,10 @@ function parseFavoriteActionFromRequest(url, postData = "") {
292
490
  const normalizedUrl = normalizeText(url).toLowerCase();
293
491
  if (!normalizedUrl) return null;
294
492
 
295
- if (/\/add(?:\/|$)|[?&](?:action|op|operation|type)=add\b|[?&](?:status|p3|favorite|collect|interested)=1\b/i.test(normalizedUrl)) {
493
+ if (firstMatchingPattern(normalizedUrl, FAVORITE_ADD_PATTERNS)) {
296
494
  return "add";
297
495
  }
298
- if (/\/del(?:\/|$)|[?&](?:action|op|operation|type)=del\b|[?&](?:status|p3|favorite|collect|interested)=0\b/i.test(normalizedUrl)) {
496
+ if (firstMatchingPattern(normalizedUrl, FAVORITE_REMOVE_PATTERNS)) {
299
497
  return "del";
300
498
  }
301
499
 
@@ -307,14 +505,14 @@ function parseFavoriteActionFromActionLog(postData = "") {
307
505
  if (!raw) return null;
308
506
  try {
309
507
  const payload = JSON.parse(raw);
310
- if (normalizeText(payload?.action).toLowerCase() !== "star-interest-click") return null;
508
+ if (normalizeText(payload?.action).toLowerCase() !== normalizeText(FAVORITE_ACTIONLOG_NAME).toLowerCase()) return null;
311
509
  return parseFavoriteActionValue(payload?.p3);
312
510
  } catch {}
313
511
 
314
512
  try {
315
513
  const params = new URLSearchParams(raw);
316
514
  const actionName = normalizeText(params.get("action")).toLowerCase();
317
- if (actionName !== "star-interest-click") return null;
515
+ if (actionName !== normalizeText(FAVORITE_ACTIONLOG_NAME).toLowerCase()) return null;
318
516
  return parseFavoriteActionValue(params.get("p3"));
319
517
  } catch {}
320
518
  return null;
@@ -345,7 +543,7 @@ function parseFavoriteActionFromWsPayload(payload, depth = 0) {
345
543
  if (depth > 3 || payload === null || payload === undefined) return null;
346
544
 
347
545
  if (typeof payload === "object") {
348
- if (normalizeText(payload?.action).toLowerCase() === "star-interest-click") {
546
+ if (normalizeText(payload?.action).toLowerCase() === normalizeText(FAVORITE_ACTIONLOG_NAME).toLowerCase()) {
349
547
  const strictAction = parseFavoriteActionValue(payload?.p3);
350
548
  if (strictAction) return strictAction;
351
549
  }
@@ -830,23 +1028,13 @@ function extractResumePayloadFromResponseBody(parsedBody) {
830
1028
  function isResumeInfoRequestUrl(url) {
831
1029
  const normalizedUrl = normalizeText(url).toLowerCase();
832
1030
  if (!normalizedUrl || !normalizedUrl.includes("/wapi/")) return false;
833
- if (!normalizedUrl.includes("geek") || !normalizedUrl.includes("info")) return false;
834
- if (/\/wapi\/zpjob\/view\/geek\/info\b/.test(normalizedUrl)) return true;
835
- if (/\/wapi\/zpitem\/web\/boss\/[^?#]*\/geek\/info\b/.test(normalizedUrl)) return true;
836
- if (/\/boss\/[^?#]*\/geek\/info\b/.test(normalizedUrl)) return true;
837
- if (/\/geek\/info\b/.test(normalizedUrl)) return true;
838
- return /[?&](?:geekid|geek_id|encryptgeekid|encryptjid|jid|securityid)=/.test(normalizedUrl);
1031
+ return Boolean(firstMatchingPattern(normalizedUrl, RESUME_INFO_URL_PATTERNS));
839
1032
  }
840
1033
 
841
1034
  function isResumeRelatedWapiUrl(url) {
842
1035
  const normalizedUrl = normalizeText(url).toLowerCase();
843
1036
  if (!normalizedUrl || !normalizedUrl.includes("/wapi/")) return false;
844
- return (
845
- normalizedUrl.includes("geek")
846
- || normalizedUrl.includes("resume")
847
- || normalizedUrl.includes("candidate")
848
- || normalizedUrl.includes("friend")
849
- );
1037
+ return RESUME_RELATED_KEYWORDS.some((keyword) => normalizedUrl.includes(String(keyword).toLowerCase()));
850
1038
  }
851
1039
 
852
1040
  function formatResumeApiData(data) {
@@ -994,20 +1182,18 @@ async function promptMaxGreetCount() {
994
1182
 
995
1183
  function buildListCandidatesExpr(processedKeys) {
996
1184
  return `((processedKeys) => {
997
- const frame = document.querySelector('iframe[name="recommendFrame"]')
998
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
999
- || document.querySelector('iframe');
1185
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1000
1186
  if (!frame || !frame.contentDocument) {
1001
1187
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1002
1188
  }
1003
1189
  const doc = frame.contentDocument;
1004
1190
  const frameRect = frame.getBoundingClientRect();
1005
1191
  const processed = new Set(processedKeys || []);
1006
- const cards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item'));
1007
- const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card'));
1008
- const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap'));
1192
+ const cards = ${buildSelectorCollectionExpression(RECOMMEND_CARD_SELECTORS, "doc")};
1193
+ const featuredCards = ${buildSelectorCollectionExpression(FEATURED_CARD_SELECTORS, "doc")};
1194
+ const latestCards = ${buildSelectorCollectionExpression(LATEST_CARD_SELECTORS, "doc")};
1009
1195
  const textOf = (el) => String(el ? el.textContent : '').replace(/\s+/g, ' ').trim();
1010
- const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
1196
+ const tabs = ${buildSelectorCollectionExpression(RECOMMEND_TAB_SELECTORS, "doc")};
1011
1197
  const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
1012
1198
  const activeStatus = activeTab ? String(activeTab.getAttribute('data-status') || '') : '';
1013
1199
  const recommendCandidates = cards.map((card, index) => {
@@ -1142,22 +1328,20 @@ function buildListCandidatesExpr(processedKeys) {
1142
1328
  }
1143
1329
 
1144
1330
  const jsGetListState = `(() => {
1145
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1146
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1147
- || document.querySelector('iframe');
1331
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1148
1332
  if (!frame || !frame.contentDocument) {
1149
1333
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1150
1334
  }
1151
1335
  const doc = frame.contentDocument;
1152
1336
  const body = doc.body;
1153
1337
  const frameRect = frame.getBoundingClientRect();
1154
- const cards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item'));
1338
+ const cards = ${buildSelectorCollectionExpression(RECOMMEND_CARD_SELECTORS, "doc")};
1155
1339
  const candidateCards = cards.filter((card) => card.querySelector('.card-inner[data-geekid]'));
1156
- const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card'));
1340
+ const featuredCards = ${buildSelectorCollectionExpression(FEATURED_CARD_SELECTORS, "doc")};
1157
1341
  const featuredCandidates = featuredCards.filter((card) => card.querySelector('a[data-geekid]'));
1158
- const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap'));
1342
+ const latestCards = ${buildSelectorCollectionExpression(LATEST_CARD_SELECTORS, "doc")};
1159
1343
  const latestCandidates = latestCards.filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
1160
- const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
1344
+ const tabs = ${buildSelectorCollectionExpression(RECOMMEND_TAB_SELECTORS, "doc")};
1161
1345
  const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
1162
1346
  const activeTabStatus = activeTab ? String(activeTab.getAttribute('data-status') || '') : '';
1163
1347
  const inferredStatus = activeTabStatus
@@ -1201,18 +1385,16 @@ const jsGetListState = `(() => {
1201
1385
  })()`;
1202
1386
 
1203
1387
  const jsScrollList = `(() => {
1204
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1205
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1206
- || document.querySelector('iframe');
1388
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1207
1389
  if (!frame || !frame.contentDocument) {
1208
1390
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1209
1391
  }
1210
1392
  const doc = frame.contentDocument;
1211
1393
  const body = doc.body;
1212
- const recommendCards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item')).filter((card) => card.querySelector('.card-inner[data-geekid]'));
1213
- const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card')).filter((card) => card.querySelector('a[data-geekid]'));
1214
- const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap')).filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
1215
- const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
1394
+ const recommendCards = ${buildSelectorCollectionExpression(RECOMMEND_CARD_SELECTORS, "doc")}.filter((card) => card.querySelector('.card-inner[data-geekid]'));
1395
+ const featuredCards = ${buildSelectorCollectionExpression(FEATURED_CARD_SELECTORS, "doc")}.filter((card) => card.querySelector('a[data-geekid]'));
1396
+ const latestCards = ${buildSelectorCollectionExpression(LATEST_CARD_SELECTORS, "doc")}.filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
1397
+ const tabs = ${buildSelectorCollectionExpression(RECOMMEND_TAB_SELECTORS, "doc")};
1216
1398
  const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
1217
1399
  const activeStatus = activeTab ? String(activeTab.getAttribute('data-status') || '') : '';
1218
1400
  const inferredStatus = activeStatus
@@ -1249,9 +1431,7 @@ const jsScrollList = `(() => {
1249
1431
  })()`;
1250
1432
 
1251
1433
  const jsDetectBottom = `(() => {
1252
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1253
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1254
- || document.querySelector('iframe');
1434
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1255
1435
  if (!frame || !frame.contentDocument) {
1256
1436
  return { isBottom: false, error: 'NO_RECOMMEND_IFRAME' };
1257
1437
  }
@@ -1267,8 +1447,9 @@ const jsDetectBottom = `(() => {
1267
1447
  const rect = el.getBoundingClientRect();
1268
1448
  return rect.width > 2 && rect.height > 2 && el.offsetParent !== null;
1269
1449
  };
1270
- const finishedWrap = Array.from(doc.querySelectorAll('.finished-wrap')).find((el) => isVisible(el)) || null;
1271
- const refreshButton = Array.from(doc.querySelectorAll('.finished-wrap .btn.btn-refresh, .finished-wrap .btn-refresh, .no-data-refresh .btn-refresh'))
1450
+ const finishedWrap = ${buildSelectorCollectionExpression(REFRESH_FINISHED_WRAP_SELECTORS, "doc")}
1451
+ .find((el) => isVisible(el)) || null;
1452
+ const refreshButton = ${buildSelectorCollectionExpression(REFRESH_BUTTON_SELECTORS, "doc")}
1272
1453
  .find((el) => isVisible(el)) || null;
1273
1454
  const keywords = ${JSON.stringify(BOTTOM_HINT_KEYWORDS)};
1274
1455
  const loadMoreKeywords = ${JSON.stringify(LOAD_MORE_HINT_KEYWORDS)};
@@ -1316,13 +1497,7 @@ const jsWaitForDetail = `(() => {
1316
1497
  const rect = el.getBoundingClientRect();
1317
1498
  return rect.width > 2 && rect.height > 2;
1318
1499
  };
1319
- const topSignals = [
1320
- '.dialog-wrap.active',
1321
- '.boss-popup__wrapper',
1322
- '.boss-popup_wrapper',
1323
- 'iframe[src*="/web/frame/c-resume/"]',
1324
- 'iframe[name*="resume"]'
1325
- ];
1500
+ const topSignals = ${JSON.stringify([...DETAIL_POPUP_SELECTORS, ...DETAIL_RESUME_IFRAME_SELECTORS])};
1326
1501
  for (const sel of topSignals) {
1327
1502
  const nodes = Array.from(document.querySelectorAll(sel));
1328
1503
  for (const node of nodes) {
@@ -1331,9 +1506,7 @@ const jsWaitForDetail = `(() => {
1331
1506
  }
1332
1507
  }
1333
1508
  }
1334
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1335
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1336
- || document.querySelector('iframe');
1509
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1337
1510
  if (!frame || !frame.contentDocument) {
1338
1511
  return { open: false, error: 'NO_RECOMMEND_IFRAME' };
1339
1512
  }
@@ -1356,10 +1529,10 @@ const jsWaitForDetail = `(() => {
1356
1529
  if (viewportWidth <= 0 || viewportHeight <= 0) return el.offsetParent !== null;
1357
1530
  return rect.right > 0 && rect.bottom > 0 && rect.left < viewportWidth && rect.top < viewportHeight;
1358
1531
  };
1359
- const close = doc.querySelector('.boss-popup__close');
1360
- const favorite = doc.querySelector('.like-icon-and-text');
1361
- const greet = doc.querySelector('button.btn-v2.btn-sure-v2.btn-greet');
1362
- const resumeFrame = doc.querySelector('iframe[src*="/web/frame/c-resume/"], iframe[name*="resume"]');
1532
+ const close = ${buildFirstSelectorLookupExpression(DETAIL_CLOSE_SELECTORS, "doc")};
1533
+ const favorite = ${buildFirstSelectorLookupExpression(FAVORITE_BUTTON_SELECTORS, "doc")};
1534
+ const greet = ${buildFirstSelectorLookupExpression(GREET_BUTTON_RECOMMEND_SELECTORS, "doc")};
1535
+ const resumeFrame = ${buildFirstSelectorLookupExpression(DETAIL_RESUME_IFRAME_SELECTORS, "doc")};
1363
1536
  const open = Boolean(
1364
1537
  isVisibleInViewport(close)
1365
1538
  || isVisibleInViewport(favorite)
@@ -1392,16 +1565,7 @@ const jsCloseDetail = `(() => {
1392
1565
  const rect = el.getBoundingClientRect();
1393
1566
  return rect.width > 2 && rect.height > 2;
1394
1567
  };
1395
- const topCloseSelectors = [
1396
- '.boss-popup__close',
1397
- '.popup-close',
1398
- '.modal-close',
1399
- '.dialog-close',
1400
- '.close-btn',
1401
- 'button[aria-label*="关闭"]',
1402
- 'button[title*="关闭"]',
1403
- '.icon-close'
1404
- ];
1568
+ const topCloseSelectors = ${JSON.stringify(DETAIL_CLOSE_SELECTORS)};
1405
1569
  for (const sel of topCloseSelectors) {
1406
1570
  const nodes = Array.from(document.querySelectorAll(sel));
1407
1571
  for (const node of nodes) {
@@ -1413,9 +1577,7 @@ const jsCloseDetail = `(() => {
1413
1577
  }
1414
1578
  }
1415
1579
 
1416
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1417
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1418
- || document.querySelector('iframe');
1580
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1419
1581
  if (!frame || !frame.contentDocument) {
1420
1582
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1421
1583
  }
@@ -1445,16 +1607,7 @@ const jsCloseDetail = `(() => {
1445
1607
  return rect.right > 0 && rect.bottom > 0 && rect.left < viewportWidth && rect.top < viewportHeight;
1446
1608
  };
1447
1609
 
1448
- const directCloseSelectors = [
1449
- '.boss-popup__close',
1450
- '.popup-close',
1451
- '.modal-close',
1452
- '.dialog-close',
1453
- '.close-btn',
1454
- 'button[aria-label*="关闭"]',
1455
- 'button[title*="关闭"]',
1456
- '.icon-close'
1457
- ];
1610
+ const directCloseSelectors = ${JSON.stringify(DETAIL_CLOSE_SELECTORS)};
1458
1611
  for (const sel of directCloseSelectors) {
1459
1612
  const nodes = Array.from(doc.querySelectorAll(sel));
1460
1613
  for (const node of nodes) {
@@ -1532,15 +1685,7 @@ const jsIsDetailClosed = `(() => {
1532
1685
  return { closed: false, reason: 'top know button visible' };
1533
1686
  }
1534
1687
 
1535
- const topPopupSelectors = [
1536
- '.boss-popup__wrapper',
1537
- '.boss-popup_wrapper',
1538
- '.boss-dialog_wrapper',
1539
- '.dialog-wrap.active',
1540
- '.boss-dialog',
1541
- '[class*="popup"][class*="wrapper"]',
1542
- '[class*="dialog"][class*="wrapper"]'
1543
- ];
1688
+ const topPopupSelectors = ${JSON.stringify(DETAIL_POPUP_SELECTORS)};
1544
1689
  for (const sel of topPopupSelectors) {
1545
1690
  const nodes = Array.from(document.querySelectorAll(sel));
1546
1691
  for (const node of nodes) {
@@ -1552,9 +1697,7 @@ const jsIsDetailClosed = `(() => {
1552
1697
  }
1553
1698
  }
1554
1699
 
1555
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1556
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1557
- || document.querySelector('iframe');
1700
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1558
1701
  if (!frame || !frame.contentDocument) {
1559
1702
  return { closed: true, reason: 'NO_RECOMMEND_IFRAME' };
1560
1703
  }
@@ -1579,17 +1722,7 @@ const jsIsDetailClosed = `(() => {
1579
1722
  return rect.right > 0 && rect.bottom > 0 && rect.left < viewportWidth && rect.top < viewportHeight;
1580
1723
  };
1581
1724
 
1582
- const popupSelectors = [
1583
- '.boss-popup__wrapper',
1584
- '.boss-popup_wrapper',
1585
- '.boss-dialog_wrapper',
1586
- '.dialog-wrap.active',
1587
- '.boss-dialog',
1588
- '[class*="popup"][class*="wrapper"]',
1589
- '[class*="dialog"][class*="wrapper"]',
1590
- '.geek-detail-modal',
1591
- '.resume-item-detail'
1592
- ];
1725
+ const popupSelectors = ${JSON.stringify(DETAIL_POPUP_SELECTORS)};
1593
1726
  for (const sel of popupSelectors) {
1594
1727
  const nodes = Array.from(doc.querySelectorAll(sel));
1595
1728
  for (const node of nodes) {
@@ -1599,12 +1732,7 @@ const jsIsDetailClosed = `(() => {
1599
1732
  }
1600
1733
  }
1601
1734
 
1602
- const detailSignals = [
1603
- '.like-icon-and-text',
1604
- 'button.btn-v2.btn-sure-v2.btn-greet',
1605
- 'iframe[src*="/web/frame/c-resume/"]',
1606
- 'iframe[name*="resume"]'
1607
- ];
1735
+ const detailSignals = ${JSON.stringify([...FAVORITE_BUTTON_SELECTORS, ...GREET_BUTTON_RECOMMEND_SELECTORS, ...DETAIL_RESUME_IFRAME_SELECTORS])};
1608
1736
  for (const sel of detailSignals) {
1609
1737
  const node = doc.querySelector(sel);
1610
1738
  if (isVisible(node)) {
@@ -1629,7 +1757,8 @@ const jsGetFavoriteState = `(() => {
1629
1757
  };
1630
1758
  const resolveFavorite = (doc, offsetX, offsetY, scope) => {
1631
1759
  if (!doc) return null;
1632
- const direct = Array.from(doc.querySelectorAll('.like-icon-and-text')).find((node) => isVisible(doc, node)) || null;
1760
+ const direct = ${buildSelectorCollectionExpression(FAVORITE_BUTTON_SELECTORS, "doc")}
1761
+ .find((node) => isVisible(doc, node)) || null;
1633
1762
  const footer = doc.querySelector('.resume-footer.item-operate, .resume-footer-wrap, .resume-footer');
1634
1763
  const fromFooter = footer
1635
1764
  ? Array.from(footer.querySelectorAll('[class*="collect"], [class*="favorite"], button, .btn, span'))
@@ -1661,9 +1790,7 @@ const jsGetFavoriteState = `(() => {
1661
1790
  const topResult = resolveFavorite(document, 0, 0, 'top');
1662
1791
  if (topResult) return topResult;
1663
1792
 
1664
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1665
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1666
- || document.querySelector('iframe');
1793
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1667
1794
  if (!frame || !frame.contentDocument) {
1668
1795
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1669
1796
  }
@@ -1674,12 +1801,10 @@ const jsGetFavoriteState = `(() => {
1674
1801
  })()`;
1675
1802
 
1676
1803
  const jsClickFavoriteFallback = `(() => {
1677
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1678
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1679
- || document.querySelector('iframe');
1804
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1680
1805
  if (!frame || !frame.contentDocument) return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1681
1806
  const doc = frame.contentDocument;
1682
- const root = doc.querySelector('.like-icon-and-text');
1807
+ const root = ${buildFirstSelectorLookupExpression(FAVORITE_BUTTON_SELECTORS, "doc")};
1683
1808
  if (!root || root.offsetParent === null) return { ok: false, error: 'FAVORITE_BUTTON_NOT_FOUND' };
1684
1809
  root.click();
1685
1810
  return { ok: true };
@@ -1697,14 +1822,10 @@ const jsGetGreetStateRecommend = `(() => {
1697
1822
  const rect = el.getBoundingClientRect();
1698
1823
  return rect.width > 2 && rect.height > 2;
1699
1824
  };
1700
- const resolveGreet = (doc, offsetX, offsetY, scope) => {
1701
- if (!doc) return null;
1702
- const candidates = [
1703
- ...Array.from(doc.querySelectorAll('button.btn-v2.btn-sure-v2.btn-greet')),
1704
- ...Array.from(doc.querySelectorAll('.resume-footer.item-operate button.btn-v2, .resume-footer-wrap button.btn-v2')),
1705
- ...Array.from(doc.querySelectorAll('.resume-footer.item-operate button, .resume-footer-wrap button'))
1706
- ];
1707
- const button = candidates.find((item) => isVisible(doc, item) && /沟通|打招呼|聊一聊/.test(normalize(item.textContent))) || null;
1825
+ const resolveGreet = (doc, offsetX, offsetY, scope) => {
1826
+ if (!doc) return null;
1827
+ const candidates = ${buildSelectorCollectionExpression(GREET_BUTTON_RECOMMEND_SELECTORS, "doc")};
1828
+ const button = candidates.find((item) => isVisible(doc, item) && /沟通|打招呼|聊一聊/.test(normalize(item.textContent))) || null;
1708
1829
  if (!button) return null;
1709
1830
  const rect = button.getBoundingClientRect();
1710
1831
  return {
@@ -1718,9 +1839,7 @@ const jsGetGreetStateRecommend = `(() => {
1718
1839
  const topResult = resolveGreet(document, 0, 0, 'top');
1719
1840
  if (topResult) return topResult;
1720
1841
 
1721
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1722
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1723
- || document.querySelector('iframe');
1842
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1724
1843
  if (!frame || !frame.contentDocument) {
1725
1844
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1726
1845
  }
@@ -1737,12 +1856,10 @@ const jsClickGreetFallbackRecommend = `(() => {
1737
1856
  topButton.click();
1738
1857
  return { ok: true, scope: 'top' };
1739
1858
  }
1740
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1741
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1742
- || document.querySelector('iframe');
1859
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1743
1860
  if (!frame || !frame.contentDocument) return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1744
1861
  const doc = frame.contentDocument;
1745
- const button = doc.querySelector('button.btn-v2.btn-sure-v2.btn-greet');
1862
+ const button = ${buildFirstSelectorLookupExpression(GREET_BUTTON_RECOMMEND_SELECTORS, "doc")};
1746
1863
  if (!button || button.offsetParent === null) return { ok: false, error: 'GREET_BUTTON_NOT_FOUND' };
1747
1864
  button.click();
1748
1865
  return { ok: true };
@@ -1760,15 +1877,10 @@ const jsGetGreetStateFeatured = `(() => {
1760
1877
  const rect = el.getBoundingClientRect();
1761
1878
  return rect.width > 2 && rect.height > 2;
1762
1879
  };
1763
- const resolveGreet = (doc, offsetX, offsetY, scope) => {
1764
- if (!doc) return null;
1765
- const candidates = [
1766
- ...Array.from(doc.querySelectorAll('button.btn-v2.position-rights.btn-sure-v2')),
1767
- ...Array.from(doc.querySelectorAll('button.btn-v2.btn-sure-v2.position-rights')),
1768
- ...Array.from(doc.querySelectorAll('.resume-footer.item-operate button.btn-v2, .resume-footer-wrap button.btn-v2')),
1769
- ...Array.from(doc.querySelectorAll('.resume-footer.item-operate button, .resume-footer-wrap button'))
1770
- ];
1771
- const button = candidates.find((item) => isVisible(doc, item) && /立即沟通|沟通|打招呼|聊一聊/.test(normalize(item.textContent))) || null;
1880
+ const resolveGreet = (doc, offsetX, offsetY, scope) => {
1881
+ if (!doc) return null;
1882
+ const candidates = ${buildSelectorCollectionExpression(GREET_BUTTON_FEATURED_SELECTORS, "doc")};
1883
+ const button = candidates.find((item) => isVisible(doc, item) && /立即沟通|沟通|打招呼|聊一聊/.test(normalize(item.textContent))) || null;
1772
1884
  if (!button) return null;
1773
1885
  const rect = button.getBoundingClientRect();
1774
1886
  return {
@@ -1782,9 +1894,7 @@ const jsGetGreetStateFeatured = `(() => {
1782
1894
  const topResult = resolveGreet(document, 0, 0, 'top');
1783
1895
  if (topResult) return topResult;
1784
1896
 
1785
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1786
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1787
- || document.querySelector('iframe');
1897
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1788
1898
  if (!frame || !frame.contentDocument) {
1789
1899
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1790
1900
  }
@@ -1801,12 +1911,10 @@ const jsClickGreetFallbackFeatured = `(() => {
1801
1911
  topButton.click();
1802
1912
  return { ok: true, scope: 'top' };
1803
1913
  }
1804
- const frame = document.querySelector('iframe[name="recommendFrame"]')
1805
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1806
- || document.querySelector('iframe');
1914
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
1807
1915
  if (!frame || !frame.contentDocument) return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1808
1916
  const doc = frame.contentDocument;
1809
- const button = doc.querySelector('button.btn-v2.position-rights.btn-sure-v2, button.btn-v2.btn-sure-v2.position-rights');
1917
+ const button = ${buildFirstSelectorLookupExpression(GREET_BUTTON_FEATURED_SELECTORS, "doc")};
1810
1918
  if (!button || button.offsetParent === null) return { ok: false, error: 'GREET_BUTTON_NOT_FOUND' };
1811
1919
  button.click();
1812
1920
  return { ok: true };
@@ -3016,9 +3124,7 @@ class RecommendScreenCli {
3016
3124
  return { ok: false, error: "CANDIDATE_KEY_MISSING" };
3017
3125
  }
3018
3126
  return this.evaluate(`((candidateKey) => {
3019
- const frame = document.querySelector('iframe[name="recommendFrame"]')
3020
- || document.querySelector('iframe[src*="/web/frame/recommend/"]')
3021
- || document.querySelector('iframe');
3127
+ const frame = ${buildFirstSelectorLookupExpression(RECOMMEND_IFRAME_SELECTORS)};
3022
3128
  if (!frame || !frame.contentDocument) {
3023
3129
  return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
3024
3130
  }
@@ -3027,11 +3133,11 @@ class RecommendScreenCli {
3027
3133
  .find((item) => (item.getAttribute('data-geekid') || '') === String(candidateKey)) || null;
3028
3134
  const latestInner = recommendInner
3029
3135
  ? null
3030
- : Array.from(doc.querySelectorAll('.candidate-card-wrap .card-inner[data-geek], .candidate-card-wrap [data-geek]'))
3136
+ : ${buildSelectorCollectionExpression([".candidate-card-wrap .card-inner[data-geek]", ".candidate-card-wrap [data-geek]"], "doc")}
3031
3137
  .find((item) => (item.getAttribute('data-geek') || '') === String(candidateKey)) || null;
3032
3138
  const featuredAnchor = (recommendInner || latestInner)
3033
3139
  ? null
3034
- : Array.from(doc.querySelectorAll('li.geek-info-card a[data-geekid], a[data-geekid]'))
3140
+ : ${buildSelectorCollectionExpression(["li.geek-info-card a[data-geekid]", "a[data-geekid]"], "doc")}
3035
3141
  .find((item) => (item.getAttribute('data-geekid') || '') === String(candidateKey)) || null;
3036
3142
  const card = recommendInner
3037
3143
  ? (recommendInner.closest('li.card-item') || recommendInner.closest('.card-item'))