diff --git a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json index 437e47f..2b202d4 100644 --- a/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json +++ b/packages/geek-auto-start-chat-with-boss/default-config-file/boss.json @@ -35,5 +35,6 @@ ], "isSageTimeEnabled": true, "sageTimeOpTimes": 100, - "sageTimePauseMinute": 15 + "sageTimePauseMinute": 15, + "blockCompanyNameRegExpStr": "" } \ No newline at end of file diff --git a/packages/geek-auto-start-chat-with-boss/index.mjs b/packages/geek-auto-start-chat-with-boss/index.mjs index 237a03d..40bf6f0 100644 --- a/packages/geek-auto-start-chat-with-boss/index.mjs +++ b/packages/geek-auto-start-chat-with-boss/index.mjs @@ -247,6 +247,19 @@ const recommendJobPageUrl = `https://www.zhipin.com/web/geek/jobs` const expectCompanySet = new Set(targetCompanyList) const enableCompanyAllowList = Boolean(expectCompanySet.size) +const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? '' +const blockCompanyNameRegExp = (() => { + if (!blockCompanyNameRegExpStr?.trim()) { + return null + } + try { + return new RegExp(blockCompanyNameRegExpStr, 'im') + } + catch { + return null + } +})() +const blockCompanyNameRegMatchStrategy = readConfigFile('boss.json').blockCompanyNameRegMatchStrategy ?? MarkAsNotSuitOp.NO_OP /** * @type { import('puppeteer').Browser } @@ -298,6 +311,18 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) { let isOptionChosen = false if (chooseReasonDialogProxy) { switch (reasonCode) { + case MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT: { + const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title*="公司"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="薪资"]`)) + if (opProxy) { + await opProxy.click() + isOptionChosen = true + } + break + } case MarkAsNotSuitReason.BOSS_INACTIVE: { const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title="BOSS活跃度低"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="职位停招/招满"]`)) @@ -312,7 +337,7 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) { case MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT: case MarkAsNotSuitReason.JOB_CITY_NOT_SUIT: { const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`)) - ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离远"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="公司"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`)) @@ -325,7 +350,7 @@ async function markJobAsNotSuitInRecommendPage (reasonCode) { case MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT: { const opProxy = (await chooseReasonDialogProxy.$(`.zp-type-item[title*="薪资"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title$="城市"]`)) - ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离远"]`)) + ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="距离"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title*="公司"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="面试过/入职过"]`)) ?? (await chooseReasonDialogProxy.$(`.zp-type-item[title="重复推荐"]`)) @@ -984,6 +1009,14 @@ async function toRecommendPage (hooks) { ].includes(expectSalaryNotMatchStrategy) && strategyScopeOptionWhenMarkSalaryNotMatch === StrategyScopeOptionWhenMarkJobNotMatch.ALL_JOB ) ? !checkIfSalarySuit(it.salaryDesc) : false + ) || ( + // enter job detail to mark as not suit for company name filter + !!blockCompanyNameRegExp && + blockCompanyNameRegExp.test(it.brandName?.toLowerCase?.() ?? '') && + [ + MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS, + MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL + ].includes(blockCompanyNameRegMatchStrategy) ) ) }) @@ -1077,6 +1110,47 @@ async function toRecommendPage (hooks) { //#region collect not suit reasons const notSuitReasonIdToStrategyMap = {} const notSuitConditionHandleMap = { + async companyName() { + blockJobNotSuit.add(targetJobData.jobInfo.encryptId) + if (blockCompanyNameRegMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL && !await page.$('.job-detail-box .job-detail-operate .not-suitable')) { + try { + await hooks.jobMarkedAsNotSuit.promise( + targetJobData, + { + markFrom: ChatStartupFrom.AutoFromRecommendList, + markReason: MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT, + extInfo: null, + markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL, + jobSource: JobSource[computedSourceList[currentSourceIndex]?.type] + } + ) + } catch { + } + } + else if (blockCompanyNameRegMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) { + try { + await waitForSageTimeOrJustContinue({ + tag: 'beforeJobNotSuitMarked', + hooks + }) + const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT) + await hooks.jobMarkedAsNotSuit.promise( + targetJobData, + { + markFrom: ChatStartupFrom.AutoFromRecommendList, + markReason: MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT, + extInfo: { + chosenReasonInUi + }, + markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS, + jobSource: JobSource[computedSourceList[currentSourceIndex]?.type] + } + ) + } catch(err) { + console.log(`mark boss inactive failed`, err) + } + } + }, async active() { blockBossNotActive.add(targetJobData.jobInfo.encryptUserId) if (jobNotActiveStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL || !await page.$('.job-detail-box .job-detail-operate .not-suitable')) { @@ -1289,6 +1363,11 @@ async function toRecommendPage (hooks) { } } + if ( + !!blockCompanyNameRegExp && blockCompanyNameRegExp.test(selectedJobData.brandName ?? '') + ) { + notSuitReasonIdToStrategyMap.companyName = blockCompanyNameRegMatchStrategy + } //#region // null // 刚刚活跃 // 今日活跃 // 昨日活跃 // 3日内活跃 // 本周活跃 // 2周内活跃 diff --git a/packages/sqlite-plugin/src/enums.ts b/packages/sqlite-plugin/src/enums.ts index c2ad8e1..0a32b27 100644 --- a/packages/sqlite-plugin/src/enums.ts +++ b/packages/sqlite-plugin/src/enums.ts @@ -6,6 +6,7 @@ export enum MarkAsNotSuitReason { JOB_CITY_NOT_SUIT = 4, JOB_WORK_EXP_NOT_SUIT = 5, JOB_SALARY_NOT_SUIT = 6, + COMPANY_NAME_NOT_SUIT = 7, } export enum MarkAsNotSuitOp { diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts index 012747a..3eb9a96 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts @@ -181,6 +181,12 @@ export default function initIpc() { if (hasOwn(payload, 'sageTimePauseMinute')) { bossConfig.sageTimePauseMinute = payload.sageTimePauseMinute } + if (hasOwn(payload, 'blockCompanyNameRegExpStr')) { + bossConfig.blockCompanyNameRegExpStr = payload.blockCompanyNameRegExpStr + } + if (hasOwn(payload, 'blockCompanyNameRegMatchStrategy')) { + bossConfig.blockCompanyNameRegMatchStrategy = payload.blockCompanyNameRegMatchStrategy + } promiseArr.push(writeConfigFile('boss.json', bossConfig)) diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts index d444f1e..c28430d 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts @@ -60,7 +60,23 @@ const rechatLlmFallback = const expectJobTypeRegExpStr = readConfigFile('boss.json').expectJobTypeRegExpStr const onlyRemindBossWithExpectJobType = - readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ?? !!expectJobTypeRegExpStr + readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ?? + !!expectJobTypeRegExpStr + +const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? '' +const blockCompanyNameRegExp = (() => { + if (!blockCompanyNameRegExpStr?.trim()) { + return null + } + try { + return new RegExp(blockCompanyNameRegExpStr, 'im') + } catch { + return null + } +})() +const onlyRemindBossWithoutBlockCompanyName = + readConfigFile('boss.json').autoReminder?.onlyRemindBossWithoutBlockCompanyName ?? + !!blockCompanyNameRegExp const dbInitPromise = initDb(getPublicDbFilePath()) @@ -156,7 +172,8 @@ async function saveCurrentChatRecord(page) { async function checkJobIsClosed() { const encryptJobId = await pageMapByName.boss!.evaluate(() => { - return document.querySelector('.chat-conversation .chat-im.chat-editor')?.__vue__?.conversation$.encryptJobId + return document.querySelector('.chat-conversation .chat-im.chat-editor')?.__vue__?.conversation$ + .encryptJobId }) if (!encryptJobId) { return false @@ -431,6 +448,9 @@ const mainLoop = async () => { const toCheckItemAtIndex = friendListData.findIndex((it, index) => { return ( index >= cursorToContinueFind && + (onlyRemindBossWithoutBlockCompanyName && blockCompanyNameRegExp + ? !blockCompanyNameRegExp.test(it.brandName) + : true) && (rechatLimitDay && it.updateTime ? +new Date() - it.updateTime < rechatLimitDay * 24 * 60 * 60 * 1000 : true) && @@ -605,11 +625,14 @@ export async function runEntry() { }) initPublicIpc() await connectToDaemon() - await sendToDaemon({ - type: 'ping' - }, { - needCallback: true - }) + await sendToDaemon( + { + type: 'ping' + }, + { + needCallback: true + } + ) sendToDaemon({ type: 'worker-to-gui-message', data: { @@ -690,7 +713,11 @@ export async function runEntry() { process.exit(AUTO_CHAT_ERROR_EXIT_CODE.ACCESS_IS_DENIED) break } - if (err.message.includes(`PUPPETEER_IS_NOT_EXECUTABLE`) || err.message.includes(`Could not find Chrome`) || err.message.includes(`no executable was found`)) { + if ( + err.message.includes(`PUPPETEER_IS_NOT_EXECUTABLE`) || + err.message.includes(`Could not find Chrome`) || + err.message.includes(`no executable was found`) + ) { process.exit(AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE) break } @@ -703,7 +730,7 @@ export async function runEntry() { buttons: ['退出'] }) process.exit(AUTO_CHAT_ERROR_EXIT_CODE.LLM_UNAVAILABLE) - break; + break } } } finally { diff --git a/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/index.vue b/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/index.vue index 9c63d91..c54bdfd 100644 --- a/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/index.vue +++ b/packages/ui/src/renderer/src/page/MainLayout/GeekAutoStartChatWithBoss/index.vue @@ -245,14 +245,16 @@
- 公司白名单(以逗号分隔,不区分大小写;输入框留空表示不筛选)公司信息UI位置图示 - + >
逗号分隔,不区分大小写;输入框留空表示不筛选
+
+
+
+ 不期望投递公司正则  + + 公司信息UI位置图示
正则表达式,不区分大小写;输入框留空表示不筛选;优先级高于上方“期望投递公司” +
+ +
+
+ + + +
+ +
当前职位对应公司名称与不期望投递公司正则匹配时:
+ + {{ op.name }} + +
+
+
工作地
@@ -1213,7 +1318,9 @@ const formContent = ref({ ]), isSageTimeEnabled: true, sageTimeOpTimes: 100, - sageTimePauseMinute: 15 + sageTimePauseMinute: 15, + blockCompanyNameRegExpStr: '', + blockCompanyNameRegMatchStrategy: MarkAsNotSuitOp.NO_OP }) const anyCombineBossRecommendFilterHasCondition = computed(() => { @@ -1371,10 +1478,15 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => { parseFloat(res.config['boss.json'].sageTimePauseMinute) < 0 ? 15 : parseFloat(res.config['boss.json'].sageTimePauseMinute) + formContent.value.blockCompanyNameRegExpStr = + res.config['boss.json'].blockCompanyNameRegExpStr?.trim() ?? '' + formContent.value.blockCompanyNameRegMatchStrategy = + res.config['boss.json'].blockCompanyNameRegMatchStrategy ?? MarkAsNotSuitOp.NO_OP }) const jobSourceFormItemSectionEl = ref() const jobDetailRegExpSectionEl = ref() +const blockCompanyNameRegExpSectionEl = ref() const formRules = { expectJobNameRegExpStr: { trigger: 'blur', @@ -1489,6 +1601,25 @@ const formRules = { } cb() } + }, + blockCompanyNameRegExpStr: { + trigger: 'blur', + validator(_, value, cb) { + if (!value) { + cb() + gtagRenderer('empty_reg_exp_for_bcn') + return + } + try { + new RegExp(value, 'ig') + gtagRenderer('valid_reg_exp_for_bcn', { v: value }) + cb() + } catch (err) { + cb(new Error(`正则无效:${err?.message}`)) + blockCompanyNameRegExpSectionEl.value?.scrollIntoViewIfNeeded() + gtagRenderer('invalid_reg_exp_for_bcn', { v: value }) + } + } } } @@ -1671,7 +1802,7 @@ const strategyScopeOptionWhenMarkJobNotMatch = [ value: StrategyScopeOptionWhenMarkJobNotMatch.ALL_JOB }, { - name: '仅和“公司白名单”匹配的职位', + name: '仅和“期望投递公司”匹配的职位', value: StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB } ] @@ -1989,7 +2120,8 @@ const handleStopButtonClick = async () => { font-size: 12px; line-height: 1.2em; } - .job-detail-filter-wrap .el-form-item__error { + .job-detail-filter-wrap .el-form-item__error, + .block-company-filter-wrap .el-form-item__error { position: static; word-break: break-word; } diff --git a/packages/ui/src/renderer/src/page/MainLayout/MarkAsNotSuitRecord.vue b/packages/ui/src/renderer/src/page/MainLayout/MarkAsNotSuitRecord.vue index 1ad0cec..73be76b 100644 --- a/packages/ui/src/renderer/src/page/MainLayout/MarkAsNotSuitRecord.vue +++ b/packages/ui/src/renderer/src/page/MainLayout/MarkAsNotSuitRecord.vue @@ -50,6 +50,10 @@ {{ markReasonTopicMap[row.markReason] }}
{{ formatMarkReason(row) }}
+ @@ -219,7 +223,8 @@ const markReasonTopicMap = { [MarkAsNotSuitReason.JOB_NOT_SUIT]: '职位不合适', [MarkAsNotSuitReason.JOB_CITY_NOT_SUIT]: '工作地不合适', [MarkAsNotSuitReason.JOB_WORK_EXP_NOT_SUIT]: '工作经验不合适', - [MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT]: '薪资不合适' + [MarkAsNotSuitReason.JOB_SALARY_NOT_SUIT]: '薪资不合适', + [MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT]: '公司名称不匹配' } function formatMarkReason(row: VMarkAsNotSuitLog) { @@ -279,6 +284,18 @@ function formatMarkReason(row: VMarkAsNotSuitLog) { .filter(Boolean) .join('\n') } + case MarkAsNotSuitReason.COMPANY_NAME_NOT_SUIT: { + const extInfo = (() => { + try { + return JSON.parse(row.extInfo) + } catch { + return null + } + })() + return [extInfo?.chosenReasonInUi?.text && `BOSS选项内容:${extInfo.chosenReasonInUi.text}`] + .filter(Boolean) + .join('\n') + } default: { return '' } diff --git a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue index f370852..3f55c0e 100644 --- a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue +++ b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue @@ -31,6 +31,21 @@
+ +
+ + 发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒 + + +
+
@@ -258,7 +273,8 @@ const formContent = ref({ rechatContentSource: 1, recentMessageQuantityForLlm: 8, rechatLlmFallback: RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION, - onlyRemindBossWithExpectJobType: true + onlyRemindBossWithExpectJobType: true, + onlyRemindBossWithoutBlockCompanyName: true } }) @@ -296,15 +312,17 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => { }) const expectJobTypeRegExpStr = ref('') -async function fetchExpectJobTypeRegExpStr() { +const blockCompanyNameRegExpStr = ref('') +async function fetchAutoStartChatConfig() { await electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => { expectJobTypeRegExpStr.value = res.config['boss.json']?.expectJobTypeRegExpStr + blockCompanyNameRegExpStr.value = res.config['boss.json']?.blockCompanyNameRegExpStr }) } -fetchExpectJobTypeRegExpStr() -mittBus.on('auto-start-chat-with-boss-config-saved', fetchExpectJobTypeRegExpStr) +fetchAutoStartChatConfig() +mittBus.on('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig) onUnmounted(() => { - mittBus.off('auto-start-chat-with-boss-config-saved', fetchExpectJobTypeRegExpStr) + mittBus.off('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig) }) const resumeContent = ref(null) diff --git a/packages/ui/src/renderer/src/page/MainLayout/resources/intro-of-job-entry.png b/packages/ui/src/renderer/src/page/MainLayout/resources/intro-of-job-entry.png index 33be529..6b5b3a5 100644 Binary files a/packages/ui/src/renderer/src/page/MainLayout/resources/intro-of-job-entry.png and b/packages/ui/src/renderer/src/page/MainLayout/resources/intro-of-job-entry.png differ