kopia lustrzana https://github.com/FacilMap/facilmap
104 wiersze
2.9 KiB
TypeScript
104 wiersze
2.9 KiB
TypeScript
import { distanceToDegreesLat, distanceToDegreesLon } from "./utils/geo.js";
|
|
import md5 from "md5-file";
|
|
import { schedule } from "node-cron";
|
|
import { open, Reader, Response } from "maxmind";
|
|
import { createWriteStream } from "fs";
|
|
import { rename } from "node:fs/promises";
|
|
import https from "https";
|
|
import zlib from "zlib";
|
|
import config from "./config.js";
|
|
import { IncomingMessage } from "http";
|
|
import { Bbox } from "facilmap-types";
|
|
import { fileURLToPath } from "url";
|
|
import { fileExists } from "./utils/utils";
|
|
import findCacheDir from "find-cache-dir";
|
|
|
|
const geoliteUrl = "https://updates.maxmind.com/geoip/databases/GeoLite2-City/update?db_md5=";
|
|
const cacheDir = findCacheDir({ name: "facilmap-server", create: true })
|
|
|| findCacheDir({ name: "facilmap-server", create: true, cwd: fileURLToPath(new URL('./', import.meta['url'])) })!;
|
|
const fname = `${cacheDir}/GeoLite2-City.mmdb`;
|
|
const tmpfname = `${fname}.tmp`;
|
|
|
|
let currentMd5: string | null = null;
|
|
let db: Reader<Response> | null = null;
|
|
|
|
if(config.maxmindUserId && config.maxmindLicenseKey) {
|
|
schedule("0 3 * * *", download);
|
|
|
|
load().catch((err) => {
|
|
console.log("Error loading maxmind database", err.stack || err);
|
|
});
|
|
download().catch((err) => {
|
|
console.log("Error downloading maxmind database", err.stack || err);
|
|
});
|
|
}
|
|
|
|
async function load() {
|
|
if(await fileExists(fname))
|
|
db = await open(fname);
|
|
else
|
|
db = null;
|
|
|
|
|
|
}
|
|
|
|
async function download() {
|
|
console.log("Downloading maxmind database");
|
|
|
|
if(!currentMd5) {
|
|
if(await fileExists(fname))
|
|
currentMd5 = await md5(fname);
|
|
}
|
|
|
|
const res = await new Promise<IncomingMessage>((resolve, reject) => {
|
|
https.get(geoliteUrl + (currentMd5 || ""), {
|
|
headers: {
|
|
Authorization: `Basic ${Buffer.from(config.maxmindUserId + ':' + config.maxmindLicenseKey).toString('base64')}`
|
|
}
|
|
}, resolve).on("error", reject);
|
|
});
|
|
|
|
if(res.statusCode == 304) {
|
|
console.log("Maxmind database is up to date, no update needed.");
|
|
return;
|
|
} else if(res.statusCode != 200)
|
|
throw new Error(`Unexpected status code ${res.statusCode} when downloading maxmind database.`);
|
|
|
|
const gunzip = zlib.createGunzip();
|
|
res.pipe(gunzip);
|
|
|
|
const file = createWriteStream(tmpfname);
|
|
gunzip.pipe(file);
|
|
|
|
await new Promise((resolve, reject) => {
|
|
file.on("finish", resolve);
|
|
file.on("error", reject);
|
|
});
|
|
|
|
await rename(tmpfname, fname);
|
|
currentMd5 = await md5(fname);
|
|
|
|
await load();
|
|
|
|
console.log("Maxmind database downloaded");
|
|
}
|
|
|
|
export async function geoipLookup(ip: string): Promise<Bbox | undefined> {
|
|
if(!db)
|
|
return undefined;
|
|
|
|
const ret = db.get(ip);
|
|
|
|
if(ret && 'location' in ret && ret.location) {
|
|
const distLat = distanceToDegreesLat(ret.location.accuracy_radius);
|
|
const distLon = distanceToDegreesLon(ret.location.accuracy_radius, ret.location.latitude);
|
|
|
|
return {
|
|
top: ret.location.latitude + distLat,
|
|
right: ret.location.longitude + distLon,
|
|
bottom: ret.location.latitude - distLat,
|
|
left: ret.location.longitude - distLon
|
|
};
|
|
}
|
|
}
|