148 lines
4.8 KiB
JavaScript
148 lines
4.8 KiB
JavaScript
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);
|
|
});
|