Skip to content

Commit

Permalink
Merge pull request #16 from gor1kartem/master
Browse files Browse the repository at this point in the history
GetOwnProfile is fixed, many other changes
  • Loading branch information
Rabergsel authored Feb 25, 2025
2 parents 6a697b6 + 8b2f1a4 commit ab8bf9b
Show file tree
Hide file tree
Showing 23 changed files with 545 additions and 186 deletions.
10 changes: 5 additions & 5 deletions LichessNET/API/APIRatelimitController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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--;
Expand Down
35 changes: 16 additions & 19 deletions LichessNET/API/AccountAPI.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,10 +22,8 @@ public async Task<string> GetAccountEmail()
var request = GetRequestScaffold("api/account/email");

var response = await SendRequest(request);
var content = await response.Content.ReadAsStringAsync();

var emailResponse = JsonConvert.DeserializeObject<dynamic>(content);
return emailResponse.email.ToObject<string>();
var content = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
return content["email"];
}

/// <summary>
Expand All @@ -39,8 +38,7 @@ public async Task<LichessUser> GetOwnProfile()

var request = GetRequestScaffold("api/account");
var response = await SendRequest(request);
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<LichessUser>(content);
return await response.Content.ReadFromJsonAsync<LichessUser>();
}

/// <summary>
Expand All @@ -56,10 +54,9 @@ public async Task<AccountPreferences> GetAccountPreferences()
var request = GetRequestScaffold("api/account/preferences");

var response = await SendRequest(request);
var content = await response.Content.ReadAsStringAsync();


var preferences = JsonConvert.DeserializeObject<AccountPreferences>(content);
var preferences = await response.Content.ReadFromJsonAsync<AccountPreferences>();
return preferences;
}

Expand All @@ -76,10 +73,8 @@ public async Task<bool> GetKidModeStatus()
var request = GetRequestScaffold("api/account/kid");

var response = await SendRequest(request);
var content = await response.Content.ReadAsStringAsync();

var kidModeStatus = JsonConvert.DeserializeObject<dynamic>(content).kid.ToObject<bool>();
return kidModeStatus;
var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
return content["kid"];
}

/// <summary>
Expand All @@ -93,8 +88,6 @@ public async Task<bool> GetKidModeStatus()
/// </returns>
public async Task<bool> 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);

Expand All @@ -116,7 +109,8 @@ public async Task<bool> FollowPlayerAsync(string username)

var request = GetRequestScaffold($"api/rel/follow/{username}");
var response = await SendRequest(request, HttpMethod.Post);
return JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync()).ok.ToObject<bool>();
var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
return content["ok"];
}

/// <summary>
Expand All @@ -134,7 +128,8 @@ public async Task<bool> UnfollowPlayerAsync(string username)

var request = GetRequestScaffold($"api/rel/unfollow/{username}");
var response = await SendRequest(request, HttpMethod.Post);
return JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync()).ok.ToObject<bool>();
var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
return content["ok"];
}

/// <summary>
Expand All @@ -152,7 +147,8 @@ public async Task<bool> BlockPlayerAsync(string username)

var request = GetRequestScaffold($"api/rel/block/{username}");
var response = await SendRequest(request, HttpMethod.Post);
return JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync()).ok.ToObject<bool>();
var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
return content["ok"];
}

/// <summary>
Expand All @@ -170,7 +166,8 @@ public async Task<bool> UnblockPlayerAsync(string username)

var request = GetRequestScaffold($"api/rel/unblock/{username}");
var response = await SendRequest(request, HttpMethod.Post);
return JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync())!.ok.ToObject<bool>();
var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
return content["ok"];
}

/// <summary>
Expand Down
78 changes: 43 additions & 35 deletions LichessNET/API/LichessAPIClient.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,9 +19,9 @@ namespace LichessNET.API;
/// Console.WriteLine($"User is part of {team.Count} teams.");
/// </code>
/// </example>
public partial class LichessApiClient

public partial class LichessApiClient
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;

/// <summary>
Expand All @@ -31,31 +32,43 @@ public partial class LichessApiClient
/// <summary>
/// The token to access the Lichess API
/// </summary>
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<string> { value });
if (tokenTest[value] is not null)
{
_token = value;
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.GetToken());
}
else
{
throw new UnauthorizedAccessException("Invalid token");
}
}

/// <summary>
/// Creates a lichess API client, according to settings
/// </summary>
/// <param name="token">The token for accessing the lichess API</param>
public LichessApiClient(string token = "")
public LichessApiClient()
{
var loggerFactory = LoggerFactory.Create(builder => builder
.SetMinimumLevel(Constants.MinimumLogLevel)
.AddSpectreConsole());

_logger = loggerFactory.CreateLogger("LichessAPIClient");


this.Token = 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());
Expand All @@ -64,7 +77,7 @@ public LichessApiClient(string token = "")
.WithFixedIntervalRefillStrategy(1, TimeSpan.FromSeconds(5)).Build());
}


/// <summary>
/// Gets the UriBuilder objects for the lichess client.
/// If something changes in the future, it will be easy to change it.
Expand Down Expand Up @@ -93,18 +106,17 @@ private HttpRequestMessage GetRequestScaffold(string endpoint, params Tuple<stri
}

private async Task<HttpResponseMessage> 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;
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
_logger.LogInformation("Request to " + request.RequestUri + " successful.");
Expand All @@ -121,16 +133,12 @@ private async Task<HttpResponseMessage> 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);
Expand Down
29 changes: 29 additions & 0 deletions LichessNET/API/OAuthAPI.cs
Original file line number Diff line number Diff line change
@@ -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<Dictionary<string, TokenInfo?>> TestTokensAsync(List<string> 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<Dictionary<string, TokenInfo>>(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", GetToken());
}
}
Loading

0 comments on commit ab8bf9b

Please sign in to comment.