@reconcrap/boss-recommend-mcp 1.1.9 → 1.1.11

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -36,6 +36,29 @@ function normalizeText(value) {
36
36
  return String(value || "").replace(/\s+/g, " ").trim();
37
37
  }
38
38
 
39
+ function sanitizeUrl(value) {
40
+ const raw = String(value || "").replace(/\s+/g, " ").trim();
41
+ const cleaned = raw
42
+ .replace(/^\uFEFF/, "")
43
+ .replace(/[\u200B-\u200F\u2028-\u202F\u2060-\u2064\uFEFF]/g, "")
44
+ .replace(/^["']|["']$/g, "");
45
+ return cleaned.replace(/\/+$/, "");
46
+ }
47
+
48
+ function validateUrlString(raw) {
49
+ const sanitized = sanitizeUrl(raw);
50
+ if (!sanitized) return { ok: false, error: "baseUrl 为空" };
51
+ try {
52
+ const url = new URL(sanitized);
53
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
54
+ return { ok: false, error: `协议无效: ${url.protocol} (期望 http 或 https)` };
55
+ }
56
+ return { ok: true, sanitized, full: sanitized };
57
+ } catch (e) {
58
+ return { ok: false, error: `URL 格式无效: ${e.message}`, raw };
59
+ }
60
+ }
61
+
39
62
  function parsePositiveInteger(raw) {
40
63
  const value = Number.parseInt(String(raw || ""), 10);
41
64
  return Number.isFinite(value) && value > 0 ? value : null;
@@ -1116,7 +1139,7 @@ const jsClickFavoriteFallback = `(() => {
1116
1139
  return { ok: true };
1117
1140
  })()`;
1118
1141
 
1119
- const jsGetGreetState = `(() => {
1142
+ const jsGetGreetStateRecommend = `(() => {
1120
1143
  const normalize = (value) => String(value || '').replace(/\s+/g, ' ').trim();
1121
1144
  const isVisible = (doc, el) => {
1122
1145
  if (!el) return false;
@@ -1161,7 +1184,7 @@ const jsGetGreetState = `(() => {
1161
1184
  return { ok: false, error: 'GREET_BUTTON_NOT_FOUND' };
1162
1185
  })()`;
1163
1186
 
1164
- const jsClickGreetFallback = `(() => {
1187
+ const jsClickGreetFallbackRecommend = `(() => {
1165
1188
  const topButton = Array.from(document.querySelectorAll('.resume-footer.item-operate button, .resume-footer-wrap button, button.btn-v2.btn-sure-v2'))
1166
1189
  .find((item) => item && item.offsetParent !== null && /沟通|打招呼|聊一聊/.test(String(item.textContent || '').replace(/\\s+/g, ' ')));
1167
1190
  if (topButton) {
@@ -1179,6 +1202,70 @@ const jsClickGreetFallback = `(() => {
1179
1202
  return { ok: true };
1180
1203
  })()`;
1181
1204
 
1205
+ const jsGetGreetStateFeatured = `(() => {
1206
+ const normalize = (value) => String(value || '').replace(/\s+/g, ' ').trim();
1207
+ const isVisible = (doc, el) => {
1208
+ if (!el) return false;
1209
+ const view = doc.defaultView || window;
1210
+ const style = view.getComputedStyle(el);
1211
+ if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.02) {
1212
+ return false;
1213
+ }
1214
+ const rect = el.getBoundingClientRect();
1215
+ return rect.width > 2 && rect.height > 2;
1216
+ };
1217
+ const resolveGreet = (doc, offsetX, offsetY, scope) => {
1218
+ if (!doc) return null;
1219
+ const candidates = [
1220
+ ...Array.from(doc.querySelectorAll('button.btn-v2.position-rights.btn-sure-v2')),
1221
+ ...Array.from(doc.querySelectorAll('button.btn-v2.btn-sure-v2.position-rights')),
1222
+ ...Array.from(doc.querySelectorAll('.resume-footer.item-operate button.btn-v2, .resume-footer-wrap button.btn-v2')),
1223
+ ...Array.from(doc.querySelectorAll('.resume-footer.item-operate button, .resume-footer-wrap button'))
1224
+ ];
1225
+ const button = candidates.find((item) => isVisible(doc, item) && /立即沟通|沟通|打招呼|聊一聊/.test(normalize(item.textContent))) || null;
1226
+ if (!button) return null;
1227
+ const rect = button.getBoundingClientRect();
1228
+ return {
1229
+ ok: true,
1230
+ disabled: Boolean(button.disabled),
1231
+ x: offsetX + rect.left + rect.width / 2,
1232
+ y: offsetY + rect.top + rect.height / 2,
1233
+ scope
1234
+ };
1235
+ };
1236
+ const topResult = resolveGreet(document, 0, 0, 'top');
1237
+ if (topResult) return topResult;
1238
+
1239
+ const frame = document.querySelector('iframe[name="recommendFrame"]')
1240
+ || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1241
+ || document.querySelector('iframe');
1242
+ if (!frame || !frame.contentDocument) {
1243
+ return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1244
+ }
1245
+ const frameRect = frame.getBoundingClientRect();
1246
+ const frameResult = resolveGreet(frame.contentDocument, frameRect.left, frameRect.top, 'frame');
1247
+ if (frameResult) return frameResult;
1248
+ return { ok: false, error: 'GREET_BUTTON_NOT_FOUND' };
1249
+ })()`;
1250
+
1251
+ const jsClickGreetFallbackFeatured = `(() => {
1252
+ const topButton = Array.from(document.querySelectorAll('button.btn-v2.position-rights.btn-sure-v2, button.btn-v2.btn-sure-v2.position-rights, .resume-footer.item-operate button, .resume-footer-wrap button'))
1253
+ .find((item) => item && item.offsetParent !== null && /立即沟通|沟通|打招呼|聊一聊/.test(String(item.textContent || '').replace(/\\s+/g, ' ')));
1254
+ if (topButton) {
1255
+ topButton.click();
1256
+ return { ok: true, scope: 'top' };
1257
+ }
1258
+ const frame = document.querySelector('iframe[name="recommendFrame"]')
1259
+ || document.querySelector('iframe[src*="/web/frame/recommend/"]')
1260
+ || document.querySelector('iframe');
1261
+ if (!frame || !frame.contentDocument) return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
1262
+ const doc = frame.contentDocument;
1263
+ const button = doc.querySelector('button.btn-v2.position-rights.btn-sure-v2, button.btn-v2.btn-sure-v2.position-rights');
1264
+ if (!button || button.offsetParent === null) return { ok: false, error: 'GREET_BUTTON_NOT_FOUND' };
1265
+ button.click();
1266
+ return { ok: true };
1267
+ })()`;
1268
+
1182
1269
  const jsGetKnowButtonState = `(() => {
1183
1270
  const normalize = (value) => String(value || '').replace(/\s+/g, '').trim();
1184
1271
  const pickVisibleKnowButton = (doc) => {
@@ -1423,6 +1510,13 @@ const jsReloadRecommendFrame = `(() => {
1423
1510
  class RecommendScreenCli {
1424
1511
  constructor(args) {
1425
1512
  this.args = args;
1513
+ const baseUrlCheck = validateUrlString(this.args.baseUrl);
1514
+ if (this.args.baseUrl && !baseUrlCheck.ok) {
1515
+ log(`[警告] baseUrl 校验失败: ${baseUrlCheck.error}, 原始值=${JSON.stringify(this.args.baseUrl)}`);
1516
+ }
1517
+ if (baseUrlCheck.sanitized) {
1518
+ this.args.baseUrl = baseUrlCheck.sanitized;
1519
+ }
1426
1520
  this.client = null;
1427
1521
  this.Runtime = null;
1428
1522
  this.Input = null;
@@ -1924,7 +2018,7 @@ class RecommendScreenCli {
1924
2018
  async pressEsc() {
1925
2019
  await this.Input.dispatchKeyEvent({ type: "keyDown", windowsVirtualKeyCode: 27, key: "Escape", code: "Escape" });
1926
2020
  await this.Input.dispatchKeyEvent({ type: "keyUp", windowsVirtualKeyCode: 27, key: "Escape", code: "Escape" });
1927
- }
2021
+ }
1928
2022
  async ensureDetailOpen() {
1929
2023
  for (let index = 0; index < 20; index += 1) {
1930
2024
  const state = await this.evaluate(jsWaitForDetail);
@@ -2244,7 +2338,9 @@ class RecommendScreenCli {
2244
2338
 
2245
2339
  async callVisionModel(imagePath) {
2246
2340
  const imageBase64 = fs.readFileSync(imagePath, "base64");
2247
- const baseUrl = this.args.baseUrl.replace(/\/$/, "");
2341
+ const rawBaseUrl = this.args.baseUrl;
2342
+ log(`[callVisionModel] baseUrl 原始值类型=${typeof rawBaseUrl}, 长度=${rawBaseUrl != null ? String(rawBaseUrl).length : "null/undefined"}, JSON编码=${JSON.stringify(rawBaseUrl)}`);
2343
+ const baseUrl = String(rawBaseUrl || "").replace(/\/$/, "");
2248
2344
  const payload = {
2249
2345
  model: this.args.model,
2250
2346
  temperature: 0.1,
@@ -2300,7 +2396,9 @@ class RecommendScreenCli {
2300
2396
 
2301
2397
  async callTextModel(resumeText) {
2302
2398
  const safeResumeText = String(resumeText || "").slice(0, 28000);
2303
- const baseUrl = this.args.baseUrl.replace(/\/$/, "");
2399
+ const rawBaseUrl = this.args.baseUrl;
2400
+ log(`[callTextModel] baseUrl 原始值类型=${typeof rawBaseUrl}, 长度=${rawBaseUrl != null ? String(rawBaseUrl).length : "null/undefined"}, JSON编码=${JSON.stringify(rawBaseUrl)}`);
2401
+ const baseUrl = String(rawBaseUrl || "").replace(/\/$/, "");
2304
2402
  const payload = {
2305
2403
  model: this.args.model,
2306
2404
  temperature: 0.1,
@@ -2433,7 +2531,11 @@ class RecommendScreenCli {
2433
2531
  }
2434
2532
 
2435
2533
  async greetCandidate() {
2436
- const greet = await this.evaluate(jsGetGreetState);
2534
+ const greetStateScript = this.args.pageScope === "featured" ? jsGetGreetStateFeatured : jsGetGreetStateRecommend;
2535
+ const greetClickFallbackScript = this.args.pageScope === "featured"
2536
+ ? jsClickGreetFallbackFeatured
2537
+ : jsClickGreetFallbackRecommend;
2538
+ const greet = await this.evaluate(greetStateScript);
2437
2539
  if (!greet?.ok || greet.disabled) {
2438
2540
  throw this.buildError("GREET_BUTTON_FAILED", greet?.error || "打招呼按钮不可用");
2439
2541
  }
@@ -2441,7 +2543,7 @@ class RecommendScreenCli {
2441
2543
  try {
2442
2544
  await this.simulateHumanClick(greet.x, greet.y);
2443
2545
  } catch {
2444
- const fallback = await this.evaluate(jsClickGreetFallback);
2546
+ const fallback = await this.evaluate(greetClickFallbackScript);
2445
2547
  if (!fallback?.ok) {
2446
2548
  throw this.buildError("GREET_BUTTON_FAILED", fallback?.error || "打招呼点击失败");
2447
2549
  }
@@ -2962,4 +3064,4 @@ if (require.main === module) {
2962
3064
  }
2963
3065
  };
2964
3066
  }
2965
-
3067
+