first commit

This commit is contained in:
admin committed 2026-11-05 15:16:24 +08:00
commit 9399a88d39
347 files changed
+167716

No files matched your search

+186
View File
@@ -0,0 +1,186 @@
// 只读 CDP 采集:连接已登录 Chrome(chrome://inspect 调试模式)
// 该模式只暴露浏览器级 WebSocket,不提供 /json HTTP 接口,
// 因此按 chrome-devtools-mcp autoConnect 的做法读取 DevToolsActivePort。
// 用法: node cdp-probe.mjs <输出目录>
import fs from 'node:fs';
import path from 'node:path';
const OUT = path.resolve(process.argv[2] || '.cdp');
fs.mkdirSync(OUT, { recursive: true });
const USER_DATA_DIR =
process.env.CHROME_USER_DATA_DIR ||
'C:\\Users\\Administrator\\AppData\\Local\\Google\\Chrome\\User Data';
const say = (s) => console.log(String(s));
const writeJson = (n, o) =>
fs.writeFileSync(path.join(OUT, n), JSON.stringify(o, null, 2), 'utf8');
const writeText = (n, s) => fs.writeFileSync(path.join(OUT, n), String(s ?? ''), 'utf8');
class Cdp {
constructor(ws) { this.ws = ws; this.seq = 0; this.pending = new Map(); }
static async attach(wsUrl) {
const ws = new WebSocket(wsUrl);
await new Promise((res, rej) => {
const t = setTimeout(() => rej(new Error('ws open timeout')), 15000);
ws.addEventListener('open', () => { clearTimeout(t); res(); }, { once: true });
ws.addEventListener('error', (e) => { clearTimeout(t); rej(new Error('ws error')); }, { once: true });
});
const c = new Cdp(ws);
ws.addEventListener('message', (ev) => {
let m; try { m = JSON.parse(ev.data); } catch { return; }
if (m.id && c.pending.has(m.id)) {
const p = c.pending.get(m.id); c.pending.delete(m.id);
clearTimeout(p.timer);
if (m.error) p.reject(new Error(m.error.message)); else p.resolve(m.result);
}
});
return c;
}
send(method, params = {}, sessionId, timeoutMs = 60000) {
const id = ++this.seq;
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 });
const msg = { id, method, params };
if (sessionId) msg.sessionId = sessionId;
this.ws.send(JSON.stringify(msg));
});
}
async evalExpr(expression, sessionId) {
const r = await this.send('Runtime.evaluate', {
expression, returnByValue: true, awaitPromise: true, userGesture: false,
}, sessionId);
if (r.exceptionDetails) {
const d = r.exceptionDetails;
return { __error: d.text + ' :: ' + (d.exception?.description || '') };
}
return r.result?.value;
}
close() { try { this.ws.close(); } catch {} }
}
// ---------------- 页面侧探针(纯只读,不点击任何按钮) ----------------
const PROBE = String.raw`(() => {
const out = {};
try {
const q = (s) => Array.from(document.querySelectorAll(s));
out.meta = { href: location.href, title: document.title, readyState: document.readyState };
out.iframeCount = q('iframe').length;
out.antTableCount = q('.ant-table').length;
out.pagination = q('.ant-pagination-item').map(e => e.innerText.trim());
out.activePage = (document.querySelector('.ant-pagination-item-active') || {}).innerText || null;
out.paginationTotal = (document.querySelector('.ant-pagination-total-text') || {}).innerText || null;
out.tabs = q('.ant-tabs-tab').map(e => e.innerText.trim());
out.radioGroups = q('.ant-radio-group').map(g => g.innerText.replace(/\s+/g, ' ').trim());
out.selects = q('.ant-select').map(s => s.innerText.replace(/\s+/g, ' ').trim());
out.dateInputs = q('.ant-picker input').map(i => i.value);
out.buttons = q('button').map(b => b.innerText.trim()).filter(Boolean).slice(0, 50);
out.headers = q('.ant-table-thead th').map(th => th.innerText.replace(/\s+/g, ' ').trim()).filter(Boolean);
const rows = q('.ant-table-tbody tr');
out.renderedRowCount = rows.length;
out.renderedRows = rows.map(tr =>
Array.from(tr.querySelectorAll('td')).map(td => (td.innerText || '').replace(/\s+/g, ' ').trim()));
// 从 React fiber 取完整 dataSource(绕开虚拟滚动只渲染部分行的问题)
let ds = null, dsPath = null;
const anchor = rows[0] || document.querySelector('.ant-table');
const fk = anchor && Object.keys(anchor).find(k =>
k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
out.reactFiberFound = !!fk;
if (fk) {
let f = anchor[fk], d = 0;
while (f && d < 80) {
const p = f.memoizedProps;
if (p && Array.isArray(p.dataSource) && p.dataSource.length) {
ds = p.dataSource; dsPath = 'fiber depth=' + d; break;
}
f = f.return; d++;
}
}
const plain = (o, depth) => {
if (o === null || o === undefined) return null;
const t = typeof o;
if (t === 'string' || t === 'number' || t === 'boolean') return o;
if (t !== 'object') return String(o);
if (Array.isArray(o)) return depth > 5 ? '[array]' : o.slice(0, 300).map(x => plain(x, depth + 1));
if (o.$$typeof) return '[react-element]';
if (depth > 5) return '[object]';
const r = {};
for (const k of Object.keys(o)) { try { r[k] = plain(o[k], depth + 1); } catch { r[k] = '[err]'; } }
return r;
};
out.dataSource = { path: dsPath, length: ds ? ds.length : 0, keys: ds ? Object.keys(ds[0] || {}) : [] };
if (ds) out._dataSourceFull = plain(ds, 0);
const main = document.querySelector('.ant-layout-content') || document.body;
out._mainText = (main.innerText || '').slice(0, 30000);
return out;
} catch (e) { return { __error: String((e && e.stack) || e) }; }
})()`;
// ---------------- 主流程 ----------------
let port, wsPath;
try {
const raw = fs.readFileSync(path.join(USER_DATA_DIR, 'DevToolsActivePort'), 'utf8');
const lines = raw.split('\n').map(s => s.trim()).filter(Boolean);
port = parseInt(lines[0], 10);
wsPath = lines[1];
if (!port || !wsPath) throw new Error('bad DevToolsActivePort: ' + JSON.stringify(lines));
} catch (e) {
say('[cdp] FATAL cannot read DevToolsActivePort: ' + e.message);
say('[cdp] -> 请在 Chrome 打开 chrome://inspect/#remote-debugging 并启用远程调试');
process.exit(3);
}
const wsUrl = 'ws://127.0.0.1:' + port + wsPath;
say('[cdp] endpoint = ' + wsUrl);
const cdp = await Cdp.attach(wsUrl);
say('[cdp] browser ws connected');
const { targetInfos } = await cdp.send('Target.getTargets');
writeJson('targets.json', targetInfos);
say('[cdp] targets = ' + targetInfos.length);
for (const t of targetInfos) say(' - ' + t.type + ' | ' + t.title + ' | ' + t.url);
let target = targetInfos.find(t => t.type === 'page' && /seller\.goofish\.com/.test(t.url));
if (!target) target = targetInfos.find(t => t.type === 'page' && /goofish\.com/.test(t.url));
if (!target) { say('[cdp] NO goofish page target found (请确认该标签页已打开)'); cdp.close(); process.exit(2); }
say('[cdp] selected = ' + target.title + ' | ' + target.url);
const { sessionId } = await cdp.send('Target.attachToTarget', { targetId: target.targetId, flatten: true });
say('[cdp] sessionId = ' + sessionId);
const res = await cdp.evalExpr(PROBE, sessionId);
if (!res || res.__error) {
say('[cdp] PROBE ERROR: ' + (res && res.__error));
writeJson('probe-error.json', res || null);
cdp.close();
process.exit(4);
}
const { _dataSourceFull, _mainText, ...rest } = res;
writeJson('probe.json', rest);
if (_dataSourceFull) writeJson('datasource.json', _dataSourceFull);
if (_mainText) writeText('maintext.txt', _mainText);
say('[cdp] ---------- probe summary ----------');
say(' href = ' + (rest.meta && rest.meta.href));
say(' tabs = ' + JSON.stringify(rest.tabs));
say(' dateInputs = ' + JSON.stringify(rest.dateInputs));
say(' radioGroups = ' + JSON.stringify(rest.radioGroups));
say(' selects = ' + JSON.stringify(rest.selects));
say(' pagination = ' + JSON.stringify(rest.pagination) + ' active=' + rest.activePage + ' total=' + rest.paginationTotal);
say(' headers = ' + JSON.stringify(rest.headers));
say(' rows = rendered ' + rest.renderedRowCount + ' / dataSource ' + JSON.stringify(rest.dataSource));
say(' buttons = ' + JSON.stringify(rest.buttons));
say(' files = probe.json' + (_dataSourceFull ? ' + datasource.json' : '') + (_mainText ? ' + maintext.txt' : ''));
const head = (rest.renderedRows || []).slice(0, 3);
say(' sampleRows = ' + JSON.stringify(head));
cdp.close();
process.exit(0);