Spaces:
Build error
Build error
File size: 3,289 Bytes
2e3c217 66db317 2e3c217 12ba1bc 2e3c217 66db317 2e3c217 12ba1bc 2e3c217 12ba1bc 2e3c217 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | import { container, singleton } from 'tsyringe';
import fsp from 'fs/promises';
import { CityResponse, Reader } from 'maxmind';
import { AsyncService, AutoCastable, Prop, runOnce } from 'civkit';
import { GlobalLogger } from './logger';
import path from 'path';
import { Threaded } from './threaded';
export enum GEOIP_SUPPORTED_LANGUAGES {
EN = 'en',
ZH_CN = 'zh-CN',
JA = 'ja',
DE = 'de',
FR = 'fr',
ES = 'es',
PT_BR = 'pt-BR',
RU = 'ru',
}
export class GeoIPInfo extends AutoCastable {
@Prop()
code?: string;
@Prop()
name?: string;
}
export class GeoIPCountryInfo extends GeoIPInfo {
@Prop()
eu?: boolean;
}
export class GeoIPCityResponse extends AutoCastable {
@Prop()
continent?: GeoIPInfo;
@Prop()
country?: GeoIPCountryInfo;
@Prop({
arrayOf: GeoIPInfo
})
subdivisions?: GeoIPInfo[];
@Prop()
city?: string;
@Prop({
arrayOf: Number
})
coordinates?: [number, number, number];
@Prop()
timezone?: string;
}
@singleton()
export class GeoIPService extends AsyncService {
logger = this.globalLogger.child({ service: this.constructor.name });
mmdbCity!: Reader<CityResponse>;
constructor(
protected globalLogger: GlobalLogger,
) {
super(...arguments);
}
override async init() {
await this.dependencyReady();
this.emit('ready');
}
@runOnce()
async _lazyload() {
const mmdpPath = path.resolve(__dirname, '..', '..', 'licensed', 'GeoLite2-City.mmdb');
const dbBuff = await fsp.readFile(mmdpPath, { flag: 'r', encoding: null });
this.mmdbCity = new Reader<CityResponse>(dbBuff);
this.logger.info(`Loaded GeoIP database, ${dbBuff.byteLength} bytes`);
}
@Threaded()
async lookupCity(ip: string, lang: GEOIP_SUPPORTED_LANGUAGES = GEOIP_SUPPORTED_LANGUAGES.EN) {
await this._lazyload();
const r = this.mmdbCity.get(ip);
if (!r) {
return undefined;
}
return GeoIPCityResponse.from({
continent: r.continent ? {
code: r.continent?.code,
name: r.continent?.names?.[lang] || r.continent?.names?.en,
} : undefined,
country: r.country ? {
code: r.country?.iso_code,
name: r.country?.names?.[lang] || r.country?.names.en,
eu: r.country?.is_in_european_union,
} : undefined,
city: r.city?.names?.[lang] || r.city?.names?.en,
subdivisions: r.subdivisions?.map((x) => ({
code: x.iso_code,
name: x.names?.[lang] || x.names?.en,
})),
coordinates: r.location ? [
r.location.latitude, r.location.longitude, r.location.accuracy_radius
] : undefined,
timezone: r.location?.time_zone,
});
}
@Threaded()
async lookupCities(ips: string[], lang: GEOIP_SUPPORTED_LANGUAGES = GEOIP_SUPPORTED_LANGUAGES.EN) {
const r = (await Promise.all(ips.map((ip) => this.lookupCity(ip, lang)))).filter(Boolean) as GeoIPCityResponse[];
return r;
}
}
const instance = container.resolve(GeoIPService);
export default instance;
|