// ==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);
})();