From 70ec657b4ead415a2355fd57b25e42d74dd8aa72 Mon Sep 17 00:00:00 2001 From: hermes Date: Thu, 27 Aug 2026 03:06:51 +0000 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=A4=B1=E6=95=88=E5=91=8A?= =?UTF-8?q?=E8=AD=A6=E5=8D=87=E7=BA=A7:=20=E8=87=AA=E5=8A=A8=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E9=A1=B5=E9=9D=A2=E6=8A=93=E7=99=BB=E5=BD=95=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81=E5=8F=91=E7=BE=A4(=E6=96=87=E5=AD=97+?= =?UTF-8?q?=E4=BA=8C=E7=BB=B4=E7=A0=81=E5=9B=BE=E7=89=87+'=E4=BB=8A?= =?UTF-8?q?=E6=97=A5=E4=B8=8D=E9=9C=80=E8=A6=81=E5=8F=AF=E5=BF=BD=E7=95=A5?= =?UTF-8?q?'=E8=AF=B4=E6=98=8E),=20=E5=A4=B1=E8=B4=A5=E9=99=8D=E7=BA=A7?= =?UTF-8?q?=E7=BA=AF=E6=96=87=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/daily-runner.js | 31 +++++++++++++++++-- scripts/login-qrcode.js | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 scripts/login-qrcode.js diff --git a/scripts/daily-runner.js b/scripts/daily-runner.js index 9a92aef..888e62c 100644 --- a/scripts/daily-runner.js +++ b/scripts/daily-runner.js @@ -47,6 +47,33 @@ function send(target, text) { } catch (e) { log(`发送失败: ${String(e.message).slice(0, 100)}`); } } +// 发送图片+文字到群(MEDIA: 语法) +function sendGroupImage(text, imgPath) { + try { + fs.writeFileSync(`/tmp/runner-imgmsg-${ACCT}.txt`, `${text}\nMEDIA:${imgPath}`); + execSync(`${HERMES} send -t "feishu:${GROUP}" -f /tmp/runner-imgmsg-${ACCT}.txt`, + { stdio: ['pipe', 'pipe', 'pipe'], timeout: 90000 }); + log(`已发送图片消息 → 群(数字HR)(附二维码)`); + } catch (e) { log(`图片消息发送失败: ${String(e.message).slice(0, 100)}`); } +} + +// 登录失效时:刷新页面抓登录二维码,发群(文字+二维码+补充说明) +function notifyLoginExpired() { + const label = `${ACCT}(${ACCT === 'hr1' ? 'Chrome·合塑账号' : 'Edge·派卡账号'})`; + const text = `🚨 当前${label}账号登录已失效,今日筛选中止。扫码可重新登录:\n如今天不需要协助筛选简历,可忽略该二维码`; + const qrPath = `/tmp/zhaopin-login-qr-${ACCT}.png`; + try { + execSync(`node ${SCRIPTS}/login-qrcode.js ${PORT} ${qrPath}`, + { cwd: SCRIPTS, timeout: 90000, stdio: ['pipe', 'pipe', 'pipe'] }); + if (fs.existsSync(qrPath) && fs.statSync(qrPath).size > 1000) { + sendGroupImage(text, qrPath); + return; + } + log('二维码截图异常(文件过小或不存在),降级为纯文字'); + } catch (e) { log(`二维码获取失败: ${String(e.message).slice(0, 100)},降级为纯文字`); } + send(GROUP, `🚨 当前${label}账号登录已失效,今日筛选中止,请在任务机重新登录智联`); +} + function writeState(status, chatLeft) { fs.writeFileSync(path.join(STATE_DIR, `state-${ACCT}.json`), JSON.stringify({ date: today(), status, chatLeft: chatLeft ?? null, endedAt: new Date().toISOString() })); @@ -159,9 +186,9 @@ function statusText(s) { maybeSendReport('kicked_out', lastChatLeft); break; } - // 登录失效 → 群告警,结束 + // 登录失效 → 群告警(带登录二维码),结束 if (/登录已失效|passport\.zhaopin\.com/.test(out)) { - send(GROUP, `🚨 ${ACCT}(${ACCT === 'hr1' ? 'Chrome·合塑账号' : 'Edge·派卡账号'})智联登录态已失效,今日筛选中止,请在任务机重新登录智联`); + notifyLoginExpired(); maybeSendReport('login_expired', lastChatLeft); break; } diff --git a/scripts/login-qrcode.js b/scripts/login-qrcode.js new file mode 100644 index 0000000..0db6af4 --- /dev/null +++ b/scripts/login-qrcode.js @@ -0,0 +1,68 @@ +#!/usr/bin/env node +/** + * 登录二维码获取器 v1.0(2026-08-27) + * 用法:node login-qrcode.js <输出png路径> + * + * 流程:连CDP → 找登录页tab(没有则刷新推荐页触发跳转)→ 截取二维码区域的图片 + * 智联登录页二维码是 canvas 或 img 动态渲染,直接整页截图最稳(剪裁左半部分登录卡片区)。 + */ +const path = require('path'); +function tryRequire(names) { + for (const n of names) { try { return require(n); } catch (_) {} } + throw new Error('无法加载 playwright-core'); +} +const { chromium } = tryRequire([process.env.PWCORE_PATH, 'playwright-core'].filter(Boolean)); + +const PORT = process.argv[2] || '9222'; +const OUT = process.argv[3] || `/tmp/zhaopin-login-qr-${PORT}.png`; + +(async () => { + const b = await chromium.connectOverCDP(`http://127.0.0.1:${PORT}`); + let p = b.contexts()[0]?.pages().find(x => /passport\.zhaopin\.com/.test(x.url())); + if (!p) { + // 找推荐页tab刷新,触发跳转到登录页 + p = b.contexts()[0]?.pages().find(x => /rd\d?\.zhaopin\.com/.test(x.url())) || b.contexts()[0]?.pages()[0]; + if (!p) { console.error('NO_PAGE'); process.exit(1); } + await p.reload({ waitUntil: 'domcontentloaded', timeout: 60000 }).catch(() => {}); + await new Promise(r => setTimeout(r, 4000)); + // 刷新后可能跳到登录页 + if (!/passport\.zhaopin\.com/.test(p.url())) { + await p.goto('https://passport.zhaopin.com/org/login', { waitUntil: 'domcontentloaded', timeout: 60000 }).catch(() => {}); + await new Promise(r => setTimeout(r, 4000)); + } + } + console.log('PAGE:', p.url().slice(0, 90)); + + // 等二维码出现(canvas 或 img.qr / 二维码容器,多选择器兜底) + const qrSel = [ + 'canvas', // 智联登录二维码多为 canvas 绘制 + 'img[src*="qr"]', + '.qrcode', '.qr-code', '.login-qr', '[class*="qr" i]', + '.zp-qr', '#qr', '[id*="qr" i]', + ]; + let el = null; + for (let i = 0; i < 10 && !el; i++) { + for (const s of qrSel) { + el = await p.$(s).catch(() => null); + if (el) { + // 校验尺寸:太小的 canvas 可能是图标 + const box = await el.boundingBox().catch(() => null); + if (!box || box.width < 100) { el = null; continue; } + break; + } + } + if (!el) await new Promise(r => setTimeout(r, 1000)); + } + + let shotPath = OUT; + if (el) { + await el.screenshot({ path: OUT }).catch(async () => { await p.screenshot({ path: OUT }); }); + console.log('QR_ELEMENT_SHOT'); + } else { + // 兜底:整页截图(登录页通常是全屏二维码布局) + await p.screenshot({ path: OUT }); + console.log('FULLPAGE_SHOT'); + } + console.log('SAVED:', OUT); + process.exit(0); +})().catch(e => { console.error('ERR:', String(e).slice(0, 150)); process.exit(1); });