Files
MoShiro/backend/src/MoShiro.Backend/HttpGeoLookup.cs
T
justme 2e146d4da7
ci/woodpecker/push/main-amd64-build Pipeline was successful
refactor!: rename the project from Monitor to MoShiro [CI]
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
2026-10-07 02:03:25 +08:00

74 lines
3.1 KiB
C#

using System.Text.Json;
using MoShiro.Backend.Application;
namespace MoShiro.Backend;
/// <summary>
/// 用 IP 归属查询服务(<c>GET {地址}/lookup?ip=…</c>)查国家和运营商。
/// 失败(超时、网络不通、服务返回错误)一律返回 null,不往外抛:展示信息没有也不影响别的功能。
/// </summary>
public sealed class HttpGeoLookup : IGeoLookup, IDisposable
{
private readonly HttpClient _http;
public HttpGeoLookup(string baseUrl)
{
_http = new HttpClient { BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"), Timeout = TimeSpan.FromSeconds(6) };
_http.DefaultRequestHeaders.UserAgent.ParseAdd("MoShiro");
}
public async ValueTask<GeoInfo?> LookupAsync(string host, CancellationToken ct)
{
try
{
using HttpResponseMessage response = await _http.GetAsync("lookup?ip=" + Uri.EscapeDataString(host), ct);
if (!response.IsSuccessStatusCode)
{
return null;
}
using JsonDocument doc = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync(ct), cancellationToken: ct);
return Parse(doc.RootElement);
}
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or JsonException or InvalidOperationException)
{
return null;
}
}
/// <summary>只取国家代码、ASN、组织名;其余字段(城市、经纬度、网段……)一概不读。</summary>
public static GeoInfo? Parse(JsonElement root)
{
if (root.TryGetProperty("found", out JsonElement found) && found.ValueKind == JsonValueKind.False)
{
return null;
}
bool isPrivate = root.TryGetProperty("is_private", out JsonElement priv) && priv.ValueKind == JsonValueKind.True;
string? country = root.TryGetProperty("country_region", out JsonElement region) && region.TryGetProperty("iso_code", out JsonElement code) && code.ValueKind == JsonValueKind.String ? code.GetString() : null;
int? asn = null;
string? organization = null;
if (root.TryGetProperty("network", out JsonElement network))
{
if (network.TryGetProperty("asn", out JsonElement asnValue) && asnValue.TryGetInt32(out int number))
{
asn = number;
}
string? isp = network.TryGetProperty("isp", out JsonElement ispValue) && ispValue.ValueKind == JsonValueKind.String ? ispValue.GetString() : null;
string? org = network.TryGetProperty("organization", out JsonElement orgValue) && orgValue.ValueKind == JsonValueKind.String ? orgValue.GetString() : null;
organization = string.IsNullOrWhiteSpace(isp) ? org : isp;
}
return new GeoInfo(country, asn, organization, isPrivate);
}
public void Dispose() => _http.Dispose();
}
/// <summary>不查(<c>MoShiro__GeoLookupUrl</c> 留空)。</summary>
public sealed class NoGeoLookup : IGeoLookup
{
public ValueTask<GeoInfo?> LookupAsync(string host, CancellationToken ct) => ValueTask.FromResult<GeoInfo?>(null);
}