PlaytimeHistory/Geo.cs

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();
}