From 3db5e78abcd72495f53a38711ee70f6b411da9a3 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Sat, 8 Feb 2025 15:18:13 +0300 Subject: [PATCH 01/23] Now we use only 1 instance of httpclient --- LichessNET/API/LichessAPIClient.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/LichessNET/API/LichessAPIClient.cs b/LichessNET/API/LichessAPIClient.cs index a859cd3..3eea658 100644 --- a/LichessNET/API/LichessAPIClient.cs +++ b/LichessNET/API/LichessAPIClient.cs @@ -18,9 +18,9 @@ namespace LichessNET.API; /// Console.WriteLine($"User is part of {team.Count} teams."); /// /// -public partial class LichessApiClient - +public partial class LichessApiClient { + private readonly HttpClient _httpClient; private readonly ILogger _logger; /// @@ -47,6 +47,9 @@ public LichessApiClient(string token = "") this.Token = token; + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.Token); + if (token != "") _logger.LogInformation("Connecting to Lichess API with token"); else @@ -64,7 +67,7 @@ public LichessApiClient(string token = "") .WithFixedIntervalRefillStrategy(1, TimeSpan.FromSeconds(5)).Build()); } - + /// /// Gets the UriBuilder objects for the lichess client. /// If something changes in the future, it will be easy to change it. @@ -93,16 +96,15 @@ private HttpRequestMessage GetRequestScaffold(string endpoint, params Tuple SendRequest(HttpRequestMessage request, HttpMethod method = null, - bool useToken = true) + bool useToken = true, HttpContent content = null) { if (method == null) method = HttpMethod.Get; - _ratelimitController.Consume(request.RequestUri.AbsolutePath, true); - var client = new HttpClient(); - if (useToken & Token != "") - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token); - } - + await _ratelimitController.Consume(request.RequestUri.AbsolutePath, true); + var client = _httpClient; + + request.Method = method; + request.Content = content; + _logger.LogInformation("Sending request to " + request.RequestUri); var response = client.SendAsync(request).Result; if (response.IsSuccessStatusCode) From daced1f1ccc99ad6ea43704eafc91c02ecb4adda Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Sat, 8 Feb 2025 15:19:20 +0300 Subject: [PATCH 02/23] Replaced Thread.Sleep with Task.Delay --- LichessNET/API/APIRatelimitController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/LichessNET/API/APIRatelimitController.cs b/LichessNET/API/APIRatelimitController.cs index 9000402..c4d8e46 100644 --- a/LichessNET/API/APIRatelimitController.cs +++ b/LichessNET/API/APIRatelimitController.cs @@ -53,31 +53,31 @@ public void RegisterBucket(string endpointUrl, ITokenBucket bucket) _buckets.Add(endpointUrl, bucket); } - public void Consume() + public async Task Consume() { PipedRequests++; if (_rateLimitedUntil > DateTime.Now) { _logger.LogWarning("Endpoint blocked due to ratelimit. Waiting for " + (_rateLimitedUntil - DateTime.Now).Milliseconds + " ms."); - Thread.Sleep((_rateLimitedUntil - DateTime.Now).Milliseconds); + await Task.Delay((_rateLimitedUntil - DateTime.Now).Milliseconds); } _defaultBucket.Consume(); PipedRequests--; } - public void Consume(string endpointUrl, bool consumedefaultBucket) + public async Task Consume(string endpointUrl, bool consumeDefaultBucket) { PipedRequests++; if (_rateLimitedUntil > DateTime.Now) { _logger.LogWarning("Endpoint call to " + endpointUrl + " blocked due to ratelimit. Waiting for " + (_rateLimitedUntil - DateTime.Now).Milliseconds + " ms."); - Thread.Sleep((_rateLimitedUntil - DateTime.Now).Milliseconds); + await Task.Delay((_rateLimitedUntil - DateTime.Now).Milliseconds); } - if (consumedefaultBucket) _defaultBucket.Consume(); + if (consumeDefaultBucket) _defaultBucket.Consume(); if (_buckets.TryGetValue(endpointUrl, out var bucket)) bucket.Consume(); PipedRequests--; From 79de5478a292630ece57e28d8f9089dd42822660 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Sat, 8 Feb 2025 16:25:40 +0300 Subject: [PATCH 03/23] Added methods to revoke api tokens, you can also test them and get their permissions --- LichessNET/API/OAuthAPI.cs | 29 +++++ .../Entities/Enumerations/TokenPermission.cs | 25 +++++ LichessNET/Entities/OAuth/TokenInfo.cs | 100 ++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 LichessNET/API/OAuthAPI.cs create mode 100644 LichessNET/Entities/Enumerations/TokenPermission.cs create mode 100644 LichessNET/Entities/OAuth/TokenInfo.cs diff --git a/LichessNET/API/OAuthAPI.cs b/LichessNET/API/OAuthAPI.cs new file mode 100644 index 0000000..79cba2c --- /dev/null +++ b/LichessNET/API/OAuthAPI.cs @@ -0,0 +1,29 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using LichessNET.Entities.Enumerations; +using LichessNET.Entities.OAuth; + +namespace LichessNET.API; + +public partial class LichessApiClient +{ + public async Task> TestTokensAsync(List tokens) + { + var tokenBody = string.Join(',', tokens); + var request = GetRequestScaffold("api/token/test"); + var response = await SendRequest(request, content: new StringContent(tokenBody), method: HttpMethod.Post); + Console.WriteLine(await response.Content.ReadAsStringAsync()); + var tokenInfo = await response.Content.ReadFromJsonAsync>(new JsonSerializerOptions { PropertyNameCaseInsensitive = true}); + return tokenInfo; + } + + public async Task DeleteTokenAsync(string token) + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + var request = GetRequestScaffold("api/token"); + var response = await SendRequest(request, method: HttpMethod.Delete); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token); + } +} \ No newline at end of file diff --git a/LichessNET/Entities/Enumerations/TokenPermission.cs b/LichessNET/Entities/Enumerations/TokenPermission.cs new file mode 100644 index 0000000..3ddcd19 --- /dev/null +++ b/LichessNET/Entities/Enumerations/TokenPermission.cs @@ -0,0 +1,25 @@ +namespace LichessNET.Entities.Enumerations; + +public enum TokenPermission +{ + ReadEmail, + ReadPreferences, + WritePreferences, + ReadFollows, + WriteFollows, + WriteMessages, + ReadChallenges, + WriteChallenges, + BulkChallenges, + WriteTournaments, + ReadTeams, + WriteTeams, + ManageTeams, + ReadPuzzleActivity, + WriteRaces, + ReadStudies, + WriteStudies, + PlayGames, + ReadEngines, + ManageEngines, +} \ No newline at end of file diff --git a/LichessNET/Entities/OAuth/TokenInfo.cs b/LichessNET/Entities/OAuth/TokenInfo.cs new file mode 100644 index 0000000..e7cca02 --- /dev/null +++ b/LichessNET/Entities/OAuth/TokenInfo.cs @@ -0,0 +1,100 @@ +using System.Text.Json.Serialization; +using LichessNET.Entities.Enumerations; + +namespace LichessNET.Entities.OAuth; + +public class TokenInfo +{ + [JsonInclude] + private string scopes { get; set; } + public string UserId { get; set; } + public List Permissions { get; set; } + public int? Expires { get; set; } + + [JsonConstructor] + public TokenInfo(string userId, string scopes, int? expires) + { + UserId = userId; + Expires = expires; + Permissions = TokenInfo.GetPermissions(scopes); + } + + public bool IsAllowed(TokenPermission permission) => Permissions.Contains(permission); + public static List GetPermissions(string permissions) + { + string[] permissionsList = permissions.Split(','); + List tokenPermissions = new List(); + + foreach (string permission in permissionsList) + { + switch (permission) + { + case "email:read": + tokenPermissions.Add(TokenPermission.ReadEmail); + break; + case "preference:read": + tokenPermissions.Add(TokenPermission.ReadPreferences); + break; + case "preference:write": + tokenPermissions.Add(TokenPermission.WritePreferences); + break; + case "follow:read": + tokenPermissions.Add(TokenPermission.ReadFollows); + break; + case "follow:write": + tokenPermissions.Add(TokenPermission.WriteFollows); + break; + case "msg:write": + tokenPermissions.Add(TokenPermission.WriteMessages); + break; + case "challenge:read": + tokenPermissions.Add(TokenPermission.ReadChallenges); + break; + case "challenge:write": + tokenPermissions.Add(TokenPermission.WriteChallenges); + break; + case "challenge:bulk": + tokenPermissions.Add(TokenPermission.BulkChallenges); + break; + case "tournament:write": + tokenPermissions.Add(TokenPermission.WriteTournaments); + break; + case "team:read": + tokenPermissions.Add(TokenPermission.ReadTeams); + break; + case "team:write": + tokenPermissions.Add(TokenPermission.WriteTeams); + break; + case "team:lead": + tokenPermissions.Add(TokenPermission.ManageTeams); + break; + case "puzzle:read": + tokenPermissions.Add(TokenPermission.ReadPuzzleActivity); + break; + case "racer:write": + tokenPermissions.Add(TokenPermission.WriteRaces); + break; + case "study:read": + tokenPermissions.Add(TokenPermission.ReadStudies); + break; + case "study:write": + tokenPermissions.Add(TokenPermission.WriteStudies); + break; + case "board:play": + tokenPermissions.Add(TokenPermission.PlayGames); + break; + case "engine:read": + tokenPermissions.Add(TokenPermission.ReadEngines); + break; + case "engine:write": + tokenPermissions.Add(TokenPermission.ManageEngines); + break; + default: + // Если не удалось сопоставить разрешение, можно вывести ошибку или просто игнорировать + break; + } + } + + return tokenPermissions; + } +} \ No newline at end of file From 9779fcb23779a4ff4261138fae9a08e1f6f4b03d Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Sat, 8 Feb 2025 16:41:16 +0300 Subject: [PATCH 04/23] Replaced SendRequest().Result with await SendRequest(). This fixed client.SetKidModeStatus, and now it works. --- LichessNET/API/AccountAPI.cs | 2 -- LichessNET/API/LichessAPIClient.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/LichessNET/API/AccountAPI.cs b/LichessNET/API/AccountAPI.cs index 7d17c65..2ea25e6 100644 --- a/LichessNET/API/AccountAPI.cs +++ b/LichessNET/API/AccountAPI.cs @@ -93,8 +93,6 @@ public async Task GetKidModeStatus() /// public async Task SetKidModeStatus(bool enable) { - _ratelimitController.Consume("api/account", false); - var request = GetRequestScaffold("api/account/kid", Tuple.Create("v", enable.ToString())); var response = await SendRequest(request, HttpMethod.Post); diff --git a/LichessNET/API/LichessAPIClient.cs b/LichessNET/API/LichessAPIClient.cs index 3eea658..e94ce25 100644 --- a/LichessNET/API/LichessAPIClient.cs +++ b/LichessNET/API/LichessAPIClient.cs @@ -106,7 +106,7 @@ private async Task SendRequest(HttpRequestMessage request, request.Content = content; _logger.LogInformation("Sending request to " + request.RequestUri); - var response = client.SendAsync(request).Result; + var response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { _logger.LogInformation("Request to " + request.RequestUri + " successful."); From 9c04d1e181a5d94e4eb169fb9bda758d4148894d Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Sat, 8 Feb 2025 17:02:13 +0300 Subject: [PATCH 05/23] Fixed GetKidModeStatus() not returning bool value --- LichessNET/API/AccountAPI.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/LichessNET/API/AccountAPI.cs b/LichessNET/API/AccountAPI.cs index 2ea25e6..29ba0d1 100644 --- a/LichessNET/API/AccountAPI.cs +++ b/LichessNET/API/AccountAPI.cs @@ -1,4 +1,5 @@ -using LichessNET.Entities; +using System.Net.Http.Json; +using LichessNET.Entities; using LichessNET.Entities.Account; using LichessNET.Entities.Social; using LichessNET.Entities.Social.Timeline; @@ -76,10 +77,8 @@ public async Task GetKidModeStatus() var request = GetRequestScaffold("api/account/kid"); var response = await SendRequest(request); - var content = await response.Content.ReadAsStringAsync(); - - var kidModeStatus = JsonConvert.DeserializeObject(content).kid.ToObject(); - return kidModeStatus; + var content = await response.Content.ReadFromJsonAsync>(); + return content["kid"]; } /// From 655a8ce19db65024a6c536c7ff1aeb14e1d99625 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Sat, 8 Feb 2025 17:25:23 +0300 Subject: [PATCH 06/23] Now api key is set in a different method instead of constructor. You can just set different key instead of making new instance of client. If you set invalid api key you will get an exception --- LichessNET/API/LichessAPIClient.cs | 56 +++++++++++++++++------------- LichessNET/API/OAuthAPI.cs | 2 +- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/LichessNET/API/LichessAPIClient.cs b/LichessNET/API/LichessAPIClient.cs index e94ce25..5d2088e 100644 --- a/LichessNET/API/LichessAPIClient.cs +++ b/LichessNET/API/LichessAPIClient.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Http.Headers; using System.Web; +using LichessNET.Entities.OAuth; using Microsoft.Extensions.Logging; using TokenBucket; using Vertical.SpectreLogger; @@ -31,13 +32,34 @@ public partial class LichessApiClient /// /// The token to access the Lichess API /// - internal string Token = ""; + private string? _token; + + public string? GetToken() => _token; + + public async Task SetToken(string? value) + { + if (value == null) + { + _token = null; + return; + } + var tokenTest = await TestTokensAsync(new List { value }); + if (tokenTest[value] is not null) + { + _token = value; + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.GetToken()); + } + else + { + throw new UnauthorizedAccessException("Invalid token"); + } + } /// /// Creates a lichess API client, according to settings /// /// The token for accessing the lichess API - public LichessApiClient(string token = "") + public LichessApiClient() { var loggerFactory = LoggerFactory.Create(builder => builder .SetMinimumLevel(Constants.MinimumLogLevel) @@ -45,20 +67,8 @@ public LichessApiClient(string token = "") _logger = loggerFactory.CreateLogger("LichessAPIClient"); - - this.Token = token; - _httpClient = new HttpClient(); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.Token); - if (token != "") - _logger.LogInformation("Connecting to Lichess API with token"); - else - _logger.LogInformation("Connecting to Lichess API without token"); - - if (!token.Contains("_")) - _logger.LogWarning("The token provided may not be a valid lichess API token. Please check the token."); - - _logger.LogInformation("Connection to Lichess API established."); + _httpClient = new HttpClient(); _ratelimitController.RegisterBucket("api/account", TokenBuckets.Construct().WithCapacity(5) .WithFixedIntervalRefillStrategy(3, TimeSpan.FromSeconds(15)).Build()); @@ -123,16 +133,12 @@ private async Task SendRequest(HttpRequestMessage request, if (response.StatusCode == HttpStatusCode.Forbidden) { - if ((await response.Content.ReadAsStringAsync()).Contains("Missing scope")) - { - _logger.LogError( - "The token provided does not have the required scope to access this endpoint. The client will " + - "resend a request without a token."); - return await SendRequest(new HttpRequestMessage() - { - RequestUri = request.RequestUri - }, method, false); - } + throw new HttpRequestException("Access denied. Your token does not have the required scope."); + } + + if (response.StatusCode == HttpStatusCode.Unauthorized) + { + throw new UnauthorizedAccessException("Api Key is invalid."); } _logger.LogError("Error while fetching data from Lichess API. Status code: " + response.StatusCode); diff --git a/LichessNET/API/OAuthAPI.cs b/LichessNET/API/OAuthAPI.cs index 79cba2c..c74fa8b 100644 --- a/LichessNET/API/OAuthAPI.cs +++ b/LichessNET/API/OAuthAPI.cs @@ -24,6 +24,6 @@ public async Task DeleteTokenAsync(string token) _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var request = GetRequestScaffold("api/token"); var response = await SendRequest(request, method: HttpMethod.Delete); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GetToken()); } } \ No newline at end of file From c66322f57f3ab8932587e6914efece1823970d7b Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Sat, 15 Feb 2025 13:13:41 +0300 Subject: [PATCH 07/23] Speed enum --- LichessNET/Entities/Enumerations/Speed.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 LichessNET/Entities/Enumerations/Speed.cs diff --git a/LichessNET/Entities/Enumerations/Speed.cs b/LichessNET/Entities/Enumerations/Speed.cs new file mode 100644 index 0000000..a1abfcc --- /dev/null +++ b/LichessNET/Entities/Enumerations/Speed.cs @@ -0,0 +1,11 @@ +namespace LichessNET.Entities.Enumerations; + +public enum Speed +{ + UltraBullet, + Bullet, + Blitz, + Rapid, + Classical, + Correspondence, +} \ No newline at end of file From c8cb220d85b05b666ae9e22d752a375dd5e6eb6a Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Sat, 15 Feb 2025 13:14:55 +0300 Subject: [PATCH 08/23] GameStatus enum --- LichessNET/Entities/Enumerations/GameStatus.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 LichessNET/Entities/Enumerations/GameStatus.cs diff --git a/LichessNET/Entities/Enumerations/GameStatus.cs b/LichessNET/Entities/Enumerations/GameStatus.cs new file mode 100644 index 0000000..a0ec292 --- /dev/null +++ b/LichessNET/Entities/Enumerations/GameStatus.cs @@ -0,0 +1,18 @@ +namespace LichessNET.Entities.Enumerations; + +public enum GameStatus +{ + Created, + Started, + Aborted, + Mate, + Resign, + Stalemate, + Timeout, + Draw, + OutOfTime, + Cheat, + NoStart, + UnknownFinish, + VariantEnd, +} \ No newline at end of file From 53dc7b913fdbbb71d9365dd03d1906e99955953e Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 16:44:11 +0300 Subject: [PATCH 09/23] Using converters instead of constructors when deserializing --- .../Converters/PermissionJsonConverter.cs | 90 +++++++++++++++++++ LichessNET/Entities/OAuth/TokenInfo.cs | 17 +--- 2 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 LichessNET/Converters/PermissionJsonConverter.cs diff --git a/LichessNET/Converters/PermissionJsonConverter.cs b/LichessNET/Converters/PermissionJsonConverter.cs new file mode 100644 index 0000000..ef55931 --- /dev/null +++ b/LichessNET/Converters/PermissionJsonConverter.cs @@ -0,0 +1,90 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using LichessNET.Entities.Enumerations; + +namespace LichessNET.Converters; + +public class PermissionJsonConverter : JsonConverter> +{ + public override List? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + + var permissions = reader.GetString(); + string[] permissionsList = permissions.Split(','); + List tokenPermissions = new List(); + + foreach (string permission in permissionsList) + { + switch (permission) + { + case "email:read": + tokenPermissions.Add(TokenPermission.ReadEmail); + break; + case "preference:read": + tokenPermissions.Add(TokenPermission.ReadPreferences); + break; + case "preference:write": + tokenPermissions.Add(TokenPermission.WritePreferences); + break; + case "follow:read": + tokenPermissions.Add(TokenPermission.ReadFollows); + break; + case "follow:write": + tokenPermissions.Add(TokenPermission.WriteFollows); + break; + case "msg:write": + tokenPermissions.Add(TokenPermission.WriteMessages); + break; + case "challenge:read": + tokenPermissions.Add(TokenPermission.ReadChallenges); + break; + case "challenge:write": + tokenPermissions.Add(TokenPermission.WriteChallenges); + break; + case "challenge:bulk": + tokenPermissions.Add(TokenPermission.BulkChallenges); + break; + case "tournament:write": + tokenPermissions.Add(TokenPermission.WriteTournaments); + break; + case "team:read": + tokenPermissions.Add(TokenPermission.ReadTeams); + break; + case "team:write": + tokenPermissions.Add(TokenPermission.WriteTeams); + break; + case "team:lead": + tokenPermissions.Add(TokenPermission.ManageTeams); + break; + case "puzzle:read": + tokenPermissions.Add(TokenPermission.ReadPuzzleActivity); + break; + case "racer:write": + tokenPermissions.Add(TokenPermission.WriteRaces); + break; + case "study:read": + tokenPermissions.Add(TokenPermission.ReadStudies); + break; + case "study:write": + tokenPermissions.Add(TokenPermission.WriteStudies); + break; + case "board:play": + tokenPermissions.Add(TokenPermission.PlayGames); + break; + case "engine:read": + tokenPermissions.Add(TokenPermission.ReadEngines); + break; + case "engine:write": + tokenPermissions.Add(TokenPermission.ManageEngines); + break; + } + } + + return tokenPermissions; + } + + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + { + + } +} \ No newline at end of file diff --git a/LichessNET/Entities/OAuth/TokenInfo.cs b/LichessNET/Entities/OAuth/TokenInfo.cs index e7cca02..fbd2ccd 100644 --- a/LichessNET/Entities/OAuth/TokenInfo.cs +++ b/LichessNET/Entities/OAuth/TokenInfo.cs @@ -1,23 +1,17 @@ using System.Text.Json.Serialization; +using LichessNET.Converters; using LichessNET.Entities.Enumerations; namespace LichessNET.Entities.OAuth; public class TokenInfo { - [JsonInclude] - private string scopes { get; set; } public string UserId { get; set; } + + [JsonConverter(typeof(PermissionJsonConverter))] + [JsonPropertyName("scopes")] public List Permissions { get; set; } public int? Expires { get; set; } - - [JsonConstructor] - public TokenInfo(string userId, string scopes, int? expires) - { - UserId = userId; - Expires = expires; - Permissions = TokenInfo.GetPermissions(scopes); - } public bool IsAllowed(TokenPermission permission) => Permissions.Contains(permission); public static List GetPermissions(string permissions) @@ -89,9 +83,6 @@ public static List GetPermissions(string permissions) case "engine:write": tokenPermissions.Add(TokenPermission.ManageEngines); break; - default: - // Если не удалось сопоставить разрешение, можно вывести ошибку или просто игнорировать - break; } } From 3c19c36e67595b349472d4b71b14edbc8be19b3a Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 17:03:59 +0300 Subject: [PATCH 10/23] Removed "None" from Title enum, added "Bot" title --- LichessNET/Entities/Enumerations/Title.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LichessNET/Entities/Enumerations/Title.cs b/LichessNET/Entities/Enumerations/Title.cs index 20f44a0..7ce0698 100644 --- a/LichessNET/Entities/Enumerations/Title.cs +++ b/LichessNET/Entities/Enumerations/Title.cs @@ -16,6 +16,5 @@ public enum Title WFM, WIM, WGM, - - None + Bot, } \ No newline at end of file From 8044080154aa778e358060396b4b31ed6ad4b0bc Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 17:32:05 +0300 Subject: [PATCH 11/23] Made title field nullable --- LichessNET/Entities/Game/GamePlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LichessNET/Entities/Game/GamePlayer.cs b/LichessNET/Entities/Game/GamePlayer.cs index 3d7201a..ac5f3bc 100644 --- a/LichessNET/Entities/Game/GamePlayer.cs +++ b/LichessNET/Entities/Game/GamePlayer.cs @@ -11,6 +11,6 @@ public class GamePlayer public string Name { get; set; } //TODO: Add serialization for Title - public Title Title { get; set; } = Title.None; + public Title? Title { get; set; } public int Rating { get; set; } } \ No newline at end of file From d0f855b751ae16067c9f179491d4c4039e34a8dc Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 17:32:27 +0300 Subject: [PATCH 12/23] Made all fields nullable --- LichessNET/Entities/Social/LichessProfile.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/LichessNET/Entities/Social/LichessProfile.cs b/LichessNET/Entities/Social/LichessProfile.cs index 2127e22..782b059 100644 --- a/LichessNET/Entities/Social/LichessProfile.cs +++ b/LichessNET/Entities/Social/LichessProfile.cs @@ -8,51 +8,51 @@ public class LichessProfile /// /// The current country flag of the user /// - public string Flag { get; set; } + public string? Flag { get; set; } /// /// The set location of this user /// - public string Location { get; set; } + public string? Location { get; set; } /// /// The bio of the user /// - public string Bio { get; set; } + public string? Bio { get; set; } /// /// The set real name of the user /// - public string RealName { get; set; } + public string? RealName { get; set; } /// /// FIDE rating of the user /// - public ushort FideRating { get; set; } + public ushort? FideRating { get; set; } /// /// USCF rating of the user /// - public ushort UsCfRating { get; set; } + public ushort? UsCfRating { get; set; } /// /// ECF rating of the user /// - public ushort EcfRating { get; set; } + public ushort? EcfRating { get; set; } /// /// CFC rating of the user /// - public ushort CfcRating { get; set; } + public ushort? CfcRating { get; set; } /// /// DSB rating of the user /// - public ushort DsbRating { get; set; } + public ushort? DsbRating { get; set; } /// /// Links mentioned in the bio of the user /// Each link is seperated by \r\n /// - public string Links { get; set; } + public string? Links { get; set; } } \ No newline at end of file From d0d9e566009b4e4137aa3af47ba2cdc644138d86 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 22:17:09 +0300 Subject: [PATCH 13/23] Added more gamemodes, we mix gamemodes and time controls, it is probably better than making interfaces for Rating property in LichessUser.cs --- LichessNET/Entities/Enumerations/Gamemode.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/LichessNET/Entities/Enumerations/Gamemode.cs b/LichessNET/Entities/Enumerations/Gamemode.cs index 9ded361..723bf49 100644 --- a/LichessNET/Entities/Enumerations/Gamemode.cs +++ b/LichessNET/Entities/Enumerations/Gamemode.cs @@ -21,7 +21,10 @@ public enum Gamemode [EnumMember(Value = "horde")] Horde, [EnumMember(Value = "racingKings")] RacingKings, [EnumMember(Value = "crazyhouse")] Crazyhouse, - [EnumMember(Value = "puzzle")] Storm, - [EnumMember(Value = "puzzle")] Racer, - [EnumMember(Value = "puzzle")] Streak + Storm, + Racer, + Streak, + UltraBullet, + Correspondence, + Puzzle } \ No newline at end of file From 73f1fc52cde686df1b653de2072e0bc0bf907721 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 22:18:04 +0300 Subject: [PATCH 14/23] Game stats are in the same class to make dictionary for Ratings property in LichessUser.cs --- LichessNET/Entities/Interfaces/IGameStats.cs | 6 ++++++ LichessNET/Entities/Stats/GamemodeStats.cs | 8 ++++++-- LichessNET/Entities/Stats/RunStats.cs | 9 +++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 LichessNET/Entities/Interfaces/IGameStats.cs create mode 100644 LichessNET/Entities/Stats/RunStats.cs diff --git a/LichessNET/Entities/Interfaces/IGameStats.cs b/LichessNET/Entities/Interfaces/IGameStats.cs new file mode 100644 index 0000000..1834d0f --- /dev/null +++ b/LichessNET/Entities/Interfaces/IGameStats.cs @@ -0,0 +1,6 @@ +namespace LichessNET.Entities.Interfaces; + +public interface IGameStats +{ + +} \ No newline at end of file diff --git a/LichessNET/Entities/Stats/GamemodeStats.cs b/LichessNET/Entities/Stats/GamemodeStats.cs index 31cd74b..57542c7 100644 --- a/LichessNET/Entities/Stats/GamemodeStats.cs +++ b/LichessNET/Entities/Stats/GamemodeStats.cs @@ -1,9 +1,12 @@ -namespace LichessNET.Entities.Stats; +using System.Text.Json.Serialization; +using LichessNET.Entities.Interfaces; + +namespace LichessNET.Entities.Stats; /// /// This class contains all stats of a user in a specific gamemode /// -public class GamemodeStats +public class GamemodeStats : IGameStats { /// /// Amount of games played in this gamemode @@ -23,6 +26,7 @@ public class GamemodeStats /// /// The current progress of the user in this gamemode over the last 12 games /// + [JsonPropertyName("prog")] public int Progress { get; set; } /// diff --git a/LichessNET/Entities/Stats/RunStats.cs b/LichessNET/Entities/Stats/RunStats.cs new file mode 100644 index 0000000..21fdbc6 --- /dev/null +++ b/LichessNET/Entities/Stats/RunStats.cs @@ -0,0 +1,9 @@ +using LichessNET.Entities.Interfaces; + +namespace LichessNET.Entities.Stats; + +public class RunStats : IGameStats +{ + public int Runs { get; set; } + public int Score { get; set; } +} \ No newline at end of file From 4ff79ed1d8539827c5b76ba096f3428417f4f4a8 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 22:18:32 +0300 Subject: [PATCH 15/23] Converts int milliseconds to DateTime for deserialization --- .../Converters/MillisecondUnixConverter.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 LichessNET/Converters/MillisecondUnixConverter.cs diff --git a/LichessNET/Converters/MillisecondUnixConverter.cs b/LichessNET/Converters/MillisecondUnixConverter.cs new file mode 100644 index 0000000..0af1ca4 --- /dev/null +++ b/LichessNET/Converters/MillisecondUnixConverter.cs @@ -0,0 +1,19 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace LichessNET.Converters; + +public class MillisecondUnixConverter : JsonConverter +{ + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var milliseconds = reader.GetInt64(); + DateTime unixEpoch = DateTime.UnixEpoch; + return unixEpoch.Add(TimeSpan.FromMilliseconds(milliseconds)); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} \ No newline at end of file From 2cd37f09f4f7544e24efd66b7c5bae01f984e432 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 22:19:00 +0300 Subject: [PATCH 16/23] Converts string from api to Token permission enum, used in Deserialization --- LichessNET/Converters/PermissionJsonConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LichessNET/Converters/PermissionJsonConverter.cs b/LichessNET/Converters/PermissionJsonConverter.cs index ef55931..1418af5 100644 --- a/LichessNET/Converters/PermissionJsonConverter.cs +++ b/LichessNET/Converters/PermissionJsonConverter.cs @@ -85,6 +85,6 @@ public class PermissionJsonConverter : JsonConverter> public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) { - + throw new NotImplementedException(); } } \ No newline at end of file From 19bb0a79f1debd778129ad40035620d8cd24a807 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 22:20:07 +0300 Subject: [PATCH 17/23] Made Title property nullable --- LichessNET/Entities/Social/UserOverview.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/LichessNET/Entities/Social/UserOverview.cs b/LichessNET/Entities/Social/UserOverview.cs index df1c2cc..c3a33a3 100644 --- a/LichessNET/Entities/Social/UserOverview.cs +++ b/LichessNET/Entities/Social/UserOverview.cs @@ -10,14 +10,6 @@ public class UserOverview public string Id { get; set; } public string Name { get; set; } public bool Patron { get; set; } - public string Title { get; set; } - public Title ChessTitle - { - get - { - if (string.IsNullOrEmpty(Title)) return Enumerations.Title.None; - return (Title)Enum.Parse(typeof(Title), Title, true); - } - } + public Title? Title { get; set; } } \ No newline at end of file From 65f0aa746cdf5dfa9e196aa5b4dd78316b8dfc9b Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Tue, 18 Feb 2025 22:23:17 +0300 Subject: [PATCH 18/23] Now we can deserialize to LichessUser model, all properties except "Streamer" should be deserialized, GetUserProfile should work, GetLeaderboard needs to be rewritten --- LichessNET/API/UsersAPI.cs | 86 ++++++++++----------- LichessNET/Converters/GameStatsConverter.cs | 49 ++++++++++++ LichessNET/Entities/Social/LichessUser.cs | 64 +++++---------- 3 files changed, 110 insertions(+), 89 deletions(-) create mode 100644 LichessNET/Converters/GameStatsConverter.cs diff --git a/LichessNET/API/UsersAPI.cs b/LichessNET/API/UsersAPI.cs index 578273a..c3b2d00 100644 --- a/LichessNET/API/UsersAPI.cs +++ b/LichessNET/API/UsersAPI.cs @@ -1,4 +1,5 @@ -using LichessNET.Entities.Account.Performance; +using System.Net.Http.Json; +using LichessNET.Entities.Account.Performance; using LichessNET.Entities.Enumerations; using LichessNET.Entities.Social; using LichessNET.Entities.Social.Stream; @@ -74,43 +75,43 @@ public async Task> GetLeaderboardAsync(int nb, Gamemode perfTy var endpoint = $"api/player/top/{nb}/{perfType.ToString().ToLower()}"; var request = GetRequestScaffold(endpoint); - try - { - var response = await SendRequest(request); - var content = await response.Content.ReadAsStringAsync(); - var json = JObject.Parse(content); - - foreach (var userJson in json["users"]) - { - var user = new LichessUser - { - Id = userJson["id"]?.ToString(), - Username = userJson["username"]?.ToString(), - }; - - var perfs = userJson["perfs"]?.ToObject>(); - if (perfs != null && perfs.ContainsKey(perfType.ToString().ToLower())) - { - user.Ratings = new Dictionary - { - { - perfType, - new GamemodeStats - { - Rating = (int)(perfs[perfType.ToString().ToLower()]["rating"] ?? 0), - Progress = (int)(perfs[perfType.ToString().ToLower()]["progress"] ?? 0) - } - } - }; - } - - users.Add(user); - } - } - catch (Exception ex) - { - _logger.LogError($"Exception occurred while fetching leaderboard for {perfType}: {ex.Message}"); - } + // try + // { + // var response = await SendRequest(request); + // var content = await response.Content.ReadAsStringAsync(); + // var json = JObject.Parse(content); + // + // foreach (var userJson in json["users"]) + // { + // var user = new LichessUser + // { + // Id = userJson["id"]?.ToString(), + // Username = userJson["username"]?.ToString(), + // }; + // + // var perfs = userJson["perfs"]?.ToObject>(); + // if (perfs != null && perfs.ContainsKey(perfType.ToString().ToLower())) + // { + // user.Ratings = new Dictionary + // { + // { + // perfType, + // new GamemodeStats + // { + // Rating = (int)(perfs[perfType.ToString().ToLower()]["rating"] ?? 0), + // Progress = (int)(perfs[perfType.ToString().ToLower()]["progress"] ?? 0) + // } + // } + // }; + // } + // + // users.Add(user); + // } + // } + // catch (Exception ex) + // { + // _logger.LogError($"Exception occurred while fetching leaderboard for {perfType}: {ex.Message}"); + // } return users; } @@ -203,15 +204,10 @@ public async Task GetUserProfile(string username) var endpoint = $"api/user/{username}"; var request = GetRequestScaffold(endpoint); var response = await SendRequest(request); - var content = await response.Content.ReadAsStringAsync(); - var json = JObject.Parse(content); - - - LichessUser user = json.ToObject(); - user.Ratings = LichessUser.DeserializeRatings(json["perfs"]); + var content = await response.Content.ReadFromJsonAsync(); - return user; + return content; } public async Task>> GetRatingHistory(string username) diff --git a/LichessNET/Converters/GameStatsConverter.cs b/LichessNET/Converters/GameStatsConverter.cs new file mode 100644 index 0000000..1cff63d --- /dev/null +++ b/LichessNET/Converters/GameStatsConverter.cs @@ -0,0 +1,49 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using LichessNET.Entities.Enumerations; +using LichessNET.Entities.Game; +using LichessNET.Entities.Interfaces; +using LichessNET.Entities.Stats; + +namespace LichessNET.Converters; + +public class GameStatsConverter : JsonConverter> +{ + public override Dictionary? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var output = new Dictionary(); + using (JsonDocument doc = JsonDocument.ParseValue(ref reader)) + { + foreach (JsonProperty property in doc.RootElement.EnumerateObject()) + { + Console.WriteLine(property.Name); + if (Gamemode.TryParse(property.Name, true, out Gamemode gamemode)) + { + IGameStats? stats = null; + if (property.Value.TryGetProperty("games", out _)) + { + stats = JsonSerializer.Deserialize(property.Value, new JsonSerializerOptions(){PropertyNameCaseInsensitive = true}); + } + else if (property.Value.TryGetProperty("runs", out _)) + { + stats = JsonSerializer.Deserialize(property.Value, new JsonSerializerOptions(){PropertyNameCaseInsensitive = true}); + } + + if (stats != null) + { + output.Add(gamemode, stats); + } + } + } + + } + + return output; + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/LichessNET/Entities/Social/LichessUser.cs b/LichessNET/Entities/Social/LichessUser.cs index 2870d38..8e2d2f9 100644 --- a/LichessNET/Entities/Social/LichessUser.cs +++ b/LichessNET/Entities/Social/LichessUser.cs @@ -1,4 +1,7 @@ -using LichessNET.Entities.Enumerations; +using System.Text.Json.Serialization; +using LichessNET.Converters; +using LichessNET.Entities.Enumerations; +using LichessNET.Entities.Interfaces; using LichessNET.Entities.Stats; using Newtonsoft.Json.Linq; @@ -25,14 +28,17 @@ public class LichessUser /// If the data is fetched in the request, the ratings will be set here. /// The dictionary only contains those gamemodes as key, which were fetched. /// - public Dictionary? Ratings { get; set; } + [JsonPropertyName("perfs")] + [JsonConverter(typeof(GameStatsConverter))] + public Dictionary? Ratings { get; set; } /// /// Current flair of the user /// public string? Flair { get; set; } - - private ulong? CreatedAt { get; set; } + + [JsonConverter(typeof(MillisecondUnixConverter))] + public DateTime? CreatedAt { get; set; } /// /// Will be set to true if the user profile is disabled @@ -42,19 +48,20 @@ public class LichessUser /// /// Will be set to true if the account is flagged for TOS violations /// - public bool? TosViolation { get; set; } + public bool TosViolation { get; set; } /// /// The LichessProfile of the user /// public LichessProfile? Profile { get; set; } - private ulong? SeenAt { get; set; } + [JsonConverter(typeof(MillisecondUnixConverter))] + public DateTime? SeenAt { get; set; } /// /// If set to true, this user is an active patron of lichess /// - public bool? Patron { get; set; } + public bool Patron { get; set; } /// /// Set to true if the user is a verfied user @@ -69,43 +76,8 @@ public class LichessUser /// /// Title of this user as string /// - internal string? title { get; set; } - - /// - /// The Title as an enumeration. - /// If the user has no title, Title.None will be returned - /// - public Title Title - { - get - { - switch (title) - { - case "CM": - return Title.CM; - case "FM": - return Title.FM; - case "IM": - return Title.IM; - case "GM": - return Title.GM; - - case "WCM": - return Title.WCM; - case "WFM": - return Title.WFM; - case "WIM": - return Title.WIM; - case "WGM": - return Title.WGM; - - case "LM": - return Title.LM; - default: - return Title.None; - } - } - } + [JsonConverter(typeof(JsonStringEnumConverter))] + public Title? Title { get; set; } /// /// The game count stats @@ -118,11 +90,15 @@ public Title Title public bool? Streaming { get; set; } + public string? Url { get; set; } + public string? Playing { get; set; } /// /// Set to true if the user allows being followed /// public bool? Followable { get; set; } + //TODO Add streamer property + /// /// Set to true if the user is following /// From 219c264fa85db7262778e8a303358973e0c36e5e Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Fri, 21 Feb 2025 15:46:00 +0300 Subject: [PATCH 19/23] Now you can get user's playtime, playtimestats were renamed to playtime --- LichessNET/API/AccountAPI.cs | 3 +- .../Converters/SecondsToTimeSpanConverter.cs | 38 +++++++++++++++++++ LichessNET/Entities/Social/LichessUser.cs | 3 +- .../Stats/{PlaytimeStats.cs => Playtime.cs} | 10 ++--- 4 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 LichessNET/Converters/SecondsToTimeSpanConverter.cs rename LichessNET/Entities/Stats/{PlaytimeStats.cs => Playtime.cs} (52%) diff --git a/LichessNET/API/AccountAPI.cs b/LichessNET/API/AccountAPI.cs index 29ba0d1..75b2b6d 100644 --- a/LichessNET/API/AccountAPI.cs +++ b/LichessNET/API/AccountAPI.cs @@ -40,8 +40,7 @@ public async Task GetOwnProfile() var request = GetRequestScaffold("api/account"); var response = await SendRequest(request); - var content = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(content); + return await response.Content.ReadFromJsonAsync(); } /// diff --git a/LichessNET/Converters/SecondsToTimeSpanConverter.cs b/LichessNET/Converters/SecondsToTimeSpanConverter.cs new file mode 100644 index 0000000..52e9f7c --- /dev/null +++ b/LichessNET/Converters/SecondsToTimeSpanConverter.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using LichessNET.Entities.Stats; + +namespace LichessNET.Converters; + +public class SecondsToTimeSpanConverter : JsonConverter +{ + public override Playtime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Playtime playtime = new Playtime(); + playtime.TotalSpan = TimeSpan.Zero; + playtime.TvSpan = TimeSpan.Zero; + + using (var doc = JsonDocument.ParseValue(ref reader)) + { + var stats = doc.RootElement.EnumerateObject(); + foreach (JsonProperty property in stats) + { + if (property.Name == "total") + { + playtime.TotalSpan = TimeSpan.FromSeconds(property.Value.GetInt64()); + } + else if (property.Name == "tv") + { + playtime.TvSpan = TimeSpan.FromSeconds(property.Value.GetInt64()); + } + } + } + + return playtime; + } + + public override void Write(Utf8JsonWriter writer, Playtime value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/LichessNET/Entities/Social/LichessUser.cs b/LichessNET/Entities/Social/LichessUser.cs index 8e2d2f9..b9a3b8a 100644 --- a/LichessNET/Entities/Social/LichessUser.cs +++ b/LichessNET/Entities/Social/LichessUser.cs @@ -71,7 +71,8 @@ public class LichessUser /// /// The total playtime /// - public PlaytimeStats? PlayTime { get; set; } + [JsonConverter(typeof(SecondsToTimeSpanConverter))] + public Playtime PlayTime { get; set; } /// /// Title of this user as string diff --git a/LichessNET/Entities/Stats/PlaytimeStats.cs b/LichessNET/Entities/Stats/Playtime.cs similarity index 52% rename from LichessNET/Entities/Stats/PlaytimeStats.cs rename to LichessNET/Entities/Stats/Playtime.cs index e548022..73b79fb 100644 --- a/LichessNET/Entities/Stats/PlaytimeStats.cs +++ b/LichessNET/Entities/Stats/Playtime.cs @@ -1,17 +1,15 @@ namespace LichessNET.Entities.Stats; -public class PlaytimeStats +public class Playtime { - public int Total; - public int Tv; /// /// The total time played by the user /// - public TimeSpan TotalSpan => TimeSpan.FromSeconds(Total); + public TimeSpan TotalSpan { get; set; } /// /// The total time seen on TV /// - public TimeSpan TvSpan => TimeSpan.FromSeconds(Tv); -} \ No newline at end of file + public TimeSpan TvSpan { get; set; } +} From ae86e3b14a1b709b36bae8a54fe7957a0bd85bfd Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Fri, 21 Feb 2025 16:38:10 +0300 Subject: [PATCH 20/23] / --- LichessNET/API/AccountAPI.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/LichessNET/API/AccountAPI.cs b/LichessNET/API/AccountAPI.cs index 75b2b6d..67650e8 100644 --- a/LichessNET/API/AccountAPI.cs +++ b/LichessNET/API/AccountAPI.cs @@ -22,10 +22,8 @@ public async Task GetAccountEmail() var request = GetRequestScaffold("api/account/email"); var response = await SendRequest(request); - var content = await response.Content.ReadAsStringAsync(); - - var emailResponse = JsonConvert.DeserializeObject(content); - return emailResponse.email.ToObject(); + var content = await response.Content.ReadFromJsonAsync>(); + return content["email"]; } /// From d517ab9f65646492e4389fe5ca4f36405669511a Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Fri, 21 Feb 2025 16:41:59 +0300 Subject: [PATCH 21/23] Changed responce.content.readasstringasync to readfromjsonasync --- LichessNET/API/AccountAPI.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LichessNET/API/AccountAPI.cs b/LichessNET/API/AccountAPI.cs index 67650e8..91a086a 100644 --- a/LichessNET/API/AccountAPI.cs +++ b/LichessNET/API/AccountAPI.cs @@ -54,10 +54,9 @@ public async Task GetAccountPreferences() var request = GetRequestScaffold("api/account/preferences"); var response = await SendRequest(request); - var content = await response.Content.ReadAsStringAsync(); - var preferences = JsonConvert.DeserializeObject(content); + var preferences = await response.Content.ReadFromJsonAsync(); return preferences; } From 4789ac75181ce71a116e657d1296b9a0b2ca4872 Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Fri, 21 Feb 2025 16:55:28 +0300 Subject: [PATCH 22/23] Changed Content.ReadAsStringAsync to Content.ReadFromJsonAsync, makes code more readable --- LichessNET/API/AccountAPI.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/LichessNET/API/AccountAPI.cs b/LichessNET/API/AccountAPI.cs index 91a086a..d365644 100644 --- a/LichessNET/API/AccountAPI.cs +++ b/LichessNET/API/AccountAPI.cs @@ -109,7 +109,8 @@ public async Task FollowPlayerAsync(string username) var request = GetRequestScaffold($"api/rel/follow/{username}"); var response = await SendRequest(request, HttpMethod.Post); - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()).ok.ToObject(); + var content = await response.Content.ReadFromJsonAsync>(); + return content["ok"]; } /// @@ -127,7 +128,8 @@ public async Task UnfollowPlayerAsync(string username) var request = GetRequestScaffold($"api/rel/unfollow/{username}"); var response = await SendRequest(request, HttpMethod.Post); - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()).ok.ToObject(); + var content = await response.Content.ReadFromJsonAsync>(); + return content["ok"]; } /// @@ -145,7 +147,8 @@ public async Task BlockPlayerAsync(string username) var request = GetRequestScaffold($"api/rel/block/{username}"); var response = await SendRequest(request, HttpMethod.Post); - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()).ok.ToObject(); + var content = await response.Content.ReadFromJsonAsync>(); + return content["ok"]; } /// @@ -163,7 +166,8 @@ public async Task UnblockPlayerAsync(string username) var request = GetRequestScaffold($"api/rel/unblock/{username}"); var response = await SendRequest(request, HttpMethod.Post); - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync())!.ok.ToObject(); + var content = await response.Content.ReadFromJsonAsync>(); + return content["ok"]; } /// From 8b2f1a4f15341c65cfa29cd6ddfe46e26ce6232a Mon Sep 17 00:00:00 2001 From: gor1kartem Date: Mon, 24 Feb 2025 19:10:36 +0300 Subject: [PATCH 23/23] Replaced content.ReadAsStringAsync with content.ReadFromJsonAsync --- LichessNET/API/UsersAPI.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/LichessNET/API/UsersAPI.cs b/LichessNET/API/UsersAPI.cs index c3b2d00..080417a 100644 --- a/LichessNET/API/UsersAPI.cs +++ b/LichessNET/API/UsersAPI.cs @@ -1,4 +1,5 @@ using System.Net.Http.Json; +using System.Text.Json; using LichessNET.Entities.Account.Performance; using LichessNET.Entities.Enumerations; using LichessNET.Entities.Social; @@ -53,10 +54,9 @@ public async Task> GetRealTimeUserStatusAsync(IEnumerab ); var response = await SendRequest(request); - var content = await response.Content.ReadAsStringAsync(); - - var userStatuses = JsonConvert.DeserializeObject>(content); - return userStatuses; + var content = await response.Content.ReadFromJsonAsync>(new JsonSerializerOptions() + { PropertyNameCaseInsensitive = true }); + return content; } ///