53 lines
2.1 KiB
C#
53 lines
2.1 KiB
C#
using System.Net;
|
|
using MaxMind.Db;
|
|
|
|
namespace PlaytimeHistory;
|
|
|
|
// Country and city for an IP, from DB-IP's City Lite database (MaxMind DB format, CC BY 4.0:
|
|
// "IP Geolocation by DB-IP", https://db-ip.com). Only the result is kept; the IP never is.
|
|
public sealed class Geo : IDisposable
|
|
{
|
|
public sealed record Location(string? CountryIso, string? CountryName, string? City);
|
|
|
|
private readonly Reader _reader;
|
|
|
|
private Geo(Reader reader) => _reader = reader;
|
|
|
|
public string DatabaseType => _reader.Metadata.DatabaseType;
|
|
public DateTime BuildDate => _reader.Metadata.BuildDate;
|
|
|
|
// Null when the file isn't there.
|
|
public static Geo? Open(string path) =>
|
|
File.Exists(path) ? new Geo(new Reader(path, FileAccessMode.MemoryMapped)) : null;
|
|
|
|
// `address` is "ip" or "ip:port", as CCSPlayerController.IpAddress gives it.
|
|
public Location? Lookup(string? address)
|
|
{
|
|
if (string.IsNullOrEmpty(address))
|
|
return null;
|
|
var colon = address.LastIndexOf(':');
|
|
var host = colon > 0 && address.IndexOf(':') == colon ? address[..colon] : address;
|
|
if (!IPAddress.TryParse(host, out var ip))
|
|
return null;
|
|
|
|
var data = _reader.Find<Dictionary<string, object>>(ip);
|
|
if (data == null)
|
|
return null;
|
|
|
|
string? iso = null, country = null, city = null;
|
|
if (data.TryGetValue("country", out var c) && c is Dictionary<string, object> countryData)
|
|
{
|
|
iso = countryData.TryGetValue("iso_code", out var code) ? code as string : null;
|
|
country = EnglishName(countryData);
|
|
}
|
|
if (data.TryGetValue("city", out var ct) && ct is Dictionary<string, object> cityData)
|
|
city = EnglishName(cityData);
|
|
return iso == null && city == null ? null : new Location(iso, country, city);
|
|
}
|
|
|
|
private static string? EnglishName(Dictionary<string, object> data) =>
|
|
data.TryGetValue("names", out var n) && n is Dictionary<string, object> names &&
|
|
names.TryGetValue("en", out var en) ? en as string : null;
|
|
|
|
public void Dispose() => _reader.Dispose();
|
|
}
|