// ==UserScript==
// @name BOSS海投助手
// @namespace https://github.com/yangshengzhou03
// @version 1.2.4.4
// @description 求职工具!Yangshengzhou开发用于提高BOSS直聘投递效率,批量沟通,高效求职
// @author Yangshengzhou
// @match https://www.zhipin.com/web/*
// @grant GM_xmlhttpRequest
// @run-at document-idle
// @supportURL https://github.com/yangshengzhou03
// @homepageURL https://gitee.com/yangshengzhou
// @license AGPL-3.0-or-later
// @icon https://www.zhipin.com/favicon.ico
// @connect zhipin.com
// @connect spark-api-open.xf-yun.com
// @connect jasun.xyz
// @noframes
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// ==/UserScript==
(function () {
"use strict";
const CONFIG = {
BASIC_INTERVAL: 1000,
OPERATION_INTERVAL: 1200,
DELAYS: {
SHORT: 30,
MEDIUM_SHORT: 200,
},
MINI_ICON_SIZE: 40,
STORAGE_KEYS: {
PROCESSED_HRS: "processedHRs",
SENT_GREETINGS_HRS: "sentGreetingsHRs",
SENT_RESUME_HRS: "sentResumeHRs",
SENT_IMAGE_RESUME_HRS: "sentImageResumeHRs",
AI_REPLY_COUNT: "aiReplyCount",
LAST_AI_DATE: "lastAiDate",
},
STORAGE_LIMITS: {
PROCESSED_HRS: 500,
SENT_GREETINGS_HRS: 500,
SENT_RESUME_HRS: 300,
SENT_IMAGE_RESUME_HRS: 300,
},
};
const getStoredJSON = (key, defaultValue) => {
try {
const val = localStorage.getItem(key);
return val ? JSON.parse(val) : defaultValue;
} catch (e) {
console.error(`Error parsing ${key}:`, e);
return defaultValue;
}
};
const state = {
isRunning: false,
currentIndex: 0,
includeKeywords: [],
locationKeywords: [],
jobList: [],
ui: {
isMinimized: false,
theme: localStorage.getItem("theme") || "light",
},
hrInteractions: {
processedHRs: new Set(getStoredJSON("processedHRs", [])),
sentGreetingsHRs: new Set(getStoredJSON("sentGreetingsHRs", [])),
sentResumeHRs: new Set(getStoredJSON("sentResumeHRs", [])),
sentImageResumeHRs: new Set(getStoredJSON("sentImageResumeHRs", [])),
},
ai: {
replyCount: getStoredJSON("aiReplyCount", 0),
lastAiDate: localStorage.getItem("lastAiDate") || "",
useAiReply: true,
},
settings: {
useAutoSendResume: getStoredJSON("useAutoSendResume", false),
actionDelays: {
click: parseInt(localStorage.getItem("clickDelay") || "130"),
},
ai: {
role:
localStorage.getItem("aiRole") ||
"你是求职的应届生正在面对HR,回复需满足:20字内,编造专业对口/实习经验/证书任一岗位优势;被问个人信息或岗位条件,直接配合提供合理数据;全程积极真诚无拒绝言辞。",
},
autoReply: getStoredJSON("autoReply", false),
useAutoSendImageResume: getStoredJSON("useAutoSendImageResume", false),
imageResumeData: localStorage.getItem("imageResumeData") || null,
communicationMode:
localStorage.getItem("communicationMode") || "new-only",
recruiterActivityStatus: getStoredJSON(
"recruiterActivityStatus",
["不限"]
),
excludeHeadhunters: getStoredJSON("excludeHeadhunters", false),
imageResumes: getStoredJSON("imageResumes", []),
keywordReplies: getStoredJSON("keywordReplies", []),
},
activation: {
isActivated: localStorage.getItem("activationStatus") === "true",
activationCode: localStorage.getItem("activationCode") || "",
cardKey: localStorage.getItem("cardKey") || "",
activatedAt: localStorage.getItem("activationDate") || "",
},
comments: {
currentCompanyName: "",
commentsList: [],
isLoading: false,
isCommentMode: false,
},
};
const elements = {
panel: null,
controlBtn: null,
log: null,
includeInput: null,
locationInput: null,
miniIcon: null,
};
class StorageManager {
static setItem(key, value) {
try {
localStorage.setItem(
key,
typeof value === "string" ? value : JSON.stringify(value)
);
return true;
} catch (error) {
Core.log(`设置存储项 ${key} 失败: ${error.message}`);
return false;
}
}
static getItem(key, defaultValue = null) {
try {
const value = localStorage.getItem(key);
return value !== null ? value : defaultValue;
} catch (error) {
Core.log(`获取存储项 ${key} 失败: ${error.message}`);
return defaultValue;
}
}
static addRecordWithLimit(storageKey, record, currentSet, limit) {
try {
if (currentSet.has(record)) {
return;
}
let records = this.getParsedItem(storageKey, []);
records = Array.isArray(records) ? records : [];
if (records.length >= limit) {
records.shift();
}
records.push(record);
currentSet.add(record);
this.setItem(storageKey, records);
console.log(
`存储管理: 添加记录${records.length >= limit ? "并删除最早记录" : ""
},当前${storageKey}数量: ${records.length}/${limit}`
);
} catch (error) {
console.log(`存储管理出错: ${error.message}`);
}
}
static getParsedItem(storageKey, defaultValue = []) {
try {
const data = this.getItem(storageKey);
return data ? JSON.parse(data) : defaultValue;
} catch (error) {
Core.log(`解析存储记录出错: ${error.message}`);
return defaultValue;
}
}
static ensureStorageLimits() {
const limitConfigs = [
{
key: CONFIG.STORAGE_KEYS.PROCESSED_HRS,
set: state.hrInteractions.processedHRs,
limit: CONFIG.STORAGE_LIMITS.PROCESSED_HRS,
},
{
key: CONFIG.STORAGE_KEYS.SENT_GREETINGS_HRS,
set: state.hrInteractions.sentGreetingsHRs,
limit: CONFIG.STORAGE_LIMITS.SENT_GREETINGS_HRS,
},
{
key: CONFIG.STORAGE_KEYS.SENT_RESUME_HRS,
set: state.hrInteractions.sentResumeHRs,
limit: CONFIG.STORAGE_LIMITS.SENT_RESUME_HRS,
},
{
key: CONFIG.STORAGE_KEYS.SENT_IMAGE_RESUME_HRS,
set: state.hrInteractions.sentImageResumeHRs,
limit: CONFIG.STORAGE_LIMITS.SENT_IMAGE_RESUME_HRS,
},
];
limitConfigs.forEach(({ key, set, limit }) => {
const records = this.getParsedItem(key, []);
if (records.length > limit) {
const trimmedRecords = records.slice(-limit);
this.setItem(key, trimmedRecords);
set.clear();
trimmedRecords.forEach((record) => set.add(record));
console.log(
`存储管理: 清理${key}记录,从${records.length}减少到${trimmedRecords.length}`
);
}
});
}
}
class StatePersistence {
static saveState() {
try {
const stateMap = {
aiReplyCount: state.ai.replyCount,
lastAiDate: state.ai.lastAiDate,
useAiReply: state.ai.useAiReply,
useAutoSendResume: state.settings.useAutoSendResume,
useAutoSendImageResume: state.settings.useAutoSendImageResume,
imageResumeData: state.settings.imageResumeData,
imageResumes: state.settings.imageResumes || [],
keywordReplies: state.settings.keywordReplies || [],
theme: state.ui.theme,
clickDelay: state.settings.actionDelays.click,
includeKeywords: state.includeKeywords,
locationKeywords: state.locationKeywords,
};
Object.entries(stateMap).forEach(([key, value]) => {
StorageManager.setItem(key, value);
});
} catch (error) {
Core.log(`保存状态失败: ${error.message}`);
}
}
static loadState() {
try {
state.includeKeywords = StorageManager.getParsedItem(
"includeKeywords",
[]
);
state.locationKeywords =
StorageManager.getParsedItem("locationKeywords") ||
StorageManager.getParsedItem("excludeKeywords", []);
const imageResumes = StorageManager.getParsedItem("imageResumes", []);
if (Array.isArray(imageResumes))
state.settings.imageResumes = imageResumes;
StorageManager.ensureStorageLimits();
} catch (error) {
Core.log(`加载状态失败: ${error.message}`);
}
}
}
class ActivationManager {
static async activateWithCardKey(cardKey) {
return new Promise((resolve, reject) => {
try {
if (!this.validateCardKey(cardKey)) {
reject(new Error("激活卡密格式有误"));
return;
}
const apiUrl = `https://jasun.xyz/api/public/card-keys/verify/${cardKey}`;
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
headers: {
"Content-Type": "application/json",
},
timeout: 10000,
onload: (response) => {
try {
const data = JSON.parse(response.responseText);
if (data.code === 200 && data.message === "success") {
state.activation.isActivated = true;
state.activation.activatedAt = new Date().toISOString();
state.activation.cardKey = cardKey;
localStorage.setItem("activationStatus", "true");
localStorage.setItem("activationDate", state.activation.activatedAt);
localStorage.setItem("cardKey", cardKey);
resolve(true);
} else {
reject(new Error(data.message || "激活卡密无效"));
}
} catch (error) {
reject(new Error("响应解析失败: " + error.message));
}
},
onerror: (error) => {
reject(new Error("网络请求失败: " + error.message));
},
ontimeout: () => {
reject(new Error("请求超时"));
},
});
} catch (error) {
reject(new Error(error.message));
}
});
}
static validateCardKey(cardKey) {
const keyPattern = /^[A-Za-z0-9]{32}$/;
return keyPattern.test(cardKey);
}
static checkActivationStatus() {
const activationStatus = localStorage.getItem("activationStatus");
const activationDate = localStorage.getItem("activationDate");
const cardKey = localStorage.getItem("cardKey");
if (activationStatus === "true" && activationDate && cardKey) {
state.activation.isActivated = true;
state.activation.activatedAt = activationDate;
state.activation.cardKey = cardKey;
return true;
}
return false;
}
}
class HRInteractionManager {
static async handleHRInteraction(hrKey) {
const hasResponded = await this.hasHRResponded();
if (!state.hrInteractions.sentGreetingsHRs.has(hrKey)) {
await this._handleFirstInteraction(hrKey);
return;
}
if (
!state.hrInteractions.sentResumeHRs.has(hrKey) ||
!state.hrInteractions.sentImageResumeHRs.has(hrKey)
) {
if (hasResponded) {
await this._handleFollowUpResponse(hrKey);
}
return;
}
await Core.aiReply();
}
static async _handleFirstInteraction(hrKey) {
Core.log(`首次沟通: ${hrKey}`);
const sentGreeting = await this.sendGreetings();
if (sentGreeting) {
StorageManager.addRecordWithLimit(
CONFIG.STORAGE_KEYS.SENT_GREETINGS_HRS,
hrKey,
state.hrInteractions.sentGreetingsHRs,
CONFIG.STORAGE_LIMITS.SENT_GREETINGS_HRS
);
await this._handleResumeSending(hrKey);
}
}
static async _handleResumeSending(hrKey) {
if (
state.settings.useAutoSendResume &&
!state.hrInteractions.sentResumeHRs.has(hrKey)
) {
const sentResume = await this.sendResume();
if (sentResume) {
StorageManager.addRecordWithLimit(
CONFIG.STORAGE_KEYS.SENT_RESUME_HRS,
hrKey,
state.hrInteractions.sentResumeHRs,
CONFIG.STORAGE_LIMITS.SENT_RESUME_HRS
);
}
}
if (
state.settings.useAutoSendImageResume &&
!state.hrInteractions.sentImageResumeHRs.has(hrKey)
) {
const sentImageResume = await this.sendImageResume();
if (sentImageResume) {
StorageManager.addRecordWithLimit(
CONFIG.STORAGE_KEYS.SENT_IMAGE_RESUME_HRS,
hrKey,
state.hrInteractions.sentImageResumeHRs,
CONFIG.STORAGE_LIMITS.SENT_IMAGE_RESUME_HRS
);
}
}
}
static async _handleFollowUpResponse(hrKey) {
if (this.hasCardMessage()) {
const handled = await this.handleCardMessage(hrKey);
if (handled) {
return;
}
}
const lastMessage = await Core.getLastFriendMessageText();
if (
lastMessage &&
(lastMessage.includes("简历") || lastMessage.includes("发送简历"))
) {
Core.log(`HR提到"简历",发送简历: ${hrKey}`);
if (
state.settings.useAutoSendImageResume &&
!state.hrInteractions.sentImageResumeHRs.has(hrKey)
) {
const sentImageResume = await this.sendImageResume();
if (sentImageResume) {
state.hrInteractions.sentImageResumeHRs.add(hrKey);
StatePersistence.saveState();
Core.log(`已向 ${hrKey} 发送图片简历`);
return;
}
}
if (!state.hrInteractions.sentResumeHRs.has(hrKey)) {
const sentResume = await this.sendResume();
if (sentResume) {
state.hrInteractions.sentResumeHRs.add(hrKey);
StatePersistence.saveState();
Core.log(`已向 ${hrKey} 发送简历`);
}
}
}
await this._handleKeywordReplies(hrKey, lastMessage);
}
static async _handleKeywordReplies(hrKey, message) {
if (
!message ||
!state.settings.keywordReplies ||
state.settings.keywordReplies.length === 0
) {
return;
}
const messageLower = message.toLowerCase();
for (const replyRule of state.settings.keywordReplies) {
if (!replyRule.keyword || !replyRule.reply) {
continue;
}
const keywordLower = replyRule.keyword.toLowerCase();
if (messageLower.includes(keywordLower)) {
Core.log(`关键词"${replyRule.keyword}",正在回复自定义内容`);
const sent = await this.sendCustomReply(replyRule.reply);
if (sent) {
return true;
}
}
}
return false;
}
static async sendCustomReply(replyText) {
try {
const inputBox = await Core.waitForElement("#chat-input");
if (!inputBox) {
Core.log("未找到聊天输入框");
return false;
}
inputBox.textContent = "";
inputBox.focus();
document.execCommand("insertText", false, replyText);
await Core.delay(CONFIG.OPERATION_INTERVAL / 10);
const sendButton = document.querySelector(".btn-send");
if (sendButton) {
await Core.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) {
Core.log(`发送自定义回复出错: ${error.message}`);
return false;
}
}
static async hasHRResponded() {
await Core.delay(state.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 hasCardMessage() {
try {
const chatContainer = document.querySelector(".chat-message .im-list");
if (!chatContainer) return false;
const friendMessages = Array.from(
chatContainer.querySelectorAll("li.message-item.item-friend")
);
if (friendMessages.length === 0) return false;
const lastMessageEl = friendMessages[friendMessages.length - 1];
const cardWrap = lastMessageEl.querySelector(".message-card-wrap");
return cardWrap !== null;
} catch (error) {
Core.log(`检测卡片消息出错: ${error.message}`);
return false;
}
}
static async handleCardMessage(hrKey) {
try {
const chatContainer = document.querySelector(".chat-message .im-list");
if (!chatContainer) {
Core.log("未找到聊天容器");
return false;
}
const friendMessages = Array.from(
chatContainer.querySelectorAll("li.message-item.item-friend")
);
if (friendMessages.length === 0) {
Core.log("未找到HR消息");
return false;
}
const lastMessageEl = friendMessages[friendMessages.length - 1];
const cardButtons = lastMessageEl.querySelectorAll(".card-btn");
if (!cardButtons || cardButtons.length === 0) {
Core.log("未找到卡片按钮");
return false;
}
for (const btn of cardButtons) {
if (btn.textContent.trim() === "同意") {
await Core.simulateClick(btn);
await Core.delay(state.settings.actionDelays.click);
return true;
}
}
Core.log(`未找到"同意"按钮`);
return false;
} catch (error) {
Core.log(`处理卡片消息出错: ${error.message}`);
return false;
}
}
static async sendGreetings() {
try {
const dictBtn = await Core.waitForElement(".btn-dict");
if (!dictBtn) {
Core.log("未找到常用语(自我介绍)按钮");
return false;
}
await Core.simulateClick(dictBtn);
await Core.smartDelay(state.settings.actionDelays.click, "click");
await Core.smartDelay(300, "dict_load");
const dictList = await Core.waitForElement('ul[data-v-f115c50c=""]');
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.settings.actionDelays.click);
}
return true;
} catch (error) {
Core.log(`发送常用语出错: ${error.message}`);
return false;
}
}
static _findMatchingResume(resumeItems, positionName) {
try {
const positionNameLower = positionName.toLowerCase();
const twoCharKeywords = Core.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;
}
}
}
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 = Core.getPositionName();
if (!positionName) {
Core.log("未找到岗位名称元素");
}
await Core.simulateClick(resumeBtn);
await Core.smartDelay(state.settings.actionDelays.click, "click");
await Core.smartDelay(800, "resume_load");
const confirmDialog = document.querySelector(
".panel-resume.sentence-popover"
);
if (confirmDialog) {
Core.log("您只有一份附件简历");
const confirmBtn = confirmDialog.querySelector(".btn-sure-v2");
if (!confirmBtn) {
Core.log("未找到确认按钮");
return false;
}
await Core.simulateClick(confirmBtn);
return true;
}
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.smartDelay(state.settings.actionDelays.click, "click");
await Core.smartDelay(500, "selection");
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;
}
}
static selectImageResume(positionName) {
try {
const positionNameLower = positionName.toLowerCase();
if (state.settings.imageResumes.length === 1) {
return state.settings.imageResumes[0];
}
const twoCharKeywords = Core.extractTwoCharKeywords(positionNameLower);
for (const keyword of twoCharKeywords) {
for (const resume of state.settings.imageResumes) {
const resumeNameLower = resume.path.toLowerCase();
if (resumeNameLower.includes(keyword)) {
Core.log(`智能匹配: "${resume.path}" 依据: "${keyword}"`);
return resume;
}
}
}
return state.settings.imageResumes[0];
} catch (error) {
Core.log(`选择图片简历出错: ${error.message}`);
return state.settings.imageResumes[0] || null;
}
}
static async sendImageResume() {
try {
if (
!state.settings.useAutoSendImageResume ||
!state.settings.imageResumes ||
state.settings.imageResumes.length === 0
) {
return false;
}
let positionName = Core.getPositionName();
if (!positionName) {
Core.log("未找到岗位名称元素");
}
const selectedResume = this.selectImageResume(positionName);
if (!selectedResume || !selectedResume.data) {
Core.log("没有可发送的图片简历数据");
return false;
}
const imageSendBtn = await Core.waitForElement(
'.toolbar-btn-content.icon.btn-sendimg input[type="file"]'
);
if (!imageSendBtn) {
Core.log("未找到图片发送按钮");
return false;
}
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" });
const file = new File([blob], selectedResume.path, {
type: "image/jpeg",
lastModified: new Date().getTime(),
});
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
imageSendBtn.files = dataTransfer.files;
const event = new Event("change", { bubbles: true });
imageSendBtn.dispatchEvent(event);
return true;
} catch (error) {
Core.log(`发送图片出错: ${error.message}`);
return false;
}
}
}
const UI = {
PAGE_TYPES: {
JOB_LIST: "jobList",
CHAT: "chat",
},
currentPageType: null,
init() {
this.currentPageType = location.pathname.includes("/chat")
? this.PAGE_TYPES.CHAT
: this.PAGE_TYPES.JOB_LIST;
this._applyTheme();
this.createControlPanel();
this.createMiniIcon();
if (this.currentPageType === this.PAGE_TYPES.JOB_LIST && !state.isRunning) {
setTimeout(() => {
Core.loadAndDisplayComments();
}, 500);
}
this.setupJobCardClickListener();
},
setupJobCardClickListener() {
if (this.currentPageType === this.PAGE_TYPES.JOB_LIST) {
document.addEventListener("click", (e) => {
const jobCard = e.target.closest("li.job-card-box");
if (jobCard && !state.isRunning) {
setTimeout(() => {
Core.loadAndDisplayComments();
}, 500);
}
});
}
},
createControlPanel() {
if (document.getElementById("boss-pro-panel")) {
document.getElementById("boss-pro-panel").remove();
}
elements.panel = this._createPanel();
const header = this._createHeader();
const controls = this._createPageControls();
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;
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: "#4285f4",
secondary: "#f5f7fa",
accent: "#e8f0fe",
neutral: "#6b7280",
},
CHAT: {
primary: "#34a853",
secondary: "#f0fdf4",
accent: "#dcfce7",
neutral: "#6b7280",
},
},
_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: 36px;
right: 24px;
width: clamp(300px, 80vw, 400px);
border-radius: 12px;
padding: 12px;
font-family: 'Segoe UI', system-ui, sans-serif;
z-index: 2147483647;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
background: #ffffff;
box-shadow: 0 10px 25px rgba(var(--primary-rgb), 0.15);
border: 1px solid var(--accent-color);
cursor: default;
`;
panel.style.cssText = baseStyles;
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: 0 10px 15px;
margin-bottom: 15px;
border-bottom: 1px solid var(--accent-color);
`;
const title = this._createTitle();
const buttonContainer = document.createElement("div");
buttonContainer.style.cssText = `
display: flex;
gap: 8px;
`;
const buttonTitles =
this.currentPageType === this.PAGE_TYPES.JOB_LIST
? {
activate: "激活插件",
settings: "插件设置",
close: "最小化海投面板",
}
: {
activate: "激活插件",
settings: "海投设置",
close: "最小化聊天面板",
};
const activationIcon = state.activation.isActivated
? ``
: ``;
const activationBtn = this._createIconButton(
activationIcon,
() => {
showActivationDialog();
},
buttonTitles.activate
);
if (state.activation.isActivated) {
activationBtn.style.color = "#fff";
activationBtn.title = "插件已激活";
}
const settingsBtn = this._createIconButton(
"⚙",
() => {
showSettingsDialog();
},
buttonTitles.settings
);
const closeBtn = this._createIconButton(
"✕",
() => {
state.isMinimized = true;
elements.panel.style.transform = "translateY(160%)";
elements.miniIcon.style.display = "flex";
},
buttonTitles.close
);
buttonContainer.append(activationBtn, 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 = "10px";
const customSvg = `
`;
const titleConfig =
this.currentPageType === this.PAGE_TYPES.JOB_LIST
? {
main: `BOSS海投助手`,
sub: "高效求职 · 智能匹配",
}
: {
main: `BOSS智能聊天`,
sub: "智能对话 · 高效沟通",
};
title.innerHTML = `
${customSvg}
${titleConfig.main}
${titleConfig.sub}
`;
return title;
},
_createPageControls() {
if (this.currentPageType === this.PAGE_TYPES.JOB_LIST) {
return this._createJobListControls();
} else {
return this._createChatControls();
}
},
_createJobListControls() {
const container = document.createElement("div");
container.className = "boss-joblist-controls";
container.style.marginBottom = "15px";
container.style.padding = "0 10px";
const filterContainer = this._createFilterContainer();
container.append(filterContainer);
return container;
},
_createChatControls() {
const container = document.createElement("div");
container.className = "boss-chat-controls";
container.style.cssText = `
background: var(--secondary-color);
border-radius: 12px;
padding: 15px;
margin-left: 10px;
margin-right: 10px;
margin-bottom: 15px;
`;
const configRow = document.createElement("div");
configRow.style.cssText = `
display: flex;
gap: 10px;
margin-bottom: 15px;
`;
const communicationIncludeCol = this._createInputControl(
"沟通岗位包含:",
"communication-include",
"如:技术,产品,设计"
);
const communicationModeCol = this._createSelectControl(
"沟通模式:",
"communication-mode-selector",
[
{ value: "new-only", text: "仅新消息" },
{ value: "auto", text: "自动轮询" },
]
);
elements.communicationIncludeInput =
communicationIncludeCol.querySelector("input");
elements.communicationModeSelector =
communicationModeCol.querySelector("select");
configRow.append(communicationIncludeCol, communicationModeCol);
elements.communicationModeSelector.addEventListener("change", (e) => {
settings.communicationMode = e.target.value;
saveSettings();
});
elements.communicationIncludeInput.addEventListener("input", (e) => {
settings.communicationIncludeKeywords = e.target.value;
saveSettings();
});
elements.controlBtn = this._createTextButton(
"开始智能聊天",
"var(--primary-color)",
() => {
toggleChatProcess();
}
);
container.append(configRow, elements.controlBtn);
return container;
},
_createFilterContainer() {
const filterContainer = document.createElement("div");
filterContainer.style.cssText = `
background: var(--secondary-color);
border-radius: 12px;
padding: 15px;
margin-bottom: 0px;
`;
const filterRow = document.createElement("div");
filterRow.style.cssText = `
display: flex;
gap: 10px;
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);
elements.controlBtn = this._createTextButton(
"启动海投",
"var(--primary-color)",
() => {
toggleProcess();
}
);
filterContainer.append(filterRow, elements.controlBtn);
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:5px; font-weight: 500; color: #333; font-size: 0.9rem;";
const input = document.createElement("input");
input.id = id;
input.placeholder = placeholder;
input.style.cssText = `
width: 100%;
padding: 8px 10px;
border-radius: 8px;
border: 1px solid #d1d5db;
font-size: 14px;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
transition: all 0.2s ease;
`;
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:5px; font-weight: 500; color: #333; font-size: 0.9rem;";
const select = document.createElement("select");
select.id = id;
select.style.cssText = `
width: 100%;
padding: 8px 10px;
border-radius: 8px;
border: 1px solid #d1d5db;
font-size: 14px;
background: white;
color: #333;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
transition: all 0.2s ease;
`;
options.forEach((option) => {
const opt = document.createElement("option");
opt.value = option.value;
opt.textContent = option.text;
select.appendChild(opt);
});
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 ? "260px" : "260px";
log.style.cssText = `
height: ${height};
overflow-y: auto;
background: var(--secondary-color);
border-radius: 12px;
padding: 12px;
font-size: 13px;
line-height: 1.5;
margin-bottom: 15px;
margin-left: 10px;
margin-right: 10px;
transition: all 0.3s ease;
user-select: text;
scrollbar-width: thin;
scrollbar-color: var(--primary-color) var(--secondary-color);
`;
log.innerHTML += `
`;
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 = `
text-align: center;
font-size: 0.8em;
color: var(--neutral-color);
padding-top: 15px;
border-top: 1px solid var(--accent-color);
margin-top: auto;
padding: 0px;
`;
const statsContainer = document.createElement("div");
statsContainer.style.cssText = `
display: flex;
justify-content: space-around;
margin-bottom: 15px;
`;
footer.append(
statsContainer,
document.createTextNode("© 2026 Yangshengzhou · All Rights Reserved")
);
return footer;
},
_createTextButton(text, bgColor, onClick) {
const btn = document.createElement("button");
btn.className = "boss-btn";
btn.textContent = text;
btn.style.cssText = `
width: 100%;
padding: 10px 16px;
background: ${bgColor};
color: #fff;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 15px;
font-weight: 500;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
transform: translateY(0px);
margin: 0 auto;
`;
this._addButtonHoverEffects(btn);
btn.addEventListener("click", onClick);
return btn;
},
_createIconButton(icon, onClick, title) {
const btn = document.createElement("button");
btn.className = "boss-icon-btn";
btn.innerHTML = icon;
btn.title = title;
const isActivationBtn = title === "激活插件";
const isActivated = state.activation.isActivated;
if (isActivationBtn && isActivated) {
btn.disabled = true;
btn.title = "插件已激活";
}
btn.style.cssText = `
width: 32px;
height: 32px;
border-radius: 50%;
border: none;
background: ${this.currentPageType === this.PAGE_TYPES.JOB_LIST
? "var(--accent-color)"
: "var(--accent-color)"
};
cursor: ${isActivationBtn && isActivated ? "not-allowed" : "pointer"
};
font-size: 16px;
transition: all 0.2s ease;
display: flex;
justify-content: center;
align-items: center;
color: var(--primary-color);
overflow: hidden;
opacity: ${isActivationBtn && isActivated ? "0.5" : "1"};
`;
if (icon.includes("