// ==UserScript== // @name 2026最新鱼泡直聘批量投递助手 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 为鱼泡直聘提供自动化投递功能 // @author 啧啧泽 (QQ: 2169595741) // @match https://www.yupao.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // @tag 鱼泡直聘 自动投递 求职助手 // ==/UserScript== (function() { 'use strict'; const bypassAntiDebug = () => { try { const _constructor = window.Function.prototype.constructor; const newConstructor = function() { if (arguments && typeof arguments[0] === 'string' && arguments[0].includes('debugger')) { return function() {}; } return _constructor.apply(this, arguments); }; window.Function.prototype.constructor = newConstructor; const _eval = window.eval; window.eval = function(code) { if (typeof code === 'string' && code.includes('debugger')) { return _eval(code.replace(/debugger/g, '')); } return _eval(code); }; const _setInterval = window.setInterval; window.setInterval = function(fn, delay) { if (typeof fn === 'function' && fn.toString().includes('debugger')) { return null; } return _setInterval(fn, delay); }; const _toString = Function.prototype.toString; Function.prototype.toString = function() { if (this === newConstructor) return _toString.call(_constructor); if (this === window.eval) return _toString.call(_eval); if (this === window.setInterval) return _toString.call(_setInterval); return _toString.call(this); }; Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth }); Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight }); console.log('%c[鱼泡助手]%c 系统已就绪', 'color:#007bff;font-weight:bold;', 'color:default;'); } catch (e) { } }; bypassAntiDebug(); // --- 样式定义 --- GM_addStyle(` #yupao-helper-panel { position: fixed; top: 20px; right: 20px; width: 350px; background: #fff; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.12); z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; border: 1px solid #eaeaea; overflow: hidden; display: flex; flex-direction: column; } .yp-header { background: #007bff; color: #fff; padding: 12px 16px; cursor: move; display: flex; justify-content: space-between; align-items: center; } .yp-header-btns { display: flex; align-items: center; gap: 10px; } .yp-header-btn { cursor: pointer; font-size: 18px; line-height: 1; transition: 0.2s; } .yp-header-btn:hover { color: #ccc; } /* 最小化后的悬浮按钮 */ #yp-restore-btn { position: fixed; bottom: 20px; left: 20px; width: 50px; height: 50px; background: #007bff; color: #fff; border-radius: 50%; display: none; justify-content: center; align-items: center; box-shadow: 0 4px 12px rgba(0,0,0,0.2); cursor: pointer; z-index: 999999; font-weight: bold; font-size: 12px; text-align: center; border: 2px solid #fff; } #yp-restore-btn:hover { transform: scale(1.1); background: #0056b3; } .yp-tabs { display: flex; border-bottom: 1px solid #eee; background: #f8f9fa; position: relative; } .yp-tab { flex: 1; padding: 12px; text-align: center; cursor: pointer; font-size: 13px; color: #666; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .yp-tab.active { color: #007bff; font-weight: bold; background: #fff; } .yp-tab-indicator { position: absolute; bottom: 0; left: 0; height: 2px; background: #007bff; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .yp-content { padding: 16px; max-height: 450px; overflow-y: auto; flex: 1; scroll-behavior: smooth; } .yp-section { display: none; animation: ypSlideIn 0.3s ease-out; } @keyframes ypSlideIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } .yp-section.active { display: block; } .yp-form-item { margin-bottom: 16px; } .yp-label { display: block; font-size: 12px; color: #666; margin-bottom: 4px; } .yp-input { width: 100%; padding: 10px; border: 1px solid #e0e0e0; border-radius: 8px; box-sizing: border-box; font-size: 13px; outline: none; transition: 0.3s; background: #fafafa; } .yp-input:focus { border-color: #007bff; background: #fff; box-shadow: 0 0 0 3px rgba(0,123,255,0.1); } /* 优化的单选按钮样式 */ .yp-radio-group { display: flex; background: #f1f3f5; padding: 4px; border-radius: 10px; gap: 4px; } .yp-radio-item { flex: 1; position: relative; } .yp-radio-item input { position: absolute; opacity: 0; cursor: pointer; height: 0; width: 0; } .yp-radio-label { display: block; padding: 8px; text-align: center; font-size: 12px; color: #495057; cursor: pointer; border-radius: 8px; transition: 0.2s; } .yp-radio-item input:checked + .yp-radio-label { background: #fff; color: #007bff; font-weight: bold; box-shadow: 0 2px 6px rgba(0,0,0,0.05); } /* 速度选择器专属颜色 */ #speed-fast:checked + .yp-radio-label { color: #dc3545; } #speed-medium:checked + .yp-radio-label { color: #007bff; } #speed-slow:checked + .yp-radio-label { color: #28a745; } /* 教程弹窗样式 */ #yp-tutorial-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 320px; background: #fff; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); z-index: 1000000; display: none; flex-direction: column; overflow: hidden; animation: ypFadeIn 0.2s ease-out; border: 1px solid #eee; } @keyframes ypFadeIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } } .yp-modal-header { background: #fff; padding: 12px 16px; font-weight: bold; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; font-size: 14px; } .yp-modal-body { padding: 16px; font-size: 12px; line-height: 1.5; color: #555; max-height: 300px; overflow-y: auto; } .yp-modal-body b { color: #007bff; } .yp-modal-footer { padding: 10px 16px; text-align: right; background: #fafafa; border-top: 1px solid #f0f0f0; } .yp-tutorial-btn-head { font-size: 14px; background: rgba(255,255,255,0.2); width: 20px; height: 20px; display: flex; justify-content: center; align-items: center; border-radius: 50%; cursor: pointer; transition: 0.2s; font-weight: bold; } .yp-tutorial-btn-head:hover { background: rgba(255,255,255,0.4); } .yp-btn { width: 100%; padding: 12px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 14px; transition: 0.3s; display: flex; justify-content: center; align-items: center; gap: 6px; } .yp-btn-primary { background: linear-gradient(135deg, #007bff, #0056b3); color: #fff; box-shadow: 0 4px 12px rgba(0,123,255,0.2); } .yp-btn-primary:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(0,123,255,0.3); } .yp-btn-outline { background: #fff; color: #007bff; border: 1.5px solid #007bff; } .yp-btn-outline:hover { background: #f8f9ff; } .yp-log { font-size: 11px; background: #1e1e1e; border-radius: 10px; padding: 10px; height: 140px; overflow-y: auto; color: #00ff00; line-height: 1.5; font-family: 'Consolas', monospace; border: 1px solid #333; } .yp-vip-tag { font-size: 10px; background: #ffc107; color: #000; padding: 2px 4px; border-radius: 3px; margin-left: 4px; } #btn-clear-img:hover { background: #fff5f5 !important; border-color: #dc3545 !important; box-shadow: 0 2px 4px rgba(220,53,69,0.1); } .yp-switch { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } /* 简易开关 */ .switch { position: relative; display: inline-block; width: 34px; height: 20px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 20px; } .slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #007bff; } input:checked + .slider:before { transform: translateX(14px); } `); // --- 混淆与工具函数 --- const Utils = { // 使用更稳健的 Base64 编解码 decode: (str) => { try { return decodeURIComponent(escape(atob(str))); } catch (e) { return ''; } }, encode: (str) => { try { return btoa(unescape(encodeURIComponent(str))); } catch (e) { return ''; } }, sleep: (ms) => new Promise(r => setTimeout(r, ms + Math.random() * 500)), // 减小随机波动,提高节奏感 simulateClick: function(el) { if (!el) return; try { // 某些环境下 window 对象作为 view 参数会报错,这里尝试更稳健的触发方式 const opts = { bubbles: true, cancelable: true, view: window.unsafeWindow || window }; el.dispatchEvent(new MouseEvent('mousedown', opts)); el.dispatchEvent(new MouseEvent('mouseup', opts)); el.click(); } catch (e) { // 降级处理:如果构造事件失败,直接调用原生点击 if (typeof el.click === 'function') { el.click(); } } }, log: (msg, type = 'info') => { const logBox = document.getElementById('yp-log'); if (!logBox) return; const div = document.createElement('div'); const colors = { info: '#00ff00', warn: '#ffc107', error: '#dc3545', success: '#28a745', system: '#17a2b8' }; div.style.cssText = `color: ${colors[type] || colors.info}; margin-bottom: 2px; border-left: 2px solid transparent; padding-left: 4px; transition: 0.3s;`; div.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; // 进场动画 div.style.opacity = '0'; div.style.transform = 'translateX(-5px)'; logBox.appendChild(div); requestAnimationFrame(() => { div.style.opacity = '1'; div.style.transform = 'translateX(0)'; }); logBox.scrollTop = logBox.scrollHeight; if (logBox.children.length > 100) logBox.removeChild(logBox.firstChild); }, makeDraggable: (el) => { const header = el.querySelector('.yp-header'); let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; header.onmousedown = (e) => { if (e.target.closest('.yp-header-btns')) return; // 避开按钮 e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = () => { document.onmouseup = null; document.onmousemove = null; }; document.onmousemove = (e) => { e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; el.style.top = (el.offsetTop - pos2) + "px"; el.style.left = (el.offsetLeft - pos1) + "px"; el.style.bottom = 'auto'; // 清除定位冲突 el.style.right = 'auto'; }; }; }, syncRemoteConfig: async () => { if (!CONFIG.security.remoteConfigUrl) return; return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: CONFIG.security.remoteConfigUrl, timeout: 5000, onload: (res) => { try { const remote = JSON.parse(res.responseText); if (remote.buyLinks) CONFIG.security.encodedBuyLinks = remote.buyLinks; if (remote.contactQQ) CONFIG.security.contactQQ = remote.contactQQ; } catch (e) {} resolve(); }, onerror: () => resolve() }); }); }, verifyCard: (c, p) => { try { const _0x4f2a = c.split('_'); if (_0x4f2a.length !== 2 || _0x4f2a[1].length !== 16) return false; const _0x1b3c = _0x4f2a[1]; const _0x5e9d = _0x1b3c.substring(0, 12); const _0x2d1f = _0x1b3c.substring(12); const _0x3a1b = Utils.decode("ZVhWd1lXOWZNakF5TkY5elpXTjFjbVZmYTJWNQ=="); const _0x7c8e = _0x5e9d + _0x3a1b + p; let _0x9f0a = 0; for (let i = 0; i < _0x7c8e.length; i++) { _0x9f0a = ((_0x9f0a << 5) - _0x9f0a) + _0x7c8e.charCodeAt(i); _0x9f0a |= 0; } const _0x6b4d = Math.abs(_0x9f0a).toString(36).substring(0, 4).toUpperCase(); return _0x2d1f.toUpperCase() === _0x6b4d; } catch (e) { return false; } }, handleActivation: () => { const cardInput = document.getElementById('ipt-card'); const card = cardInput.value.trim(); if (!card) return alert('请输入激活码'); if (Store.settings.activatedCards[card]) { const usedTime = new Date(Store.settings.activatedCards[card]).toLocaleString(); return alert(`⚠️ 该卡密已被使用过!\n\n激活时间:${usedTime}\n请勿重复使用同一个卡密。`); } let plan = null; if (card.startsWith('yp1d_') && Utils.verifyCard(card, '1d')) { plan = { d: 86400000, t: '1天' }; } else if (card.startsWith('yp3d_') && Utils.verifyCard(card, '3d')) { plan = { d: 86400000 * 5, t: '5天' }; } else if (card.startsWith('ypforever_') && Utils.verifyCard(card, 'forever')) { plan = { d: -1, t: '永久' }; } if (plan) { let startTime = Date.now(); if (Store.checkVip() && Store.settings.vipExpire !== -1) { startTime = Store.settings.vipExpire; } const expireTime = plan.d === -1 ? -1 : startTime + plan.d; Store.settings.activatedCards[card] = Date.now(); Store.set('activatedCards', Store.settings.activatedCards); Store.setVip(true, expireTime); const introField = document.getElementById('txt-selfIntro'); if (introField) { introField.disabled = false; introField.placeholder = "请输入自动打招呼的内容..."; introField.parentElement.style.opacity = "1"; introField.parentElement.style.cursor = "default"; } alert(`🎉 ${plan.t}授权激活成功!\n卡密校验通过,已成功绑定\n到期时间:${expireTime === -1 ? '永久有效' : new Date(expireTime).toLocaleString()}`); location.reload(); } else { alert(`❌ 卡密无效或格式错误!\n\n该卡密不属于本店铺生成的“真实卡密”。\n请确保从官方店铺购买,且不要随意修改卡密内容。\n\n如有疑问请联系QQ:${CONFIG.security.contactQQ}`); } } }; // --- 配置管理 --- const CONFIG = { security: { encodedBuyLinks: [ "aHR0cHM6Ly9wYXkubGR4cC5jbi9zaG9wL3pleWlnb3UvdXRtMXp6", "aHR0cHM6Ly9wYXkubGR4cC5jbi9zaG9wL3pleWlnb3UvaXFlY3Zh", "aHR0cHM6Ly9wYXkubGR4cC5jbi9zaG9wL3pleWlnb3UvcHN6b2k3" ], contactQQ: "2169595741", remoteConfigUrl: "" }, selectors: { jobItem: 'div.acbad', chatBtn: 'div.abdhe.abdhj', continueChatBtn: 'div.abdhe', sendAttachmentBtn: 'div.abdhe.abdhi', hrActive: '.job-detail-hr-info, .name, .active-status, .hr-info', chatEditor: 'div.fb-editor, .chat-input, #chat-editor, [contenteditable="true"]', sendBtn: 'button.btn-send, .send-btn, .fb-btn-send', chatListItem: 'div.adedi', chatHistoryMsg: '.extra-line, .msg-item, .chat-msg, .chat-message, .message-item', chatScrollArea: '.chat-message-list, .message-list, .chat-content', noMoreData: '.html, .ant-empty, div:contains("暂无更多数据")', }, pricing: [ { label: '1天', value: '1d', price: 2 }, { label: '5天', value: '3d', price: 8 }, { label: '永久', value: 'forever', price: 19.9 } ] }; const SPEED_MAP = { fast: { label: '🚀 极速', delay: 1000 }, medium: { label: '⚖️ 标准', delay: 2500 }, slow: { label: '🐢 稳健', delay: 5000 } }; // --- 数据存储 --- const Store = { settings: { keywords: GM_getValue('yp_keywords', ''), isVip: GM_getValue('yp_isVip', false), vipExpire: GM_getValue('yp_vipExpire', 0), vipSign: GM_getValue('yp_vipSign', ''), resumeMode: GM_getValue('yp_resumeMode', 'image'), imageResumeData: GM_getValue('yp_imageResumeData', null), imageResumeName: GM_getValue('yp_imageResumeName', ''), selfIntro: GM_getValue('yp_selfIntro', '你好,我对这个职位很感兴趣,希望能进一步沟通。'), excludeHeadhunter: GM_getValue('yp_excludeHeadhunter', true), hrActiveLimit: JSON.parse(GM_getValue('yp_hrActiveLimit', '["今日活跃"]')), activatedCards: JSON.parse(GM_getValue('yp_activatedCards', '{}')), processedJobs: JSON.parse(GM_getValue('yp_processedJobs', '[]')), deliverySpeed: GM_getValue('yp_deliverySpeed', 'medium'), autoDeliveryEnabled: GM_getValue('yp_autoDeliveryEnabled', true), deliveryLimit: 50, // 默认每次加载都重置为 50 dailyDeliveryCount: parseInt(GM_getValue('yp_dailyDeliveryCount', '0')), lastResetDate: GM_getValue('yp_lastResetDate', ''), trialData: JSON.parse(GM_getValue('yp_trialData', '{"c":0,"s":""}')), }, init: function() { // 默认每次加载脚本都重置投递上限为 50,防止缓存了之前的错误数值 this.settings.deliveryLimit = 50; // --- [测试专用:如需重置 VIP 和试用次数,请取消下面两行的注释,保存并刷新页面] --- // this.setVip(false, 0); // this.resetTrial(); // 每日计数重置逻辑 const today = new Date().toLocaleDateString(); if (this.settings.lastResetDate !== today) { this.set('dailyDeliveryCount', 0); this.set('lastResetDate', today); } if (this.settings.isVip && !this.checkVip()) { console.warn('[鱼泡助手] VIP 签名校验失败,已重置状态'); this.setVip(false, 0); } this.verifyTrialData(); }, _genTrialSign: function(count) { const salt = Utils.decode("dHJpYWxfc2FsdF94eXpfODg4"); let hash = 0; const str = `trial_${count}_${salt}`; for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash |= 0; } return Math.abs(hash).toString(16); }, verifyTrialData: function() { const data = this.settings.trialData; if (data.c > 0 && data.s !== this._genTrialSign(data.c)) { console.error('[鱼泡助手] 警告:检测到试用数据异常篡改'); data.c = 999; this.set('trialData', data); } }, getTrialInfo: function() { const limit = parseInt(Utils.decode("NQ==")); return { count: this.settings.trialData.c, limit: limit, isExpired: this.settings.trialData.c >= limit }; }, useTrial: function() { if (this.settings.isVip) return true; const info = this.getTrialInfo(); if (info.isExpired) return false; const newData = { c: this.settings.trialData.c + 1, s: this._genTrialSign(this.settings.trialData.c + 1) }; this.set('trialData', newData); return true; }, set: function(key, val) { const fullKey = key.startsWith('yp_') ? key : `yp_${key}`; const saveVal = (typeof val === 'object') ? JSON.stringify(val) : val; GM_setValue(fullKey, saveVal); const settingKey = key.replace('yp_', ''); if (settingKey in this.settings) this.settings[settingKey] = val; }, _genSign: (isVip, expire) => { const str = `yp_${isVip}_${expire}_v2_salt`; let hash = 0; for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash |= 0; } return (hash ^ 0xDEADBEEF).toString(16); }, setVip: function(isVip, expire) { this.set('isVip', isVip); this.set('vipExpire', expire); this.set('vipSign', this._genSign(isVip, expire)); }, checkVip: function() { const { isVip, vipExpire, vipSign } = this.settings; if (!isVip) return false; if (vipSign !== this._genSign(isVip, vipExpire)) return false; return vipExpire === -1 || Date.now() < vipExpire; }, getVipRemainingTime: function() { if (!this.checkVip()) return null; if (this.settings.vipExpire === -1) return '永久有效'; const remain = this.settings.vipExpire - Date.now(); if (remain <= 0) return null; const days = Math.floor(remain / 86400000); const hours = Math.floor((remain % 86400000) / 3600000); const mins = Math.floor((remain % 3600000) / 60000); const secs = Math.floor((remain % 60000) / 1000); let res = ''; if (days > 0) res += `${days}天`; res += `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; return res; }, addProcessedJob: function(id) { if (!this.settings.processedJobs.includes(id)) { this.settings.processedJobs.push(id); if (this.settings.processedJobs.length > 1000) this.settings.processedJobs.shift(); GM_setValue('yp_processedJobs', JSON.stringify(this.settings.processedJobs)); } }, clearProcessedJobs: function() { this.settings.processedJobs = []; GM_setValue('yp_processedJobs', '[]'); }, resetTrial: function() { const data = { c: 0, s: this._genTrialSign(0) }; this.set('trialData', data); } }; // --- UI 逻辑 --- const UI = { panel: null, restoreBtn: null, modal: null, create: function() { this.createTutorial(); this.createRestoreBtn(); this.createMainPanel(); this.setupEvents(); Utils.makeDraggable(this.panel); }, createTutorial: function() { const modal = document.createElement('div'); modal.id = 'yp-tutorial-modal'; modal.innerHTML = `
鱼泡海投助手 - 使用教程 & 注意事项 ×

