@reconcrap/boss-recommend-mcp 1.2.5 → 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.
- package/README.md +7 -0
- package/package.json +2 -1
- package/src/adapters.js +402 -95
- package/src/index.js +97 -0
- package/src/pipeline.js +34 -2
- package/src/recommend-healing-config.js +131 -0
- package/src/recommend-healing-rules.json +261 -0
- package/src/self-heal.js +2237 -0
- package/src/test-pipeline.js +67 -0
- package/src/test-self-heal.js +224 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +259 -153
- package/vendor/boss-recommend-search-cli/src/cli.js +98 -50
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
runRecommendCalibration
|
|
10
10
|
} from "./adapters.js";
|
|
11
11
|
import { runRecommendPipeline } from "./pipeline.js";
|
|
12
|
+
import { runRecommendSelfHeal } from "./self-heal.js";
|
|
12
13
|
import {
|
|
13
14
|
RUN_MODE_ASYNC,
|
|
14
15
|
RUN_STAGE_PREFLIGHT,
|
|
@@ -39,6 +40,7 @@ const TOOL_PAUSE_RUN = "pause_recommend_pipeline_run";
|
|
|
39
40
|
const TOOL_RESUME_RUN = "resume_recommend_pipeline_run";
|
|
40
41
|
const TOOL_RUN_FEATURED_CALIBRATION = "run_featured_calibration";
|
|
41
42
|
const TOOL_GET_FEATURED_CALIBRATION_STATUS = "get_featured_calibration_status";
|
|
43
|
+
const TOOL_RUN_RECOMMEND_SELF_HEAL = "run_recommend_self_heal";
|
|
42
44
|
|
|
43
45
|
const SERVER_NAME = "boss-recommend-mcp";
|
|
44
46
|
const FRAMING_UNKNOWN = "unknown";
|
|
@@ -49,6 +51,7 @@ const DETACHED_WORKER_RUN_ID_FLAG = "--run-id";
|
|
|
49
51
|
const DETACHED_WORKER_RESUME_FLAG = "--resume";
|
|
50
52
|
|
|
51
53
|
let runPipelineImpl = runRecommendPipeline;
|
|
54
|
+
let runSelfHealImpl = runRecommendSelfHeal;
|
|
52
55
|
let spawnProcessImpl = spawn;
|
|
53
56
|
const TERMINAL_RUN_STATES = new Set([RUN_STATE_COMPLETED, RUN_STATE_FAILED, RUN_STATE_CANCELED]);
|
|
54
57
|
|
|
@@ -321,6 +324,43 @@ function createRunFeaturedCalibrationInputSchema() {
|
|
|
321
324
|
};
|
|
322
325
|
}
|
|
323
326
|
|
|
327
|
+
function createRunRecommendSelfHealInputSchema() {
|
|
328
|
+
return {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
mode: {
|
|
332
|
+
type: "string",
|
|
333
|
+
enum: ["scan", "apply"],
|
|
334
|
+
description: "scan=扫描漂移;apply=按 repair_session_id 应用高置信度修复"
|
|
335
|
+
},
|
|
336
|
+
scope: {
|
|
337
|
+
type: "string",
|
|
338
|
+
enum: ["full", "search_screen", "selectors_only"],
|
|
339
|
+
description: "扫描范围,默认 full"
|
|
340
|
+
},
|
|
341
|
+
validation_profile: {
|
|
342
|
+
type: "string",
|
|
343
|
+
enum: ["safe", "full"],
|
|
344
|
+
description: "校验强度,默认 full"
|
|
345
|
+
},
|
|
346
|
+
port: {
|
|
347
|
+
type: "integer",
|
|
348
|
+
minimum: 1,
|
|
349
|
+
description: "可选,Boss Chrome 远程调试端口"
|
|
350
|
+
},
|
|
351
|
+
repair_session_id: {
|
|
352
|
+
type: "string",
|
|
353
|
+
description: "apply 模式必填,来自 scan 返回值"
|
|
354
|
+
},
|
|
355
|
+
confirm_apply: {
|
|
356
|
+
type: "boolean",
|
|
357
|
+
description: "apply 模式必填,必须显式传 true"
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
additionalProperties: false
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
324
364
|
function createToolsSchema() {
|
|
325
365
|
return [
|
|
326
366
|
{
|
|
@@ -389,6 +429,11 @@ function createToolsSchema() {
|
|
|
389
429
|
properties: {},
|
|
390
430
|
additionalProperties: false
|
|
391
431
|
}
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: TOOL_RUN_RECOMMEND_SELF_HEAL,
|
|
435
|
+
description: "手动运维自愈工具:扫描 Boss recommend 相关 selector / network 规则漂移,并在确认后应用高置信度修复。",
|
|
436
|
+
inputSchema: createRunRecommendSelfHealInputSchema()
|
|
392
437
|
}
|
|
393
438
|
];
|
|
394
439
|
}
|
|
@@ -448,6 +493,42 @@ function validateRunFeaturedCalibrationArgs(args) {
|
|
|
448
493
|
return null;
|
|
449
494
|
}
|
|
450
495
|
|
|
496
|
+
function validateRunRecommendSelfHealArgs(args) {
|
|
497
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
498
|
+
return "arguments must be an object";
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (Object.prototype.hasOwnProperty.call(args, "mode")) {
|
|
502
|
+
const mode = normalizeText(args.mode).toLowerCase();
|
|
503
|
+
if (!["scan", "apply"].includes(mode)) {
|
|
504
|
+
return "mode must be one of: scan, apply";
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (Object.prototype.hasOwnProperty.call(args, "scope")) {
|
|
509
|
+
const scope = normalizeText(args.scope).toLowerCase();
|
|
510
|
+
if (!["full", "search_screen", "selectors_only"].includes(scope)) {
|
|
511
|
+
return "scope must be one of: full, search_screen, selectors_only";
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (Object.prototype.hasOwnProperty.call(args, "validation_profile")) {
|
|
516
|
+
const profile = normalizeText(args.validation_profile).toLowerCase();
|
|
517
|
+
if (!["safe", "full"].includes(profile)) {
|
|
518
|
+
return "validation_profile must be one of: safe, full";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (Object.prototype.hasOwnProperty.call(args, "port")) {
|
|
523
|
+
const port = Number.parseInt(String(args.port), 10);
|
|
524
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
525
|
+
return "port must be a positive integer";
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
|
|
451
532
|
function getLastOutputLine(text) {
|
|
452
533
|
const lines = String(text || "")
|
|
453
534
|
.split(/\r?\n/)
|
|
@@ -1340,6 +1421,10 @@ async function handleRunFeaturedCalibrationTool({ workspaceRoot, args }) {
|
|
|
1340
1421
|
};
|
|
1341
1422
|
}
|
|
1342
1423
|
|
|
1424
|
+
async function handleRunRecommendSelfHealTool({ workspaceRoot, args }) {
|
|
1425
|
+
return runSelfHealImpl({ workspaceRoot, args });
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1343
1428
|
async function handleRequest(message, workspaceRoot) {
|
|
1344
1429
|
if (!message || message.jsonrpc !== "2.0") {
|
|
1345
1430
|
return createJsonRpcError(null, -32600, "Invalid JSON-RPC request");
|
|
@@ -1396,6 +1481,13 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
1396
1481
|
}
|
|
1397
1482
|
}
|
|
1398
1483
|
|
|
1484
|
+
if (toolName === TOOL_RUN_RECOMMEND_SELF_HEAL) {
|
|
1485
|
+
const inputError = validateRunRecommendSelfHealArgs(args);
|
|
1486
|
+
if (inputError) {
|
|
1487
|
+
return createJsonRpcError(id, -32602, inputError);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1399
1491
|
if ([TOOL_GET_RUN, TOOL_CANCEL_RUN, TOOL_PAUSE_RUN, TOOL_RESUME_RUN].includes(toolName)) {
|
|
1400
1492
|
if (!args || typeof args.run_id !== "string" || !normalizeText(args.run_id)) {
|
|
1401
1493
|
return createJsonRpcError(id, -32602, "run_id is required and must be a string");
|
|
@@ -1418,6 +1510,8 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
1418
1510
|
payload = handleGetFeaturedCalibrationStatusTool(workspaceRoot);
|
|
1419
1511
|
} else if (toolName === TOOL_RUN_FEATURED_CALIBRATION) {
|
|
1420
1512
|
payload = await handleRunFeaturedCalibrationTool({ workspaceRoot, args });
|
|
1513
|
+
} else if (toolName === TOOL_RUN_RECOMMEND_SELF_HEAL) {
|
|
1514
|
+
payload = await handleRunRecommendSelfHealTool({ workspaceRoot, args });
|
|
1421
1515
|
} else {
|
|
1422
1516
|
return createJsonRpcError(id, -32602, `Unknown tool: ${toolName || ""}`);
|
|
1423
1517
|
}
|
|
@@ -1553,6 +1647,9 @@ export const __testables = {
|
|
|
1553
1647
|
},
|
|
1554
1648
|
setRunPipelineImplForTests(nextImpl) {
|
|
1555
1649
|
runPipelineImpl = typeof nextImpl === "function" ? nextImpl : runRecommendPipeline;
|
|
1650
|
+
},
|
|
1651
|
+
setRunSelfHealImplForTests(nextImpl) {
|
|
1652
|
+
runSelfHealImpl = typeof nextImpl === "function" ? nextImpl : runRecommendSelfHeal;
|
|
1556
1653
|
}
|
|
1557
1654
|
};
|
|
1558
1655
|
|
package/src/pipeline.js
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
|
|
17
17
|
const FORCED_RECENT_NOT_VIEW_ON_SCREEN_RECOVERY = "近14天没有";
|
|
18
18
|
const MAX_SCREEN_AUTO_RECOVERY_ATTEMPTS = 5;
|
|
19
|
+
const MAX_SEARCH_NO_IFRAME_RETRY_ATTEMPTS = 1;
|
|
20
|
+
const SEARCH_NO_IFRAME_RETRY_DELAY_MS = 1200;
|
|
19
21
|
const PAGE_SCOPE_TO_TAB_STATUS = {
|
|
20
22
|
recommend: "0",
|
|
21
23
|
latest: "1",
|
|
@@ -40,6 +42,10 @@ function normalizeText(value) {
|
|
|
40
42
|
return String(value || "").replace(/\s+/g, " ").trim();
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
function sleep(ms) {
|
|
46
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
function normalizePageScope(value) {
|
|
44
50
|
const normalized = normalizeText(value).toLowerCase();
|
|
45
51
|
if (!normalized) return null;
|
|
@@ -886,6 +892,7 @@ export async function runRecommendPipeline(
|
|
|
886
892
|
let shouldRunSearch = !skipSearchOnResume;
|
|
887
893
|
let screenAutoRecoveryCount = 0;
|
|
888
894
|
let lastAutoRecovery = null;
|
|
895
|
+
let searchNoIframeRetryCount = 0;
|
|
889
896
|
let activeTabStatus = null;
|
|
890
897
|
let currentResumeConfig = {
|
|
891
898
|
checkpoint_path: resume?.checkpoint_path || null,
|
|
@@ -971,11 +978,14 @@ export async function runRecommendPipeline(
|
|
|
971
978
|
if (!searchResult.ok) {
|
|
972
979
|
const searchErrorCode = String(searchResult.error?.code || "");
|
|
973
980
|
const searchErrorMessage = String(searchResult.error?.message || "");
|
|
981
|
+
const isNoIframeSearchFailure = (
|
|
982
|
+
searchErrorCode === "NO_RECOMMEND_IFRAME"
|
|
983
|
+
|| searchErrorMessage.includes("NO_RECOMMEND_IFRAME")
|
|
984
|
+
);
|
|
974
985
|
const loginRelatedSearchFailure = (
|
|
975
986
|
searchErrorCode === "LOGIN_REQUIRED"
|
|
976
|
-
||
|
|
987
|
+
|| isNoIframeSearchFailure
|
|
977
988
|
|| searchErrorMessage.includes("LOGIN_REQUIRED")
|
|
978
|
-
|| searchErrorMessage.includes("NO_RECOMMEND_IFRAME")
|
|
979
989
|
);
|
|
980
990
|
if (loginRelatedSearchFailure) {
|
|
981
991
|
const recheck = await ensureRecommendPageReady(workspaceRoot, {
|
|
@@ -1006,6 +1016,28 @@ export async function runRecommendPipeline(
|
|
|
1006
1016
|
}
|
|
1007
1017
|
);
|
|
1008
1018
|
}
|
|
1019
|
+
if (
|
|
1020
|
+
isNoIframeSearchFailure
|
|
1021
|
+
&& recheck.state === "RECOMMEND_READY"
|
|
1022
|
+
&& searchNoIframeRetryCount < MAX_SEARCH_NO_IFRAME_RETRY_ATTEMPTS
|
|
1023
|
+
) {
|
|
1024
|
+
searchNoIframeRetryCount += 1;
|
|
1025
|
+
const retryDelayMs = SEARCH_NO_IFRAME_RETRY_DELAY_MS;
|
|
1026
|
+
const retryDiagnostics = {
|
|
1027
|
+
trigger: "NO_RECOMMEND_IFRAME",
|
|
1028
|
+
attempt: searchNoIframeRetryCount,
|
|
1029
|
+
max_attempts: MAX_SEARCH_NO_IFRAME_RETRY_ATTEMPTS,
|
|
1030
|
+
delay_ms: retryDelayMs,
|
|
1031
|
+
page_state: recheck.page_state || null
|
|
1032
|
+
};
|
|
1033
|
+
runtimeHooks.setStage(
|
|
1034
|
+
"search_recovery",
|
|
1035
|
+
`检测到 recommend iframe 暂未就绪,等待 ${Math.round(retryDelayMs / 1000)} 秒后重试 search(第 ${searchNoIframeRetryCount}/${MAX_SEARCH_NO_IFRAME_RETRY_ATTEMPTS} 次)。`
|
|
1036
|
+
);
|
|
1037
|
+
runtimeHooks.heartbeat("search_recovery", retryDiagnostics);
|
|
1038
|
+
await sleep(retryDelayMs);
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1009
1041
|
}
|
|
1010
1042
|
return buildFailedResponse(
|
|
1011
1043
|
searchResult.error?.code || "RECOMMEND_SEARCH_FAILED",
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
9
|
+
const defaultRulesPath = path.join(path.dirname(currentFilePath), "recommend-healing-rules.json");
|
|
10
|
+
|
|
11
|
+
let cachedRulesPath = null;
|
|
12
|
+
let cachedRulesMtime = null;
|
|
13
|
+
let cachedRules = null;
|
|
14
|
+
|
|
15
|
+
function clone(value) {
|
|
16
|
+
return JSON.parse(JSON.stringify(value));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizePathLike(value) {
|
|
20
|
+
return String(value || "").trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getNestedValue(root, pathParts = []) {
|
|
24
|
+
let current = root;
|
|
25
|
+
for (const part of pathParts) {
|
|
26
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
current = current[part];
|
|
30
|
+
}
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getRecommendHealingRulesPath() {
|
|
35
|
+
const fromEnv = normalizePathLike(process.env.BOSS_RECOMMEND_HEALING_RULES_FILE);
|
|
36
|
+
return fromEnv ? path.resolve(fromEnv) : defaultRulesPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function loadRecommendHealingRules(options = {}) {
|
|
40
|
+
const fresh = options.fresh === true;
|
|
41
|
+
const rulesPath = getRecommendHealingRulesPath();
|
|
42
|
+
const stats = fs.statSync(rulesPath);
|
|
43
|
+
if (
|
|
44
|
+
!fresh
|
|
45
|
+
&& cachedRules
|
|
46
|
+
&& cachedRulesPath === rulesPath
|
|
47
|
+
&& cachedRulesMtime === Number(stats.mtimeMs)
|
|
48
|
+
) {
|
|
49
|
+
return clone(cachedRules);
|
|
50
|
+
}
|
|
51
|
+
const nextRules = require(rulesPath);
|
|
52
|
+
cachedRulesPath = rulesPath;
|
|
53
|
+
cachedRulesMtime = Number(stats.mtimeMs);
|
|
54
|
+
cachedRules = clone(nextRules);
|
|
55
|
+
return clone(cachedRules);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function saveRecommendHealingRules(nextRules) {
|
|
59
|
+
const rulesPath = getRecommendHealingRulesPath();
|
|
60
|
+
const serialized = `${JSON.stringify(nextRules, null, 2)}\n`;
|
|
61
|
+
fs.writeFileSync(rulesPath, serialized, "utf8");
|
|
62
|
+
cachedRulesPath = rulesPath;
|
|
63
|
+
cachedRulesMtime = Number(fs.statSync(rulesPath).mtimeMs);
|
|
64
|
+
cachedRules = clone(nextRules);
|
|
65
|
+
return rulesPath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getRecommendSelectorRule(pathParts = [], fallback = []) {
|
|
69
|
+
const value = getNestedValue(loadRecommendHealingRules(), ["selectors", ...pathParts]);
|
|
70
|
+
return Array.isArray(value) && value.length > 0 ? value.map((item) => String(item)) : fallback.slice();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getRecommendNetworkRule(pathParts = [], fallback = null) {
|
|
74
|
+
const value = getNestedValue(loadRecommendHealingRules(), ["network", ...pathParts]);
|
|
75
|
+
if (Array.isArray(value)) return value.map((item) => String(item));
|
|
76
|
+
if (value && typeof value === "object") return clone(value);
|
|
77
|
+
if (typeof value === "string") return value;
|
|
78
|
+
if (Array.isArray(fallback)) return fallback.slice();
|
|
79
|
+
if (fallback && typeof fallback === "object") return clone(fallback);
|
|
80
|
+
return fallback;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function compileRegexList(patterns = []) {
|
|
84
|
+
return (Array.isArray(patterns) ? patterns : [])
|
|
85
|
+
.map((pattern) => {
|
|
86
|
+
try {
|
|
87
|
+
return new RegExp(String(pattern), "i");
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
.filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function findFirstMatchingPattern(value, patterns = []) {
|
|
96
|
+
const text = String(value || "");
|
|
97
|
+
for (const pattern of compileRegexList(patterns)) {
|
|
98
|
+
if (pattern.test(text)) return pattern.source;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function matchesAnyPattern(value, patterns = []) {
|
|
104
|
+
return Boolean(findFirstMatchingPattern(value, patterns));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function buildFirstSelectorLookupExpression(selectors = [], rootExpr = "document") {
|
|
108
|
+
return `(() => {
|
|
109
|
+
const selectors = ${JSON.stringify(selectors)};
|
|
110
|
+
for (const selector of selectors) {
|
|
111
|
+
try {
|
|
112
|
+
const node = ${rootExpr}.querySelector(selector);
|
|
113
|
+
if (node) return node;
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
})()`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function buildSelectorCollectionExpression(selectors = [], rootExpr = "document") {
|
|
121
|
+
return `(() => {
|
|
122
|
+
const selectors = ${JSON.stringify(selectors)};
|
|
123
|
+
const nodes = [];
|
|
124
|
+
for (const selector of selectors) {
|
|
125
|
+
try {
|
|
126
|
+
nodes.push(...Array.from(${rootExpr}.querySelectorAll(selector)));
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
return Array.from(new Set(nodes));
|
|
130
|
+
})()`;
|
|
131
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"selectors": {
|
|
4
|
+
"top": {
|
|
5
|
+
"recommend_iframe": [
|
|
6
|
+
"iframe[name=\"recommendFrame\"]",
|
|
7
|
+
"iframe[src*=\"/web/frame/recommend/\"]",
|
|
8
|
+
"iframe"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"frame": {
|
|
12
|
+
"tab_items": [
|
|
13
|
+
"li.tab-item[data-status]",
|
|
14
|
+
"li[data-status][class*=\"tab\"]"
|
|
15
|
+
],
|
|
16
|
+
"filter_trigger": [
|
|
17
|
+
".filter-label-wrap",
|
|
18
|
+
".recommend-filter.op-filter"
|
|
19
|
+
],
|
|
20
|
+
"filter_panel": [
|
|
21
|
+
".recommend-filter.op-filter .filter-panel",
|
|
22
|
+
".recommend-filter .filter-panel",
|
|
23
|
+
".filter-panel"
|
|
24
|
+
],
|
|
25
|
+
"filter_confirm_button": [
|
|
26
|
+
".recommend-filter.op-filter .filter-panel .btn",
|
|
27
|
+
".recommend-filter.op-filter .filter-panel button",
|
|
28
|
+
".filter-panel .btn",
|
|
29
|
+
".filter-panel button"
|
|
30
|
+
],
|
|
31
|
+
"filter_group_container": [
|
|
32
|
+
".check-box"
|
|
33
|
+
],
|
|
34
|
+
"filter_group_school": [
|
|
35
|
+
".check-box.school"
|
|
36
|
+
],
|
|
37
|
+
"filter_group_degree": [
|
|
38
|
+
".check-box.degree"
|
|
39
|
+
],
|
|
40
|
+
"filter_group_gender": [
|
|
41
|
+
".check-box.gender"
|
|
42
|
+
],
|
|
43
|
+
"filter_group_recent_not_view": [
|
|
44
|
+
".check-box.recentNotView"
|
|
45
|
+
],
|
|
46
|
+
"filter_option": [
|
|
47
|
+
".check-box .default.option",
|
|
48
|
+
".check-box .options .option",
|
|
49
|
+
".check-box .option"
|
|
50
|
+
],
|
|
51
|
+
"filter_option_all": [
|
|
52
|
+
".default.option, .options .option, .option"
|
|
53
|
+
],
|
|
54
|
+
"filter_option_active": [
|
|
55
|
+
".default.option.active, .options .option.active, .option.active"
|
|
56
|
+
],
|
|
57
|
+
"filter_scroll_container": [
|
|
58
|
+
".recommend-filter.op-filter .filter-panel .top",
|
|
59
|
+
".recommend-filter.op-filter .top",
|
|
60
|
+
".recommend-filter.op-filter .filter-panel"
|
|
61
|
+
],
|
|
62
|
+
"filter_confirm_candidates": [
|
|
63
|
+
".btn, button"
|
|
64
|
+
],
|
|
65
|
+
"job_dropdown_trigger": [
|
|
66
|
+
".top-chat-search",
|
|
67
|
+
".chat-job-select",
|
|
68
|
+
".chat-job-selector",
|
|
69
|
+
".job-selecter",
|
|
70
|
+
".job-selector",
|
|
71
|
+
".job-select-wrap",
|
|
72
|
+
".job-select",
|
|
73
|
+
".job-select-box",
|
|
74
|
+
".job-wrap",
|
|
75
|
+
".chat-job-name"
|
|
76
|
+
],
|
|
77
|
+
"job_list_items": [
|
|
78
|
+
".ui-dropmenu-list .job-list .job-item",
|
|
79
|
+
".job-selecter-options .job-list .job-item",
|
|
80
|
+
".job-selector-options .job-list .job-item",
|
|
81
|
+
".dropmenu-list .job-list .job-item",
|
|
82
|
+
".job-list .job-item"
|
|
83
|
+
],
|
|
84
|
+
"job_item_label": [
|
|
85
|
+
".ui-dropmenu-list .job-list .job-item .label",
|
|
86
|
+
".job-list .job-item .label",
|
|
87
|
+
".label"
|
|
88
|
+
],
|
|
89
|
+
"job_search_input": [
|
|
90
|
+
".top-chat-search"
|
|
91
|
+
],
|
|
92
|
+
"job_selected_label": [
|
|
93
|
+
".job-selecter-wrap .ui-dropmenu-label",
|
|
94
|
+
".ui-dropmenu-label",
|
|
95
|
+
".chat-job-name",
|
|
96
|
+
".job-selecter .label",
|
|
97
|
+
".job-selecter .job-name",
|
|
98
|
+
".job-select .label"
|
|
99
|
+
],
|
|
100
|
+
"recommend_cards": [
|
|
101
|
+
"ul.card-list > li.card-item"
|
|
102
|
+
],
|
|
103
|
+
"recommend_card_inner": [
|
|
104
|
+
".card-inner[data-geekid]"
|
|
105
|
+
],
|
|
106
|
+
"featured_cards": [
|
|
107
|
+
"li.geek-info-card"
|
|
108
|
+
],
|
|
109
|
+
"featured_card_anchor": [
|
|
110
|
+
"a[data-geekid]"
|
|
111
|
+
],
|
|
112
|
+
"latest_cards": [
|
|
113
|
+
".candidate-card-wrap"
|
|
114
|
+
],
|
|
115
|
+
"latest_card_inner": [
|
|
116
|
+
".candidate-card-wrap .card-inner[data-geek]",
|
|
117
|
+
".candidate-card-wrap [data-geek]"
|
|
118
|
+
],
|
|
119
|
+
"refresh_finished_wrap": [
|
|
120
|
+
".finished-wrap"
|
|
121
|
+
],
|
|
122
|
+
"refresh_button": [
|
|
123
|
+
".finished-wrap .btn.btn-refresh",
|
|
124
|
+
".finished-wrap .btn-refresh",
|
|
125
|
+
".no-data-refresh .btn-refresh"
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
"detail": {
|
|
129
|
+
"popup": [
|
|
130
|
+
".dialog-wrap.active",
|
|
131
|
+
".boss-popup__wrapper",
|
|
132
|
+
".boss-popup_wrapper",
|
|
133
|
+
".boss-dialog_wrapper",
|
|
134
|
+
".boss-dialog",
|
|
135
|
+
".resume-item-detail",
|
|
136
|
+
".geek-detail-modal",
|
|
137
|
+
"[class*=\"popup\"][class*=\"wrapper\"]",
|
|
138
|
+
"[class*=\"dialog\"][class*=\"wrapper\"]"
|
|
139
|
+
],
|
|
140
|
+
"resume_iframe": [
|
|
141
|
+
"iframe[src*=\"/web/frame/c-resume/\"]",
|
|
142
|
+
"iframe[name*=\"resume\"]"
|
|
143
|
+
],
|
|
144
|
+
"close_button": [
|
|
145
|
+
".boss-popup__close",
|
|
146
|
+
".popup-close",
|
|
147
|
+
".modal-close",
|
|
148
|
+
".dialog-close",
|
|
149
|
+
".close-btn",
|
|
150
|
+
"button[aria-label*=\"关闭\"]",
|
|
151
|
+
"button[title*=\"关闭\"]",
|
|
152
|
+
".icon-close"
|
|
153
|
+
],
|
|
154
|
+
"close_fallback_candidates": [
|
|
155
|
+
"[aria-label*=\"关闭\"]",
|
|
156
|
+
"[aria-label*=\"返回\"]",
|
|
157
|
+
"[title*=\"关闭\"]",
|
|
158
|
+
"[title*=\"返回\"]",
|
|
159
|
+
"[class*=\"close\"]",
|
|
160
|
+
"[class*=\"Close\"]",
|
|
161
|
+
"[class*=\"back\"]",
|
|
162
|
+
"[class*=\"Back\"]",
|
|
163
|
+
"button",
|
|
164
|
+
"a",
|
|
165
|
+
"i",
|
|
166
|
+
"span"
|
|
167
|
+
],
|
|
168
|
+
"ack_button": [
|
|
169
|
+
"button.btn-v2.btn-sure-v2",
|
|
170
|
+
"button.btn",
|
|
171
|
+
".boss-dialog button",
|
|
172
|
+
".boss-popup__wrapper button"
|
|
173
|
+
],
|
|
174
|
+
"favorite_button": [
|
|
175
|
+
".like-icon-and-text",
|
|
176
|
+
".resume-footer.item-operate [class*=\"collect\"]",
|
|
177
|
+
".resume-footer.item-operate [class*=\"favorite\"]",
|
|
178
|
+
".resume-footer-wrap [class*=\"collect\"]",
|
|
179
|
+
".resume-footer-wrap [class*=\"favorite\"]"
|
|
180
|
+
],
|
|
181
|
+
"greet_button_recommend": [
|
|
182
|
+
"button.btn-v2.btn-sure-v2.btn-greet",
|
|
183
|
+
".resume-footer.item-operate button.btn-v2",
|
|
184
|
+
".resume-footer-wrap button.btn-v2",
|
|
185
|
+
".resume-footer.item-operate button",
|
|
186
|
+
".resume-footer-wrap button"
|
|
187
|
+
],
|
|
188
|
+
"greet_button_featured": [
|
|
189
|
+
"button.btn-v2.position-rights.btn-sure-v2",
|
|
190
|
+
"button.btn-v2.btn-sure-v2.position-rights",
|
|
191
|
+
".resume-footer.item-operate button.btn-v2",
|
|
192
|
+
".resume-footer-wrap button.btn-v2",
|
|
193
|
+
".resume-footer.item-operate button",
|
|
194
|
+
".resume-footer-wrap button"
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"network": {
|
|
199
|
+
"resume": {
|
|
200
|
+
"info_url_patterns": [
|
|
201
|
+
"\\/wapi\\/zpjob\\/view\\/geek\\/info\\b",
|
|
202
|
+
"\\/wapi\\/zpitem\\/web\\/boss\\/[^?#]*\\/geek\\/info\\b",
|
|
203
|
+
"\\/boss\\/[^?#]*\\/geek\\/info\\b",
|
|
204
|
+
"\\/geek\\/info\\b",
|
|
205
|
+
"[?&](?:geekid|geek_id|encryptgeekid|encryptjid|jid|securityid)="
|
|
206
|
+
],
|
|
207
|
+
"related_keywords": [
|
|
208
|
+
"geek",
|
|
209
|
+
"resume",
|
|
210
|
+
"candidate",
|
|
211
|
+
"friend"
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
"favorite": {
|
|
215
|
+
"request_source_keywords": [
|
|
216
|
+
"usermark",
|
|
217
|
+
"actionlog/common.json"
|
|
218
|
+
],
|
|
219
|
+
"add_patterns": [
|
|
220
|
+
"(?:^|[_\\W])(add|favorite|collect|interest(?:ed)?)(?:$|[_\\W])",
|
|
221
|
+
"\\/add(?:\\/|$)|[?&](?:action|op|operation|type)=add\\b|[?&](?:status|p3|favorite|collect|interested)=1\\b"
|
|
222
|
+
],
|
|
223
|
+
"remove_patterns": [
|
|
224
|
+
"(?:^|[_\\W])(del|delete|remove|cancel|unfavorite|uncollect|uninterest)(?:$|[_\\W])",
|
|
225
|
+
"\\/del(?:\\/|$)|[?&](?:action|op|operation|type)=del\\b|[?&](?:status|p3|favorite|collect|interested)=0\\b"
|
|
226
|
+
],
|
|
227
|
+
"actionlog_action_name": "star-interest-click",
|
|
228
|
+
"status_keys": [
|
|
229
|
+
"p3",
|
|
230
|
+
"status",
|
|
231
|
+
"state",
|
|
232
|
+
"favorite",
|
|
233
|
+
"collect",
|
|
234
|
+
"interested",
|
|
235
|
+
"markstatus",
|
|
236
|
+
"isfavorite",
|
|
237
|
+
"iscollect"
|
|
238
|
+
],
|
|
239
|
+
"operation_keys": [
|
|
240
|
+
"action",
|
|
241
|
+
"op",
|
|
242
|
+
"operation",
|
|
243
|
+
"type",
|
|
244
|
+
"mode",
|
|
245
|
+
"mark",
|
|
246
|
+
"interest"
|
|
247
|
+
]
|
|
248
|
+
},
|
|
249
|
+
"greet": {
|
|
250
|
+
"url_patterns": [
|
|
251
|
+
"greet",
|
|
252
|
+
"sayhi",
|
|
253
|
+
"hello",
|
|
254
|
+
"friend/add",
|
|
255
|
+
"chat/start",
|
|
256
|
+
"boss\\/friend",
|
|
257
|
+
"boss\\/dialog"
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|