// ==UserScript== // @name BOSS求职速投助手 // @namespace https://gitee.com/yourname // @version 2.0.1 // @description BOSS求职速投助手 - 高效求职利器,一键批量投递简历,智能沟通HR,让求职更高效! // @author YourName // @icon https://se1.360simg.com/t11e5ce71a48f30a7a5525c7dd5.png // @match https://www.zhipin.com/web/geek/jobs* // @match https://zhipin.com/web/geek/jobs* // @grant none // @license MIT // ==/UserScript== (() => { 'use strict'; // ==================== 配置参数 ==================== const AppConfig = Object.freeze({ timing: { loopWait: 1000, actionWait: 1200, quickPause: 30, shortPause: 200 }, animation: { duration: 300, debounce: 300 }, storeLink: 'https://microsoftedge.microsoft.com/addons/detail/boss%E5%BF%AB%E6%8A%95/eliaelcadhfbmeijgflobgkkomolaoog' }); // ==================== 运行时状态 ==================== const AppState = { active: false, pointer: 0, jobs: [], minimized: false, history: new Set(), jobTitles: [] }; // ==================== 元素引用 ==================== const Refs = { container: null, triggerBtn: null, console: null, floatBtn: null }; // ==================== 辅助工具 ==================== const Utils = { sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }, rgbFromHex(hex) { const clean = hex.replace('#', ''); const num = parseInt(clean, 16); return `${(num >> 16) & 255}, ${(num >> 8) & 255}, ${num & 255}`; }, timestamp() { return new Date().toLocaleTimeString(); } }; // ==================== 样式管理 ==================== const StyleManager = { applyTheme() { const root = document.documentElement; root.style.setProperty('--main-blue', '#2563eb'); root.style.setProperty('--light-bg', '#f8fafc'); root.style.setProperty('--soft-blue', '#dbeafe'); root.style.setProperty('--gray-text', '#64748b'); root.style.setProperty('--blue-rgb', Utils.rgbFromHex('#2563eb')); }, injectScrollbarStyles() { if (document.getElementById('custom-scrollbar-style')) return; const styleTag = document.createElement('style'); styleTag.id = 'custom-scrollbar-style'; styleTag.textContent = ` #zp-helper-box::-webkit-scrollbar, #zp-helper-box *::-webkit-scrollbar, #zp-console::-webkit-scrollbar { width: 6px !important; height: 6px !important; } #zp-helper-box::-webkit-scrollbar-track, #zp-helper-box *::-webkit-scrollbar-track, #zp-console::-webkit-scrollbar-track { background: transparent !important; } #zp-helper-box::-webkit-scrollbar-thumb, #zp-helper-box *::-webkit-scrollbar-thumb, #zp-console::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15) !important; border-radius: 3px !important; } #zp-helper-box::-webkit-scrollbar-thumb:hover, #zp-helper-box *::-webkit-scrollbar-thumb:hover, #zp-console::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.25) !important; } #zp-helper-box, #zp-helper-box *, #zp-console, #zp-console * { scrollbar-width: thin !important; scrollbar-color: rgba(0,0,0,0.15) transparent !important; } `; document.head.appendChild(styleTag); } }; // ==================== 界面构建 ==================== const UIManager = { buildInterface() { StyleManager.applyTheme(); StyleManager.injectScrollbarStyles(); this.renderPanel(); this.renderFloatButton(); }, renderPanel() { const existing = document.getElementById('zp-helper-box'); if (existing) existing.remove(); Refs.container = document.createElement('div'); Refs.container.id = 'zp-helper-box'; Refs.container.className = 'zp-panel-root'; Object.assign(Refs.container.style, { position: 'fixed', top: '32px', right: '20px', width: 'clamp(300px, 80vw, 380px)', borderRadius: '16px', padding: '14px', fontFamily: "'Segoe UI', system-ui, sans-serif", zIndex: '2147483647', display: 'flex', flexDirection: 'column', transition: 'all 0.3s ease', background: '#ffffff', boxShadow: '0 8px 24px rgba(var(--blue-rgb), 0.12)', border: '1px solid var(--soft-blue)', cursor: 'default' }); const sections = [ this.buildHeader(), this.buildControlArea(), this.buildConsole(), this.buildFooter() ]; sections.forEach(el => Refs.container.appendChild(el)); document.body.appendChild(Refs.container); this.enableDrag(Refs.container); }, buildHeader() { const header = document.createElement('div'); header.className = 'zp-panel-header'; Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '0 10px 15px', marginBottom: '15px', borderBottom: '1px solid var(--soft-blue)' }); const titleArea = this.composeTitle(); const btnGroup = document.createElement('div'); Object.assign(btnGroup.style, { display: 'flex', gap: '8px', alignItems: 'center' }); const storeBtn = this.composeStoreBtn(); const minBtn = this.composeIconBtn('✕', () => this.minimize(), '收起面板'); btnGroup.append(storeBtn, minBtn); header.append(titleArea, btnGroup); return header; }, composeTitle() { const wrapper = document.createElement('div'); Object.assign(wrapper.style, { display: 'flex', alignItems: 'center', gap: '10px' }); const iconBox = document.createElement('div'); iconBox.innerHTML = ` `; Object.assign(iconBox.style, { width: '40px', height: '40px', background: 'var(--main-blue)', borderRadius: '10px', display: 'flex', justifyContent: 'center', alignItems: 'center', boxShadow: '0 2px 8px rgba(var(--blue-rgb), 0.3)' }); const textCol = document.createElement('div'); const heading = document.createElement('h3'); heading.innerHTML = 'BOSS速投助手'; Object.assign(heading.style, { margin: '0', color: '#2c3e50', fontWeight: '600', fontSize: '1.2rem' }); const sub = document.createElement('span'); sub.textContent = '快速投递,轻松求职'; Object.assign(sub.style, { fontSize: '0.8em', color: 'var(--gray-text)' }); textCol.append(heading, sub); wrapper.append(iconBox, textCol); return wrapper; }, composeStoreBtn() { const link = document.createElement('a'); link.href = AppConfig.storeLink; link.target = '_blank'; link.rel = 'noopener'; link.title = '访问扩展商店下载正式版'; link.className = 'zp-circle-btn'; link.innerHTML = ``; Object.assign(link.style, { width: '30px', height: '30px', borderRadius: '50%', border: 'none', background: 'var(--soft-blue)', cursor: 'pointer', display: 'flex', justifyContent: 'center', alignItems: 'center', textDecoration: 'none' }); const path = link.querySelector('path'); path.setAttribute('fill', '#2563eb'); link.addEventListener('mouseenter', () => { link.style.background = 'var(--main-blue)'; path.setAttribute('fill', '#ffffff'); }); link.addEventListener('mouseleave', () => { link.style.background = 'var(--soft-blue)'; path.setAttribute('fill', '#2563eb'); }); return link; }, composeIconBtn(symbol, handler, tooltip) { const btn = document.createElement('button'); btn.className = 'zp-circle-btn'; btn.innerHTML = symbol; btn.title = tooltip; Object.assign(btn.style, { width: '30px', height: '30px', borderRadius: '50%', border: 'none', background: 'var(--soft-blue)', cursor: 'pointer', fontSize: '15px', transition: 'all 0.2s ease', display: 'flex', justifyContent: 'center', alignItems: 'center', color: 'var(--main-blue)' }); btn.addEventListener('click', handler); btn.addEventListener('mouseenter', () => { btn.style.background = 'var(--main-blue)'; btn.style.color = '#fff'; }); btn.addEventListener('mouseleave', () => { btn.style.background = 'var(--soft-blue)'; btn.style.color = 'var(--main-blue)'; }); return btn; }, buildControlArea() { const wrapper = document.createElement('div'); wrapper.className = 'zp-control-section'; Object.assign(wrapper.style, { marginBottom: '12px', padding: '0 8px' }); const card = document.createElement('div'); Object.assign(card.style, { background: 'var(--light-bg)', borderRadius: '12px', padding: '14px' }); Refs.triggerBtn = this.composeActionButton('开启速投', 'var(--main-blue)', () => { Controller.toggle(); }); card.appendChild(Refs.triggerBtn); wrapper.appendChild(card); return wrapper; }, composeActionButton(label, color, handler) { const btn = document.createElement('button'); btn.className = 'zp-primary-btn'; btn.textContent = label; Object.assign(btn.style, { width: '100%', padding: '11px 16px', background: color, color: '#fff', border: 'none', borderRadius: '10px', cursor: 'pointer', fontSize: '14px', fontWeight: '500', transition: 'all 0.2s ease', display: 'flex', justifyContent: 'center', alignItems: 'center', boxShadow: '0 2px 8px rgba(0,0,0,0.08)' }); btn.addEventListener('mouseenter', () => { btn.style.boxShadow = '0 6px 15px rgba(var(--blue-rgb), 0.3)'; }); btn.addEventListener('mouseleave', () => { btn.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)'; }); btn.addEventListener('click', handler); return btn; }, buildConsole() { Refs.console = document.createElement('div'); Refs.console.id = 'zp-console'; Refs.console.className = 'zp-log-area'; Object.assign(Refs.console.style, { height: '260px', overflowY: 'auto', background: 'var(--light-bg)', borderRadius: '12px', padding: '12px', fontSize: '13px', lineHeight: '1.5', marginBottom: '15px', marginLeft: '10px', marginRight: '10px', transition: 'all 0.3s ease', userSelect: 'text' }); return Refs.console; }, buildFooter() { const footer = document.createElement('div'); footer.className = 'zp-panel-footer'; Object.assign(footer.style, { textAlign: 'center', fontSize: '0.8em', color: 'var(--gray-text)', paddingTop: '15px', borderTop: '1px solid var(--soft-blue)', marginTop: 'auto' }); footer.textContent = `© ${new Date().getFullYear()} YourName · All Rights Reserved`; return footer; }, enableDrag(target) { const handle = target.querySelector('.zp-panel-header'); if (!handle) return; handle.style.cursor = 'move'; let dragging = false; let startX, startY, originX, originY; handle.addEventListener('mousedown', e => { dragging = true; startX = e.clientX; startY = e.clientY; originX = target.offsetLeft; originY = target.offsetTop; target.style.transition = 'none'; target.style.zIndex = '2147483647'; }); document.addEventListener('mousemove', e => { if (!dragging) return; target.style.left = `${originX + e.clientX - startX}px`; target.style.top = `${originY + e.clientY - startY}px`; target.style.right = 'auto'; }); document.addEventListener('mouseup', () => { if (dragging) { dragging = false; target.style.transition = 'all 0.3s ease'; target.style.zIndex = '2147483646'; } }); }, minimize() { AppState.minimized = true; Refs.container.style.transform = 'translateY(160%)'; Refs.floatBtn.style.display = 'flex'; }, renderFloatButton() { const sideEntry = document.querySelector('.zp-side-entry'); Refs.floatBtn = document.createElement('div'); Refs.floatBtn.id = 'zp-float-entry'; Refs.floatBtn.title = 'BOSS速投助手'; Object.assign(Refs.floatBtn.style, { width: '48px', height: '48px', cursor: 'pointer', display: 'none', justifyContent: 'center', alignItems: 'center', transition: 'all 0.3s ease', position: 'relative' }); const inner = document.createElement('div'); inner.className = 'zp-float-inner'; inner.innerHTML = ` `; Object.assign(inner.style, { width: '48px', height: '48px', background: 'var(--main-blue)', borderRadius: '50%', boxShadow: '0 4px 12px rgba(var(--blue-rgb), 0.4)', display: 'flex', justifyContent: 'center', alignItems: 'center', overflow: 'hidden' }); Refs.floatBtn.appendChild(inner); Refs.floatBtn.addEventListener('mouseenter', () => { inner.style.boxShadow = '0 6px 16px rgba(var(--blue-rgb), 0.5)'; }); Refs.floatBtn.addEventListener('mouseleave', () => { inner.style.boxShadow = '0 4px 12px rgba(var(--blue-rgb), 0.4)'; }); Refs.floatBtn.addEventListener('click', () => { AppState.minimized = false; Refs.container.style.transform = 'translateY(0)'; Refs.floatBtn.style.display = 'none'; }); if (sideEntry) { const entryWrapper = document.createElement('div'); entryWrapper.className = 'zp-side-entry-question side-entry zp-float-entry-wrap'; Object.assign(entryWrapper.style, { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1px' }); const innerWrap = document.createElement('div'); innerWrap.className = 'zp-side-second'; Object.assign(innerWrap.style, { display: 'flex', flexDirection: 'column', gap: '1px' }); Refs.floatBtn.className = 'zp-side-entry2'; innerWrap.appendChild(Refs.floatBtn); entryWrapper.appendChild(innerWrap); sideEntry.appendChild(entryWrapper); } else { Refs.floatBtn.style.position = 'fixed'; Refs.floatBtn.style.right = '30px'; Refs.floatBtn.style.bottom = '150px'; Refs.floatBtn.style.zIndex = '2147483647'; document.body.appendChild(Refs.floatBtn); } } }; // ==================== 业务逻辑 ==================== const Controller = { cache: {}, getElement(selector, refresh = false) { if (refresh || !this.cache[selector]) { this.cache[selector] = document.querySelector(selector); } return this.cache[selector]; }, async run() { while (AppState.active) { await this.scanJobs(); await Utils.sleep(AppConfig.timing.loopWait); } }, identifyCard(card) { const nameEl = card.querySelector('.job-name'); return nameEl ? nameEl.href : card.innerText.slice(0, 50); }, getUnprocessedCards() { const cards = Array.from(document.querySelectorAll('li.job-card-box')); return cards.filter(card => { const id = this.identifyCard(card); return !AppState.history.has(id); }); }, async scanJobs() { const freshCards = this.getUnprocessedCards(); if (freshCards.length === 0) { const before = document.querySelectorAll('li.job-card-box').length; window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); await Utils.sleep(AppConfig.timing.loopWait * 2); const after = document.querySelectorAll('li.job-card-box').length; const retry = this.getUnprocessedCards(); if (after <= before && retry.length === 0) { this.complete(); AppState.history.clear(); AppState.jobTitles = []; return; } return; } const target = freshCards[0]; const id = this.identifyCard(target); AppState.history.add(id); const title = target.querySelector('.job-name')?.textContent?.trim() || ''; if (title) { AppState.jobTitles.push(title); } target.scrollIntoView({ behavior: 'smooth', block: 'center' }); target.click(); await Utils.sleep(AppConfig.timing.actionWait * 2); this.printLog(`正在沟通:已处理${AppState.history.size}个职位${title ? `,当前:"${title}"` : ''}`); const chatLink = document.querySelector('a.op-btn-chat'); if (chatLink) { const text = chatLink.textContent.trim(); if (text === '立即沟通') { chatLink.click(); await this.handleModal(); } } }, async handleModal() { await Utils.sleep(AppConfig.timing.actionWait * 4); const modalBtns = [...document.querySelectorAll('.default-btn.cancel-btn')]; const stayBtn = modalBtns.find(b => b.textContent.trim() === '留在此页'); if (stayBtn) { stayBtn.click(); await Utils.sleep(AppConfig.timing.actionWait * 2); } }, complete() { AppState.active = false; Refs.triggerBtn.textContent = '开始投递'; Refs.triggerBtn.style.background = 'var(--main-blue)'; this.printLog('全部职位沟通完成,祝您早日拿到心仪offer!'); AppState.pointer = 0; }, printLog(msg) { const entry = `[${Utils.timestamp()}] ${msg}`; const panel = document.querySelector('#zp-console'); if (panel) { const line = document.createElement('div'); line.className = 'zp-log-line'; line.style.padding = '0px 8px'; line.textContent = entry; panel.appendChild(line); panel.scrollTop = panel.scrollHeight; } }, toggle() { AppState.active = !AppState.active; if (AppState.active) { AppState.jobs = []; AppState.history.clear(); AppState.jobTitles = []; Refs.triggerBtn.textContent = '停止速投'; Refs.triggerBtn.style.background = '#ef4444'; const panel = document.querySelector('#zp-console'); if (panel) panel.innerHTML = ''; const now = new Date(); this.printLog(`速投已启动,当前时间:${now.toLocaleTimeString()}`); this.printLog('投递规则:按顺序与当前页面所有职位沟通'); this.run(); } else { Refs.triggerBtn.textContent = '开启速投'; Refs.triggerBtn.style.background = 'var(--main-blue)'; AppState.active = false; AppState.pointer = 0; AppState.history.clear(); } } }; // ==================== 启动入口 ==================== function bootstrap() { if (!location.pathname.startsWith('/web/geek/jobs')) return; UIManager.buildInterface(); document.body.style.position = 'relative'; Controller.printLog('欢迎使用 BOSS速投助手(社区版)'); } window.addEventListener('load', bootstrap); })();