1. 投递模式说明:

附件简历:点击页面上的“发送简历”按钮。发完后按钮变“继续聊”即视为成功。推荐使用此模式,稳定性最高。

图片简历:打开聊天框发送您上传的图片,并自动发送“自我介绍”文字。注意:由于平台限制,已沟通的企业不会自动隐藏,图片模式可能会因重复匹配而略显不够准确。

2. 关键注意事项:

保存配置:修改任何设置(关键字、简历图、速度等)后,必须点击【保存配置】按钮,否则刷新页面将丢失!

活跃度过滤:若设置了“3日内”,则“刚刚、昨日、2日内”等均符合;若HR从未在线,脚本会提示“活跃度不符”并跳过。

投递限制:达到设定的“每日上限”后脚本会自动停止,保护账号安全。


【免责声明】

1. 本脚本仅供学习交流,严禁用于违法行为。产生的一切后果由使用者承担。

2. 脚本不保证永久可用,如因平台更新导致失效,开发者会尽力修复但不作补偿保证。


Bug反馈与联系方式:
QQ:${CONFIG.security.contactQQ}

`; document.body.appendChild(modal); this.modal = modal; }, createRestoreBtn: function() { const btn = document.createElement('div'); btn.id = 'yp-restore-btn'; btn.innerHTML = '鱼泡
助手'; document.body.appendChild(btn); this.restoreBtn = btn; }, createMainPanel: function() { const isVip = Store.checkVip(); const trial = Store.getTrialInfo(); const panel = document.createElement('div'); panel.id = 'yupao-helper-panel'; panel.innerHTML = `
鱼泡海投助手 ${isVip ? 'VIP' : ''}
? ×
投递
高级
激活
${isVip ? `
💎 VIP 到期倒计时 ${Store.getVipRemainingTime()}
` : ''} ${!isVip && trial.isExpired ? `
🔒
免费试用已结束
5次免费试用已用完,请激活后继续使用
` : ''} ${!isVip && !trial.isExpired ? `
🎁 免费试用中 (仅限免费聊) ${trial.count}/5
` : ''}
[${new Date().toLocaleTimeString()}] 助手已就绪
复制日志
${!isVip ? `
🔒
配置已锁定
请激活 VIP 后解锁高级设置
` : ''}
自动投递模式
开启后自动发送附件或图片简历 (需VIP)
${Store.settings.imageResumeData ? '' : ''}
${(Store.settings.imageResumeData && Store.settings.imageResumeData.length > 100) ? `✅ 图片已就绪 ${Store.settings.imageResumeName ? `(${Store.settings.imageResumeName})` : ''}` : '⏳ 图片待上传'}
${Object.keys(SPEED_MAP).map(k => `
`).join('')}
例如选择“3日内”,则“刚刚、今日、昨日”均符合
过滤猎头职位
单次运行达到此数量后将自动停止
${CONFIG.pricing.map((p, i) => { const encoded = CONFIG.security.encodedBuyLinks[i]; return `
${p.label}
¥${p.price}
点击购买
`; }).join('')}
${isVip ? `✅ VIP 已激活
到期:${Store.settings.vipExpire === -1 ? '永久' : new Date(Store.settings.vipExpire).toLocaleDateString()}` : `❌ 未激活` }
`; document.body.appendChild(panel); this.panel = panel; this.updateTabIndicator(); }, updateTabIndicator: function() { const activeTab = this.panel.querySelector('.yp-tab.active'); const indicator = this.panel.querySelector('.yp-tab-indicator'); if (activeTab && indicator) { indicator.style.width = `${activeTab.offsetWidth}px`; indicator.style.left = `${activeTab.offsetLeft}px`; } }, setupEvents: function() { // VIP 倒计时定时器 if (Store.checkVip() && Store.settings.vipExpire !== -1) { setInterval(() => { const timerEl = document.getElementById('vip-timer'); if (timerEl) { const remain = Store.getVipRemainingTime(); if (remain) { timerEl.textContent = remain; } else { timerEl.textContent = '已到期'; location.reload(); // 到期自动刷新重置状态 } } }, 1000); } // Tab 切换 this.panel.querySelectorAll('.yp-tab').forEach(tab => { tab.onclick = () => { this.panel.querySelectorAll('.yp-tab').forEach(t => t.classList.remove('active')); this.panel.querySelectorAll('.yp-section').forEach(s => s.classList.remove('active')); tab.classList.add('active'); this.panel.querySelector(`#sec-${tab.dataset.tab}`).classList.add('active'); this.updateTabIndicator(); }; }); // 自动投递总开关 const chkAuto = document.getElementById('chk-autoDelivery'); if (chkAuto) { chkAuto.onchange = (e) => { const enabled = e.target.checked; const box = document.getElementById('box-delivery-methods'); box.style.display = enabled ? 'block' : 'none'; box.style.opacity = enabled ? '1' : '0.5'; }; } // 清除图片缓存 const btnClearImg = document.getElementById('btn-clear-img'); if (btnClearImg) { btnClearImg.onclick = () => { if (confirm('确定要清除缓存的图片简历吗?')) { Store.set('imageResumeData', null); Store.set('imageResumeName', ''); Utils.log('图片简历缓存已清除', 'info'); location.reload(); } }; } // 简历模式切换 this.panel.querySelectorAll('input[name="resumeMode"]').forEach(rad => { rad.onchange = (e) => { document.getElementById('box-image-upload').style.display = e.target.value === 'image' ? 'block' : 'none'; }; }); // 图片上传预览 const fileInput = document.getElementById('file-image'); if (fileInput) { fileInput.onchange = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (ev) => { Store.settings.imageResumeData = ev.target.result; Store.settings.imageResumeName = file.name; // 暂存文件名 const preview = document.getElementById('img-preview'); preview.style.background = '#eef5ff'; preview.innerHTML = `✅ 已加载: ${file.name} (点击保存)`; }; reader.readAsDataURL(file); } }; } // 保存设置 document.getElementById('btn-save-adv').onclick = async (e) => { const btn = e.target; btn.disabled = true; btn.textContent = '保存中...'; Store.set('yp_autoDeliveryEnabled', document.getElementById('chk-autoDelivery').checked); Store.set('yp_resumeMode', this.panel.querySelector('input[name="resumeMode"]:checked').value); Store.set('yp_deliverySpeed', this.panel.querySelector('input[name="deliverySpeed"]:checked').value); Store.set('yp_selfIntro', document.getElementById('txt-selfIntro').value); Store.set('yp_excludeHeadhunter', document.getElementById('chk-excludeHeadhunter').checked); Store.set('yp_keywords', document.getElementById('ipt-keywords').value); Store.set('yp_deliveryLimit', parseInt(document.getElementById('ipt-deliveryLimit').value) || 50); const hrActive = document.getElementById('sel-hrActive').value; Store.set('yp_hrActiveLimit', hrActive === '不限' ? [] : [hrActive]); if (Store.settings.imageResumeData && Store.settings.imageResumeData.length > 100) { Store.set('imageResumeData', Store.settings.imageResumeData); Store.set('imageResumeName', Store.settings.imageResumeName || '已保存的图片'); } Utils.log('✅ 设置已保存', 'success'); await Utils.sleep(500); btn.disabled = false; btn.textContent = '保存配置'; }; // 复制日志 document.getElementById('btn-copy-log').onclick = () => { const logs = Array.from(document.querySelectorAll('#yp-log div')).map(d => d.textContent).join('\n'); navigator.clipboard.writeText(logs).then(() => { const btn = document.getElementById('btn-copy-log'); const oldText = btn.textContent; btn.textContent = '已复制!'; setTimeout(() => btn.textContent = oldText, 1000); }); }; // 运行控制 document.getElementById('btn-toggle-run').onclick = () => { if (Scanner.isRunning) { Scanner.stop(); } else { // 启动前强制保存一下当前页面的设置(特别是自我介绍) const intro = document.getElementById('txt-selfIntro'); const keywords = document.getElementById('ipt-keywords'); if (intro) Store.set('yp_selfIntro', intro.value); if (keywords) Store.set('yp_keywords', keywords.value); Scanner.start(); } }; // 试用结束后的跳转按钮 this.panel.querySelectorAll('.yp-btn-to-activation').forEach(btn => { btn.onclick = () => { const tab = this.panel.querySelector('.yp-tab[data-tab="activation"]'); if (tab) tab.click(); }; }); // 激活逻辑 document.getElementById('btn-activate').onclick = Utils.handleActivation; // 购买跳转绑定 this.panel.querySelectorAll('.buy-btn-trigger').forEach(btn => { btn.onclick = () => { const encoded = btn.dataset.link; const url = Utils.decode(encoded); if (url) { window.open(url, '_blank'); } else { alert('❌ 购买链接失效,请联系QQ:' + CONFIG.security.contactQQ); } }; }); // 去激活跳转 const toActivation = document.querySelectorAll('.yp-btn-to-activation'); toActivation.forEach(btn => { btn.onclick = () => { document.querySelector('.yp-tab[data-tab="activation"]').click(); }; }); // 窗口控制 document.getElementById('yp-min').onclick = () => { this.panel.style.display = 'none'; this.restoreBtn.style.display = 'flex'; }; this.restoreBtn.onclick = () => { this.panel.style.display = 'flex'; this.restoreBtn.style.display = 'none'; this.updateTabIndicator(); }; document.getElementById('btn-tutorial-head').onclick = () => this.modal.style.display = 'flex'; this.modal.querySelector('.yp-modal-close').onclick = this.modal.querySelector('.yp-modal-ok').onclick = () => this.modal.style.display = 'none'; document.getElementById('yp-close').onclick = () => { if (confirm('关闭助手并停止运行?')) { Scanner.stop(); this.panel.remove(); this.restoreBtn.remove(); this.modal.remove(); } }; } }; // --- 自动化核心 --- const Scanner = { isRunning: false, currentSessionCount: 0, stop: function() { this.isRunning = false; const btn = document.getElementById('btn-toggle-run'); if (btn) { const isVip = Store.checkVip(); const trial = Store.getTrialInfo(); btn.textContent = isVip ? '启动海投' : (trial.isExpired ? '试用已结束' : '开始免费试用'); btn.style.background = isVip || !trial.isExpired ? '#007bff' : '#6c757d'; btn.disabled = !isVip && trial.isExpired; } // 更新状态面板 const statusPanel = document.getElementById('yp-status-panel'); const statusText = document.getElementById('status-text'); if (statusPanel) { statusText.textContent = '已停止'; statusText.style.color = '#ff4d4f'; } }, updateStatusUI: function() { const delivered = document.getElementById('status-delivered'); const target = document.getElementById('status-target'); const percent = document.getElementById('status-percent'); const progressBar = document.getElementById('status-progress-bar'); if (delivered && target && percent && progressBar) { const limit = Store.settings.deliveryLimit; const count = this.currentSessionCount; const p = Math.min(100, Math.floor((count / limit) * 100)); delivered.textContent = count; target.textContent = limit; percent.textContent = `${p}%`; progressBar.style.width = `${p}%`; } }, start: async function() { if (this.isRunning) return; // 检查当前运行页面 const url = window.location.href; const isJobPage = url.includes('/zhaopin/') || url.includes('/zhaogong/') || url.includes('/topic/') || url.includes('keywords='); const isMsgPage = url.includes('/message/'); if (!isJobPage && !isMsgPage) { alert('⚠️ 请前往“职位列表”或“消息”界面运行投递助手!\n\n当前页面不支持自动扫描。'); Utils.log('🛑 运行失败:当前页面非职位列表或消息页', 'error'); return; } const isVip = Store.checkVip(); const trial = Store.getTrialInfo(); // 权限检查 if (!isVip) { if (trial.isExpired) { Utils.log('❌ 试用已结束:5次免费机会已用完', 'error'); alert('⚠️ 您的 5 次免费试用机会已用完!\n\n请前往激活页面购买卡密以解锁完整功能。'); document.querySelector('.yp-tab[data-tab="activation"]').click(); return; } Utils.log(`💡 正在使用试用模式 (${trial.count}/5),仅限免费聊`, 'system'); } this.isRunning = true; this.currentSessionCount = 0; // 每次点击“开始”都从 0 开始计数,方便匹配投递目标 const btn = document.getElementById('btn-toggle-run'); if (btn) { btn.textContent = '停止运行'; btn.style.background = '#dc3545'; } // 显示并更新状态面板 const statusPanel = document.getElementById('yp-status-panel'); const statusText = document.getElementById('status-text'); if (statusPanel) { statusPanel.style.display = 'block'; statusText.textContent = '正在运行'; statusText.style.color = '#52c41a'; this.updateStatusUI(); } Utils.log('🚀 开始扫描职位列表...', 'success'); const keywords = document.getElementById('ipt-keywords').value.split(/[,,]/).filter(s => s.trim()); const baseDelay = SPEED_MAP[Store.settings.deliverySpeed].delay; while (this.isRunning) { try { if (!this.isRunning) break; if (this.checkFinished()) { Utils.log('🏁 检测到已经到底了,停止投递。', 'warn'); this.stop(); break; } const items = this.getAvailableItems(); if (items.length === 0) { Utils.log('⏳ 正在寻找新职位,请稍候...', 'info'); window.scrollTo(0, document.body.scrollHeight); await Utils.sleep(3000); continue; } for (let item of items) { if (!this.isRunning) break; // 检查是否达到本次运行上限 (试用用户不受此 Target 限制,以 5 次总限额为准) if (isVip && this.currentSessionCount >= Store.settings.deliveryLimit) { Utils.log(`✅ 已达到本次设定的投递上限 (${Store.settings.deliveryLimit}),停止运行`, 'success'); this.stop(); return; } // 试用模式下,每处理一个职位前检查次数 (试用总额限制) if (!isVip && Store.getTrialInfo().isExpired) { Utils.log('🛑 试用次数已耗尽,请激活 VIP', 'error'); this.stop(); return; } await this.processItem(item, keywords, baseDelay); if (!this.isRunning) break; } } catch (e) { Utils.log('❌ 扫描循环异常: ' + e.message, 'error'); if (this.isRunning) await Utils.sleep(5000); } } this.stop(); }, checkFinished: function() { return !!Array.from(document.querySelectorAll('div, span')).find(el => el.innerText.includes('暂无更多数据') || el.innerText.includes('已经到底了') ); }, getAvailableItems: function() { const items = Array.from(document.querySelectorAll(CONFIG.selectors.jobItem)); return items.filter(item => { const btn = item.querySelector(CONFIG.selectors.chatBtn) || item.querySelector(CONFIG.selectors.continueChatBtn) || item.querySelector('button, .btn'); if (btn && btn.innerText.includes('继续')) { return false; // 直接在列表页过滤掉“继续聊”的职位 } return true; }); }, processItem: async function(item, keywords, baseDelay) { const jobId = item.getAttribute('data-id') || item.innerText.slice(0, 50); Store.addProcessedJob(jobId); if (keywords.length && !keywords.some(k => item.innerText.includes(k))) return; try { item.scrollIntoView({ behavior: 'smooth', block: 'center' }); item.click(); await Utils.sleep(baseDelay); // HR 活跃度检查 if (!this.checkHRActive()) return; const isVip = Store.checkVip(); const autoEnabled = Store.settings.autoDeliveryEnabled; if (isVip && autoEnabled) { // VIP 且开启了自动投递 -> 执行高级投递 const mode = Store.settings.resumeMode; let success = false; if (mode === 'attachment') { success = await this.handleAttachmentMode(); } else { success = await this.handleImageMode(baseDelay); } if (success === 'sent') { // 增加本次运行计数 this.currentSessionCount++; // 同时更新今日总投递数 const total = (Store.settings.dailyDeliveryCount || 0) + 1; Store.set('dailyDeliveryCount', total); this.updateStatusUI(); } } else { // 试用用户 或 关闭了自动投递 -> 仅执行简单打招呼 if (!isVip && autoEnabled) { Utils.log('💡 试用模式:自动投递简历(附件/图片)需 VIP,当前仅执行打招呼', 'warn'); } const chatResult = await this.handleSimpleChat(baseDelay); if (chatResult === 'sent') { // 增加本次运行计数 this.currentSessionCount++; // 同时更新今日总投递数 const total = (Store.settings.dailyDeliveryCount || 0) + 1; Store.set('dailyDeliveryCount', total); this.updateStatusUI(); } else if (chatResult === 'skipped') { // 如果是跳过的,不计入本次投递数量,继续找下一个 Utils.log('⏭️ 自动跳过重复职位,不占用本次投递名额', 'info'); } } } catch (err) { Utils.log('❌ 处理职位失败: ' + err.message, 'error'); } await Utils.sleep(baseDelay); }, openChatWindow: async function(baseDelay) { const chatBtn = document.querySelector(CONFIG.selectors.chatBtn) || document.querySelector(CONFIG.selectors.continueChatBtn); if (!chatBtn) return false; if (chatBtn.innerText.includes('继续')) { Utils.log('⏭️ 跳过:该 HR 已在沟通中', 'info'); return false; } // 记录当前点击目标的特征(如职位名或HR名) const targetName = document.querySelector('.job-name, .title, h1, .name')?.innerText || ''; chatBtn.click(); await Utils.sleep(baseDelay + 1500); // 增加等待时间 // 检查侧边栏第一个是否是我们要找的人,或者是否已经切换成功 // 在消息列表页,侧边栏第一个通常是最新点击的 const firstListItem = document.querySelector(CONFIG.selectors.chatListItem); if (firstListItem) { const itemText = firstListItem.innerText; // 如果第一个项目的文字和我们点击的目标名称完全对不上,尝试点击它 if (targetName && !itemText.includes(targetName.slice(0, 4))) { Utils.log('🔄 正在尝试切换到正确的聊天窗口...', 'info'); firstListItem.click(); await Utils.sleep(1000); } } // 等待聊天区域内容加载 const scrollArea = document.querySelector(CONFIG.selectors.chatScrollArea); if (scrollArea && scrollArea.innerText.includes('正在加载')) { await Utils.sleep(2000); } return true; }, handleSimpleChat: async function(baseDelay) { const isVip = Store.checkVip(); let trial = Store.getTrialInfo(); // 权限校验 if (!isVip && trial.isExpired) { this.stop(); Utils.log('🛑 试用次数耗尽,请激活 VIP', 'error'); return 'skipped'; } // 尝试打开窗口 if (!await this.openChatWindow(baseDelay)) return 'skipped'; // 等待内容加载 await Utils.sleep(baseDelay + 1500); const historyArea = document.querySelector(CONFIG.selectors.chatScrollArea) || document.body; let retry = 0; while (historyArea.innerText.includes('正在加载') && retry < 5) { await Utils.sleep(1000); retry++; } const messages = Array.from(historyArea.querySelectorAll(CONFIG.selectors.chatHistoryMsg)); const myIntro = Store.settings.selfIntro || ""; let needSend = false; if (messages.length === 0) { needSend = true; } else { if (isVip) { const allText = messages.map(m => m.innerText).join('\n'); const hasMyIntro = myIntro && allText.includes(myIntro.slice(0, 10)); if (hasMyIntro) { Utils.log('⏭️ 跳过:该 HR 已经发过自我介绍了', 'info'); return 'skipped'; } } if (messages.length === 1) { const firstMsgText = messages[0].innerText || ""; const isDefaultMsg = firstMsgText.includes('岗位职责') || firstMsgText.includes('希望能与您进一步沟通') || firstMsgText.includes('你好,我对这个职位很感兴趣'); if (isDefaultMsg) { needSend = true; } else { Utils.log('⏭️ 跳过:已有其他沟通记录', 'info'); return 'skipped'; } } else { Utils.log('⏭️ 跳过:已有多次沟通记录', 'info'); return 'skipped'; } } if (!needSend) { Utils.log('⏭️ 跳过:无需重复操作', 'info'); return 'skipped'; } let success = false; if (isVip && myIntro) { Utils.log(`💬 发送自定义介绍: "${myIntro.slice(0, 10)}..."`, 'info'); success = await this.sendTextMessage(myIntro); } else { const sendBtn = document.querySelector(CONFIG.selectors.sendBtn); if (sendBtn && !sendBtn.disabled) { sendBtn.click(); success = true; Utils.log('✅ 自动打招呼已完成 (平台默认语)', 'success'); } else { if (messages.length <= 1) { success = true; Utils.log('✅ 自动打招呼已完成 (已触发免费聊)', 'success'); } } } if (success) { await Utils.sleep(1500); if (!isVip) { Store.useTrial(); trial = Store.getTrialInfo(); Utils.log(`🎁 试用次数消耗: ${trial.count}/5`, 'success'); const display = document.getElementById('trial-count-display'); if (display) display.textContent = `${trial.count}/5`; } return 'sent'; } else { Utils.log('❌ 操作失败,不计入本次投递名额', 'error'); return 'skipped'; } }, handleImageMode: async function(baseDelay) { if (!Store.checkVip()) { Utils.log('⚠️ 自动投递简历仅限 VIP', 'error'); return false; } if (!await this.openChatWindow(baseDelay)) return false; Utils.log('🔍 打开窗口,检测历史记录...', 'info'); await Utils.sleep(baseDelay + 1500); const historyArea = document.querySelector(CONFIG.selectors.chatScrollArea) || document.body; let retry = 0; while (historyArea.innerText.includes('正在加载') && retry < 5) { await Utils.sleep(1000); retry++; } const messages = Array.from(historyArea.querySelectorAll(CONFIG.selectors.chatHistoryMsg)); const historyText = historyArea.innerText || ""; let sentAnything = false; const myIntro = Store.settings.selfIntro || ""; const hasMyIntro = myIntro && messages.some(m => m.innerText.includes(myIntro.slice(0, 10))); if (myIntro && !hasMyIntro) { Utils.log(`💬 发送 VIP 自定义介绍: "${myIntro.slice(0, 10)}..."`, 'info'); const introSuccess = await this.sendTextMessage(myIntro); if (introSuccess) { sentAnything = true; await Utils.sleep(1200); } } else if (hasMyIntro) { Utils.log('⏭️ 跳过自我介绍:检测到内容已存在', 'info'); } const hasImage = historyArea.querySelector('.msg-image, .chat-img, .image-msg'); if (!hasImage) { await this.sendResumeImage(); sentAnything = true; } else { Utils.log('⏭️ 跳过图片简历:检测到已有图片记录', 'info'); } return sentAnything ? 'sent' : 'skipped'; }, handleAttachmentMode: async function() { if (!Store.checkVip()) { Utils.log('⚠️ 自动投递简历仅限 VIP', 'error'); return false; } // 增加点击前的缓冲等待,确保详情页完全加载 await Utils.sleep(1000); const attachBtn = document.querySelector(CONFIG.selectors.sendAttachmentBtn); if (!attachBtn) { Utils.log('❌ 未找到发送简历按钮', 'error'); return false; } const text = attachBtn.innerText; if (text.includes('发送简历') || text.includes('投递')) { // 使用增强版点击模拟 Utils.simulateClick(attachBtn); Utils.log('✅ 已尝试投递附件简历', 'success'); // 处理弹窗 await Utils.sleep(1500); const modal = Array.from(document.querySelectorAll('.ant-modal, .modal, div[role="dialog"]')).find(el => el.innerText.includes('投递成功') || el.innerText.includes('简历已发送') ); if (modal) { const okBtn = Array.from(modal.querySelectorAll('button')).find(b => b.innerText.includes('确定') || b.innerText.includes('我知道了') || b.innerText.includes('关闭') ) || modal.querySelector('button'); if (okBtn) { Utils.simulateClick(okBtn); Utils.log('🆗 已关闭成功弹窗', 'info'); } } await Utils.sleep(2500); // 二次确认 const afterBtn = document.querySelector(CONFIG.selectors.sendAttachmentBtn); if (afterBtn && (afterBtn.innerText.includes('发送简历') || afterBtn.innerText.includes('投递'))) { Utils.log('⚠️ 投递状态似乎未更新,可能未触发成功', 'warn'); return false; } return 'sent'; } else if (text.includes('继续') || text.includes('已投递')) { Utils.log('⏭️ 跳过:该职位已投递过附件简历', 'info'); return 'skipped'; } return false; }, checkHRActive: function() { const limits = Store.settings.hrActiveLimit; if (!limits || limits.length === 0 || limits.includes('不限')) return true; const target = limits[0]; const getWeight = (text) => { if (!text) return 999; const t = text.replace(/\s+/g, ''); if (t.includes('刚刚') || t.includes('在线') || /分.*前/.test(t) || /小.*前/.test(t) || t.includes('今日')) return 0; if (t.includes('昨日')) return 1; if (t.includes('2日')) return 2; if (t.includes('3日')) return 3; if (t.includes('4日')) return 4; if (t.includes('5日')) return 5; if (t.includes('6日')) return 6; if (t.includes('7日')) return 7; if (t.includes('15日')) return 15; if (t.includes('本月')) return 30; if (t.includes('半年')) return 180; return 999; }; const targetWeight = getWeight(target); // 1. 尝试特定的状态标签 let hrInfo = ''; const statusEl = document.querySelector('.active-status, .hr-active-time, .time-text, .abda8, .abda9'); if (statusEl && statusEl.innerText.trim()) { hrInfo = statusEl.innerText; } else { // 2. 如果没找到标签,使用更精确的关键词在详情页搜索 // 排除掉“前端”这种干扰项,关键词改为更具特征的短语 const activeKeywords = ['活跃', '在线', '刚刚', '今日', '昨日', '本月', '日内', '小时前', '分钟前', '天前']; const detailPanel = document.querySelector('.job-detail, .job-detail-box, .right-content, .job-info-wrapper, .job-detail-hr') || document.body; const allText = detailPanel.innerText || ''; const lines = allText.split('\n'); const activeLine = lines .filter(line => { // 必须包含活跃关键词,且不能包含“前端”等干扰词(除非该行很短) const hasKey = activeKeywords.some(k => line.includes(k)); const isJobTag = line.includes('前端') || line.includes('后端'); return hasKey && (!isJobTag || line.length < 10); }) .sort((a, b) => a.length - b.length)[0]; if (activeLine) hrInfo = activeLine; } const cleanInfo = hrInfo.replace(/\s+/g, '').slice(0, 50); const currentWeight = getWeight(cleanInfo); if (currentWeight <= targetWeight) { return true; } else { Utils.log(`⏭️ 跳过:HR活跃度不符 (${cleanInfo || '未知'})`, 'warn'); return false; } }, shouldSkipByHistory: function() { const historyArea = document.querySelector(CONFIG.selectors.chatScrollArea) || document.querySelector('.chat-message-list') || document.body; const bubbles = historyArea.querySelectorAll(CONFIG.selectors.chatHistoryMsg); // 1. 如果总消息数 <= 2 条,直接判定为新对话,不跳过 // 刚点击“免费聊”通常是:1条职位卡片 + 1条系统提示(或自动打招呼) if (bubbles.length <= 2) { return false; } // 2. 检查关键沟通记录(如微信、电话等),这些是必须跳过的 const text = historyArea.innerText || ""; const criticalKeywords = ['对方微信', '对方已拒绝', '电话:', '简历投递成功']; if (criticalKeywords.some(k => text.includes(k))) { Utils.log('⏭️ 跳过:检测到关键沟通/投递记录', 'info'); return true; } // 3. 检查是否有 HR 的人工回复(排除掉那些已知的自动内容) const hrReplies = Array.from(historyArea.querySelectorAll('.chat-item-left, .message-left, .msg-left')).filter(el => { const t = el.innerText; const isAuto = t.includes('发起了沟通') || t.includes('温馨提示') || t.includes('薪资:') || t.includes('查看职位') || t.includes('元/'); return !isAuto && t.length > 3; // 人工回复通常不是纯数字或极短字符 }); if (hrReplies.length > 0) { Utils.log(`⏭️ 跳过:检测到 HR 人工回复`, 'info'); return true; } return false; }, sendTextMessage: async function(text) { // 尝试多个可能的编辑器选择器 let editor = document.querySelector(CONFIG.selectors.chatEditor); // 兜底:尝试查找任何 contenteditable 元素 if (!editor) { editor = Array.from(document.querySelectorAll('div[contenteditable="true"]')).pop(); } if (editor) { editor.focus(); // 清空当前内容(如果有残留) if (editor.innerText.trim()) { document.execCommand('selectAll', false, null); document.execCommand('delete', false, null); } // 模拟输入 document.execCommand('insertText', false, text); // 触发输入事件,让页面感知到内容变化 editor.dispatchEvent(new Event('input', { bubbles: true })); editor.dispatchEvent(new Event('change', { bubbles: true })); await Utils.sleep(800); // 尝试查找发送按钮 let sendBtn = document.querySelector(CONFIG.selectors.sendBtn); // 增加更多的发送按钮查找逻辑 if (!sendBtn) { sendBtn = Array.from(document.querySelectorAll('button, .btn, div[role="button"]')).find(b => { const t = b.innerText.trim(); return t === '发送' || t === 'Send' || b.classList.contains('fb-btn-send'); }); } if (sendBtn && !sendBtn.disabled) { sendBtn.click(); Utils.log('✅ 自我介绍已发送', 'success'); await Utils.sleep(1000); return true; } else { // 最终兜底:模拟回车键发送 const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true }); editor.dispatchEvent(enterEvent); Utils.log('✅ 已尝试通过回车键发送', 'info'); await Utils.sleep(1000); return true; // 既然尝试了回车,也视为发送动作 } } else { Utils.log('❌ 未找到聊天输入框,无法发送消息', 'error'); return false; } }, sendResumeImage: async function() { const editor = document.querySelector(CONFIG.selectors.chatEditor); const btn = document.querySelector(CONFIG.selectors.sendBtn); const data = Store.settings.imageResumeData; if (editor && data) { try { const blob = await (await fetch(data)).blob(); const file = new File([blob], "resume.jpg", { type: "image/jpeg" }); editor.focus(); await Utils.sleep(300); const dt = new DataTransfer(); dt.items.add(file); editor.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); Utils.log('⏳ 已粘贴图片,等待加载...', 'info'); await Utils.sleep(2500); if (btn && !btn.disabled) { btn.click(); Utils.log('✅ 图片简历已发送', 'success'); } else { editor.dispatchEvent(new Event('input', { bubbles: true })); await Utils.sleep(500); document.querySelector(CONFIG.selectors.sendBtn)?.click(); Utils.log('✅ 图片简历尝试发送', 'success'); } } catch (e) { Utils.log('❌ 图片发送失败: ' + e.message, 'error'); } } } }; // --- 启动逻辑 --- const startApp = async () => { Store.init(); await Utils.syncRemoteConfig(); UI.create(); if (Store.checkVip()) { Utils.log('💎 尊贵的 VIP 用户,欢迎回来!', 'success'); } else { Utils.log('💡 当前为免费模式,自动投递功能需激活。', 'system'); } }; if (document.readyState === 'complete') { startApp(); } else { window.addEventListener('load', startApp); } // 暴露调试接口 (仅供开发者使用) window.yp_resetTrial = () => { Store.resetTrial(); console.log('🔄 试用次数已重置,请刷新页面'); }; })();