mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-08-28 19:46:50 +08:00
feat(boss): 登录后自动关闭治理公告弹窗;沟通页新招呼/未读 tab 初始化
- 新增 GOVERNANCE_NOTICE_DIALOG_* 选择器与 dismissGovernanceNoticeDialog(), 在 launchBrowserAndNavigateToChat 与 startBossAutoBrowse 登录后调用,避免阻塞自动化。 - 沟通页:先切「新招呼」再强制点「未读」刷新列表;switchToTab 支持 force 选项。 - 文档:recruiter_architecture §14 弹窗清单与 §6 常量更新;CLAUDE.md 补充登录后弹窗说明; 新增 plan/README.md、plan/chat_page_tab_navigation.md。 Made-with: Cursor
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
CHAT_PAGE_ITEM_UNREAD_SELECTOR,
|
||||
CHAT_PAGE_ALL_FILTER_SELECTOR,
|
||||
CHAT_PAGE_UNREAD_FILTER_SELECTOR,
|
||||
CHAT_PAGE_TAB_NEW_GREET_SELECTOR,
|
||||
CHAT_PAGE_NAME_SELECTOR,
|
||||
CHAT_PAGE_JOB_SELECTOR,
|
||||
CHAT_PAGE_PREVIEW_RESUME_BTN_SELECTOR,
|
||||
@@ -391,14 +392,16 @@ export default async function startBossChatPageProcess (hooksFromCaller, options
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// 内部辅助:切换到指定 tab(封闭 page、cursor)
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
const switchToTab = async (selector, tabName) => {
|
||||
const isActive = await page.evaluate(
|
||||
(sel) => document.querySelector(sel)?.classList.contains('active') ?? false,
|
||||
selector
|
||||
)
|
||||
if (isActive) {
|
||||
logDebug(`${LOG} 已在「${tabName}」tab`)
|
||||
return
|
||||
const switchToTab = async (selector, tabName, opts = {}) => {
|
||||
if (!opts.force) {
|
||||
const isActive = await page.evaluate(
|
||||
(sel) => document.querySelector(sel)?.classList.contains('active') ?? false,
|
||||
selector
|
||||
)
|
||||
if (isActive) {
|
||||
logDebug(`${LOG} 已在「${tabName}」tab`)
|
||||
return
|
||||
}
|
||||
}
|
||||
logInfo(`${LOG} 切换到「${tabName}」tab...`)
|
||||
const tabEl = await page.$(selector)
|
||||
@@ -762,6 +765,16 @@ export default async function startBossChatPageProcess (hooksFromCaller, options
|
||||
}
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── 职位 tab 初始化:切换到「新招呼」分类,再强制点击「未读」触发列表刷新 ────────
|
||||
// 必须先进入「新招呼」分类,才能只扫描当前职位下候选人主动发来的招呼,避免遍历其他类型会话。
|
||||
// 「未读」tab 只有被实际点击时 BOSS 才会刷新列表;若上次运行后页面已停在「未读」tab,
|
||||
// 不点击则不会刷新,已处理过的会话仍会出现,导致重复操作。因此此处强制点击(force: true)。
|
||||
await switchToTab(CHAT_PAGE_TAB_NEW_GREET_SELECTOR, '新招呼', { force: true })
|
||||
await sleepWithRandomDelay(300, 500)
|
||||
await switchToTab(CHAT_PAGE_UNREAD_FILTER_SELECTOR, '未读', { force: true })
|
||||
await sleepWithRandomDelay(400, 600)
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── 验证恢复:若上次被验证中断,优先重试被中断的候选人 ────────────────────────
|
||||
if (retryCandidate) {
|
||||
logInfo(`${LOG} ── 验证恢复:重试被中断候选人 ${retryCandidate.geekName}(${retryCandidate.encryptGeekId})──`)
|
||||
@@ -781,9 +794,9 @@ export default async function startBossChatPageProcess (hooksFromCaller, options
|
||||
await sleepWithRandomDelay(300)
|
||||
}
|
||||
|
||||
// ── 正常扫描:切换到「未读」tab,处理未读会话 ───────────────────────────────
|
||||
await switchToTab(CHAT_PAGE_UNREAD_FILTER_SELECTOR, '未读')
|
||||
await sleepWithRandomDelay(300)
|
||||
// ── 正常扫描:处理「新招呼」分类下的未读会话 ─────────────────────────────────
|
||||
// 「新招呼」分类与「未读」tab 已在上方初始化阶段完成切换(force: true),此处直接解析列表。
|
||||
// 若经过 retryCandidate 流程,retry 结束时已切回「未读」tab,状态同样正确。
|
||||
|
||||
const conversations = await parseConversationList(page)
|
||||
logDebug(`${LOG} DOM 解析到 ${conversations.length} 条会话`)
|
||||
|
||||
@@ -217,3 +217,26 @@ export const CHAT_PAGE_INTENT_DIALOG_CLOSE_SELECTOR = '.op-btn.rightbar-item div
|
||||
* HTML: div.chat-label-item[title="全部"],选中态有 class selected。
|
||||
*/
|
||||
export const CHAT_PAGE_TAB_ALL_SELECTOR = '.chat-label-item[title="全部"]'
|
||||
|
||||
// =============================================================================
|
||||
// 三、治理公告弹窗(登录后出现,须点击「我已知晓」才能继续操作)
|
||||
// =============================================================================
|
||||
/**
|
||||
* 治理公告弹窗(dialog-uninstall-extension)容器选择器。
|
||||
* BOSS 每次登录后会弹出此公告,告知平台禁止使用第三方自动化工具。
|
||||
* HTML: div.boss-popup__wrapper.boss-dialog.boss-dialog__wrapper.dialog-uninstall-extension
|
||||
*/
|
||||
export const GOVERNANCE_NOTICE_DIALOG_SELECTOR = '.dialog-uninstall-extension'
|
||||
|
||||
/**
|
||||
* 治理公告弹窗内的「我已知晓」确认按钮(div.confirm-btn,背景图模拟按钮样式)。
|
||||
* HTML: div.dialog-uninstall-extension div.uninstall-extension div.content div.confirm-btn
|
||||
*/
|
||||
export const GOVERNANCE_NOTICE_DIALOG_CONFIRM_BTN_SELECTOR = '.dialog-uninstall-extension .confirm-btn'
|
||||
|
||||
/**
|
||||
* 沟通页:左侧会话列表分类 tab——「新招呼」(候选人主动发来招呼的会话)。
|
||||
* 每次开始处理前须先点击此 tab,确保只扫描新招呼消息,避免遍历其他类型会话。
|
||||
* HTML: div.chat-label-item[title="新招呼"],选中态有 class selected。
|
||||
*/
|
||||
export const CHAT_PAGE_TAB_NEW_GREET_SELECTOR = '.chat-label-item[title="新招呼"]'
|
||||
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
BOSS_RECOMMEND_PAGE_URL,
|
||||
BOSS_CHAT_PAGE_URL,
|
||||
RECOMMEND_JOB_DROPDOWN_LABEL_SELECTOR,
|
||||
RECOMMEND_JOB_ITEM_SELECTOR
|
||||
RECOMMEND_JOB_ITEM_SELECTOR,
|
||||
GOVERNANCE_NOTICE_DIALOG_SELECTOR,
|
||||
GOVERNANCE_NOTICE_DIALOG_CONFIRM_BTN_SELECTOR
|
||||
} from './constant.mjs'
|
||||
import { setupNetworkInterceptor, setupCanvasTextHook } from './resume-extractor.mjs'
|
||||
import { parseCandidateList, filterCandidates, scrollAndLoadMore } from './candidate-processor.mjs'
|
||||
@@ -60,6 +62,34 @@ export async function initPuppeteer () {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭登录后弹出的「治理公告」弹窗(点击「我已知晓」确认按钮)。
|
||||
* 该弹窗在每次登录后必现,不处理会导致后续自动化操作卡死超时。
|
||||
* @param {import('puppeteer').Page} page
|
||||
*/
|
||||
async function dismissGovernanceNoticeDialog (page) {
|
||||
try {
|
||||
const confirmBtn = await page.$(GOVERNANCE_NOTICE_DIALOG_CONFIRM_BTN_SELECTOR)
|
||||
if (!confirmBtn) return
|
||||
logInfo('[boss-auto-browse] 检测到「治理公告」弹窗,点击「我已知晓」关闭...')
|
||||
try {
|
||||
const box = await confirmBtn.boundingBox().catch(() => null)
|
||||
if (box) {
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2)
|
||||
} else {
|
||||
await confirmBtn.click()
|
||||
}
|
||||
} catch {
|
||||
await confirmBtn.click().catch(() => {})
|
||||
}
|
||||
await page.waitForSelector(GOVERNANCE_NOTICE_DIALOG_SELECTOR, { hidden: true, timeout: 5000 }).catch(() => {})
|
||||
logInfo('[boss-auto-browse] 「治理公告」弹窗已关闭')
|
||||
await sleep(300)
|
||||
} catch {
|
||||
// 弹窗不存在或关闭失败时静默继续
|
||||
}
|
||||
}
|
||||
|
||||
/** 招聘端 localStorage 生效的页面 URL(与 geek 端一致使用 desktop) */
|
||||
const localStoragePageUrl = 'https://www.zhipin.com/desktop/'
|
||||
|
||||
@@ -86,6 +116,7 @@ export async function launchBrowserAndNavigateToChat () {
|
||||
await page.goto(BOSS_CHAT_PAGE_URL, { timeout: 60 * 1000 })
|
||||
await page.waitForFunction(() => document.readyState === 'complete', { timeout: 120 * 1000 })
|
||||
await new Promise(r => setTimeout(r, 1500))
|
||||
await dismissGovernanceNoticeDialog(page)
|
||||
return { browser, page }
|
||||
}
|
||||
|
||||
@@ -304,6 +335,9 @@ export default async function startBossAutoBrowse (hooksFromCaller, opts = {}) {
|
||||
await storeStorage(page).catch(() => {})
|
||||
}
|
||||
|
||||
// 关闭登录后弹出的「治理公告」弹窗(每次登录必现,不处理会阻塞后续操作)
|
||||
await dismissGovernanceNoticeDialog(page)
|
||||
|
||||
// 切换职位(若指定了 jobId 且非全部职位标志)
|
||||
if (jobId && jobId !== '-1' && jobId !== '0') {
|
||||
await switchRecommendJobId(page, jobId)
|
||||
|
||||
Reference in New Issue
Block a user