// ==UserScript==
// @name 忒星boss直聘批量简历投递+自动发送自定义消息[忒星修复魔改版]
// @description 忒星boss直聘批量简历投递[忒星修复魔改版]
// @namespace yongjiu
// @version 1.2.4
// @author maple,Ocyss,忒星
// @license Apache License 2.0
// @run-at document-start
// @match https://www.zhipin.com/*
// @connect https://github.com/yongjiu8/boss_push
// @include https://www.zhipin.com
// @require https://unpkg.com/[email protected]/log.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/js2wordcloud.min.js
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_addValueChangeListener
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_cookie
// @grant GM_notification
// ==/UserScript==
"use strict";
let logger = Logger.log("info")
class BossBatchExp extends Error {
constructor(msg) {
super(msg);
this.name = "BossBatchExp";
}
}
class JobNotMatchExp extends BossBatchExp {
constructor(msg) {
super(msg);
this.name = "JobNotMatchExp";
}
}
class PublishLimitExp extends BossBatchExp {
constructor(msg) {
super(msg);
this.name = "PublishLimitExp";
}
}
class FetchJobDetailFailExp extends BossBatchExp {
constructor(msg) {
super(msg);
this.name = "FetchJobDetailFailExp";
}
}
class SendPublishExp extends BossBatchExp {
constructor(msg) {
super(msg);
this.name = "SendPublishExp";
}
}
class PublishStopExp extends BossBatchExp {
constructor(msg) {
super(msg);
this.name = "PublishStopExp";
}
}
class TampermonkeyApi {
static CUR_CK = ""
constructor() {
// fix 还未创建对象时,CUR_CK为空字符串,创建完对象之后【如果没有配置,则为null】导致key前缀不一致
TampermonkeyApi.CUR_CK = GM_getValue("ck_cur", "");
}
static GmSetValue(key, val) {
return GM_setValue(TampermonkeyApi.CUR_CK + key, val);
}
static GmGetValue(key, defVal) {
return GM_getValue(TampermonkeyApi.CUR_CK + key, defVal);
}
static GMXmlHttpRequest(options) {
return GM_xmlhttpRequest(options)
}
static GmAddValueChangeListener(key, func) {
return GM_addValueChangeListener(TampermonkeyApi.CUR_CK + key, func);
}
static GmNotification(content) {
GM_notification({
title: "Boss直聘批量投简历",
image:
"https://img.bosszhipin.com/beijin/mcs/banner/3e9d37e9effaa2b6daf43f3f03f7cb15cfcd208495d565ef66e7dff9f98764da.jpg",
text: content,
highlight: true, // 布尔值,是否突出显示发送通知的选项卡
silent: true, // 布尔值,是否播放声音
timeout: 10000, // 设置通知隐藏时间
onclick: function () {
console.log("点击了通知");
},
ondone() {
}, // 在通知关闭(无论这是由超时还是单击触发)或突出显示选项卡时调用
});
}
}
class Tools {
/**
* 模糊匹配
* @param arr
* @param input
* @param emptyStatus
* @returns {boolean|*}
*/
static fuzzyMatch(arr, input, emptyStatus) {
if (arr.length === 0) {
// 为空时直接返回指定的空状态
return emptyStatus;
}
input = input.toLowerCase();
let emptyEle = false;
// 遍历数组中的每个元素
for (let i = 0; i < arr.length; i++) {
// 如果当前元素包含指定值,则返回 true
let arrEleStr = arr[i].toLowerCase();
if (arrEleStr.length === 0) {
emptyEle = true;
continue;
}
if (arrEleStr.includes(input) || input.includes(arrEleStr)) {
return true;
}
}
// 所有元素均为空元素【返回空状态】
if (emptyEle) {
return emptyStatus;
}
// 如果没有找到匹配的元素,则返回 false
return false;
}
// 范围匹配
static rangeMatch(rangeStr, input, by = 1) {
if (!rangeStr) {
return true;
}
// 匹配定义范围的正则表达式
let reg = /^(\d+)(?:-(\d+))?$/;
let match = rangeStr.match(reg);
if (match) {
let start = parseInt(match[1]) * by;
let end = parseInt(match[2] || match[1]) * by;
// 如果输入只有一个数字的情况
if (/^\d+$/.test(input)) {
let number = parseInt(input);
return number >= start && number <= end;
}
// 如果输入有两个数字的情况
let inputReg = /^(\d+)(?:-(\d+))?/;
let inputMatch = input.match(inputReg);
if (inputMatch) {
let inputStart = parseInt(inputMatch[1]);
let inputEnd = parseInt(inputMatch[2] || inputMatch[1]);
return (
(inputStart >= start && inputStart <= end) ||
(inputEnd >= start && inputEnd <= end)
);
}
}
// 其他情况均视为不匹配
return false;
}
/**
* 语义匹配
* @param configArr
* @param content
* @returns {boolean}
*/
static semanticMatch(configArr, content) {
for (let i = 0; i < configArr.length; i++) {
if (!configArr[i]) {
continue
}
let re = new RegExp("(?<!(不|无).{0,5})" + configArr[i] + "(?!系统|软件|工具|服务)");
if (re.test(content)) {
return configArr[i];
}
}
}
static bossIsActive(activeText) {
return !(activeText.includes("月") || activeText.includes("年"));
}
static getRandomNumber(startMs, endMs) {
return Math.floor(Math.random() * (endMs - startMs + 1)) + startMs;
}
static getCookieValue(key) {
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const [cookieKey, cookieValue] = cookie.trim().split('=');
if (cookieKey === key) {
return decodeURIComponent(cookieValue);
}
}
return null;
}
static parseURL(url) {
const urlObj = new URL(url);
const pathSegments = urlObj.pathname.split('/');
const jobId = pathSegments[2].replace('.html', '');
const lid = urlObj.searchParams.get('lid');
const securityId = urlObj.searchParams.get('securityId');
return {
securityId,
jobId,
lid
};
}
static queryString(baseURL, queryParams) {
const queryString = Object.entries(queryParams)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
return `${baseURL}?${queryString}`;
}
}
class DOMApi {
static createTag(tag, name, style) {
let htmlTag = document.createElement(tag);
if (name) {
htmlTag.innerHTML = name;
}
if (style) {
htmlTag.style.cssText = style;
}
return htmlTag;
}
static createInputTag(descName, valueStr) {
const inputNameLabel = document.createElement("label");
inputNameLabel.textContent = descName;
const inputTag = document.createElement("input");
inputTag.type = "text";
inputNameLabel.appendChild(inputTag);
if (valueStr) {
inputTag.value = valueStr;
}
// 样式
inputNameLabel.style.cssText = "display: inline-block; margin: 0px 10px; font-weight: bold; width: 200px;";
inputTag.style.cssText = "margin-left: 2px; width: 100%; padding: 5px; border-radius: 5px; border: 1px solid rgb(204, 204, 204); box-sizing: border-box;";
return inputNameLabel;
}
static getInputVal(inputLab) {
return inputLab.querySelector("input").value
}
static eventListener(tag, eventType, func) {
tag.addEventListener(eventType, func)
}
static delElement(name, loop = false, el = document) {
let t = setInterval(() => {
const element = el.querySelector(name)
if (!element) {
if (!loop) {
clearInterval(t)
}
return
}
element.remove()
clearInterval(t)
}, 1000)
}
static setElement(name, style, el = document) {
const element = el.querySelector(name)
if (element) {
for (let atr in style) {
element.style[atr] = style[atr]
}
}
}
}
class OperationPanel {
constructor(jobListHandler) {
// button
this.batchPushBtn = null
this.activeSwitchBtn = null
this.sendSelfGreetSwitchBtn = null
this.headhunterSwitchBtn = null
this.bossOnlineSwitchBtn = null
// inputLab
// 公司名包含输入框lab
this.cnInInputLab = null
// 公司名排除输入框lab
this.cnExInputLab = null
// job名称包含输入框lab
this.jnInInputLab = null
// job内容排除输入框lab
this.jcExInputLab = null
// 薪资范围输入框lab
this.srInInputLab = null
// 公司规模范围输入框lab
this.csrInInputLab = null
// 自定义招呼语lab
this.selfGreetInputLab = null
// 词云图
this.worldCloudModal = null
this.worldCloudState = false // false:标签 true:内容
this.worldCloudAllBtn = null
this.topTitle = null
// boss活跃度检测
this.bossActiveState = true;
//hr在线检测
this.bossOnlineState = true;
// 发送自定义招呼语
this.sendSelfGreet = false;
// 猎头岗位检测
this.headhunterState = true;
// 文档说明
this.docTextArr = [
"!加油,相信自己😶🌫️",
"1.批量投递:点击批量投递开始批量投简历,请先通过上方Boss的筛选功能筛选大致的范围,然后通过脚本的筛选进一步确认投递目标。",
"2.生成Job词云图:获取当前页面的所有job详情,并进行分词权重分析;生成岗位热点词汇词云图;帮助分析简历匹配度",
"3.保存配置:保持下方脚本筛选项,用于后续直接使用当前配置。",
"4.过滤不活跃Boss:打开后会自动过滤掉最近未活跃的Boss发布的工作。以免浪费每天的100次机会。",
"5.发送自定义招呼语:因为boss不支持将自定义的招呼语设置为默认招呼语。开启表示发送boss默认的招呼语后还会发送自定义招呼语",
"6.过滤猎头岗位:打开后会自动过滤掉猎头发布的工作。猎头的岗位要求一般都非常高,实际投此类岗位是无意义的,以免浪费每天的100次机会。",
"7.可以在网站管理中打开通知权限,当停止时会自动发送桌面端通知提醒。",
"😏",
"脚本筛选项介绍:",
"公司名包含:投递工作的公司名一定包含在当前集合中,模糊匹配,多个使用逗号分割。这个一般不用,如果使用了也就代表只投这些公司的岗位。例子:【阿里,华为】",
"排除公司名:投递工作的公司名一定不在当前集合中,也就是排除当前集合中的公司,模糊匹配,多个使用逗号分割。例子:【xxx外包】",
"排除工作内容:会自动检测上文(不是,不,无需等关键字),下文(系统,工具),例子:【外包,上门,销售,驾照】,如果写着是'不是外包''销售系统'那也不会被排除",
"Job名包含:投递工作的名称一定包含在当前集合中,模糊匹配,多个使用逗号分割。例如:【软件,Java,后端,服务端,开发,后台】",
"薪资范围:投递工作的薪资范围一定在当前区间中,一定是区间,使用-连接范围。例如:【12-20】",
"公司规模范围:投递工作的公司人员范围一定在当前区间中,一定是区间,使用-连接范围。例如:【500-20000000】",
"自定义招呼语:编辑自定义招呼语,当【发送自定义招呼语】打开时,投递后发送boss默认的招呼语【建议关闭默认打招呼语,使用自定义招呼语】后还会发送自定义招呼语;使用<br> \\n 换行;例子:【你好\\n我...】",
"<h3>作者失业,找到工作别忘了赞助支持一下哦,多谢</h3>",
"<img src='https://www.teixing.com/img/pay1.jpg' height='300px'/><img src='https://www.teixing.com/img/pay2.png' height='300px' />"
];
// 相关链接
this.aboutLink = [
[
["GreasyFork", "https://greasyfork.org/zh-CN/scripts?q=%E5%BF%92%E6%98%9Fboss%E7%9B%B4%E8%81%98%E6%89%B9%E9%87%8F%E7%AE%80%E5%8E%86%E6%8A%95%E9%80%92",],
["魔改作者:忒星", "https://github.com/yongjiu8"],
["原版作者:yangfeng20", "https://github.com/yangfeng20"],
["去GitHub点个star⭐", "https://github.com/yongjiu8/boss_push"],
]
]
this.scriptConfig = new ScriptConfig()
this.jobListHandler = jobListHandler;
}
init() {
this.renderOperationPanel();
this.registerEvent();
}
/**
* 渲染操作面板
*/
renderOperationPanel() {
logger.info("操作面板开始初始化")
// 1.创建操作按钮并添加到按钮容器中【以下绑定事件处理函数均采用箭头函数作为中转,避免this执行事件对象】
let btnCssText = "display: inline-block;border-radius: 4px;background: #e5f8f8;color: #00a6a7; text-decoration: none;margin: 20px 20px 0px 20px;padding: 6px 12px;cursor: pointer";
// 批量投递按钮
let batchPushBtn = DOMApi.createTag("div", "批量投递", btnCssText);
this.batchPushBtn = batchPushBtn
DOMApi.eventListener(batchPushBtn, "click", () => {
this.batchPushBtnHandler()
})
// 保存配置按钮
let storeConfigBtn = DOMApi.createTag("div", "保存配置", btnCssText);
DOMApi.eventListener(storeConfigBtn, "click", () => {
this.storeConfigBtnHandler()
})
// 生成Job词云图按钮
let generateImgBtn = DOMApi.createTag("div", "生成词云图", btnCssText);
DOMApi.eventListener(generateImgBtn, "click", () => {
this.worldCloudModal.style.display = "flex"
this.refreshQuantity()
})
// 投递后发送自定义打招呼语句
this.sendSelfGreetSwitchBtn = DOMApi.createTag("div", "发送自定义打招呼语句", btnCssText);
DOMApi.eventListener(this.sendSelfGreetSwitchBtn, "click", () => {
this.sendSelfGreetSwitchBtnHandler(!this.sendSelfGreet)
})
this.sendSelfGreetSwitchBtnHandler(TampermonkeyApi.GmGetValue(ScriptConfig.SEND_SELF_GREET_ENABLE, false))
// 过滤不活跃boss按钮
this.activeSwitchBtn = DOMApi.createTag("div", "活跃度过滤", btnCssText);
DOMApi.eventListener(this.activeSwitchBtn, "click", () => {
this.activeSwitchBtnHandler(!this.bossActiveState)
})
// 默认开启活跃校验
this.activeSwitchBtnHandler(this.bossActiveState)
// 过滤HR在线按钮
this.bossOnlineSwitchBtn = DOMApi.createTag("div", "HR在线过滤", btnCssText);
DOMApi.eventListener(this.bossOnlineSwitchBtn, "click", () => {
this.bossOnlineSwitchBtnHandler(!this.bossOnlineState)
})
//默认开启HR在线
this.bossOnlineSwitchBtnHandler(this.bossOnlineState)
// 过滤猎头岗位
this.headhunterSwitchBtn = DOMApi.createTag("div", "过滤猎头岗位", btnCssText);
DOMApi.eventListener(this.headhunterSwitchBtn, "click", () => {
this.sendHeadhunterSwitchBtnHandler(!this.headhunterState)
})
this.sendHeadhunterSwitchBtnHandler(TampermonkeyApi.GmGetValue(ScriptConfig.SEND_HEADHUNTER_ENABLE, true));
// 2.创建筛选条件输入框并添加到input容器中
this.cnInInputLab = DOMApi.createInputTag("公司名包含", this.scriptConfig.getCompanyNameInclude());
this.cnExInputLab = DOMApi.createInputTag("公司名排除", this.scriptConfig.getCompanyNameExclude());
this.jnInInputLab = DOMApi.createInputTag("工作名包含", this.scriptConfig.getJobNameInclude());
this.jcExInputLab = DOMApi.createInputTag("工作内容排除", this.scriptConfig.getJobContentExclude());
this.srInInputLab = DOMApi.createInputTag("薪资范围", this.scriptConfig.getSalaryRange());
this.csrInInputLab = DOMApi.createInputTag("公司规模范围", this.scriptConfig.getCompanyScaleRange());
this.selfGreetInputLab = DOMApi.createInputTag("自定义招呼语", this.scriptConfig.getSelfGreet());
DOMApi.eventListener(this.selfGreetInputLab.querySelector("input"), "blur", () => {
// 失去焦点,编辑的招呼语保存到内存中;用于msgPage每次实时获取到最新的,即便不保存
ScriptConfig.setSelfGreetMemory(DOMApi.getInputVal(this.selfGreetInputLab))
})
// 每次刷新页面;将保存的数据覆盖内存临时数据;否则编辑了自定义招呼语,未保存刷新页面;发的的是之前内存中编辑的临时数据
ScriptConfig.setSelfGreetMemory(this.scriptConfig.getSelfGreet())
let inputContainerDiv = DOMApi.createTag("div", "", "margin: 10px 0px;");
inputContainerDiv.appendChild(this.cnInInputLab)
inputContainerDiv.appendChild(this.cnExInputLab)
inputContainerDiv.appendChild(this.jnInInputLab)
inputContainerDiv.appendChild(this.jcExInputLab)
inputContainerDiv.appendChild(this.srInInputLab)
inputContainerDiv.appendChild(this.csrInInputLab)
inputContainerDiv.appendChild(this.selfGreetInputLab)
// 进度显示
this.showTable = this.buildShowTable();
// 操作面板结构:
let operationPanel = DOMApi.createTag("div");
// 说明文档
// 链接关于
// 操作按钮
// 筛选输入框
// iframe【详情页投递内部页】
operationPanel.appendChild(this.buildDocDiv())
operationPanel.appendChild(inputContainerDiv)
// 发送自定义招呼语的iframe
operationPanel.appendChild(this.buildMsgPageIframe())
operationPanel.appendChild(this.showTable)
// 词云图模态框 加到根节点
document.body.appendChild(this.buildWordCloudModel())
// 找到页面锚点并将操作面板添加入页面
let timingCutPageTask = setInterval(() => {
logger.info("等待页面加载,添加操作面板")
// 页面锚点
const jobSearchWrapper = document.querySelector(".job-search-wrapper")
if (!jobSearchWrapper) {
return;
}
const jobConditionWrapper = jobSearchWrapper.querySelector(".search-condition-wrapper")
if (!jobConditionWrapper) {
return
}
let topTitle = DOMApi.createTag("h2");
this.topTitle = topTitle;
topTitle.textContent = `Boos直聘投递助手(${this.scriptConfig.getVal(ScriptConfig.PUSH_COUNT, 0)}次) 记得 star⭐`;
jobConditionWrapper.insertBefore(topTitle, jobConditionWrapper.firstElementChild)
// 按钮/搜索换位
const jobSearchBox = jobSearchWrapper.querySelector(".job-search-box")
jobSearchBox.style.margin = "20px 0"
jobSearchBox.style.width = "100%"
const city = jobConditionWrapper.querySelector(".city-area-select")
city.querySelector(".city-area-current").style.width = "85px"
const condition = jobSearchWrapper.querySelectorAll(".condition-industry-select,.condition-position-select,.condition-filter-select,.clear-search-btn")
const cityAreaDropdown = jobSearchWrapper.querySelector(".city-area-dropdown")
cityAreaDropdown.insertBefore(jobSearchBox, cityAreaDropdown.firstElementChild)
const filter = DOMApi.createTag("div", "", "overflow:hidden ")
condition.forEach(item => {
filter.appendChild(item)
})
filter.appendChild(DOMApi.createTag("div", "", "clear:both"))
cityAreaDropdown.appendChild(filter)
const bttt = [batchPushBtn, generateImgBtn, storeConfigBtn, this.activeSwitchBtn, this.bossOnlineSwitchBtn, this.sendSelfGreetSwitchBtn, this.headhunterSwitchBtn]
bttt.forEach(item => {
jobConditionWrapper.appendChild(item);
})
cityAreaDropdown.appendChild(operationPanel);
clearInterval(timingCutPageTask);
logger.info("初始化【操作面板】成功")
// 页面美化
this.pageBeautification()
}, 1000);
}
/**
* 页面美化
*/
pageBeautification() {
// 侧栏
DOMApi.delElement(".job-side-wrapper")
// 侧边悬浮框
DOMApi.delElement(".side-bar-box")
// 新职位发布时通知我
DOMApi.delElement(".subscribe-weixin-wrapper", true)
// 搜索栏登录框
DOMApi.delElement(".go-login-btn")
// 搜索栏去APP
DOMApi.delElement(".job-search-scan", true)
// 顶部面板
// DOMApi.setElement(".job-search-wrapper",{width:"90%"})
// DOMApi.setElement(".page-job-content",{width:"90%"})
// DOMApi.setElement(".job-list-wrapper",{width:"100%"})
GM_addStyle(`
.job-search-wrapper,.page-job-content{width: 90% !important}
.job-list-wrapper,.job-card-wrapper,.job-search-wrapper.fix-top{width: 100% !important}
.job-card-wrapper .job-card-body{display: flex;justify-content: space-between;}
.job-card-wrapper .job-card-left{width: 50% !important}
.job-card-wrapper .start-chat-btn,.job-card-wrapper:hover .info-public{display: initial !important}
.job-card-wrapper .job-card-footer{min-height: 48px;display: flex;justify-content: space-between}
.job-card-wrapper .clearfix:after{content: none}
.job-card-wrapper .job-card-footer .info-desc{width: auto !important}
.job-card-wrapper .job-card-footer .tag-list{width: auto !important;margin-right:10px}
.city-area-select.pick-up .city-area-dropdown{width: 80vw;min-width: 1030px;}
.job-search-box .job-search-form{width: 100%;}
.job-search-box .job-search-form .city-label{width: 10%;}
.job-search-box .job-search-form .search-input-box{width: 82%;}
.job-search-box .job-search-form .search-btn{width: 8%;}
.job-search-wrapper.fix-top .job-search-box, .job-search-wrapper.fix-top .search-condition-wrapper{width: 90%;min-width:990px;}
`)
logger.info("初始化【页面美化】成功")
}
registerEvent() {
TampermonkeyApi.GmAddValueChangeListener(ScriptConfig.PUSH_COUNT, this.publishCountChangeEventHandler.bind(this))
}
refreshShow(text) {
this.showTable.innerHTML = "当前操作:" + text
}
refreshQuantity() {
this.worldCloudAllBtn.innerHTML = `生成全部(${this.jobListHandler.cacheSize()}个)`
}
/*-------------------------------------------------构建复合DOM元素--------------------------------------------------*/
buildDocDiv() {
const docDiv = DOMApi.createTag("div", "", "margin: 10px 0px; width: 100%;")
let txtDiv = DOMApi.createTag("div", "", "display: block;");
const title = DOMApi.createTag("h3", "操作说明(点击关闭)", "margin: 10px 0px;cursor: pointer")
docDiv.appendChild(title)
docDiv.appendChild(txtDiv)
this.docTextArr.forEach(doc => {
const textTag = document.createElement("p");
textTag.style.color = "#666";
textTag.innerHTML = doc;
txtDiv.appendChild(textTag)
})
this.aboutLink.forEach((linkMap) => {
let about = DOMApi.createTag("p", "", "padding-top: 12px;");
linkMap.forEach((item) => {
const a = document.createElement("a");
a.innerText = item[0];
a.href = item[1];
a.target = "_blank";
a.style.margin = "0 20px 0 0";
about.appendChild(a);
});
txtDiv.appendChild(about);
});
// 点击title,内部元素折叠
DOMApi.eventListener(title, "click", () => {
let divDisplay = txtDiv.style.display;
if (divDisplay === 'block' || divDisplay === '') {
txtDiv.style.display = 'none';
} else {
txtDiv.style.display = 'block';
}
})
return docDiv;
}
buildMsgPageIframe() {
let msgPageIframe = DOMApi.createTag("iframe", "", "height:1px;width: 1px;");
msgPageIframe.src = 'https://www.zhipin.com/web/geek/chat';
msgPageIframe.id = 'msgIframe';
return msgPageIframe
}
buildShowTable() {
return DOMApi.createTag('p', '', 'font-size: 20px;color: rgb(64, 158, 255);margin-left: 50px;');
}
buildWordCloudModel() {
this.worldCloudModal = DOMApi.createTag("div", `
<div class="dialog-layer"></div>
<div class="dialog-container" style="width: 80%;height: 80%;">
<div class="dialog-header">
<h3>词云图</h3>
<span class="close"><i class="icon-close"></i></span>
</div>
<div class="dialog-body" style="height: 98%;width: 100%;display: flex;flex-direction: column;">
<div id="worldCloudCanvas" class="dialog-body" style="height: 100%;width: 100%;flex-grow: inherit;"></div>
</div>
</div>
`, "display: none;")
const model = this.worldCloudModal
model.className = "dialog-wrap"
model.querySelector(".close").onclick = function () {
model.style.display = "none";
}
const body = model.querySelector(".dialog-body")
const div = DOMApi.createTag("div")
let btnCssText = "display: inline-block;border-radius: 4px;background: #e5f8f8;color: #00a6a7; text-decoration: none;margin: 0px 20px;padding: 6px 12px;cursor: pointer";
// 当前状态
let stateBtn = DOMApi.createTag("div", "状态: 工作标签", btnCssText);
DOMApi.eventListener(stateBtn, "click", () => {
if (this.worldCloudState) {
stateBtn.innerHTML = "状态: 工作标签"
} else {
stateBtn.innerHTML = "状态: 工作内容"
}
this.worldCloudState = !this.worldCloudState
})
// 爬取当前页面生成词云
let curBtn = DOMApi.createTag("div", "生成当前页", btnCssText);
DOMApi.eventListener(curBtn, "click", () => {
if (this.worldCloudState) {
this.generateImgHandler()
} else {
this.generateImgHandlerJobLabel()
}
})
// 根据已爬取的数据生成词云
let allBtn = DOMApi.createTag("div", "生成全部(0个)", btnCssText);
DOMApi.eventListener(allBtn, "click", () => {
if (this.worldCloudState) {
// this.generateImgHandlerAll()
window.alert("卡顿严重,数据量大已禁用,请用标签模式")
} else {
this.generateImgHandlerJobLabelAll()
}
})
this.worldCloudAllBtn = allBtn
// 清空已爬取的数据
let delBtn = DOMApi.createTag("div", "清空数据", btnCssText);
DOMApi.eventListener(delBtn, "click", () => {
this.jobListHandler.cacheClear()
this.refreshQuantity()
})
div.appendChild(stateBtn)
div.appendChild(curBtn)
div.appendChild(allBtn)
div.appendChild(delBtn)
body.insertBefore(div, body.firstElementChild)
return this.worldCloudModal
}
/*-------------------------------------------------操作面板事件处理--------------------------------------------------*/
batchPushBtnHandler() {
this.jobListHandler.batchPushHandler()
}
/**
* 生成词云图
* 使用的数据源为 job工作内容,进行分词
*/
generateImgHandler() {
let jobList = BossDOMApi.getJobList();
let allJobContent = ""
this.refreshShow("生成词云图【获取Job数据中】")
Array.from(jobList).reduce((promiseChain, jobTag) => {
return promiseChain
.then(() => this.jobListHandler.reqJobDetail(jobTag))
.then(jobCardJson => {
allJobContent += jobCardJson.postDescription + ""
})
}, Promise.resolve())
.then(() => {
this.refreshShow("生成词云图【构建数据中】")
return JobWordCloud.participle(allJobContent)
}).then(worldArr => {
let weightWordArr = JobWordCloud.buildWord(worldArr);
logger.info("根据权重排序的world结果:", JobWordCloud.getKeyWorldArr(weightWordArr));
JobWordCloud.generateWorldCloudImage("worldCloudCanvas", weightWordArr)
this.refreshShow("生成词云图【完成】")
})
}
/**
* 生成词云图
* 使用的数据源为 job标签,并且不进行分词,直接计算权重
*/
generateImgHandlerJobLabel() {
let jobList = BossDOMApi.getJobList();
let jobLabelArr = []
this.refreshShow("生成词云图【获取Job数据中】")
Array.from(jobList).reduce((promiseChain, jobTag) => {
return promiseChain
.then(() => this.jobListHandler.reqJobDetail(jobTag))
.then(jobCardJson => {
jobLabelArr.push(...jobCardJson.jobLabels)
})
}, Promise.resolve())
.then(() => {
this.refreshShow("生成词云图【构建数据中】")
let weightWordArr = JobWordCloud.buildWord(jobLabelArr);
logger.info("根据权重排序的world结果:", JobWordCloud.getKeyWorldArr(weightWordArr));
this.worldCloudModal.style.display = "flex"
JobWordCloud.generateWorldCloudImage("worldCloudCanvas", weightWordArr)
this.refreshShow("生成词云图【完成】")
})
}
/**
* 生成All词云图
* 使用的数据源为 job工作内容,进行分词
*/
generateImgHandlerAll() {
let allJobContent = ""
this.jobListHandler.cache.forEach((val) => {
allJobContent += val.postDescription
})
Promise.resolve()
.then(() => {
this.refreshShow("生成词云图【构建数据中】")
return JobWordCloud.participle(allJobContent)
}).then(worldArr => {
let weightWordArr = JobWordCloud.buildWord(worldArr);
logger.info("根据权重排序的world结果:", JobWordCloud.getKeyWorldArr(weightWordArr));
JobWordCloud.generateWorldCloudImage("worldCloudCanvas", weightWordArr)
this.refreshShow("生成词云图【完成】")
})
}
/**
* 生成All词云图
* 使用的数据源为 job标签,并且不进行分词,直接计算权重
*/
generateImgHandlerJobLabelAll() {
let jobLabelArr = []
this.jobListHandler.cache.forEach((val) => {
jobLabelArr.push(...val.jobLabels)
})
this.refreshShow("生成词云图【获取Job数据中】")
Promise.resolve()
.then(() => {
this.refreshShow("生成词云图【构建数据中】")
let weightWordArr = JobWordCloud.buildWord(jobLabelArr);
logger.info("根据权重排序的world结果:", JobWordCloud.getKeyWorldArr(weightWordArr));
this.worldCloudModal.style.display = "flex"
JobWordCloud.generateWorldCloudImage("worldCloudCanvas", weightWordArr)
this.refreshShow("生成词云图【完成】")
})
}
readInputConfig() {
this.scriptConfig.setCompanyNameInclude(DOMApi.getInputVal(this.cnInInputLab))
this.scriptConfig.setCompanyNameExclude(DOMApi.getInputVal(this.cnExInputLab))
this.scriptConfig.setJobNameInclude(DOMApi.getInputVal(this.jnInInputLab))
this.scriptConfig.setJobContentExclude(DOMApi.getInputVal(this.jcExInputLab))
this.scriptConfig.setSalaryRange(DOMApi.getInputVal(this.srInInputLab))
this.scriptConfig.setCompanyScaleRange(DOMApi.getInputVal(this.csrInInputLab))
this.scriptConfig.setSelfGreet(DOMApi.getInputVal(this.selfGreetInputLab))
}
storeConfigBtnHandler() {
// 先修改配置对象内存中的值,然后更新到本地储存中
this.readInputConfig()
logger.info("config", this.scriptConfig)
this.scriptConfig.storeConfig()
}
activeSwitchBtnHandler(isOpen) {
this.bossActiveState = isOpen;
if (this.bossActiveState) {
this.activeSwitchBtn.innerText = "过滤不活跃Boss:已开启";
this.activeSwitchBtn.style.backgroundColor = "rgb(215,254,195)";
this.activeSwitchBtn.style.color = "rgb(2,180,6)";
} else {
this.activeSwitchBtn.innerText = "过滤不活跃Boss:已关闭";
this.activeSwitchBtn.style.backgroundColor = "rgb(251,224,224)";
this.activeSwitchBtn.style.color = "rgb(254,61,61)";
}
this.scriptConfig.setVal(ScriptConfig.ACTIVE_ENABLE, isOpen)
}
//检查是否在线
bossOnlineSwitchBtnHandler(isOpen) {
this.bossOnlineState = isOpen;
if (this.bossOnlineState) {
this.bossOnlineSwitchBtn.innerText = "过滤HR在线:已开启";
this.bossOnlineSwitchBtn.style.backgroundColor = "rgb(215,254,195)";
this.bossOnlineSwitchBtn.style.color = "rgb(2,180,6)";
} else {
this.bossOnlineSwitchBtn.innerText = "过滤HR在线:已关闭";
this.bossOnlineSwitchBtn.style.backgroundColor = "rgb(251,224,224)";
this.bossOnlineSwitchBtn.style.color = "rgb(254,61,61)";
}
this.scriptConfig.setVal(ScriptConfig.BOSS_ONLINE_ENABLE, isOpen)
}
sendSelfGreetSwitchBtnHandler(isOpen) {
this.sendSelfGreet = isOpen;
if (isOpen) {
this.sendSelfGreetSwitchBtn.innerText = "发送自定义招呼语:已开启";
this.sendSelfGreetSwitchBtn.style.backgroundColor = "rgb(215,254,195)";
this.sendSelfGreetSwitchBtn.style.color = "rgb(2,180,6)";
} else {
this.sendSelfGreetSwitchBtn.innerText = "发送自定义招呼语:已关闭";
this.sendSelfGreetSwitchBtn.style.backgroundColor = "rgb(251,224,224)";
this.sendSelfGreetSwitchBtn.style.color = "rgb(254,61,61)";
}
this.scriptConfig.setVal(ScriptConfig.SEND_SELF_GREET_ENABLE, isOpen)
}
sendHeadhunterSwitchBtnHandler(isOpen) {
this.headhunterState = isOpen;
if (isOpen) {
this.headhunterSwitchBtn.innerText = "过滤猎头岗位:已开启";
this.headhunterSwitchBtn.style.backgroundColor = "rgb(215,254,195)";
this.headhunterSwitchBtn.style.color = "rgb(2,180,6)";
} else {
this.headhunterSwitchBtn.innerText = "过滤猎头岗位:已关闭";
this.headhunterSwitchBtn.style.backgroundColor = "rgb(251,224,224)";
this.headhunterSwitchBtn.style.color = "rgb(254,61,61)";
}
this.scriptConfig.setVal(ScriptConfig.SEND_HEADHUNTER_ENABLE, isOpen)
}
publishCountChangeEventHandler(key, oldValue, newValue, isOtherScriptChange) {
this.topTitle.textContent = `Boos直聘投递助手(${newValue}次) 记得 star⭐`;
logger.info("投递次数变更事件", {key, oldValue, newValue, isOtherScriptChange})
}
/*-------------------------------------------------other method--------------------------------------------------*/
changeBatchPublishBtn(start) {
if (start) {
this.batchPushBtn.innerHTML = "停止投递"
this.batchPushBtn.style.backgroundColor = "rgb(251,224,224)";
this.batchPushBtn.style.color = "rgb(254,61,61)";
} else {
this.batchPushBtn.innerHTML = "批量投递"
this.batchPushBtn.style.backgroundColor = "rgb(215,254,195)";
this.batchPushBtn.style.color = "rgb(2,180,6)";
}
}
}
class ScriptConfig extends TampermonkeyApi {
static LOCAL_CONFIG = "config";
static PUSH_COUNT = "pushCount:" + ScriptConfig.getCurDay();
static ACTIVE_ENABLE = "activeEnable";
static PUSH_LIMIT = "push_limit" + ScriptConfig.getCurDay();
// 投递锁是否被占用,可重入;value表示当前正在投递的job
static PUSH_LOCK = "push_lock";
static PUSH_MESSAGE = "push_message";
static SEND_SELF_GREET_ENABLE = "sendSelfGreetEnable";
static SEND_HEADHUNTER_ENABLE = "sendHeadhunterEnable";
//HR在线检测
static BOSS_ONLINE_ENABLE = "bossOnlineEnable";
// 公司名包含输入框lab
static cnInKey = "companyNameInclude"
// 公司名排除输入框lab
static cnExKey = "companyNameExclude"
// job名称包含输入框lab
static jnInKey = "jobNameInclude"
// job内容排除输入框lab
static jcExKey = "jobContentExclude"
// 薪资范围输入框lab
static srInKey = "salaryRange"
// 公司规模范围输入框lab
static csrInKey = "companyScaleRange"
// 自定义招呼语输入框
static sgInKey = "sendSelfGreet"
static SEND_SELF_GREET_MEMORY = "sendSelfGreetMemory"
constructor() {
super();
this.configObj = {}
this.loaderConfig()
}
static getCurDay() {
// 创建 Date 对象获取当前时间
const currentDate = new Date();
// 获取年、月、日、小时、分钟和秒
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
const day = String(currentDate.getDate()).padStart(2, '0');
// 格式化时间字符串
return `${year}-${month}-${day}`;
}
static pushCountIncr() {
let number = TampermonkeyApi.GmGetValue(ScriptConfig.PUSH_COUNT, 0);
TampermonkeyApi.GmSetValue(ScriptConfig.PUSH_COUNT, ++number)
}
getVal(key, defVal) {
return TampermonkeyApi.GmGetValue(key, defVal)
}
setVal(key, val) {
TampermonkeyApi.GmSetValue(key, val)
}
getArrConfig(key, isArr) {
let arr = this.configObj[key];
if (isArr) {
return arr;
}
if (!arr) {
return "";
}
return arr.join(",");
}
getStrConfig(key) {
let str = this.configObj[key];
if (!str) {
return "";
}
return str;
}
getCompanyNameInclude(isArr) {
return this.getArrConfig(ScriptConfig.cnInKey, isArr);
}
getCompanyNameExclude(isArr) {
return this.getArrConfig(ScriptConfig.cnExKey, isArr);
}
getJobContentExclude(isArr) {
return this.getArrConfig(ScriptConfig.jcExKey, isArr);
}
getJobNameInclude(isArr) {
return this.getArrConfig(ScriptConfig.jnInKey, isArr);
}
getSalaryRange() {
return this.getStrConfig(ScriptConfig.srInKey);
}
getCompanyScaleRange() {
return this.getStrConfig(ScriptConfig.csrInKey);
}
getSelfGreet() {
return this.getStrConfig(ScriptConfig.sgInKey);
}
setCompanyNameInclude(val) {
return this.configObj[ScriptConfig.cnInKey] = val.split(",");
}
setCompanyNameExclude(val) {
this.configObj[ScriptConfig.cnExKey] = val.split(",");
}
setJobNameInclude(val) {
this.configObj[ScriptConfig.jnInKey] = val.split(",");
}
setJobContentExclude(val) {
this.configObj[ScriptConfig.jcExKey] = val.split(",");
}
setSalaryRange(val) {
this.configObj[ScriptConfig.srInKey] = val;
}
setCompanyScaleRange(val) {
this.configObj[ScriptConfig.csrInKey] = val;
}
setSelfGreet(val) {
this.configObj[ScriptConfig.sgInKey] = val;
}
static setSelfGreetMemory(val) {
TampermonkeyApi.GmSetValue(ScriptConfig.SEND_SELF_GREET_MEMORY, val)
}
getSelfGreetMemory() {
let value = TampermonkeyApi.GmGetValue(ScriptConfig.SEND_SELF_GREET_MEMORY);
if (value) {
return value;
}
return this.getSelfGreet();
}
/**
* 存储配置到本地存储中
*/
storeConfig() {
let configStr = JSON.stringify(this.configObj);
TampermonkeyApi.GmSetValue(ScriptConfig.LOCAL_CONFIG, configStr);
logger.info("存储配置到本地储存", configStr)
}
/**
* 从本地存储中加载配置
*/
loaderConfig() {
let localConfig = TampermonkeyApi.GmGetValue(ScriptConfig.LOCAL_CONFIG, "");
if (!localConfig) {
logger.warn("未加载到本地配置")
return;
}
this.configObj = JSON.parse(localConfig);
logger.info("成功加载本地配置", this.configObj)
}
}
class BossDOMApi {
static getJobList() {
return document.querySelectorAll(".job-card-wrapper");
}
static getJobTitle(jobTag) {
let innerText = jobTag.querySelector(".job-title").innerText;
return innerText.replace("\n", " ");
}
//是猎头发布的职位吗?
static isHeadhunter(jobTag) {
let jobTagIcon = jobTag.querySelector("img.job-tag-icon");
return !!jobTagIcon;
}
static getCompanyName(jobTag) {
return jobTag.querySelector(".company-name").innerText;
}
static getJobName(jobTag) {
return jobTag.querySelector(".job-name").innerText;
}
static getSalaryRange(jobTag) {
let text = jobTag.querySelector(".salary").innerText;
if (text.includes(".")) {
// 1-2K·13薪
return text.split("·")[0];
}
return text;
}
static getCompanyScaleRange(jobTag) {
return jobTag.querySelector(".company-tag-list").lastElementChild.innerHTML;
}
/**
* 获取当前job标签的招聘人名称以及他的职位
* @param jobTag
*/
static getBossNameAndPosition(jobTag) {
let nameAndPositionTextArr = jobTag.querySelector(".info-public").innerHTML.split("<em>");
nameAndPositionTextArr[0] = nameAndPositionTextArr[0].trim();
nameAndPositionTextArr[1] = nameAndPositionTextArr[1].replace("</em>", "").trim();
return nameAndPositionTextArr;
}
/**
* 是否为未沟通
* @param jobTag
*/
static isNotCommunication(jobTag) {
const jobStatusStr = jobTag.querySelector(".start-chat-btn").innerText;
return jobStatusStr.includes("立即沟通");
}
static getJobDetailUrlParams(jobTag) {
return jobTag.querySelector(".job-card-left").href.split("?")[1]
}
static getDetailSrc(jobTag) {
return jobTag.querySelector(".job-card-left").href;
}
static getUniqueKey(jobTag) {
const title = this.getJobTitle(jobTag)
const company = this.getCompanyName(jobTag)
return `${title}--${company}`
}
static nextPage() {
let nextPageBtn = document.querySelector(".ui-icon-arrow-right");
if (nextPageBtn.parentElement.className === "disabled") {
// 没有下一页
return;
}
nextPageBtn.click();
return true;
}
}
class JobListPageHandler {
constructor() {
this.operationPanel = new OperationPanel(this);
this.scriptConfig = this.operationPanel.scriptConfig
this.operationPanel.init()
this.publishState = false
this.nextPage = false
this.mock = false
this.cache = new Map()
this.selfDefCount = -1
}
/**
* 点击批量投递事件处理
*/
batchPushHandler() {
this.changeBatchPublishState(!this.publishState);
if (!this.publishState) {
return;
}
// 每次投递前清空投递锁,未被占用
this.scriptConfig.setVal(ScriptConfig.PUSH_LIMIT, false)
TampermonkeyApi.GmSetValue(ScriptConfig.PUSH_LOCK, "")
// 每次读取操作面板中用户实时输入的值
this.operationPanel.readInputConfig()
this.loopPublish()
}
loopPublish() {
// 过滤当前页满足条件的job并投递
this.filterCurPageAndPush()
// 等待处理完当前页的jobList在投递下一页
let nextPageTask = setInterval(() => {
if (!this.nextPage) {
logger.info("正在等待当前页投递完毕...")
return;
}
clearInterval(nextPageTask)
if (!this.publishState) {
logger.info("投递结束")
TampermonkeyApi.GmNotification("投递结束")
this.operationPanel.refreshShow("投递停止")
this.changeBatchPublishState(false);
return;
}
if (!BossDOMApi.nextPage()) {
logger.info("投递结束,没有下一页")
TampermonkeyApi.GmNotification("投递结束,没有下一页")
this.operationPanel.refreshShow("投递结束,没有下一页")
this.changeBatchPublishState(false);
return;
}
this.operationPanel.refreshShow("开始等待 10 秒钟,进行下一页")
// 点击下一页,需要等待页面元素变化,否则将重复拿到当前页的jobList
setTimeout(() => {
this.loopPublish()
}, 10000)
}, 3000);
}
changeBatchPublishState(publishState) {
this.publishState = publishState;
this.operationPanel.changeBatchPublishBtn(publishState)
}
filterCurPageAndPush() {
this.nextPage = false;
let notMatchCount = 0;
let publishResultCount = {
successCount: 0,
failCount: 0,
}
let jobList = BossDOMApi.getJobList();
logger.info("jobList", jobList)
let process = Array.from(jobList).reduce((promiseChain, jobTag) => {
let jobTitle = BossDOMApi.getJobTitle(jobTag);
return promiseChain
.then(() => this.matchJobPromise(jobTag))
.then(() => this.reqJobDetail(jobTag))
.then(jobCardJson => this.jobDetailFilter(jobTag, jobCardJson))
.then(() => this.sendPublishReq(jobTag))
.then(publishResult => this.handlerPublishResult(jobTag, publishResult, publishResultCount))
.catch(error => {
// 在catch中return是结束当前元素,不会结束整个promiseChain;
// 需要结束整个promiseChain,在catch throw exp,但还会继续执行下一个元素catch中的逻辑
switch (true) {
case error instanceof JobNotMatchExp:
this.operationPanel.refreshShow(jobTitle + " 不满足投递条件")
++notMatchCount;
break;
case error instanceof FetchJobDetailFailExp:
logger.error("job详情页数据获取失败:" + error);
break;
case error instanceof SendPublishExp:
logger.error("投递失败;" + jobTitle + " 原因:" + error.message);
this.operationPanel.refreshShow(jobTitle + " 投递失败")
publishResultCount.failCount++
break;
case error instanceof PublishLimitExp:
TampermonkeyApi.GmSetValue(ScriptConfig.PUSH_LIMIT, true);
this.operationPanel.refreshShow("停止投递 " + error.message)
logger.error("投递停止; 原因:" + error.message);
throw new PublishStopExp(error.message)
case error instanceof PublishStopExp:
this.changeBatchPublishState(false)
// 结束整个投递链路
throw error;
default:
logger.info(BossDOMApi.getDetailSrc(jobTag) + "-->未捕获投递异常:", error);
}
})
}, Promise.resolve()).catch(error => {
// 这里只是让报错不显示,不需要处理异常
});
// 当前页jobList中所有job处理完毕执行
process.finally(() => {
logger.info("当前页投递完毕---------------------------------------------------")
logger.info("不满足条件的job数量:" + notMatchCount)
logger.info("投递Job成功数量:" + publishResultCount.successCount)
logger.info("投递Job失败数量:" + publishResultCount.failCount)
logger.info("当前页投递完毕---------------------------------------------------")
this.nextPage = true;
})
}
cacheClear() {
this.cache.clear()
}
cacheSize() {
return this.cache.size
}
reqJobDetail(jobTag, retries = 3) {
return new Promise((resolve, reject) => {
if (retries === 0) {
return reject(new FetchJobDetailFailExp());
}
// todo 如果在投递当前页中,点击停止投递,那么当前页重新投递的话,会将已经投递的再重新投递一遍
// 原因是没有重新获取数据;沟通状态还是立即沟通,实际已经投递过一遍,已经为继续沟通
// 暂时不影响逻辑,重复投递,boss自己会过滤,不会重复发送消息;发送自定义招呼语也没问题;油猴会过滤【oldVal===newVal】的数据,也就不会重复发送自定义招呼语
const key = BossDOMApi.getUniqueKey(jobTag)
if (this.cache.has(key)) {
return resolve(this.cache.get(key))
}
let params = BossDOMApi.getJobDetailUrlParams(jobTag);
axios.get("https://www.zhipin.com/wapi/zpgeek/job/card.json?" + params, {timeout: 5000})
.then(resp => {
this.cache.set(key, resp.data.zpData.jobCard)
return resolve(resp.data.zpData.jobCard);
}).catch(error => {
logger.info("获取详情页异常正在重试:", error)
return this.reqJobDetail(jobTag, retries - 1)
})
})
}
jobDetailFilter(jobTag, jobCardJson) {
let jobTitle = BossDOMApi.getJobTitle(jobTag);
return new Promise((resolve, reject) => {
//检查是否在线
let bossOnlineCheck = TampermonkeyApi.GmGetValue(ScriptConfig.BOSS_ONLINE_ENABLE, true);
logger.info("当前职位【" + jobTitle + "】HR在线状态:" + jobCardJson.online)
if (bossOnlineCheck && !jobCardJson.online) {
logger.info("当前job被过滤:【" + jobTitle + "】 HR原因:不在线")
return reject(new JobNotMatchExp())
}
// 工作详情活跃度检查
let activeCheck = TampermonkeyApi.GmGetValue(ScriptConfig.ACTIVE_ENABLE, true);
let activeTimeDesc = jobCardJson.activeTimeDesc;
if (activeCheck && !Tools.bossIsActive(activeTimeDesc)) {
logger.info("当前boss活跃度:" + activeTimeDesc)
logger.info("当前job被过滤:【" + jobTitle + "】 原因:不满足活跃度检查")
return reject(new JobNotMatchExp())
}
// 猎头工作岗位检查
let headhunterCheck = TampermonkeyApi.GmGetValue(ScriptConfig.SEND_HEADHUNTER_ENABLE, true);
if (headhunterCheck && BossDOMApi.isHeadhunter(jobTag)) {
logger.info("当前工作为猎头发布:" + jobTitle);
logger.info("当前job被过滤:【" + jobTitle + "】 原因:为猎头发布的工作");
return reject(new JobNotMatchExp());
}
// 工作内容检查
let jobContentExclude = this.scriptConfig.getJobContentExclude(true);
const jobContentMismatch = Tools.semanticMatch(jobContentExclude, jobCardJson.postDescription)
if (jobContentMismatch) {
logger.info("当前job工作内容:" + jobCardJson.postDescription)
logger.info(`当前job被过滤:【${jobTitle}】 原因:不满足工作内容(${jobContentMismatch})`)
return reject(new JobNotMatchExp())
}
setTimeout(() => {
// 获取不同的延时,避免后面投递时一起导致频繁
return resolve();
}, Tools.getRandomNumber(100, 200))
})
}
handlerPublishResult(jobTag, result, publishResultCount) {
return new Promise((resolve, reject) => {
if (result.message === 'Success' && result.code === 0) {
// 增加投递数量,触发投递监听,更新页面投递计数
ScriptConfig.pushCountIncr()
publishResultCount.successCount++
logger.info("投递成功:" + BossDOMApi.getJobTitle(jobTag))
// 改变消息key,通知msg页面处理当前job发送自定义招呼语句
TampermonkeyApi.GmSetValue(ScriptConfig.PUSH_MESSAGE, JobMessagePageHandler.buildMsgKey(jobTag))
// 每页投递次数【默认不会走】
if (this.selfDefCount !== -1 && publishResultCount.successCount >= this.selfDefCount) {
return reject(new PublishStopExp("自定义投递限制:" + this.selfDefCount))
}
return resolve()
}
if (result.message.includes("今日沟通人数已达上限")) {
return reject(new PublishLimitExp(result.message))
}
return reject(new SendPublishExp(result.message))
})
}
sendPublishReq(jobTag, errorMsg, retries = 3) {
let jobTitle = BossDOMApi.getJobTitle(jobTag);
if (retries === 3) {
logger.info("正在投递:" + jobTitle)
}
return new Promise((resolve, reject) => {
if (retries === 0) {
return reject(new SendPublishExp(errorMsg));
}
if (!this.publishState) {
return reject(new PublishStopExp("停止投递"))
}
// 检查投递限制
let pushLimit = TampermonkeyApi.GmGetValue(ScriptConfig.PUSH_LIMIT, false);
if (pushLimit) {
this.changeBatchPublishState(false)
return reject(new PublishLimitExp("boss投递限制每天100次"))
}
if (this.mock) {
let result = {
message: 'Success',
code: 0
}
return resolve(result)
}
let src = BossDOMApi.getDetailSrc(jobTag);
let paramObj = Tools.parseURL(src);
let publishUrl = "https://www.zhipin.com/wapi/zpgeek/friend/add.json"
let url = Tools.queryString(publishUrl, paramObj);
let pushLockTask = setInterval(() => {
if (!this.publishState) {
clearInterval(pushLockTask)
return reject(new PublishStopExp())
}
let lock = TampermonkeyApi.GmGetValue(ScriptConfig.PUSH_LOCK, "");
if (lock && lock !== jobTitle) {
return logger.info("投递锁被其他job占用:" + lock)
}
// 停止锁检查并占用投递锁
clearInterval(pushLockTask)
TampermonkeyApi.GmSetValue(ScriptConfig.PUSH_LOCK, jobTitle)
logger.info("锁定投递锁:" + jobTitle)
this.operationPanel.refreshShow("正在投递-->" + jobTitle)
// 投递请求
axios.post(url, null, {headers: {"Zp_token": Tools.getCookieValue("bst")}})
.then(resp => {
if (resp.data.code === 1 && resp.data?.zpData?.bizData?.chatRemindDialog?.content) {
// 某些条件不满足,boss限制投递,无需重试,在结果处理器中处理
return resolve({
code: 1,
message: resp.data?.zpData?.bizData?.chatRemindDialog?.content
})
}
if (resp.data.code !== 0) {
throw new SendPublishExp(resp.data.message)
}
return resolve(resp.data);
}).catch(error => {
logger.info("投递异常正在重试:" + jobTitle, error)
return resolve(this.sendPublishReq(jobTag, error.message, retries - 1))
}).finally(() => {
// 释放投递锁
logger.info("释放投递锁:" + jobTitle)
TampermonkeyApi.GmSetValue(ScriptConfig.PUSH_LOCK, "")
})
}, 800);
})
}
matchJobPromise(jobTag) {
return new Promise(((resolve, reject) => {
if (!this.matchJob(jobTag)) {
return reject(new JobNotMatchExp())
}
return resolve(jobTag)
}))
}
matchJob(jobTag) {
let jobTitle = BossDOMApi.getJobTitle(jobTag);
let pageCompanyName = BossDOMApi.getCompanyName(jobTag);
// 不满足配置公司名
if (!Tools.fuzzyMatch(this.scriptConfig.getCompanyNameInclude(true),
pageCompanyName, true)) {
logger.info("当前公司名:" + pageCompanyName)
logger.info("当前job被过滤:【" + jobTitle + "】 原因:不满足配置公司名")
return false;
}
// 满足排除公司名
if (Tools.fuzzyMatch(this.scriptConfig.getCompanyNameExclude(true),
pageCompanyName, false)) {
logger.info("当前公司名:" + pageCompanyName)
logger.info("当前job被过滤:【" + jobTitle + "】 原因:满足排除公司名")
return false;
}
// 不满足配置工作名
let pageJobName = BossDOMApi.getJobName(jobTag);
if (!Tools.fuzzyMatch(this.scriptConfig.getJobNameInclude(true),
pageJobName, true)) {
logger.info("当前工作名:" + pageJobName)
logger.info("当前job被过滤:【" + jobTitle + "】 原因:不满足配置工作名")
return false;
}
// 不满足新增范围
let pageSalaryRange = BossDOMApi.getSalaryRange(jobTag);
let salaryRange = this.scriptConfig.getSalaryRange();
if (!Tools.rangeMatch(salaryRange, pageSalaryRange)) {
logger.info("当前薪资范围:" + pageSalaryRange)
logger.info("当前job被过滤:【" + jobTitle + "】 原因:不满足薪资范围")
return false;
}
let pageCompanyScaleRange = this.scriptConfig.getCompanyScaleRange();
if (!Tools.rangeMatch(pageCompanyScaleRange, BossDOMApi.getCompanyScaleRange(jobTag))) {
logger.info("当前公司规模范围:" + pageCompanyScaleRange)
logger.info("当前job被过滤:【" + jobTitle + "】 原因:不满足公司规模范围")
return false;
}
if (!BossDOMApi.isNotCommunication(jobTag)) {
logger.info("当前job被过滤:【" + jobTitle + "】 原因:已经沟通过")
return false;
}
return true;
}
}
class JobMessagePageHandler {
constructor() {
this.scriptConfig = new ScriptConfig();
this.init()
}
init() {
this.registerEvent();
}
registerEvent() {
TampermonkeyApi.GmAddValueChangeListener(ScriptConfig.PUSH_MESSAGE, this.pushAlterMsgHandler.bind(this))
logger.info("注册投递推送消费者成功")
}
/**
* 投递后发送自定义打招呼语句【发送自定义消息】
*/
pushAlterMsgHandler(key, oldValue, newValue, isOtherScriptChange) {
logger.info("投递后推送自定义招呼语消费者", {key, oldValue, newValue, isOtherScriptChange})
if (!isOtherScriptChange) {
return;
}
if (oldValue === newValue) {
return;
}
// 是否打开配置
if (!TampermonkeyApi.GmGetValue(ScriptConfig.SEND_SELF_GREET_ENABLE, false)) {
return;
}
let selfGreetMsg = this.getSelfGreet();
if (!selfGreetMsg) {
logger.info("自定义招呼语为空结束")
return;
}
let count = 0;
let process = Promise.resolve()
let sendMsgTask = setInterval(() => {
process.then(() => {
if (++count >= 5) {
logger.info("发送自定义打招呼语句超时结束")
clearInterval(sendMsgTask);
return;
}
return new Promise(async (resolve, reject) => {
let msgTag = await JobMessagePageHandler.selectMessage(newValue);
if (!msgTag) {
return reject();
}
// 点击当前待处理的消息框
msgTag.click();
logger.info("选中消息", msgTag)
return resolve();
})
}).then(() => {
return new Promise((resolve, reject) => {
if (!JobMessagePageHandler.ableInput()) {
return reject();
}
return resolve();
})
}).then(() => {
return new Promise((resolve => {
JobMessagePageHandler.inputMsg(selfGreetMsg)
return resolve();
}))
}).then(() => {
return new Promise(((resolve, reject) => {
if (!JobMessagePageHandler.sendAble()) {
return reject();
}
return resolve();
}))
}).then(() => {
return new Promise((resolve => {
JobMessagePageHandler.sendMsg()
logger.info("推送自定义招呼语成功:" + newValue)
clearInterval(sendMsgTask)
return resolve()
}))
}).catch(() => {
// 不报错
})
}, 500);
}
getSelfGreet() {
return this.scriptConfig.getSelfGreetMemory();
}
static buildMsgKey(jobTag) {
let companyName = BossDOMApi.getCompanyName(jobTag);
let bossNameAndPosition = BossDOMApi.getBossNameAndPosition(jobTag);
let bossName = bossNameAndPosition[0];
let bossPositionName = bossNameAndPosition[1];
return bossName + companyName + bossPositionName;
}
static ableInput() {
return document.querySelector(".chat-input") && document.querySelector(".chat-im.chat-editor");
}
static inputMsg(msg) {
// <br> \n 都可以换行
return document.querySelector(".chat-input").innerHTML = msg.replaceAll("\\n", "\n");
}
static sendAble() {
let btn = document.querySelector(".btn-v2.btn-sure-v2.btn-send");
// 删除按钮标签类名;按钮可点击
btn.classList.remove("disabled");
return btn;
}
static sendMsg() {
// 当前标签绑定的vue组件对象,
let chatFrameVueComponent = document.querySelector(".chat-im.chat-editor").__vue__;
// 更新开启提交;否则提交拦截
chatFrameVueComponent.enableSubmit = true;
// 赋值发送websocket的to.uid;手动触发导致uid无值,从friendId获取
chatFrameVueComponent.bossInfo$.uid = chatFrameVueComponent.bossInfo$.friendId;
let element = document.querySelector(".btn-v2.btn-sure-v2.btn-send");
element.click();
}
static async getMessageListTag() {
return new Promise((resolve) => {
document.querySelector("li.selected").click();
//等待bom渲染后获取
setTimeout(() => {
const lis = document.querySelector(".user-list").querySelector("div").querySelectorAll("li");
resolve(lis);
}, 100);
});
}
static async selectMessage(messageKey) {
let messageListTag = await JobMessagePageHandler.getMessageListTag();
for (let i = 0; i < messageListTag.length; i++) {
// '09月02日\n刘女士赛德勤人事行政专员\n您好,打扰了,我想和您聊聊这个职位。'
// 日期\n【boss名+公司名+职位名】\n 问候语
let msgTitle = messageListTag[i].innerText;
if (msgTitle.split("\n")[1] === messageKey) {
return messageListTag[i].querySelector("div");
}
}
logger.info("本次循环消息key未检索到消息框: " + messageKey)
}
}
class JobWordCloud {
// 不应该使用分词,而应该是分句,结合上下文,自然语言处理
static filterableWorldArr = ['', ' ', ',', '?', '+', '\n', '\r', "/", '有', '的', '等', '及', '了', '和', '公司', '熟悉', '服务', '并', '同', '如', '于', '或', '到',
'开发', '技术', '我们', '提供', '武汉', '经验', '为', '在', '团队', '员工', '工作', '能力', '-', '1', '2', '3', '4', '5', '6', '7', '8', '', '年', '与', '平台', '研发', '行业',
"实现", "负责", "代码", "精通", "图谱", "需求", "分析", "良好", "知识", "相关", "编码", "参与", "产品", "扎实", "具备", "较", "强", "沟通", "者", "优先", "具有", "精神", "编写", "功能", "完成", "详细", "岗位职责",
"包括", "解决", "应用", "性能", "调", "优", "本科", "以上学历", "基础", "责任心", "高", "构建", "合作", "能", "学习", "以上", "熟练", "问题", "优质", "运行", "工具", "方案", "根据", "业务", "类", "文档", "分配",
"其他", "亿", "级", "关系", "算法", "系统", "上线", "考虑", "工程师", "华为", "自动", "驾驶", "网络", "后", "端", "云", "高质量", "承担", "重点", "难点", "攻坚", "主导", "选型", "任务", "分解", "工作量", "评估",
"创造性", "过程", "中", "提升", "核心", "竞争力", "可靠性", "要求", "计算机专业", "基本功", "ee", "主流", "微", "框架", "其", "原理", "推进", "优秀", "团队精神", "热爱", "可用", "大型", "网站", "表达", "理解能力",
"同事", "分享", "愿意", "接受", "挑战", "拥有", "将", "压力", "转变", "动力", "乐观", "心态", "思路清晰", "严谨", "地", "习惯", "运用", "线", "上", "独立", "处理", "熟练掌握", "至少", "一种", "常见", "脚本", "环境",
"搭建", "开发工具", "人员", "讨论", "制定", "用", "相应", "保证", "质量", "说明", "领导", "包含", "节点", "存储", "检索", "api", "基于", "数据", "落地", "个性化", "场景", "支撑", "概要", "按照", "规范", "所", "模块",
"评审", "编译", "调试", "单元测试", "发布", "集成", "支持", "功能测试", "测试", "结果", "优化", "持续", "改进", "配合", "交付", "出现", "任职", "资格", "编程", "型", "使用", "认真负责", "高度", "责任感", "快速", "创新", "金融",
"设计", "项目", "对", "常用", "掌握", "专业", "进行", "了解", "岗位", "能够", "中间件", "以及", "开源", "理解", ")", "软件", "计算机", "架构", "一定", "缓存", "可", "解决问题", "计算机相关", "发展", "时间", "奖金", "培训", "部署",
"互联网", "享受", "善于", "需要", "游戏", " ", "维护", "统招", "语言", "消息", "机制", "逻辑思维", "一", "意识", "新", "攻关", "升级", "管理", "重构", "【", "职位", "】", "成员", "好", "接口", "语句", "后台", "通用", "不", "描述",
"福利", "险", "机会", "会", "人", "完善", "技术难题", "技能", "应用服务器", "配置", "协助", "或者", "组织", "现有", "迭代", "流程", "项目管理", "从", "深入", "复杂", "专业本科", "协议", "不断", "项目经理", "协作", "五", "金", "待遇",
"年终奖", "各类", "节日", "带薪", "你", "智慧", "前沿技术", "常用命令", "方案设计", "基本", "积极", "产品开发", "用户", "确保", "带领", "软件系统", "撰写", "软件工程", "职责", "抗压", "积极主动", "双休", "法定", "节假日", "假", "客户",
"日常", "协同", "是", "修改", "要", "软件开发", "丰富", "乐于", "识别", "风险", "合理", "服务器", "指导", "规划", "提高", "稳定性", "扩展性", "功底", "钻研", "c", "高可用性", "计算机软件", "高效", "前端", "内部", "一起", "程序", "程序开发",
"计划", "按时", "数理", "及其", "集合", "正式", "劳动合同", "薪资", "丰厚", "奖励", "补贴", "免费", "体检", "每年", "调薪", "活动", "职业", "素养", "晋升", "港", "氛围", "您", "存在", "关注", "停车", "参加", "系统分析", "发现", "稳定", "自主",
"实际", "开发技术", "(", "一些", "综合", "条件", "学历", "薪酬", "维", "保", "全日制", "专科", "体系结构", "协调", "出差", "自测", "周一", "至", "周五", "周末", "公积金", "准备", "内容", "部门", "满足", "兴趣", "方式", "操作", "超过", "结合",
"同时", "对接", "及时", "研究", "统一", "管控", "福利待遇", "政策", "办理", "凡是", "均", "丧假", "对于", "核心技术", "安全", "服务端", "游", "电商", "零售", "下", "扩展", "负载", "信息化", "命令", "供应链", "商业", "抽象", "模型", "领域", "瓶颈",
"充分", "编程语言", "自我", "但", "限于", "应用软件", "适合", "各种", "大", "前后", "复用", "执行", "流行", "app", "小", "二", "多种", "转正", "空间", "盒", "马", "长期", "成长", "间", "通讯", "全过程", "提交", "目标", "电气工程", "阅读", "严密",
"电力系统", "电力", "大小", "周", "心动", "入", "职", "即", "缴纳", "签署", "绩效奖金", "评优", "专利", "论文", "职称", "加班", "带薪休假", "专项", "健康", "每周", "运动", "休闲", "不定期", "小型", "团建", "旅游", "岗前", "牛", "带队", "答疑", "解惑",
"晋级", "晋升为", "管理层", "跨部门", "转岗", "地点", "武汉市", "东湖新技术开发区", "一路", "光谷", "园", "栋", "地铁", "号", "北站", "坐", "拥", "独栋", "办公楼", "环境优美", "办公", "和谐", "交通", "便利", "地铁站", "有轨电车", "公交站", "交通工具",
"齐全", "凯", "默", "电气", "期待", "加入", "积极参与", "依据", "工程", "跟进", "推动", "风险意识", "owner", "保持", "积极性", "自", "研", "内", "岗", "体验", "系统维护", "可能", "在线", "沟通交流", "简洁", "清晰", "录取", "优异者", "适当", "放宽", "上浮",
"必要", "后期", "软件技术", "形成", "技术成果", "调研", "分析师", "专", "含", "信息管理", "跨专业", "从业人员", "注", "安排", "交代", "书写", "做事", "细心", "好学", "可以", "公休", "年终奖金", "定期", "正规", "养老", "医疗", "生育", "工伤", "失业", "关怀",
"传统", "佳节", "之际", "礼包", "团结友爱", "伙伴", "丰富多彩", "两年", "过", "连接池", "划分", "检查", "部分", "甚至", "拆解", "硕士", "年龄", "周岁", "以下", "深厚", "语法", "浓厚", "优良", "治理", "a", "力", "高级", "能看懂", "有效", "共同", "想法", "提出",
"意见", "前", "最", "重要", "企业", "极好", "驻场", "并且", "表单", "交互方式", "样式", "前端开发", "遵循", "开发进度", "实战经验", "其中", "强烈", "三维", "多个", "net", "对应", "数学", "理工科", "背景", "软件设计", "模式", "方法", "动手", "按", "质", "软件产品",
"严格执行", "传", "帮", "带", "任务分配", "进度", "阶段", "介入", "本科学历", "五年", "尤佳", "比较", "细致", "态度", "享", "国家", "上班时间", "基本工资", "有关", "社会保险", "公司员工", "连续", "达到", "年限", "婚假", "产假", "护理", "发展潜力", "职员", "外出",
"做好", "效率", "沉淀", "网络服务", "数据分析", "查询", "规范化", "标准化", "思考", "手", "款", "成功", "卡", "牌", "slg", "更佳", "可用性", "新人", "预研", "突破", "lambda", "理念", "它", "rest", "一个", "趋势", "思路", "影响", "医疗系统", "具体", "架构师",
"保证系统", "大专", "三年", "体系", "写", "医院", "遇到", "验证", "运", "保障", "基本操作", "独立思考", "技术手段", "熟知", "懂", "应用环境", "表达能力", "个人", "新能源", "汽车", "权限", "排班", "绩效", "考勤", "知识库", "全局", "搜索", "门店", "渠道", "选址",
"所有", "长远", "眼光", "局限于", "逻辑", "侧", "更好", "解决方案", "针对", "建模", "定位系统", "高质", "把", "控", "攻克", "t", "必须", "组件", "基本原理", "上进心", "驱动", "适应能力", "自信", "追求", "卓越", "感兴趣", "站", "角度", "思考问题", "tob", "商业化",
"售后", "毕业", "通信", "数种", "优选", "it", "课堂", "所学", "在校", "期间", "校内外", "大赛", "参", "社区", "招聘", "类库", "优等", "b", "s", "方面", "海量", "数据系统", "测试工具", "曾", "主要", "爱好", "欢迎", "洁癖", "人士", "银行", "财务", "城市", "类产品", "实施",
"保障系统", "健壮性", "可读性", "rpd", "原型", "联调", "准确无误", "系统优化", "技术标准", "总体设计", "文件", "整理", "功能设计", "技术类", "写作能力", "尤其", "套件", "公安", "细分", "增加", "bug", "电子", "swing", "桌面", "认证", "台", "检测", "安全隐患", "及时发现",
"修补", "上级领导", "交办", "其它", "面向对象分析", "思想", "乐于助人", "全", "栈", "共享", "经济", "信", "主管", "下达", "执行力", "技巧", "试用期", "个", "月", "适应", "快", "随时", "表现", "\u003d", "到手", "工资", "享有", "提成", "超额", "业绩", "封顶", "足够", "发展前景",
"发挥", "处", "高速", "发展期", "敢", "就", "元旦", "春节", "清明", "端午", "五一", "中秋", "国庆", "婚", "病假", "商品", "导购", "增长", "互动", "营销", "面对", "不断创新", "规模化", "上下游", "各", "域", "最终", "完整", "梳理", "链路", "关键", "点", "给出", "策略", "从业", "且",
"可维护性", "不仅", "短期", "更", "方向", "不错", "交互", "主动", "应急", "组长", "tl", "加", "分", "一群", "怎样", "很", "热情", "喜欢", "敬畏", "心", "坚持", "主义", "持之以恒", "自己", "收获", "重视", "每", "一位", "主观", "能动性", "同学", "给予", "为此", "求贤若渴", "干货", "满满",
"战斗", "大胆", "互相", "信任", "互相帮助", "生活", "里", "嗨", "皮", "徒步", "桌", "轰", "趴", "聚餐", "应有尽有"
]
static numberRegex = /^[0-9]+$/
static splitChar = " "
static participleUrl = "https://www.tl.beer/api/v1/fenci"
static participle(text) {
return new Promise((resolve, reject) => {
TampermonkeyApi.GMXmlHttpRequest({
method: 'POST',
timeout: 5000,
url: JobWordCloud.participleUrl,
data: "cont=" + encodeURIComponent(text) + "&cixin=false&model=false",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
},
onload: function (response) {
if (response.status !== 200) {
logger.error("分词状态码不是200", response.responseText)
return reject(response.responseText)
}
return resolve(JSON.parse(response.responseText).data.split(JobWordCloud.splitChar))
},
onerror: function (error) {
logger.error("分词出错", error)
reject(error)
}
});
})
}
static buildWord(wordArr) {
// {"word1":1, "word2":4}
let weightMap = {};
for (let i = 0; i < wordArr.length; i++) {
let str = wordArr[i];
if (JobWordCloud.filterableWorldArr.includes(str)) {
continue;
}
if (JobWordCloud.numberRegex.test(str)) {
continue;
}
if (str in weightMap) {
weightMap[str] = weightMap[str] + 1;
continue
}
weightMap[str] = 1;
}
// 将对象转换为二维数组并排序: [['word1', 2], ['word2', 4]]
let weightWordArr = JobWordCloud.sortByValue(Object.entries(weightMap));
return JobWordCloud.cutData(weightWordArr)
}
static cutData(weightWordArr) {
return weightWordArr
}
static generateWorldCloudImage(canvasTagId, weightWordArr) {
// 词云图的配置选项
let options = {
tooltip: {
show: true,
formatter: function (item) {
return item[0] + ': ' + item[1]
}
},
list: weightWordArr,
// 网格尺寸
//gridSize: 10,
// 权重系数
weightFactor: 2,
// 字体
fontFamily: 'Finger Paint, cursive, sans-serif',
// 字体颜色,也可以指定特定颜色值
//color: '#26ad7e',
color: 'random-dark',
// 旋转比例
// rotateRatio: 0.2,
// 背景颜色
backgroundColor: 'white',
// 形状
//shape: 'square',
shape: 'circle',
ellipticity: 1,
// 随机排列词语
shuffle: true,
// 不绘制超出容器边界的词语
drawOutOfBound: false
};
// WordCloud(document.getElementById(canvasTagId), options);
const wc = new Js2WordCloud(document.getElementById(canvasTagId));
wc.setOption(options)
}
static getKeyWorldArr(twoArr) {
let worldArr = []
for (let i = 0; i < twoArr.length; i++) {
let world = twoArr[i][0];
worldArr.push(world)
}
return worldArr;
}
static sortByValue(arr, order = 'desc') {
if (order === 'asc') {
return arr.sort((a, b) => a[1] - b[1]);
} else if (order === 'desc') {
return arr.sort((a, b) => b[1] - a[1]);
} else {
throw new Error('Invalid sort key. Use "asc" or "desc".');
}
}
}
GM_registerMenuCommand("切换Ck", async () => {
let value = GM_getValue("ck_list") || [];
GM_cookie("list", {}, async (list, error) => {
if (error === undefined) {
console.log(list, value);
// 储存覆盖老的值
GM_setValue("ck_list", list);
// 先清空 再设置
for (let i = 0; i < list.length; i++) {
list[i].url = window.location.origin;
await GM_cookie("delete", list[i]);
}
if (value.length) {
// 循环set
for (let i = 0; i < value.length; i++) {
value[i].url = window.location.origin;
await GM_cookie("set", value[i]);
}
}
if (GM_getValue("ck_cur", "") === "") {
GM_setValue("ck_cur", "_");
} else {
GM_setValue("ck_cur", "");
}
window.location.reload();
// window.alert("手动刷新~");
} else {
window.alert("你当前版本可能不支持Ck操作,错误代码:" + error);
}
});
});
GM_registerMenuCommand("清除当前Ck", () => {
if (GM_getValue("ck_cur", "") === "_") {
GM_setValue("ck_cur", "");
}
GM_cookie("list", {}, async (list, error) => {
if (error === undefined) {
// 清空
for (let i = 0; i < list.length; i++) {
list[i].url = window.location.origin;
// console.log(list[i]);
await GM_cookie("delete", list[i]);
}
window.location.reload();
} else {
window.alert("你当前版本可能不支持Ck操作,错误代码:" + error);
}
});
});
GM_registerMenuCommand("清空所有存储!", async () => {
if (confirm("将清空脚本全部的设置!!")) {
const asyncKeys = await GM_listValues();
for (let index in asyncKeys) {
if (!asyncKeys.hasOwnProperty(index)) {
continue;
}
console.log(asyncKeys[index]);
await GM_deleteValue(asyncKeys[index]);
}
window.alert("OK!");
}
});
(function () {
const list_url = "web/geek/job";
const recommend_url = "web/geek/recommend";
const message_url = "web/geek/chat";
if (document.URL.includes(list_url) || document.URL.includes(recommend_url)) {
window.addEventListener("load", () => {
new JobListPageHandler()
});
} else if (document.URL.includes(message_url) && parent?.document?.getElementById('msgIframe')) {
window.addEventListener("load", () => {
// jobListPage内部的 msgIframe才注册
new JobMessagePageHandler();
});
}
})();