// ==UserScript==
// @name 觅星小臣 - BOSS职位收集器(本地版)
// @namespace https://github.com/job-collector
// @version 4.0.0
// @description 小臣求职助手 - 收集BOSS直聘职位详情,直接传回觅星本地桌面版(无需登录)
// @author AI Assistant
// @match https://www.zhipin.com/job_detail/*.html*
// @match https://www.zhipin.com/web/geek/job*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-idle
// @connect localhost
// @connect 127.0.0.1
// ==/UserScript==
(function () {
"use strict";
const SERVER_URL = 'http://localhost:5001';
const CONFIG = {
API: {
BASE_URL: SERVER_URL,
COLLECT_JOB: '/api/jobs/collect',
},
STORAGE_KEY_SESSION: 'job_collector_session',
ANALYTICS_ENDPOINT: '/api/analytics/event',
DEBUG: true
};
const Utils = {
log(message, type = 'info') {
if (!CONFIG.DEBUG) return;
const styles = {
info: 'color: #4285f4',
success: 'color: #34a853',
warning: 'color: #fbbc04',
error: 'color: #ea4335'
};
console.log(`%c[觅星小臣] ${message}`, styles[type] || styles.info);
},
cleanText(text) {
if (!text) return '';
return text.replace(/\s+/g, ' ').trim();
},
generateId(text) {
let hash = 0;
for (let i = 0; i < text.length; i++) {
const char = text.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return `job_${Math.abs(hash).toString(16)}`;
}
};
const AnalyticsTracker = {
_sessionId: null,
getSessionId() {
if (!this._sessionId) {
this._sessionId = GM_getValue(CONFIG.STORAGE_KEY_SESSION, null);
if (!this._sessionId) {
this._sessionId = 'userscript_' + Date.now() + '_' + Math.random().toString(36).slice(2, 9);
GM_setValue(CONFIG.STORAGE_KEY_SESSION, this._sessionId);
}
}
return this._sessionId;
},
track(eventType, metadata, category) {
try {
GM_xmlhttpRequest({
method: 'POST',
url: `${CONFIG.API.BASE_URL}${CONFIG.ANALYTICS_ENDPOINT}`,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify([{
event_type: eventType,
category: category || 'script',
page: window.location.href,
metadata: metadata || {},
session_id: this.getSessionId(),
client_ts: Date.now()
}]),
timeout: 3000,
onload: () => {},
onerror: () => {},
ontimeout: () => {}
});
} catch (e) {}
}
};
function shouldSkipDialog() {
const referrer = document.referrer || '';
if (['localhost', '127.0.0.1', 'seeking-stars', 'career-ai'].some(d => referrer.includes(d))) {
Utils.log('从觅星跳转,自动跳过收集');
return true;
}
if (window.location.hash.includes('__tracker_skip__')) {
history.replaceState(null, '', window.location.href.replace(/#__tracker_skip__.*$/, ''));
return true;
}
return false;
}
function showConfirmDialog(jobData) {
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.id = 'job-collector-overlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:100000;display:flex;align-items:center;justify-content:center';
const dialog = document.createElement('div');
dialog.style.cssText = 'background:#fff;border-radius:12px;padding:24px;max-width:420px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,0.3);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif';
dialog.innerHTML = `
职位${jobData.jobName || '未知职位'}
公司${jobData.companyName || '未知公司'}
薪资${jobData.salary || '面议'}
规模${jobData.companyScale || '-'}
提示:添加后可进行简历匹配分析
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
dialog.querySelector('#jc-confirm').addEventListener('click', () => {
document.body.removeChild(overlay);
AnalyticsTracker.track('script:confirm_accept', { jobName: jobData.jobName, companyName: jobData.companyName }, 'script');
resolve(true);
});
dialog.querySelector('#jc-cancel').addEventListener('click', () => {
document.body.removeChild(overlay);
resolve(false);
});
overlay.addEventListener('click', (e) => {
if (e.target === overlay) { document.body.removeChild(overlay); resolve(false); }
});
});
}
const JobCollector = {
isCollected: false,
async collect() {
Utils.log('========== 开始收集 ==========');
AnalyticsTracker.track('script:collection_start', { url: window.location.href }, 'script');
showStatus('正在收集职位数据...', 'info');
try {
const jobData = {
...this.extractBasicInfo(),
...this.extractCompanyInfo(),
...this.extractJD(),
source: 'boss_zhipin',
sourceUrl: window.location.href,
collectedAt: Date.now(),
status: 'interested'
};
Utils.log('职位: ' + jobData.jobName + ' | 公司: ' + jobData.companyName + ' | 薪资: ' + jobData.salary);
if (shouldSkipDialog()) { showStatus('已在追踪中', 'info'); return; }
showStatus('等待确认...', 'info');
const confirmed = await showConfirmDialog(jobData);
if (!confirmed) { showStatus('已取消', 'info'); return; }
showStatus('正在发送...', 'info');
await this.sendToBackend(jobData);
this.isCollected = true;
Utils.log('========== 收集完成 ==========', 'success');
AnalyticsTracker.track('script:collection_complete', { jobName: jobData.jobName, companyName: jobData.companyName }, 'script');
showStatus('已发送到觅星!', 'success');
} catch (error) {
Utils.log('收集失败: ' + error.message, 'error');
showStatus('收集失败: ' + error.message, 'error');
console.error(error);
}
},
extractBasicInfo() {
let jobName = '', salary = '', companyName = '';
if (window._jobInfo) {
jobName = window._jobInfo.job_name || '';
salary = window._jobInfo.job_salary || '';
companyName = window._jobInfo.company || '';
}
if (!jobName) jobName = document.querySelector('.job-title')?.textContent?.trim() || document.title.match(/「(.+?)招聘」/)?.[1] || '';
if (!salary) salary = document.querySelector('.badge')?.textContent?.trim() || document.querySelector('.salary')?.textContent?.trim() || '';
if (!companyName) companyName = document.querySelector('.info')?.childNodes[0]?.textContent?.trim() || document.querySelector('.sider-company .company-info a')?.textContent?.trim() || '';
const location = document.querySelector('.location-address')?.textContent?.trim() || '';
const jdText = document.querySelector('.job-sec-text')?.textContent || '';
let experience = '', education = '';
const expMatch = jdText.match(/(\d+-\d+年|\d+年以上|在校\/应届|经验不限)/);
if (expMatch) experience = expMatch[1];
const eduMatch = jdText.match(/(本科|大专|硕士|博士|学历不限)/);
if (eduMatch) education = eduMatch[1];
return {
id: Utils.generateId(jobName + '-' + companyName + '-' + Date.now()),
jobName: Utils.cleanText(jobName),
companyName: Utils.cleanText(companyName),
salary: Utils.cleanText(salary),
location: Utils.cleanText(location),
experience, education
};
},
extractCompanyInfo() {
const result = { companyStage: '', companyScale: '', companyIndustry: '' };
const siderCompany = document.querySelector('.sider-company');
if (siderCompany) {
siderCompany.querySelectorAll('p').forEach(p => {
const text = p.textContent.trim();
if (p.querySelector('.icon-stage')) result.companyStage = text;
else if (p.querySelector('.icon-scale')) result.companyScale = text;
else if (p.querySelector('.icon-industry')) result.companyIndustry = text;
});
}
const businessInfo = {};
const businessBox = document.querySelector('.business-info-box');
if (businessBox) {
businessBox.querySelectorAll('.level-list li').forEach(li => {
const label = li.querySelector('span')?.textContent?.trim();
const value = li.childNodes[li.childNodes.length - 1]?.textContent?.trim();
if (label && value) {
if (label.includes('公司名称')) businessInfo.companyName = value;
if (label.includes('成立日期')) businessInfo.establishDate = value;
if (label.includes('企业类型')) businessInfo.companyType = value;
if (label.includes('经营状态')) businessInfo.operatingStatus = value;
}
});
}
return { ...result, businessInfo };
},
extractJD() {
const result = { jd: '', jobResponsibilities: '', jobRequirements: '' };
const jdElement = document.querySelector('.job-sec-text');
if (jdElement) {
const fullText = jdElement.textContent.trim();
result.jd = fullText;
const patterns = [
/岗位职责[::]?\s*([\s\S]*?)(?:任职要求|岗位要求|任职资格|职位要求)[::]?\s*([\s\S]*)/i,
/职位描述[::]?\s*([\s\S]*?)(?:任职要求|岗位要求|任职资格|职位要求)[::]?\s*([\s\S]*)/i,
/工作职责[::]?\s*([\s\S]*?)(?:任职要求|岗位要求|任职资格|职位要求)[::]?\s*([\s\S]*)/i
];
for (const pattern of patterns) {
const match = fullText.match(pattern);
if (match) { result.jobResponsibilities = Utils.cleanText(match[1]); result.jobRequirements = Utils.cleanText(match[2]); break; }
}
}
return result;
},
sendToBackend(data) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: CONFIG.API.BASE_URL + CONFIG.API.COLLECT_JOB,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify(data),
timeout: 10000,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
try { resolve(JSON.parse(response.responseText)); }
catch (e) { reject(new Error('解析响应失败')); }
} else {
reject(new Error('服务器错误 (' + response.status + ')'));
}
},
onerror: function() { reject(new Error('网络错误,请确认觅星已启动')); },
ontimeout: function() { reject(new Error('请求超时,请确认觅星已启动')); }
});
});
}
};
function showStatus(message, type) {
type = type || 'info';
var indicator = document.getElementById('job-collector-status');
if (!indicator) {
indicator = document.createElement('div');
indicator.id = 'job-collector-status';
indicator.style.cssText = 'position:fixed;bottom:20px;right:20px;padding:12px 20px;border-radius:8px;font-size:14px;font-weight:500;z-index:99999;box-shadow:0 4px 12px rgba(0,0,0,0.15);transition:all 0.3s ease;max-width:300px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif';
document.body.appendChild(indicator);
}
var colors = { info: { bg: '#4285f4', text: '#fff' }, success: { bg: '#34a853', text: '#fff' }, warning: { bg: '#fbbc04', text: '#000' }, error: { bg: '#ea4335', text: '#fff' } };
var color = colors[type] || colors.info;
indicator.style.backgroundColor = color.bg;
indicator.style.color = color.text;
indicator.textContent = '[觅星小臣] ' + message;
indicator.style.display = 'block';
clearTimeout(indicator._timeout);
indicator._timeout = setTimeout(function() {
indicator.style.opacity = '0';
setTimeout(function() { indicator.style.display = 'none'; indicator.style.opacity = '1'; }, 300);
}, 3000);
}
function init() {
Utils.log('========== 觅星收集器 v4.0 已加载 ==========');
Utils.log('服务器: ' + CONFIG.API.BASE_URL);
if (!(location.pathname.includes('/job_detail/') && location.pathname.includes('.html'))) {
Utils.log('非职位详情页,不启动', 'warning');
return;
}
Utils.log('检测到职位详情页', 'success');
showStatus('觅星收集器已就绪', 'info');
setTimeout(function() { JobCollector.collect(); }, 2000);
var lastUrl = location.href;
new MutationObserver(function() {
if (location.href !== lastUrl) {
lastUrl = location.href;
if (!location.pathname.includes('/job_detail/') || !location.pathname.includes('.html')) return;
JobCollector.isCollected = false;
Utils.log('页面切换,重新收集...');
showStatus('页面变化,重新收集...', 'info');
setTimeout(function() { JobCollector.collect(); }, 2000);
}
}).observe(document, { subtree: true, childList: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();