更新若干功能

This commit is contained in:
李琦
2026-08-14 07:52:01 +08:00
parent 153c7ed448
commit 89bc265f68
196 changed files with 17675 additions and 0 deletions

119
tools/cdp.js Normal file
View File

@@ -0,0 +1,119 @@
// cdp.js - drive the Code Count WebView2 via Chrome DevTools Protocol.
// Usage:
// node cdp.js eval "<expression>" evaluate JS (await-ed, JSON result)
// node cdp.js click <x> <y> trusted click at CSS viewport coords
// node cdp.js rclick <x> <y> trusted right-click
// node cdp.js key <Key> [text] dispatch a key press (e.g. Enter, Escape)
// node cdp.js type "<text>" insert text into the focused element
// node cdp.js shot <file.png> capture in-page screenshot
// node cdp.js dialog <accept|dismiss> set auto dialog policy for this run (default: report only)
// Multiple commands can be chained with ";;" between argument groups.
const PORT = process.env.CC_DEVTOOLS_PORT || '9222';
let seq = 0;
const pending = new Map();
let ws;
let dialogPolicy = null;
const dialogEvents = [];
function send(method, params = {}) {
return new Promise((resolve, reject) => {
const id = ++seq;
pending.set(id, { resolve, reject });
ws.send(JSON.stringify({ id, method, params }));
});
}
async function connect() {
const res = await fetch(`http://127.0.0.1:${PORT}/json/list`);
const targets = await res.json();
const page = targets.find(t => t.type === 'page');
if (!page) throw new Error('no page target');
ws = new WebSocket(page.webSocketDebuggerUrl);
await new Promise((ok, bad) => { ws.onopen = ok; ws.onerror = bad; });
ws.onmessage = ev => {
const msg = JSON.parse(ev.data);
if (msg.id && pending.has(msg.id)) {
const { resolve, reject } = pending.get(msg.id);
pending.delete(msg.id);
msg.error ? reject(new Error(msg.error.message)) : resolve(msg.result);
} else if (msg.method === 'Page.javascriptDialogOpening') {
dialogEvents.push(msg.params);
console.log('DIALOG-OPEN', JSON.stringify({ type: msg.params.type, message: msg.params.message }));
if (dialogPolicy) {
send('Page.handleJavaScriptDialog', { accept: dialogPolicy === 'accept' })
.then(() => console.log('DIALOG-HANDLED', dialogPolicy))
.catch(e => console.log('DIALOG-ERR', e.message));
}
}
};
await send('Page.enable');
await send('Runtime.enable');
}
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function mouse(type, x, y, button, clickCount = 1) {
await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y, button: 'none' });
await sleep(40);
await send('Input.dispatchMouseEvent', { type, x, y, button, clickCount });
}
async function click(x, y, button = 'left') {
await mouse('mousePressed', x, y, button);
await sleep(50);
await send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button, clickCount: 1 });
}
async function main() {
const groups = process.argv.slice(2).join('\u0000').split('\u0000;;\u0000').map(g => g.split('\u0000'));
await connect();
for (const g of groups) {
const [cmd, ...args] = g;
if (cmd === 'dialog') {
dialogPolicy = args[0];
console.log('dialog policy:', dialogPolicy);
} else if (cmd === 'eval') {
const r = await send('Runtime.evaluate', { expression: args.join(' '), awaitPromise: true, returnByValue: true });
console.log('EVAL', JSON.stringify(r.result && 'value' in r.result ? r.result.value : r.result));
if (r.exceptionDetails) console.log('EXC', JSON.stringify(r.exceptionDetails.exception?.description || r.exceptionDetails.text));
} else if (cmd === 'click') {
await click(Number(args[0]), Number(args[1]));
console.log('clicked', args[0], args[1]);
} else if (cmd === 'rclick') {
await click(Number(args[0]), Number(args[1]), 'right');
console.log('right-clicked', args[0], args[1]);
} else if (cmd === 'key') {
const key = args[0];
const defs = { Enter: { code: 'Enter', keyCode: 13, text: '\r' }, Escape: { code: 'Escape', keyCode: 27 }, Tab: { code: 'Tab', keyCode: 9 } };
const d = defs[key] || { code: key, keyCode: key.charCodeAt(0) };
await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code: d.code, windowsVirtualKeyCode: d.keyCode, text: d.text });
await sleep(30);
await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code: d.code, windowsVirtualKeyCode: d.keyCode });
console.log('key', key);
} else if (cmd === 'type') {
await send('Input.insertText', { text: args.join(' ') });
console.log('typed', args.join(' '));
} else if (cmd === 'shot') {
const shot = await send('Page.captureScreenshot', { format: 'png' });
require('fs').writeFileSync(args[0], Buffer.from(shot.data, 'base64'));
console.log('saved', args[0]);
} else if (cmd === 'viewport') {
// viewport <w> <h> 模拟视口尺寸viewport 0 恢复真实尺寸
const w = Number(args[0]);
if (w) await send('Emulation.setDeviceMetricsOverride', { width: w, height: Number(args[1] || 700), deviceScaleFactor: 0, mobile: false });
else await send('Emulation.clearDeviceMetricsOverride');
console.log('viewport', args.join('x') || 'cleared');
} else if (cmd === 'wait') {
await sleep(Number(args[0] || 500));
} else {
throw new Error('unknown cmd: ' + cmd);
}
await sleep(120);
}
// give async dialog handlers a moment before exit
await sleep(300);
ws.close();
}
main().catch(e => { console.error('FATAL', e.message); process.exit(1); });