// ==UserScript==
// @name BOSS海投助手
// @namespace https://github.com/yangshengzhou03
// @version 1.1.1
// @description 👔 求职者的效率神器!由🧑💻Yangshengzhou开发,🚀 专为提升BOSS直聘的简历投递效率,支持自动批量发送简历,助您高效求职 💼✨
// @author Yangshengzhou
// @match https://www.zhipin.com/web/*
// @grant none
// @run-at document-idle
// @supportURL https://github.com/yangshengzhou03
// @homepageURL https://gitee.com/yangshengzhou
// @license MIT
// @icon https://static.zhipin.com/favicon.ico
// @connect zhipin.com
// @noframes
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
// 配置项
const CONFIG = {
INTERVAL: 2500, // 自动化操作的时间间隔(毫秒)
CARD_STYLE: {
BACKGROUND: '#ffffff', // 面板背景颜色
SHADOW: '0 6px 18px rgba(0,0,0,0.12)', // 面板阴影效果
BORDER: '1px solid #e4e7ed' // 面板边框样式
},
COLORS: {
PRIMARY: '#2196f3', // 主色调(蓝色)
SECONDARY: '#ff5722', // 次要色调(橙色)
NEUTRAL: '#95a5a6' // 中性色(灰色)
},
MINI_ICON_SIZE: 40 // 悬浮球图标大小(像素)
};
// 状态管理
const state = {
isRunning: false, // 是否正在运行自动化流程
currentIndex: 0, // 当前处理的岗位索引
filterKeyword: '', // 用户输入的过滤关键词
locationKeyword: '', // 地区关键词
jobList: [], // 收集到的所有岗位数据
isMinimized: false // 是否处于最小化状态
};
// DOM元素引用
const elements = {
panel: null, // 控制面板元素
controlBtn: null, // 启动/暂停按钮
log: null, // 日志输出区域
filterInput: null, // 过滤关键词输入框
miniIcon: null // 最小化后的悬浮图标
};
const UI = {
createControlPanel() {
if (document.getElementById('boss-pro-panel')) return;
elements.panel = this._createPanel();
const header = this._createHeader();
const controls = this._createControls();
elements.log = this._createLogger();
const footer = this._createFooter();
elements.panel.append(header, controls, elements.log, footer);
document.body.appendChild(elements.panel);
this._makeDraggable(elements.panel);
},
_createPanel() {
const panel = document.createElement('div');
panel.id = 'boss-pro-panel';
panel.className = 'boss-pro-panel';
panel.style.cssText = `
position: fixed;
top: 36px;
right: 24px;
width: 380px;
background: linear-gradient(145deg, #ffffff, #f9f9fc);
border-radius: 16px;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
padding: 18px;
font-family: 'Segoe UI', system-ui, sans-serif;
z-index: 2147483647;
cursor: move;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
`;
return panel;
},
_createHeader() {
const header = document.createElement('div');
header.className = 'boss-header';
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
`;
const title = document.createElement('div');
title.innerHTML = `
BOSS海投助手
v1.1-Beta (Professional)
`;
const closeBtn = this._createIconButton('✕', () => {
state.isMinimized = true;
elements.panel.style.transform = 'translateY(160%)';
elements.miniIcon.style.display = 'flex';
});
closeBtn.style.color = CONFIG.COLORS.NEUTRAL;
header.append(title, closeBtn);
return header;
},
_createControls() {
const container = document.createElement('div');
container.className = 'boss-controls';
container.style.marginBottom = '0.8rem';
// 岗位标签和输入框
const jobLabel = document.createElement('label');
jobLabel.textContent = '岗位名称关键词:';
jobLabel.style.cssText = 'display:block; margin-bottom:0.5rem;';
elements.filterInput = document.createElement('input');
elements.filterInput.id = 'job-filter';
elements.filterInput.placeholder = '职位关键词(逗号分隔,为空则不限制)';
elements.filterInput.className = 'boss-filter-input';
elements.filterInput.style.cssText = `
width: calc(100%);
padding: 12px 16px;
border-radius: 10px;
border: 2px solid #ddd;
margin-bottom: 1rem;
font-size: 14px;
transition: border-color 0.3s ease;
outline: none;
`;
elements.filterInput.addEventListener('focus', () => {
elements.filterInput.style.borderColor = CONFIG.COLORS.PRIMARY;
});
elements.filterInput.addEventListener('blur', () => {
elements.filterInput.style.borderColor = '#ddd';
});
// 地区标签和输入框
const locationLabel = document.createElement('label');
locationLabel.textContent = '地区:';
locationLabel.style.cssText = 'display:block; margin-bottom:0.5rem;';
elements.locationInput = document.createElement('input');
elements.locationInput.id = 'location-filter';
elements.locationInput.placeholder = '岗位地区(为空则不限制)';
elements.locationInput.className = 'boss-filter-input';
elements.locationInput.style.cssText = `
width: calc(100%);
padding: 12px 16px;
border-radius: 10px;
border: 2px solid #ddd;
margin-bottom: 1rem;
font-size: 14px;
transition: border-color 0.3s ease;
outline: none;
`;
elements.locationInput.addEventListener('focus', () => {
elements.locationInput.style.borderColor = CONFIG.COLORS.PRIMARY;
});
elements.locationInput.addEventListener('blur', () => {
elements.locationInput.style.borderColor = '#ddd';
});
// 控制按钮
elements.controlBtn = this._createTextButton('启动海投', `linear-gradient(45deg, ${CONFIG.COLORS.PRIMARY}, #4db6ac)`, () => {
toggleProcess();
});
// 工具按钮组
const utilGroup = document.createElement('div');
utilGroup.className = 'boss-util-group';
utilGroup.style.cssText = `
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 1rem;
`;
const clearLogBtn = this._createIconButton('🗑', () => {
elements.log.innerHTML = `海投助手,工作我有。
`;
});
const settingsBtn = this._createIconButton('⚙', () => {
window.open('https://gitee.com/Yangshengzhou', '_blank')
});
container.append(jobLabel, elements.filterInput, locationLabel, elements.locationInput, elements.controlBtn, utilGroup);
utilGroup.append(clearLogBtn, settingsBtn);
return container;
},
_createLogger() {
const log = document.createElement('div');
log.id = 'pro-log';
log.className = 'boss-log';
log.style.cssText = `
height: 200px;
overflow-y: auto;
background: #f8f9fa;
border-radius: 10px;
padding: 12px;
border: 1px solid #eceff1;
font-size: 13px;
line-height: 1.5;
margin-bottom: 1rem;
transition: all 0.3s ease;
user-select: text;
`;
log.innerHTML = `
启动前请先BOSS筛选岗位列表。海投助手,工作我有!
`;
return log;
},
_createFooter() {
const footer = document.createElement('div');
footer.textContent = '© 2025 Yangshengzhou · All Rights Reserved';
footer.style.cssText = `
text-align: center;
font-size: 0.8em;
color: ${CONFIG.COLORS.NEUTRAL};
padding-top: 10px;
border-top: 1px solid #eee;
margin-top: auto;
transition: color 0.3s ease;
`;
return footer;
},
_createTextButton(text, bgColor, onClick) {
const btn = document.createElement('button');
btn.className = 'boss-btn';
btn.textContent = text;
btn.style.cssText = `
width: 100%;
padding: 12px 16px;
background: ${bgColor};
color: #fff;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 15px;
font-weight: 500;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
`;
btn.addEventListener('click', onClick);
btn.addEventListener('mouseenter', () => {
btn.style.transform = 'scale(1.03)';
btn.style.boxShadow = '0 6px 15px rgba(0,0,0,0.15)';
});
btn.addEventListener('mouseleave', () => {
btn.style.transform = 'scale(1)';
btn.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
});
return btn;
},
_createIconButton(icon, onClick) {
const btn = document.createElement('button');
btn.className = 'boss-icon-btn';
btn.innerHTML = icon;
btn.style.cssText = `
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: #f0f0f0;
cursor: pointer;
font-size: 18px;
transition: all 0.2s ease;
display: flex;
justify-content: center;
align-items: center;
color: ${CONFIG.COLORS.PRIMARY};
transform: translateY(8px); /* 视觉微调:向下移动8px以补偿视觉中心偏上的错觉 */
`;
btn.addEventListener('click', onClick);
btn.addEventListener('mouseenter', () => {
btn.style.backgroundColor = CONFIG.COLORS.PRIMARY;
btn.style.color = '#fff';
btn.style.transform = 'translateY(8px) scale(1.1)';
});
btn.addEventListener('mouseleave', () => {
btn.style.backgroundColor = '#f0f0f0';
btn.style.color = CONFIG.COLORS.PRIMARY;
btn.style.transform = 'translateY(8px)';
});
return btn;
},
_makeDraggable(panel) {
const draggableAreaHeightRatio = 0.3; // 可拖动区域占面板高度的30%
panel.addEventListener('mousemove', (e) => {
const rect = panel.getBoundingClientRect();
const relativeY = e.clientY - rect.top;
const draggableHeight = rect.height * draggableAreaHeightRatio;
if (relativeY <= draggableHeight) {
panel.style.cursor = 'move';
} else {
panel.style.cursor = 'default';
}
});
let isDragging = false;
let startX = 0, startY = 0;
let initialX = panel.offsetLeft, initialY = panel.offsetTop;
panel.addEventListener('mousedown', (e) => {
const rect = panel.getBoundingClientRect();
const relativeY = e.clientY - rect.top;
const draggableHeight = rect.height * draggableAreaHeightRatio;
if (relativeY <= draggableHeight) {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initialX = panel.offsetLeft;
initialY = panel.offsetTop;
panel.style.transition = 'none';
}
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
panel.style.left = `${initialX + dx}px`;
panel.style.top = `${initialY + dy}px`;
panel.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
panel.style.transition = 'all 0.3s ease';
}
});
},
createMiniIcon() {
elements.miniIcon = document.createElement('div');
elements.miniIcon.style.cssText = `
width: ${CONFIG.MINI_ICON_SIZE}px;
height: ${CONFIG.MINI_ICON_SIZE}px;
position: fixed;
bottom: 40px;
left: 40px;
background: linear-gradient(135deg, ${CONFIG.COLORS.PRIMARY}, #4db6ac);
border-radius: 50%;
box-shadow: 0 6px 16px rgba(33, 150, 243, 0.4);
cursor: pointer;
display: none;
justify-content: center;
align-items: center;
color: #fff;
font-size: 18px;
z-index: 2147483647;
transition: all 0.3s ease;
overflow: hidden;
text-align: center;
line-height: 1;
`;
elements.miniIcon.innerHTML = '↗';
elements.miniIcon.addEventListener('mouseenter', () => {
elements.miniIcon.style.transform = 'scale(1.1)';
elements.miniIcon.style.boxShadow = '0 8px 20px rgba(33, 150, 243, 0.5)';
});
elements.miniIcon.addEventListener('mouseleave', () => {
elements.miniIcon.style.transform = 'scale(1)';
elements.miniIcon.style.boxShadow = '0 6px 16px rgba(33, 150, 243, 0.4)';
});
elements.miniIcon.addEventListener('click', () => {
state.isMinimized = false;
elements.panel.style.transform = 'translateY(0)';
elements.miniIcon.style.display = 'none';
});
document.body.appendChild(elements.miniIcon);
}
};
// 核心代码
const Core = {
async startProcessing() {
if (location.pathname.includes('/jobs')) {
await this.autoScrollJobList();
}
while (state.isRunning) {
if (location.pathname.includes('/jobs')) {
await this.processJobList();
} else if (location.pathname.includes('/chat')) {
await this.handleChatPage();
}
await this.delay(CONFIG.INTERVAL);
}
},
// 自动滚动
async autoScrollJobList() {
return new Promise((resolve) => {
const cardSelector = 'li.job-card-box'; // 写死选择器
const maxHistory = 3; // 最多记录3次
const waitTime = 800; // 滚动间隔时间
let cardCountHistory = [];
let isStopped = false;
const scrollStep = async () => {
if (isStopped) return;
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior: 'smooth'
});
await this.delay(waitTime);
const cards = document.querySelectorAll(cardSelector);
const currentCount = cards.length;
cardCountHistory.push(currentCount);
if (cardCountHistory.length > maxHistory) {
cardCountHistory.shift();
}
if (
cardCountHistory.length === maxHistory &&
new Set(cardCountHistory).size === 1
) {
this.log("职位卡片加载完成(已拉到底),开始沟通");
resolve(cards);
return;
}
scrollStep(); // 继续滚动
};
scrollStep();
// 提供外部停止接口
this.stopAutoScroll = () => {
isStopped = true;
resolve(null);
};
});
},
// 点击Job页面的立即沟通按钮
async processJobList() {
state.jobList = Array.from(document.querySelectorAll('li.job-card-box'))
.filter(card => {
const title = card.querySelector('.job-name')?.textContent?.toLowerCase() || '';
const location = card.querySelector('.company-location')?.textContent?.toLowerCase().trim() || '';
// 岗位名称匹配
const jobMatch = state.filterKeyword ?
state.filterKeyword.split(',').some(kw => title.includes(kw.trim())) :
true;
// 地区匹配(模糊包含)
const locationMatch = state.locationKeyword ?
state.locationKeyword.split(',').some(kw => location.includes(kw.trim())) :
true;
return jobMatch && locationMatch;
});
if (!state.jobList.length) {
this.log('未找到符合条件的职位');
toggleProcess();
return;
}
if (state.currentIndex >= state.jobList.length) {
this.resetCycle();
return;
}
const currentCard = state.jobList[state.currentIndex];
currentCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
currentCard.click();
this.log(`正在沟通:${++state.currentIndex}/${state.jobList.length}`);
await this.delay(600);
const chatBtn = document.querySelector('a.op-btn-chat');
if (chatBtn) {
const btnText = chatBtn.textContent.trim();
if (btnText === '立即沟通') {
chatBtn.click();
await this.handleGreetingModal();
}
}
},
async handleGreetingModal() {
await this.delay(800);
const btn = [...document.querySelectorAll('.default-btn.cancel-btn')].find(b => b.textContent.trim() === '留在此页');
if (btn) {
btn.click();
await this.delay(500);
}
},
async handleChatPage() {
const chatList = await this.waitForElement('ul');
if (!chatList) {
this.log('未找到聊天列表');
return;
}
const observer = new MutationObserver(async () => {
await this.clickLatestChat();
});
observer.observe(chatList, { childList: true });
await this.clickLatestChat();
},
getLatestChatLi() {
return document.querySelector('li[role="listitem"][class]:has(.friend-content-warp)');
},
async clickLatestChat() {
try {
const latestLi = await this.waitForElement(this.getLatestChatLi);
if (!latestLi) return;
if (latestLi.classList.contains('last-clicked')) return;
// 获取聊天人姓名和公司信息
const nameEl = latestLi.querySelector('.name-text');
const companyEl = latestLi.querySelector('.name-box span:nth-child(2)');
const name = nameEl ? nameEl.textContent : '未知';
const company = companyEl ? companyEl.textContent : '';
this.log(`介绍并发送简历: ${name}${company ? ' - ' + company : ''}`);
const avatar = latestLi.querySelector('.figure');
await this.simulateClick(avatar);
latestLi.classList.add('last-clicked');
await this.processChatContent();
} catch (error) {
this.log(`自我介绍时出错: ${error.message}`);
}
},
async processChatContent() {
try {
await this.delay(500);
const dictBtn = await this.waitForElement('.btn-dict');
if (!dictBtn) {
this.log('未找到常用语按钮');
return;
}
await this.simulateClick(dictBtn);
await this.delay(500);
const dictList = await this.waitForElement('ul[data-v-8e790d94=""]');
if (!dictList) {
this.log('未找到常用语列表');
return;
}
const dictItems = dictList.querySelectorAll('li');
if (!dictItems || dictItems.length === 0) {
this.log('常用语列表为空');
return;
}
for (let i = 0; i < dictItems.length; i++) {
const item = dictItems[i];
this.log(`发送常用语 ${i + 1}/${dictItems.length}`);
await this.simulateClick(item);
await this.delay(600);
}
const resumeBtn = await this.waitForElement('div[d-c="62009"]');
if (!resumeBtn) {
this.log('无法发送简历');
return;
}
if (resumeBtn.classList.contains('unable')) {
this.log('对方未回复,您无权发送简历');
return;
}
await this.simulateClick(resumeBtn);
await this.delay(500);
const confirmBtn = await this.waitForElement('span.btn-sure-v2');
if (!confirmBtn) {
return;
}
await this.simulateClick(confirmBtn);
} catch (error) {
this.log(`处理出错: ${error.message}`);
}
},
async simulateClick(element) {
if (!element) return;
const rect = element.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
const dispatchMouseEvent = (type, options) => {
const event = new MouseEvent(type, {
bubbles: true,
cancelable: true,
view: window,
clientX: x,
clientY: y,
...options
});
element.dispatchEvent(event);
};
dispatchMouseEvent('mouseover');
await this.delay(50);
dispatchMouseEvent('mousemove');
await this.delay(50);
dispatchMouseEvent('mousedown', { button: 0 });
await this.delay(50);
dispatchMouseEvent('mouseup', { button: 0 });
await this.delay(50);
dispatchMouseEvent('click', { button: 0 });
},
async waitForElement(selectorOrFunction, timeout = 5000) {
return new Promise((resolve) => {
if (typeof selectorOrFunction === 'function') {
const element = selectorOrFunction();
if (element) return resolve(element);
} else {
const element = document.querySelector(selectorOrFunction);
if (element) return resolve(element);
}
// 设置超时
const timeoutId = setTimeout(() => {
observer.disconnect();
resolve(null);
}, timeout);
const observer = new MutationObserver(() => {
let element;
if (typeof selectorOrFunction === 'function') {
element = selectorOrFunction();
} else {
element = document.querySelector(selectorOrFunction);
}
if (element) {
clearTimeout(timeoutId);
observer.disconnect();
resolve(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
},
async delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
resetCycle() {
toggleProcess();
this.log('所有列表职位沟通结束,请充值。');
state.currentIndex = 0;
state.lastMessageTime = 0;
},
log(message) {
const logEntry = `[${new Date().toLocaleTimeString()}] ${message}`;
const logPanel = document.querySelector('#pro-log');
if (logPanel) {
const logItem = document.createElement('div');
logItem.className = 'log-item';
logItem.textContent = logEntry;
logPanel.appendChild(logItem);
logPanel.scrollTop = logPanel.scrollHeight;
}
}
};
function toggleProcess() {
state.isRunning = !state.isRunning;
if (state.isRunning) {
state.filterKeyword = elements.filterInput.value.trim().toLowerCase();
state.locationKeyword = elements.locationInput.value.trim().toLowerCase(); // 新增地区
elements.controlBtn.textContent = '停止海投';
elements.controlBtn.style.backgroundColor = CONFIG.COLORS.SECONDARY;
Core.startProcessing();
} else {
elements.controlBtn.textContent = '启动海投';
elements.controlBtn.style.backgroundColor = CONFIG.COLORS.PRIMARY;
state.isRunning = false;
}
}
function init() {
UI.createControlPanel();
UI.createMiniIcon();
document.body.style.position = 'relative';
if (location.pathname.includes('/jobs')) {
window.open('https://www.zhipin.com/web/geek/chat', '_blank');
} else if (location.pathname.includes('/chat')) {
alert("在 BOSS 聊天界面中,海投可以继续替您沟通。\n请点击 [启动海投] 按钮以开始!");
}
}
window.addEventListener('load', init);
})();