ci/woodpecker/push/main-amd64-build Pipeline was successful
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.
74 lines
3.1 KiB
C#
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);
|
|
}
|