shot-interactive.mjs
Rama main · réplica de lectura del repositorio original.
/**
* Interactive screenshots: click Settings -> Web Search section -> provider modal.
*/
import { spawn } from "node:child_process";
import { writeFileSync, mkdirSync, rmSync } from "node:fs";
const EDGE = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe";
const OUT = "D:\\web\\dsh-plugins\\dsh-web-tools\\screenshots";
mkdirSync(OUT, { recursive: true });
const PORT = 9335;
const tmpDir = `${process.env.TEMP}\\edge-dsh-int-${Date.now()}`;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
class CDP {
constructor(wsUrl) { this.wsUrl = wsUrl; this.msgId = 0; this.pending = new Map(); }
async connect() {
this.ws = new WebSocket(this.wsUrl);
this.ws.addEventListener("message", (ev) => {
const msg = JSON.parse(ev.data.toString());
if (msg.id) {
const cb = this.pending.get(msg.id);
if (cb) { this.pending.delete(msg.id); cb(msg); }
}
});
await new Promise((res, rej) => {
this.ws.addEventListener("open", res);
this.ws.addEventListener("error", rej);
});
}
async send(method, params = {}) {
const id = ++this.msgId;
return new Promise((resolve, reject) => {
const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`timeout ${method}`)); }, 20000);
this.pending.set(id, (msg) => { clearTimeout(timer); msg.error ? reject(new Error(msg.error.message)) : resolve(msg.result); });
this.ws.send(JSON.stringify({ id, method, params }));
});
}
async eval(expression) {
const r = await this.send("Runtime.evaluate", { expression, returnByValue: true, awaitPromise: true, userGesture: true });
if (r.exceptionDetails) return "EVAL_ERR: " + JSON.stringify(r.exceptionDetails).slice(0, 300);
return r.result?.value;
}
close() { try { this.ws.close(); } catch {} }
}
async function waitFor(cdp, expr, timeoutMs = 30000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const v = await cdp.eval(expr);
if (v === true) return;
await sleep(500);
}
throw new Error("waitFor timed out: " + expr);
}
let edge = null;
try {
edge = spawn(EDGE, [
"--headless=new", "--disable-gpu", "--no-sandbox", "--disable-software-rasterizer",
`--user-data-dir=${tmpDir}`, "--no-first-run", "--disable-sync", "--disable-default-apps", "--disable-extensions",
`--remote-debugging-port=${PORT}`, "--window-size=1440,900", "about:blank",
], { stdio: "ignore" });
let targets;
for (let i = 0; i < 40; i++) {
try { targets = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json(); break; } catch { await sleep(500); }
}
const page = targets.find((t) => t.type === "page");
const cdp = new CDP(page.webSocketDebuggerUrl);
await cdp.connect();
await cdp.send("Page.enable");
await cdp.send("Runtime.enable");
await cdp.send("Emulation.setDeviceMetricsOverride", { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
await cdp.send("Page.navigate", { url: "http://127.0.0.1:3080/" });
await sleep(12000);
console.log("Loaded home. Body head:", JSON.stringify((await cdp.eval(`document.body.innerText.slice(0,120)`))));
// Find and click "设置" (Settings) in the nav
const clickedSettings = await cdp.eval(`(() => {
const all = [...document.querySelectorAll('button, a, [role="button"], [role="menuitem"], [role="tab"], [role="treeitem"], span, div')];
const el = all.find(e => e.textContent && e.textContent.trim() === '设置' && e.offsetParent !== null);
if (el) { el.click(); return 'clicked 设置'; }
const el2 = all.find(e => e.textContent && e.textContent.trim() === 'Settings' && e.offsetParent !== null);
if (el2) { el2.click(); return 'clicked Settings'; }
return 'NOT FOUND';
})()`);
console.log("Click 设置:", clickedSettings);
await sleep(4000);
const bodyAfterSettings = await cdp.eval(`document.body.innerText.slice(0, 400)`);
console.log("After settings click:", JSON.stringify(bodyAfterSettings));
// Look for the web-tools section entry (网页搜索)
const webToolsClick = await cdp.eval(`(() => {
const all = [...document.querySelectorAll('a, button, [role="tab"], [role="button"], span, div')];
const candidates = all.filter(e => e.offsetParent !== null && e.textContent && (e.textContent.includes('网页搜索') || e.textContent.includes('Web Search')));
if (candidates.length === 0) return 'NO web search entry';
// Pick the smallest (most leaf) element
candidates.sort((a, b) => a.textContent.length - b.textContent.length);
candidates[0].click();
const el = candidates[0];
return 'clicked: ' + (el.tagName) + ' | ' + el.textContent.slice(0, 40);
})()`);
console.log("Click web-tools:", webToolsClick);
await sleep(5000);
const bodyAfterWebTools = await cdp.eval(`document.body.innerText.slice(0, 600)`);
console.log("After web-tools click:", JSON.stringify(bodyAfterWebTools));
// Check for our classes
const classCheck = await cdp.eval(`(() => ({
dswtRows: document.querySelectorAll('.dswt-settings-row').length,
dswtGroups: document.querySelectorAll('.dswt-group-card').length,
imgs: document.querySelectorAll('.dswt-settings-row img').length,
}))()`);
console.log("Class check:", JSON.stringify(classCheck));
// Screenshot overview
let shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\overview_1440x900.png`, Buffer.from(shot.data, "base64"));
console.log("saved overview_1440x900.png");
// Click first provider row
const clickedRow = await cdp.eval(`(() => {
const imgs = [...document.querySelectorAll('.dswt-settings-row img')];
if (imgs.length === 0) return 'NO ROW IMG';
const row = imgs[0].closest('.dswt-settings-row.clickable');
if (!row) return 'NO CLICKABLE ROW';
row.click();
return 'clicked row for ' + (imgs[0].alt || 'provider');
})()`);
console.log("Click provider row:", clickedRow);
await sleep(3000);
// Check modal
const modal = await cdp.eval(`(() => {
const m = document.querySelector('.dswt-modal-content, .wt-modal-content');
return m ? { text: m.innerText.slice(0, 200), cls: m.className } : null;
})()`);
console.log("Modal:", JSON.stringify(modal));
if (modal) {
shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\brave_detail_collapsed_1440x900.png`, Buffer.from(shot.data, "base64"));
console.log("saved brave_detail_collapsed_1440x900.png");
// Advanced
const adv = await cdp.eval(`(() => {
const b = document.querySelector('.dswt-advanced-btn');
if (b) { b.click(); return 'clicked advanced'; }
return 'no advanced btn';
})()`);
console.log("Advanced:", adv);
await sleep(1000);
shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\brave_detail_advanced_1440x900.png`, Buffer.from(shot.data, "base64"));
console.log("saved brave_detail_advanced_1440x900.png");
}
cdp.close();
console.log("DONE");
} catch (e) {
console.error("FATAL:", e);
process.exitCode = 1;
} finally {
if (edge && !edge.killed) edge.kill();
try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
}