import http from "node:http"; import { spawn } from "node:child_process"; import { readFileSync } from "node:fs"; import path from "node:path"; function readEnvKey(path, key) { const txt = readFileSync(path, "utf8"); for (const line of txt.split(/\r?\n/)) { if (line.trim().startsWith(`${key}=`)) return line.split("=").slice(1).join("=").trim(); } return ""; } function httpJson(url) { return new Promise((resolve, reject) => { http.get(url, (res) => { let data = ""; res.on("data", (chunk) => (data += chunk)); res.on("end", () => { try { resolve(JSON.parse(data)); } catch (e) { reject(e); } }); }).on("error", reject); }); } class Cdp { constructor(wsUrl) { this.ws = new WebSocket(wsUrl); this.seq = 1; this.pending = new Map(); this.ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.id && this.pending.has(msg.id)) { const { resolve, reject } = this.pending.get(msg.id); this.pending.delete(msg.id); if (msg.error) reject(new Error(JSON.stringify(msg.error))); else resolve(msg.result); } }; } async open() { await new Promise((resolve, reject) => { this.ws.onopen = resolve; this.ws.onerror = reject; }); } call(method, params = {}) { const id = this.seq++; this.ws.send(JSON.stringify({ id, method, params })); return new Promise((resolve, reject) => this.pending.set(id, { resolve, reject })); } close() { this.ws.close(); } } async function wait(ms) { await new Promise((resolve) => setTimeout(resolve, ms)); } async function main() { const root = path.resolve(new URL("..", import.meta.url).pathname, ".."); const envPath = process.env.TRAVEL_KG_ENV_PATH || path.join(root, ".env"); const key = readEnvKey(envPath, "AMAP_JS_KEY"); const security = readEnvKey(envPath, "AMAP_SECURITY_JSCODE"); if (!key || !security) throw new Error("missing AMap JS key/security"); const chrome = spawn( "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", [ "--headless=new", "--disable-gpu", "--no-first-run", "--no-default-browser-check", "--remote-debugging-port=9229", "--user-data-dir=/tmp/znkg-amap-cdp-profile", "http://localhost:8102/admin/plaza/graph", ], { stdio: "ignore" }, ); try { let pages = null; for (let i = 0; i < 30; i += 1) { try { pages = await httpJson("http://127.0.0.1:9229/json"); if (pages?.[0]?.webSocketDebuggerUrl) break; } catch {} await wait(300); } if (!pages?.[0]?.webSocketDebuggerUrl) throw new Error("Chrome CDP not ready"); const cdp = new Cdp(pages[0].webSocketDebuggerUrl); await cdp.open(); await cdp.call("Runtime.enable"); await wait(800); const expr = ` (async () => { window._AMapSecurityConfig = { securityJsCode: ${JSON.stringify(security)} }; await new Promise((resolve, reject) => { if (window.AMap) return resolve(); const s = document.createElement('script'); s.src = 'https://webapi.amap.com/maps?v=2.0&key=${encodeURIComponent(key)}&plugin=AMap.PlaceSearch,AMap.Driving'; s.onload = resolve; s.onerror = () => reject(new Error('amap script load failed')); document.head.appendChild(s); }); const place = await new Promise((resolve, reject) => { const ps = new AMap.PlaceSearch({ city: '安顺市', pageSize: 3, extensions: 'all' }); ps.search('黄果树风景名胜区', (status, result) => { if (status === 'complete') resolve(result.poiList?.pois || []); else reject(new Error(status + ':' + JSON.stringify(result))); }); }); const first = place[0] || {}; const driving = await new Promise((resolve, reject) => { const d = new AMap.Driving(); d.search([106.630153,26.647661], [first.location.lng, first.location.lat], (status, result) => { if (status === 'complete') resolve(result.routes?.[0] || {}); else reject(new Error(status + ':' + JSON.stringify(result))); }); }); return { placeCount: place.length, firstName: first.name, firstId: first.id, firstLocation: first.location ? [first.location.lng, first.location.lat].join(',') : '', firstPhotos: (first.photos || []).length, distance: driving.distance, time: driving.time, }; })() `; const out = await cdp.call("Runtime.evaluate", { expression: expr, awaitPromise: true, returnByValue: true }); console.log(JSON.stringify(out, null, 2)); cdp.close(); } finally { chrome.kill(); } } main().catch((err) => { console.error(err); process.exit(1); });