185 lines
6.1 KiB
C#
185 lines
6.1 KiB
C#
using AnotherReplayReader.PluginSystem;
|
||
using AnotherReplayReader.Utils;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Security.Cryptography;
|
||
using System.Text.Json;
|
||
using System.Threading.Tasks;
|
||
using static System.Text.Json.JsonSerializer;
|
||
|
||
namespace AnotherReplayReader
|
||
{
|
||
internal class PlayerIdentity
|
||
{
|
||
private class NetworkSerializer : IGenericCallable
|
||
{
|
||
public TResult Invoke<TArg, TResult>(TArg arg) => throw new NotImplementedException();
|
||
|
||
public Task<TResult?> InvokeAsync<TArg, TResult>(TArg arg)
|
||
{
|
||
var url = arg as string ?? throw new NotImplementedException();
|
||
return Network.HttpGetJson<TResult>(url);
|
||
}
|
||
}
|
||
|
||
private const string StoredKey = "pt";
|
||
private const string IvKey = "jw";
|
||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||
{
|
||
PropertyNameCaseInsensitive = true
|
||
};
|
||
private readonly object _lock = new();
|
||
private readonly Cache _cache;
|
||
private readonly TaskCompletionSource<IPlayerIdentityService> _serviceSource = new();
|
||
private IReadOnlyDictionary<uint, string>? _list;
|
||
|
||
public bool IsUsable => Lock.Run(_lock, () => _list is not null);
|
||
|
||
public PlayerIdentity(Cache cache)
|
||
{
|
||
_cache = cache;
|
||
Task.Run(FetchLocal);
|
||
Fetch();
|
||
}
|
||
|
||
public void LoadPlugin(IPlugin plugin)
|
||
{
|
||
var service = plugin.CreatePlayerIdentityService(Network.UrlEncode, new NetworkSerializer());
|
||
_serviceSource.TrySetResult(service);
|
||
}
|
||
|
||
public Task Fetch()
|
||
{
|
||
return Task.Run(async () =>
|
||
{
|
||
try
|
||
{
|
||
if (await Auth.Id is null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var service = await _serviceSource.Task.ConfigureAwait(false);
|
||
var list = await service.Fetch<IpAndPlayer>().ConfigureAwait(false);
|
||
var converted = list?.ToDictionary(x => x.Ip, x => x.Id);
|
||
lock (_lock)
|
||
{
|
||
_list = converted;
|
||
}
|
||
|
||
if (list is null || await Auth.IdAsKey() is not { } encryptKey)
|
||
{
|
||
_cache.Set<string?>(StoredKey, null);
|
||
return;
|
||
}
|
||
|
||
using var memory = new MemoryStream();
|
||
using var aes = Aes.Create();
|
||
using (var encryptor = aes.CreateEncryptor(encryptKey, aes.IV))
|
||
using (var decryptorStream = new CryptoStream(memory, encryptor, CryptoStreamMode.Write))
|
||
{
|
||
await SerializeAsync(decryptorStream, list, _jsonOptions).ConfigureAwait(false);
|
||
}
|
||
memory.Flush();
|
||
_cache.SetValues((StoredKey, Convert.ToBase64String(memory.ToArray())), (IvKey, Convert.ToBase64String(aes.IV)));
|
||
}
|
||
catch { }
|
||
});
|
||
}
|
||
|
||
public async Task<bool> UpdateIpTable(uint ip, string idText)
|
||
{
|
||
var service = await _serviceSource.Task.ConfigureAwait(false);
|
||
return await service.UpdateIpTable(ip, idText).ConfigureAwait(false);
|
||
}
|
||
|
||
public List<IpAndPlayer> AsSortedList()
|
||
{
|
||
using var locker = new Lock(_lock);
|
||
|
||
return _list?
|
||
.Select((kv) => new IpAndPlayer { Ip = kv.Key, Id = kv.Value })
|
||
.OrderBy(x => x.Ip)
|
||
.ToList() ?? new(0);
|
||
}
|
||
|
||
public string? GetRealName(uint ip)
|
||
{
|
||
using var locker = new Lock(_lock);
|
||
if (_list is null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (ip is 0 || !_list.TryGetValue(ip, out var name))
|
||
{
|
||
return string.Empty;
|
||
}
|
||
|
||
return name;
|
||
}
|
||
|
||
public string FormatRealName(uint ip)
|
||
{
|
||
var name = GetRealName(ip);
|
||
if (name is null)
|
||
{
|
||
return string.Empty;
|
||
}
|
||
if (string.IsNullOrEmpty(name))
|
||
{
|
||
name = IpAndPlayer.SimpleIPToString(ip);
|
||
}
|
||
return $"({name})";
|
||
}
|
||
|
||
public string? QueryRealNameAndIP(uint ip)
|
||
{
|
||
var name = GetRealName(ip);
|
||
if (name is null)
|
||
{
|
||
return null;
|
||
}
|
||
var ipText = IpAndPlayer.SimpleIPToString(ip);
|
||
if (string.IsNullOrEmpty(name))
|
||
{
|
||
return ipText;
|
||
}
|
||
return $"{name},{ipText}";
|
||
}
|
||
|
||
private async Task FetchLocal()
|
||
{
|
||
try
|
||
{
|
||
await _cache.Initialization;
|
||
var stored = _cache.GetOrDefault(StoredKey, string.Empty);
|
||
var iv = Convert.FromBase64String(_cache.GetOrDefault(IvKey, string.Empty));
|
||
if (string.IsNullOrWhiteSpace(stored) || await Auth.IdAsKey() is not { } key)
|
||
{
|
||
return;
|
||
}
|
||
|
||
using var aes = Aes.Create();
|
||
using var decryptor = aes.CreateDecryptor(key, iv);
|
||
using var memory = new MemoryStream(Convert.FromBase64String(stored));
|
||
using var decryptorStream = new CryptoStream(memory, decryptor, CryptoStreamMode.Read);
|
||
var cachedTable = await DeserializeAsync<List<IpAndPlayer>>(decryptorStream, _jsonOptions).ConfigureAwait(false);
|
||
if (cachedTable is null)
|
||
{
|
||
return;
|
||
}
|
||
var converted = cachedTable.ToDictionary(x => x.Ip, x => x.Id);
|
||
converted[0] = "【没有网络连接,正在使用上次保存的数据】";
|
||
lock (_lock)
|
||
{
|
||
_list ??= converted;
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
}
|