v2 (WPF)
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using CliWrap;
|
||||
using CliWrap.Buffered;
|
||||
using LeagueAPI.Models.Challenges;
|
||||
using LeagueAPI.Models.ChampSelect;
|
||||
using LeagueAPI.Models.DDragon;
|
||||
using LeagueAPI.Models.DDragon.Champions;
|
||||
using LeagueAPI.Utils;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace LeagueAPI;
|
||||
|
||||
@@ -23,7 +19,6 @@ public class APIClient : IDisposable
|
||||
private bool _isDisposed;
|
||||
|
||||
private readonly LcuHttpClient _lcuHttpClient;
|
||||
private readonly HttpClient _dDragonHttpClient;
|
||||
|
||||
private string? _latestVersion;
|
||||
|
||||
@@ -32,7 +27,6 @@ public class APIClient : IDisposable
|
||||
public APIClient()
|
||||
{
|
||||
_lcuHttpClient = new(new LcuHttpClientHandler());
|
||||
_dDragonHttpClient = new HttpClient() { BaseAddress = new Uri(DDRAGON_BASE_URL) };
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, LolChallengesUIChallenge>> GetAllChallengesAsync()
|
||||
@@ -51,23 +45,23 @@ public class APIClient : IDisposable
|
||||
return allRandomAllChampions.CompletedIds ?? [];
|
||||
}
|
||||
|
||||
public async Task<int[]> GetSelectableChampionIdsAsync()
|
||||
public async Task<(IEnumerable<int> teamChampions, IEnumerable<int> benchChampions)> GetSelectableChampionIdsAsync()
|
||||
{
|
||||
ChampSelectSession? session = await _lcuHttpClient.GetContentAsync<ChampSelectSession>("/lol-champ-select/v1/session");
|
||||
return GetSelectableChampionIds(session);
|
||||
}
|
||||
|
||||
public int[] GetSelectableChampionIds(ChampSelectSession? session)
|
||||
public static (IEnumerable<int> teamChampions, IEnumerable<int> benchChampions) GetSelectableChampionIds(ChampSelectSession? session)
|
||||
{
|
||||
if (session is null || !session.BenchEnabled)
|
||||
if (session is null || !session.BenchEnabled || string.IsNullOrEmpty(session.Id))
|
||||
{
|
||||
return [];
|
||||
return ([], []);
|
||||
}
|
||||
|
||||
IEnumerable<int> benchChampions = session.BenchChampions.Select(b => b.ChampionId);
|
||||
IEnumerable<int> teamChampions = session.MyTeam.Select(c => c.ChampionId);
|
||||
|
||||
return [.. benchChampions, .. teamChampions];
|
||||
return (teamChampions, benchChampions);
|
||||
}
|
||||
|
||||
private record struct LobbyChangeGame([property: JsonPropertyName("queueId")] int QueueId);
|
||||
@@ -87,20 +81,12 @@ public class APIClient : IDisposable
|
||||
}
|
||||
|
||||
#region DDragon
|
||||
private async Task<string> DDragonGetAsync(string path)
|
||||
private static async Task<string> DDragonGetAsync(string path)
|
||||
{
|
||||
HttpResponseMessage response = await _dDragonHttpClient.GetAsync(path);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Command cmd = Cli.Wrap("curl")
|
||||
.WithArguments($"{DDRAGON_BASE_URL}{path}");
|
||||
BufferedCommandResult result = await cmd.ExecuteBufferedAsync();
|
||||
return result.IsSuccess ? result.StandardOutput : throw new Exception($"Failed to fetch from Datadragon: {path}");
|
||||
}
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
return await CachingHttpClient.GetStringAsync($"{DDRAGON_BASE_URL}{path}");
|
||||
}
|
||||
|
||||
private async Task<T?> DDragonGetAsync<T>(string path)
|
||||
private static async Task<T?> DDragonGetAsync<T>(string path)
|
||||
{
|
||||
string json = await DDragonGetAsync(path);
|
||||
return JsonSerializer.Deserialize<T>(json);
|
||||
@@ -159,7 +145,6 @@ public class APIClient : IDisposable
|
||||
if (disposing)
|
||||
{
|
||||
_lcuHttpClient?.Dispose();
|
||||
_dDragonHttpClient?.Dispose();
|
||||
}
|
||||
_championResponseCache?.Clear();
|
||||
|
||||
|
||||
109
LeagueAPI/ARAM/ARAMBalanceService.cs
Normal file
109
LeagueAPI/ARAM/ARAMBalanceService.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System.Text.Json;
|
||||
using CliWrap;
|
||||
using CliWrap.Buffered;
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace LeagueAPI.ARAM;
|
||||
|
||||
public record class WikiChampion(int Id, WikiChampionStats Stats);
|
||||
public record class WikiChampionStats(Dictionary<string, double> Aram);
|
||||
|
||||
public class ARAMBalanceLookup : Dictionary<int, Dictionary<string, double>> { }
|
||||
|
||||
public class ARAMBalanceService
|
||||
{
|
||||
private static readonly string URL = "https://wiki.leagueoflegends.com/en-us/rest.php/v1/page/Module:ChampionData%2Fdata";
|
||||
|
||||
private ARAMBalanceLookup _champions = [];
|
||||
|
||||
static ARAMBalanceService()
|
||||
{
|
||||
UserData.RegisterType<WikiChampion>();
|
||||
UserData.RegisterType<WikiChampionStats>();
|
||||
}
|
||||
|
||||
public async Task EnsureIsLoadedAsync()
|
||||
{
|
||||
if (_champions is not { Count: > 0 })
|
||||
{
|
||||
await ReloadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReloadAsync(bool force = false)
|
||||
{
|
||||
ARAMBalanceLookup? champions = ResourceService.GetARAMBalanceLookup();
|
||||
if (!force && champions is { Count: > 0 })
|
||||
{
|
||||
_champions = champions;
|
||||
return;
|
||||
}
|
||||
|
||||
Command curl = Cli.Wrap("curl")
|
||||
.WithArguments(URL);
|
||||
BufferedCommandResult result = await curl.ExecuteBufferedAsync();
|
||||
|
||||
string json = result.StandardOutput;
|
||||
JsonDocument jsonDocument = JsonDocument.Parse(json);
|
||||
string lua = jsonDocument.RootElement.GetProperty("source").GetString() ?? string.Empty;
|
||||
|
||||
DynValue champs = Script.RunString(lua);
|
||||
if (champs.Type == DataType.Table)
|
||||
{
|
||||
Dictionary<string, WikiChampion> nameDictionary = [];
|
||||
foreach (TablePair kv in champs.Table.Pairs)
|
||||
{
|
||||
if (kv.Key.Type is not DataType.String || kv.Value.Type is not DataType.Table)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string key = kv.Key.String;
|
||||
Table championTable = kv.Value.Table;
|
||||
DynValue idValue = championTable.Get("id");
|
||||
DynValue statsValue = championTable.Get("stats");
|
||||
|
||||
Dictionary<string, double> aramStats = [];
|
||||
|
||||
if (statsValue.Type is DataType.Table)
|
||||
{
|
||||
DynValue aramValue = statsValue.Table.Get("aram");
|
||||
|
||||
if (aramValue.Type is DataType.Table)
|
||||
{
|
||||
foreach (TablePair aramKv in aramValue.Table.Pairs)
|
||||
{
|
||||
if (aramKv.Key.Type is DataType.String && aramKv.Value.Type is DataType.Number)
|
||||
{
|
||||
aramStats[aramKv.Key.String] = aramKv.Value.Number;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WikiChampion champ = new(idValue.Type is DataType.Number ? (int)idValue.Number : -1, new(aramStats));
|
||||
nameDictionary.Add(key, champ);
|
||||
}
|
||||
|
||||
_champions = [];
|
||||
|
||||
foreach (KeyValuePair<string, WikiChampion> kv in nameDictionary)
|
||||
{
|
||||
if (!_champions.TryGetValue(kv.Value.Id, out Dictionary<string, double>? value))
|
||||
{
|
||||
_champions[kv.Value.Id] = new(kv.Value.Stats.Aram);
|
||||
}
|
||||
else
|
||||
{
|
||||
kv.Value.Stats.Aram.ToList().ForEach(kv => value.Add(kv.Key, kv.Value));
|
||||
}
|
||||
}
|
||||
ResourceService.SetARAMBalanceLookup(_champions);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, double> GetAramStats(int championId)
|
||||
{
|
||||
EnsureIsLoadedAsync().Wait();
|
||||
return _champions.TryGetValue(championId, out Dictionary<string, double>? stats) ? stats : [];
|
||||
}
|
||||
}
|
||||
18
LeagueAPI/CachingHttpClient.cs
Normal file
18
LeagueAPI/CachingHttpClient.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace LeagueAPI;
|
||||
|
||||
public class HttpCache : Dictionary<string, string> { }
|
||||
|
||||
public static class CachingHttpClient
|
||||
{
|
||||
private static HttpCache _cache = ResourceService.GetHttpCache() ?? [];
|
||||
private static HttpClient _client = new();
|
||||
|
||||
public static async Task<string> GetStringAsync(string requestUri)
|
||||
{
|
||||
if (_cache.TryGetValue(requestUri, out string? response))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
return await _client.GetStringAsync(requestUri);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Management;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LeagueAPI;
|
||||
|
||||
@@ -9,20 +7,6 @@ public record WebsocketMessageResult(byte[] Message, WebSocketMessageType Messag
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
extension(Process process)
|
||||
{
|
||||
public string GetCommandLine()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
throw new PlatformNotSupportedException("Only supported on Windows.");
|
||||
}
|
||||
using ManagementObjectSearcher searcher = new("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + process.Id);
|
||||
using ManagementObjectCollection objects = searcher.Get();
|
||||
return objects.Cast<ManagementBaseObject>().SingleOrDefault()?["CommandLine"]?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
extension(string? s)
|
||||
{
|
||||
public int ParseInt(int defaultValue = default)
|
||||
@@ -44,6 +28,40 @@ public static class Extensions
|
||||
}
|
||||
}
|
||||
|
||||
extension<TSource>(IEnumerable<TSource?> source)
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses <see cref="Enumerable.Aggregate{TSource}(IEnumerable{TSource}, Func{TSource, TSource, TSource})"/>, evaluates immediately.
|
||||
/// </summary>
|
||||
public IEnumerable<TSource> WhereNotNull()
|
||||
{
|
||||
return source.Aggregate(Enumerable.Empty<TSource>(), Accumulate);
|
||||
|
||||
static IEnumerable<TSource> Accumulate(IEnumerable<TSource> accumulator, TSource? next)
|
||||
{
|
||||
if (next is not null)
|
||||
{
|
||||
return accumulator.Append(next);
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses lazy-evaluation
|
||||
/// </summary>
|
||||
public IEnumerable<TSource> WhereNotNullLazy()
|
||||
{
|
||||
foreach (TSource? element in source)
|
||||
{
|
||||
if (element is not null)
|
||||
{
|
||||
yield return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension<T>(Task<T> task)
|
||||
{
|
||||
public T WaitForResult()
|
||||
|
||||
@@ -22,6 +22,9 @@ public class LcuWebsocket : IDisposable
|
||||
|
||||
private readonly ClientWebSocket _socket = new();
|
||||
|
||||
public event EventHandler? Connecting;
|
||||
public event EventHandler? Connected;
|
||||
public event EventHandler? Disconnected;
|
||||
public event EventHandler<LcuApiEvent>? LcuApiEvent;
|
||||
public event EventHandler<Exception>? LcuApiException;
|
||||
|
||||
@@ -42,7 +45,15 @@ public class LcuWebsocket : IDisposable
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
ProcessInfo = ProcessFinder.GetProcessInfo();
|
||||
Connecting?.Invoke(this, EventArgs.Empty);
|
||||
try
|
||||
{
|
||||
ProcessInfo = ProcessFinder.GetProcessInfo();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!ProcessFinder.IsPortOpen(ProcessInfo))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to connect to LCUx process port.");
|
||||
@@ -57,6 +68,8 @@ public class LcuWebsocket : IDisposable
|
||||
{
|
||||
await _socket.ConnectAsync(uri, CancellationToken.None);
|
||||
|
||||
Connected?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
foreach (string eventName in SUBSCRIBE_EVENTS)
|
||||
{
|
||||
string message = $"[{OPCODE_SUBSCRIBE}, \"{eventName}\"]";
|
||||
@@ -108,6 +121,10 @@ public class LcuWebsocket : IDisposable
|
||||
{
|
||||
LcuApiException?.Invoke(this, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliWrap" Version="3.10.0" />
|
||||
<PackageReference Include="System.Management" Version="10.0.3" />
|
||||
<PackageReference Include="MoonSharp" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -39,4 +39,7 @@ public record ChampionData
|
||||
|
||||
[JsonPropertyName("stats")]
|
||||
public ChampionDataStats? Stats { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, double> AramBalance { get; set; } = [];
|
||||
}
|
||||
|
||||
85
LeagueAPI/ResourceService.cs
Normal file
85
LeagueAPI/ResourceService.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.Text.Json;
|
||||
using LeagueAPI.ARAM;
|
||||
|
||||
namespace LeagueAPI;
|
||||
|
||||
public static class ResourceService
|
||||
{
|
||||
private const string DDRAGON_CHAMPION_URL = "https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/{0}.png";
|
||||
private const string CHAMPION_FILENAME_FORMAT = "{0}.png";
|
||||
private const string ARAM_BALANCE_FILENAME = "aram.json";
|
||||
private const string HTTP_CLIENT_CACHE_FILENAME = "httpcache.json";
|
||||
|
||||
private static readonly DirectoryInfo AssetDirectory = new("./assets");
|
||||
|
||||
static ResourceService()
|
||||
{
|
||||
if (!AssetDirectory.Exists)
|
||||
{
|
||||
AssetDirectory.Create();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> GetChampionIconPathAsync(int championId = -1)
|
||||
{
|
||||
FileInfo assetFile = new(Path.Combine(AssetDirectory.FullName, CHAMPION_FILENAME_FORMAT.AsFormatWith(championId)));
|
||||
if (assetFile.Exists)
|
||||
{
|
||||
return assetFile.FullName;
|
||||
}
|
||||
|
||||
using HttpClient client = new();
|
||||
HttpResponseMessage response = await client.GetAsync(DDRAGON_CHAMPION_URL.AsFormatWith(championId));
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
byte[] buffer = await response.Content.ReadAsByteArrayAsync();
|
||||
try
|
||||
{
|
||||
if (!assetFile.Exists)
|
||||
{
|
||||
await File.WriteAllBytesAsync(assetFile.FullName, buffer);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return assetFile.FullName;
|
||||
}
|
||||
|
||||
public static ARAMBalanceLookup? GetARAMBalanceLookup()
|
||||
{
|
||||
FileInfo aramFile = new(Path.Combine(AssetDirectory.FullName, ARAM_BALANCE_FILENAME));
|
||||
if (!aramFile.Exists)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string json = File.ReadAllText(aramFile.FullName);
|
||||
return JsonSerializer.Deserialize<ARAMBalanceLookup>(json);
|
||||
}
|
||||
|
||||
public static void SetARAMBalanceLookup(ARAMBalanceLookup champions)
|
||||
{
|
||||
FileInfo aramFile = new(Path.Combine(AssetDirectory.FullName, ARAM_BALANCE_FILENAME));
|
||||
string json = JsonSerializer.Serialize(champions);
|
||||
File.WriteAllText(aramFile.FullName, json);
|
||||
}
|
||||
|
||||
public static HttpCache? GetHttpCache()
|
||||
{
|
||||
FileInfo cacheFile = new(Path.Combine(AssetDirectory.FullName, HTTP_CLIENT_CACHE_FILENAME));
|
||||
if (!cacheFile.Exists)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string json = File.ReadAllText(cacheFile.FullName);
|
||||
return JsonSerializer.Deserialize<HttpCache>(json);
|
||||
}
|
||||
|
||||
public static void SetHttpCache(HttpCache cache)
|
||||
{
|
||||
FileInfo cacheFile = new(Path.Combine(AssetDirectory.FullName, HTTP_CLIENT_CACHE_FILENAME));
|
||||
string json = JsonSerializer.Serialize(cache);
|
||||
File.WriteAllText(cacheFile.FullName, json);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,6 @@ namespace LeagueAPI.Utils;
|
||||
|
||||
public class LcuHttpClient : HttpClient
|
||||
{
|
||||
private static readonly Lazy<LcuHttpClient> LazyInstance = new(() => new LcuHttpClient(new LcuHttpClientHandler()));
|
||||
internal static LcuHttpClient Instance => LazyInstance.Value;
|
||||
|
||||
private LcuHttpClientHandler Handler { get; }
|
||||
public ProcessInfo? ProcessInfo
|
||||
{
|
||||
@@ -22,11 +19,11 @@ public class LcuHttpClient : HttpClient
|
||||
|
||||
public RiotAuthentication? RiotAuthentication => Handler.RiotAuthentication;
|
||||
|
||||
internal LcuHttpClient(LcuHttpClientHandler lcuHttpClientHandler)
|
||||
public LcuHttpClient(LcuHttpClientHandler lcuHttpClientHandler)
|
||||
: base(lcuHttpClientHandler)
|
||||
{
|
||||
Handler = lcuHttpClientHandler;
|
||||
base.BaseAddress = new Uri("https://127.0.0.1");
|
||||
BaseAddress = new Uri("https://127.0.0.1");
|
||||
}
|
||||
|
||||
public async Task<T?> GetContentAsync<T>(string requestUri) where T : class
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LeagueAPI.Utils;
|
||||
|
||||
internal class LcuHttpClientHandler : HttpClientHandler
|
||||
public class LcuHttpClientHandler : HttpClientHandler
|
||||
{
|
||||
private Lazy<bool> _isFirstRequest = new(() => true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user