Files
openclaw-market/lib/geo/ip-location.ts
richarjiang fa4c458eda init
2026-03-13 11:00:01 +08:00

246 lines
4.3 KiB
TypeScript

import { db } from "@/lib/db";
import { geoCache } from "@/lib/db/schema";
import { eq } from "drizzle-orm";
interface GeoLocation {
latitude: number;
longitude: number;
city: string;
country: string;
countryCode: string;
region: string;
}
interface IpApiResponse {
status: string;
lat: number;
lon: number;
city: string;
country: string;
countryCode: string;
}
const CONTINENT_MAP: Record<string, string> = {
// Asia
CN: "Asia",
JP: "Asia",
KR: "Asia",
IN: "Asia",
ID: "Asia",
TH: "Asia",
VN: "Asia",
PH: "Asia",
MY: "Asia",
SG: "Asia",
TW: "Asia",
HK: "Asia",
BD: "Asia",
PK: "Asia",
LK: "Asia",
NP: "Asia",
MM: "Asia",
KH: "Asia",
LA: "Asia",
MN: "Asia",
KZ: "Asia",
UZ: "Asia",
AE: "Asia",
SA: "Asia",
IL: "Asia",
TR: "Asia",
IQ: "Asia",
IR: "Asia",
QA: "Asia",
KW: "Asia",
BH: "Asia",
OM: "Asia",
JO: "Asia",
LB: "Asia",
AF: "Asia",
// Europe
GB: "Europe",
DE: "Europe",
FR: "Europe",
IT: "Europe",
ES: "Europe",
PT: "Europe",
NL: "Europe",
BE: "Europe",
SE: "Europe",
NO: "Europe",
DK: "Europe",
FI: "Europe",
PL: "Europe",
CZ: "Europe",
AT: "Europe",
CH: "Europe",
IE: "Europe",
RO: "Europe",
HU: "Europe",
GR: "Europe",
UA: "Europe",
RU: "Europe",
BG: "Europe",
HR: "Europe",
SK: "Europe",
SI: "Europe",
RS: "Europe",
LT: "Europe",
LV: "Europe",
EE: "Europe",
IS: "Europe",
LU: "Europe",
MT: "Europe",
CY: "Europe",
AL: "Europe",
MK: "Europe",
BA: "Europe",
ME: "Europe",
MD: "Europe",
BY: "Europe",
// Americas
US: "Americas",
CA: "Americas",
MX: "Americas",
BR: "Americas",
AR: "Americas",
CO: "Americas",
CL: "Americas",
PE: "Americas",
VE: "Americas",
EC: "Americas",
BO: "Americas",
PY: "Americas",
UY: "Americas",
CR: "Americas",
PA: "Americas",
CU: "Americas",
DO: "Americas",
GT: "Americas",
HN: "Americas",
SV: "Americas",
NI: "Americas",
JM: "Americas",
TT: "Americas",
HT: "Americas",
PR: "Americas",
GY: "Americas",
SR: "Americas",
BZ: "Americas",
// Africa
ZA: "Africa",
NG: "Africa",
KE: "Africa",
EG: "Africa",
GH: "Africa",
ET: "Africa",
TZ: "Africa",
MA: "Africa",
DZ: "Africa",
TN: "Africa",
UG: "Africa",
SN: "Africa",
CI: "Africa",
CM: "Africa",
MZ: "Africa",
MG: "Africa",
AO: "Africa",
ZW: "Africa",
RW: "Africa",
LY: "Africa",
SD: "Africa",
CD: "Africa",
ML: "Africa",
BF: "Africa",
NE: "Africa",
MW: "Africa",
ZM: "Africa",
BW: "Africa",
NA: "Africa",
MU: "Africa",
// Oceania
AU: "Oceania",
NZ: "Oceania",
FJ: "Oceania",
PG: "Oceania",
WS: "Oceania",
TO: "Oceania",
VU: "Oceania",
SB: "Oceania",
GU: "Oceania",
NC: "Oceania",
PF: "Oceania",
};
function getRegion(countryCode: string): string {
return CONTINENT_MAP[countryCode] ?? "Unknown";
}
export async function getGeoLocation(
ip: string
): Promise<GeoLocation | null> {
try {
const cached = await db
.select()
.from(geoCache)
.where(eq(geoCache.ip, ip))
.limit(1);
if (cached.length > 0) {
const row = cached[0];
return {
latitude: Number(row.latitude),
longitude: Number(row.longitude),
city: row.city ?? "",
country: row.country ?? "",
countryCode: row.countryCode ?? "",
region: row.region ?? "",
};
}
const response = await fetch(`http://ip-api.com/json/${ip}`, {
signal: AbortSignal.timeout(5000),
});
if (!response.ok) {
return null;
}
const data: IpApiResponse = await response.json();
if (data.status !== "success") {
return null;
}
const region = getRegion(data.countryCode);
const geoData: GeoLocation = {
latitude: data.lat,
longitude: data.lon,
city: data.city,
country: data.country,
countryCode: data.countryCode,
region,
};
await db.insert(geoCache).values({
ip,
latitude: String(geoData.latitude),
longitude: String(geoData.longitude),
city: geoData.city,
country: geoData.country,
countryCode: geoData.countryCode,
region: geoData.region,
});
return geoData;
} catch (error) {
console.error("Failed to get geo location for IP:", ip, error);
return null;
}
}