Files
blog/blog-admin/tools/check-human-widget.mjs
T
zqlit a74b3c7127
Deploy to Production / pre-check (push) Successful in 58s
Deploy to Production / build (push) Successful in 4m3s
Deploy to Production / deploy-edgeone (push) Successful in 3m48s
Deploy to Production / finalize (push) Successful in 26s
Deploy to Production / notify-failure (push) Skipped
归档 artalk-cf 评论后端 + rss-robot 到 blog-admin(含技术选型/模块分布 README)
2026-10-04 08:45:40 +08:00

94 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 人机验证前端控件自测:本地评论页加载后
// 1) 看静默流程结果(__humanCheck.state())
// 2) 手动触发「点一下验证」控件并截图
// 3) 手动触发「图形验证码」控件并截图
// 4) 收集控制台错误
import { spawn } from 'node:child_process';
import { mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const CHROME = 'C:/Users/QunLIn/.agent-browser/browsers/chrome-154.0.8037.92/chrome.exe';
const PORT = 9381;
const URL_ = process.argv[2] || 'http://127.0.0.1:1313/comment.html';
const OUT = process.argv[3] || 'C:/Users/QunLIn/WorkBuddy/2026-10-02-15-13-24/.workbuddy/human-widget.png';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const userDir = mkdtempSync(join(tmpdir(), 'cdp-human-'));
const chrome = spawn(CHROME, ['--headless=new', `--remote-debugging-port=${PORT}`, `--user-data-dir=${userDir}`,
'--no-first-run', '--disable-gpu', '--window-size=1280,1000', 'about:blank'], { stdio: 'ignore' });
const errs = [];
try {
for (let i = 0; i < 40; i++) { try { const r = await fetch(`http://127.0.0.1:${PORT}/json/version`); if (r.ok) break; } catch {} await sleep(300); }
const tab = await (await fetch(`http://127.0.0.1:${PORT}/json/new?${encodeURIComponent(URL_)}`, { method: 'PUT' })).json();
const ws = new WebSocket(tab.webSocketDebuggerUrl);
let id = 0; const pending = new Map();
ws.addEventListener('message', (ev) => {
const m = JSON.parse(ev.data);
if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); }
if (m.method === 'Runtime.consoleAPICalled' && m.params.type === 'error') {
errs.push((m.params.args || []).map((a) => a.value || a.description || '').join(' ').slice(0, 160));
}
if (m.method === 'Runtime.exceptionThrown') {
errs.push('EXCEPTION ' + (m.params.exceptionDetails?.exception?.description || '').slice(0, 160));
}
});
await new Promise((res) => ws.addEventListener('open', res));
const send = (method, params = {}) => new Promise((res) => { const mid = ++id; pending.set(mid, res); ws.send(JSON.stringify({ id: mid, method, params })); });
const evalJs = async (expr) => {
const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true });
return r.result?.result?.value ?? r.result?.value ?? null;
};
await send('Runtime.enable');
await send('Page.enable');
await sleep(7000);
await evalJs(`(() => { const el = document.querySelector('#Comments') || document.querySelector('.comments-area'); if (el) el.scrollIntoView({ block: 'center' }); return 'ok'; })()`);
await sleep(2500);
console.log('— 钩子存在:', await evalJs('typeof window.__humanCheck'));
// 每 500ms 采一次状态,直到拿到通行证(用于量静默 PoW 的耗时)
const t0 = Date.now();
for (let i = 0; i < 40; i++) {
const st = await evalJs('JSON.stringify(window.__humanCheck ? window.__humanCheck.state() : null)');
if (st && st.indexOf('"hasPass":true') >= 0) {
console.log(`— 静默流程:${((Date.now() - t0) / 1000).toFixed(1)}s 后拿到通行证 ${st}`);
break;
}
if (i === 39) console.log('— 静默流程:60s 内未拿到通行证,最后状态', st);
await sleep(500);
}
console.log('— 是否已有控件:', await evalJs('document.querySelectorAll(".atk-human-box").length'));
// 手动触发「点一下验证」
await evalJs('window.__humanCheck && window.__humanCheck.drop(); window.__humanCheck && window.__humanCheck.showClick();');
await sleep(900);
console.log('— 点一下控件:', await evalJs('JSON.stringify((()=>{const w=document.querySelector(".atk-human-box");if(!w)return null;const b=w.getBoundingClientRect();return {cls:w.className,text:w.textContent.trim(),x:Math.round(b.x),y:Math.round(b.y),w:Math.round(b.width),h:Math.round(b.height)};})())'));
const shot1 = await send('Page.captureScreenshot', { format: 'png' });
// 点一下 → 应立即拿到通行证(复用缓存的 PoW 证明,不重算)
const tClick = Date.now();
await evalJs('document.querySelector(".atk-human-box") && document.querySelector(".atk-human-box").click(); "ok"');
let clicked;
for (let i = 0; i < 20; i++) {
clicked = await evalJs('JSON.stringify(window.__humanCheck.state())');
if (clicked && clicked.indexOf('"hasPass":true') >= 0) break;
await sleep(250);
}
console.log(`— 点击验证:${((Date.now() - tClick) / 1000).toFixed(2)}s → ${clicked}`);
writeFileSync(OUT.replace(/\.png$/, '-click.png'), Buffer.from(shot1.result.data, 'base64'));
// 手动触发「图形验证码」
await evalJs('window.__humanCheck && window.__humanCheck.drop(); window.__humanCheck && window.__humanCheck.showCaptcha();');
await sleep(1800);
console.log('— 图形验证码控件:', await evalJs('JSON.stringify((()=>{const w=document.querySelector(".atk-human-box");if(!w)return null;const img=w.querySelector(".atk-human-img");return {cls:w.className,imgLoaded:!!(img&&img.src&&img.naturalWidth>0),imgW:img?img.naturalWidth:0,text:w.textContent.trim()};})())'));
const shot2 = await send('Page.captureScreenshot', { format: 'png' });
writeFileSync(OUT.replace(/\.png$/, '-captcha.png'), Buffer.from(shot2.result.data, 'base64'));
console.log('— 控制台错误:', errs.length ? errs.slice(0, 5) : '(无)');
ws.close();
} catch (e) {
console.error('ERROR', e.message);
} finally {
chrome.kill();
}