Greasy Fork

Greasy Fork is available in English.

BOSS海投助手

👔 求职者的效率神器!由🧑‍💻Yangshengzhou开发,🚀 专为提升BOSS直聘的简历投递效率,支持自动批量发送简历,助您高效求职 💼✨

当前为 2025-05-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
// ==/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 = `
                <h3 style="margin:0; color:#2c3e50; font-weight:660;">
                    <span style="color:${CONFIG.COLORS.PRIMARY};">BOSS</span>海投助手
                </h3>
                <span style="font-size:0.8em; color:${CONFIG.COLORS.NEUTRAL};">v1.1-Beta (Professional)</span>
            `;

            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 = `<div style="color:${CONFIG.COLORS.NEUTRAL}; margin-bottom:8px;">海投助手,工作我有。</div>`;
            });

            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 = `
                <div style="color:${CONFIG.COLORS.NEUTRAL}; margin-bottom:8px;">
                    启动前请先BOSS筛选岗位列表。海投助手,工作我有!
                </div>
            `;
            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);
})();