// ==UserScript==
// @name JobPilot 海投助手
// @name:en JobPilot Helper for BOSS
// @namespace https://github.com/fancyboi999/JobPilot
// @version 1.0.0
// @description BOSS 直聘批量沟通 + 图片简历 + AI 回复的一站式脚本,装好即可开启自动求职流程。
// @description:en All-in-one automation helper for BOSS Zhipin (auto apply, image resume, AI replies).
// @author fancyboi999
// @match https://www.zhipin.com/web/*
// @grant GM_xmlhttpRequest
// @run-at document-idle
// @supportURL https://github.com/fancyboi999/JobPilot/issues
// @homepageURL https://github.com/fancyboi999/JobPilot
// @downloadURL https://raw.githubusercontent.com/fancyboi999/JobPilot/refs/heads/main/JOBPILOT.user.js
// @updateURL https://raw.githubusercontent.com/fancyboi999/JobPilot/refs/heads/main/JOBPILOT.user.js
// @license AGPL-3.0-or-later
// @icon https://static.zhipin.com/favicon.ico
// @connect zhipin.com
// @connect spark-api-open.xf-yun.com
// @noframes
// ==/UserScript==
(function () {
'use strict';
// 基本配置设置内容
const CONFIG$1 = {
BASIC_INTERVAL: 1000,
OPERATION_INTERVAL: 1500,
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,
SELECTORS: {
JOB_CARD: 'li.job-card-box',
CHAT_BTN: 'a.op-btn-chat',
CHAT_LIST: 'ul[data-v-8e790d94=""]',
CHAT_INPUT: '#chat-input',
SEND_BUTTON: '.btn-send',
FRIEND_MESSAGE: '.item-friend .text span',
COMMON_PHRASE_BTN: '.btn-dict',
RESUME_BTN: '.toolbar-btn:contains("发简历")',
CONFIRM_SEND: 'span.btn-sure-v2',
IMAGE_SEND_BTN: '.toolbar-btn-content.icon.btn-sendimg input[type="file"]'
},
AI: {
MAX_REPLIES_FREE: 10,
MAX_REPLIES_PREMIUM: 25,
DEFAULT_ROLE: '你是个正在积极寻找工作机会的求职者,回复礼貌简短、言简意赅且避免大段文字,突出优势和能力展现专业素养。'
},
MESSAGES: {
JOB_MATCHED: '找到匹配岗位: ',
JOB_NOT_FOUND: '没有找到符合条件的岗位',
START_PROCESSING: '开始自动处理...',
STOP_PROCESSING: '已停止自动处理',
RESUME_SENT: '简历已发送',
AI_REPLYING: 'AI 正在回复...',
MAX_REPLIES_REACHED: '今日 AI 回复次数已达上限'
},
STORAGE_KEYS: {
PROCESSED_HRS: 'processedHRs',
AI_REPLY_COUNT: 'aiReplyCount',
LAST_AI_DATE: 'lastAiDate',
AI_ROLE: 'aiRole',
LETTER_LAST_SHOWN: 'letterLastShown'
}
};
window.CONFIG = CONFIG$1;
// 状态管理
const state$1 = {
// 运行状态
isRunning: false,
chatMode: null, // 'GREETINGS', 'IMAGE_RESUME', 'AI_REPLY'
currentIndex: 0,
// 筛选条件 - 职位名/工作地关键词
includeKeywords: [], // 职位名包含关键词
locationKeywords: [], // 工作地包含关键词
// 数据缓存
jobList: [],
// UI 状态
ui: {
isMinimized: false,
theme: localStorage.getItem('theme') || 'light',
showWelcomeMessage: JSON.parse(localStorage.getItem('showWelcomeMessage') || 'true')
},
// HR交互状态
hrInteractions: {
processedHRs: new Set(JSON.parse(localStorage.getItem('processedHRs') || '[]')),
currentTopHRKey: null,
sentGreetingsHRs: new Set(JSON.parse(localStorage.getItem('sentGreetingsHRs') || '[]')),
sentResumeHRs: new Set(JSON.parse(localStorage.getItem('sentResumeHRs') || '[]')),
sentImageResumeHRs: new Set(JSON.parse(localStorage.getItem('sentImageResumeHRs') || '[]')) // 已发送图片简历的HR
},
// AI 功能
ai: {
replyCount: JSON.parse(localStorage.getItem('aiReplyCount') || '0'),
lastAiDate: localStorage.getItem('lastAiDate') || '',
useAiReply: true
},
// 操作记录
operation: {
lastMessageTime: 0,
processedJobsCount: 0,
lastProcessedDate: localStorage.getItem('lastProcessedDate') || '',
dailyJobLimit: 50
},
// 用户权限
user: {
isPremiumUser: localStorage.getItem('isPremiumUser') === 'true'
},
// 应用设置
settings: {
useAutoSendResume: JSON.parse(localStorage.getItem('useAutoSendResume') || 'true'),
useAutoSendImageResume: JSON.parse(localStorage.getItem('useAutoSendImageResume') || 'false'), // 自动发送图片简历
imageResumes: JSON.parse(localStorage.getItem('imageResumes') || '[]'), // 图片简历数组,最多3个,格式:[{path: 'xxx.jpg', data: 'base64data'}]
autoScrollSpeed: parseInt(localStorage.getItem('autoScrollSpeed') || '500'),
customPhrases: JSON.parse(localStorage.getItem('customPhrases') || '[]'),
actionDelays: {
click: parseInt(localStorage.getItem('clickDelay') || '130'), // 发送常用语的速度
},
notifications: {
enabled: JSON.parse(localStorage.getItem('notificationsEnabled') || 'true'),
sound: JSON.parse(localStorage.getItem('notificationSound') || 'true')
}
}
};
// DOM 元素引用 - 更新为新的输入框引用
const elements$1 = {
panel: null,
controlBtn: null,
log: null,
includeInput: null, // 职位名包含输入框
locationInput: null, // 工作地包含输入框
miniIcon: null,
aiRoleInput: null,
themeToggle: null,
settingsPanel: null,
greetingsBtn: null,
resumeBtn: null,
aiBtn: null
};
// 状态持久化工具类
let StatePersistence$1 = class StatePersistence {
/**
* 保存所有状态到localStorage
*/
static saveState() {
// 保存HR交互状态
localStorage.setItem('processedHRs', JSON.stringify([...state$1.hrInteractions.processedHRs]));
localStorage.setItem('sentGreetingsHRs', JSON.stringify([...state$1.hrInteractions.sentGreetingsHRs]));
localStorage.setItem('sentResumeHRs', JSON.stringify([...state$1.hrInteractions.sentResumeHRs]));
localStorage.setItem('sentImageResumeHRs', JSON.stringify([...state$1.hrInteractions.sentImageResumeHRs]));
// 保存AI状态
localStorage.setItem('aiReplyCount', state$1.ai.replyCount);
localStorage.setItem('lastAiDate', state$1.ai.lastAiDate);
// 保存操作记录
localStorage.setItem('lastProcessedDate', state$1.operation.lastProcessedDate);
// 保存用户设置
localStorage.setItem('showWelcomeMessage', state$1.ui.showWelcomeMessage);
localStorage.setItem('isPremiumUser', state$1.user.isPremiumUser);
localStorage.setItem('useAiReply', state$1.ai.useAiReply);
localStorage.setItem('useAutoSendResume', state$1.settings.useAutoSendResume);
localStorage.setItem('useAutoSendImageResume', state$1.settings.useAutoSendImageResume);
// 更新:保存多图片简历数据,单独存储以处理大型Base64数据
if (state$1.settings.imageResumes && Array.isArray(state$1.settings.imageResumes)) {
// 过滤无效数据并限制最多3个
const validResumes = state$1.settings.imageResumes
.filter(resume => resume && typeof resume === 'object' && resume.path && resume.data)
.slice(0, 3);
localStorage.setItem('imageResumes', JSON.stringify(validResumes));
}
// 清理旧的单图片简历数据
localStorage.removeItem('imageResumePath');
localStorage.removeItem('imageResumeData');
localStorage.setItem('autoScrollSpeed', state$1.settings.autoScrollSpeed);
localStorage.setItem('customPhrases', JSON.stringify(state$1.settings.customPhrases));
// 保存UI设置
localStorage.setItem('theme', state$1.ui.theme);
// 保存操作延迟
localStorage.setItem('clickDelay', state$1.settings.actionDelays.click);
// 保存通知设置
localStorage.setItem('notificationsEnabled', state$1.settings.notifications.enabled);
localStorage.setItem('notificationSound', state$1.settings.notifications.sound);
// 新增:保存筛选条件
localStorage.setItem('includeKeywords', JSON.stringify(state$1.includeKeywords));
localStorage.setItem('locationKeywords', JSON.stringify(state$1.locationKeywords));
}
/**
* 从localStorage加载状态
*/
static loadState() {
// 初始化加载已在 state 对象定义中完成
// 加载筛选条件(兼容旧版 excludeKeywords -> 迁移为 locationKeywords)
const includeKeywords = JSON.parse(localStorage.getItem('includeKeywords') || '[]');
const locationKeywords = JSON.parse(
localStorage.getItem('locationKeywords') ||
localStorage.getItem('excludeKeywords') || '[]'
);
if (Array.isArray(includeKeywords)) state$1.includeKeywords = includeKeywords;
if (Array.isArray(locationKeywords)) state$1.locationKeywords = locationKeywords;
// 执行图片简历数据迁移
this.migrateImageResumeData();
}
/**
* 迁移旧版的单个图片简历数据到新版的多图片简历数组
*/
static migrateImageResumeData() {
try {
// 检查是否有旧版数据且新版数据为空
const hasOldData = localStorage.getItem('imageResumePath') || localStorage.getItem('imageResumeData');
const imageResumesStr = localStorage.getItem('imageResumes');
let hasNewData = false;
if (imageResumesStr) {
try {
const imageResumes = JSON.parse(imageResumesStr);
hasNewData = Array.isArray(imageResumes) && imageResumes.length > 0;
} catch (e) {
// 解析失败,视为无新数据
}
}
// 如果有旧数据且无新数据,执行迁移
if (hasOldData && !hasNewData) {
console.log('执行图片简历数据迁移...');
const oldResume = {
path: localStorage.getItem('imageResumePath') || 'legacy_resume.jpg',
data: localStorage.getItem('imageResumeData')
};
// 只有在有数据的情况下才迁移
if (oldResume.data) {
state$1.settings.imageResumes = [oldResume];
localStorage.setItem('imageResumes', JSON.stringify(state$1.settings.imageResumes));
console.log('图片简历数据迁移完成');
}
// 清理旧数据
localStorage.removeItem('imageResumePath');
localStorage.removeItem('imageResumeData');
}
} catch (error) {
console.error('图片简历数据迁移失败:', error);
}
}
};
// HR交互管理类
let HRInteractionManager$1 = class HRInteractionManager {
/**
* 根据HR状态执行相应操作
* @param {string} hrKey - HR标识(如:"姓名-公司名")
*/
static async handleHRInteraction(hrKey) {
// 根据当前模式执行特定操作
if (state$1.chatMode === 'GREETINGS') {
if (!state$1.hrInteractions.sentGreetingsHRs.has(hrKey)) {
Core.log(`正在发送常用语: ${hrKey}`);
const sent = await this.sendGreetings();
if (sent) {
state$1.hrInteractions.sentGreetingsHRs.add(hrKey);
StatePersistence$1.saveState();
Core.log(`常用语发送成功`);
}
} else {
Core.log(`已发送过常用语,跳过`);
}
return;
}
if (state$1.chatMode === 'IMAGE_RESUME') {
if (!state$1.hrInteractions.sentImageResumeHRs.has(hrKey)) {
Core.log(`正在发送图片简历: ${hrKey}`);
const sentImageResume = await this.sendImageResume();
if (sentImageResume) {
state$1.hrInteractions.sentImageResumeHRs.add(hrKey);
StatePersistence$1.saveState();
Core.log(`图片简历发送成功`);
}
} else {
Core.log(`已发送过图片简历,跳过`);
}
return;
}
if (state$1.chatMode === 'AI_REPLY') {
Core.log(`正在进行AI回复: ${hrKey}`);
await Core.aiReply();
return;
}
// 原有的智能逻辑(如果chatMode为null或SMART,虽然现在UI上没有入口了,但保留逻辑以防万一)
// ... (保留原有逻辑或直接返回)
// 由于用户明确要求拆分,且UI已更改,这里可以简化或保留作为兜底
// 检查HR是否已发送消息
await this.hasHRResponded();
// ... (原有逻辑)
if (!state$1.hrInteractions.sentGreetingsHRs.has(hrKey)) {
// ...
return;
}
// ...
}
/**
* 检查HR是否已回复消息
*/
static async hasHRResponded() {
await Core.delay(state$1.settings.actionDelays.click);
const chatContainer = document.querySelector('.chat-message .im-list');
if (!chatContainer) return false;
const friendMessages = Array.from(chatContainer.querySelectorAll('li.message-item.item-friend'));
return friendMessages.length > 0;
}
/**
* 发送常用语
*/
static async sendGreetings() {
try {
// await Core.delay(state.settings.actionDelays.click);
// 点击“常用语”按钮
const dictBtn = await Core.waitForElement('.btn-dict');
if (!dictBtn) {
Core.log('未找到常用语按钮');
return false;
}
await Core.simulateClick(dictBtn);
await Core.delay(state$1.settings.actionDelays.click);
// 等待0.3秒,确保常用语列表加载完成
await Core.delay(300);
// 查找常用语列表
const dictList = await Core.waitForElement('ul[data-v-1d93a2d5=""]');
if (!dictList) {
Core.log('未找到常用语列表');
return false;
}
const dictItems = dictList.querySelectorAll('li');
if (!dictItems || dictItems.length === 0) {
Core.log('常用语列表为空');
return false;
}
// 遍历并点击每条常用语
for (let i = 0; i < dictItems.length; i++) {
const item = dictItems[i];
Core.log(`发送常用语(自我介绍):第${i + 1}条/共${dictItems.length}条`);
await Core.simulateClick(item);
await Core.delay(state$1.settings.actionDelays.click);
}
return true;
} catch (error) {
Core.log(`发送常用语出错: ${error.message}`);
return false;
}
}
/**
* 发送简历
*/
/**
* 从岗位名称中提取连续的两个字作为关键词
* @param {string} positionName - 岗位名称
* @returns {Array} 双字关键词数组
*/
static _extractTwoCharKeywords(positionName) {
const keywords = [];
// 确保中文处理正确,过滤掉标点符号和空格
const cleanedName = positionName.replace(/[\s,,.。::;;""''\[\]\(\)\{\}]/g, '');
// 提取连续的两个字
for (let i = 0; i < cleanedName.length - 1; i++) {
keywords.push(cleanedName.substring(i, i + 2));
}
return keywords;
}
/**
* 根据双字关键词查找匹配的简历
* @param {Array} resumeItems - 简历项数组
* @param {string} positionName - 岗位名称
* @returns {HTMLElement|null} 匹配的简历项或null
*/
static _findMatchingResume(resumeItems, positionName) {
try {
// 转换为小写用于匹配
const positionNameLower = positionName.toLowerCase();
// 提取双字关键词
const twoCharKeywords = this._extractTwoCharKeywords(positionNameLower);
// 按关键词优先级(从左到右)遍历
for (const keyword of twoCharKeywords) {
// 查找包含当前关键词的简历
for (const item of resumeItems) {
const resumeNameElement = item.querySelector('.resume-name');
if (!resumeNameElement) continue;
const resumeName = resumeNameElement.textContent.trim().toLowerCase();
// 检查简历名称是否包含当前关键词
if (resumeName.includes(keyword)) {
const resumeNameText = resumeNameElement.textContent.trim();
Core.log(`找到匹配简历: "${resumeNameText}" 匹配关键词: "${keyword}"`);
return item;
}
}
}
// 如果没有找到匹配的简历,返回null(将在主方法中使用第一个简历)
return null;
} catch (error) {
Core.log(`简历匹配出错: ${error.message}`);
return null;
}
}
static async sendResume() {
try {
// 查找"发简历"按钮
const resumeBtn = await Core.waitForElement(() => {
return [...document.querySelectorAll('.toolbar-btn')].find(
el => el.textContent.trim() === '发简历'
);
});
if (!resumeBtn) {
Core.log('无法发送简历,未找到发简历按钮');
return false;
}
if (resumeBtn.classList.contains('unable')) {
Core.log('对方未回复,您无权发送简历');
return false;
}
// 获取当前岗位名称
let positionName = '';
try {
// 尝试从职位卡片获取岗位名称
const positionNameElement = document.querySelector('.position-name') ||
document.querySelector('.job-name') ||
document.querySelector('[class*="position-content"] .left-content .position-name');
if (positionNameElement) {
positionName = positionNameElement.textContent.trim();
Core.log(`当前岗位: ${positionName}`);
} else {
Core.log('未找到岗位名称元素');
}
} catch (e) {
Core.log(`获取岗位名称出错: ${e.message}`);
}
// 点击"发简历"
await Core.simulateClick(resumeBtn);
await Core.delay(state$1.settings.actionDelays.click);
// 等待1秒,确保简历列表加载完成
await Core.delay(1000);
// 查找简历列表
const resumeList = await Core.waitForElement('ul.resume-list');
if (!resumeList) {
Core.log('未找到简历列表');
return false;
}
// 获取所有简历项
const resumeItems = Array.from(resumeList.querySelectorAll('li.list-item'));
if (resumeItems.length === 0) {
Core.log('未找到简历列表项');
return false;
}
// 选择简历:优先根据岗位名称匹配,否则选择第一个
let selectedResumeItem = null;
if (positionName) {
// 根据双字关键词匹配简历
selectedResumeItem = this._findMatchingResume(resumeItems, positionName);
}
// 如果没有找到匹配的简历,使用第一个
if (!selectedResumeItem) {
selectedResumeItem = resumeItems[0];
const resumeName = selectedResumeItem.querySelector('.resume-name').textContent.trim();
Core.log('使用第一个简历: "' + resumeName + '"');
}
// 点击选中的简历项
await Core.simulateClick(selectedResumeItem);
await Core.delay(state$1.settings.actionDelays.click);
// 等待0.5秒,确保选择生效
await Core.delay(500);
// 查找发送按钮(注意:按钮可能初始为disabled状态)
const sendBtn = await Core.waitForElement('button.btn-v2.btn-sure-v2.btn-confirm');
if (!sendBtn) {
Core.log('未找到发送按钮');
return false;
}
// 检查按钮是否可用
if (sendBtn.disabled) {
Core.log('发送按钮不可用,可能简历未正确选择');
return false;
}
await Core.simulateClick(sendBtn);
return true;
} catch (error) {
Core.log(`发送简历出错: ${error.message}`);
return false;
}
}
/**
* 根据岗位名称智能选择合适的图片简历
* @returns {Object|null} 选中的图片简历对象,格式: {path, data}
*/
static selectImageResume() {
if (!state$1.settings.imageResumes || state$1.settings.imageResumes.length === 0) {
return null;
}
// 获取当前岗位名称
let positionName = '';
try {
const positionNameElement = document.querySelector('.position-name') ||
document.querySelector('.job-name') ||
document.querySelector('[class*="position-content"] .left-content .position-name');
if (positionNameElement) {
positionName = positionNameElement.textContent.trim();
Core.log(`当前岗位: ${positionName}`);
}
} catch (e) {
Core.log(`获取岗位名称出错: ${e.message}`);
}
// 如果只有一个简历,直接返回
if (state$1.settings.imageResumes.length === 1) {
const selectedResume = state$1.settings.imageResumes[0];
Core.log(`只有一个图片简历,选择: ${selectedResume.path}`);
return selectedResume;
}
// 智能匹配算法:根据简历文件名和岗位名称的相似度进行匹配
const positionLower = positionName.toLowerCase();
let bestMatch = null;
let highestScore = -1;
state$1.settings.imageResumes.forEach(resume => {
const resumeNameLower = resume.path.toLowerCase();
let score = 0;
// 检查文件名是否包含岗位关键词
// 提取简历文件名(不含扩展名)
const resumeName = resume.path.substring(0, resume.path.lastIndexOf('.'));
// 检查简历名中是否有与岗位相关的关键词
const commonKeywords = ['前端', '后端', '全栈', '开发', '工程师', '程序', '软件',
'frontend', 'backend', 'fullstack', 'developer', 'engineer'];
commonKeywords.forEach(keyword => {
if (positionLower.includes(keyword) && resumeNameLower.includes(keyword)) {
score += 1;
}
});
// 根据文件名长度调整匹配策略
if (positionName && resumeName) {
// 计算简单的字符匹配度
const matchingChars = Array.from(positionLower).filter(char =>
resumeNameLower.includes(char)
).length;
// 计算匹配率(避免除零错误)
const matchRate = resumeNameLower.length > 0 ?
matchingChars / resumeNameLower.length : 0;
score += matchRate * 2;
}
// 记录最高分的简历
if (score > highestScore) {
highestScore = score;
bestMatch = resume;
}
});
// 如果找到匹配的简历,返回它,否则返回第一个简历
const selectedResume = bestMatch || state$1.settings.imageResumes[0];
Core.log(`智能选择图片简历: ${selectedResume.path}`);
return selectedResume;
}
/**
* 发送图片简历
*/
static async sendImageResume() {
try {
if (!state$1.settings.useAutoSendImageResume || !state$1.settings.imageResumes ||
state$1.settings.imageResumes.length === 0) {
return false;
}
// 智能选择图片简历
const selectedResume = this.selectImageResume();
if (!selectedResume) {
return false;
}
// 找到图片发送按钮
const imageSendBtn = await Core.waitForElement('.toolbar-btn-content.icon.btn-sendimg input[type="file"]');
if (!imageSendBtn) {
Core.log('未找到图片发送按钮');
return false;
}
// 创建一个Blob对象
const byteCharacters = atob(selectedResume.data.split(',')[1]);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: 'image/jpeg' });
// 创建一个File对象
const file = new File([blob], selectedResume.path, {
type: 'image/jpeg',
lastModified: new Date().getTime()
});
// 创建一个DataTransfer对象来模拟文件选择
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
// 设置文件输入的值
imageSendBtn.files = dataTransfer.files;
// 触发change事件
const event = new Event('change', { bubbles: true });
imageSendBtn.dispatchEvent(event);
return true;
} catch (error) {
Core.log(`发送图片简历出错: ${error.message}`);
return false;
}
}
};
// 初始化加载状态
StatePersistence$1.loadState();
// 导出状态和工具类
window.state = state$1;
window.elements = elements$1;
window.StatePersistence = StatePersistence$1;
window.HRInteractionManager = HRInteractionManager$1;
function toggleProcess$1() {
state.isRunning = !state.isRunning;
if (state.isRunning) {
// 获取职位名包含与工作地包含关键词
state.includeKeywords = elements.includeInput.value.trim().toLowerCase().split(',').filter(keyword => keyword.trim() !== '');
state.locationKeywords = (elements.locationInput?.value || '').trim().toLowerCase().split(',').filter(keyword => keyword.trim() !== '');
elements.controlBtn.textContent = '停止海投';
elements.controlBtn.style.background = `linear-gradient(45deg, ${CONFIG.COLORS.SECONDARY}, #f44336)`;
const startTime = new Date();
Core.log(`开始自动海投,时间:${startTime.toLocaleTimeString()}`);
Core.log(`筛选条件:职位名包含【${state.includeKeywords.join('、') || '无'}】,工作地包含【${state.locationKeywords.join('、') || '无'}】`);
Core.startProcessing();
} else {
elements.controlBtn.textContent = '启动海投';
elements.controlBtn.style.background = `linear-gradient(45deg, ${CONFIG.COLORS.PRIMARY}, #4db6ac)`;
state.isRunning = false;
const stopTime = new Date();
Core.log(`停止自动海投,时间:${stopTime.toLocaleTimeString()}`);
Core.log(`本次共沟通 ${state.currentIndex} 个岗位`);
state.currentIndex = 0;
}
}
function startSendGreetings$1() {
if (state.isRunning) {
stopChatProcess();
return;
}
state.isRunning = true;
state.chatMode = 'GREETINGS';
// 更新按钮状态
updateChatButtonsState(true, 'greetings');
const startTime = new Date();
Core.log(`开始发送常用语,时间:${startTime.toLocaleTimeString()}`);
Core.startProcessing();
}
function startSendImageResume$1() {
if (state.isRunning) {
stopChatProcess();
return;
}
state.isRunning = true;
state.chatMode = 'IMAGE_RESUME';
updateChatButtonsState(true, 'imageResume');
const startTime = new Date();
Core.log(`开始发送图片简历,时间:${startTime.toLocaleTimeString()}`);
Core.startProcessing();
}
function startAiReply$1() {
if (state.isRunning) {
stopChatProcess();
return;
}
state.isRunning = true;
state.chatMode = 'AI_REPLY';
updateChatButtonsState(true, 'aiReply');
const startTime = new Date();
Core.log(`开始AI回复,时间:${startTime.toLocaleTimeString()}`);
Core.startProcessing();
}
function stopChatProcess() {
state.isRunning = false;
state.chatMode = null;
updateChatButtonsState(false);
// 断开消息监听
if (Core.messageObserver) {
Core.messageObserver.disconnect();
Core.messageObserver = null;
}
const stopTime = new Date();
Core.log(`停止操作,时间:${stopTime.toLocaleTimeString()}`);
}
function updateChatButtonsState(isRunning, activeMode = null) {
const greetingsBtn = document.getElementById('btn-greetings');
const resumeBtn = document.getElementById('btn-image-resume');
const aiBtn = document.getElementById('btn-ai-reply');
if (!greetingsBtn || !resumeBtn || !aiBtn) return;
if (isRunning) {
// 禁用所有按钮,除了当前运行的按钮变为"停止"
greetingsBtn.disabled = activeMode !== 'greetings';
resumeBtn.disabled = activeMode !== 'imageResume';
aiBtn.disabled = activeMode !== 'aiReply';
if (activeMode === 'greetings') {
greetingsBtn.textContent = '停止发送';
greetingsBtn.style.background = `linear-gradient(45deg, ${CONFIG.COLORS.SECONDARY}, #f44336)`;
} else if (activeMode === 'imageResume') {
resumeBtn.textContent = '停止发送';
resumeBtn.style.background = `linear-gradient(45deg, ${CONFIG.COLORS.SECONDARY}, #f44336)`;
} else if (activeMode === 'aiReply') {
aiBtn.textContent = '停止回复';
aiBtn.style.background = `linear-gradient(45deg, ${CONFIG.COLORS.SECONDARY}, #f44336)`;
}
} else {
// 恢复所有按钮
greetingsBtn.disabled = false;
resumeBtn.disabled = false;
aiBtn.disabled = false;
greetingsBtn.textContent = '发常用语';
resumeBtn.textContent = '发图片简历';
aiBtn.textContent = 'AI回复';
greetingsBtn.style.background = 'var(--primary-color)';
resumeBtn.style.background = 'var(--primary-color)';
aiBtn.style.background = 'var(--primary-color)';
}
}
function showCustomAlert(message) {
const overlay = document.createElement('div');
overlay.id = 'custom-alert-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
backdrop-filter: blur(3px);
animation: fadeIn 0.3s ease-out;
`;
const dialog = document.createElement('div');
dialog.id = 'custom-alert-dialog';
dialog.style.cssText = `
background: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
width: 90%;
max-width: 400px;
overflow: hidden;
transform: scale(0.95);
animation: scaleIn 0.3s ease-out forwards;
`;
const header = document.createElement('div');
header.style.cssText = `
padding: 16px 24px;
background: ${CONFIG.COLORS.PRIMARY};
color: white;
font-size: 18px;
font-weight: 500;
display: flex;
justify-content: space-between;
align-items: center;
`;
header.innerHTML = `JOBPILOT 投递助手`;
const content = document.createElement('div');
content.style.cssText = `
padding: 24px;
font-size: 16px;
line-height: 1.8;
color: #333;
`;
content.innerHTML = message.replace(/\n/g, '
');
const footer = document.createElement('div');
footer.style.cssText = `
padding: 12px 24px;
display: flex;
justify-content: center;
border-top: 1px solid #eee;
`;
const confirmBtn = document.createElement('button');
confirmBtn.style.cssText = `
background: ${CONFIG.COLORS.PRIMARY};
color: white;
border: none;
border-radius: 8px;
padding: 10px 24px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4);
`;
confirmBtn.textContent = '确定';
confirmBtn.addEventListener('click', () => {
overlay.remove();
});
confirmBtn.addEventListener('mouseenter', () => {
confirmBtn.style.transform = 'translateY(-2px)';
confirmBtn.style.boxShadow = '0 6px 16px rgba(33, 150, 243, 0.5)';
});
confirmBtn.addEventListener('mouseleave', () => {
confirmBtn.style.transform = 'translateY(0)';
confirmBtn.style.boxShadow = '0 4px 12px rgba(33, 150, 243, 0.4)';
});
footer.appendChild(confirmBtn);
dialog.appendChild(header);
dialog.appendChild(content);
dialog.appendChild(footer);
overlay.appendChild(dialog);
document.body.appendChild(overlay);
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleIn {
from {
transform: scale(0.95);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
`;
document.head.appendChild(style);
}
function showConfirmDialog(message, confirmCallback, cancelCallback) {
const overlay = document.createElement('div');
overlay.id = 'custom-confirm-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
backdrop-filter: blur(3px);
animation: fadeIn 0.3s ease-out;
`;
const dialog = document.createElement('div');
dialog.id = 'custom-confirm-dialog';
dialog.style.cssText = `
background: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
width: 90%;
max-width: 400px;
overflow: hidden;
transform: scale(0.95);
animation: scaleIn 0.3s ease-out forwards;
`;
const header = document.createElement('div');
header.style.cssText = `
padding: 16px 24px;
background: ${CONFIG.COLORS.PRIMARY};
color: white;
font-size: 18px;
font-weight: 500;
display: flex;
justify-content: space-between;
align-items: center;
`;
header.innerHTML = `JOBPILOT 投递助手`;
const content = document.createElement('div');
content.style.cssText = `
padding: 24px;
font-size: 16px;
line-height: 1.8;
color: #333;
`;
content.textContent = message;
const buttonArea = document.createElement('div');
buttonArea.style.cssText = `
padding: 12px 24px;
display: flex;
justify-content: space-around;
border-top: 1px solid #eee;
`;
const confirmBtn = document.createElement('button');
confirmBtn.style.cssText = `
background: ${CONFIG.COLORS.PRIMARY};
color: white;
border: none;
border-radius: 8px;
padding: 10px 24px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4);
`;
confirmBtn.textContent = '确认';
confirmBtn.addEventListener('click', () => {
if (typeof confirmCallback === 'function') {
confirmCallback();
}
overlay.remove();
});
const cancelBtn = document.createElement('button');
cancelBtn.style.cssText = `
background: #f0f0f0;
color: #666;
border: none;
border-radius: 8px;
padding: 10px 24px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
`;
cancelBtn.textContent = '取消';
cancelBtn.addEventListener('click', () => {
if (typeof cancelCallback === 'function') {
cancelCallback();
}
overlay.remove();
});
buttonArea.appendChild(cancelBtn);
buttonArea.appendChild(confirmBtn);
dialog.appendChild(header);
dialog.appendChild(content);
dialog.appendChild(buttonArea);
overlay.appendChild(dialog);
document.body.appendChild(overlay);
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleIn {
from {
transform: scale(0.95);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
`;
document.head.appendChild(style);
}
function showProgress(message, progress) {
let progressContainer = document.getElementById('progress-container');
let progressBar = document.getElementById('progress-bar');
let progressText = document.getElementById('progress-text');
if (!progressContainer) {
progressContainer = document.createElement('div');
progressContainer.id = 'progress-container';
progressContainer.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
padding: 24px;
width: 90%;
max-width: 400px;
z-index: 99999;
display: flex;
flex-direction: column;
gap: 16px;
`;
progressText = document.createElement('div');
progressText.id = 'progress-text';
progressText.style.cssText = `
font-size: 16px;
color: #333;
text-align: center;
`;
const progressBackground = document.createElement('div');
progressBackground.style.cssText = `
height: 12px;
background: #f0f0f0;
border-radius: 6px;
overflow: hidden;
`;
progressBar = document.createElement('div');
progressBar.id = 'progress-bar';
progressBar.style.cssText = `
height: 100%;
background: linear-gradient(90deg, ${CONFIG.COLORS.PRIMARY}, #4db6ac);
border-radius: 6px;
transition: width 0.3s ease;
width: 0%;
`;
progressBackground.appendChild(progressBar);
progressContainer.appendChild(progressText);
progressContainer.appendChild(progressBackground);
document.body.appendChild(progressContainer);
}
progressText.textContent = message;
progressBar.style.width = `${progress}%`;
if (progress >= 100) {
setTimeout(() => {
if (progressContainer && progressContainer.parentNode) {
progressContainer.parentNode.removeChild(progressContainer);
}
}, 1000);
}
}
window.toggleProcess = toggleProcess$1;
window.startSendGreetings = startSendGreetings$1;
window.startSendImageResume = startSendImageResume$1;
window.startAiReply = startAiReply$1;
window.showCustomAlert = showCustomAlert;
window.showConfirmDialog = showConfirmDialog;
window.showProgress = showProgress;
/**
* UI模块:面板界面 - 复古描边风
*/
const UI$1 = {
// 页面类型常量
PAGE_TYPES: {
JOB_LIST: 'jobList',
CHAT: 'chat'
},
// 当前页面类型
currentPageType: null,
// UI状态
isReady: false,
// 初始化UI
init(isLoading = false) {
this.currentPageType = location.pathname.includes('/chat')
? this.PAGE_TYPES.CHAT
: this.PAGE_TYPES.JOB_LIST;
this._applyTheme();
this.createControlPanel(isLoading);
this.createMiniIcon();
},
// 设置为就绪状态
setReady() {
this.isReady = true;
this._enableControlButton();
},
// 创建主控制面板
createControlPanel(isLoading = false) {
if (document.getElementById('boss-pro-panel')) {
document.getElementById('boss-pro-panel').remove();
}
elements.panel = this._createPanel();
const header = this._createHeader();
const controls = this._createPageControls(isLoading);
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);
},
// 应用主题配置 - 复古描边风
_applyTheme() {
CONFIG.COLORS = this.currentPageType === this.PAGE_TYPES.JOB_LIST
? this.THEMES.JOB_LIST
: this.THEMES.CHAT;
// 将颜色配置应用到CSS变量
document.documentElement.style.setProperty('--primary-color', CONFIG.COLORS.primary);
document.documentElement.style.setProperty('--secondary-color', CONFIG.COLORS.secondary);
document.documentElement.style.setProperty('--accent-color', CONFIG.COLORS.accent);
document.documentElement.style.setProperty('--neutral-color', CONFIG.COLORS.neutral);
},
// 复古 颜色主题配置
THEMES: {
JOB_LIST: {
primary: '#6FC2FF', // 品牌蓝
secondary: '#F4EFEA', // 奶油色
accent: '#383838', // 墨黑
neutral: '#A1A1A1' // 中性灰
},
CHAT: {
primary: '#53DBC9', // 品牌绿
secondary: '#F4EFEA', // 奶油色
accent: '#383838', // 墨黑
neutral: '#A1A1A1' // 中性灰
}
},
// 创建面板容器 - 复古描边风
_createPanel() {
const panel = document.createElement('div');
panel.id = 'boss-pro-panel';
panel.className = this.currentPageType === this.PAGE_TYPES.JOB_LIST
? 'boss-joblist-panel'
: 'boss-chat-panel';
// 复古描边风样式
const baseStyles = `
position: fixed;
top: 40px;
right: 30px;
width: clamp(320px, 90vw, 420px);
border-radius: 2px;
padding: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
z-index: 2147483647;
display: flex;
flex-direction: column;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: #FFFFFF;
box-shadow: 0 10px 25px rgba(56, 56, 56, 0.2);
border: 2px solid #383838;
cursor: default;
`;
panel.style.cssText = baseStyles;
// 设置RGB颜色变量
const rgbColor = this._hexToRgb(CONFIG.COLORS.primary);
document.documentElement.style.setProperty('--primary-rgb', rgbColor);
return panel;
},
// 创建头部 - 复古描边风
_createHeader() {
const header = document.createElement('div');
header.className = this.currentPageType === this.PAGE_TYPES.JOB_LIST
? 'boss-header'
: 'boss-chat-header';
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background: #F4EFEA;
border-bottom: 2px solid #383838;
cursor: move;
`;
const title = this._createTitle();
// 创建按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = `
display: flex;
gap: 8px;
`;
// 添加清空日志按钮
const clearLogBtn = this._createIconButton('🗑', () => {
elements.log.innerHTML = `
🚢 欢迎使用 JobPilot 海投助手!
`;
}, this.currentPageType === this.PAGE_TYPES.JOB_LIST
? '清空日志'
: '清空聊天记录');
// 添加设置按钮
const settingsBtn = this._createIconButton('⚙', () => {
showSettingsDialog();
}, this.currentPageType === this.PAGE_TYPES.JOB_LIST
? '插件设置'
: 'AI人设设置');
// 添加最小化按钮
const closeBtn = this._createIconButton('−', () => {
state.isMinimized = true;
elements.panel.style.transform = 'translateY(-120%)';
elements.miniIcon.style.display = 'flex';
}, this.currentPageType === this.PAGE_TYPES.JOB_LIST
? '最小化海投面板'
: '最小化聊天面板');
buttonContainer.append(clearLogBtn, settingsBtn, closeBtn);
header.append(title, buttonContainer);
return header;
},
// 创建标题 - 复古描边风
_createTitle() {
const title = document.createElement('div');
title.style.display = 'flex';
title.style.alignItems = 'center';
title.style.gap = '12px';
// 复古描边风图标
const icon = document.createElement('div');
icon.style.cssText = `
width: 44px;
height: 44px;
background: var(--primary-color);
border: 2px solid #383838;
border-radius: 2px;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
`;
icon.textContent = '🚢';
const titleText = document.createElement('div');
const mainTitle = document.createElement('h3');
mainTitle.style.cssText = `
margin: 0;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 18px;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.02em;
color: #383838;
line-height: 1.2;
`;
mainTitle.innerHTML = this.currentPageType === this.PAGE_TYPES.JOB_LIST
? 'BOSS 海投 助手'
: 'BOSS 智能 聊天';
const subTitle = document.createElement('p');
subTitle.style.cssText = `
margin: 2px 0 0 0;
font-size: 12px;
font-weight: 300;
color: var(--neutral-color);
`;
subTitle.textContent = this.currentPageType === this.PAGE_TYPES.JOB_LIST
? 'Automate your job search'
: 'AI-powered conversations';
titleText.appendChild(mainTitle);
titleText.appendChild(subTitle);
title.appendChild(icon);
title.appendChild(titleText);
return title;
},
// 创建页面控制区域
_createPageControls(isLoading = false) {
if (this.currentPageType === this.PAGE_TYPES.JOB_LIST) {
return this._createJobListControls(isLoading);
} else {
return this._createChatControls(isLoading);
}
},
// 创建职位列表页面控制区域
_createJobListControls(isLoading = false) {
const container = document.createElement('div');
container.className = 'boss-joblist-controls';
container.style.cssText = `
padding: 16px;
background: #FFFFFF;
`;
// 筛选条件区域
const filterContainer = this._createFilterContainer();
elements.controlBtn = this._createTextButton(
isLoading ? '初始化中...' : '启动海投',
isLoading ? '#A1A1A1' : 'var(--primary-color)',
() => {
if (this.isReady) toggleProcess();
},
isLoading
);
// 居中控制按钮
const btnContainer = document.createElement('div');
btnContainer.style.cssText = `
display: flex;
justify-content: center;
width: 100%;
`;
btnContainer.appendChild(elements.controlBtn);
container.append(filterContainer, btnContainer);
return container;
},
// 创建聊天页面控制区域
_createChatControls(isLoading = false) {
const container = document.createElement('div');
container.className = 'boss-chat-controls';
container.style.cssText = `
padding: 16px;
background: #FFFFFF;
`;
// 人设和模板选择
const configRow = document.createElement('div');
configRow.style.cssText = `
display: flex;
gap: 12px;
margin-bottom: 16px;
`;
// 动态获取AI人设选项
const getPersonaOptions = () => {
const defaultOptions = [
{ value: 'default', text: '默认' }
];
const customPersonas = settings.customPersonas || [];
return [...defaultOptions, ...customPersonas];
};
const personaCol = this._createSelectControl('AI人设:', 'ai-persona-selector', getPersonaOptions());
const templateCol = this._createSelectControl('仅回复岗位包含:', 'reply-template-selector', [
{ value: 'standard', text: '标准' },
{ value: 'brief', text: '简洁' },
{ value: 'detailed', text: '详细' }
]);
elements.personaSelector = personaCol.querySelector('select');
configRow.append(personaCol, templateCol);
// 按钮容器 - 使用Grid布局优化美观度
const btnContainer = document.createElement('div');
btnContainer.style.cssText = `
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
width: 100%;
`;
// 1. 发常用语按钮
const greetingsBtn = this._createTextButton(
'发常用语',
'var(--primary-color)',
() => {
if (this.isReady) startSendGreetings();
},
isLoading
);
greetingsBtn.id = 'btn-greetings';
greetingsBtn.style.maxWidth = '100%'; // 填满网格
elements.greetingsBtn = greetingsBtn; // 保存引用
// 2. 发图片简历按钮
const resumeBtn = this._createTextButton(
'发图片简历',
'var(--primary-color)',
() => {
if (this.isReady) startSendImageResume();
},
isLoading
);
resumeBtn.id = 'btn-image-resume';
resumeBtn.style.maxWidth = '100%'; // 填满网格
elements.resumeBtn = resumeBtn; // 保存引用
// 3. AI回复按钮 - 占据两列
const aiBtn = this._createTextButton(
'AI回复',
'var(--primary-color)',
() => {
if (this.isReady) startAiReply();
},
isLoading
);
aiBtn.id = 'btn-ai-reply';
aiBtn.style.gridColumn = 'span 2'; // 占据整行
aiBtn.style.maxWidth = '100%'; // 填满网格
elements.aiBtn = aiBtn; // 保存引用
btnContainer.append(greetingsBtn, resumeBtn, aiBtn);
container.append(configRow, btnContainer);
return container;
},
// 创建筛选容器 - 复古描边风
_createFilterContainer() {
const filterContainer = document.createElement('div');
filterContainer.style.cssText = `
background: #F4EFEA;
border: 2px solid #383838;
border-radius: 2px;
padding: 16px;
margin-bottom: 16px;
`;
const filterRow = document.createElement('div');
filterRow.style.cssText = `
display: flex;
gap: 12px;
margin-bottom: 12px;
`;
const includeFilterCol = this._createInputControl('职位名包含:', 'include-filter', '如:前端,开发');
const locationFilterCol = this._createInputControl('工作地包含:', 'location-filter', '如:杭州,滨江');
elements.includeInput = includeFilterCol.querySelector('input');
elements.locationInput = locationFilterCol.querySelector('input');
filterRow.append(includeFilterCol, locationFilterCol);
filterContainer.append(filterRow);
return filterContainer;
},
// 创建输入控件 - 复古描边风
_createInputControl(labelText, id, placeholder) {
const controlCol = document.createElement('div');
controlCol.style.cssText = 'flex: 1;';
const label = document.createElement('label');
label.textContent = labelText;
label.style.cssText = `
display: block;
margin-bottom: 8px;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 12px;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #383838;
`;
const input = document.createElement('input');
input.id = id;
input.placeholder = placeholder;
input.style.cssText = `
width: 100%;
padding: 12px 16px;
border: 2px solid #383838;
border-radius: 2px;
font-family: 'Inter', sans-serif;
font-size: 14px;
font-weight: 300;
background: #FFFFFF;
transition: all 0.2s ease;
`;
input.addEventListener('focus', () => {
input.style.outline = '2px solid #2BA5FF';
input.style.outlineOffset = '0';
input.style.borderColor = '#2BA5FF';
});
input.addEventListener('blur', () => {
input.style.outline = 'none';
input.style.borderColor = '#383838';
});
controlCol.append(label, input);
return controlCol;
},
// 创建选择控件 - 复古描边风
_createSelectControl(labelText, id, options) {
const controlCol = document.createElement('div');
controlCol.style.cssText = 'flex: 1;';
const label = document.createElement('label');
label.textContent = labelText;
label.style.cssText = `
display: block;
margin-bottom: 8px;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 12px;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #383838;
`;
const select = document.createElement('select');
select.id = id;
select.style.cssText = `
width: 100%;
padding: 12px 16px;
border: 2px solid #383838;
border-radius: 2px;
font-family: 'Inter', sans-serif;
font-size: 14px;
background: #FFFFFF;
color: #383838;
transition: all 0.2s ease;
`;
options.forEach(option => {
const opt = document.createElement('option');
opt.value = option.value;
opt.textContent = option.text;
select.appendChild(opt);
});
select.addEventListener('focus', () => {
select.style.outline = '2px solid #2BA5FF';
select.style.outlineOffset = '0';
});
select.addEventListener('blur', () => {
select.style.outline = 'none';
});
controlCol.append(label, select);
return controlCol;
},
// 日志区域 - 复古描边风
_createLogger() {
const log = document.createElement('div');
log.id = 'pro-log';
log.className = this.currentPageType === this.PAGE_TYPES.JOB_LIST
? 'boss-joblist-log'
: 'boss-chat-log';
const height = this.currentPageType === this.PAGE_TYPES.JOB_LIST ? '300px' : '300px';
log.style.cssText = `
height: ${height};
overflow-y: auto;
background: #F4EFEA;
border-top: 2px solid #383838;
padding: 16px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
line-height: 1.6;
transition: all 0.3s ease;
user-select: text;
`;
// 自定义滚动条
const style = document.createElement('style');
style.textContent = `
#pro-log::-webkit-scrollbar {
width: 8px;
}
#pro-log::-webkit-scrollbar-track {
background: #F8F8F7;
}
#pro-log::-webkit-scrollbar-thumb {
background: #A1A1A1;
border-radius: 0;
}
#pro-log::-webkit-scrollbar-thumb:hover {
background: #383838;
}
`;
document.head.appendChild(style);
return log;
},
// 面板页脚 - 复古描边风
_createFooter() {
const footer = document.createElement('div');
footer.className = this.currentPageType === this.PAGE_TYPES.JOB_LIST
? 'boss-joblist-footer'
: 'boss-chat-footer';
footer.style.cssText = `
padding: 12px 16px;
background: #383838;
color: #FFFFFF;
font-size: 11px;
font-weight: 300;
text-align: center;
border-top: 2px solid #383838;
`;
footer.textContent = 'JobPilot · © 2025 fancyboi999';
return footer;
},
// 文本按钮 - 复古描边风
_createTextButton(text, bgColor, onClick, isDisabled = false) {
const btn = document.createElement('button');
btn.className = 'boss-btn';
btn.textContent = text;
btn.disabled = isDisabled;
const baseCursor = isDisabled ? 'not-allowed' : 'pointer';
const baseOpacity = isDisabled ? '0.6' : '1';
btn.style.cssText = `
width: 100%;
max-width: 360px;
margin: 0 auto; /* 居中显示 */
padding: 12px 20px;
background: ${bgColor};
color: #383838;
border: 2px solid #383838;
border-radius: 2px;
cursor: ${baseCursor};
font-family: 'Monaco', 'Consolas', monospace;
font-size: 14px;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.02em;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
justify-content: center;
align-items: center;
opacity: ${baseOpacity};
`;
if (!isDisabled) {
btn.addEventListener('mouseenter', () => {
btn.style.background = '#2BA5FF';
btn.style.transform = 'translate(7px, -7px)';
btn.style.boxShadow = '-7px 7px 0 #383838';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = bgColor;
btn.style.transform = 'translate(0, 0)';
btn.style.boxShadow = 'none';
});
btn.addEventListener('mousedown', () => {
btn.style.transform = 'translate(0, 0)';
btn.style.boxShadow = 'none';
});
}
btn.addEventListener('click', onClick);
return btn;
},
// 启用控制按钮
_enableControlButton() {
if (this.currentPageType === this.PAGE_TYPES.JOB_LIST) {
if (!elements.controlBtn) return;
const newBgColor = 'var(--primary-color)';
elements.controlBtn.disabled = false;
elements.controlBtn.textContent = '启动海投';
elements.controlBtn.style.cursor = 'pointer';
elements.controlBtn.style.opacity = '1';
elements.controlBtn.style.background = newBgColor;
elements.controlBtn.style.animation = 'pulse-ready 0.6s ease-out';
// 绑定事件
this._bindButtonEffects(elements.controlBtn, newBgColor);
} else {
// 聊天页面启用三个按钮
const buttons = [elements.greetingsBtn, elements.resumeBtn, elements.aiBtn];
const newBgColor = 'var(--primary-color)';
buttons.forEach(btn => {
if (btn) {
btn.disabled = false;
btn.style.cursor = 'pointer';
btn.style.opacity = '1';
btn.style.background = newBgColor;
btn.style.animation = 'pulse-ready 0.6s ease-out';
this._bindButtonEffects(btn, newBgColor);
}
});
}
// 添加动画样式
if (!document.getElementById('button-ready-animation')) {
const style = document.createElement('style');
style.id = 'button-ready-animation';
style.textContent = `
@keyframes pulse-ready {
0% { transform: scale(1); }
50% { transform: scale(1.05); box-shadow: -7px 7px 0 #383838, 0 0 20px rgba(111, 194, 255, 0.6); }
100% { transform: scale(1); }
}
`;
document.head.appendChild(style);
}
},
// 绑定按钮特效
_bindButtonEffects(btn, bgColor) {
btn.onmouseenter = () => {
btn.style.background = '#2BA5FF';
btn.style.transform = 'translate(7px, -7px)';
btn.style.boxShadow = '-7px 7px 0 #383838';
};
btn.onmouseleave = () => {
btn.style.background = bgColor;
btn.style.transform = 'translate(0, 0)';
btn.style.boxShadow = 'none';
};
btn.onmousedown = () => {
btn.style.transform = 'translate(0, 0)';
btn.style.boxShadow = 'none';
};
},
// 图标按钮 - 复古描边风
_createIconButton(icon, onClick, title) {
const btn = document.createElement('button');
btn.className = 'boss-icon-btn';
btn.innerHTML = icon;
btn.title = title;
btn.style.cssText = `
width: 32px;
height: 32px;
border-radius: 2px;
border: 2px solid #383838;
background: #FFFFFF;
cursor: pointer;
font-size: 14px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
justify-content: center;
align-items: center;
color: #383838;
`;
btn.addEventListener('click', onClick);
btn.addEventListener('mouseenter', () => {
btn.style.background = '#F4EFEA';
btn.style.transform = 'translate(3px, -3px)';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = '#FFFFFF';
btn.style.transform = 'translate(0, 0)';
});
btn.addEventListener('mousedown', () => {
btn.style.background = '#2BA5FF';
btn.style.color = '#FFFFFF';
btn.style.transform = 'translate(0, 0)';
});
btn.addEventListener('mouseup', () => {
btn.style.background = '#F4EFEA';
btn.style.color = '#383838';
});
return btn;
},
// 设置面板可拖动功能
_makeDraggable(panel) {
const header = panel.querySelector('.boss-header, .boss-chat-header');
if (!header) return;
header.style.cursor = 'move';
let isDragging = false;
let startX = 0, startY = 0;
let initialX = panel.offsetLeft, initialY = panel.offsetTop;
header.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initialX = panel.offsetLeft;
initialY = panel.offsetTop;
panel.style.transition = 'none';
panel.style.zIndex = '2147483647';
});
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 cubic-bezier(0.4, 0, 0.2, 1)';
panel.style.zIndex = '2147483646';
}
});
},
// 最小化图标 - 复古描边风
createMiniIcon() {
elements.miniIcon = document.createElement('div');
elements.miniIcon.style.cssText = `
width: 60px;
height: 60px;
position: fixed;
bottom: 30px;
right: 30px;
background: var(--primary-color);
border: 2px solid #383838;
border-radius: 50%;
box-shadow: 0 10px 25px rgba(56, 56, 56, 0.2);
cursor: pointer;
display: none;
justify-content: center;
align-items: center;
color: #383838;
font-size: 28px;
z-index: 2147483647;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
`;
elements.miniIcon.textContent = '🚢';
elements.miniIcon.addEventListener('mouseenter', () => {
elements.miniIcon.style.background = '#2BA5FF';
elements.miniIcon.style.transform = 'translate(5px, -5px) scale(1.1)';
elements.miniIcon.style.boxShadow = '-5px 5px 0 #383838, 0 10px 25px rgba(56, 56, 56, 0.2)';
});
elements.miniIcon.addEventListener('mouseleave', () => {
elements.miniIcon.style.background = 'var(--primary-color)';
elements.miniIcon.style.transform = 'scale(1)';
elements.miniIcon.style.boxShadow = '0 10px 25px rgba(56, 56, 56, 0.2)';
});
elements.miniIcon.addEventListener('click', () => {
state.isMinimized = false;
elements.panel.style.transform = 'translateY(0)';
elements.miniIcon.style.display = 'none';
});
document.body.appendChild(elements.miniIcon);
},
// 十六进制颜色转RGB
_hexToRgb(hex) {
hex = hex.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return `${r}, ${g}, ${b}`;
}
};
// 导出到全局
window.UI = UI$1;
const Core$1 = {
basicInterval: parseInt(localStorage.getItem('basicInterval')) || CONFIG.BASIC_INTERVAL,
operationInterval: parseInt(localStorage.getItem('operationInterval')) || CONFIG.OPERATION_INTERVAL,
messageObserver: null,
lastProcessedMessage: null,
processingMessage: false,
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(this.basicInterval);
}
},
async autoScrollJobList() {
// 自动滚动加载所有岗位卡片,直到页面底部
return new Promise((resolve) => {
const cardSelector = 'li.job-card-box';
const maxHistory = 3;
const waitTime = this.basicInterval;
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);
};
});
},
async processJobList() {
// 点击岗位列表中的岗位卡片, operationInterval控制点击卡片的时间间隔
const excludeHeadhunters = JSON.parse(localStorage.getItem('excludeHeadhunters') || 'false');
const activeStatusFilter = JSON.parse(localStorage.getItem('recruiterActivityStatus') || '["不限"]');
// 使用新的包含关键词筛选逻辑
state.jobList = Array.from(document.querySelectorAll('li.job-card-box')).filter(card => {
const title = card.querySelector('.job-name')?.textContent?.toLowerCase() || '';
// 优先从卡片内取地址,兜底从常见位置类名取
const addressText = (
card.querySelector('.job-address-desc')?.textContent ||
card.querySelector('.company-location')?.textContent ||
card.querySelector('.job-area')?.textContent ||
''
).toLowerCase().trim();
const headhuntingElement = card.querySelector('.job-tag-icon');
const altText = headhuntingElement ? headhuntingElement.alt : '';
// 职位名包含(空数组表示不限制)
const includeMatch = state.includeKeywords.length === 0 ||
state.includeKeywords.some(kw => kw && title.includes(kw.trim()));
// 工作地包含(空数组表示不限制)
const locationMatch = state.locationKeywords.length === 0 ||
state.locationKeywords.some(kw => kw && addressText.includes(kw.trim()));
const excludeHeadhunterMatch = !excludeHeadhunters || !altText.includes("猎头");
return includeMatch && locationMatch && excludeHeadhunterMatch;
});
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();
await this.delay(this.operationInterval * 2);
let activeTime = '未知';
const onlineTag = document.querySelector('.boss-online-tag');
if (onlineTag && onlineTag.textContent.trim() === '在线') {
activeTime = '在线';
} else {
const activeTimeElement = document.querySelector('.boss-active-time');
activeTime = activeTimeElement?.textContent?.trim() || '未知';
}
const isActiveStatusMatch = activeStatusFilter.includes('不限') || activeStatusFilter.includes(activeTime);
if (!isActiveStatusMatch) {
this.log(`跳过: 招聘者状态 "${activeTime}"`);
state.currentIndex++;
return;
}
const includeLog = state.includeKeywords.length ? `职位名包含[${state.includeKeywords.join('、')}]` : '职位名不限';
const locationLog = state.locationKeywords.length ? `工作地包含[${state.locationKeywords.join('、')}]` : '工作地不限';
this.log(`正在沟通:${++state.currentIndex}/${state.jobList.length},${includeLog},${locationLog},招聘者"${activeTime}"`);
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(this.operationInterval * 4);
const btn = [...document.querySelectorAll('.default-btn.cancel-btn')]
.find(b => b.textContent.trim() === '留在此页');
if (btn) {
btn.click();
await this.delay(this.operationInterval * 2);
}
},
async handleChatPage() {
this.resetMessageState();
if (this.messageObserver) {
this.messageObserver.disconnect();
this.messageObserver = null;
}
const latestChatLi = await this.waitForElement(this.getLatestChatLi);
if (!latestChatLi) return;
const nameEl = latestChatLi.querySelector('.name-text');
const companyEl = latestChatLi.querySelector('.name-box span:nth-child(2)');
const name = (nameEl?.textContent || '未知').trim();
const company = (companyEl?.textContent || '').trim();
const hrKey = `${name}-${company}`.toLowerCase();
if (!latestChatLi.classList.contains('last-clicked')) {
await this.simulateClick(latestChatLi.querySelector('.figure'));
latestChatLi.classList.add('last-clicked');
state.hrInteractions.currentTopHRKey = hrKey;
await this.delay(this.operationInterval);
await HRInteractionManager.handleHRInteraction(hrKey);
}
await this.setupMessageObserver(hrKey);
},
resetMessageState() {
this.lastProcessedMessage = null;
this.processingMessage = false;
},
async setupMessageObserver(hrKey) {
const chatContainer = await this.waitForElement('.chat-message .im-list');
if (!chatContainer) return;
this.messageObserver = new MutationObserver(async (mutations) => {
let hasNewFriendMessage = false;
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
hasNewFriendMessage = Array.from(mutation.addedNodes).some(node =>
node.classList?.contains('item-friend')
);
if (hasNewFriendMessage) break;
}
}
if (hasNewFriendMessage) {
await this.handleNewMessage(hrKey);
}
});
this.messageObserver.observe(chatContainer, { childList: true, subtree: true });
},
async handleNewMessage(hrKey) {
if (!state.isRunning) return;
// 如果处于特定模式且不是AI回复模式,不处理新消息
if (state.chatMode && state.chatMode !== 'AI_REPLY') return;
if (this.processingMessage) return;
this.processingMessage = true;
try {
await this.delay(this.operationInterval);
const lastMessage = await this.getLastFriendMessageText();
if (!lastMessage) return;
const cleanedMessage = this.cleanMessage(lastMessage);
const shouldSendResumeOnly = cleanedMessage.includes('简历');
if (cleanedMessage === this.lastProcessedMessage) return;
this.lastProcessedMessage = cleanedMessage;
this.log(`对方: ${lastMessage}`);
await this.delay(200);
const updatedMessage = await this.getLastFriendMessageText();
if (updatedMessage && this.cleanMessage(updatedMessage) !== cleanedMessage) {
await this.handleNewMessage(hrKey);
return;
}
const autoSendResume = JSON.parse(localStorage.getItem('useAutoSendResume') || 'true');
const autoReplyEnabled = JSON.parse(localStorage.getItem('autoReply') || 'true');
if (shouldSendResumeOnly && autoSendResume) {
this.log('对方提到"简历",正在发送简历');
const sent = await HRInteractionManager.sendResume();
if (sent) {
state.hrInteractions.sentResumeHRs.add(hrKey);
StatePersistence.saveState();
this.log(`已向 ${hrKey} 发送简历`);
}
} else if (autoReplyEnabled) {
await HRInteractionManager.handleHRInteraction(hrKey);
}
await this.delay(200);
const postReplyMessage = await this.getLastFriendMessageText();
} catch (error) {
this.log(`处理消息出错: ${error.message}`);
} finally {
this.processingMessage = false;
}
},
cleanMessage(message) {
if (!message) return '';
let clean = message.replace(/<[^>]*>/g, '');
clean = clean.trim().replace(/\s+/g, ' ').replace(/[\u200B-\u200D\uFEFF]/g, '');
return clean;
},
getLatestChatLi() {
return document.querySelector('li[role="listitem"][class]:has(.friend-content-warp)');
},
async aiReply() {
if (!state.isRunning) return;
try {
const autoReplyEnabled = JSON.parse(localStorage.getItem('autoReply') || 'true');
if (!autoReplyEnabled) {
return false;
}
if (!state.ai.useAiReply) {
return false;
}
const lastMessage = await this.getLastFriendMessageText();
if (!lastMessage) return false;
const today = new Date().toISOString().split('T')[0];
if (state.ai.lastAiDate !== today) {
state.ai.replyCount = 0;
state.ai.lastAiDate = today;
StatePersistence.saveState();
}
const maxReplies = state.user.isPremiumUser ? CONFIG.AI.MAX_REPLIES_PREMIUM : CONFIG.AI.MAX_REPLIES_FREE;
if (state.ai.replyCount >= maxReplies) {
this.log(`AI回复已达上限,充值即可继续使用`);
return false;
}
const aiReplyText = await this.requestAi(lastMessage);
if (!aiReplyText) return false;
this.log(`AI回复: ${aiReplyText.slice(0, 30)}...`);
state.ai.replyCount++;
StatePersistence.saveState();
const inputBox = await this.waitForElement('#chat-input');
if (!inputBox) return false;
inputBox.textContent = '';
inputBox.focus();
document.execCommand('insertText', false, aiReplyText);
await this.delay(this.operationInterval / 10);
const sendButton = document.querySelector('.btn-send');
if (sendButton) {
await this.simulateClick(sendButton);
} else {
const enterKeyEvent = new KeyboardEvent('keydown', {
key: 'Enter', keyCode: 13, code: 'Enter', which: 13, bubbles: true
});
inputBox.dispatchEvent(enterKeyEvent);
}
return true;
} catch (error) {
this.log(`AI回复出错: ${error.message}`);
return false;
}
},
async requestAi(message) {
const authToken = (function () {
const c = [0x73, 0x64, 0x56, 0x45, 0x44, 0x41, 0x42, 0x6a, 0x5a, 0x65, 0x49, 0x6b, 0x77,
0x58, 0x4e, 0x42, 0x46, 0x4e, 0x42, 0x73, 0x3a, 0x43, 0x71, 0x4d, 0x58, 0x6a,
0x71, 0x65, 0x50, 0x56, 0x43, 0x4a, 0x62, 0x55, 0x59, 0x4a, 0x50, 0x63, 0x69, 0x70, 0x4a
];
return c.map(d => String.fromCharCode(d)).join('');
})();
const apiUrl = (function () {
const e = '68747470733a2f2f737061726b2d6170692d6f70656e2e78662d79756e2e636f6d2f76312f636861742f636f6d706c6574696f6e73';
return e.replace(/../g, f => String.fromCharCode(parseInt(f, 16)));
})();
const requestBody = {
model: 'lite',
messages: [
{ role: 'system', content: localStorage.getItem('aiRole') || '你是有经验的求职者,你会用口语化的表达(如“行”、“呃”)和语气词(如“啊”、“吗”)使对话自然。你回复对方很肯定且言简意赅,不会发送段落和长句子。' },
{ role: 'user', content: message }
],
temperature: 0.9, top_p: 0.8, max_tokens: 512
};
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST', url: apiUrl,
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + authToken },
data: JSON.stringify(requestBody),
onload: (response) => {
try {
const result = JSON.parse(response.responseText);
if (result.code !== 0) throw new Error('API错误: ' + result.message + '(Code: ' + result.code + ')');
resolve(result.choices[0].message.content.trim());
} catch (error) {
reject(new Error('响应解析失败: ' + error.message + '\n原始响应: ' + response.responseText));
}
},
onerror: (error) => reject(new Error('网络请求失败: ' + error))
});
});
},
async getLastFriendMessageText() {
try {
const chatContainer = document.querySelector('.chat-message .im-list');
if (!chatContainer) return null;
const friendMessages = Array.from(chatContainer.querySelectorAll('li.message-item.item-friend'));
if (friendMessages.length === 0) return null;
const lastMessageEl = friendMessages[friendMessages.length - 1];
const textEl = lastMessageEl.querySelector('.text span');
return textEl?.textContent?.trim() || null;
} catch (error) {
this.log(`获取消息出错: ${error.message}`);
return null;
}
},
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: document.defaultView, clientX: x, clientY: y, ...options
});
element.dispatchEvent(event);
};
dispatchMouseEvent('mouseover'); await this.delay(30);
dispatchMouseEvent('mousemove'); await this.delay(30);
dispatchMouseEvent('mousedown', { button: 0 }); await this.delay(30);
dispatchMouseEvent('mouseup', { button: 0 }); await this.delay(30);
dispatchMouseEvent('click', { button: 0 });
},
async waitForElement(selectorOrFunction, timeout = 5000) {
return new Promise((resolve) => {
let element;
if (typeof selectorOrFunction === 'function') element = selectorOrFunction();
else element = document.querySelector(selectorOrFunction);
if (element) return resolve(element);
const timeoutId = setTimeout(() => { observer.disconnect(); resolve(null); }, timeout);
const observer = new MutationObserver(() => {
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.operation.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;
}
}
};
window.Core = Core$1;
const letter$1 = {
showLetterToUser: function () {
const COLORS = {
ink: '#383838',
cream: '#F4EFEA',
paper: '#FFFFFF',
accent: '#6FC2FF',
accentDark: '#2BA5FF',
neutral: '#A1A1A1'
};
const overlay = document.createElement('div');
overlay.id = 'letter-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(244, 239, 234, 0.95);
display: flex;
justify-content: center;
align-items: center;
padding: 24px;
z-index: 2147483646;
border: 2px solid ${COLORS.ink};
animation: md-overlay-in 0.3s ease forwards;
`;
const card = document.createElement('div');
card.id = 'motherduck-letter-card';
card.style.cssText = `
width: clamp(320px, 90vw, 640px);
background: ${COLORS.cream};
border: 2px solid ${COLORS.ink};
border-radius: 2px;
box-shadow: -18px 18px 0 ${COLORS.ink}, 0 25px 45px rgba(56, 56, 56, 0.25);
padding: 32px;
position: relative;
font-family: 'Inter', sans-serif;
animation: md-letter-enter 0.4s ease forwards;
`;
const ribbon = document.createElement('div');
ribbon.style.cssText = `
position: absolute;
top: -24px;
left: 32px;
background: ${COLORS.accent};
color: ${COLORS.ink};
border: 2px solid ${COLORS.ink};
padding: 6px 18px;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.1em;
box-shadow: -6px 6px 0 ${COLORS.ink};
`;
ribbon.textContent = 'FIELD NOTE';
const header = document.createElement('div');
header.style.cssText = `
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
`;
const icon = document.createElement('div');
icon.textContent = '🚢';
icon.style.cssText = `
width: 64px;
height: 64px;
border: 2px solid ${COLORS.ink};
background: ${COLORS.paper};
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
`;
const headerText = document.createElement('div');
const title = document.createElement('h2');
title.textContent = '致海投助手用户';
title.style.cssText = `
margin: 0;
font-size: 20px;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.08em;
color: ${COLORS.ink};
`;
const subtitle = document.createElement('p');
subtitle.textContent = 'JobPilot 求职行动手册 · 2025';
subtitle.style.cssText = `
margin: 6px 0 0;
color: ${COLORS.neutral};
font-size: 13px;
letter-spacing: 0.05em;
`;
headerText.appendChild(title);
headerText.appendChild(subtitle);
header.append(icon, headerText);
const content = document.createElement('div');
content.style.cssText = `
font-size: 15px;
line-height: 1.7;
color: ${COLORS.ink};
`;
content.innerHTML = `
你好,未来的成功人士:
我是 fancyboi999——曾在求职路上狂刷 BOSS,却换来无尽的“稍后联系”。于是我把所有痛点写进代码,让海投这件事带点幽默感和效率。
这封信交到你手上,说明你已经准备升级求职姿势。记得以下 3 条作战准则:
- 自动扫描 + 打招呼:像卷轴一样收集岗位,再精准出击。
- AI 智能回话:24 小时保持在线,HR 不再被晾着。
- 个性化策略:人设、话术全定制,形成属于你的专属态度。
工具是助推器,你的诚意才是终极武器。愿你被更多好岗位看见,搞定 Offer,顺便来 Repo 点颗 🌟。
—— fancyboi999
`;
const actions = document.createElement('div');
actions.style.cssText = `
margin-top: 24px;
display: flex;
gap: 12px;
flex-wrap: wrap;
`;
const applyButtonMotion = (button, accent = false) => {
const baseBg = accent ? COLORS.accent : COLORS.paper;
const hoverBg = accent ? COLORS.accentDark : COLORS.cream;
button.style.background = baseBg;
button.style.border = `2px solid ${COLORS.ink}`;
button.style.color = COLORS.ink;
button.style.fontFamily = `'Monaco', 'Consolas', monospace`;
button.style.textTransform = 'uppercase';
button.style.letterSpacing = '0.08em';
button.style.padding = '12px 24px';
button.style.cursor = 'pointer';
button.style.boxShadow = `-6px 6px 0 ${COLORS.ink}`;
button.style.transition = 'transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease';
button.addEventListener('mouseenter', () => {
button.style.background = hoverBg;
button.style.transform = 'translate(4px, -4px)';
button.style.boxShadow = `-4px 4px 0 ${COLORS.ink}`;
});
button.addEventListener('mouseleave', () => {
button.style.background = baseBg;
button.style.transform = 'translate(0, 0)';
button.style.boxShadow = `-6px 6px 0 ${COLORS.ink}`;
});
button.addEventListener('mousedown', () => {
button.style.transform = 'translate(0, 0)';
button.style.boxShadow = 'none';
});
button.addEventListener('mouseup', () => {
button.style.transform = 'translate(4px, -4px)';
button.style.boxShadow = `-4px 4px 0 ${COLORS.ink}`;
});
};
const closeLetter = (callback) => {
card.style.animation = 'md-letter-exit 0.35s ease forwards';
overlay.style.animation = 'md-overlay-out 0.3s ease forwards';
setTimeout(() => {
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
if (typeof callback === 'function') {
callback();
}
}, 280);
};
const startButton = document.createElement('button');
startButton.textContent = '立即出发';
applyButtonMotion(startButton, true);
startButton.addEventListener('click', () => {
closeLetter(() => {
window.open('https://github.com/fancyboi999', '_blank');
});
});
const laterButton = document.createElement('button');
laterButton.textContent = '稍后阅读';
applyButtonMotion(laterButton, false);
laterButton.addEventListener('click', () => closeLetter());
actions.append(startButton, laterButton);
card.append(ribbon, header, content, actions);
overlay.appendChild(card);
document.body.appendChild(overlay);
const style = document.createElement('style');
style.textContent = `
@keyframes md-letter-enter {
from { transform: translateY(30px) scale(0.95); opacity: 0; }
to { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes md-letter-exit {
from { transform: translateY(0) scale(1); opacity: 1; }
to { transform: translateY(-20px) scale(0.97); opacity: 0; }
}
@keyframes md-overlay-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes md-overlay-out {
from { opacity: 1; }
to { opacity: 0; }
}
@media (max-width: 520px) {
#motherduck-letter-card { padding: 24px; }
#motherduck-letter-card h2 { font-size: 18px; }
}
`;
document.head.appendChild(style);
}
};
// 导出到全局
window.letter = letter$1;
const GUIDE_THEME = {
ink: '#383838',
cream: '#F4EFEA',
paper: '#FFFFFF',
accent: '#6FC2FF',
accentDark: '#2BA5FF',
warning: '#FF7169'
};
const guide$1 = {
steps: [
{
target: 'div.city-label.active',
content: '👋 海投前,先在BOSS筛选出岗位!\n\n助手会先滚动收集界面上显示的岗位,\n随后依次进行沟通~',
highlightColor: '#4285f4', // 主蓝色
arrowPosition: 'bottom',
defaultPosition: { left: '50%', top: '20%', transform: 'translateX(-50%)' }
},
{
target: 'a[ka="header-jobs"]',
content: '🚀 职位页操作流程:\n\n1️⃣ 扫描职位卡片\n2️⃣ 点击"立即沟通"(需开启“自动打招呼”)\n3️⃣ 留在当前页,继续沟通下一个职位\n\n全程无需手动干预,高效投递!',
highlightColor: '#3367d6', // 主蓝加深10%
arrowPosition: 'bottom',
defaultPosition: { left: '25%', top: '80px' }
},
{
target: 'a[ka="header-message"]',
content: '💬 海投建议!\n\n✅ HR与您沟通,HR需要付费给平台\n因此您尽可能先自我介绍以提高效率 \n\n✅ HR查看附件简历,HR也要付费给平台\n所以尽量先发送`图片简历`给HR',
highlightColor: '#2a56c6', // 主蓝加深15%
arrowPosition: 'left',
defaultPosition: { right: '150px', top: '100px' }
},
{
target: 'div.logo',
content: '🤖 您需要打开两个浏览器窗口:\n\n左侧窗口自动打招呼发起沟通\n右侧发送自我介绍和图片简历\n\n您只需专注于挑选offer!',
highlightColor: '#1a73e8', // 主蓝加深20%
arrowPosition: 'right',
defaultPosition: { left: '200px', top: '20px' }
},
{
target: 'div.logo',
content: '❗ 特别注意:\n\n1. BOSS直聘每日打招呼上限为150次\n2. 聊天页仅处理最上方的最新对话\n3. 打招呼后对方会显示在聊天页\n4. 投递操作过于频繁有封号风险!',
highlightColor: '#0d47a1', // 主蓝加深30%
arrowPosition: 'bottom',
defaultPosition: { left: '50px', top: '80px' }
}
],
currentStep: 0,
guideElement: null,
overlay: null,
highlightElements: [],
chatUrl: 'https://www.zhipin.com/web/geek/chat', // 聊天页面URL
showGuideToUser() {
// 创建遮罩层
this.overlay = document.createElement('div');
this.overlay.id = 'guide-overlay';
this.overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(244, 239, 234, 0.9);
backdrop-filter: blur(3px);
mix-blend-mode: multiply;
z-index: 99997;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
`;
document.body.appendChild(this.overlay);
// 创建引导卡片
this.guideElement = document.createElement('div');
this.guideElement.id = 'guide-tooltip';
this.guideElement.style.cssText = `
position: fixed;
z-index: 99999;
width: 340px;
background: ${GUIDE_THEME.cream};
border-radius: 2px;
border: 2px solid ${GUIDE_THEME.ink};
box-shadow: -12px 12px 0 ${GUIDE_THEME.ink}, 0 18px 35px rgba(56, 56, 56, 0.25);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
overflow: hidden;
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
`;
document.body.appendChild(this.guideElement);
// 显示遮罩层
setTimeout(() => {
this.overlay.style.opacity = '1';
// 延迟显示第一步,增强视觉层次感
setTimeout(() => {
this.showStep(0);
}, 300);
}, 100);
},
showStep(stepIndex) {
const step = this.steps[stepIndex];
if (!step) return;
this.clearHighlights();
const target = document.querySelector(step.target);
const accentColor = step.highlightColor || GUIDE_THEME.accent;
if (target) {
// 创建高亮区域
const rect = target.getBoundingClientRect();
const highlight = document.createElement('div');
highlight.className = 'guide-highlight';
highlight.style.cssText = `
position: fixed;
top: ${rect.top}px;
left: ${rect.left}px;
width: ${rect.width}px;
height: ${rect.height}px;
background: rgba(244, 239, 234, 0.25);
border-radius: 2px;
z-index: 99998;
border: 2px dashed ${GUIDE_THEME.ink};
box-shadow: -6px 6px 0 ${GUIDE_THEME.ink}, 0 0 0 6px ${accentColor}33;
animation: guide-outline 1.8s infinite;
`;
document.body.appendChild(highlight);
this.highlightElements.push(highlight);
// 计算提示框位置(基于目标元素)
this.setGuidePositionFromTarget(step, rect);
} else {
console.warn('引导目标元素未找到,使用默认位置:', step.target);
// 使用默认位置显示提示框
this.setGuidePositionFromDefault(step);
}
// 设置引导提示框内容
let buttonsHtml = '';
const buttonBaseStyle = `
padding: 12px 22px;
border: 2px solid ${GUIDE_THEME.ink};
font-size: 13px;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.08em;
cursor: pointer;
box-shadow: -4px 4px 0 ${GUIDE_THEME.ink};
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
`;
// 根据是否为最后一步生成不同的按钮
if (stepIndex === this.steps.length - 1) {
buttonsHtml = `
`;
} else {
buttonsHtml = `
`;
}
this.guideElement.innerHTML = `
${buttonsHtml}
`;
// 重新绑定按钮事件
if (stepIndex === this.steps.length - 1) {
const finishBtn = document.getElementById('guide-finish-btn');
finishBtn.addEventListener('click', () => this.endGuide(true));
this.bindGuideButtonHover(finishBtn, {
defaultBg: accentColor,
hoverBg: this.darkenColor(accentColor, 10)
});
} else {
const nextBtn = document.getElementById('guide-next-btn');
const skipBtn = document.getElementById('guide-skip-btn');
nextBtn.addEventListener('click', () => this.nextStep());
skipBtn.addEventListener('click', () => this.endGuide());
this.bindGuideButtonHover(nextBtn, {
defaultBg: accentColor,
hoverBg: this.darkenColor(accentColor, 10)
});
this.bindGuideButtonHover(skipBtn, {
defaultBg: GUIDE_THEME.paper,
hoverBg: GUIDE_THEME.cream
});
}
// 显示提示框
this.guideElement.style.opacity = '1';
this.guideElement.style.transform = 'translateY(0)';
},
// 根据目标元素计算提示框位置
setGuidePositionFromTarget(step, rect) {
let left, top;
const guideWidth = 320;
const guideHeight = 240;
// 根据箭头方向调整位置
switch (step.arrowPosition) {
case 'top':
left = rect.left + rect.width / 2 - guideWidth / 2;
top = rect.top - guideHeight - 20;
break;
case 'bottom':
left = rect.left + rect.width / 2 - guideWidth / 2;
top = rect.bottom + 20;
break;
case 'left':
left = rect.left - guideWidth - 20;
top = rect.top + rect.height / 2 - guideHeight / 2;
break;
case 'right':
left = rect.right + 20;
top = rect.top + rect.height / 2 - guideHeight / 2;
break;
default:
left = rect.right + 20;
top = rect.top;
}
// 确保提示框不超出屏幕
left = Math.max(10, Math.min(left, window.innerWidth - guideWidth - 10));
top = Math.max(10, Math.min(top, window.innerHeight - guideHeight - 10));
// 设置位置
this.guideElement.style.left = `${left}px`;
this.guideElement.style.top = `${top}px`;
this.guideElement.style.transform = 'translateY(0)';
},
// 使用默认位置显示提示框
setGuidePositionFromDefault(step) {
const position = step.defaultPosition || { left: '50%', top: '50%', transform: 'translate(-50%, -50%)' };
// 应用默认位置样式
Object.assign(this.guideElement.style, {
left: position.left,
top: position.top,
right: position.right || 'auto',
bottom: position.bottom || 'auto',
transform: position.transform || 'none'
});
},
nextStep() {
// 清除当前步骤的事件监听
const currentStep = this.steps[this.currentStep];
if (currentStep) {
const target = document.querySelector(currentStep.target);
if (target) {
target.removeEventListener('click', this.nextStep);
}
}
this.currentStep++;
if (this.currentStep < this.steps.length) {
// 隐藏当前提示框,显示下一步
this.guideElement.style.opacity = '0';
this.guideElement.style.transform = 'translateY(10px)';
setTimeout(() => {
this.showStep(this.currentStep);
}, 300);
} else {
this.endGuide(true); // 传递true表示引导已完成
}
},
clearHighlights() {
this.highlightElements.forEach(el => el.remove());
this.highlightElements = [];
},
endGuide(isCompleted = false) {
// 清除高亮和事件
this.clearHighlights();
// 淡出提示框和遮罩
this.guideElement.style.opacity = '0';
this.guideElement.style.transform = 'translateY(10px)';
this.overlay.style.opacity = '0';
// 延迟移除元素
setTimeout(() => {
if (this.overlay && this.overlay.parentNode) {
this.overlay.parentNode.removeChild(this.overlay);
}
if (this.guideElement && this.guideElement.parentNode) {
this.guideElement.parentNode.removeChild(this.guideElement);
}
// 当引导完成时打开聊天页面
if (isCompleted && this.chatUrl) {
window.open(this.chatUrl, '_blank');
}
}, 300);
// 触发引导结束事件
document.dispatchEvent(new Event('guideEnd'));
},
bindGuideButtonHover(button, { defaultBg, hoverBg } = {}) {
if (!button) return;
const baseBackground = defaultBg || button.style.background || GUIDE_THEME.paper;
const hoverBackground = hoverBg || GUIDE_THEME.cream;
button.style.background = baseBackground;
button.style.boxShadow = `-4px 4px 0 ${GUIDE_THEME.ink}`;
button.addEventListener('mouseenter', () => {
button.style.background = hoverBackground;
button.style.transform = 'translate(4px, -4px)';
button.style.boxShadow = `-4px 4px 0 ${GUIDE_THEME.ink}`;
});
button.addEventListener('mouseleave', () => {
button.style.background = baseBackground;
button.style.transform = 'translate(0, 0)';
button.style.boxShadow = `-4px 4px 0 ${GUIDE_THEME.ink}`;
});
button.addEventListener('mousedown', () => {
button.style.transform = 'translate(0, 0)';
button.style.boxShadow = 'none';
});
button.addEventListener('mouseup', () => {
button.style.transform = 'translate(4px, -4px)';
button.style.boxShadow = `-4px 4px 0 ${GUIDE_THEME.ink}`;
});
},
// 辅助函数:颜色加深
darkenColor(color, percent) {
let R = parseInt(color.substring(1, 3), 16);
let G = parseInt(color.substring(3, 5), 16);
let B = parseInt(color.substring(5, 7), 16);
R = parseInt(R * (100 - percent) / 100);
G = parseInt(G * (100 - percent) / 100);
B = parseInt(B * (100 - percent) / 100);
R = (R < 255) ? R : 255;
G = (G < 255) ? G : 255;
B = (B < 255) ? B : 255;
R = Math.round(R);
G = Math.round(G);
B = Math.round(B);
const RR = ((R.toString(16).length === 1) ? "0" + R.toString(16) : R.toString(16));
const GG = ((G.toString(16).length === 1) ? "0" + G.toString(16) : G.toString(16));
const BB = ((B.toString(16).length === 1) ? "0" + B.toString(16) : B.toString(16));
return `#${RR}${GG}${BB}`;
}
};
// 添加脉冲动画样式和高亮样式
const style = document.createElement('style');
style.textContent = `
@keyframes guide-outline {
0% { box-shadow: 0 0 0 0 rgba(111, 194, 255, 0.6); }
100% { box-shadow: 0 0 0 12px rgba(111, 194, 255, 0); }
}
.guide-content .highlight {
font-weight: 700;
color: ${GUIDE_THEME.accentDark};
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
}
.guide-content .warning {
font-weight: 700;
color: ${GUIDE_THEME.warning};
}
`;
document.head.appendChild(style);
// 导出到全局
window.guide = guide$1;
// 1. 更新settings对象
const settings$1 = {
useAutoSendResume: JSON.parse(localStorage.getItem('useAutoSendResume') || 'true'),
excludeHeadhunters: JSON.parse(localStorage.getItem('excludeHeadhunters') || 'false'),
autoScrollSpeed: parseInt(localStorage.getItem('autoScrollSpeed') || '500'),
customPhrases: JSON.parse(localStorage.getItem('customPhrases') || '[]'),
actionDelays: {
click: parseInt(localStorage.getItem('clickDelay') || '130')
},
notifications: {
enabled: JSON.parse(localStorage.getItem('notificationsEnabled') || 'true'),
sound: JSON.parse(localStorage.getItem('notificationSound') || 'true')
},
ai: {
role: localStorage.getItem('aiRole') || '你是大四应届生正冲刺秋招,目标是尽力得到可签三方的正式岗位,也接受优质实习机会。沟通中做到:1. 语气自然,回复简洁精炼,不堆砌文字;2. 主动呈现核心优势(可根据对方要求编造亮点,如专业技能、项目经验、实习经历等);3. 若被询问联系方式,提供手机号 123456、微信号 BOSS;4. 全程表现对岗位的渴望,不冗余、不过度客气。'
},
autoReply: JSON.parse(localStorage.getItem('autoReply') || 'true'),
autoApply: JSON.parse(localStorage.getItem('autoApply') || 'true'),
intervals: {
basic: parseInt(localStorage.getItem('basicInterval') || '1000'),
operation: parseInt(localStorage.getItem('operationInterval') || '800')
},
recruiterActivityStatus: JSON.parse(localStorage.getItem('recruiterActivityStatus') || '["不限"]'),
// 自动发送图片简历相关配置
useAutoSendImageResume: JSON.parse(localStorage.getItem('useAutoSendImageResume') || 'false'),
imageResumePath: localStorage.getItem('imageResumePath') || '',
imageResumeData: localStorage.getItem('imageResumeData') || null
};
// 面板主题的一致色板
const PANEL_THEME = {
ink: '#383838',
cream: '#F4EFEA',
paper: '#FFFFFF',
primary: '#6FC2FF',
primaryHover: '#2BA5FF',
secondary: '#53DBC9',
neutral: '#A1A1A1'};
// 2. saveSettings函数,保存配置
function saveSettings() {
localStorage.setItem('useAutoSendResume', settings$1.useAutoSendResume.toString());
localStorage.setItem('excludeHeadhunters', settings$1.excludeHeadhunters.toString());
localStorage.setItem('autoScrollSpeed', settings$1.autoScrollSpeed.toString());
localStorage.setItem('customPhrases', JSON.stringify(settings$1.customPhrases));
localStorage.setItem('clickDelay', settings$1.actionDelays.click.toString());
localStorage.setItem('notificationsEnabled', settings$1.notifications.enabled.toString());
localStorage.setItem('notificationSound', settings$1.notifications.sound.toString());
localStorage.setItem('aiRole', settings$1.ai.role);
localStorage.setItem('autoReply', settings$1.autoReply.toString());
localStorage.setItem('autoApply', settings$1.autoApply.toString());
localStorage.setItem('basicInterval', settings$1.intervals.basic.toString());
localStorage.setItem('operationInterval', settings$1.intervals.operation.toString());
localStorage.setItem('recruiterActivityStatus', JSON.stringify(settings$1.recruiterActivityStatus));
// 保存图片简历配置
localStorage.setItem('useAutoSendImageResume', settings$1.useAutoSendImageResume.toString());
localStorage.setItem('imageResumePath', settings$1.imageResumePath);
// 存储图片数据
if (settings$1.imageResumeData) {
localStorage.setItem('imageResumeData', settings$1.imageResumeData);
} else {
localStorage.removeItem('imageResumeData');
}
}
// 4. createSettingsDialog函数添加新UI元素
function createSettingsDialog() {
const dialog = document.createElement('div');
dialog.id = 'boss-settings-dialog';
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -60%);
width: clamp(340px, 92vw, 620px);
height: min(85vh, 720px);
background: ${PANEL_THEME.cream};
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
box-shadow: -18px 18px 0 ${PANEL_THEME.ink}, 0 20px 45px rgba(56, 56, 56, 0.25);
z-index: 2147483646;
display: none;
flex-direction: column;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
overflow: hidden;
transition: transform 0.3s ease, opacity 0.3s ease;
background-image: linear-gradient(120deg, rgba(255,255,255,0.9) 0%, rgba(244,239,234,0.95) 100%);
`;
dialog.innerHTML += `
`;
const dialogHeader = createDialogHeader('海投助手·BOSS设置');
const dialogContent = document.createElement('div');
dialogContent.style.cssText = `
padding: 20px 24px 24px;
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: ${PANEL_THEME.primary} rgba(0, 0, 0, 0);
`;
dialogContent.innerHTML += `
`;
const tabsContainer = document.createElement('div');
tabsContainer.style.cssText = `
display: flex;
border-bottom: 2px solid ${PANEL_THEME.ink};
margin-bottom: 16px;
gap: 8px;
`;
const aiTab = document.createElement('button');
aiTab.textContent = 'AI人设';
aiTab.className = 'settings-tab active';
aiTab.style.cssText = `
padding: 10px 18px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
border: 2px solid ${PANEL_THEME.ink};
border-radius: 2px 2px 0 0;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.05em;
`;
const advancedTab = document.createElement('button');
advancedTab.textContent = '高级设置';
advancedTab.className = 'settings-tab';
advancedTab.style.cssText = `
padding: 10px 18px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
border: 2px solid ${PANEL_THEME.ink};
border-radius: 2px 2px 0 0;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.05em;
`;
const intervalTab = document.createElement('button');
intervalTab.textContent = '间隔设置';
intervalTab.className = 'settings-tab';
intervalTab.style.cssText = `
padding: 10px 18px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
border: 2px solid ${PANEL_THEME.ink};
border-radius: 2px 2px 0 0;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.05em;
`;
tabsContainer.append(aiTab, advancedTab, intervalTab);
const aiSettingsPanel = document.createElement('div');
aiSettingsPanel.id = 'ai-settings-panel';
const roleSettingResult = createSettingItem(
'AI角色定位',
'定义AI在对话中的角色和语气特点',
() => document.getElementById('ai-role-input')
);
const roleSetting = roleSettingResult.settingItem;
const roleInput = document.createElement('textarea');
roleInput.id = 'ai-role-input';
roleInput.rows = 5;
roleInput.style.cssText = `
width: 100%;
padding: 12px 14px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
resize: vertical;
font-size: 14px;
transition: box-shadow 0.2s ease, background 0.2s ease;
margin-top: 12px;
`;
addFocusBlurEffects(roleInput);
roleSetting.append(roleInput);
const presetRoleSettingResult = createSettingItem(
'预设角色',
'选择预设的AI角色模板',
() => document.getElementById('ai-preset-select')
);
const presetRoleSetting = presetRoleSettingResult.settingItem;
const presetSelect = document.createElement('select');
presetSelect.id = 'ai-preset-select';
presetSelect.style.cssText = `
width: 100%;
padding: 12px 14px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
font-size: 14px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
margin-top: 12px;
transition: box-shadow 0.2s ease, background 0.2s ease;
`;
const presets = [
{ value: 'default', text: '默认角色' }
];
presets.forEach(preset => {
const option = document.createElement('option');
option.value = preset.value;
option.textContent = preset.text;
presetSelect.appendChild(option);
});
presetSelect.addEventListener('change', () => {
if (presetSelect.value !== 'custom') {
const presetValues = {
'default': '你是大四应届生正冲刺秋招,目标是尽力得到可签三方的正式岗位,也接受优质实习机会。沟通中做到:1. 语气自然,回复简洁精炼,不堆砌文字;2. 主动呈现核心优势(可根据对方要求编造亮点,如专业技能、项目经验、实习经历等);3. 若被询问联系方式,提供手机号 123456、微信号 BOSS;4. 全程表现对岗位的渴望,不冗余、不过度客气。'
};
roleInput.value = presetValues[presetSelect.value];
}
});
presetRoleSetting.append(presetSelect);
aiSettingsPanel.append(roleSetting, presetRoleSetting);
const advancedSettingsPanel = document.createElement('div');
advancedSettingsPanel.id = 'advanced-settings-panel';
advancedSettingsPanel.style.display = 'none';
const autoReplySettingResult = createSettingItem(
'Ai回复模式',
'开启后Ai将自动回复消息',
() => document.querySelector('#toggle-auto-reply-mode input')
);
const autoReplySetting = autoReplySettingResult.settingItem;
const autoReplyDescriptionContainer = autoReplySettingResult.descriptionContainer;
const autoReplyToggle = createToggleSwitch(
'auto-reply-mode',
settings$1.autoReply,
(checked) => { settings$1.autoReply = checked; }
);
autoReplyDescriptionContainer.append(autoReplyToggle);
const autoSendResumeSettingResult = createSettingItem(
'自动发送简历',
'开启后系统将自动发送简历给HR',
() => document.querySelector('#toggle-auto-send-resume input')
);
const autoSendResumeSetting = autoSendResumeSettingResult.settingItem;
const autoSendResumeDescriptionContainer = autoSendResumeSettingResult.descriptionContainer;
const autoSendResumeToggle = createToggleSwitch(
'auto-send-resume',
settings$1.useAutoSendResume,
(checked) => { settings$1.useAutoSendResume = checked; }
);
autoSendResumeDescriptionContainer.append(autoSendResumeToggle);
const excludeHeadhuntersSettingResult = createSettingItem(
'投递时排除猎头',
'开启后将不会向猎头职位自动投递简历',
() => document.querySelector('#toggle-exclude-headhunters input')
);
const excludeHeadhuntersSetting = excludeHeadhuntersSettingResult.settingItem;
const excludeHeadhuntersDescriptionContainer = excludeHeadhuntersSettingResult.descriptionContainer;
const excludeHeadhuntersToggle = createToggleSwitch(
'exclude-headhunters',
settings$1.excludeHeadhunters,
(checked) => { settings$1.excludeHeadhunters = checked; }
);
excludeHeadhuntersDescriptionContainer.append(excludeHeadhuntersToggle);
// 改进后的自动发送图片简历设置
const imageResumeSettingResult = createSettingItem(
'自动发送图片简历',
'开启后将根据岗位智能选择发送图片简历给HR(最多添加3个)',
() => document.querySelector('#toggle-auto-send-image-resume input')
);
const imageResumeSetting = imageResumeSettingResult.settingItem;
const imageResumeDescriptionContainer = imageResumeSettingResult.descriptionContainer;
// 确保全局state中的imageResumes数组已初始化
if (!state.settings.imageResumes) {
state.settings.imageResumes = [];
}
// 多图片简历上传容器
const fileUploadsContainer = document.createElement('div');
fileUploadsContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
margin-top: 10px;
`;
// 创建添加简历按钮
const addResumeBtn = document.createElement('button');
addResumeBtn.id = 'add-image-resume-btn';
addResumeBtn.textContent = '添加图片简历(最多3个)';
addResumeBtn.style.cssText = `
padding: 12px 18px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
cursor: pointer;
font-size: 14px;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.04em;
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
align-self: flex-start;
`;
addResumeBtn.addEventListener('mouseenter', () => {
addResumeBtn.style.background = PANEL_THEME.cream;
addResumeBtn.style.transform = 'translate(4px, -4px)';
addResumeBtn.style.boxShadow = `-4px 4px 0 ${PANEL_THEME.ink}`;
});
addResumeBtn.addEventListener('mouseleave', () => {
addResumeBtn.style.background = PANEL_THEME.paper;
addResumeBtn.style.transform = 'translate(0, 0)';
addResumeBtn.style.boxShadow = 'none';
});
// 创建隐藏的文件输入
const hiddenFileInput = document.createElement('input');
hiddenFileInput.type = 'file';
hiddenFileInput.accept = 'image/*';
hiddenFileInput.style.display = 'none';
// 显示已上传简历的容器
const uploadedResumesContainer = document.createElement('div');
uploadedResumesContainer.id = 'uploaded-resumes-container';
uploadedResumesContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
`;
// 更新按钮状态
function updateAddButtonState() {
addResumeBtn.disabled = state.settings.imageResumes.length >= 3;
addResumeBtn.style.opacity = state.settings.imageResumes.length >= 3 ? '0.5' : '1';
addResumeBtn.style.cursor = state.settings.imageResumes.length >= 3 ? 'not-allowed' : 'pointer';
addResumeBtn.textContent = state.settings.imageResumes.length >= 3 ? '已达最大限制(3个)' : '添加图片简历';
}
// 渲染简历项
function renderResumeItem(index, resume) {
const resumeItem = document.createElement('div');
resumeItem.id = `resume-item-${index}`;
resumeItem.style.cssText = `
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
background: ${PANEL_THEME.paper};
width: 100%;
box-shadow: -4px 4px 0 ${PANEL_THEME.ink};
`;
const fileNameSpan = document.createElement('span');
fileNameSpan.style.cssText = `
flex: 1;
color: ${PANEL_THEME.ink};
font-size: 14px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
fileNameSpan.textContent = resume.path;
const removeBtn = document.createElement('button');
removeBtn.textContent = '删除';
removeBtn.style.cssText = `
padding: 6px 14px;
border-radius: 2px;
border: 2px solid #ff7169;
background: rgba(255, 113, 105, 0.15);
color: #A11D12;
cursor: pointer;
font-size: 12px;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
transition: transform 0.2s ease, box-shadow 0.2s ease;
`;
removeBtn.addEventListener('mouseenter', () => {
removeBtn.style.transform = 'translate(2px, -2px)';
removeBtn.style.boxShadow = '-2px 2px 0 #A11D12';
});
removeBtn.addEventListener('mouseleave', () => {
removeBtn.style.transform = 'translate(0, 0)';
removeBtn.style.boxShadow = 'none';
});
// 删除简历
removeBtn.addEventListener('click', () => {
state.settings.imageResumes.splice(index, 1);
resumeItem.remove();
updateAddButtonState();
updateToggleState();
StatePersistence.saveState();
});
resumeItem.append(fileNameSpan, removeBtn);
return resumeItem;
}
// 更新开关状态
function updateToggleState() {
const hasResumes = state.settings.imageResumes && state.settings.imageResumes.length > 0;
const toggleInput = document.querySelector('#toggle-auto-send-image-resume input');
if (!hasResumes) {
state.settings.useAutoSendImageResume = false;
if (toggleInput) {
toggleInput.checked = false;
toggleInput.dispatchEvent(new Event('change'));
}
}
}
// 初始渲染已上传的简历
if (state.settings.imageResumes && state.settings.imageResumes.length > 0) {
state.settings.imageResumes.forEach((resume, index) => {
const resumeItem = renderResumeItem(index, resume);
uploadedResumesContainer.appendChild(resumeItem);
});
}
// 修改后的 createToggleSwitch 调用
const autoSendImageResumeToggle = (() => {
// 检查是否有简历,但保留用户的设置状态
const hasResumes = state.settings.imageResumes && state.settings.imageResumes.length > 0;
// UI上显示的状态是设置为true且有简历时才为true
const displayState = hasResumes && state.settings.useAutoSendImageResume;
return createToggleSwitch(
'auto-send-image-resume',
displayState,
(checked) => {
const hasResumes = state.settings.imageResumes && state.settings.imageResumes.length > 0;
if (checked && !hasResumes) {
showNotification('请先添加至少一个图片简历', 'error');
return false; // 阻止后续处理
}
state.settings.useAutoSendImageResume = checked;
StatePersistence.saveState(); // 保存设置
return true;
}
);
})();
// 添加简历按钮点击事件
addResumeBtn.addEventListener('click', () => {
if (state.settings.imageResumes.length < 3) {
hiddenFileInput.click();
}
});
// 文件选择变化事件
hiddenFileInput.addEventListener('change', (e) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
// 检查是否已存在同名文件
const isDuplicate = state.settings.imageResumes.some(resume => resume.path === file.name);
if (isDuplicate) {
showNotification('该文件名已存在', 'error');
return;
}
// 读取图片并转换为Base64
const reader = new FileReader();
reader.onload = function (event) {
const newResume = {
path: file.name,
data: event.target.result
};
state.settings.imageResumes.push(newResume);
// 添加到UI
const index = state.settings.imageResumes.length - 1;
const resumeItem = renderResumeItem(index, newResume);
uploadedResumesContainer.appendChild(resumeItem);
// 更新按钮状态
updateAddButtonState();
// 自动启用开关
if (!state.settings.useAutoSendImageResume) {
state.settings.useAutoSendImageResume = true;
const toggleInput = document.querySelector('#toggle-auto-send-image-resume input');
toggleInput.checked = true;
toggleInput.dispatchEvent(new Event('change'));
}
// 保存设置
StatePersistence.saveState();
};
reader.readAsDataURL(file);
}
});
// 初始化按钮状态
updateAddButtonState();
fileUploadsContainer.append(addResumeBtn, uploadedResumesContainer, hiddenFileInput);
imageResumeDescriptionContainer.append(autoSendImageResumeToggle);
imageResumeSetting.append(fileUploadsContainer);
const recruiterStatusSettingResult = createSettingItem(
'投递招聘者状态',
'筛选活跃状态符合要求的招聘者进行投递',
() => document.querySelector('#recruiter-status-select .select-header')
);
const recruiterStatusSetting = recruiterStatusSettingResult.settingItem;
const statusSelect = document.createElement('div');
statusSelect.id = 'recruiter-status-select';
statusSelect.className = 'custom-select';
statusSelect.style.cssText = `
position: relative;
width: 100%;
margin-top: 10px;
`;
const statusHeader = document.createElement('div');
statusHeader.className = 'select-header';
statusHeader.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
background: ${PANEL_THEME.paper};
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
min-height: 48px;
`;
const statusDisplay = document.createElement('div');
statusDisplay.className = 'select-value';
statusDisplay.style.cssText = `
flex: 1;
text-align: left;
color: ${PANEL_THEME.ink};
font-size: 14px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
statusDisplay.textContent = getStatusDisplayText();
const statusIcon = document.createElement('div');
statusIcon.className = 'select-icon';
statusIcon.innerHTML = '▼';
statusIcon.style.cssText = `
margin-left: 10px;
color: ${PANEL_THEME.ink};
transition: transform 0.2s ease;
`;
const statusClear = document.createElement('button');
statusClear.className = 'select-clear';
statusClear.innerHTML = '×';
statusClear.style.cssText = `
background: none;
border: none;
color: ${PANEL_THEME.neutral};
cursor: pointer;
font-size: 16px;
margin-left: 8px;
display: none;
transition: color 0.2s ease;
`;
statusHeader.append(statusDisplay, statusClear, statusIcon);
const statusOptions = document.createElement('div');
statusOptions.className = 'select-options';
statusOptions.style.cssText = `
position: absolute;
top: calc(100% + 6px);
left: 0;
right: 0;
max-height: 240px;
overflow-y: auto;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
background: ${PANEL_THEME.paper};
z-index: 100;
box-shadow: -8px 8px 0 ${PANEL_THEME.ink}, 0 12px 25px rgba(56,56,56,0.2);
display: none;
transition: all 0.2s ease;
scrollbar-width: thin;
scrollbar-color: ${PANEL_THEME.primary} ${PANEL_THEME.cream};
`;
statusOptions.innerHTML += `
`;
const statusOptionsList = [
{ value: '不限', text: '不限' },
{ value: '在线', text: '在线' },
{ value: '刚刚活跃', text: '刚刚活跃' },
{ value: '今日活跃', text: '今日活跃' },
{ value: '3日内活跃', text: '3日内活跃' },
{ value: '本周活跃', text: '本周活跃' },
{ value: '本月活跃', text: '本月活跃' },
{ value: '半年前活跃', text: '半年前活跃' }
];
statusOptionsList.forEach(option => {
const statusOption = document.createElement('div');
statusOption.className = 'select-option' + (settings$1.recruiterActivityStatus.includes(option.value) ? ' selected' : '');
statusOption.dataset.value = option.value;
statusOption.style.cssText = `
padding: 12px 16px;
cursor: pointer;
transition: background 0.2s ease;
display: flex;
align-items: center;
font-size: 14px;
color: ${PANEL_THEME.ink};
`;
const checkIcon = document.createElement('span');
checkIcon.className = 'check-icon';
checkIcon.innerHTML = '✓';
checkIcon.style.cssText = `
margin-right: 8px;
color: ${PANEL_THEME.primary};
font-weight: bold;
display: ${settings$1.recruiterActivityStatus.includes(option.value) ? 'inline' : 'none'};
`;
const textSpan = document.createElement('span');
textSpan.textContent = option.text;
statusOption.append(checkIcon, textSpan);
statusOption.addEventListener('click', (e) => {
e.stopPropagation();
toggleStatusOption(option.value);
});
statusOptions.appendChild(statusOption);
});
statusHeader.addEventListener('click', () => {
statusOptions.style.display = statusOptions.style.display === 'block' ? 'none' : 'block';
statusIcon.style.transform = statusOptions.style.display === 'block' ? 'rotate(180deg)' : 'rotate(0)';
});
statusClear.addEventListener('click', (e) => {
e.stopPropagation();
settings$1.recruiterActivityStatus = [];
updateStatusOptions();
});
document.addEventListener('click', (e) => {
if (!statusSelect.contains(e.target)) {
statusOptions.style.display = 'none';
statusIcon.style.transform = 'rotate(0)';
}
});
statusHeader.addEventListener('mouseenter', () => {
statusHeader.style.background = PANEL_THEME.cream;
statusHeader.style.transform = 'translate(4px, -4px)';
statusHeader.style.boxShadow = `-4px 4px 0 ${PANEL_THEME.ink}`;
});
statusHeader.addEventListener('mouseleave', () => {
if (!statusHeader.contains(document.activeElement)) {
statusHeader.style.background = PANEL_THEME.paper;
statusHeader.style.transform = 'translate(0, 0)';
statusHeader.style.boxShadow = 'none';
}
});
statusHeader.addEventListener('focus', () => {
statusHeader.style.background = PANEL_THEME.cream;
statusHeader.style.transform = 'translate(4px, -4px)';
statusHeader.style.boxShadow = `-4px 4px 0 ${PANEL_THEME.ink}`;
});
statusHeader.addEventListener('blur', () => {
statusHeader.style.background = PANEL_THEME.paper;
statusHeader.style.transform = 'translate(0, 0)';
statusHeader.style.boxShadow = 'none';
});
statusSelect.append(statusHeader, statusOptions);
recruiterStatusSetting.append(statusSelect);
advancedSettingsPanel.append(autoReplySetting, autoSendResumeSetting, excludeHeadhuntersSetting, imageResumeSetting, recruiterStatusSetting);
const intervalSettingsPanel = document.createElement('div');
intervalSettingsPanel.id = 'interval-settings-panel';
intervalSettingsPanel.style.display = 'none';
const basicIntervalSettingResult = createSettingItem(
'基本间隔',
'滚动、检查新聊天等间隔时间(毫秒)',
() => document.getElementById('basic-interval-input')
);
const basicIntervalSetting = basicIntervalSettingResult.settingItem;
const basicIntervalInput = document.createElement('input');
basicIntervalInput.id = 'basic-interval-input';
basicIntervalInput.type = 'number';
basicIntervalInput.min = 500;
basicIntervalInput.max = 10000;
basicIntervalInput.step = 100;
basicIntervalInput.style.cssText = `
width: 100%;
padding: 12px 14px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
font-size: 14px;
margin-top: 12px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
transition: box-shadow 0.2s ease;
`;
addFocusBlurEffects(basicIntervalInput);
basicIntervalSetting.append(basicIntervalInput);
const operationIntervalSettingResult = createSettingItem(
'操作间隔',
'点击沟通按钮之间的间隔时间(毫秒)',
() => document.getElementById('operation-interval-input')
);
const operationIntervalSetting = operationIntervalSettingResult.settingItem;
const operationIntervalInput = document.createElement('input');
operationIntervalInput.id = 'operation-interval-input';
operationIntervalInput.type = 'number';
operationIntervalInput.min = 100;
operationIntervalInput.max = 2000;
operationIntervalInput.step = 50;
operationIntervalInput.style.cssText = `
width: 100%;
padding: 12px 14px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
font-size: 14px;
margin-top: 12px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
transition: box-shadow 0.2s ease;
`;
addFocusBlurEffects(operationIntervalInput);
operationIntervalSetting.append(operationIntervalInput);
const scrollSpeedSettingResult = createSettingItem(
'自动滚动速度',
'页面自动滚动的速度 (毫秒/像素)',
() => document.getElementById('scroll-speed-input')
);
const scrollSpeedSetting = scrollSpeedSettingResult.settingItem;
const scrollSpeedInput = document.createElement('input');
scrollSpeedInput.id = 'scroll-speed-input';
scrollSpeedInput.type = 'number';
scrollSpeedInput.min = 100;
scrollSpeedInput.max = 2000;
scrollSpeedInput.step = 50;
scrollSpeedInput.style.cssText = `
width: 100%;
padding: 12px 14px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
font-size: 14px;
margin-top: 12px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
transition: box-shadow 0.2s ease;
`;
addFocusBlurEffects(scrollSpeedInput);
scrollSpeedSetting.append(scrollSpeedInput);
intervalSettingsPanel.append(basicIntervalSetting, operationIntervalSetting, scrollSpeedSetting);
aiTab.style.background = 'var(--primary-color)';
aiTab.style.color = PANEL_THEME.ink;
aiTab.style.transform = 'translate(4px, -4px)';
aiTab.style.boxShadow = `-4px 4px 0 ${PANEL_THEME.ink}`;
aiTab.addEventListener('click', () => {
setActiveTab(aiTab, aiSettingsPanel);
});
advancedTab.addEventListener('click', () => {
setActiveTab(advancedTab, advancedSettingsPanel);
});
intervalTab.addEventListener('click', () => {
setActiveTab(intervalTab, intervalSettingsPanel);
});
const dialogFooter = document.createElement('div');
dialogFooter.style.cssText = `
padding: 16px 24px;
border-top: 2px solid ${PANEL_THEME.ink};
display: flex;
justify-content: flex-end;
gap: 12px;
background: ${PANEL_THEME.paper};
`;
const cancelBtn = createTextButton(
'取消',
PANEL_THEME.paper,
() => {
dialog.classList.remove('active');
setTimeout(() => {
dialog.style.display = 'none';
}, 150);
}
);
const saveBtn = createTextButton(
'保存设置',
'var(--primary-color)',
() => {
try {
const aiRoleInput = document.getElementById('ai-role-input');
settings$1.ai.role = aiRoleInput ? aiRoleInput.value : '';
const basicIntervalInput = document.getElementById('basic-interval-input');
const basicIntervalValue = basicIntervalInput ? parseInt(basicIntervalInput.value) : settings$1.intervals.basic;
settings$1.intervals.basic = isNaN(basicIntervalValue) ? settings$1.intervals.basic : basicIntervalValue;
const operationIntervalInput = document.getElementById('operation-interval-input');
const operationIntervalValue = operationIntervalInput ? parseInt(operationIntervalInput.value) : settings$1.intervals.operation;
settings$1.intervals.operation = isNaN(operationIntervalValue) ? settings$1.intervals.operation : operationIntervalValue;
const scrollSpeedInput = document.getElementById('scroll-speed-input');
const scrollSpeedValue = scrollSpeedInput ? parseInt(scrollSpeedInput.value) : settings$1.autoScrollSpeed;
settings$1.autoScrollSpeed = isNaN(scrollSpeedValue) ? settings$1.autoScrollSpeed : scrollSpeedValue;
saveSettings();
showNotification('设置已保存');
dialog.classList.remove('active');
setTimeout(() => {
dialog.style.display = 'none';
}, 150);
} catch (error) {
showNotification('保存失败: ' + error.message, 'error');
console.error('保存设置失败:', error);
}
}
);
dialogFooter.append(cancelBtn, saveBtn);
dialogContent.append(tabsContainer, aiSettingsPanel, advancedSettingsPanel, intervalSettingsPanel);
dialog.append(dialogHeader, dialogContent, dialogFooter);
dialog.addEventListener('click', (e) => {
if (e.target === dialog) {
dialog.classList.remove('active');
setTimeout(() => {
dialog.style.display = 'none';
}, 150);
}
});
return dialog;
}
function showSettingsDialog$1() {
let dialog = document.getElementById('boss-settings-dialog');
if (!dialog) {
dialog = createSettingsDialog();
document.body.appendChild(dialog);
}
dialog.style.display = 'flex';
setTimeout(() => {
dialog.classList.add('active');
setTimeout(loadSettingsIntoUI, 100);
}, 10);
}
function toggleStatusOption(value) {
if (value === '不限') {
settings$1.recruiterActivityStatus = settings$1.recruiterActivityStatus.includes('不限') ? [] : ['不限'];
} else {
if (settings$1.recruiterActivityStatus.includes('不限')) {
settings$1.recruiterActivityStatus = [value];
} else {
if (settings$1.recruiterActivityStatus.includes(value)) {
settings$1.recruiterActivityStatus = settings$1.recruiterActivityStatus.filter(v => v !== value);
} else {
settings$1.recruiterActivityStatus.push(value);
}
if (settings$1.recruiterActivityStatus.length === 0) {
settings$1.recruiterActivityStatus = ['不限'];
}
}
}
updateStatusOptions();
}
function updateStatusOptions() {
const options = document.querySelectorAll('#recruiter-status-select .select-option');
options.forEach(option => {
const isSelected = settings$1.recruiterActivityStatus.includes(option.dataset.value);
option.className = 'select-option' + (isSelected ? ' selected' : '');
option.querySelector('.check-icon').style.display = isSelected ? 'inline' : 'none';
if (option.dataset.value === '不限') {
if (isSelected) {
options.forEach(opt => {
if (opt.dataset.value !== '不限') {
opt.className = 'select-option';
opt.querySelector('.check-icon').style.display = 'none';
}
});
}
}
});
document.querySelector('#recruiter-status-select .select-value').textContent = getStatusDisplayText();
document.querySelector('#recruiter-status-select .select-clear').style.display =
settings$1.recruiterActivityStatus.length > 0 && !settings$1.recruiterActivityStatus.includes('不限') ? 'inline' : 'none';
}
function getStatusDisplayText() {
if (settings$1.recruiterActivityStatus.includes('不限')) {
return '不限';
}
if (settings$1.recruiterActivityStatus.length === 0) {
return '请选择';
}
if (settings$1.recruiterActivityStatus.length <= 2) {
return settings$1.recruiterActivityStatus.join('、');
}
return `${settings$1.recruiterActivityStatus[0]}、${settings$1.recruiterActivityStatus[1]}等${settings$1.recruiterActivityStatus.length}项`;
}
function loadSettingsIntoUI() {
const aiRoleInput = document.getElementById('ai-role-input');
if (aiRoleInput) {
aiRoleInput.value = settings$1.ai.role;
}
const autoReplyInput = document.querySelector('#toggle-auto-reply-mode input');
if (autoReplyInput) {
autoReplyInput.checked = settings$1.autoReply;
}
const autoSendResumeInput = document.querySelector('#toggle-auto-send-resume input');
if (autoSendResumeInput) {
autoSendResumeInput.checked = settings$1.useAutoSendResume;
}
const excludeHeadhuntersInput = document.querySelector('#toggle-exclude-headhunters input');
if (excludeHeadhuntersInput) {
excludeHeadhuntersInput.checked = settings$1.excludeHeadhunters;
}
const basicIntervalInput = document.getElementById('basic-interval-input');
if (basicIntervalInput) {
basicIntervalInput.value = settings$1.intervals.basic.toString();
}
const operationIntervalInput = document.getElementById('operation-interval-input');
if (operationIntervalInput) {
operationIntervalInput.value = settings$1.intervals.operation.toString();
}
const scrollSpeedInput = document.getElementById('scroll-speed-input');
if (scrollSpeedInput) {
scrollSpeedInput.value = settings$1.autoScrollSpeed.toString();
}
// 加载图片简历设置
const autoSendImageResumeInput = document.querySelector('#toggle-auto-send-image-resume input');
if (autoSendImageResumeInput) {
// 只有在有图片文件时才允许开启
autoSendImageResumeInput.checked = settings$1.useAutoSendImageResume && settings$1.imageResumePath;
}
const fileNameDisplay = document.getElementById('image-resume-filename');
if (fileNameDisplay) {
fileNameDisplay.textContent = settings$1.imageResumePath || '未选择文件';
}
updateStatusOptions();
}
function createDialogHeader(title) {
const header = document.createElement('div');
header.style.cssText = `
padding: 18px 24px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
font-size: 18px;
font-weight: 500;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid ${PANEL_THEME.ink};
`;
const titleElement = document.createElement('div');
titleElement.textContent = title;
titleElement.style.cssText = `
font-family: 'Monaco', 'Consolas', monospace;
letter-spacing: 0.08em;
text-transform: uppercase;
`;
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.title = '关闭设置';
closeBtn.style.cssText = `
width: 36px;
height: 36px;
background: ${PANEL_THEME.paper};
color: ${PANEL_THEME.ink};
border-radius: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: 2px solid ${PANEL_THEME.ink};
font-size: 16px;
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.transform = 'translate(3px, -3px)';
closeBtn.style.boxShadow = `-3px 3px 0 ${PANEL_THEME.ink}`;
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.transform = 'translate(0, 0)';
closeBtn.style.boxShadow = 'none';
});
closeBtn.addEventListener('click', () => {
const dialog = document.getElementById('boss-settings-dialog');
if (dialog) {
dialog.classList.remove('active');
setTimeout(() => {
dialog.style.display = 'none';
}, 150);
}
});
header.append(titleElement, closeBtn);
return header;
}
function createSettingItem(title, description, controlGetter) {
const settingItem = document.createElement('div');
settingItem.className = 'setting-item';
settingItem.style.cssText = `
padding: 18px 20px;
border-radius: 2px;
margin-bottom: 16px;
background: ${PANEL_THEME.paper};
box-shadow: -6px 6px 0 ${PANEL_THEME.ink};
border: 2px solid ${PANEL_THEME.ink};
display: flex;
flex-direction: column;
gap: 10px;
`;
const titleElement = document.createElement('h4');
titleElement.textContent = title;
titleElement.style.cssText = `
margin: 0;
color: ${PANEL_THEME.ink};
font-size: 15px;
font-weight: 500;
letter-spacing: 0.05em;
text-transform: uppercase;
`;
const descElement = document.createElement('p');
descElement.textContent = description;
descElement.style.cssText = `
margin: 4px 0 0;
color: ${PANEL_THEME.neutral};
font-size: 13px;
line-height: 1.5;
`;
const descriptionContainer = document.createElement('div');
descriptionContainer.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
gap: 12px;
`;
const textContainer = document.createElement('div');
textContainer.append(titleElement, descElement);
descriptionContainer.append(textContainer);
settingItem.append(descriptionContainer);
settingItem.addEventListener('click', () => {
const control = controlGetter();
if (control && typeof control.focus === 'function') {
control.focus();
}
});
return {
settingItem,
descriptionContainer
};
}
function createToggleSwitch(id, isChecked, onChange) {
const container = document.createElement('div');
container.className = 'toggle-container';
container.style.cssText = 'display: flex; justify-content: space-between; align-items: center;';
const switchContainer = document.createElement('div');
switchContainer.className = 'toggle-switch';
switchContainer.style.cssText = `
position: relative;
width: 64px;
height: 28px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
background: ${isChecked ? 'var(--primary-color)' : PANEL_THEME.paper};
transition: background 0.2s ease;
cursor: pointer;
`;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `toggle-${id}`;
checkbox.checked = isChecked;
checkbox.style.display = 'none';
const slider = document.createElement('span');
slider.className = 'toggle-slider';
slider.style.cssText = `
position: absolute;
top: 2px;
left: ${isChecked ? '34px' : '2px'};
width: 24px;
height: 24px;
border-radius: 2px;
background-color: ${PANEL_THEME.paper};
border: 2px solid ${PANEL_THEME.ink};
box-shadow: -2px 2px 0 ${PANEL_THEME.ink};
transition: left 0.2s ease;
`;
// 新增强制状态同步函数
const forceUpdateUI = (checked) => {
checkbox.checked = checked;
switchContainer.style.background = checked ? 'var(--primary-color)' : PANEL_THEME.paper;
slider.style.left = checked ? '34px' : '2px';
};
checkbox.addEventListener('change', () => {
let allowChange = true;
if (onChange) {
// 回调函数可通过返回 false 阻断状态更新
allowChange = onChange(checkbox.checked) !== false;
}
if (!allowChange) {
// 非法操作时立即恢复原状态(不触发动画)
forceUpdateUI(!checkbox.checked);
return;
}
// 正常操作更新UI
forceUpdateUI(checkbox.checked);
});
switchContainer.addEventListener('click', () => {
// 直接触发状态变化(不再通过模拟事件)
const newState = !checkbox.checked;
if (onChange) {
if (onChange(newState) !== false) {
forceUpdateUI(newState);
}
} else {
forceUpdateUI(newState);
}
});
switchContainer.append(checkbox, slider);
container.append(switchContainer);
return container;
}
function createTextButton(text, backgroundColor, onClick) {
const button = document.createElement('button');
button.textContent = text;
const defaultBackground = backgroundColor || PANEL_THEME.paper;
const isAccentButton = defaultBackground !== PANEL_THEME.paper;
button.style.cssText = `
padding: 12px 22px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
background: ${defaultBackground};
color: ${PANEL_THEME.ink};
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.06em;
box-shadow: -4px 4px 0 ${PANEL_THEME.ink};
min-width: 120px;
`;
button.addEventListener('mouseenter', () => {
button.style.background = isAccentButton ? PANEL_THEME.primaryHover : PANEL_THEME.cream;
button.style.transform = 'translate(4px, -4px)';
button.style.boxShadow = `-4px 4px 0 ${PANEL_THEME.ink}`;
});
button.addEventListener('mouseleave', () => {
button.style.background = defaultBackground;
button.style.transform = 'translate(0, 0)';
button.style.boxShadow = '-4px 4px 0 ' + PANEL_THEME.ink;
});
button.addEventListener('mousedown', () => {
button.style.transform = 'translate(0, 0)';
button.style.boxShadow = 'none';
});
button.addEventListener('mouseup', () => {
button.style.transform = 'translate(4px, -4px)';
button.style.boxShadow = `-4px 4px 0 ${PANEL_THEME.ink}`;
});
button.addEventListener('click', onClick);
return button;
}
function addFocusBlurEffects(element) {
element.addEventListener('focus', () => {
element.style.borderColor = 'var(--primary-color)';
element.style.boxShadow = `-4px 4px 0 ${PANEL_THEME.ink}`;
});
element.addEventListener('blur', () => {
element.style.borderColor = PANEL_THEME.ink;
element.style.boxShadow = 'none';
});
}
function setActiveTab(tab, panel) {
const tabs = document.querySelectorAll('.settings-tab');
const panels = [
document.getElementById('ai-settings-panel'),
document.getElementById('advanced-settings-panel'),
document.getElementById('interval-settings-panel')
];
tabs.forEach(t => {
t.classList.remove('active');
t.style.background = PANEL_THEME.paper;
t.style.color = PANEL_THEME.ink;
t.style.transform = 'translate(0, 0)';
t.style.boxShadow = 'none';
});
panels.forEach(p => {
if (p) {
p.style.display = 'none';
}
});
tab.classList.add('active');
tab.style.background = 'var(--primary-color)';
tab.style.color = PANEL_THEME.ink;
tab.style.transform = 'translate(4px, -4px)';
tab.style.boxShadow = `-4px 4px 0 ${PANEL_THEME.ink}`;
if (panel) {
panel.style.display = 'block';
}
}
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
const bgColor = type === 'success' ? PANEL_THEME.secondary : '#FF7169';
notification.style.cssText = `
position: fixed;
top: 24px;
left: 50%;
transform: translateX(-50%);
background: ${bgColor};
color: ${PANEL_THEME.ink};
padding: 12px 20px;
border-radius: 2px;
border: 2px solid ${PANEL_THEME.ink};
box-shadow: -6px 6px 0 ${PANEL_THEME.ink};
z-index: 9999999;
opacity: 0;
transition: opacity 0.3s ease;
font-family: 'Monaco', 'Consolas', monospace;
text-transform: uppercase;
letter-spacing: 0.05em;
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.style.opacity = '1', 10);
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => document.body.removeChild(notification), 300);
}, 2000);
}
// 导出到全局
window.showSettingsDialog = showSettingsDialog$1;
window.settings = settings$1;
/**
* JOBPILOT 投递助手 - 主入口文件
* 模块化开发,自动构建打包
*/
// 主程序启动逻辑(来自 main.js)
(function () {
const STORAGE = {
LETTER: 'letterLastShown',
GUIDE: 'shouldShowGuide',
AI_COUNT: 'aiReplyCount',
AI_DATE: 'lastAiDate'
};
function getToday() {
return new Date().toISOString().split('T')[0];
}
// 立即显示UI(加载中状态)
function initUIFast() {
try {
// 立即初始化UI面板(禁用状态)
UI.init(true); // 传入true表示加载中状态
document.body.style.position = 'relative';
} catch (error) {
console.error('UI快速初始化失败:', error);
}
}
// DOM就绪后启用功能
function enableFeatures() {
try {
const midnight = new Date();
midnight.setDate(midnight.getDate() + 1);
midnight.setHours(0, 0, 0, 0);
setTimeout(() => {
localStorage.removeItem(STORAGE.AI_COUNT);
localStorage.removeItem(STORAGE.AI_DATE);
localStorage.removeItem(STORAGE.LETTER);
}, midnight - Date.now());
// 启用UI功能
UI.setReady();
const today = getToday();
if (location.pathname.includes('/jobs')) {
if (localStorage.getItem(STORAGE.LETTER) !== today) {
letter.showLetterToUser();
localStorage.setItem(STORAGE.LETTER, today);
} else if (localStorage.getItem(STORAGE.GUIDE) !== 'true') {
guide.showGuideToUser();
localStorage.setItem(STORAGE.GUIDE, 'true');
Core.delay(800);
window.open('https://www.zhipin.com/web/geek/notify-set?ka=notify-set', '_blank');
}
Core.log('欢迎使用海投助手,我将自动投递岗位!');
} else if (location.pathname.includes('/chat')) {
Core.log('欢迎使用海投助手,我将自动发送简历!');
} else if (location.pathname.includes('/notify-set')) {
Core.log('请将常用语换为自我介绍来引起HR的注意!');
const targetSelector = 'h3.normal.title';
const observer = new MutationObserver((mutations, obs) => {
const targetElement = document.querySelector(targetSelector);
if (targetElement) {
targetElement.textContent = '把常用语换为自我介绍,并设图片简历; 招呼语功能必须启用。';
obs.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
} else {
Core.log('当前页面暂不支持,请移步至职位页面!');
}
} catch (error) {
console.error('功能启用失败:', error);
if (UI.notify) UI.notify('功能启用失败', 'error');
}
}
// 立即执行UI初始化
initUIFast();
// DOM完全加载后启用功能
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', enableFeatures);
} else {
// DOM已经就绪,立即启用
enableFeatures();
}
})();
})();