Files
wr.do/lib/geo.ts
2025-05-27 10:50:07 +08:00

151 lines
4.0 KiB
TypeScript

import { userAgent } from "next/server";
import { Geo, geolocation, ipAddress } from "@vercel/functions";
import { NextAuthRequest } from "next-auth/lib";
import UAParser from "ua-parser-js";
interface GeoLocation extends Geo {
ip?: string;
}
const isVercel = process.env.VERCEL;
export async function getGeolocation(
req: NextAuthRequest,
ip: string,
): Promise<GeoLocation | null> {
// console.log("[Runtime Env]", isVercel ? "Vercel" : "Other");
if (isVercel) {
return geolocation(req);
} else {
return await getClientGeolocationWithIpApi(ip);
}
}
export function getUserAgent(req: NextAuthRequest) {
if (isVercel) {
return userAgent(req);
} else {
const headers = req.headers;
const userAgent = headers.get("user-agent") || "";
const parser = new UAParser(userAgent);
return {
browser: parser.getBrowser(),
device: parser.getDevice(),
os: parser.getOS(),
engine: parser.getEngine(),
cpu: parser.getCPU(),
isBot: false,
};
}
}
export async function getClientGeolocation(
req,
ip,
): Promise<GeoLocation | null> {
// const new_headers = new Headers();
// new_headers.set("X-Forwarded-For", ip);
// new_headers.set("User-Agent", req.headers.get("user-agent") || "");
const response = await fetch(`https://ip.wr.do/api?ip=${ip}`);
if (!response.ok) return null;
return await response.json();
}
export async function getClientGeolocationWithIpApi(ip: string) {
const response = await fetch(`http://ip-api.com/json/${ip}`);
if (!response.ok) return null;
const res = await response.json();
// {
// "query": "154.64.226.29",
// "status": "success",
// "continent": "North America",
// "continentCode": "NA",
// "country": "United States",
// "countryCode": "US",
// "region": "CA",
// "regionName": "California",
// "city": "Los Angeles",
// "district": "",
// "zip": "90009",
// "lat": 34.0549,
// "lon": -118.243,
// "timezone": "America/Los_Angeles",
// "offset": -25200,
// "currency": "USD",
// "isp": "Cogent Communications",
// "org": "NetLab Global",
// "as": "AS51847 Nearoute Limited",
// "asname": "NEAROUTE",
// "mobile": true,
// "proxy": true,
// "hosting": false
// }
return {
ip: res.query,
country: res.countryCode,
countryName: res.country,
region: res.region,
regionName: res.regionName,
city: res.city,
latitude: res.lat.toString(),
longitude: res.lon.toString(),
timezone: res.timezone,
};
}
export function extractRealIP(headers: Headers): string {
const ipHeaders = [
"X-Forwarded-For",
"X-Real-IP",
"CF-Connecting-IP",
"X-Client-IP",
"X-Cluster-Client-IP",
];
for (const header of ipHeaders) {
const value = headers.get(header);
if (value) {
const ip = value.split(",")[0].trim();
if (isValidIP(ip)) {
return ip;
}
}
}
return "::1";
}
function isValidIP(ip: string): boolean {
// IPv4 正则
const ipv4Regex =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
// IPv6 正则(简化版)
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
}
export async function getIpInfo(req) {
const headers = req.headers;
const ip = isVercel ? ipAddress(req) : extractRealIP(headers);
const ua = getUserAgent(req);
const geo = await getGeolocation(req, ip || "::1");
const userLanguage =
req.headers.get("accept-language")?.split(",")[0] || "en-US";
return {
referer: headers.get("referer") || "(None)",
ip: isVercel ? ip : geo?.ip,
city: geo?.city || "",
region: geo?.region || "",
country: geo?.country || "",
latitude: geo?.latitude || "",
longitude: geo?.longitude || "",
flag: geo?.flag,
lang: userLanguage,
device: ua.device.model || "Unknown",
browser: ua.browser.name || "Unknown",
};
}