// ==UserScript==
// @name BOSS直聘智能助手
// @namespace http://tampermonkey.net/
// @version 0.1.1
// @description BOSS直聘智能助手 - 自动筛选简历并智能打招呼
// @author Your Name
// @match https://www.zhipin.com/*
// @icon https://www.zhipin.com/favicon.ico
// @grant none
// @license MIT
// ==/UserScript==
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 8:
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.matchesFilters = matchesFilters;
exports.extractCandidateInfo = extractCandidateInfo;
/**
* 比较教育水平
* @param required 要求的教育水平
* @param actual 实际教育水平
* @returns 是否满足要求
*/
function compareEducation(required, actual) {
if (required === 'all')
return true;
const educationLevels = [
'初中及以下',
'高中',
'大专',
'本科',
'硕士',
'博士'
];
const requiredIndex = educationLevels.indexOf(required);
const actualIndex = educationLevels.findIndex(level => actual.includes(level));
if (requiredIndex === -1 || actualIndex === -1)
return false;
return actualIndex >= requiredIndex;
}
/**
* 比较工作经验
* @param required 要求的工作经验
* @param actual 实际工作经验
* @returns 是否满足要求
*/
function compareExperience(required, actual) {
if (required === 'all')
return true;
// 提取要求的年限数字
const requiredYearsMatch = required.match(/(\d+)/);
if (!requiredYearsMatch)
return true;
const requiredYears = parseInt(requiredYearsMatch[1]);
// 提取实际年限数字
const actualYearsMatch = actual.match(/(\d+)/);
if (!actualYearsMatch)
return false;
const actualYears = parseInt(actualYearsMatch[1]);
return actualYears >= requiredYears;
}
/**
* 检查技能匹配
* @param requiredSkills 要求的技能列表
* @param actualTags 实际拥有的标签
* @returns 是否满足要求
*/
function matchesSkills(requiredSkills, actualTags) {
if (requiredSkills.length === 0)
return true;
// 将所有标签转为小写进行比较
const lowerTags = actualTags.map(tag => tag.toLowerCase());
// 检查是否至少匹配一个技能
return requiredSkills.some(skill => lowerTags.some(tag => tag.includes(skill.toLowerCase())));
}
/**
* 检查年龄是否在范围内
* @param min 最小年龄
* @param max 最大年龄
* @param actual 实际年龄
* @returns 是否在范围内
*/
function isAgeInRange(min, max, actual) {
if (min <= 0 && max >= 100)
return true;
if (actual <= 0)
return true; // 年龄未知情况
return actual >= min && actual <= max;
}
/**
* 根据筛选条件检查候选人是否符合要求
* @param filters 筛选条件
* @param candidate 候选人信息
* @returns 是否符合筛选条件
*/
function matchesFilters(filters, candidate) {
// 检查教育背景
if (!compareEducation(filters.education, candidate.education)) {
return false;
}
// 检查工作经验
if (!compareExperience(filters.experience, candidate.experience)) {
return false;
}
// 检查技能标签
if (!matchesSkills(filters.skills, candidate.tags)) {
return false;
}
// 检查年龄范围
if (!isAgeInRange(filters.ageMin, filters.ageMax, candidate.age)) {
return false;
}
return true;
}
/**
* 从DOM元素中提取候选人信息
* @param element 候选人卡片DOM元素
* @returns 候选人信息对象
*/
function extractCandidateInfo(element) {
var _a, _b;
// 根据实际DOM结构提取信息
console.log('提取候选人信息', element);
// 提取姓名 - 使用更精确的选择器
let name = '';
// 首先尝试使用最精确的选择器匹配span.name
const nameElement = element.querySelector('span.name, .name-wrap span.name');
if (nameElement) {
name = ((_a = nameElement.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '';
console.log('使用精确选择器提取到姓名:', name);
}
else {
// 如果上面的选择器没找到,尝试更通用的选择器
const altNameElement = element.querySelector('.name, [class*="name"], .col-2 .row.name-wrap .name');
if (altNameElement) {
name = ((_b = altNameElement.textContent) === null || _b === void 0 ? void 0 : _b.trim()) || '';
console.log('使用通用选择器提取到姓名:', name);
}
}
// 对姓名进行验证,避免提取到职位信息
if (name.includes('工程师') || name.includes('开发') || name.length > 10) {
console.log('姓名可能误提取了职位信息:', name);
// 尝试从名字中提取更合理的姓名部分
const possibleName = name.split(/\s+/)[0];
if (possibleName && possibleName.length <= 5) {
name = possibleName;
console.log('修正后的姓名:', name);
}
else {
// 如果无法修正,将姓名置为空字符串,后续流程会处理
console.log('无法从 "' + name + '" 中提取出合理的姓名,置为空');
name = '';
}
}
// 提取教育信息
let education = '';
// 尝试从基本信息中查找
const baseInfoElement = element.querySelector('.base-info, [class*="base-info"], .join-text-wrap, .row .base-info');
if (baseInfoElement) {
const baseInfoText = baseInfoElement.textContent || '';
const eduMatch = baseInfoText.match(/(本科|大专|硕士|博士|MBA|EMBA|中专|高中|初中)/);
if (eduMatch) {
education = eduMatch[1];
}
}
// 尝试从教育经历中查找
if (!education) {
const eduExpSelectors = [
'.edu-exps',
'[class*="edu-exps"]',
'.timeline-wrap.edu-exps',
'[data-v-8126c9ce].timeline-wrap.edu-exps',
'.edu-wrap',
'[class*="edu"] .content'
];
for (const selector of eduExpSelectors) {
const eduExpElement = element.querySelector(selector);
if (eduExpElement) {
const eduText = eduExpElement.textContent || '';
const eduMatch = eduText.match(/(本科|大专|硕士|博士|MBA|EMBA|中专|高中|初中)/);
if (eduMatch) {
education = eduMatch[1];
break;
}
}
}
}
console.log('提取到学历:', education);
// 提取工作经验
let experience = '';
// 尝试从基本信息中查找
if (baseInfoElement) {
const baseInfoText = baseInfoElement.textContent || '';
const expMatch = baseInfoText.match(/(\d+)年/);
if (expMatch) {
experience = expMatch[0];
}
}
// 如果基本信息中没找到,尝试从工作经历中查找
if (!experience) {
const workExpElement = element.querySelector('.work-exps, [class*="work-exps"], .timeline-wrap.work-exps');
if (workExpElement) {
const workExpText = workExpElement.textContent || '';
const expMatch = workExpText.match(/(\d+)年/);
if (expMatch) {
experience = expMatch[0];
}
else {
// 如果没有明确显示几年经验,尝试计算最早工作时间到现在
const earliestWorkTimeMatch = workExpText.match(/(\d{4})[\.年\-]/);
if (earliestWorkTimeMatch) {
const earliestYear = parseInt(earliestWorkTimeMatch[1]);
const currentYear = new Date().getFullYear();
const yearsOfExperience = currentYear - earliestYear;
if (yearsOfExperience > 0) {
experience = `${yearsOfExperience}年`;
}
}
}
}
}
console.log('提取到工作经验:', experience);
// 提取年龄
let age = 0;
// 尝试从基本信息中提取
if (baseInfoElement) {
const baseInfoText = baseInfoElement.textContent || '';
const ageMatch = baseInfoText.match(/(\d+)岁/);
if (ageMatch) {
age = parseInt(ageMatch[1]);
}
}
console.log('提取到年龄:', age);
// 提取标签 - 使用更广泛的选择器
const tagElements = element.querySelectorAll('.tag-item, [class*="tag-item"], .tag, .tags span, .row.tags [class*="tag-item"], [data-v-d62606c4].tag-item');
const tags = Array.from(tagElements).map(tag => { var _a; return ((_a = tag.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || ''; }).filter(Boolean);
// 如果没有找到标签,尝试从优势描述中提取关键词
if (tags.length === 0) {
const geekDescElement = element.querySelector('.geek-desc, [class*="desc"], .row-flex .content');
if (geekDescElement) {
const descText = geekDescElement.textContent || '';
// 分析描述文本,提取常见技术关键词
const techKeywords = [
'C++', 'Java', 'Python', 'JavaScript', 'TypeScript', 'Go', 'PHP', 'C#', 'Ruby', 'Swift',
'React', 'Vue', 'Angular', 'Node.js', 'Express', 'Django', 'Spring', 'Flask',
'MySQL', 'PostgreSQL', 'MongoDB', 'Redis', 'Oracle', 'SQL Server',
'Docker', 'Kubernetes', 'AWS', 'Azure', 'GCP', 'Linux', 'Windows',
'Git', 'CI/CD', 'DevOps', 'Agile', 'Scrum',
'前端', '后端', '全栈', '架构', '测试', '运维', '安全',
'机器学习', '深度学习', '数据分析', '数据挖掘', '人工智能',
'微服务', '分布式', '云计算', '大数据'
];
for (const keyword of techKeywords) {
if (descText.includes(keyword) && !tags.includes(keyword)) {
tags.push(keyword);
}
}
}
}
console.log('提取到标签:', tags);
return {
name,
education,
experience,
age,
tags
};
}
/***/ }),
/***/ 28:
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.loadConfig = loadConfig;
exports.saveConfig = saveConfig;
exports.resetConfig = resetConfig;
exports.updateConfig = updateConfig;
// 默认筛选条件
const defaultFilters = {
education: 'all',
experience: 'all',
skills: [],
ageMin: 0,
ageMax: 100
};
// 默认LLM配置
const defaultLLMConfig = {
apiKey: '',
model: 'gpt-3.5-turbo',
customModelName: ''
};
// 默认用户配置
const defaultConfig = {
filters: defaultFilters,
operationInterval: 5,
maxProcessCount: 10, // 默认最多处理10个候选人
llmConfig: defaultLLMConfig,
jobRequirements: '请在此处填写您的招聘要求,例如:\n- 熟练掌握 TypeScript 和 React\n- 3年以上前端开发经验\n- 熟悉 Node.js 加分'
};
// 配置键名
const CONFIG_KEY = 'boss_assistant_config';
/**
* 加载用户配置
* @returns 用户配置对象
*/
function loadConfig() {
var _a, _b, _c;
try {
const savedConfig = localStorage.getItem(CONFIG_KEY);
if (!savedConfig) {
console.log('未找到保存的配置,使用默认配置');
return defaultConfig;
}
// 解析保存的配置
const parsedConfig = JSON.parse(savedConfig);
// 合并默认配置以确保兼容性
// 确保新增的字段如maxProcessCount在旧配置中也有默认值
return {
filters: Object.assign(Object.assign({}, defaultFilters), parsedConfig.filters),
operationInterval: (_a = parsedConfig.operationInterval) !== null && _a !== void 0 ? _a : defaultConfig.operationInterval,
maxProcessCount: (_b = parsedConfig.maxProcessCount) !== null && _b !== void 0 ? _b : defaultConfig.maxProcessCount, // 确保旧配置也有此字段
llmConfig: Object.assign(Object.assign({}, defaultLLMConfig), parsedConfig.llmConfig),
jobRequirements: (_c = parsedConfig.jobRequirements) !== null && _c !== void 0 ? _c : defaultConfig.jobRequirements
};
}
catch (e) {
console.error('加载配置失败,使用默认配置', e);
return defaultConfig;
}
}
/**
* 保存用户配置
* @param config 用户配置对象
*/
function saveConfig(config) {
try {
localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
console.log('配置已保存到localStorage');
}
catch (error) {
console.error('保存配置失败:', error);
}
}
/**
* 重置用户配置为默认值
*/
function resetConfig() {
saveConfig(defaultConfig);
console.log('配置已重置为默认值');
}
/**
* 更新部分配置
* @param partialConfig 部分配置对象
*/
function updateConfig(partialConfig) {
const currentConfig = loadConfig();
saveConfig(Object.assign(Object.assign({}, currentConfig), partialConfig));
console.log('配置已更新');
}
/***/ }),
/***/ 55:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.testLLMConfig = testLLMConfig;
exports.analyzeResumeWithLLM = analyzeResumeWithLLM;
exports.shouldContactCandidate = shouldContactCandidate;
const config_1 = __webpack_require__(28);
/**
* 测试LLM配置是否正确
* @param llmConfig LLM配置
* @returns 测试是否成功
*/
function testLLMConfig(llmConfig) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const apiUrl = llmConfig.model === 'custom' && llmConfig.customApiUrl
? llmConfig.customApiUrl
: 'https://api.openai.com/v1/chat/completions';
const modelName = llmConfig.model !== 'custom' ? llmConfig.model :
(llmConfig.customModelName ? llmConfig.customModelName : undefined);
if (!modelName) {
throw new Error('请提供有效的模型名称');
}
if (!llmConfig.apiKey) {
throw new Error('API Key不能为空');
}
// 构建一个简单的测试请求
try {
const response = yield fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${llmConfig.apiKey}`
},
body: JSON.stringify({
model: modelName,
messages: [{
role: 'user',
content: '你好,这是一个测试消息,请回复"测试成功"'
}],
max_tokens: 10,
temperature: 0.7
})
});
console.log('API响应状态:', response.status);
if (response.ok) {
const result = yield response.json();
console.log('API响应:', result);
if (result.choices && result.choices.length > 0) {
return true;
}
else if (result.error) {
console.error('API错误:', result.error);
throw new Error(`API错误: ${result.error.message || '未知错误'}`);
}
else {
console.error('未知响应结构:', result);
throw new Error(`请求失败: 状态码 ${response.status}`);
}
}
else {
// 尝试获取错误信息
try {
const errorData = yield response.json();
throw new Error(`API请求失败: 状态码 ${response.status}, 错误信息: ${((_a = errorData.error) === null || _a === void 0 ? void 0 : _a.message) || JSON.stringify(errorData)}`);
}
catch (jsonError) {
// 如果无法解析JSON,则使用状态文本
throw new Error(`API请求失败: 状态码 ${response.status}, ${response.statusText}`);
}
}
}
catch (error) {
console.error('请求错误:', error);
throw error;
}
});
}
/**
* 使用LLM大模型分析简历内容
* @param resumeText 简历文本内容
* @returns Promise<ResumeAnalysis> 简历分析结果
*/
function analyzeResumeWithLLM(resumeText) {
return __awaiter(this, void 0, void 0, function* () {
const config = (0, config_1.loadConfig)();
const llmConfig = config.llmConfig;
const apiUrl = llmConfig.model === 'custom' && llmConfig.customApiUrl
? llmConfig.customApiUrl
: 'https://api.openai.com/v1/chat/completions';
const prompt = `
请根据以下招聘要求,分析以下求职者简历,并从以下几个维度进行评分(1-10分)和分析,返回JSON格式:
招聘要求:
${config.jobRequirements}
1. 教育背景
2. 工作经验
3. 项目经验
4. 技能匹配度
5. 稳定性评估
最后给出是否推荐进一步沟通的建议。如果推荐沟通,请生成一段针对该候选人的开场沟通话术。
简历内容:
${resumeText}
回复格式(JSON):
{
"educationScore": 分数,
"workExpScore": 分数,
"projectExpScore": 分数,
"skillMatchScore": 分数,
"stabilityScore": 分数,
"overallScore": 总分,
"recommendation": true/false,
"comments": "总体评价和建议",
"openingMessage": "如果推荐沟通,这里生成一段开场话术(注意:话术中不要表明自己的公司或身份),否则为空字符串"
}
`;
try {
const response = yield fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${llmConfig.apiKey}`
},
body: JSON.stringify({
model: llmConfig.model !== 'custom' ? llmConfig.model :
(llmConfig.customModelName ? llmConfig.customModelName : undefined),
messages: [{
role: 'user',
content: prompt
}],
temperature: 0.7
})
});
if (!response.ok) {
throw new Error(`API请求失败: 状态码 ${response.status}`);
}
const result = yield response.json();
if (result.choices && result.choices.length > 0) {
const content = result.choices[0].message.content;
// 尝试从返回内容中提取JSON
try {
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const analysisResult = JSON.parse(jsonMatch[0]);
return analysisResult;
}
else {
throw new Error('无法从LLM返回结果中解析JSON');
}
}
catch (jsonError) {
throw new Error('JSON解析失败: ' + jsonError.message);
}
}
else {
throw new Error('无法解析LLM返回结果');
}
}
catch (error) {
console.error('LLM分析请求失败:', error);
// 返回模拟的分析结果,以便在API失败的情况下仍能继续工作
return {
educationScore: 5,
workExpScore: 5,
projectExpScore: 5,
skillMatchScore: 5,
stabilityScore: 5,
overallScore: 5,
recommendation: false,
comments: "由于API请求失败,无法获取真实分析结果。请检查网络连接或API配置。",
openingMessage: ""
};
}
});
}
/**
* 判断分析结果是否推荐进一步沟通
* @param analysis 分析结果
* @returns boolean 是否推荐
*/
function shouldContactCandidate(analysis) {
return analysis.recommendation === true || analysis.overallScore >= 7;
}
/***/ }),
/***/ 60:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.createControlPanel = createControlPanel;
exports.updateAnalysisResult = updateAnalysisResult;
exports.clearAnalysisResult = clearAnalysisResult;
exports.getRunningStatus = getRunningStatus;
exports.setRunningStatus = setRunningStatus;
const config_1 = __webpack_require__(28);
const llm_1 = __webpack_require__(55);
// CSS 样式
const CSS_STYLES = `
.boss-assistant-panel {
position: fixed;
top: 80px;
right: 20px;
width: 320px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
z-index: 9999;
font-size: 14px;
max-height: calc(100vh - 100px);
overflow-y: auto;
transition: all 0.3s ease;
}
.boss-assistant-header {
padding: 12px 15px;
background-color: #1677ff;
color: #fff;
border-radius: 8px 8px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
}
.boss-assistant-header h3 {
margin: 0;
font-size: 16px;
}
.boss-assistant-toggle {
background: none;
border: none;
color: #fff;
cursor: pointer;
font-size: 16px;
}
.boss-assistant-body {
padding: 15px;
}
.boss-assistant-section {
margin-bottom: 15px;
border-bottom: 1px solid #eee;
padding-bottom: 15px;
}
.boss-assistant-section:last-child {
border-bottom: none;
margin-bottom: 0;
}
.boss-assistant-section h4 {
margin-top: 0;
margin-bottom: 10px;
font-size: 15px;
color: #333;
}
.form-group {
margin-bottom: 12px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #666;
}
.form-group select,
.form-group input,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.form-group textarea {
min-height: 60px;
resize: vertical;
}
.form-group-inline {
display: flex;
gap: 10px;
align-items: center;
}
.form-group-inline input {
flex: 1;
}
.boss-assistant-footer {
padding: 12px 15px;
border-top: 1px solid #eee;
display: flex;
justify-content: space-between;
}
.boss-assistant-btn {
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
color: #fff;
background-color: #1677ff;
}
.boss-assistant-btn.secondary {
background-color: #f5f5f5;
color: #666;
}
.boss-assistant-badge {
display: inline-block;
margin-left: 5px;
padding: 2px 5px;
background-color: #f56c6c;
color: white;
font-size: 12px;
border-radius: 10px;
}
.resume-analysis {
background-color: #f9f9f9;
border: 1px solid #eee;
border-radius: 5px;
padding: 12px;
margin-top: 10px;
}
.analysis-item {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.score {
font-weight: bold;
}
.score-high {
color: #67c23a;
}
.score-medium {
color: #e6a23c;
}
.score-low {
color: #f56c6c;
}
.boss-assistant-collapsed {
height: 40px;
overflow: hidden;
}
.boss-assistant-mini {
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #1677ff;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 9999;
font-size: 20px;
}
.highlight-card {
border: 2px solid #67c23a !important;
box-shadow: 0 0 5px rgba(103, 194, 58, 0.5) !important;
}
`;
// 全局状态
let isRunning = false;
let isPanelVisible = true;
let resumeAnalysisResult = null;
/**
* 创建DOM元素
* @param tag HTML标签
* @param props 属性对象
* @param children 子元素
* @returns HTML元素
*/
function createElement(tag, props = {}, children = []) {
const element = document.createElement(tag);
Object.entries(props).forEach(([key, value]) => {
if (key === 'className') {
element.className = value;
}
else if (key === 'style') {
element.setAttribute('style', value);
}
else {
element.setAttribute(key, value);
}
});
children.forEach(child => {
if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
}
else {
element.appendChild(child);
}
});
return element;
}
/**
* 创建筛选条件配置界面
* @param config 用户配置
* @returns HTML元素
*/
function createFilterSection(config) {
const educationSelect = createElement('select', { id: 'education-filter' }, [
createElement('option', { value: 'all' }, ['不限']),
createElement('option', { value: '大专' }, ['大专及以上']),
createElement('option', { value: '本科' }, ['本科及以上']),
createElement('option', { value: '硕士' }, ['硕士及以上']),
createElement('option', { value: '博士' }, ['博士'])
]);
const experienceSelect = createElement('select', { id: 'experience-filter' }, [
createElement('option', { value: 'all' }, ['不限']),
createElement('option', { value: '1年' }, ['1年以上']),
createElement('option', { value: '3年' }, ['3年以上']),
createElement('option', { value: '5年' }, ['5年以上']),
createElement('option', { value: '10年' }, ['10年以上'])
]);
// 设置选中值
educationSelect.value = config.filters.education;
experienceSelect.value = config.filters.experience;
const skillsInput = createElement('input', {
type: 'text',
id: 'skills-filter',
placeholder: '技能关键词,用逗号分隔',
value: config.filters.skills.join(', ')
});
const ageMinInput = createElement('input', {
type: 'number',
id: 'age-min',
placeholder: '最小年龄',
value: config.filters.ageMin.toString(),
style: 'width: 48%'
});
const ageMaxInput = createElement('input', {
type: 'number',
id: 'age-max',
placeholder: '最大年龄',
value: config.filters.ageMax.toString(),
style: 'width: 48%'
});
const section = createElement('div', { className: 'boss-assistant-section' }, [
createElement('h4', {}, ['筛选条件']),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['学历要求']),
educationSelect
]),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['工作经验']),
experienceSelect
]),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['技能关键词']),
skillsInput
]),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['年龄范围']),
createElement('div', { className: 'form-group-inline' }, [
ageMinInput,
createElement('span', {}, ['-']),
ageMaxInput
])
])
]);
return section;
}
/**
* 创建操作间隔设置界面
* @param config
* @returns HTML元素
*/
function createIntervalSection(config) {
const intervalInput = createElement('input', {
type: 'number',
id: 'operation-interval',
value: config.operationInterval.toString(),
min: '3',
max: '30'
});
const maxProcessCountInput = createElement('input', {
type: 'number',
id: 'max-process-count',
value: config.maxProcessCount.toString(),
min: '0',
placeholder: '0表示处理全部'
});
const section = createElement('div', { className: 'boss-assistant-section' }, [
createElement('h4', {}, ['操作设置']),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['操作间隔(秒)']),
intervalInput
]),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['最大处理数量(0表示全部)']),
maxProcessCountInput
])
]);
return section;
}
/**
* 创建LLM配置界面
* @param config 用户配置
* @returns HTML元素
*/
function createLLMSection(config) {
const apiKeyInput = createElement('input', {
type: 'password',
id: 'llm-api-key',
placeholder: '输入API Key',
value: config.llmConfig.apiKey
});
const modelSelect = createElement('select', { id: 'llm-model' }, [
createElement('option', { value: 'gpt-3.5-turbo' }, ['GPT-3.5 Turbo']),
createElement('option', { value: 'gpt-4' }, ['GPT-4']),
createElement('option', { value: 'custom' }, ['自定义模型'])
]);
modelSelect.value = config.llmConfig.model;
const customModelNameInput = createElement('input', {
type: 'text',
id: 'custom-model-name',
placeholder: '自定义模型名称',
value: config.llmConfig.customModelName || '',
style: modelSelect.value === 'custom' ? '' : 'display: none;'
});
const customApiInput = createElement('input', {
type: 'text',
id: 'custom-model-api',
placeholder: '自定义API地址',
value: config.llmConfig.customApiUrl || '',
style: modelSelect.value === 'custom' ? '' : 'display: none;'
});
// 监听模型选择变化
modelSelect.addEventListener('change', () => {
const isCustomModel = modelSelect.value === 'custom';
customApiInput.style.display = isCustomModel ? '' : 'none';
customModelNameInput.style.display = isCustomModel ? '' : 'none';
});
const section = createElement('div', { className: 'boss-assistant-section' }, [
createElement('h4', {}, ['LLM大模型设置']),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['API Key']),
apiKeyInput
]),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['模型选择']),
modelSelect
]),
createElement('div', { className: 'form-group', id: 'custom-model-name-container' }, [
createElement('label', {}, ['自定义模型名称']),
customModelNameInput
]),
createElement('div', { className: 'form-group', id: 'custom-model-container' }, [
createElement('label', {}, ['自定义API地址']),
customApiInput
]),
createElement('div', { className: 'form-group' }, [
createElement('button', {
className: 'boss-assistant-btn',
id: 'test-llm-btn',
style: 'width: auto; margin-top: 8px;'
}, ['测试LLM配置'])
])
]);
return section;
}
/**
* 创建招聘要求配置界面
* @param config 用户配置
* @returns HTML元素
*/
function createJobRequirementsSection(config) {
const requirementsTextarea = createElement('textarea', {
id: 'job-requirements',
placeholder: '输入招聘要求,例如:\\n- 熟练掌握 TypeScript 和 React\\n- 3年以上前端开发经验\\n- 熟悉 Node.js 加分',
value: config.jobRequirements
});
const section = createElement('div', { className: 'boss-assistant-section' }, [
createElement('h4', {}, ['招聘要求']),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['职位要求描述']),
requirementsTextarea
])
]);
return section;
}
/**
* 创建简历分析结果展示
* @param analysis 分析结果
* @returns HTML元素
*/
function createAnalysisResultView(analysis) {
const getScoreClass = (score) => {
if (score >= 8)
return 'score-high';
if (score >= 6)
return 'score-medium';
return 'score-low';
};
return createElement('div', { className: 'resume-analysis' }, [
createElement('h4', {}, ['简历分析结果']),
createElement('div', { className: 'analysis-item' }, [
createElement('div', {}, ['教育背景:']),
createElement('div', { className: `score ${getScoreClass(analysis.educationScore)}` }, [analysis.educationScore.toString()])
]),
createElement('div', { className: 'analysis-item' }, [
createElement('div', {}, ['工作经验:']),
createElement('div', { className: `score ${getScoreClass(analysis.workExpScore)}` }, [analysis.workExpScore.toString()])
]),
createElement('div', { className: 'analysis-item' }, [
createElement('div', {}, ['项目经验:']),
createElement('div', { className: `score ${getScoreClass(analysis.projectExpScore)}` }, [analysis.projectExpScore.toString()])
]),
createElement('div', { className: 'analysis-item' }, [
createElement('div', {}, ['技能匹配:']),
createElement('div', { className: `score ${getScoreClass(analysis.skillMatchScore)}` }, [analysis.skillMatchScore.toString()])
]),
createElement('div', { className: 'analysis-item' }, [
createElement('div', {}, ['稳定性评估:']),
createElement('div', { className: `score ${getScoreClass(analysis.stabilityScore)}` }, [analysis.stabilityScore.toString()])
]),
createElement('div', { className: 'analysis-item' }, [
createElement('div', {}, ['总体评分:']),
createElement('div', { className: `score ${getScoreClass(analysis.overallScore)}` }, [analysis.overallScore.toString()])
]),
createElement('div', { className: 'analysis-item' }, [
createElement('div', {}, ['推荐沟通:']),
createElement('div', { className: `score ${analysis.recommendation ? 'score-high' : 'score-low'}` }, [analysis.recommendation ? '是' : '否'])
]),
createElement('div', { className: 'form-group' }, [
createElement('label', {}, ['评价意见:']),
createElement('div', {}, [analysis.comments])
])
]);
}
/**
* 从表单收集用户配置
* @returns 用户配置对象
*/
function collectFormConfig() {
const educationSelect = document.getElementById('education-filter');
const experienceSelect = document.getElementById('experience-filter');
const skillsInput = document.getElementById('skills-filter');
const ageMinInput = document.getElementById('age-min');
const ageMaxInput = document.getElementById('age-max');
const intervalInput = document.getElementById('operation-interval');
const maxProcessCountInput = document.getElementById('max-process-count');
const apiKeyInput = document.getElementById('llm-api-key');
const modelSelect = document.getElementById('llm-model');
const customModelNameInput = document.getElementById('custom-model-name');
const customApiInput = document.getElementById('custom-model-api');
const requirementsTextarea = document.getElementById('job-requirements');
// 解析技能列表
const skillsString = skillsInput.value.trim();
const skills = skillsString ? skillsString.split(',').map(s => s.trim()).filter(s => s) : [];
const config = {
filters: {
education: educationSelect.value,
experience: experienceSelect.value,
skills: skills,
ageMin: parseInt(ageMinInput.value) || 0,
ageMax: parseInt(ageMaxInput.value) || 100
},
operationInterval: parseInt(intervalInput.value) || 5,
maxProcessCount: parseInt(maxProcessCountInput.value) || 10,
llmConfig: {
apiKey: apiKeyInput.value,
model: modelSelect.value,
customApiUrl: modelSelect.value === 'custom' ? customApiInput.value : undefined,
customModelName: modelSelect.value === 'custom' ? customModelNameInput.value : undefined
},
jobRequirements: requirementsTextarea.value
};
return config;
}
/**
* 创建并显示控制面板
* @returns 面板元素
*/
function createControlPanel() {
// 添加样式
addStyles();
const config = (0, config_1.loadConfig)();
// 创建主面板
const panel = createElement('div', { className: 'boss-assistant-panel', id: 'boss-assistant-panel' }, [
createElement('div', { className: 'boss-assistant-header', id: 'boss-assistant-header' }, [
createElement('h3', {}, ['BOSS直聘智能助手']),
createElement('button', { className: 'boss-assistant-toggle', id: 'boss-assistant-toggle' }, ['−'])
]),
createElement('div', { className: 'boss-assistant-body' }, [
createFilterSection(config),
createIntervalSection(config),
createLLMSection(config),
createJobRequirementsSection(config),
createElement('div', { id: 'analysis-result-container' }, [])
]),
createElement('div', { className: 'boss-assistant-footer' }, [
createElement('button', { className: 'boss-assistant-btn secondary', id: 'save-config-btn' }, ['保存配置']),
createElement('button', { className: 'boss-assistant-btn', id: 'start-auto-btn' }, ['开始自动操作'])
])
]);
document.body.appendChild(panel);
// 事件处理程序
setupEventHandlers();
return panel;
}
/**
* 添加CSS样式
*/
function addStyles() {
const styleElement = document.createElement('style');
styleElement.textContent = CSS_STYLES;
document.head.appendChild(styleElement);
}
/**
* 设置事件处理程序
*/
function setupEventHandlers() {
const panel = document.getElementById('boss-assistant-panel');
const toggleBtn = document.getElementById('boss-assistant-toggle');
const saveBtn = document.getElementById('save-config-btn');
const startBtn = document.getElementById('start-auto-btn');
const testLLMBtn = document.getElementById('test-llm-btn');
const header = document.getElementById('boss-assistant-header');
if (!panel || !toggleBtn || !saveBtn || !startBtn || !header)
return;
// 切换面板显示/隐藏
toggleBtn.addEventListener('click', () => {
isPanelVisible = !isPanelVisible;
if (isPanelVisible) {
panel.classList.remove('boss-assistant-collapsed');
toggleBtn.textContent = '−';
}
else {
panel.classList.add('boss-assistant-collapsed');
toggleBtn.textContent = '+';
}
});
// 保存配置
saveBtn.addEventListener('click', () => {
const config = collectFormConfig();
(0, config_1.saveConfig)(config);
alert('配置已保存');
});
// 开始/停止自动操作
startBtn.addEventListener('click', () => {
isRunning = !isRunning;
if (isRunning) {
const config = collectFormConfig();
(0, config_1.saveConfig)(config);
startBtn.textContent = '停止自动操作';
startBtn.classList.add('secondary');
// 触发自动化操作开始事件
const event = new CustomEvent('boss-assistant:start', {
detail: { maxCount: config.maxProcessCount }
});
document.dispatchEvent(event);
}
else {
startBtn.textContent = '开始自动操作';
startBtn.classList.remove('secondary');
// 触发自动化操作停止事件
const event = new CustomEvent('boss-assistant:stop');
document.dispatchEvent(event);
}
});
// 测试LLM配置
if (testLLMBtn) {
testLLMBtn.addEventListener('click', () => __awaiter(this, void 0, void 0, function* () {
// 提取当前的LLM配置
const apiKeyInput = document.getElementById('llm-api-key');
const modelSelect = document.getElementById('llm-model');
const customModelNameInput = document.getElementById('custom-model-name');
const customApiInput = document.getElementById('custom-model-api');
const llmConfig = {
apiKey: apiKeyInput.value,
model: modelSelect.value,
customApiUrl: modelSelect.value === 'custom' ? customApiInput.value : undefined,
customModelName: modelSelect.value === 'custom' ? customModelNameInput.value : undefined
};
// 禁用按钮,显示正在测试的状态
testLLMBtn.disabled = true;
testLLMBtn.textContent = '测试中...';
try {
// 调用测试函数
const result = yield (0, llm_1.testLLMConfig)(llmConfig);
alert(result ? '配置测试成功!模型连接正常。' : '配置测试失败,请检查API Key和模型设置。');
}
catch (error) {
alert(`测试失败: ${error instanceof Error ? error.message : String(error)}`);
}
finally {
// 恢复按钮状态
testLLMBtn.disabled = false;
testLLMBtn.textContent = '测试LLM配置';
}
}));
}
// 拖动功能
makeDraggable(panel, header);
}
/**
* 使元素可拖动
* @param element 要拖动的元素
* @param handle 拖动的手柄元素
*/
function makeDraggable(element, handle) {
let startX = 0, startY = 0;
let startLeft = 0, startTop = 0;
handle.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e.preventDefault();
// 记录初始位置
startX = e.clientX;
startY = e.clientY;
// 获取元素的当前位置
const rect = element.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
// 确保元素使用绝对定位
element.style.position = 'fixed';
element.style.right = 'auto';
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
// 添加拖动时的视觉反馈
handle.style.cursor = 'grabbing';
}
function elementDrag(e) {
e.preventDefault();
// 计算鼠标移动的距离
const dx = e.clientX - startX;
const dy = e.clientY - startY;
// 设置元素的新位置
const newLeft = startLeft + dx;
const newTop = startTop + dy;
// 确保元素不会被拖出视口
const maxLeft = window.innerWidth - element.offsetWidth;
const maxTop = window.innerHeight - element.offsetHeight;
element.style.left = `${Math.max(0, Math.min(newLeft, maxLeft))}px`;
element.style.top = `${Math.max(0, Math.min(newTop, maxTop))}px`;
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
handle.style.cursor = 'move';
}
}
/**
* 更新简历分析结果显示
* @param analysis 分析结果
*/
function updateAnalysisResult(analysis) {
resumeAnalysisResult = analysis;
const container = document.getElementById('analysis-result-container');
if (!container)
return;
// 清空容器
container.innerHTML = '';
// 添加分析结果视图
container.appendChild(createAnalysisResultView(analysis));
}
/**
* 清除简历分析结果
*/
function clearAnalysisResult() {
resumeAnalysisResult = null;
const container = document.getElementById('analysis-result-container');
if (!container)
return;
container.innerHTML = '';
}
/**
* 获取当前的运行状态
* @returns 是否正在运行
*/
function getRunningStatus() {
return isRunning;
}
/**
* 设置运行状态
* @param status 运行状态
*/
function setRunningStatus(status) {
isRunning = status;
const startBtn = document.getElementById('start-auto-btn');
if (!startBtn)
return;
if (isRunning) {
startBtn.textContent = '停止自动操作';
startBtn.classList.add('secondary');
}
else {
startBtn.textContent = '开始自动操作';
startBtn.classList.remove('secondary');
}
}
/***/ }),
/***/ 357:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.autoProcessCandidates = autoProcessCandidates;
exports.initAutoProcess = initAutoProcess;
const config_1 = __webpack_require__(28);
const filter_1 = __webpack_require__(8);
const llm_1 = __webpack_require__(55);
const ui_1 = __webpack_require__(60);
/**
* 等待指定的毫秒数
* @param ms 毫秒数
* @returns Promise
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 添加随机延时
* @param baseMs 基础毫秒数
* @returns 实际延时毫秒数
*/
function randomDelay(baseMs) {
// 添加随机延时,模拟人工操作
const randomMs = Math.floor(Math.random() * 2000);
return sleep(baseMs + randomMs);
}
/**
* 获取iframe文档对象
* @returns iframe的document对象,如果找不到则返回null
*/
function getIframeDocument() {
var _a, _b, _c;
try {
// 尝试查找所有可能的iframe元素
const iframes = document.querySelectorAll('iframe');
console.log(`找到 ${iframes.length} 个iframe元素`);
if (iframes.length === 0) {
console.log('页面中没有iframe元素');
return null;
}
// 检查每个iframe
for (const iframe of Array.from(iframes)) {
try {
console.log('检查iframe:', iframe.src || '无src属性', iframe.id || '无id属性', iframe.name || '无name属性');
// 尝试访问iframe的内容
const iframeDoc = iframe.contentDocument || ((_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.document);
if (iframeDoc) {
// 检查是否可以访问iframe内容(同源策略)
try {
const bodyContent = (_b = iframeDoc.body) === null || _b === void 0 ? void 0 : _b.innerHTML;
if (bodyContent && bodyContent.length > 0) {
console.log('成功访问iframe文档内容,内容长度:', bodyContent.length);
// 检查是否包含简历相关内容
const hasResumeContent = bodyContent.includes('resume') ||
bodyContent.includes('简历') ||
iframeDoc.querySelector('.resume-detail-wrap, .lib-resume-recommend, [class*="resume"]');
if (hasResumeContent) {
console.log('iframe中发现简历相关内容');
return iframeDoc;
}
else {
console.log('此iframe不包含简历相关内容');
}
}
else {
console.log('iframe body为空或无法访问');
}
}
catch (e) {
console.log('无法读取iframe内容(可能受同源策略限制):', e);
}
}
else {
console.log('无法获取iframe文档');
}
}
catch (frameError) {
console.log('访问iframe时发生错误:', frameError);
}
}
// 如果没有找到包含简历的iframe,返回第一个可访问的iframe
for (const iframe of Array.from(iframes)) {
try {
const iframeDoc = iframe.contentDocument || ((_c = iframe.contentWindow) === null || _c === void 0 ? void 0 : _c.document);
if (iframeDoc && iframeDoc.body) {
console.log('返回第一个可访问的iframe文档');
return iframeDoc;
}
}
catch (e) {
// 忽略访问错误
}
}
console.log('无法找到可用的iframe文档');
return null;
}
catch (error) {
console.error('获取iframe文档失败', error);
return null;
}
}
/**
* 从简历详情页提取候选人信息
* @returns 提取的简历信息对象
*/
function extractResumeDetails() {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g;
try {
console.log('开始提取简历详情');
// 等待简历详情页面完全加载
yield sleep(1000);
// 创建简历信息对象
let resume = {
text: '' // 初始化text字段
};
// 查找简历详情弹窗 - 包含更多针对boss-dynamic-dialog的选择器
const dialogSelectors = [
// 直接针对test.html提供的结构
'#boss-dynamic-dialog-1iou6r9m7',
'.dialog-wrap.active',
'.boss-popup__wrapper.boss-dialog.boss-dialog__wrapper dialog-lib-resume',
'.lib-resume-recommend.lib-standard-resume',
// 通用选择器
'.resume-detail-wrap',
'.lib-resume-recommend',
'.boss-dialog__body',
'.boss-popup__content',
'[data-type="boss-dialog"]',
'[class*="dialog"]',
'[class*="popup"]',
'.boss-dialog__wrapper',
'.boss-layer__wrapper',
'[id^="boss-dynamic-dialog"]',
'.dialog-lib-resume',
'.lib-standard-resume'
];
// 首先尝试在主文档中查找简历详情容器
let resumeContainer = null;
for (const selector of dialogSelectors) {
resumeContainer = document.querySelector(selector);
if (resumeContainer) {
console.log(`找到简历详情容器: ${selector}`);
break;
}
}
// 如果在主文档中没找到,尝试在iframe中查找
if (!resumeContainer) {
console.log('在主文档中未找到简历详情容器,尝试在iframe中查找');
const iframeDoc = getIframeDocument();
if (iframeDoc) {
for (const selector of dialogSelectors) {
resumeContainer = iframeDoc.querySelector(selector);
if (resumeContainer) {
console.log(`在iframe中找到简历详情容器: ${selector}`);
break;
}
}
}
}
if (!resumeContainer) {
console.error('无法找到简历详情容器');
return null;
}
console.log('开始解析简历详情');
// 尝试提取姓名
// 注意:在提供的HTML中可能无法直接获取姓名,可能需要从其他地方获取
const nameSelectors = [
'.resume-right-side .geek-name .name',
// 移除 '.resume-anonymous-geek-card .name' 选择器,因为它可能指向其他推荐候选人
'.name, .geek-name',
'h1, h2, header h3',
'.candidate-name'
];
// 先检查是否有推荐候选人卡片,如果有则排除此区域查找姓名
const anonymousGeekCard = resumeContainer.querySelector('.resume-anonymous-geek-card, .resume-anonymous-geek-card.v2');
if (anonymousGeekCard) {
console.log('发现推荐候选人卡片区域,将排除此区域查找姓名');
}
for (const selector of nameSelectors) {
let nameElement = null;
// 如果存在推荐候选人卡片,确保不从其中查找姓名
if (anonymousGeekCard) {
// 只在非推荐卡片区域查找姓名
const allNameElements = resumeContainer.querySelectorAll(selector);
for (const element of Array.from(allNameElements)) {
if (!anonymousGeekCard.contains(element)) {
nameElement = element;
break;
}
}
}
else {
nameElement = resumeContainer.querySelector(selector);
}
if (nameElement) {
resume.name = (_a = nameElement.textContent) === null || _a === void 0 ? void 0 : _a.trim();
console.log(`提取到姓名: ${resume.name}`);
break;
}
}
// 如果无法从简历中提取姓名,将使用processCandidate传入的候选人信息
if (!resume.name) {
console.log('无法从简历详情中提取姓名,将在生成文本时使用列表项中的姓名');
}
// 提取基本信息(年龄、工作经验等)
const infoSelectors = [
'.anonymous-info-labels',
'.resume-section.geek-position-experience-wrap',
'.section-content .join-text-wrap',
'.basic-info',
'.candidate-info-content',
'[class*="info"]',
'.base-info-wrap'
];
let basicInfoElement = null;
for (const selector of infoSelectors) {
basicInfoElement = resumeContainer.querySelector(selector);
if (basicInfoElement) {
console.log(`找到基本信息元素: ${selector}`);
break;
}
}
if (basicInfoElement) {
const basicInfoText = ((_b = basicInfoElement.textContent) === null || _b === void 0 ? void 0 : _b.trim()) || '';
console.log('提取到基本信息文本:', basicInfoText);
// 尝试提取年龄
const ageMatch = basicInfoText.match(/(\d+)岁/) ||
basicInfoText.match(/年龄[::]\s*(\d+)/) ||
basicInfoText.match(/(\d+)\s*岁/);
if (ageMatch && ageMatch[1]) {
resume.age = ageMatch[1];
console.log(`提取到年龄: ${resume.age}`);
}
// 尝试提取工作经验
const expMatch = basicInfoText.match(/(\d+)年经验/) ||
basicInfoText.match(/经验[::]\s*(\d+)/) ||
basicInfoText.match(/(\d+)年/) ||
basicInfoText.match(/(\d+)个月/);
if (expMatch && expMatch[1]) {
const unit = basicInfoText.includes('月') ? '个月' : '年';
resume.experience = expMatch[1] + unit;
console.log(`提取到工作经验: ${resume.experience}`);
}
}
// 检查岗位经验部分
const positionExpElement = resumeContainer.querySelector('.resume-section.geek-position-experience-wrap');
if (positionExpElement && !resume.experience) {
const posExpText = ((_c = positionExpElement.textContent) === null || _c === void 0 ? void 0 : _c.trim()) || '';
const expMatch = posExpText.match(/(\d+)年(\d+)个月/) ||
posExpText.match(/(\d+)年/) ||
posExpText.match(/(\d+)个月/);
if (expMatch) {
resume.experience = expMatch[0];
console.log(`从岗位经验提取到工作经验: ${resume.experience}`);
}
}
// 提取教育经历
const eduSelectors = [
'.resume-section.geek-education-experience-wrap',
'.edu-wrap .school-name-wrap',
'.education',
'[class*="education"]',
'.edu-exp',
'[data-view-name="resume_education"]',
'.school-name'
];
let educationElement = null;
for (const selector of eduSelectors) {
educationElement = resumeContainer.querySelector(selector);
if (educationElement) {
console.log(`找到教育经历元素: ${selector}`);
break;
}
}
if (educationElement) {
resume.education = (_d = educationElement.textContent) === null || _d === void 0 ? void 0 : _d.trim();
// 提取简要的教育信息
const schoolMatch = resume.education.match(/([\u4e00-\u9fa5]+大学|学院|学校)/);
const degreeMatch = resume.education.match(/(本科|硕士|博士|大专|高中|中专)/);
if (schoolMatch && degreeMatch) {
resume.education = `${schoolMatch[1]} ${degreeMatch[1]}`;
}
console.log(`提取到教育经历: ${resume.education}`);
}
// 提取期望薪资
const expectSelectors = [
'.resume-section.geek-expect-wrap',
'[class*="salary"]',
'[class*="expect"]',
'.expect-salary',
'.salary-wrap'
];
let salaryElement = null;
for (const selector of expectSelectors) {
salaryElement = resumeContainer.querySelector(selector);
if (salaryElement) {
console.log(`找到期望薪资元素: ${selector}`);
break;
}
}
if (salaryElement) {
const salaryText = ((_e = salaryElement.textContent) === null || _e === void 0 ? void 0 : _e.trim()) || '';
const salaryMatch = salaryText.match(/(\d+-\d+[KkWw万])|(\d+[KkWw万]-\d+[KkWw万])/);
if (salaryMatch) {
resume.salary = salaryMatch[0];
}
else {
resume.salary = salaryText;
}
console.log(`提取到期望薪资: ${resume.salary}`);
}
// 提取技能标签
const tagSelectors = [
'.resume-section.geek-position-experience-wrap .tags .tag',
'.work-wrap .tags .tag',
'.skill-tag',
'[class*="skill"]',
'[class*="tag"]',
'.tag-item',
'.match-labels .label'
];
const skillTags = [];
for (const selector of tagSelectors) {
const tagElements = resumeContainer.querySelectorAll(selector);
if (tagElements && tagElements.length > 0) {
console.log(`找到技能标签元素: ${selector}, 数量: ${tagElements.length}`);
const tags = Array.from(tagElements)
.map(tag => { var _a; return (_a = tag.textContent) === null || _a === void 0 ? void 0 : _a.trim(); })
.filter(Boolean);
skillTags.push(...tags);
}
}
if (skillTags.length > 0) {
// 去重
resume.skills = Array.from(new Set(skillTags));
console.log(`提取到技能: ${resume.skills.join(', ')}`);
}
// 提取个人简介或自我评价
const descSelectors = [
'.geek-desc',
'.resume-section .geek-desc',
'[data-high-light="true"].geek-desc',
'.summary',
'[class*="summary"]',
'[class*="evaluate"]',
'.self-evaluation'
];
let summaryElement = null;
for (const selector of descSelectors) {
summaryElement = resumeContainer.querySelector(selector);
if (summaryElement) {
console.log(`找到个人简介元素: ${selector}`);
break;
}
}
if (summaryElement) {
// 获取原始HTML并移除<span>标签,保留文本内容
const originalHtml = summaryElement.innerHTML;
let summaryText = '';
if (originalHtml.includes('font-highlight')) {
// 如果有高亮内容,尝试提取纯文本
const tempDiv = document.createElement('div');
tempDiv.innerHTML = originalHtml;
summaryText = tempDiv.textContent || '';
}
else {
summaryText = ((_f = summaryElement.textContent) === null || _f === void 0 ? void 0 : _f.trim()) || '';
}
resume.summary = summaryText;
console.log(`提取到自我评价: ${resume.summary.substring(0, 50)}...`);
}
// 尝试提取工作经历
const workSelectors = [
'.resume-section.geek-work-experience-wrap .work-wrap',
'.work-wrap',
'.timeline-item',
'.geek-work-experience-wrap',
'[class*="work-exp"]'
];
let workElements = null;
for (const selector of workSelectors) {
workElements = resumeContainer.querySelectorAll(selector);
if (workElements && workElements.length > 0) {
console.log(`找到工作经历元素: ${selector}, 数量: ${workElements.length}`);
break;
}
}
if (workElements && workElements.length > 0) {
const workHistory = Array.from(workElements)
.map(workItem => {
var _a, _b, _c, _d;
// 提取公司名称、职位和时间
const companyElem = workItem.querySelector('.company-name, .company-name-wrap');
const positionElem = workItem.querySelector('.position');
const periodElem = workItem.querySelector('.period');
const contentElem = workItem.querySelector('.item-content');
let workText = '';
if (companyElem)
workText += ((_a = companyElem.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '';
if (positionElem)
workText += ' ' + (((_b = positionElem.textContent) === null || _b === void 0 ? void 0 : _b.trim()) || '');
if (periodElem)
workText += ' ' + (((_c = periodElem.textContent) === null || _c === void 0 ? void 0 : _c.trim()) || '');
if (contentElem)
workText += '\n' + (((_d = contentElem.textContent) === null || _d === void 0 ? void 0 : _d.trim()) || '');
return workText.trim();
})
.filter(Boolean)
.join(' | ');
resume.workHistory = workHistory;
console.log(`提取到工作经历: ${workHistory.substring(0, 50)}...`);
}
// 生成完整文本用于兼容原有代码
resume.text = `
姓名:${resume.name || '未知'}
年龄:${resume.age || '未知'}
工作经验:${resume.experience || '未知'}
教育经历:${resume.education || '未知'}
期望薪资:${resume.salary || '未知'}
技能:${((_g = resume.skills) === null || _g === void 0 ? void 0 : _g.join(', ')) || '未知'}
简介:${resume.summary || '未知'}
工作经历:${resume.workHistory || '未知'}
`;
console.log('简历信息提取完成');
return resume;
}
catch (error) {
console.error('提取简历信息时发生错误', error);
return null;
}
});
}
/**
* 高亮符合条件的候选人卡片
* @param card 候选人卡片元素
*/
function highlightCard(card) {
card.classList.add('highlight-card');
}
/**
* 查找所有候选人卡片
* @param processedCards 已处理过的卡片数组,用于排除已处理的卡片
* @returns 候选人卡片元素列表(未处理的)
*/
function findCandidateCards(processedCards = new Set()) {
// 首先尝试获取iframe内容
const iframeDoc = getIframeDocument();
const doc = iframeDoc || document;
console.log('开始在页面中查找候选人卡片');
// 首先尝试查找列表元素,确保按照DOM顺序处理
const cardList = doc.querySelector('.card-list, [class*="card-list"], .recommend-list, [data-v-b753c1ac].card-list');
let cards = [];
if (cardList) {
console.log('找到卡片列表容器,按列表顺序查找卡片');
// 如果找到列表容器,按列表项顺序处理
cards = Array.from(cardList.querySelectorAll('li.card-item, [class*="card-item"], li'));
}
else {
// 按照提供的实际DOM结构查找候选人卡片 - 更新选择器
cards = Array.from(doc.querySelectorAll('.candidate-card-wrap, .card-inner[data-geek], [data-geekid], ' +
'li.card-item, [data-v-b753c1ac].card-item, ' +
'.card-list li, [class*="card-list"] li, ' +
'[class*="candidate-recommend"] li, [class*="recommend-list"] li'));
// 如果没有找到卡片,尝试更通用的选择器
if (cards.length === 0) {
console.log('尝试使用更通用的选择器查找候选人卡片');
cards = Array.from(doc.querySelectorAll('[class*="candidate-card"], [class*="card-inner"], [class*="card-wrap"], ' +
'[class*="card-item"], [data-v-b753c1ac], ' +
'[data-v*="card"], [data-v*="candidate"], ' +
'[class*="list"] > li, .candidate-card-wrap, ' +
'[data-geek], [data-geekid]'));
}
}
console.log(`找到 ${cards.length} 个候选人卡片`);
// 过滤掉已处理过的卡片
const newCards = cards.filter(card => !processedCards.has(card));
console.log(`其中 ${newCards.length} 个卡片未处理`);
return newCards;
}
// 默认配置
const config = {
autoGreet: true,
greetingMessages: [
'您好,看到您的简历很感兴趣,方便聊一下吗?',
'您好,我们正在招聘相关岗位,您是否有兴趣了解一下?',
'您好,我对您的经历很感兴趣,想进一步了解一下,方便沟通吗?'
]
};
/**
* 等待指定选择器的元素出现在DOM中
* @param selector CSS选择器
* @param context 查询上下文,默认为主文档
* @param timeout 超时时间(毫秒)
* @returns 找到的元素或null
*/
function waitForElement(selector_1) {
return __awaiter(this, arguments, void 0, function* (selector, context = document, timeout = 7000) {
const contextName = context === document ? 'main document' : 'specified context';
console.log(`Waiting for element: ${selector} in ${contextName}`);
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const element = context.querySelector(selector);
if (element) {
console.log(`Element found: ${selector} in ${contextName}`);
yield sleep(300); // 确保元素渲染稳定
return element;
}
yield sleep(100); // 每100毫秒检查一次
}
console.log(`Timeout waiting for element: ${selector} in ${contextName}`);
return null;
});
}
/**
* 点击候选人卡片的查看按钮或卡片本身
* @param card 要点击的卡片元素 (来自 iframe)
* @returns 是否成功打开简历详情
*/
function clickViewButton(card) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
// 尝试从卡片中提取姓名用于日志
const nameElement = card.querySelector('span.name, .name-wrap .name, .geek-name, .name, [class*="name"]');
const candidateName = ((_a = nameElement === null || nameElement === void 0 ? void 0 : nameElement.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '未知姓名 (从卡片提取)';
console.log(`尝试点击卡片查看 ${candidateName} 的简历`);
// 检查卡片元素是否存在
if (!card) {
console.error('传递给clickViewButton的卡片元素无效');
return false;
}
// 检查是否已经有打开的简历详情弹窗
const existingDialogSelectors = [
// 针对test.html的特定结构
'[id^="boss-dynamic-dialog"]',
'.dialog-wrap.active',
'.boss-popup__wrapper.boss-dialog',
'.boss-popup__content',
'.lib-resume-recommend',
// 通用选择器
'.resume-detail-wrap',
'.boss-dialog__body',
'[class*="dialog"][class*="active"]',
'[class*="popup"][style*="z-index"]'
];
for (const selector of existingDialogSelectors) {
const existingDialog = document.querySelector(selector);
if (existingDialog) {
console.log(`页面上已经有打开的简历详情弹窗: ${selector}`);
return true;
}
}
// 优先尝试点击卡片本身或卡片内的可点击区域来触发简历详情
console.log('尝试点击卡片本身触发简历详情...');
// 尝试找到卡片中的可点击区域
const clickableAreaSelectors = [
'.card-inner',
'.candidate-card-content',
'[class*="inner"]',
'[class*="content"]',
'.row.name-wrap',
'.avatar-wrap',
'.col-2',
'.candidate-card-wrap',
'[data-geek]',
'[data-geekid]',
'.card-item'
];
let clickableArea = null;
for (const selector of clickableAreaSelectors) {
clickableArea = card.querySelector(selector);
if (clickableArea) {
console.log(`找到卡片内可点击区域 (${selector}),点击...`);
break;
}
}
if (clickableArea) {
console.log('开始点击卡片可点击区域...');
// 尝试使用不同的点击方法
try {
clickableArea.click();
console.log('已使用Element.click方法点击');
}
catch (clickError) {
console.log('Element.click方法失败,尝试模拟鼠标点击', clickError);
// 模拟鼠标点击
try {
const clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
clickableArea.dispatchEvent(clickEvent);
console.log('已模拟鼠标点击事件');
}
catch (mouseError) {
console.error('模拟鼠标点击失败', mouseError);
}
}
console.log('已点击卡片可点击区域,等待简历详情出现...');
}
else {
// 直接点击卡片
console.log('开始点击整个卡片...');
try {
card.click();
console.log('已使用Element.click方法点击卡片');
}
catch (clickError) {
console.log('Element.click方法失败,尝试模拟鼠标点击', clickError);
// 模拟鼠标点击
try {
const clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
card.dispatchEvent(clickEvent);
console.log('已模拟鼠标点击事件');
}
catch (mouseError) {
console.error('模拟鼠标点击失败', mouseError);
}
}
console.log('已点击整个卡片,等待简历详情出现...');
}
// 等待简历详情出现
console.log('等待简历详情弹窗出现...');
yield sleep(1200);
// 直接检查特定的弹窗元素 - 针对test.html提供的结构
const specificDialogSelectors = [
'[id^="boss-dynamic-dialog"]',
'.dialog-wrap.active',
'.boss-popup__wrapper',
'.boss-dialog',
'.boss-popup__content',
'.lib-resume-recommend',
'.lib-standard-resume'
];
for (const selector of specificDialogSelectors) {
const dialogElement = document.querySelector(selector);
if (dialogElement) {
console.log(`成功找到简历详情弹窗: ${selector}`);
return true;
}
}
// 检查是否存在任何对话框或弹窗元素
const genericDialogSelectors = [
'[class*="dialog"]',
'[class*="popup"]',
'[class*="resume"]',
'[class*="modal"]',
'[role="dialog"]',
'.drawer',
'.layer'
];
for (const selector of genericDialogSelectors) {
const dialogElement = document.querySelector(selector);
if (dialogElement && (dialogElement.classList.contains('active') ||
(dialogElement.getAttribute('style') && dialogElement.getAttribute('style').includes('z-index')) ||
dialogElement.classList.contains('boss-dialog') ||
dialogElement.classList.contains('boss-popup'))) {
console.log(`找到活跃的对话框元素: ${selector}`);
return true;
}
}
// 尝试强制检测document.body的变化
const bodyChildren = document.body.children;
for (let i = 0; i < bodyChildren.length; i++) {
const child = bodyChildren[i];
if (child.classList.contains('boss-dialog') ||
child.classList.contains('boss-popup') ||
child.classList.contains('dialog-wrap') ||
child.id.startsWith('boss-dynamic-dialog') ||
(child.getAttribute('style') && child.getAttribute('style').includes('z-index'))) {
console.log(`从document.body直接找到弹窗元素: ${child.tagName}#${child.id}.${Array.from(child.classList).join('.')}`);
return true;
}
}
// 备用检测:递归检查DOM中的弹窗元素
function findDialogInElement(element, depth = 0) {
if (depth > 3)
return null; // 限制搜索深度
// 检查当前元素是否是对话框
if (element.id.startsWith('boss-dynamic-dialog') ||
element.classList.contains('dialog-wrap') ||
element.classList.contains('boss-dialog') ||
element.classList.contains('boss-popup') ||
element.classList.contains('lib-resume-recommend')) {
return element;
}
// 递归检查子元素
for (let i = 0; i < element.children.length; i++) {
const result = findDialogInElement(element.children[i], depth + 1);
if (result)
return result;
}
return null;
}
const dialogElement = findDialogInElement(document.body);
if (dialogElement) {
console.log(`递归查找找到弹窗元素: ${dialogElement.tagName}#${dialogElement.id}.${Array.from(dialogElement.classList).join('.')}`);
return true;
}
// 尝试通过定时任务再次检查
let dialogFound = false;
for (let attempt = 1; attempt <= 3; attempt++) {
console.log(`第${attempt}次额外检查弹窗...`);
yield sleep(500);
// 检查特定选择器
for (const selector of [...specificDialogSelectors, ...genericDialogSelectors]) {
const dialogElement = document.querySelector(selector);
if (dialogElement) {
console.log(`额外检查找到简历详情弹窗: ${selector}`);
dialogFound = true;
break;
}
}
if (dialogFound)
break;
}
if (dialogFound) {
return true;
}
console.log('无法检测到简历详情或对话框元素,点击可能未生效');
return false;
}
catch (error) {
console.error('在卡片点击或详情视图检测过程中发生错误:', error);
return false;
}
});
}
/**
* 关闭简历详情
*/
function closeResumeDetail() {
return __awaiter(this, void 0, void 0, function* () {
console.log('[closeResumeDetail] 尝试关闭简历详情');
// 复用 extractResumeDetails 的弹窗查找逻辑来确定上下文
const dialogSelectors = [
'#boss-dynamic-dialog-1iou6r9m7',
'.dialog-wrap.active',
'.boss-popup__wrapper.boss-dialog.boss-dialog__wrapper dialog-lib-resume',
'.lib-resume-recommend.lib-standard-resume',
'.resume-detail-wrap',
'.lib-resume-recommend',
'.boss-dialog__body',
'.boss-popup__content',
'[data-type="boss-dialog"]',
'[class*="dialog"]',
'[class*="popup"]',
'.boss-dialog__wrapper',
'.boss-layer__wrapper',
'[id^="boss-dynamic-dialog"]',
'.dialog-lib-resume',
'.lib-standard-resume'
];
let resumeContainer = null;
let containerContext = null;
let containerSelector = '';
// 尝试在主文档中查找
console.log('[closeResumeDetail] 在主文档中查找弹窗容器...');
for (const selector of dialogSelectors) {
resumeContainer = document.querySelector(selector);
if (resumeContainer) {
console.log(`[closeResumeDetail] 在主文档找到容器: ${selector}`);
containerContext = document;
containerSelector = selector;
break;
}
}
// 如果主文档找不到,尝试在 iframe 中查找
if (!resumeContainer) {
console.log('[closeResumeDetail] 主文档未找到,尝试在 iframe 中查找...');
const iframeDoc = getIframeDocument();
if (iframeDoc) {
for (const selector of dialogSelectors) {
resumeContainer = iframeDoc.querySelector(selector);
if (resumeContainer) {
console.log(`[closeResumeDetail] 在 iframe 中找到容器: ${selector}`);
containerContext = iframeDoc;
containerSelector = selector;
break;
}
}
}
}
if (!resumeContainer || !containerContext) {
console.warn('[closeResumeDetail] 未能定位到简历弹窗容器,无法继续关闭操作。尝试后备方案...');
// 尝试后备方案:直接在 document 查找关闭按钮和 ESC
yield attemptFallbackClose(document);
return;
}
console.log(`[closeResumeDetail] 定位到弹窗容器: ${containerSelector},在其内部和父级查找关闭按钮`);
// 优先尝试在弹窗容器内部查找关闭按钮
let closeButton = yield findCloseButtonInContext(resumeContainer);
// 如果在容器内部找不到,尝试在容器的父级元素查找(有时关闭按钮在弹窗外部)
if (!closeButton && resumeContainer.parentElement) {
console.log('[closeResumeDetail] 在容器内部未找到关闭按钮,尝试查找父级元素...');
closeButton = yield findCloseButtonInContext(resumeContainer.parentElement);
}
// 如果找到关闭按钮,点击它
if (closeButton) {
console.log(`[closeResumeDetail] 找到关闭按钮,点击...`);
closeButton.click();
yield sleep(1000); // 等待关闭动画
console.log('[closeResumeDetail] 简历详情已关闭 (通过按钮点击)');
}
else {
// 如果找不到按钮,使用后备方案
console.warn('[closeResumeDetail] 在容器及其父级中未找到关闭按钮,尝试后备方案 (返回按钮 / ESC)...');
yield attemptFallbackClose(containerContext);
}
});
}
// 辅助函数:在指定上下文中查找关闭按钮
function findCloseButtonInContext(contextElement) {
return __awaiter(this, void 0, void 0, function* () {
const closeSelectors = [
'.boss-popup__close .icon-close', // 优先用户提供的
'.icon-close',
'.close-icon',
'[class*="close"]',
'button[class*="close"]', // 更具体的按钮
'.boss-dialog__close',
'.close-wrapper'
];
console.log(`[findCloseButtonInContext] 在元素 <${contextElement.tagName.toLowerCase()}> 内查找关闭按钮`);
for (const selector of closeSelectors) {
const button = contextElement.querySelector(selector);
if (button) {
console.log(`[findCloseButtonInContext] 找到按钮: ${selector}`);
return button;
}
}
console.log(`[findCloseButtonInContext] 在元素 <${contextElement.tagName.toLowerCase()}> 内未找到任何关闭按钮`);
return null;
});
}
// 辅助函数:尝试后备关闭方法(返回按钮或ESC)
function attemptFallbackClose(doc) {
return __awaiter(this, void 0, void 0, function* () {
console.log('[attemptFallbackClose] 尝试后备关闭方法...');
const backButton = doc.querySelector('.header-back, [class*="back"], .go-back');
if (backButton) {
console.log('[attemptFallbackClose] 找到返回按钮,点击...');
backButton.click();
yield sleep(1000);
console.log('[attemptFallbackClose] 已点击返回按钮');
return;
}
console.log('[attemptFallbackClose] 尝试按ESC键关闭...');
const escEvent = new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
keyCode: 27,
which: 27,
bubbles: true,
cancelable: true
});
doc.dispatchEvent(escEvent);
yield sleep(1000);
console.log('[attemptFallbackClose] 已尝试按ESC键');
});
}
/**
* 发送打招呼消息
* @param candidateName 候选人姓名
* @returns 是否成功发送打招呼消息
*/
function sendGreeting(candidateName) {
return __awaiter(this, void 0, void 0, function* () {
try {
console.log('准备发送打招呼消息');
// 确保有问候消息配置
if (!config.greetingMessages || config.greetingMessages.length === 0) {
console.error('未配置问候消息,无法发送');
return false;
}
// 从配置的问候语中选择一条
let greeting = '';
const randomIndex = Math.floor(Math.random() * config.greetingMessages.length);
greeting = config.greetingMessages[randomIndex];
// 如果有候选人姓名,替换问候语中的{name}占位符
if (candidateName) {
greeting = greeting.replace(/{name}/g, candidateName);
}
// 发送消息
return yield sendMessage(greeting);
}
catch (error) {
console.error('发送打招呼消息时发生错误', error);
return false;
}
});
}
/**
* 处理单个候选人
* @param card 候选人卡片元素
* @returns 是否处理成功 (成功定义为LLM推荐且尝试了打招呼)
*/
function processCandidate(card) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
let shouldClosePopup = false; // 标记是否需要关闭弹窗
try {
console.log('[processCandidate] 开始处理候选人卡片:', card);
const candidate = (0, filter_1.extractCandidateInfo)(card);
// 优先从卡片中正确提取姓名
let candidateName = '';
const nameElementInCard = card.querySelector('span.name, .name-wrap .name');
if (nameElementInCard) {
candidateName = ((_a = nameElementInCard.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '';
console.log(`[processCandidate] 从卡片直接提取到候选人姓名: ${candidateName}`);
}
// 如果直接从卡片提取失败,则使用extractCandidateInfo的结果
if (!candidateName && candidate.name) {
candidateName = candidate.name;
}
console.log('[processCandidate] 提取到卡片信息:', {
name: candidateName || candidate.name,
education: candidate.education,
experience: candidate.experience,
tags: candidate.tags
});
const config = (0, config_1.loadConfig)();
// 1. 检查技能筛选
if (config.filters.skills && config.filters.skills.length > 0) {
const candidateSkills = candidate.tags || [];
const requiredSkills = config.filters.skills;
const hasMatchingSkill = requiredSkills.some((skill) => candidateSkills.some((candidateSkill) => candidateSkill.toLowerCase().includes(skill.toLowerCase())));
if (!hasMatchingSkill) {
console.log(`[processCandidate] 候选人 ${candidateName} 技能不符合筛选条件,跳过`);
return false; // 不符合,直接返回,不打开详情
}
console.log(`[processCandidate] 候选人 ${candidateName} 技能符合筛选条件`);
}
highlightCard(card);
console.log(`[processCandidate] 已高亮 ${candidateName} 卡片`);
const delayTime = config.operationInterval * 1000 || 1000;
console.log(`[processCandidate] 添加 ${delayTime}ms 延时...`);
yield randomDelay(delayTime);
// 2. 点击查看简历详情
console.log(`[processCandidate] 尝试点击查看 ${candidateName} 简历详情...`);
let viewSuccess = yield clickViewButton(card);
if (!viewSuccess) {
console.log(`[processCandidate] 无法打开 ${candidateName} 简历详情,跳过`);
return false; // 打开失败,无法继续
}
shouldClosePopup = true; // 成功打开,标记需要关闭
console.log(`[processCandidate] 成功打开 ${candidateName} 简历详情`);
// 3. 等待弹窗加载并提取内容
console.log('[processCandidate] 等待弹窗加载 (1500ms)...');
yield sleep(1500);
console.log('[processCandidate] 开始提取简历详情文本...');
let resumeDetails = yield extractResumeDetails(); // 注意这里返回的是包含 text 的对象
// 重试逻辑
let retryCount = 0;
const maxRetries = 3;
while (!resumeDetails && retryCount < maxRetries) {
retryCount++;
console.log(`[processCandidate] 提取简历失败,第 ${retryCount} 次重试...`);
yield sleep(1000 + retryCount * 500); // 增加重试等待时间
resumeDetails = yield extractResumeDetails();
}
if (!resumeDetails || !resumeDetails.text) { // 确保 resumeDetails 和 text 都存在
console.error('[processCandidate] 多次尝试后仍无法提取简历信息,跳过 LLM 分析');
yield closeResumeDetail(); // 尝试关闭弹窗
return false;
}
// 直接使用从卡片中提取的候选人姓名,不依赖简历详情中提取的姓名
if (candidateName) {
console.log(`[processCandidate] 使用卡片中的姓名: ${candidateName}`);
resumeDetails.name = candidateName;
// 替换简历文本中的姓名行
resumeDetails.text = resumeDetails.text.replace(/姓名:.*?\n/, `姓名:${candidateName}\n`);
}
else if (!resumeDetails.name || resumeDetails.name.includes('工程师') || resumeDetails.name.includes('开发') || resumeDetails.name === '未知') {
// 如果姓名中包含"工程师"或"开发"等职位相关词汇,很可能是职位被误识别为姓名
console.log('[processCandidate] 简历详情中可能将职位误识别为姓名,将使用"未知"替代');
resumeDetails.name = '未知候选人';
resumeDetails.text = resumeDetails.text.replace(/姓名:.*?\n/, `姓名:未知候选人\n`);
}
console.log('[processCandidate] 成功提取简历详情文本:', resumeDetails.text.substring(0, 100) + '...');
// 4. LLM 分析简历
console.log('[processCandidate] 开始 LLM 分析...');
const analysis = yield (0, llm_1.analyzeResumeWithLLM)(resumeDetails.text); // 确保传递的是 text 字段
console.log('[processCandidate] LLM 分析完成');
(0, ui_1.updateAnalysisResult)(analysis);
// 5. 根据分析结果决定是否打招呼
let greetAttempted = false;
if ((0, llm_1.shouldContactCandidate)(analysis)) {
console.log(`[processCandidate] LLM 推荐与 ${candidateName} 沟通,尝试点击打招呼...`);
greetAttempted = yield clickGreetButtonOnCard(card);
if (greetAttempted) {
console.log(`[processCandidate] 已成功在卡片上点击 ${candidateName} 的打招呼按钮`);
// 等待按钮变成继续沟通
yield sleep(2000);
// 再次点击继续沟通按钮
const continueButton = card.querySelector('.btn-continue, button.btn-continue, .button-chat-wrap button.btn-continue');
if (continueButton) {
continueButton.click();
console.log(`[processCandidate] 已点击继续沟通按钮`);
// 等待聊天窗口加载
yield sleep(2000);
// 查找聊天输入框
const chatInput = document.querySelector('#boss-chat-global-input');
if (chatInput) {
// 设置AI建议的内容
chatInput.textContent = analysis.openingMessage;
// 触发input事件
chatInput.dispatchEvent(new Event('input', { bubbles: true }));
// 等待一下确保内容设置成功
yield sleep(500);
// 查找发送按钮并点击
const sendButton = document.querySelector('.btn-send:not(.btn-disabled)');
if (sendButton) {
sendButton.click();
console.log('[processCandidate] 已发送AI建议的沟通内容');
// 等待消息发送
yield sleep(1000);
// 查找并点击关闭按钮
const closeButton = document.querySelector('.iboss-close');
if (closeButton) {
closeButton.click();
console.log('[processCandidate] 已关闭聊天窗口');
}
}
}
}
else {
console.warn(`[processCandidate] 未找到继续沟通按钮`);
}
}
else {
console.warn(`[processCandidate] 未能在卡片上找到 ${candidateName} 的打招呼/沟通按钮`);
}
}
else {
console.log(`[processCandidate] LLM 不推荐与 ${candidateName} 沟通`);
}
// 6. 关闭简历详情弹窗
console.log('[processCandidate] 准备关闭简历详情弹窗...');
yield closeResumeDetail();
shouldClosePopup = false; // 标记已关闭
return greetAttempted; // 返回是否尝试了打招呼
}
catch (error) {
console.error('[processCandidate] 处理候选人时发生严重错误:', error);
if (shouldClosePopup) {
console.log('[processCandidate] 发生错误,尝试关闭可能打开的弹窗...');
yield closeResumeDetail(); // 确保异常时也尝试关闭
}
return false; // 出错则认为未成功处理
}
});
}
/**
* 在候选人卡片元素上查找并点击打招呼或继续沟通按钮
* @param card 候选人卡片 HTMLElement
* @returns 是否成功点击了按钮
*/
function clickGreetButtonOnCard(card) {
return __awaiter(this, void 0, void 0, function* () {
console.log('[clickGreetButtonOnCard] 开始在卡片上查找打招呼/沟通按钮...');
const buttonSelectors = [
'button.btn-greet',
'.button-chat button',
'button.btn-sure-v2',
'button[class*="greet"]',
'.button-chat-wrap button.btn-continue',
'.button-chat-wrap button',
'button.chat-btn',
'button.btn-continue',
'.btn-continue',
'.button-list button',
'[class*="button-chat"] button'
];
let targetButton = null;
for (const selector of buttonSelectors) {
targetButton = card.querySelector(selector);
if (targetButton) {
console.log(`[clickGreetButtonOnCard] 找到按钮 (${selector})`);
break;
}
}
if (targetButton) {
console.log('[clickGreetButtonOnCard] 点击按钮...');
targetButton.click();
yield sleep(500); // 短暂等待确保点击生效
console.log('[clickGreetButtonOnCard] 已点击按钮');
return true;
}
else {
console.warn('[clickGreetButtonOnCard] 在卡片上未找到任何打招呼或继续沟通按钮');
return false;
}
});
}
/**
* 滚动页面以加载更多候选人卡片
* @returns 是否成功加载更多卡片
*/
function scrollToLoadMore() {
return __awaiter(this, void 0, void 0, function* () {
console.log('滚动页面以加载更多候选人');
// 获取iframe文档
const iframeDoc = getIframeDocument();
const doc = iframeDoc || document;
// 记录滚动前的卡片数量
const beforeCount = findCandidateCards().length;
// 找到候选人列表容器
const container = doc.querySelector('.candidate-list-content, [class*="list-content"], .recommend-list-content');
if (container) {
// 滚动到容器底部
container.scrollTop = container.scrollHeight;
console.log('已滚动到列表底部');
}
else {
// 如果找不到特定容器,则滚动整个文档
doc.documentElement.scrollTop = doc.documentElement.scrollHeight;
console.log('已滚动到页面底部');
}
// 等待新内容加载
yield sleep(2000);
// 检查是否加载了新卡片
const afterCount = findCandidateCards().length;
return afterCount > beforeCount;
});
}
/**
* 自动处理所有候选人 (顺序处理当前页,处理完滚动加载)
* @param maxCount 最大处理数量,默认为10,设为0表示处理所有
*/
function autoProcessCandidates() {
return __awaiter(this, arguments, void 0, function* (maxCount = 10) {
(0, ui_1.setRunningStatus)(true);
(0, ui_1.clearAnalysisResult)();
let totalProcessedCount = 0;
let totalSuccessCount = 0;
const totalToProcess = maxCount > 0 ? maxCount : Number.MAX_SAFE_INTEGER;
console.log(`[autoProcessCandidates] 计划处理最多 ${totalToProcess === Number.MAX_SAFE_INTEGER ? '全部' : totalToProcess} 位候选人`);
// 跟踪已处理过的卡片,避免重复处理
const processedCards = new Set();
let noMoreCards = false;
while (totalProcessedCount < totalToProcess && (0, ui_1.getRunningStatus)() && !noMoreCards) {
console.log(`[autoProcessCandidates] --- 开始新一轮处理 (已处理 ${totalProcessedCount} / ${totalToProcess}) ---`);
// 获取未处理的卡片
let cardsToProcess = findCandidateCards(processedCards);
console.log(`[autoProcessCandidates] 找到 ${cardsToProcess.length} 个未处理的卡片`);
if (cardsToProcess.length === 0) {
console.log('[autoProcessCandidates] 页面无未处理卡片,尝试滚动...');
// 尝试滚动加载
const loadedMore = yield scrollToLoadMore();
if (!loadedMore) {
console.log('[autoProcessCandidates] 无法加载更多卡片,停止处理');
noMoreCards = true;
continue; // 结束外层while循环
}
// 滚动后重新获取卡片
cardsToProcess = findCandidateCards(processedCards);
console.log(`[autoProcessCandidates] 滚动后找到 ${cardsToProcess.length} 个未处理卡片`);
if (cardsToProcess.length === 0) {
console.log('[autoProcessCandidates] 滚动后仍无未处理卡片,停止处理');
noMoreCards = true;
continue; // 结束外层while循环
}
}
// 获取当前批次要处理的卡片数量(不超过剩余总数)
const remainingToProcess = totalToProcess - totalProcessedCount;
const batchSize = Math.min(cardsToProcess.length, remainingToProcess);
console.log(`[autoProcessCandidates] 本轮将处理 ${batchSize} 个卡片`);
// 顺序处理卡片 - 按照DOM中的顺序
for (let i = 0; i < batchSize && (0, ui_1.getRunningStatus)(); i++) {
const card = cardsToProcess[i];
// 标记卡片为处理中
card.setAttribute('data-processing', 'true');
console.log(`[autoProcessCandidates] 处理第 ${i + 1} / ${batchSize} 个卡片 (总计 ${totalProcessedCount + 1})`);
try {
// 确保只处理未处理过的卡片
if (!processedCards.has(card)) {
const success = yield processCandidate(card);
// 将卡片加入已处理集合
processedCards.add(card);
if (success) {
totalSuccessCount++;
console.log(`[autoProcessCandidates] 候选人处理成功 (总成功 ${totalSuccessCount})`);
}
else {
console.log('[autoProcessCandidates] 候选人处理未通过或失败');
}
totalProcessedCount++;
}
else {
console.log('[autoProcessCandidates] 跳过已处理的卡片');
}
}
catch (error) {
console.error('[autoProcessCandidates] 处理候选人时发生严重错误:', error);
// 出错也标记为已处理,避免死循环
processedCards.add(card);
totalProcessedCount++;
}
finally {
// 移除处理中标记
card.removeAttribute('data-processing');
}
// 添加短暂延时,确保页面响应和界面更新
yield sleep(1000);
// 检查是否达到处理上限
if (totalProcessedCount >= totalToProcess) {
break;
}
}
// 如果还没达到总目标,尝试滚动加载更多卡片
if (totalProcessedCount < totalToProcess && (0, ui_1.getRunningStatus)()) {
const visibleCardsCount = findCandidateCards(processedCards).length;
if (visibleCardsCount === 0) {
console.log('[autoProcessCandidates] 当前页面所有卡片已处理,尝试滚动加载下一页...');
const loadedMore = yield scrollToLoadMore();
if (!loadedMore) {
console.log('[autoProcessCandidates] 无法加载更多卡片,停止处理');
noMoreCards = true;
}
}
else {
console.log(`[autoProcessCandidates] 当前页面还有 ${visibleCardsCount} 个未处理卡片,继续处理`);
}
}
}
// 处理完成
(0, ui_1.setRunningStatus)(false);
alert(`自动操作完成\n总共尝试处理了 ${totalProcessedCount} 位候选人\n其中 ${totalSuccessCount} 位符合条件并已点击打招呼`);
console.log(`[autoProcessCandidates] 自动操作完成。尝试处理: ${totalProcessedCount}, 成功打招呼: ${totalSuccessCount}`);
});
}
/**
* 初始化自动处理功能
*/
function initAutoProcess() {
// 监听开始自动处理事件
document.addEventListener('boss-assistant:start', ((event) => {
var _a;
// 从事件中获取最大处理数量,默认为10
const customEvent = event;
const maxCount = ((_a = customEvent.detail) === null || _a === void 0 ? void 0 : _a.maxCount) || 10;
autoProcessCandidates(maxCount);
}));
// 监听停止自动处理事件
document.addEventListener('boss-assistant:stop', () => {
(0, ui_1.setRunningStatus)(false);
});
}
/**
* 浏览候选人简历,自动点击多个候选人卡片
* @param count 要浏览的候选人数量
*/
function browse(count) {
return __awaiter(this, void 0, void 0, function* () {
try {
console.log(`开始浏览 ${count} 个候选人简历`);
// 获取iframe文档对象
const iframeDoc = getIframeDocument();
if (!iframeDoc) {
console.error('未找到iframe文档,无法浏览候选人');
return;
}
// 查找所有候选人卡片
const cards = iframeDoc.querySelectorAll('.card-inner[data-geek], .card-inner[data-geekid], [data-geekid], .recommend-card-wrap');
console.log(`找到 ${cards.length} 个候选人卡片`);
if (cards.length === 0) {
console.error('未找到候选人卡片,无法浏览');
return;
}
// 确定实际浏览数量(不超过卡片总数)
const browseCount = Math.min(count, cards.length);
console.log(`将浏览 ${browseCount} 个候选人简历`);
for (let i = 0; i < browseCount; i++) {
console.log(`开始浏览第 ${i + 1} 个候选人简历`);
// 点击当前索引的候选人卡片查看按钮
const viewSuccess = yield clickViewButton(cards[i]);
if (viewSuccess) {
console.log(`成功打开第 ${i + 1} 个候选人简历`);
// 提取简历详情
const resumeDetails = yield extractResumeDetails();
const resumeText = (resumeDetails === null || resumeDetails === void 0 ? void 0 : resumeDetails.text) || '';
console.log(`简历内容长度: ${resumeText.length} 字符`);
// 自动点击打招呼按钮(如果设置了自动打招呼)
if (config.autoGreet) {
console.log('尝试点击打招呼按钮');
// 关闭简历详情,回到列表页面
yield closeResumeDetail();
yield sleep(800);
// 查找打招呼或继续沟通按钮
const greetButtonSelectors = [
'button.btn-greet',
'.button-chat button',
'button.btn-sure-v2',
'button[class*="greet"]',
'.button-chat-wrap button.btn-continue',
'.button-chat-wrap button',
'button.chat-btn',
'button.btn-continue',
'.btn-continue',
'.button-list button',
'[class*="button-chat"] button'
];
let greetButton = null;
for (const selector of greetButtonSelectors) {
greetButton = cards[i].querySelector(selector);
if (greetButton) {
console.log(`找到打招呼按钮 (${selector}),点击...`);
break;
}
}
if (greetButton) {
console.log('开始点击打招呼按钮...');
greetButton.click();
console.log('已点击打招呼按钮,系统会自动发送问候消息');
// 简单等待一下确保按钮点击成功
yield sleep(500);
console.log('成功点击打招呼按钮');
}
else {
console.log('未找到打招呼按钮,尝试寻找继续沟通按钮');
// 尝试找到"继续沟通"按钮
const continueButtonSelectors = [
'.btn-continue',
'button.btn-continue',
'.button-chat-wrap button.btn-continue',
'.button-list .btn-continue-wrap button',
'[class*="button-chat"] button.btn-continue'
];
let continueButton = null;
for (const selector of continueButtonSelectors) {
continueButton = cards[i].querySelector(selector);
if (continueButton) {
console.log(`找到继续沟通按钮 (${selector}),点击...`);
break;
}
}
if (continueButton) {
console.log('开始点击继续沟通按钮...');
continueButton.click();
console.log('已点击继续沟通按钮,系统会自动发送问候消息');
// 简单等待一下确保按钮点击成功
yield sleep(500);
console.log('成功点击继续沟通按钮');
}
else {
console.log('未找到任何沟通按钮,跳过打招呼');
}
}
}
else {
// 关闭简历详情,返回到列表页
console.log('关闭简历详情...');
yield closeResumeDetail();
}
}
else {
console.log(`无法打开第 ${i + 1} 个候选人简历,跳过`);
}
// 在浏览下一个简历前等待
yield sleep(2000);
}
console.log(`已完成 ${browseCount} 个候选人简历的浏览`);
}
catch (error) {
console.error('浏览候选人简历过程中发生错误', error);
}
});
}
/**
* 自动向候选人点击打招呼按钮
*/
function autoGreet() {
return __awaiter(this, void 0, void 0, function* () {
try {
console.log('开始自动点击打招呼按钮...');
// 等待加载
yield sleep(1000);
// 查找打招呼或继续沟通按钮
const greetButtonSelectors = [
'button.btn-greet',
'.button-chat button',
'button.btn-sure-v2',
'button[class*="greet"]',
'.button-chat-wrap button.btn-continue',
'.button-chat-wrap button',
'button.chat-btn',
'button.btn-continue',
'.btn-continue',
'.button-list button',
'[class*="button-chat"] button'
];
const doc = getIframeDocument() || document;
let greetButton = null;
for (const selector of greetButtonSelectors) {
greetButton = doc.querySelector(selector);
if (greetButton) {
console.log(`找到打招呼按钮 (${selector}),点击...`);
break;
}
}
if (greetButton) {
console.log('开始点击打招呼按钮...');
greetButton.click();
console.log('已点击打招呼按钮,系统会自动发送问候消息');
// 简单等待一下确保按钮点击成功
yield sleep(500);
console.log('成功点击打招呼按钮');
return true;
}
else {
console.log('未找到打招呼按钮,尝试寻找继续沟通按钮');
// 尝试找到"继续沟通"按钮
const continueButtonSelectors = [
'.btn-continue',
'button.btn-continue',
'.button-chat-wrap button.btn-continue',
'.button-list .btn-continue-wrap button',
'[class*="button-chat"] button.btn-continue'
];
let continueButton = null;
for (const selector of continueButtonSelectors) {
continueButton = doc.querySelector(selector);
if (continueButton) {
console.log(`找到继续沟通按钮 (${selector}),点击...`);
break;
}
}
if (continueButton) {
console.log('开始点击继续沟通按钮...');
continueButton.click();
console.log('已点击继续沟通按钮,系统会自动发送问候消息');
// 简单等待一下确保按钮点击成功
yield sleep(500);
console.log('成功点击继续沟通按钮');
return true;
}
else {
console.log('未找到任何沟通按钮,无法自动打招呼');
return false;
}
}
}
catch (error) {
console.error('自动点击打招呼按钮过程中发生错误:', error);
return false;
}
});
}
// 处理简历和建议回复
function handleResumeAndSuggestReplies() {
return __awaiter(this, void 0, void 0, function* () {
try {
// 提取简历信息
const resumeInfo = yield extractResumeDetails();
if (!resumeInfo) {
console.log('无法提取简历信息');
return;
}
// 根据简历信息生成回复建议
console.log('根据简历生成回复建议');
// 这里可以添加生成回复建议的逻辑
// 例如根据技能、经验等生成相关的问候语
// 将建议回复显示在界面上
// ... 实现显示建议回复的代码
}
catch (error) {
console.error('处理简历和生成回复建议时发生错误:', error);
}
});
}
/**
* 发送消息
* @param message 要发送的消息内容
* @returns 是否发送成功
*/
function sendMessage(message) {
return __awaiter(this, void 0, void 0, function* () {
try {
// 获取文档对象(主文档或iframe)
const iframeDoc = getIframeDocument();
const doc = iframeDoc || document;
// 查找聊天输入框
const inputElement = doc.querySelector('textarea[placeholder*="发送"], textarea.chat-input, [contenteditable="true"][class*="input"]');
if (!inputElement) {
console.error('未找到聊天输入框');
return false;
}
// 根据元素类型设置消息内容
if (inputElement instanceof HTMLTextAreaElement) {
inputElement.value = message;
// 触发input事件
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
}
else if (inputElement.getAttribute('contenteditable') === 'true') {
inputElement.textContent = message;
// 触发input事件
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
}
else {
console.error('无法识别的输入元素类型');
return false;
}
// 等待输入完成
yield sleep(500);
// 查找发送按钮
const sendButton = doc.querySelector('button[class*="send"], .send-btn, button.chat-btn');
if (!sendButton) {
console.error('未找到发送按钮');
return false;
}
// 点击发送按钮
sendButton.click();
// 等待发送完成
yield sleep(500);
console.log('消息发送成功');
return true;
}
catch (error) {
console.error('发送消息时发生错误', error);
return false;
}
});
}
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry needs to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
(() => {
var exports = __webpack_exports__;
var __webpack_unused_export__;
// ==UserScript==
// @name BOSS直聘智能助手
// @namespace http://tampermonkey.net/
// @version 0.1.0
// @description 自动筛选简历、分析简历并智能打招呼的BOSS直聘助手
// @author Your Name
// @match https://www.zhipin.com/*
// @icon https://www.zhipin.com/favicon.ico
// @grant none
// @connect api.openai.com
// @connect *
// @license MIT
// ==/UserScript==
__webpack_unused_export__ = ({ value: true });
const ui_1 = __webpack_require__(60);
const auto_1 = __webpack_require__(357);
// 直接执行代码,不等待window.load事件
console.log('BOSS直聘智能助手脚本已加载');
// 定义一个全局变量表示脚本是否已初始化
let scriptInitialized = false;
/**
* 主入口函数
*/
function main() {
// 避免重复初始化
if (scriptInitialized) {
console.log('脚本已初始化,跳过重复执行');
return;
}
scriptInitialized = true;
console.log('BOSS直聘智能助手初始化...');
console.log('当前URL:', window.location.href);
console.log('文档准备状态:', document.readyState);
// 判断是否为候选人列表页
if (isRecommendPage()) {
console.log('检测到推荐候选人页面,准备加载控制面板');
try {
// 创建控制面板
(0, ui_1.createControlPanel)();
// 初始化自动处理功能
(0, auto_1.initAutoProcess)();
console.log('BOSS直聘智能助手已启动');
}
catch (error) {
console.error('BOSS直聘智能助手启动失败:', error);
}
}
else {
console.log('当前页面不是推荐候选人页面,不加载控制面板');
}
}
/**
* 判断当前页面是否为推荐候选人页面
* @returns 是否为推荐页面
*/
function isRecommendPage() {
const url = window.location.href;
console.log('检查URL是否为推荐页面:', url);
// 检查多种可能的URL模式
return (url.includes('web/chat/recommend') ||
url.includes('web/boss/recommend') ||
url.includes('web/geek/recommend') ||
url.includes('boss/recommend') ||
url.includes('geek/recommend') ||
url.includes('brc/') ||
url.includes('bossguide/') ||
url.includes('bosspage/') ||
url.includes('bpc_geek_rcmd') ||
// 检查DOM中是否有候选人列表相关元素
!!document.querySelector('.candidate-recommend, .candidate-list, .recommend-list, .card-list, .card-inner'));
}
// 尝试多种方式启动脚本
// 1. 当DOM内容加载完成时执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
}
else {
// 如果DOM已经加载完成,立即执行
main();
}
// 2. 当页面完全加载完成时执行(包括图片等资源)
window.addEventListener('load', main);
// 3. 定时检查页面,处理SPA应用
setTimeout(main, 1000);
setTimeout(main, 3000);
// 监听URL变化
let lastUrl = window.location.href;
const observer = new MutationObserver(() => {
if (lastUrl !== window.location.href) {
lastUrl = window.location.href;
console.log('URL已变化,重新检查页面:', lastUrl);
setTimeout(main, 1000);
}
});
observer.observe(document, { subtree: true, childList: true });
// 暴露给油猴使用
(function () {
'use strict';
// 这里不需要额外代码,因为上面已经注册了各种事件
})();
})();
/******/ })()
;