// ==UserScript==
// @name 鱼泡直聘批量投递助手
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @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 到期倒计时
${Store.getVipRemainingTime()}
` : ''}
${!isVip && trial.isExpired ? `
🔒
免费试用已结束
5次免费试用已用完,请激活后继续使用
` : ''}
${!isVip && !trial.isExpired ? `
🎁 免费试用中 (仅限免费聊)
${trial.count}/5
` : ''}
⚡ 运行状态
未运行
投递目标数量
${Store.settings.deliveryLimit}
完成进度: 0%
[${new Date().toLocaleTimeString()}] 助手已就绪
复制日志
${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('🔄 试用次数已重置,请刷新页面');
};
})();