Files
blog/blog-admin/tools/check-comment-error.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

144 lines
6.5 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.
// 打开评论页,抓「加载失败的请求」+ 控制台错误 + Artalk 错误层文案
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 = 9391;
const URL_ = process.argv[2] || 'http://127.0.0.1:1313/comment.html';
const OUT = process.argv[3] || '';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const userDir = mkdtempSync(join(tmpdir(), 'cdp-cmterr-'));
const chrome = spawn(CHROME, [
'--headless=new', `--remote-debugging-port=${PORT}`, `--user-data-dir=${userDir}`,
'--no-first-run', '--disable-gpu', '--window-size=1280,900', 'about:blank',
], { stdio: 'ignore' });
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(); const net = []; const consoleMsgs = [];
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); return; }
if (m.method === 'Network.responseReceived') {
const r = m.params.response;
if (r.status >= 400 || r.url.includes('/api/')) {
net.push(`HTTP ${r.status} ${r.url.slice(0, 120)}`);
}
}
if (m.method === 'Network.loadingFailed') {
net.push(`FAIL ${m.params.errorText} ${(m.params.blockedReason || '')} type=${m.params.type}`);
}
if (m.method === 'Runtime.consoleAPICalled') {
consoleMsgs.push(`[${m.params.type}] ` + (m.params.args || []).map((a) => a.value ?? a.description ?? a.type).join(' '));
}
if (m.method === 'Runtime.exceptionThrown') {
consoleMsgs.push('[exception] ' + (m.params.exceptionDetails?.exception?.description || m.params.exceptionDetails?.text || ''));
}
});
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 ev = 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('Network.enable');
await send('Page.enable');
await send('Page.navigate', { url: URL_ });
await sleep(9000);
console.log('=== 网络(>=400 或 /api/) ===');
console.log(net.length ? [...new Set(net)].slice(0, 20).join('\n') : '(无)');
console.log('\n=== 控制台 ===');
console.log(consoleMsgs.length ? consoleMsgs.slice(0, 12).join('\n') : '(无)');
console.log('\n=== 卡片计算样式(代码级验证) ===');
console.log(await ev(`(() => {
const c = document.querySelector('.atk-err-card');
if (!c) return 'NO CARD';
const cs = getComputedStyle(c);
const ic = c.querySelector('.atk-err-ic');
const t = c.querySelector('.atk-err-t');
const d = c.querySelector('.atk-err-d');
const r = c.querySelector('.atk-err-retry');
const rect = c.getBoundingClientRect();
const center = Math.abs((rect.x + rect.width / 2) - window.innerWidth / 2);
return JSON.stringify({
textAlign: cs.textAlign,
padding: cs.padding,
border: cs.borderTopWidth + ' ' + cs.borderTopStyle,
background: cs.backgroundColor,
hasDetails: !!c.querySelector('details'),
hasBodyWrap: !!c.querySelector('.atk-err-body'),
icon: ic ? getComputedStyle(ic).display + '/' + getComputedStyle(ic).fontSize : null,
titleAlign: t ? getComputedStyle(t).textAlign : null,
descMaxW: d ? getComputedStyle(d).maxWidth : null,
retryRadius: r ? getComputedStyle(r).borderRadius : null,
cardCentered: center < 8 ? 'yes(off=' + Math.round(center) + 'px)' : 'no(off=' + Math.round(center) + 'px)'
}, null, 1);
})()`));
console.log('\n=== Artalk 错误层 ===');
console.log(await ev(`(() => {
const el = document.querySelector('.atk-error-layer');
if (!el) return '(没有错误层)';
const h = el.getBoundingClientRect();
return JSON.stringify({ text: el.innerText.replace(/\\n/g, ' | '), h: Math.round(h.height), html: el.innerHTML.slice(0, 500) }, null, 1);
})()`));
if (OUT) {
const s = await send('Page.captureScreenshot', { format: 'png' });
writeFileSync(OUT, Buffer.from(s.result.data, 'base64'));
// 错误卡片特写(放大 2 倍,用文档坐标 + captureBeyondViewport 才准)
const rectRaw = await ev(`(() => {
const el = document.querySelector('.atk-err-card');
if (!el) return 'null';
const r = el.getBoundingClientRect();
return JSON.stringify({
x: Math.max(0, r.x + window.scrollX - 10),
y: Math.max(0, r.y + window.scrollY - 10),
width: r.width + 20,
height: r.height + 20
});
})()`);
if (rectRaw && rectRaw !== 'null') {
const clip = JSON.parse(rectRaw);
const s2 = await send('Page.captureScreenshot', {
format: 'png', captureBeyondViewport: true, clip: { ...clip, scale: 2 },
});
writeFileSync(OUT.replace(/\.png$/, '.card.png'), Buffer.from(s2.result.data, 'base64'));
}
}
// ── 点「重新加载」→ 应触发新的一轮 /api/v2 请求(验证移动节点后事件还在)
const countApi = () => net.filter((x) => x.includes('/api/v2/')).length;
const before = countApi();
const clicked = await ev(`(() => { const b = document.querySelector('.atk-err-retry'); if (b) b.click(); return !!b; })()`);
await sleep(4500);
const after = countApi();
console.log(`\n=== 点「重新加载」 ===`);
console.log(`按钮存在=${clicked} | /api/v2 请求数 ${before} → ${after} ${after > before ? '✓ 事件仍在' : '✗ 事件丢了'}`);
console.log('错误层现状:', await ev(`(() => {
const el = document.querySelector('.atk-error-layer');
if (!el) return '(已移除)';
const c = el.querySelector('.atk-err-card');
return c ? '仍是我们重写后的卡片(重试再次失败)' : 'Artalk 原生层:' + el.innerText.slice(0, 60);
})()`));
ws.close();
} catch (e) {
console.error('ERROR', e.message);
} finally {
chrome.kill();
}