Remove standalone capture debug helpers
This commit is contained in:
@@ -33,7 +33,6 @@ RUN ./node_modules/.bin/playwright install --with-deps --only-shell chromium \
|
|||||||
&& rm -rf /var/lib/apt/lists/* /tmp/*
|
&& rm -rf /var/lib/apt/lists/* /tmp/*
|
||||||
COPY --from=build /app/dist ./dist
|
COPY --from=build /app/dist ./dist
|
||||||
COPY --from=build /app/dist-client ./dist-client
|
COPY --from=build /app/dist-client ./dist-client
|
||||||
COPY scripts ./scripts
|
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD ["node", "dist/server.js"]
|
CMD ["node", "dist/server.js"]
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import base64
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
|
|
||||||
CDP_ENDPOINT = os.environ.get("CDP_ENDPOINT", "http://127.0.0.1:9222")
|
|
||||||
|
|
||||||
|
|
||||||
def get_page_ws_url():
|
|
||||||
with urllib.request.urlopen(f"{CDP_ENDPOINT}/json/list", timeout=30) as response:
|
|
||||||
targets = json.loads(response.read().decode("utf-8"))
|
|
||||||
pages = [target for target in targets if target.get("type") == "page"]
|
|
||||||
if not pages:
|
|
||||||
raise SystemExit("No CDP page target found.")
|
|
||||||
selected = next((page for page in pages if "chatgpt.com/share/" in page.get("url", "")), pages[0])
|
|
||||||
return selected["webSocketDebuggerUrl"], selected
|
|
||||||
|
|
||||||
|
|
||||||
def websocket_connect(ws_url):
|
|
||||||
parsed = urllib.parse.urlparse(ws_url)
|
|
||||||
if parsed.scheme != "ws":
|
|
||||||
raise SystemExit(f"Only ws:// endpoints are supported, got {ws_url}")
|
|
||||||
host = parsed.hostname or "127.0.0.1"
|
|
||||||
port = parsed.port or 80
|
|
||||||
path = parsed.path
|
|
||||||
if parsed.query:
|
|
||||||
path += "?" + parsed.query
|
|
||||||
|
|
||||||
sock = socket.create_connection((host, port), timeout=30)
|
|
||||||
key = base64.b64encode(os.urandom(16)).decode("ascii")
|
|
||||||
request = (
|
|
||||||
f"GET {path} HTTP/1.1\r\n"
|
|
||||||
f"Host: {host}:{port}\r\n"
|
|
||||||
"Upgrade: websocket\r\n"
|
|
||||||
"Connection: Upgrade\r\n"
|
|
||||||
f"Sec-WebSocket-Key: {key}\r\n"
|
|
||||||
"Sec-WebSocket-Version: 13\r\n"
|
|
||||||
"\r\n"
|
|
||||||
).encode("ascii")
|
|
||||||
sock.sendall(request)
|
|
||||||
response = b""
|
|
||||||
while b"\r\n\r\n" not in response:
|
|
||||||
response += sock.recv(4096)
|
|
||||||
header = response.split(b"\r\n\r\n", 1)[0].decode("iso-8859-1")
|
|
||||||
if " 101 " not in header.split("\r\n", 1)[0]:
|
|
||||||
raise SystemExit(f"WebSocket upgrade failed:\n{header}")
|
|
||||||
accept = base64.b64encode(hashlib.sha1((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode("ascii")).digest()).decode("ascii")
|
|
||||||
if f"Sec-WebSocket-Accept: {accept}".lower() not in header.lower():
|
|
||||||
raise SystemExit("WebSocket accept header did not match.")
|
|
||||||
return sock
|
|
||||||
|
|
||||||
|
|
||||||
def send_frame(sock, payload):
|
|
||||||
data = payload.encode("utf-8")
|
|
||||||
header = bytearray([0x81])
|
|
||||||
if len(data) < 126:
|
|
||||||
header.append(0x80 | len(data))
|
|
||||||
elif len(data) < 65536:
|
|
||||||
header.append(0x80 | 126)
|
|
||||||
header.extend(struct.pack("!H", len(data)))
|
|
||||||
else:
|
|
||||||
header.append(0x80 | 127)
|
|
||||||
header.extend(struct.pack("!Q", len(data)))
|
|
||||||
mask = os.urandom(4)
|
|
||||||
header.extend(mask)
|
|
||||||
masked = bytes(byte ^ mask[index % 4] for index, byte in enumerate(data))
|
|
||||||
sock.sendall(bytes(header) + masked)
|
|
||||||
|
|
||||||
|
|
||||||
def recv_exact(sock, size):
|
|
||||||
chunks = []
|
|
||||||
remaining = size
|
|
||||||
while remaining:
|
|
||||||
chunk = sock.recv(remaining)
|
|
||||||
if not chunk:
|
|
||||||
raise EOFError("WebSocket closed")
|
|
||||||
chunks.append(chunk)
|
|
||||||
remaining -= len(chunk)
|
|
||||||
return b"".join(chunks)
|
|
||||||
|
|
||||||
|
|
||||||
def recv_frame(sock):
|
|
||||||
first, second = recv_exact(sock, 2)
|
|
||||||
opcode = first & 0x0F
|
|
||||||
masked = second & 0x80
|
|
||||||
length = second & 0x7F
|
|
||||||
if length == 126:
|
|
||||||
length = struct.unpack("!H", recv_exact(sock, 2))[0]
|
|
||||||
elif length == 127:
|
|
||||||
length = struct.unpack("!Q", recv_exact(sock, 8))[0]
|
|
||||||
mask = recv_exact(sock, 4) if masked else b""
|
|
||||||
payload = recv_exact(sock, length) if length else b""
|
|
||||||
if masked:
|
|
||||||
payload = bytes(byte ^ mask[index % 4] for index, byte in enumerate(payload))
|
|
||||||
if opcode == 0x8:
|
|
||||||
raise EOFError("WebSocket closed")
|
|
||||||
if opcode == 0x9:
|
|
||||||
return None
|
|
||||||
if opcode != 0x1:
|
|
||||||
return None
|
|
||||||
return payload.decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def call(sock, method, params=None, message_id=1):
|
|
||||||
send_frame(sock, json.dumps({"id": message_id, "method": method, "params": params or {}}))
|
|
||||||
while True:
|
|
||||||
message = recv_frame(sock)
|
|
||||||
if not message:
|
|
||||||
continue
|
|
||||||
parsed = json.loads(message)
|
|
||||||
if parsed.get("id") == message_id:
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
expression = sys.stdin.read().strip()
|
|
||||||
if not expression:
|
|
||||||
expression = "({ title: document.title, url: location.href, text: document.body.innerText.slice(0, 1000) })"
|
|
||||||
|
|
||||||
ws_url, target = get_page_ws_url()
|
|
||||||
sock = websocket_connect(ws_url)
|
|
||||||
try:
|
|
||||||
result = call(
|
|
||||||
sock,
|
|
||||||
"Runtime.evaluate",
|
|
||||||
{
|
|
||||||
"expression": expression,
|
|
||||||
"returnByValue": True,
|
|
||||||
"awaitPromise": True,
|
|
||||||
"timeout": 30000,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
print(json.dumps({"target": target, "result": result}, ensure_ascii=False, indent=2))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
import { chromium } from "playwright";
|
|
||||||
import { spawn } from "node:child_process";
|
|
||||||
import { existsSync, readdirSync } from "node:fs";
|
|
||||||
import { mkdtemp, rm } from "node:fs/promises";
|
|
||||||
import net from "node:net";
|
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
const sourceUrl = process.argv[2];
|
|
||||||
const holdMs = Number(process.env.DEBUG_HOLD_MS || "900000");
|
|
||||||
const port = Number(process.env.DEBUG_PORT || "9222");
|
|
||||||
const proxyPort = process.env.DEBUG_PROXY_PORT ? Number(process.env.DEBUG_PROXY_PORT) : null;
|
|
||||||
const cdpOnly = process.env.DEBUG_CDP_ONLY === "1";
|
|
||||||
|
|
||||||
if (!sourceUrl) {
|
|
||||||
console.error("usage: node scripts/debug-chatgpt-share.mjs <chatgpt-share-url>");
|
|
||||||
process.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
let browser;
|
|
||||||
let context;
|
|
||||||
let browserProcess;
|
|
||||||
let proxyServer;
|
|
||||||
let userDataDir;
|
|
||||||
|
|
||||||
function now() {
|
|
||||||
return new Date().toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function findChromiumExecutable() {
|
|
||||||
const preferred = process.env.DEBUG_CHROMIUM_EXECUTABLE || chromium.executablePath();
|
|
||||||
if (existsSync(preferred)) return preferred;
|
|
||||||
|
|
||||||
const browsersRoot = process.env.PLAYWRIGHT_BROWSERS_PATH || path.dirname(path.dirname(preferred));
|
|
||||||
const headlessShell = readdirSync(browsersRoot)
|
|
||||||
.filter((entry) => entry.startsWith("chromium_headless_shell-"))
|
|
||||||
.sort()
|
|
||||||
.reverse()
|
|
||||||
.map((entry) => path.join(browsersRoot, entry, "chrome-headless-shell-linux64", "chrome-headless-shell"))
|
|
||||||
.find((candidate) => existsSync(candidate));
|
|
||||||
|
|
||||||
if (headlessShell) return headlessShell;
|
|
||||||
throw new Error(`No Chromium executable found. Tried ${preferred} and ${browsersRoot}/chromium_headless_shell-*.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function close() {
|
|
||||||
if (context) await context.close().catch(() => undefined);
|
|
||||||
if (browser) await browser.close().catch(() => undefined);
|
|
||||||
if (proxyServer) await new Promise((resolve) => proxyServer.close(resolve)).catch(() => undefined);
|
|
||||||
if (browserProcess && !browserProcess.killed) browserProcess.kill("SIGTERM");
|
|
||||||
if (userDataDir) await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
process.on("SIGINT", () => close().finally(() => process.exit(130)));
|
|
||||||
process.on("SIGTERM", () => close().finally(() => process.exit(143)));
|
|
||||||
|
|
||||||
userDataDir = await mkdtemp(path.join(os.tmpdir(), "aishare-debug-"));
|
|
||||||
const executablePath = findChromiumExecutable();
|
|
||||||
console.log(`[${now()}] Chromium executable: ${executablePath}`);
|
|
||||||
browserProcess = spawn(executablePath, [
|
|
||||||
"--headless",
|
|
||||||
"--no-sandbox",
|
|
||||||
"--disable-dev-shm-usage",
|
|
||||||
"--disable-gpu",
|
|
||||||
"--hide-scrollbars",
|
|
||||||
"--mute-audio",
|
|
||||||
"--window-size=1360,900",
|
|
||||||
"--remote-debugging-address=0.0.0.0",
|
|
||||||
`--remote-debugging-port=${port}`,
|
|
||||||
`--user-data-dir=${userDataDir}`,
|
|
||||||
cdpOnly ? sourceUrl : "about:blank"
|
|
||||||
], {
|
|
||||||
stdio: ["ignore", "pipe", "pipe"]
|
|
||||||
});
|
|
||||||
|
|
||||||
browserProcess.stdout.on("data", (data) => process.stdout.write(data));
|
|
||||||
browserProcess.stderr.on("data", (data) => process.stderr.write(data));
|
|
||||||
browserProcess.on("exit", (code, signal) => {
|
|
||||||
console.log(`[${now()}] Chromium exited code=${code} signal=${signal}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
let devtoolsReady = false;
|
|
||||||
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
||||||
const response = await fetch(`http://127.0.0.1:${port}/json/version`).catch(() => null);
|
|
||||||
if (response?.ok) {
|
|
||||||
devtoolsReady = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
||||||
}
|
|
||||||
if (!devtoolsReady) throw new Error(`Chromium DevTools endpoint did not start on 127.0.0.1:${port}.`);
|
|
||||||
|
|
||||||
if (proxyPort) {
|
|
||||||
proxyServer = net.createServer((client) => {
|
|
||||||
const upstream = net.connect(port, "127.0.0.1");
|
|
||||||
client.pipe(upstream);
|
|
||||||
upstream.pipe(client);
|
|
||||||
client.on("error", () => upstream.destroy());
|
|
||||||
upstream.on("error", () => client.destroy());
|
|
||||||
});
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
proxyServer.once("error", reject);
|
|
||||||
proxyServer.listen(proxyPort, "0.0.0.0", resolve);
|
|
||||||
});
|
|
||||||
console.log(`[${now()}] DevTools proxy: 0.0.0.0:${proxyPort} -> 127.0.0.1:${port}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cdpOnly) {
|
|
||||||
console.log(`[${now()}] CDP-only mode; holding browser for ${Math.round(holdMs / 1000)}s.`);
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, holdMs));
|
|
||||||
await close();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
|
|
||||||
|
|
||||||
context = await browser.newContext({
|
|
||||||
viewport: { width: 1360, height: 900 },
|
|
||||||
deviceScaleFactor: 1,
|
|
||||||
locale: "en-US"
|
|
||||||
});
|
|
||||||
|
|
||||||
const page = await context.newPage();
|
|
||||||
page.setDefaultTimeout(30_000);
|
|
||||||
|
|
||||||
console.log(`[${now()}] Chromium DevTools port: ${port}`);
|
|
||||||
console.log(`[${now()}] Opening ${sourceUrl}`);
|
|
||||||
|
|
||||||
await page.goto(sourceUrl, { waitUntil: "commit", timeout: 45_000 }).catch((error) => {
|
|
||||||
console.log(`[${now()}] goto did not complete: ${error.message}`);
|
|
||||||
});
|
|
||||||
await page.waitForLoadState("domcontentloaded", { timeout: 20_000 }).catch(() => undefined);
|
|
||||||
await page.waitForLoadState("networkidle", { timeout: 10_000 }).catch(() => undefined);
|
|
||||||
await page.waitForTimeout(3000);
|
|
||||||
|
|
||||||
const summary = await page.evaluate(() => {
|
|
||||||
const selectors = [
|
|
||||||
"[data-message-author-role]",
|
|
||||||
"[data-message-id]",
|
|
||||||
"[data-testid]",
|
|
||||||
"main article",
|
|
||||||
"main [role=listitem]",
|
|
||||||
"main .markdown",
|
|
||||||
"main [class*=markdown]",
|
|
||||||
"script[type='application/json']",
|
|
||||||
"script#__NEXT_DATA__"
|
|
||||||
];
|
|
||||||
|
|
||||||
const counts = Object.fromEntries(selectors.map((selector) => [selector, document.querySelectorAll(selector).length]));
|
|
||||||
const title = document.querySelector("h1")?.textContent?.trim() || document.title;
|
|
||||||
const bodySample = (document.body.textContent || "").replace(/\s+/g, " ").trim().slice(0, 600);
|
|
||||||
const testIds = Array.from(document.querySelectorAll("[data-testid]"))
|
|
||||||
.map((element) => element.getAttribute("data-testid"))
|
|
||||||
.filter(Boolean)
|
|
||||||
.slice(0, 80);
|
|
||||||
|
|
||||||
return {
|
|
||||||
href: location.href,
|
|
||||||
title,
|
|
||||||
counts,
|
|
||||||
testIds,
|
|
||||||
bodySample
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(JSON.stringify(summary, null, 2));
|
|
||||||
console.log(`[${now()}] Holding browser for ${Math.round(holdMs / 1000)}s.`);
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, holdMs));
|
|
||||||
await close();
|
|
||||||
+4
-1
@@ -106,7 +106,10 @@ function findChromiumExecutable(): string {
|
|||||||
.filter((entry) => entry.startsWith("chromium_headless_shell-"))
|
.filter((entry) => entry.startsWith("chromium_headless_shell-"))
|
||||||
.sort()
|
.sort()
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((entry) => path.join(browsersRoot, entry, "chrome-headless-shell-linux64", "chrome-headless-shell"))
|
.flatMap((entry) => [
|
||||||
|
path.join(browsersRoot, entry, "chrome-linux", "headless_shell"),
|
||||||
|
path.join(browsersRoot, entry, "chrome-headless-shell-linux64", "chrome-headless-shell")
|
||||||
|
])
|
||||||
.find((candidate) => nodeFs.existsSync(candidate));
|
.find((candidate) => nodeFs.existsSync(candidate));
|
||||||
|
|
||||||
if (headlessShell) return headlessShell;
|
if (headlessShell) return headlessShell;
|
||||||
|
|||||||
Reference in New Issue
Block a user