// ==UserScript== // @name 觅星小臣 - BOSS职位收集器(本地版) // @namespace mifox-job-collector // @version 1.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(); } })();