#!/usr/bin/env node import { mkdirSync, writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; const PORT = process.env.LWLT_CDP_PORT || '9223'; function parseArgs(argv) { const args = {}; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; if (!arg.startsWith('--')) continue; const key = arg.slice(2); const next = argv[i + 1]; if (!next || next.startsWith('--')) args[key] = true; else { args[key] = next; i += 1; } } return args; } function writeJson(path, data) { mkdirSync(dirname(resolve(path)), { recursive: true }); writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`); } async function getJson(url) { const res = await fetch(url); if (!res.ok) throw new Error(`${res.status} ${res.statusText}: ${url}`); return res.json(); } async function getTarget(port) { const targets = await getJson(`http://127.0.0.1:${port}/json`); const target = targets.find((item) => item.type === 'page' && item.url.includes('ltjt.yunzhi.run')); if (!target) throw new Error(`No ltjt.yunzhi.run page target on port ${port}`); return target; } class CDP { constructor(wsUrl) { this.wsUrl = wsUrl; this.id = 0; this.pending = new Map(); } connect() { return new Promise((resolve, reject) => { this.ws = new WebSocket(this.wsUrl); this.ws.onopen = resolve; this.ws.onerror = (event) => reject(new Error(event.message || event.type || 'WebSocket error')); this.ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (!msg.id || !this.pending.has(msg.id)) return; const { resolve: ok, reject: fail, timer } = this.pending.get(msg.id); clearTimeout(timer); this.pending.delete(msg.id); msg.error ? fail(new Error(msg.error.message)) : ok(msg.result); }; }); } send(method, params = {}, timeoutMs = 30000) { const id = ++this.id; this.ws.send(JSON.stringify({ id, method, params })); return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`Timeout: ${method}`)); }, timeoutMs); this.pending.set(id, { resolve, reject, timer }); }); } close() { this.ws?.close(); } } async function evaluate(cdp, expression, timeoutMs = 30000) { const result = await cdp.send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true }, timeoutMs); if (result.exceptionDetails) throw new Error(result.exceptionDetails.text || 'Runtime.evaluate failed'); return result.result.value; } function inspectionExpression(openFresh) { return `(async () => { const openFresh = ${JSON.stringify(Boolean(openFresh))}; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const clean = (value, max = 500) => String(value || '').replace(/\\s+/g, ' ').trim().slice(0, max); const entryUrl = 'https://ltjt.yunzhi.run/System/Business/orders_add.asp?fabudanwei=' + encodeURIComponent('老挝联泰') + '©=0&ddid=0&_=' + Date.now(); const entryStamp = new URL(entryUrl).searchParams.get('_'); function findOrderWindow() { const queue = [window]; while (queue.length) { const win = queue.shift(); let doc; try { doc = win.document; } catch (err) { continue; } if (doc.querySelector('#ListForm') && doc.location.href.includes('/orders_add.asp')) return win; Array.from(win.frames).forEach((frame) => queue.push(frame)); } return null; } if (openFresh) { const frame = document.getElementById('Iframe_Home') || document.querySelector('iframe[name="MainIframe"], iframe'); if (frame) frame.src = entryUrl; } const started = Date.now(); let orderWin = findOrderWindow(); while ((!orderWin || !orderWin.document.querySelector('#ListForm') || orderWin.document.querySelector('#ListForm').elements.length < 800 || (openFresh && !orderWin.document.location.href.includes('_=' + entryStamp))) && Date.now() - started < 25000) { await sleep(500); orderWin = findOrderWindow(); } if (!orderWin) return { ok: false, status: 'orders_add_form_not_found' }; const doc = orderWin.document; const form = doc.querySelector('#ListForm'); const inlineScript = Array.from(doc.querySelectorAll('script:not([src])')).map((script) => script.textContent || '').join('\\n'); function splitTopLevelArgs(text) { const out = []; let start = 0; let depth = 0; let quote = ''; for (let i = 0; i < text.length; i += 1) { const ch = text[i]; const prev = text[i - 1]; if (quote) { if (ch === quote && prev !== '\\\\') quote = ''; continue; } if (ch === '"' || ch === "'" || ch === String.fromCharCode(96)) { quote = ch; continue; } if (ch === '(' || ch === '{' || ch === '[') depth += 1; else if (ch === ')' || ch === '}' || ch === ']') depth -= 1; else if (ch === ',' && depth === 0) { out.push(text.slice(start, i).trim()); start = i + 1; } } out.push(text.slice(start).trim()); return out.filter(Boolean); } function parseObjectLiteralArgs(body) { const item = {}; for (const part of splitTopLevelArgs(body)) { const idx = part.indexOf(':'); if (idx < 0) continue; const key = part.slice(0, idx).trim().replace(/^['"]|['"]$/g, ''); const raw = part.slice(idx + 1).trim(); item[key] = clean(raw, 300); } return item; } const snippets = []; const re = /SelectBox\\.set\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)\\s*;/g; let match; while ((match = re.exec(inlineScript))) { snippets.push({ snippet: clean(match[0], 600), config: parseObjectLiteralArgs(match[1]) }); } const endpointRefs = Array.from(new Set((inlineScript.match(/(?:\\.\\.\\/|\\/)?(?:DAT|dat)\\/AjaxPublicFun\\.asp\\?[^"'\\s+)]+/gi) || []) .map((value) => value.replace(/&/g, '&')))); const variableNames = [ 'Shouxiangmu_Data', 'Jipao_Data', 'canting_Datas', 'jiudian_Datas', 'jingdian_Datas', 'zhu_Datas', 'zao_Datas', 'zhong_Datas', 'wan_Datas' ]; function summarizeDataString(value) { const str = String(value || ''); const separators = ['◇', '◆', '^^', '\\n', '\\r\\n', '|', ',', String.fromCharCode(9)]; const sepCounts = Object.fromEntries(separators.map((sep) => [sep === String.fromCharCode(9) ? 'TAB' : sep, str.split(sep).length - 1])); const rowSep = sepCounts['◇'] > 0 ? '◇' : (sepCounts['\\n'] > 0 ? '\\n' : ''); const rows = rowSep ? str.split(rowSep).filter(Boolean) : (str ? [str] : []); const columnCountHistogram = {}; for (const row of rows.slice(0, 200)) { const counts = ['◆', '^', '|', ',', String.fromCharCode(9)].map((sep) => row.split(sep).length); const max = Math.max(...counts); columnCountHistogram[max] = (columnCountHistogram[max] || 0) + 1; } return { type: typeof value, length: str.length, separator_counts: sepCounts, inferred_row_separator: rowSep, inferred_row_count: rows.length, column_count_histogram_first_200_rows: columnCountHistogram, value_redacted: true }; } const dataVariables = {}; for (const name of variableNames) { dataVariables[name] = summarizeDataString(orderWin[name]); } dataVariables['window.top.QJ_Yuangong_Data'] = summarizeDataString(orderWin.top.QJ_Yuangong_Data); const selectLinkedFields = Array.from(form.elements) .filter((el) => { const name = el.name || el.id || ''; return /^(TianShu|zhuanxianming|chanpinming|zutuanshe|gendanren|xiaoshouren|ys_|banhao|zhusu|zao|zaos|zhong|zhongs|wan|wans|jingdian|Text)/.test(name); }) .map((el) => ({ name: el.name || '', id: el.id || '', tag: el.tagName.toLowerCase(), type: el.type || '', readonly: Boolean(el.readOnly), hidden: el.type === 'hidden' || el.offsetParent == null, required: el.getAttribute('MastInput') === '1' || el.required })); return { ok: true, status: 'selectbox_inspected', page: { url: doc.location.href, title: doc.title, form_element_count: form.elements.length }, selectbox_count: snippets.length, selectboxes: snippets, endpoint_refs: endpointRefs, data_variables: dataVariables, select_linked_fields: selectLinkedFields }; })()`; } async function main() { const args = parseArgs(process.argv.slice(2)); const port = args.port || PORT; const target = await getTarget(port); const cdp = new CDP(target.webSocketDebuggerUrl); await cdp.connect(); try { await cdp.send('Runtime.enable'); const output = await evaluate(cdp, inspectionExpression(args['open-form']), 45000); if (args.out) writeJson(args.out, output); console.log(JSON.stringify({ status: output.status, page: output.page, selectbox_count: output.selectbox_count, endpoint_refs: output.endpoint_refs?.length || 0, data_variables: output.data_variables ? Object.fromEntries(Object.entries(output.data_variables).map(([key, value]) => [key, { length: value.length, inferred_row_count: value.inferred_row_count, inferred_row_separator: value.inferred_row_separator }])) : {}, out: args.out || '' }, null, 2)); } finally { cdp.close(); } } main().catch((error) => { console.error(error.stack || error.message); process.exit(1); });