s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (242 total matches found)
brycpt.lichessnetport / API/AccountAPI.cs
Game library
#nullable enable annotations

using LichessNET.Entities;
using LichessNET.Entities.Account;
using LichessNET.Entities.Social;
using LichessNET.Entities.Social.Timeline;

namespace LichessNET.API;

public partial class LichessApiClient
{
    /// <summary>
    ///     Retrieves the email address of the authenticated user.
    /// </summary>
    /// <returns>
    ///     A string representing the email address of the authenticated user.
    /// </returns>
    public async Task<string> GetAccountEmail()
    {
        await _ratelimitController.Consume("api/account", false);

        var request = GetRequestScaffold("api/account/email");

        var response = await SendRequest(request);
        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
        return content["email"];
    }

    /// <summary>
    /// Retrieves the profile information of the authenticated user from the Lichess platform.
    /// </summary>
    /// <returns>
    /// An instance of <see cref="LichessUser"/> representing the profile details of the user.
    /// </returns>
    public async Task<LichessUser> GetOwnProfile()
    {
        await _ratelimitController.Consume("api/account", false);

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

    /// <summary>
    /// Retrieves the account preferences of the authenticated user.
    /// </summary>
    /// <returns>
    /// An instance of <see cref="AccountPreferences"/> representing the user's account preferences.
    /// </returns>
    public async Task<AccountPreferences> GetAccountPreferences()
    {
        await _ratelimitController.Consume("api/account", false);

        var request = GetRequestScaffold("api/account/preferences");

        var response = await SendRequest(request);


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

    /// <summary>
    ///     Determines whether the authenticated user's account is in kid mode.
    /// </summary>
    /// <returns>
    ///     A boolean value indicating if the user's account is set to kid mode.
    /// </returns>
    public async Task<bool> GetKidModeStatus()
    {
        await _ratelimitController.Consume("api/account", false);

        var request = GetRequestScaffold("api/account/kid");

        var response = await SendRequest(request);
        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
        return content["kid"];
    }

    /// <summary>
    /// Sets the kid mode status for the authenticated account.
    /// </summary>
    /// <param name="enable">
    /// A boolean value indicating whether to enable or disable kid mode.
    /// </param>
    /// <returns>
    /// A boolean indicating whether the operation was successful.
    /// </returns>
    public async Task<bool> SetKidModeStatus(bool enable)
    {
        var request = GetRequestScaffold("api/account/kid", Tuple.Create("v", enable.ToString()));
        var response = await SendRequest(request, "POST");

                var result = LichessJson.Deserialize<Dictionary<string, bool>>(await response.Content.ReadAsStringAsync());
        return result != null && result.TryGetValue("ok", out var ok) && ok;
    }

    /// <summary>
    /// Follows a player with the specified username on Lichess.
    /// </summary>
    /// <param name="username">
    /// The username of the player to follow.
    /// </param>
    /// <returns>
    /// A boolean value indicating whether the operation was successful.
    /// </returns>
    public async Task<bool> FollowPlayerAsync(string username)
    {
        await _ratelimitController.Consume("api/account", false);

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

    /// <summary>
    /// Unfollows a specified player using their username.
    /// </summary>
    /// <param name="username">
    /// The username of the player to unfollow.
    /// </param>
    /// <returns>
    /// A boolean value indicating whether the operation was successful.
    /// </returns>
    public async Task<bool> UnfollowPlayerAsync(string username)
    {
        await _ratelimitController.Consume("api/account", false);

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

    /// <summary>
    /// Blocks a player on Lichess based on their username.
    /// </summary>
    /// <param name="username">
    /// The username of the player to be blocked.
    /// </param>
    /// <returns>
    /// A boolean indicating whether the player was successfully blocked.
    /// </returns>
    public async Task<bool> BlockPlayerAsync(string username)
    {
        await _ratelimitController.Consume("api/account", false);

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

    /// <summary>
    /// Unblocks a player specified by username.
    /// </summary>
    /// <param name="username">
    /// The username of the player to unblock.
    /// </param>
    /// <returns>
    /// A boolean value indicating whether the player was successfully unblocked.
    /// </returns>
    public async Task<bool> UnblockPlayerAsync(string username)
    {
        await _ratelimitController.Consume("api/account", false);

        var request = GetRequestScaffold($"api/rel/unblock/{username}");
        var response = await SendRequest(request, "POST");
        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
        return content["ok"];
    }

    /// <summary>
    /// Gets the timeline of the authenticated user.
    /// </summary>
    /// <param name="since">When the timeline should start</param>
    /// <param name="nb">The number of timeline events that should be loaded. Defaults to 15, the maximum is 50.</param>
    /// <returns>A Timeline object</returns>
    public async Task<Timeline?> GetTimelineAsync(DateTime since, int nb = 15)
    {
        await _ratelimitController.Consume("api/timeline", false);

        var unixTimestamp = new DateTimeOffset(since).ToUnixTimeMilliseconds();
        var request = GetRequestScaffold($"api/timeline",
            new Tuple<string, string>("since", unixTimestamp.ToString()),
            new Tuple<string, string>("nb", nb.ToString()));

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

                var root = LichessJson.Parse(content);
        var timeline = new Timeline
        {
            Entries = new List<TimelineEntry>(),
            Users = root.TryGetProperty("users", out var users)
                ? users.Deserialize<Dictionary<string, TimelineUser>>(LichessJson.Options) ?? new Dictionary<string, TimelineUser>()
                : new Dictionary<string, TimelineUser>()
        };

        if (root.TryGetProperty("entries", out var entries))
        {
            foreach (var entryElement in entries.EnumerateArray())
            {
                var type = entryElement.TryGetProperty("type", out var typeElement) ? typeElement.GetString() : null;
                var date = entryElement.TryGetProperty("date", out var dateElement) ? dateElement.GetInt64() : 0;
                var data = entryElement.TryGetProperty("data", out var dataElement)
                    ? TimelineEventDataConverter.DeserializeData(type, dataElement, LichessJson.Options)
                    : new UnknownEventData();

                timeline.Entries.Add(new TimelineEntry
                {
                    Type = type,
                    Date = date,
                    Data = data
                });
            }
        }

        return timeline;
    }
}



brycpt.lichessnetport / Code/Entities/Enumerations/Speed.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Enumerations;

public enum Speed
{
    UltraBullet, 
    Bullet,
    Blitz,
    Rapid,
    Classical,
    Correspondence,
}

brycpt.lichessnetport / Code/Entities/Social/Timeline/TimelineEventData.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Social.Timeline
{
    public abstract class TimelineEventData
    {
    }

    public class FollowEventData : TimelineEventData
    {
        public string U1 { get; set; }
        public string U2 { get; set; }
    }

    public class UnknownEventData : TimelineEventData
    {
    }

    public class TeamJoinEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string TeamId { get; set; }
    }

    public class TeamCreateEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string TeamId { get; set; }
    }

    public class ForumPostEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string TopicId { get; set; }
        public string TopicName { get; set; }
        public string PostId { get; set; }
    }

    public class UblogPostEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string Id { get; set; }
        public string Slug { get; set; }
        public string Title { get; set; }
    }

    public class TourJoinEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string TourId { get; set; }
        public string TourName { get; set; }
    }

    public class GameEndEventData : TimelineEventData
    {
        public string FullId { get; set; }
        public string Perf { get; set; }
        public string Opponent { get; set; }
        public bool Win { get; set; }
    }

    public class SimulCreateEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string SimulId { get; set; }
        public string SimulName { get; set; }
    }

    public class SimulJoinEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string SimulId { get; set; }
        public string SimulName { get; set; }
    }

    public class StudyLikeEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string StudyId { get; set; }
        public string StudyName { get; set; }
    }

    public class PlanStartEventData : TimelineEventData
    {
        public string UserId { get; set; }
    }

    public class PlanRenewEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public int Months { get; set; }
    }

    public class BlogPostEventData : TimelineEventData
    {
        public string Id { get; set; }
        public string Slug { get; set; }
        public string Title { get; set; }
    }

    public class UblogPostLikeEventData : TimelineEventData
    {
        public string UserId { get; set; }
        public string Id { get; set; }
        public string Title { get; set; }
    }

    public class StreamStartEventData : TimelineEventData
    {
        public string Id { get; set; }
        public string Title { get; set; }
    }
}

brycpt.lichessnetport / Code/Entities/Social/Timeline/TimelineEventDataConverter.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Social.Timeline;

internal class TimelineEventDataConverter : JsonConverter<TimelineEventData>
{
    public override TimelineEventData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var document = JsonDocument.ParseValue(ref reader);
        return DeserializeData(null, document.RootElement, options);
    }

    public override void Write(Utf8JsonWriter writer, TimelineEventData value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, (object)value, options);
    }

    internal static TimelineEventData DeserializeData(string type, JsonElement data, JsonSerializerOptions options)
    {
        if (string.IsNullOrWhiteSpace(type) && data.TryGetProperty("type", out var embeddedType))
            type = embeddedType.GetString();

        if (data.TryGetProperty("data", out var nestedData))
            data = nestedData;

                TimelineEventData result = type switch
        {
            "follow" => data.Deserialize<FollowEventData>(options),
            "team-join" => data.Deserialize<TeamJoinEventData>(options),
            "team-create" => data.Deserialize<TeamCreateEventData>(options),
            "forum-post" => data.Deserialize<ForumPostEventData>(options),
            "ublog-post" => data.Deserialize<UblogPostEventData>(options),
            "tour-join" => data.Deserialize<TourJoinEventData>(options),
            "game-end" => data.Deserialize<GameEndEventData>(options),
            "simul-create" => data.Deserialize<SimulCreateEventData>(options),
            "simul-join" => data.Deserialize<SimulJoinEventData>(options),
            "study-like" => data.Deserialize<StudyLikeEventData>(options),
            "plan-start" => data.Deserialize<PlanStartEventData>(options),
            "plan-renew" => data.Deserialize<PlanRenewEventData>(options),
            "blog-post" => data.Deserialize<BlogPostEventData>(options),
            "ublog-post-like" => data.Deserialize<UblogPostLikeEventData>(options),
            "stream-start" => data.Deserialize<StreamStartEventData>(options),
            _ => null
        };

        return result ?? new UnknownEventData();
    }
}


brycpt.lichessnetport / Converters/PermissionJsonConverter.cs
Game library
#nullable enable annotations

using System.Text.Json;
using System.Text.Json.Serialization;
using LichessNET.Entities.Enumerations;

namespace LichessNET.Converters;

public class PermissionJsonConverter : JsonConverter<List<TokenPermission>>
{
    public override List<TokenPermission>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {

        var permissions = reader.GetString();
        string[] permissionsList = permissions.Split(',');
        List<TokenPermission> tokenPermissions = new List<TokenPermission>();

        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<TokenPermission> value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

brycpt.lichessnetport / Entities/Game/GameStream.cs
Game library
#nullable enable annotations

using LichessNET.API;

namespace LichessNET.Entities.Game;

/// <summary>
/// Represents a stream for a Lichess game, handling real-time updates of moves and game information.
/// </summary>
public class GameStream
{
    public delegate void GameInfoFetchedHandler(object sender, OngoingGame game);
    public delegate void MoveUpdateHandler(object sender, Move move);

    private readonly Dictionary<string, OngoingGame> _games = new Dictionary<string, OngoingGame>();
    private readonly LichessStream _stream;

    public GameStream(string requestUri, string method = "GET")
        : this(requestUri, method, CancellationToken.None)
    {
    }

    public GameStream(string requestUri, string method, CancellationToken cancellationToken)
    {
        _stream = new LichessStream(requestUri, method);
        _stream.GameUpdateReceived += ProcessData;
        _ = _stream.StreamGameAsync(cancellationToken);
    }

    public event MoveUpdateHandler? OnMoveMade;
    public event GameInfoFetchedHandler? OnGameInfoFetched;

    private void ProcessData(object sender, JsonElement data)
    {
        FetchDataForNewGame(data);
        FetchDataForMove(data);
    }

    private void FetchDataForNewGame(JsonElement data)
    {
        if (!data.TryGetProperty("id", out var idProperty))
            return;

        var gameId = idProperty.GetString();
        if (string.IsNullOrWhiteSpace(gameId))
            return;

        var game = new OngoingGame
        {
            GameId = gameId,
            PlysAtInitFen = data.TryGetProperty("turns", out var turns) ? turns.GetInt32() : 0
        };

        _games[gameId] = game;
        OnGameInfoFetched?.Invoke(this, game);
    }

    private void FetchDataForMove(JsonElement data)
    {
        if (!data.TryGetProperty("lm", out var lastMoveProperty))
            return;

        var gameId = _games.Count == 1 ? _games.First().Key : string.Empty;
        if (string.IsNullOrWhiteSpace(gameId) || !_games.TryGetValue(gameId, out var game))
            return;

        game.Moves ??= new List<Move>();
        var fen = data.TryGetProperty("fen", out var fenProperty) ? fenProperty.GetString() : string.Empty;
        var move = new Move
        {
            Notation = lastMoveProperty.GetString(),
            IsWhite = fen?.Contains(" w ") ?? false,
            GameID = gameId,
            MoveNumber = (game.Moves.Count / 2) + 1
        };

        game.Moves.Add(move);
        OnMoveMade?.Invoke(this, move);
    }
}

brycpt.lichessnetport / Entities/Game/TimeControl.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Game;

public class TimeControl
{
    public string Type { get; set; }
    public int Limit { get; set; }
    public int Increment { get; set; }
    public string Show { get; set; }
}

brycpt.lichessnetport / Entities/Puzzle/Dashboard/PuzzleResults.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Puzzle.Dashboard;

public class PuzzleResults
{
    public int FirstWins { get; set; }
    public int Nb { get; set; }
    public int Performance { get; set; }
    public int PuzzleRatingAvg { get; set; }
    public int ReplayWins { get; set; }
}

brycpt.lichessnetport / Entities/Social/Stream/Streamer.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Social.Stream;

public class Streamer
{
    public string Name { get; set; }
    public string Headline { get; set; }
    public string Description { get; set; }
    public string Twitch { get; set; }
    public string YouTube { get; set; }
    public string Image { get; set; }
}

brycpt.lichessnetport / Entities/Stats/Performance/Glicko.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Account.Performance;

public class Glicko
{
    public double Rating { get; set; }
    public double Deviation { get; set; }
    public bool Provisional { get; set; }
}

brycpt.lichessnetport / Entities/Stats/Performance/PerformanceStats.cs
Game library
#nullable enable annotations

using System.Text.Json.Serialization;

namespace LichessNET.Entities.Account.Performance;

public class PerformanceStats
{
    public string User { get; set; }
    public Perf Perf { get; set; }
    public int Rank { get; set; }
    public double Percentile { get; set; }
    public Stat Stat { get; set; }
}

brycpt.lichessnetport / Entities/Stats/Performance/StatEntities.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Account.Performance;

public class PerfType
{
    public string Key { get; set; }
    public string Name { get; set; }
}

public class Highest
{
    public int Int { get; set; }
    public DateTime At { get; set; }
    public string GameId { get; set; }
}

public class BestWins
{
    public List<Result> Results { get; set; }
}

public class WorstLosses
{
    public List<Result> Results { get; set; }
}

public class Result
{
    public int OpRating { get; set; }
    public OpId OpId { get; set; }
    public DateTime At { get; set; }
    public string GameId { get; set; }
}

public class OpId
{
    public string Id { get; set; }
    public string Name { get; set; }
}

public class Count
{
    public int All { get; set; }
    public int Rated { get; set; }
    public int Win { get; set; }
    public int Loss { get; set; }
    public int Draw { get; set; }
    public int Tour { get; set; }
    public int Berserk { get; set; }
    public double OpAvg { get; set; }
    public int Seconds { get; set; }
    public int Disconnects { get; set; }
}

public class ResultStreak
{
    public Streak Win { get; set; }
    public Streak Loss { get; set; }
}

public class Streak
{
    public Cur Cur { get; set; }
    public Max Max { get; set; }
}

public class Cur
{
    public int V { get; set; }
}

public class Max
{
    public int V { get; set; }
    public From From { get; set; }
    public To To { get; set; }
}

public class From
{
    public DateTime At { get; set; }
    public string GameId { get; set; }
}

public class To
{
    public DateTime At { get; set; }
    public string GameId { get; set; }
}

public class UserId
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Title { get; set; }
}

public class PlayStreak
{
    public Nb Nb { get; set; }
    public Time Time { get; set; }
    public DateTime LastDate { get; set; }
}

public class Nb
{
    public Cur Cur { get; set; }
    public Max Max { get; set; }
}

public class Time
{
    public Cur Cur { get; set; }
    public Max Max { get; set; }
}

brycpt.lichessnetport / Entities/Teams/LichessTeam.cs
Game library
#nullable enable annotations

using LichessNET.Entities.Social;

namespace LichessNET.Entities.Teams;

/// <summary>
/// Represents a team on Lichess, containing information such as its ID, name, description, and members.
/// </summary>
public class LichessTeam
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Flair { get; set; }
    public TeamLeader Leader { get; set; }
    public List<TeamLeader> Leaders { get; set; }
    public int NbMembers { get; set; }
    public bool Open { get; set; }
    public bool Joined { get; set; }
    public bool Requested { get; set; }
}

brycpt.lichessnetport / Entities/Teams/TeamLeader.cs
Game library
#nullable enable annotations

namespace LichessNET.Entities.Social;

/// <summary>
/// Represents a leader of a team on Lichess, providing properties for flair, identification, name, and patron status.
/// </summary>
public class TeamLeader
{
    public string Flair { get; set; }
    public string Id { get; set; }
    public string Name { get; set; }
    public bool Patron { get; set; }
}

brycpt.lichessnetport / Internal/LichessLog.cs
Game library
#nullable enable annotations

namespace LichessNET.Internal;

internal sealed class LichessLog
{
	private readonly string _name;
	private readonly bool _enabled;

	public LichessLog( string name, bool enabled = true )
	{
		_name = name;
		_enabled = enabled;
	}

	public void Debug( string message )
	{
		if ( _enabled )
			Log.Info( $"[{_name}] {message}" );
	}

	public void Information( string message )
	{
		if ( _enabled )
			Log.Info( $"[{_name}] {message}" );
	}

	public void Warning( string message )
	{
		if ( _enabled )
			Log.Warning( $"[{_name}] {message}" );
	}

	public void Error( string message )
	{
		if ( _enabled )
			Log.Error( $"[{_name}] {message}" );
	}
}



brycpt.lichessnetport / API/LichessApiException.cs
Game library
#nullable enable annotations

namespace LichessNET.API;

/// <summary>
/// A sanitized failure returned by the Lichess API.
/// </summary>
public sealed class LichessApiException : HttpRequestException
{
    public LichessApiException(HttpStatusCode? statusCode = null)
        : this(CreateMessage(statusCode), statusCode)
    {
    }

    private LichessApiException(string message, HttpStatusCode? statusCode)
        : base(message, null, statusCode)
    {
    }

    internal static LichessApiException InvalidResponse(HttpStatusCode statusCode)
    {
        return new LichessApiException(
            "Lichess API returned a response that could not be processed.", statusCode);
    }

    private static string CreateMessage(HttpStatusCode? statusCode)
    {
        return statusCode.HasValue
            ? $"Lichess API request failed with HTTP status {(int)statusCode.Value} ({statusCode.Value})."
            : "Lichess API request failed before an HTTP status was received.";
    }
}
brycpt.lichessnetport / API/OAuthAPI.cs
Game library
#nullable enable annotations

using LichessNET.Entities.OAuth;

namespace LichessNET.API;

public partial class LichessApiClient
{
    /// <summary>
    /// Tests OAuth tokens without placing them in the request URI or logs.
    /// </summary>
    public Task<Dictionary<string, TokenInfo?>> TestTokensAsync(List<string> tokens)
    {
        return TestTokensAsync(tokens, CancellationToken.None);
    }

    public async Task<Dictionary<string, TokenInfo?>> TestTokensAsync(List<string> tokens,
        CancellationToken cancellationToken = default)
    {
        if (tokens == null)
            throw new ArgumentNullException(nameof(tokens));

        if (tokens.Count == 0 || tokens.Count > 1000)
            throw new ArgumentOutOfRangeException(nameof(tokens),
                "Between 1 and 1000 OAuth tokens are required.");

        if (tokens.Any(string.IsNullOrWhiteSpace))
            throw new ArgumentException("OAuth tokens cannot be empty.", nameof(tokens));

        cancellationToken.ThrowIfCancellationRequested();

        var request = GetRequestScaffold("api/token/test");
        var requestContent = new StringContent(
            string.Join(',', tokens), Encoding.UTF8, "text/plain");
        var response = await SendRequest(request, "POST", useToken: false,
            cancellationToken: cancellationToken, requestContent: requestContent);

        cancellationToken.ThrowIfCancellationRequested();
        try
        {
            return await response.Content.ReadFromJsonAsync<Dictionary<string, TokenInfo?>>(
                       LichessJson.Options)
                   ?? new Dictionary<string, TokenInfo?>();
        }
        catch (Exception exception) when (exception is not OperationCanceledException)
        {
            throw LichessApiException.InvalidResponse(response.StatusCode);
        }
    }

    /// <summary>
    /// Deletes a token.
    /// </summary>
    public async Task DeleteTokenAsync(string token)
    {
        var request = GetRequestScaffold("api/token");
        request.Headers["Authorization"] = "Bearer " + token;
        await SendRequest(request, "DELETE", useToken: false);
    }
}

brycpt.lichessnetport / Code/API/TeamAPI.cs
Game library
#nullable enable annotations

using LichessNET.Entities.Teams;
using LichessNET.Entities.Tournament;
using LichessNET.Entities.Tournament.Arena;

namespace LichessNET.API;

public partial class LichessApiClient
{
    /// <summary>
    ///     Get the team with the specified ID
    /// </summary>
    /// <param name="teamId">The ID of the team</param>
    /// <returns>The team with the specified ID</returns>
    public async Task<LichessTeam> GetTeamAsync(string teamId)
    {
        await _ratelimitController.Consume();
        var request = GetRequestScaffold($"api/team/{teamId}");
        var response = await SendRequest(request);

        var content = await response.Content.ReadAsStringAsync();
        var team = LichessJson.Deserialize<LichessTeam>(content);

        return team;
    }

    /// <summary>
    /// Gets the most populat teams on Lichess in a paginated manner.
    /// </summary>
    /// <param name="page">Page number, defaults to 1</param>
    /// <returns>The list of the most popular teams on the specified page.</returns>
    public async Task<List<LichessTeam>> GetPopularTeamsAsync(int page = 1)
    {
        await _ratelimitController.Consume();
        var request = GetRequestScaffold("api/team/all", new Tuple<string, string>("page", page.ToString()));
        var response = await SendRequest(request);

        var content = await response.Content.ReadAsStringAsync();
                var json = LichessJson.Parse(content);
        return json.TryGetProperty("currentPageResults", out var results)
            ? results.Deserialize<List<LichessTeam>>(LichessJson.Options) ?? new List<LichessTeam>()
            : new List<LichessTeam>();
    }

    /// <summary>
    /// Get the list of teams that the specified user is in.
    /// </summary>
    /// <param name="username">The username of the user</param>
    /// <returns>A list of teams that the specified user is in</returns>
    public async Task<List<LichessTeam>> GetTeamOfUserAsync(string username)
    {
        await _ratelimitController.Consume();

        var request = GetRequestScaffold($"api/team/of/{username}");
        var response = await SendRequest(request);

        var content = await response.Content.ReadAsStringAsync();
        var teams = LichessJson.Deserialize<List<LichessTeam>>(content);

        return teams;
    }

    /// <summary>
    /// This funciton gets the members of a team, in chronoclogical order of joining the team. (Latest first),
    /// with up to 5000 members.
    /// </summary>
    /// <param name="teamId">ID of the Team</param>
    /// <returns></returns>
    public async Task<List<TeamMember>> GetTeamMembersAsync(string teamId)
    {
        await _ratelimitController.Consume();
        var request = GetRequestScaffold($"api/team/{teamId}/users");

        var response = await SendRequest(request);

        var content = await response.Content.ReadAsStringAsync();
        var members = LichessJson.Deserialize<List<TeamMember>>(content);

        return members;
    }

    /// <summary>
    /// Retrieves all Swiss tournaments of a team.
    /// </summary>
    /// <param name="teamId">The ID of the team.</param>
    /// <param name="max">The maximum number of tournaments to download. Default is 100.</param>
    /// <returns>A task representing the asynchronous operation, containing the list of Swiss tournaments.</returns>
    public async Task<List<SwissTournament>> GetTeamSwissTournamentsAsync(string teamId, int max = 100)
    {
        await _ratelimitController.Consume();

        var endpoint = $"api/team/{teamId}/swiss?max={max}";
        var request = GetRequestScaffold(endpoint);

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

        var tournaments = new List<SwissTournament>();
        foreach (var line in content.Split('\n')) { if (string.IsNullOrWhiteSpace(line)) continue; var tournament = LichessJson.Deserialize<SwissTournament>(line.Trim()); if (tournament != null) tournaments.Add(tournament); }

        return tournaments;
    }

    /// <summary>
    /// Retrieves all Arena tournaments relevant to a team.
    /// </summary>
    /// <param name="teamId">The ID of the team.</param>
    /// <param name="max">The maximum number of tournaments to download. Default is 100.</param>
    /// <returns>A task representing the asynchronous operation, containing the list of Arena tournaments.</returns>
    public async Task<List<ArenaTournament>> GetTeamArenaTournamentsAsync(string teamId, int max = 100)
    {
        await _ratelimitController.Consume();

        var endpoint = $"api/team/{teamId}/arena?max={max}";
        var request = GetRequestScaffold(endpoint);

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

        var tournaments = new List<ArenaTournament>();
        foreach (var line in content.Split('\n')) { if (string.IsNullOrWhiteSpace(line)) continue; var tournament = LichessJson.Deserialize<ArenaTournament>(line.Trim()); if (tournament != null) tournaments.Add(tournament); }

        return tournaments;
    }

    /// <summary>
    /// Joins a team.
    /// </summary>
    /// <param name="teamId">The ID of the team.</param>
    /// <param name="message">The message for the team join request, required if the team manually reviews admission requests.</param>
    /// <param name="password">The password for the team, if required.</param>
    /// <returns>A task representing the asynchronous operation, containing the join team response.</returns>
    public async Task<bool> JoinTeamAsync(string teamId, string message = null, string password = null)
    {
        await _ratelimitController.Consume();

        var endpoint = $"team/{teamId}/join";
        var request = GetRequestScaffold(endpoint);

        var formData = new Dictionary<string, string>();
        if (!string.IsNullOrEmpty(message))
        {
            formData.Add("message", message);
        }

        if (!string.IsNullOrEmpty(password))
        {
            formData.Add("password", password);
        }

        var response = await SendRequest(request, "POST", formData: formData);
        var content = await response.Content.ReadAsStringAsync();

        return content.Contains("true");
    }

    /// <summary>
    /// Leaves a team.
    /// </summary>
    /// <param name="teamId">The ID of the team.</param>
    /// <returns>A task representing the asynchronous operation, containing the leave team response.</returns>
    public async Task<bool> LeaveTeamAsync(string teamId)
    {
        await _ratelimitController.Consume();

        var endpoint = $"team/{teamId}/quit";
        var request = GetRequestScaffold(endpoint);

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

        return content.Contains("true");
    }
}




brycpt.lichessnetport / Code/Entities/Game/GamePlayer.cs
Game library
#nullable enable annotations

using LichessNET.Entities.Enumerations;

namespace LichessNET.Entities.Game;

/// <summary>
/// Represents a player in a chess game, including their name, title, and rating.
/// This class will be used when parsing PGN Files
/// </summary>
public class GamePlayer
{
    public string Name { get; set; }

    //TODO: Add serialization for Title
    public Title? Title { get; set; }
    public int Rating { get; set; }
}

brycpt.lichessnetport / Entities/Board/BoardModels.cs
Game library
#nullable enable annotations

using System.Globalization;
using LichessNET.Entities.Enumerations;
using LichessNET.Extensions;

namespace LichessNET.Entities.Board;

public enum BoardColorPreference
{
    Random,
    White,
    Black
}

public enum BoardChatRoom
{
    Player,
    Spectator
}

public sealed class BoardSeekOptions
{
    public bool Rated { get; set; }
    public double TimeMinutes { get; set; } = 5;
    public int IncrementSeconds { get; set; }
    public int? DaysPerTurn { get; set; }
    public BoardColorPreference Color { get; set; } = BoardColorPreference.Random;
    public ChessVariant Variant { get; set; } = ChessVariant.Standard;
    public string RatingRange { get; set; }

    internal Dictionary<string, string> ToFormData()
    {
        Validate();

        var form = new Dictionary<string, string>
        {
            ["rated"] = Rated.ToString().ToLowerInvariant(),
            ["variant"] = Variant.ToApiName(),
            ["color"] = ToApiName(Color)
        };

        if (DaysPerTurn.HasValue)
        {
            form["days"] = DaysPerTurn.Value.ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            form["time"] = TimeMinutes.ToString(CultureInfo.InvariantCulture);
            form["increment"] = IncrementSeconds.ToString(CultureInfo.InvariantCulture);
        }

        if (!string.IsNullOrWhiteSpace(RatingRange))
            form["ratingRange"] = RatingRange;

        return form;
    }

    private void Validate()
    {
        if (DaysPerTurn.HasValue)
        {
            if (DaysPerTurn.Value < 1 || DaysPerTurn.Value > 14)
                throw new ArgumentOutOfRangeException(nameof(DaysPerTurn), "Days per turn must be between 1 and 14.");

            return;
        }

        if (TimeMinutes <= 0 || TimeMinutes > 180)
            throw new ArgumentOutOfRangeException(nameof(TimeMinutes), "Clock time must be between 0 and 180 minutes.");

        if (IncrementSeconds < 0 || IncrementSeconds > 180)
            throw new ArgumentOutOfRangeException(nameof(IncrementSeconds), "Increment must be between 0 and 180 seconds.");
    }

    private static string ToApiName(BoardColorPreference color)
    {
        return color switch
        {
            BoardColorPreference.White => "white",
            BoardColorPreference.Black => "black",
            _ => "random"
        };
    }
}

public sealed class BoardGameInfo
{
    [JsonPropertyName("gameId")]
    public string GameId { get; set; }

    [JsonPropertyName("id")]
    public string Id { get; set; }

    [JsonPropertyName("fullId")]
    public string FullId { get; set; }

    [JsonPropertyName("color")]
    public string Color { get; set; }

    [JsonPropertyName("fen")]
    public string Fen { get; set; }

    [JsonPropertyName("hasMoved")]
    public bool? HasMoved { get; set; }

    [JsonPropertyName("isMyTurn")]
    public bool? IsMyTurn { get; set; }

    [JsonPropertyName("lastMove")]
    public string LastMove { get; set; }

    [JsonPropertyName("source")]
    public string Source { get; set; }

    [JsonPropertyName("status")]
    public string Status { get; set; }

    [JsonPropertyName("variant")]
    [JsonConverter(typeof(BoardObjectNameConverter))]
    public string Variant { get; set; }

    [JsonPropertyName("speed")]
    public string Speed { get; set; }

    [JsonPropertyName("perf")]
    [JsonConverter(typeof(BoardObjectNameConverter))]
    public string Perf { get; set; }

    [JsonPropertyName("rated")]
    public bool? Rated { get; set; }

    [JsonPropertyName("opponent")]
    public BoardPlayerInfo Opponent { get; set; }

    [JsonPropertyName("white")]
    public BoardPlayerInfo White { get; set; }

    [JsonPropertyName("black")]
    public BoardPlayerInfo Black { get; set; }

    [JsonPropertyName("state")]
    public BoardGameState State { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JsonElement> Extra { get; set; }

    public string BestGameId => !string.IsNullOrWhiteSpace(GameId) ? GameId : Id;
}

public sealed class BoardPlayerInfo
{
    [JsonPropertyName("id")]
    public string Id { get; set; }

    [JsonPropertyName("name")]
    public string Name { get; set; }

    [JsonPropertyName("username")]
    public string Username { get; set; }

    [JsonPropertyName("title")]
    public string Title { get; set; }

    [JsonPropertyName("rating")]
    public int? Rating { get; set; }

    [JsonPropertyName("provisional")]
    public bool? Provisional { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JsonElement> Extra { get; set; }
}

public sealed class BoardClockState
{
    public int? WhiteTimeMilliseconds { get; set; }
    public int? BlackTimeMilliseconds { get; set; }
    public int? WhiteIncrementMilliseconds { get; set; }
    public int? BlackIncrementMilliseconds { get; set; }

    public TimeSpan? WhiteTime => WhiteTimeMilliseconds.HasValue
        ? TimeSpan.FromMilliseconds(WhiteTimeMilliseconds.Value)
        : null;

    public TimeSpan? BlackTime => BlackTimeMilliseconds.HasValue
        ? TimeSpan.FromMilliseconds(BlackTimeMilliseconds.Value)
        : null;

    public static BoardClockState FromState(BoardGameState state)
    {
        return new BoardClockState
        {
            WhiteTimeMilliseconds = state?.WhiteTimeMilliseconds,
            BlackTimeMilliseconds = state?.BlackTimeMilliseconds,
            WhiteIncrementMilliseconds = state?.WhiteIncrementMilliseconds,
            BlackIncrementMilliseconds = state?.BlackIncrementMilliseconds
        };
    }
}

public sealed class BoardGameState
{
    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("moves")]
    public string Moves { get; set; }

    [JsonPropertyName("wtime")]
    public int? WhiteTimeMilliseconds { get; set; }

    [JsonPropertyName("btime")]
    public int? BlackTimeMilliseconds { get; set; }

    [JsonPropertyName("winc")]
    public int? WhiteIncrementMilliseconds { get; set; }

    [JsonPropertyName("binc")]
    public int? BlackIncrementMilliseconds { get; set; }

    [JsonPropertyName("status")]
    public string Status { get; set; }

    [JsonPropertyName("winner")]
    public string Winner { get; set; }

    [JsonPropertyName("wdraw")]
    public bool? WhiteOfferingDraw { get; set; }

    [JsonPropertyName("bdraw")]
    public bool? BlackOfferingDraw { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JsonElement> Extra { get; set; }

    public BoardClockState Clock => BoardClockState.FromState(this);

    public bool IsFinished => !string.IsNullOrWhiteSpace(Status)
                              && !Status.Equals("started", StringComparison.OrdinalIgnoreCase)
                              && !Status.Equals("created", StringComparison.OrdinalIgnoreCase);

    public IReadOnlyList<string> MoveList => BoardEventParser.SplitMoves(Moves);
}

public sealed class BoardGameStartEvent
{
    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("game")]
    public BoardGameInfo Game { get; set; }
}

public sealed class BoardGameFinishEvent
{
    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("game")]
    public BoardGameInfo Game { get; set; }
}

public sealed class BoardGameFullEvent
{
    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("id")]
    public string Id { get; set; }

    [JsonPropertyName("initialFen")]
    public string InitialFen { get; set; }

    [JsonPropertyName("rated")]
    public bool? Rated { get; set; }

    [JsonPropertyName("variant")]
    [JsonConverter(typeof(BoardObjectNameConverter))]
    public string Variant { get; set; }

    [JsonPropertyName("speed")]
    public string Speed { get; set; }

    [JsonPropertyName("perf")]
    [JsonConverter(typeof(BoardObjectNameConverter))]
    public string Perf { get; set; }

    [JsonPropertyName("createdAt")]
    public long? CreatedAt { get; set; }

    [JsonPropertyName("white")]
    public BoardPlayerInfo White { get; set; }

    [JsonPropertyName("black")]
    public BoardPlayerInfo Black { get; set; }

    [JsonPropertyName("state")]
    public BoardGameState State { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JsonElement> Extra { get; set; }
}

public sealed class BoardChatLineEvent
{
    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("room")]
    public string Room { get; set; }

    [JsonPropertyName("username")]
    public string Username { get; set; }

    [JsonPropertyName("text")]
    public string Text { get; set; }
}

public static class BoardEventParser
{
    public static string GetEventType(JsonElement element)
    {
        return element.TryGetProperty("type", out var type)
            ? type.GetString()
            : string.Empty;
    }

    public static BoardGameStartEvent ParseGameStart(JsonElement element)
    {
        return element.Deserialize<BoardGameStartEvent>(LichessJson.Options);
    }

    public static BoardGameFinishEvent ParseGameFinish(JsonElement element)
    {
        return element.Deserialize<BoardGameFinishEvent>(LichessJson.Options);
    }

    public static BoardGameFullEvent ParseGameFull(JsonElement element)
    {
        return element.Deserialize<BoardGameFullEvent>(LichessJson.Options);
    }

    public static BoardGameState ParseGameState(JsonElement element)
    {
        return element.Deserialize<BoardGameState>(LichessJson.Options);
    }

    public static BoardChatLineEvent ParseChatLine(JsonElement element)
    {
        return element.Deserialize<BoardChatLineEvent>(LichessJson.Options);
    }

    public static IReadOnlyList<string> SplitMoves(string moves)
    {
        if (string.IsNullOrWhiteSpace(moves))
            return Array.Empty<string>();

        return moves.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
    }
}


/// <summary>
/// Preserves legacy string properties while accepting the object form used by
/// current Board API gameFull payloads.
/// </summary>
public sealed class BoardObjectNameConverter : JsonConverter<string>
{
    public override string? Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.String)
            return reader.GetString();

        if (reader.TokenType == JsonTokenType.Null)
            return null;

        using var document = JsonDocument.ParseValue(ref reader);
        var value = document.RootElement;
        if (value.ValueKind != JsonValueKind.Object)
            return value.ToString();

        foreach (var propertyName in new[] { "key", "name", "short" })
        {
            if (value.TryGetProperty(propertyName, out var property) &&
                property.ValueKind == JsonValueKind.String)
            {
                return property.GetString();
            }
        }

        return value.GetRawText();
    }

    public override void Write(Utf8JsonWriter writer, string value,
        JsonSerializerOptions options)
    {
        writer.WriteStringValue(value);
    }
}
Debug: View Raw JSON Response
{
    "TotalCount": 242,
    "Files": [
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "API/AccountAPI.cs",
            "FileName": "AccountAPI.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing LichessNET.Entities;\r\nusing LichessNET.Entities.Account;\r\nusing LichessNET.Entities.Social;\r\nusing LichessNET.Entities.Social.Timeline;\r\n\r\nnamespace LichessNET.API;\r\n\r\npublic partial class LichessApiClient\r\n{\r\n    /// <summary>\r\n    ///     Retrieves the email address of the authenticated user.\r\n    /// </summary>\r\n    /// <returns>\r\n    ///     A string representing the email address of the authenticated user.\r\n    /// </returns>\r\n    public async Task<string> GetAccountEmail()\r\n    {\r\n        await _ratelimitController.Consume(\"api/account\", false);\r\n\r\n        var request = GetRequestScaffold(\"api/account/email\");\r\n\r\n        var response = await SendRequest(request);\r\n        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();\r\n        return content[\"email\"];\r\n    }\r\n\r\n    /// <summary>\r\n    /// Retrieves the profile information of the authenticated user from the Lichess platform.\r\n    /// </summary>\r\n    /// <returns>\r\n    /// An instance of <see cref=\"LichessUser\"/> representing the profile details of the user.\r\n    /// </returns>\r\n    public async Task<LichessUser> GetOwnProfile()\r\n    {\r\n        await _ratelimitController.Consume(\"api/account\", false);\r\n\r\n        var request = GetRequestScaffold(\"api/account\");\r\n        var response = await SendRequest(request);\r\n        return await response.Content.ReadFromJsonAsync<LichessUser>();\r\n    }\r\n\r\n    /// <summary>\r\n    /// Retrieves the account preferences of the authenticated user.\r\n    /// </summary>\r\n    /// <returns>\r\n    /// An instance of <see cref=\"AccountPreferences\"/> representing the user's account preferences.\r\n    /// </returns>\r\n    public async Task<AccountPreferences> GetAccountPreferences()\r\n    {\r\n        await _ratelimitController.Consume(\"api/account\", false);\r\n\r\n        var request = GetRequestScaffold(\"api/account/preferences\");\r\n\r\n        var response = await SendRequest(request);\r\n\r\n\r\n        var preferences = await response.Content.ReadFromJsonAsync<AccountPreferences>();\r\n        return preferences;\r\n    }\r\n\r\n    /// <summary>\r\n    ///     Determines whether the authenticated user's account is in kid mode.\r\n    /// </summary>\r\n    /// <returns>\r\n    ///     A boolean value indicating if the user's account is set to kid mode.\r\n    /// </returns>\r\n    public async Task<bool> GetKidModeStatus()\r\n    {\r\n        await _ratelimitController.Consume(\"api/account\", false);\r\n\r\n        var request = GetRequestScaffold(\"api/account/kid\");\r\n\r\n        var response = await SendRequest(request);\r\n        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();\r\n        return content[\"kid\"];\r\n    }\r\n\r\n    /// <summary>\r\n    /// Sets the kid mode status for the authenticated account.\r\n    /// </summary>\r\n    /// <param name=\"enable\">\r\n    /// A boolean value indicating whether to enable or disable kid mode.\r\n    /// </param>\r\n    /// <returns>\r\n    /// A boolean indicating whether the operation was successful.\r\n    /// </returns>\r\n    public async Task<bool> SetKidModeStatus(bool enable)\r\n    {\r\n        var request = GetRequestScaffold(\"api/account/kid\", Tuple.Create(\"v\", enable.ToString()));\r\n        var response = await SendRequest(request, \"POST\");\r\n\r\n                var result = LichessJson.Deserialize<Dictionary<string, bool>>(await response.Content.ReadAsStringAsync());\r\n        return result != null && result.TryGetValue(\"ok\", out var ok) && ok;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Follows a player with the specified username on Lichess.\r\n    /// </summary>\r\n    /// <param name=\"username\">\r\n    /// The username of the player to follow.\r\n    /// </param>\r\n    /// <returns>\r\n    /// A boolean value indicating whether the operation was successful.\r\n    /// </returns>\r\n    public async Task<bool> FollowPlayerAsync(string username)\r\n    {\r\n        await _ratelimitController.Consume(\"api/account\", false);\r\n\r\n        var request = GetRequestScaffold($\"api/rel/follow/{username}\");\r\n        var response = await SendRequest(request, \"POST\");\r\n        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();\r\n        return content[\"ok\"];\r\n    }\r\n\r\n    /// <summary>\r\n    /// Unfollows a specified player using their username.\r\n    /// </summary>\r\n    /// <param name=\"username\">\r\n    /// The username of the player to unfollow.\r\n    /// </param>\r\n    /// <returns>\r\n    /// A boolean value indicating whether the operation was successful.\r\n    /// </returns>\r\n    public async Task<bool> UnfollowPlayerAsync(string username)\r\n    {\r\n        await _ratelimitController.Consume(\"api/account\", false);\r\n\r\n        var request = GetRequestScaffold($\"api/rel/unfollow/{username}\");\r\n        var response = await SendRequest(request, \"POST\");\r\n        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();\r\n        return content[\"ok\"];\r\n    }\r\n\r\n    /// <summary>\r\n    /// Blocks a player on Lichess based on their username.\r\n    /// </summary>\r\n    /// <param name=\"username\">\r\n    /// The username of the player to be blocked.\r\n    /// </param>\r\n    /// <returns>\r\n    /// A boolean indicating whether the player was successfully blocked.\r\n    /// </returns>\r\n    public async Task<bool> BlockPlayerAsync(string username)\r\n    {\r\n        await _ratelimitController.Consume(\"api/account\", false);\r\n\r\n        var request = GetRequestScaffold($\"api/rel/block/{username}\");\r\n        var response = await SendRequest(request, \"POST\");\r\n        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();\r\n        return content[\"ok\"];\r\n    }\r\n\r\n    /// <summary>\r\n    /// Unblocks a player specified by username.\r\n    /// </summary>\r\n    /// <param name=\"username\">\r\n    /// The username of the player to unblock.\r\n    /// </param>\r\n    /// <returns>\r\n    /// A boolean value indicating whether the player was successfully unblocked.\r\n    /// </returns>\r\n    public async Task<bool> UnblockPlayerAsync(string username)\r\n    {\r\n        await _ratelimitController.Consume(\"api/account\", false);\r\n\r\n        var request = GetRequestScaffold($\"api/rel/unblock/{username}\");\r\n        var response = await SendRequest(request, \"POST\");\r\n        var content = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();\r\n        return content[\"ok\"];\r\n    }\r\n\r\n    /// <summary>\r\n    /// Gets the timeline of the authenticated user.\r\n    /// </summary>\r\n    /// <param name=\"since\">When the timeline should start</param>\r\n    /// <param name=\"nb\">The number of timeline events that should be loaded. Defaults to 15, the maximum is 50.</param>\r\n    /// <returns>A Timeline object</returns>\r\n    public async Task<Timeline?> GetTimelineAsync(DateTime since, int nb = 15)\r\n    {\r\n        await _ratelimitController.Consume(\"api/timeline\", false);\r\n\r\n        var unixTimestamp = new DateTimeOffset(since).ToUnixTimeMilliseconds();\r\n        var request = GetRequestScaffold($\"api/timeline\",\r\n            new Tuple<string, string>(\"since\", unixTimestamp.ToString()),\r\n            new Tuple<string, string>(\"nb\", nb.ToString()));\r\n\r\n        var response = await SendRequest(request);\r\n        var content = await response.Content.ReadAsStringAsync();\r\n\r\n                var root = LichessJson.Parse(content);\r\n        var timeline = new Timeline\r\n        {\r\n            Entries = new List<TimelineEntry>(),\r\n            Users = root.TryGetProperty(\"users\", out var users)\r\n                ? users.Deserialize<Dictionary<string, TimelineUser>>(LichessJson.Options) ?? new Dictionary<string, TimelineUser>()\r\n                : new Dictionary<string, TimelineUser>()\r\n        };\r\n\r\n        if (root.TryGetProperty(\"entries\", out var entries))\r\n        {\r\n            foreach (var entryElement in entries.EnumerateArray())\r\n            {\r\n                var type = entryElement.TryGetProperty(\"type\", out var typeElement) ? typeElement.GetString() : null;\r\n                var date = entryElement.TryGetProperty(\"date\", out var dateElement) ? dateElement.GetInt64() : 0;\r\n                var data = entryElement.TryGetProperty(\"data\", out var dataElement)\r\n                    ? TimelineEventDataConverter.DeserializeData(type, dataElement, LichessJson.Options)\r\n                    : new UnknownEventData();\r\n\r\n                timeline.Entries.Add(new TimelineEntry\r\n                {\r\n                    Type = type,\r\n                    Date = date,\r\n                    Data = data\r\n                });\r\n            }\r\n        }\r\n\r\n        return timeline;\r\n    }\r\n}\r\n\r\n\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Code/Entities/Enumerations/Speed.cs",
            "FileName": "Speed.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Enumerations;\r\n\r\npublic enum Speed\r\n{\r\n    UltraBullet, \r\n    Bullet,\r\n    Blitz,\r\n    Rapid,\r\n    Classical,\r\n    Correspondence,\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Code/Entities/Social/Timeline/TimelineEventData.cs",
            "FileName": "TimelineEventData.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Social.Timeline\r\n{\r\n    public abstract class TimelineEventData\r\n    {\r\n    }\r\n\r\n    public class FollowEventData : TimelineEventData\r\n    {\r\n        public string U1 { get; set; }\r\n        public string U2 { get; set; }\r\n    }\r\n\r\n    public class UnknownEventData : TimelineEventData\r\n    {\r\n    }\r\n\r\n    public class TeamJoinEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string TeamId { get; set; }\r\n    }\r\n\r\n    public class TeamCreateEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string TeamId { get; set; }\r\n    }\r\n\r\n    public class ForumPostEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string TopicId { get; set; }\r\n        public string TopicName { get; set; }\r\n        public string PostId { get; set; }\r\n    }\r\n\r\n    public class UblogPostEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string Id { get; set; }\r\n        public string Slug { get; set; }\r\n        public string Title { get; set; }\r\n    }\r\n\r\n    public class TourJoinEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string TourId { get; set; }\r\n        public string TourName { get; set; }\r\n    }\r\n\r\n    public class GameEndEventData : TimelineEventData\r\n    {\r\n        public string FullId { get; set; }\r\n        public string Perf { get; set; }\r\n        public string Opponent { get; set; }\r\n        public bool Win { get; set; }\r\n    }\r\n\r\n    public class SimulCreateEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string SimulId { get; set; }\r\n        public string SimulName { get; set; }\r\n    }\r\n\r\n    public class SimulJoinEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string SimulId { get; set; }\r\n        public string SimulName { get; set; }\r\n    }\r\n\r\n    public class StudyLikeEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string StudyId { get; set; }\r\n        public string StudyName { get; set; }\r\n    }\r\n\r\n    public class PlanStartEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n    }\r\n\r\n    public class PlanRenewEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public int Months { get; set; }\r\n    }\r\n\r\n    public class BlogPostEventData : TimelineEventData\r\n    {\r\n        public string Id { get; set; }\r\n        public string Slug { get; set; }\r\n        public string Title { get; set; }\r\n    }\r\n\r\n    public class UblogPostLikeEventData : TimelineEventData\r\n    {\r\n        public string UserId { get; set; }\r\n        public string Id { get; set; }\r\n        public string Title { get; set; }\r\n    }\r\n\r\n    public class StreamStartEventData : TimelineEventData\r\n    {\r\n        public string Id { get; set; }\r\n        public string Title { get; set; }\r\n    }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Code/Entities/Social/Timeline/TimelineEventDataConverter.cs",
            "FileName": "TimelineEventDataConverter.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Social.Timeline;\r\n\r\ninternal class TimelineEventDataConverter : JsonConverter<TimelineEventData>\r\n{\r\n    public override TimelineEventData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\r\n    {\r\n        using var document = JsonDocument.ParseValue(ref reader);\r\n        return DeserializeData(null, document.RootElement, options);\r\n    }\r\n\r\n    public override void Write(Utf8JsonWriter writer, TimelineEventData value, JsonSerializerOptions options)\r\n    {\r\n        JsonSerializer.Serialize(writer, (object)value, options);\r\n    }\r\n\r\n    internal static TimelineEventData DeserializeData(string type, JsonElement data, JsonSerializerOptions options)\r\n    {\r\n        if (string.IsNullOrWhiteSpace(type) && data.TryGetProperty(\"type\", out var embeddedType))\r\n            type = embeddedType.GetString();\r\n\r\n        if (data.TryGetProperty(\"data\", out var nestedData))\r\n            data = nestedData;\r\n\r\n                TimelineEventData result = type switch\r\n        {\r\n            \"follow\" => data.Deserialize<FollowEventData>(options),\r\n            \"team-join\" => data.Deserialize<TeamJoinEventData>(options),\r\n            \"team-create\" => data.Deserialize<TeamCreateEventData>(options),\r\n            \"forum-post\" => data.Deserialize<ForumPostEventData>(options),\r\n            \"ublog-post\" => data.Deserialize<UblogPostEventData>(options),\r\n            \"tour-join\" => data.Deserialize<TourJoinEventData>(options),\r\n            \"game-end\" => data.Deserialize<GameEndEventData>(options),\r\n            \"simul-create\" => data.Deserialize<SimulCreateEventData>(options),\r\n            \"simul-join\" => data.Deserialize<SimulJoinEventData>(options),\r\n            \"study-like\" => data.Deserialize<StudyLikeEventData>(options),\r\n            \"plan-start\" => data.Deserialize<PlanStartEventData>(options),\r\n            \"plan-renew\" => data.Deserialize<PlanRenewEventData>(options),\r\n            \"blog-post\" => data.Deserialize<BlogPostEventData>(options),\r\n            \"ublog-post-like\" => data.Deserialize<UblogPostLikeEventData>(options),\r\n            \"stream-start\" => data.Deserialize<StreamStartEventData>(options),\r\n            _ => null\r\n        };\r\n\r\n        return result ?? new UnknownEventData();\r\n    }\r\n}\r\n\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Converters/PermissionJsonConverter.cs",
            "FileName": "PermissionJsonConverter.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System.Text.Json;\r\nusing System.Text.Json.Serialization;\r\nusing LichessNET.Entities.Enumerations;\r\n\r\nnamespace LichessNET.Converters;\r\n\r\npublic class PermissionJsonConverter : JsonConverter<List<TokenPermission>>\r\n{\r\n    public override List<TokenPermission>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\r\n    {\r\n\r\n        var permissions = reader.GetString();\r\n        string[] permissionsList = permissions.Split(',');\r\n        List<TokenPermission> tokenPermissions = new List<TokenPermission>();\r\n\r\n        foreach (string permission in permissionsList)\r\n        {\r\n            switch (permission)\r\n            {\r\n                case \"email:read\":\r\n                    tokenPermissions.Add(TokenPermission.ReadEmail);\r\n                    break;\r\n                case \"preference:read\":\r\n                    tokenPermissions.Add(TokenPermission.ReadPreferences);\r\n                    break;\r\n                case \"preference:write\":\r\n                    tokenPermissions.Add(TokenPermission.WritePreferences);\r\n                    break;\r\n                case \"follow:read\":\r\n                    tokenPermissions.Add(TokenPermission.ReadFollows);\r\n                    break;\r\n                case \"follow:write\":\r\n                    tokenPermissions.Add(TokenPermission.WriteFollows);\r\n                    break;\r\n                case \"msg:write\":\r\n                    tokenPermissions.Add(TokenPermission.WriteMessages);\r\n                    break;\r\n                case \"challenge:read\":\r\n                    tokenPermissions.Add(TokenPermission.ReadChallenges);\r\n                    break;\r\n                case \"challenge:write\":\r\n                    tokenPermissions.Add(TokenPermission.WriteChallenges);\r\n                    break;\r\n                case \"challenge:bulk\":\r\n                    tokenPermissions.Add(TokenPermission.BulkChallenges);\r\n                    break;\r\n                case \"tournament:write\":\r\n                    tokenPermissions.Add(TokenPermission.WriteTournaments);\r\n                    break;\r\n                case \"team:read\":\r\n                    tokenPermissions.Add(TokenPermission.ReadTeams);\r\n                    break;\r\n                case \"team:write\":\r\n                    tokenPermissions.Add(TokenPermission.WriteTeams);\r\n                    break;\r\n                case \"team:lead\":\r\n                    tokenPermissions.Add(TokenPermission.ManageTeams);\r\n                    break;\r\n                case \"puzzle:read\":\r\n                    tokenPermissions.Add(TokenPermission.ReadPuzzleActivity);\r\n                    break;\r\n                case \"racer:write\":\r\n                    tokenPermissions.Add(TokenPermission.WriteRaces);\r\n                    break;\r\n                case \"study:read\":\r\n                    tokenPermissions.Add(TokenPermission.ReadStudies);\r\n                    break;\r\n                case \"study:write\":\r\n                    tokenPermissions.Add(TokenPermission.WriteStudies);\r\n                    break;\r\n                case \"board:play\":\r\n                    tokenPermissions.Add(TokenPermission.PlayGames);\r\n                    break;\r\n                case \"engine:read\":\r\n                    tokenPermissions.Add(TokenPermission.ReadEngines);\r\n                    break;\r\n                case \"engine:write\":\r\n                    tokenPermissions.Add(TokenPermission.ManageEngines);\r\n                    break;\r\n            }\r\n        }\r\n\r\n        return tokenPermissions;\r\n    }\r\n\r\n    public override void Write(Utf8JsonWriter writer, List<TokenPermission> value, JsonSerializerOptions options)\r\n    {\r\n        throw new NotImplementedException();\r\n    }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Game/GameStream.cs",
            "FileName": "GameStream.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing LichessNET.API;\r\n\r\nnamespace LichessNET.Entities.Game;\r\n\r\n/// <summary>\r\n/// Represents a stream for a Lichess game, handling real-time updates of moves and game information.\r\n/// </summary>\r\npublic class GameStream\r\n{\r\n    public delegate void GameInfoFetchedHandler(object sender, OngoingGame game);\r\n    public delegate void MoveUpdateHandler(object sender, Move move);\r\n\r\n    private readonly Dictionary<string, OngoingGame> _games = new Dictionary<string, OngoingGame>();\r\n    private readonly LichessStream _stream;\r\n\r\n    public GameStream(string requestUri, string method = \"GET\")\r\n        : this(requestUri, method, CancellationToken.None)\r\n    {\r\n    }\r\n\r\n    public GameStream(string requestUri, string method, CancellationToken cancellationToken)\r\n    {\r\n        _stream = new LichessStream(requestUri, method);\r\n        _stream.GameUpdateReceived += ProcessData;\r\n        _ = _stream.StreamGameAsync(cancellationToken);\r\n    }\r\n\r\n    public event MoveUpdateHandler? OnMoveMade;\r\n    public event GameInfoFetchedHandler? OnGameInfoFetched;\r\n\r\n    private void ProcessData(object sender, JsonElement data)\r\n    {\r\n        FetchDataForNewGame(data);\r\n        FetchDataForMove(data);\r\n    }\r\n\r\n    private void FetchDataForNewGame(JsonElement data)\r\n    {\r\n        if (!data.TryGetProperty(\"id\", out var idProperty))\r\n            return;\r\n\r\n        var gameId = idProperty.GetString();\r\n        if (string.IsNullOrWhiteSpace(gameId))\r\n            return;\r\n\r\n        var game = new OngoingGame\r\n        {\r\n            GameId = gameId,\r\n            PlysAtInitFen = data.TryGetProperty(\"turns\", out var turns) ? turns.GetInt32() : 0\r\n        };\r\n\r\n        _games[gameId] = game;\r\n        OnGameInfoFetched?.Invoke(this, game);\r\n    }\r\n\r\n    private void FetchDataForMove(JsonElement data)\r\n    {\r\n        if (!data.TryGetProperty(\"lm\", out var lastMoveProperty))\r\n            return;\r\n\r\n        var gameId = _games.Count == 1 ? _games.First().Key : string.Empty;\r\n        if (string.IsNullOrWhiteSpace(gameId) || !_games.TryGetValue(gameId, out var game))\r\n            return;\r\n\r\n        game.Moves ??= new List<Move>();\r\n        var fen = data.TryGetProperty(\"fen\", out var fenProperty) ? fenProperty.GetString() : string.Empty;\r\n        var move = new Move\r\n        {\r\n            Notation = lastMoveProperty.GetString(),\r\n            IsWhite = fen?.Contains(\" w \") ?? false,\r\n            GameID = gameId,\r\n            MoveNumber = (game.Moves.Count / 2) + 1\r\n        };\r\n\r\n        game.Moves.Add(move);\r\n        OnMoveMade?.Invoke(this, move);\r\n    }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Game/TimeControl.cs",
            "FileName": "TimeControl.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Game;\r\n\r\npublic class TimeControl\r\n{\r\n    public string Type { get; set; }\r\n    public int Limit { get; set; }\r\n    public int Increment { get; set; }\r\n    public string Show { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Puzzle/Dashboard/PuzzleResults.cs",
            "FileName": "PuzzleResults.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Puzzle.Dashboard;\r\n\r\npublic class PuzzleResults\r\n{\r\n    public int FirstWins { get; set; }\r\n    public int Nb { get; set; }\r\n    public int Performance { get; set; }\r\n    public int PuzzleRatingAvg { get; set; }\r\n    public int ReplayWins { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Social/Stream/Streamer.cs",
            "FileName": "Streamer.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Social.Stream;\r\n\r\npublic class Streamer\r\n{\r\n    public string Name { get; set; }\r\n    public string Headline { get; set; }\r\n    public string Description { get; set; }\r\n    public string Twitch { get; set; }\r\n    public string YouTube { get; set; }\r\n    public string Image { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Stats/Performance/Glicko.cs",
            "FileName": "Glicko.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Account.Performance;\r\n\r\npublic class Glicko\r\n{\r\n    public double Rating { get; set; }\r\n    public double Deviation { get; set; }\r\n    public bool Provisional { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Stats/Performance/PerformanceStats.cs",
            "FileName": "PerformanceStats.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System.Text.Json.Serialization;\r\n\r\nnamespace LichessNET.Entities.Account.Performance;\r\n\r\npublic class PerformanceStats\r\n{\r\n    public string User { get; set; }\r\n    public Perf Perf { get; set; }\r\n    public int Rank { get; set; }\r\n    public double Percentile { get; set; }\r\n    public Stat Stat { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Stats/Performance/StatEntities.cs",
            "FileName": "StatEntities.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Account.Performance;\r\n\r\npublic class PerfType\r\n{\r\n    public string Key { get; set; }\r\n    public string Name { get; set; }\r\n}\r\n\r\npublic class Highest\r\n{\r\n    public int Int { get; set; }\r\n    public DateTime At { get; set; }\r\n    public string GameId { get; set; }\r\n}\r\n\r\npublic class BestWins\r\n{\r\n    public List<Result> Results { get; set; }\r\n}\r\n\r\npublic class WorstLosses\r\n{\r\n    public List<Result> Results { get; set; }\r\n}\r\n\r\npublic class Result\r\n{\r\n    public int OpRating { get; set; }\r\n    public OpId OpId { get; set; }\r\n    public DateTime At { get; set; }\r\n    public string GameId { get; set; }\r\n}\r\n\r\npublic class OpId\r\n{\r\n    public string Id { get; set; }\r\n    public string Name { get; set; }\r\n}\r\n\r\npublic class Count\r\n{\r\n    public int All { get; set; }\r\n    public int Rated { get; set; }\r\n    public int Win { get; set; }\r\n    public int Loss { get; set; }\r\n    public int Draw { get; set; }\r\n    public int Tour { get; set; }\r\n    public int Berserk { get; set; }\r\n    public double OpAvg { get; set; }\r\n    public int Seconds { get; set; }\r\n    public int Disconnects { get; set; }\r\n}\r\n\r\npublic class ResultStreak\r\n{\r\n    public Streak Win { get; set; }\r\n    public Streak Loss { get; set; }\r\n}\r\n\r\npublic class Streak\r\n{\r\n    public Cur Cur { get; set; }\r\n    public Max Max { get; set; }\r\n}\r\n\r\npublic class Cur\r\n{\r\n    public int V { get; set; }\r\n}\r\n\r\npublic class Max\r\n{\r\n    public int V { get; set; }\r\n    public From From { get; set; }\r\n    public To To { get; set; }\r\n}\r\n\r\npublic class From\r\n{\r\n    public DateTime At { get; set; }\r\n    public string GameId { get; set; }\r\n}\r\n\r\npublic class To\r\n{\r\n    public DateTime At { get; set; }\r\n    public string GameId { get; set; }\r\n}\r\n\r\npublic class UserId\r\n{\r\n    public string Id { get; set; }\r\n    public string Name { get; set; }\r\n    public string Title { get; set; }\r\n}\r\n\r\npublic class PlayStreak\r\n{\r\n    public Nb Nb { get; set; }\r\n    public Time Time { get; set; }\r\n    public DateTime LastDate { get; set; }\r\n}\r\n\r\npublic class Nb\r\n{\r\n    public Cur Cur { get; set; }\r\n    public Max Max { get; set; }\r\n}\r\n\r\npublic class Time\r\n{\r\n    public Cur Cur { get; set; }\r\n    public Max Max { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Teams/LichessTeam.cs",
            "FileName": "LichessTeam.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing LichessNET.Entities.Social;\r\n\r\nnamespace LichessNET.Entities.Teams;\r\n\r\n/// <summary>\r\n/// Represents a team on Lichess, containing information such as its ID, name, description, and members.\r\n/// </summary>\r\npublic class LichessTeam\r\n{\r\n    public string Id { get; set; }\r\n    public string Name { get; set; }\r\n    public string Description { get; set; }\r\n    public string Flair { get; set; }\r\n    public TeamLeader Leader { get; set; }\r\n    public List<TeamLeader> Leaders { get; set; }\r\n    public int NbMembers { get; set; }\r\n    public bool Open { get; set; }\r\n    public bool Joined { get; set; }\r\n    public bool Requested { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Teams/TeamLeader.cs",
            "FileName": "TeamLeader.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Entities.Social;\r\n\r\n/// <summary>\r\n/// Represents a leader of a team on Lichess, providing properties for flair, identification, name, and patron status.\r\n/// </summary>\r\npublic class TeamLeader\r\n{\r\n    public string Flair { get; set; }\r\n    public string Id { get; set; }\r\n    public string Name { get; set; }\r\n    public bool Patron { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Internal/LichessLog.cs",
            "FileName": "LichessLog.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nnamespace LichessNET.Internal;\r\n\r\ninternal sealed class LichessLog\r\n{\r\n\tprivate readonly string _name;\r\n\tprivate readonly bool _enabled;\r\n\r\n\tpublic LichessLog( string name, bool enabled = true )\r\n\t{\r\n\t\t_name = name;\r\n\t\t_enabled = enabled;\r\n\t}\r\n\r\n\tpublic void Debug( string message )\r\n\t{\r\n\t\tif ( _enabled )\r\n\t\t\tLog.Info( $\"[{_name}] {message}\" );\r\n\t}\r\n\r\n\tpublic void Information( string message )\r\n\t{\r\n\t\tif ( _enabled )\r\n\t\t\tLog.Info( $\"[{_name}] {message}\" );\r\n\t}\r\n\r\n\tpublic void Warning( string message )\r\n\t{\r\n\t\tif ( _enabled )\r\n\t\t\tLog.Warning( $\"[{_name}] {message}\" );\r\n\t}\r\n\r\n\tpublic void Error( string message )\r\n\t{\r\n\t\tif ( _enabled )\r\n\t\t\tLog.Error( $\"[{_name}] {message}\" );\r\n\t}\r\n}\r\n\r\n\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "API/LichessApiException.cs",
            "FileName": "LichessApiException.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\n\nnamespace LichessNET.API;\n\n/// <summary>\n/// A sanitized failure returned by the Lichess API.\n/// </summary>\npublic sealed class LichessApiException : HttpRequestException\n{\n    public LichessApiException(HttpStatusCode? statusCode = null)\n        : this(CreateMessage(statusCode), statusCode)\n    {\n    }\n\n    private LichessApiException(string message, HttpStatusCode? statusCode)\n        : base(message, null, statusCode)\n    {\n    }\n\n    internal static LichessApiException InvalidResponse(HttpStatusCode statusCode)\n    {\n        return new LichessApiException(\n            \"Lichess API returned a response that could not be processed.\", statusCode);\n    }\n\n    private static string CreateMessage(HttpStatusCode? statusCode)\n    {\n        return statusCode.HasValue\n            ? $\"Lichess API request failed with HTTP status {(int)statusCode.Value} ({statusCode.Value}).\"\n            : \"Lichess API request failed before an HTTP status was received.\";\n    }\n}\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "API/OAuthAPI.cs",
            "FileName": "OAuthAPI.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing LichessNET.Entities.OAuth;\r\n\r\nnamespace LichessNET.API;\r\n\r\npublic partial class LichessApiClient\r\n{\r\n    /// <summary>\r\n    /// Tests OAuth tokens without placing them in the request URI or logs.\r\n    /// </summary>\r\n    public Task<Dictionary<string, TokenInfo?>> TestTokensAsync(List<string> tokens)\r\n    {\r\n        return TestTokensAsync(tokens, CancellationToken.None);\r\n    }\r\n\r\n    public async Task<Dictionary<string, TokenInfo?>> TestTokensAsync(List<string> tokens,\r\n        CancellationToken cancellationToken = default)\r\n    {\r\n        if (tokens == null)\r\n            throw new ArgumentNullException(nameof(tokens));\r\n\r\n        if (tokens.Count == 0 || tokens.Count > 1000)\r\n            throw new ArgumentOutOfRangeException(nameof(tokens),\r\n                \"Between 1 and 1000 OAuth tokens are required.\");\r\n\r\n        if (tokens.Any(string.IsNullOrWhiteSpace))\r\n            throw new ArgumentException(\"OAuth tokens cannot be empty.\", nameof(tokens));\r\n\r\n        cancellationToken.ThrowIfCancellationRequested();\r\n\r\n        var request = GetRequestScaffold(\"api/token/test\");\r\n        var requestContent = new StringContent(\r\n            string.Join(',', tokens), Encoding.UTF8, \"text/plain\");\r\n        var response = await SendRequest(request, \"POST\", useToken: false,\r\n            cancellationToken: cancellationToken, requestContent: requestContent);\r\n\r\n        cancellationToken.ThrowIfCancellationRequested();\r\n        try\r\n        {\r\n            return await response.Content.ReadFromJsonAsync<Dictionary<string, TokenInfo?>>(\r\n                       LichessJson.Options)\r\n                   ?? new Dictionary<string, TokenInfo?>();\r\n        }\r\n        catch (Exception exception) when (exception is not OperationCanceledException)\r\n        {\r\n            throw LichessApiException.InvalidResponse(response.StatusCode);\r\n        }\r\n    }\r\n\r\n    /// <summary>\r\n    /// Deletes a token.\r\n    /// </summary>\r\n    public async Task DeleteTokenAsync(string token)\r\n    {\r\n        var request = GetRequestScaffold(\"api/token\");\r\n        request.Headers[\"Authorization\"] = \"Bearer \" + token;\r\n        await SendRequest(request, \"DELETE\", useToken: false);\r\n    }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Code/API/TeamAPI.cs",
            "FileName": "TeamAPI.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing LichessNET.Entities.Teams;\r\nusing LichessNET.Entities.Tournament;\r\nusing LichessNET.Entities.Tournament.Arena;\r\n\r\nnamespace LichessNET.API;\r\n\r\npublic partial class LichessApiClient\r\n{\r\n    /// <summary>\r\n    ///     Get the team with the specified ID\r\n    /// </summary>\r\n    /// <param name=\"teamId\">The ID of the team</param>\r\n    /// <returns>The team with the specified ID</returns>\r\n    public async Task<LichessTeam> GetTeamAsync(string teamId)\r\n    {\r\n        await _ratelimitController.Consume();\r\n        var request = GetRequestScaffold($\"api/team/{teamId}\");\r\n        var response = await SendRequest(request);\r\n\r\n        var content = await response.Content.ReadAsStringAsync();\r\n        var team = LichessJson.Deserialize<LichessTeam>(content);\r\n\r\n        return team;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Gets the most populat teams on Lichess in a paginated manner.\r\n    /// </summary>\r\n    /// <param name=\"page\">Page number, defaults to 1</param>\r\n    /// <returns>The list of the most popular teams on the specified page.</returns>\r\n    public async Task<List<LichessTeam>> GetPopularTeamsAsync(int page = 1)\r\n    {\r\n        await _ratelimitController.Consume();\r\n        var request = GetRequestScaffold(\"api/team/all\", new Tuple<string, string>(\"page\", page.ToString()));\r\n        var response = await SendRequest(request);\r\n\r\n        var content = await response.Content.ReadAsStringAsync();\r\n                var json = LichessJson.Parse(content);\r\n        return json.TryGetProperty(\"currentPageResults\", out var results)\r\n            ? results.Deserialize<List<LichessTeam>>(LichessJson.Options) ?? new List<LichessTeam>()\r\n            : new List<LichessTeam>();\r\n    }\r\n\r\n    /// <summary>\r\n    /// Get the list of teams that the specified user is in.\r\n    /// </summary>\r\n    /// <param name=\"username\">The username of the user</param>\r\n    /// <returns>A list of teams that the specified user is in</returns>\r\n    public async Task<List<LichessTeam>> GetTeamOfUserAsync(string username)\r\n    {\r\n        await _ratelimitController.Consume();\r\n\r\n        var request = GetRequestScaffold($\"api/team/of/{username}\");\r\n        var response = await SendRequest(request);\r\n\r\n        var content = await response.Content.ReadAsStringAsync();\r\n        var teams = LichessJson.Deserialize<List<LichessTeam>>(content);\r\n\r\n        return teams;\r\n    }\r\n\r\n    /// <summary>\r\n    /// This funciton gets the members of a team, in chronoclogical order of joining the team. (Latest first),\r\n    /// with up to 5000 members.\r\n    /// </summary>\r\n    /// <param name=\"teamId\">ID of the Team</param>\r\n    /// <returns></returns>\r\n    public async Task<List<TeamMember>> GetTeamMembersAsync(string teamId)\r\n    {\r\n        await _ratelimitController.Consume();\r\n        var request = GetRequestScaffold($\"api/team/{teamId}/users\");\r\n\r\n        var response = await SendRequest(request);\r\n\r\n        var content = await response.Content.ReadAsStringAsync();\r\n        var members = LichessJson.Deserialize<List<TeamMember>>(content);\r\n\r\n        return members;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Retrieves all Swiss tournaments of a team.\r\n    /// </summary>\r\n    /// <param name=\"teamId\">The ID of the team.</param>\r\n    /// <param name=\"max\">The maximum number of tournaments to download. Default is 100.</param>\r\n    /// <returns>A task representing the asynchronous operation, containing the list of Swiss tournaments.</returns>\r\n    public async Task<List<SwissTournament>> GetTeamSwissTournamentsAsync(string teamId, int max = 100)\r\n    {\r\n        await _ratelimitController.Consume();\r\n\r\n        var endpoint = $\"api/team/{teamId}/swiss?max={max}\";\r\n        var request = GetRequestScaffold(endpoint);\r\n\r\n        var response = await SendRequest(request);\r\n        var content = await response.Content.ReadAsStringAsync();\r\n\r\n        var tournaments = new List<SwissTournament>();\r\n        foreach (var line in content.Split('\\n')) { if (string.IsNullOrWhiteSpace(line)) continue; var tournament = LichessJson.Deserialize<SwissTournament>(line.Trim()); if (tournament != null) tournaments.Add(tournament); }\r\n\r\n        return tournaments;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Retrieves all Arena tournaments relevant to a team.\r\n    /// </summary>\r\n    /// <param name=\"teamId\">The ID of the team.</param>\r\n    /// <param name=\"max\">The maximum number of tournaments to download. Default is 100.</param>\r\n    /// <returns>A task representing the asynchronous operation, containing the list of Arena tournaments.</returns>\r\n    public async Task<List<ArenaTournament>> GetTeamArenaTournamentsAsync(string teamId, int max = 100)\r\n    {\r\n        await _ratelimitController.Consume();\r\n\r\n        var endpoint = $\"api/team/{teamId}/arena?max={max}\";\r\n        var request = GetRequestScaffold(endpoint);\r\n\r\n        var response = await SendRequest(request);\r\n        var content = await response.Content.ReadAsStringAsync();\r\n\r\n        var tournaments = new List<ArenaTournament>();\r\n        foreach (var line in content.Split('\\n')) { if (string.IsNullOrWhiteSpace(line)) continue; var tournament = LichessJson.Deserialize<ArenaTournament>(line.Trim()); if (tournament != null) tournaments.Add(tournament); }\r\n\r\n        return tournaments;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Joins a team.\r\n    /// </summary>\r\n    /// <param name=\"teamId\">The ID of the team.</param>\r\n    /// <param name=\"message\">The message for the team join request, required if the team manually reviews admission requests.</param>\r\n    /// <param name=\"password\">The password for the team, if required.</param>\r\n    /// <returns>A task representing the asynchronous operation, containing the join team response.</returns>\r\n    public async Task<bool> JoinTeamAsync(string teamId, string message = null, string password = null)\r\n    {\r\n        await _ratelimitController.Consume();\r\n\r\n        var endpoint = $\"team/{teamId}/join\";\r\n        var request = GetRequestScaffold(endpoint);\r\n\r\n        var formData = new Dictionary<string, string>();\r\n        if (!string.IsNullOrEmpty(message))\r\n        {\r\n            formData.Add(\"message\", message);\r\n        }\r\n\r\n        if (!string.IsNullOrEmpty(password))\r\n        {\r\n            formData.Add(\"password\", password);\r\n        }\r\n\r\n        var response = await SendRequest(request, \"POST\", formData: formData);\r\n        var content = await response.Content.ReadAsStringAsync();\r\n\r\n        return content.Contains(\"true\");\r\n    }\r\n\r\n    /// <summary>\r\n    /// Leaves a team.\r\n    /// </summary>\r\n    /// <param name=\"teamId\">The ID of the team.</param>\r\n    /// <returns>A task representing the asynchronous operation, containing the leave team response.</returns>\r\n    public async Task<bool> LeaveTeamAsync(string teamId)\r\n    {\r\n        await _ratelimitController.Consume();\r\n\r\n        var endpoint = $\"team/{teamId}/quit\";\r\n        var request = GetRequestScaffold(endpoint);\r\n\r\n        var response = await SendRequest(request, \"POST\");\r\n        var content = await response.Content.ReadAsStringAsync();\r\n\r\n        return content.Contains(\"true\");\r\n    }\r\n}\r\n\r\n\r\n\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Code/Entities/Game/GamePlayer.cs",
            "FileName": "GamePlayer.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing LichessNET.Entities.Enumerations;\r\n\r\nnamespace LichessNET.Entities.Game;\r\n\r\n/// <summary>\r\n/// Represents a player in a chess game, including their name, title, and rating.\r\n/// This class will be used when parsing PGN Files\r\n/// </summary>\r\npublic class GamePlayer\r\n{\r\n    public string Name { get; set; }\r\n\r\n    //TODO: Add serialization for Title\r\n    public Title? Title { get; set; }\r\n    public int Rating { get; set; }\r\n}\r\n\r\n"
        },
        {
            "Ident": "brycpt.lichessnetport",
            "Path": "Entities/Board/BoardModels.cs",
            "FileName": "BoardModels.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 308901,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System.Globalization;\r\nusing LichessNET.Entities.Enumerations;\r\nusing LichessNET.Extensions;\r\n\r\nnamespace LichessNET.Entities.Board;\r\n\r\npublic enum BoardColorPreference\r\n{\r\n    Random,\r\n    White,\r\n    Black\r\n}\r\n\r\npublic enum BoardChatRoom\r\n{\r\n    Player,\r\n    Spectator\r\n}\r\n\r\npublic sealed class BoardSeekOptions\r\n{\r\n    public bool Rated { get; set; }\r\n    public double TimeMinutes { get; set; } = 5;\r\n    public int IncrementSeconds { get; set; }\r\n    public int? DaysPerTurn { get; set; }\r\n    public BoardColorPreference Color { get; set; } = BoardColorPreference.Random;\r\n    public ChessVariant Variant { get; set; } = ChessVariant.Standard;\r\n    public string RatingRange { get; set; }\r\n\r\n    internal Dictionary<string, string> ToFormData()\r\n    {\r\n        Validate();\r\n\r\n        var form = new Dictionary<string, string>\r\n        {\r\n            [\"rated\"] = Rated.ToString().ToLowerInvariant(),\r\n            [\"variant\"] = Variant.ToApiName(),\r\n            [\"color\"] = ToApiName(Color)\r\n        };\r\n\r\n        if (DaysPerTurn.HasValue)\r\n        {\r\n            form[\"days\"] = DaysPerTurn.Value.ToString(CultureInfo.InvariantCulture);\r\n        }\r\n        else\r\n        {\r\n            form[\"time\"] = TimeMinutes.ToString(CultureInfo.InvariantCulture);\r\n            form[\"increment\"] = IncrementSeconds.ToString(CultureInfo.InvariantCulture);\r\n        }\r\n\r\n        if (!string.IsNullOrWhiteSpace(RatingRange))\r\n            form[\"ratingRange\"] = RatingRange;\r\n\r\n        return form;\r\n    }\r\n\r\n    private void Validate()\r\n    {\r\n        if (DaysPerTurn.HasValue)\r\n        {\r\n            if (DaysPerTurn.Value < 1 || DaysPerTurn.Value > 14)\r\n                throw new ArgumentOutOfRangeException(nameof(DaysPerTurn), \"Days per turn must be between 1 and 14.\");\r\n\r\n            return;\r\n        }\r\n\r\n        if (TimeMinutes <= 0 || TimeMinutes > 180)\r\n            throw new ArgumentOutOfRangeException(nameof(TimeMinutes), \"Clock time must be between 0 and 180 minutes.\");\r\n\r\n        if (IncrementSeconds < 0 || IncrementSeconds > 180)\r\n            throw new ArgumentOutOfRangeException(nameof(IncrementSeconds), \"Increment must be between 0 and 180 seconds.\");\r\n    }\r\n\r\n    private static string ToApiName(BoardColorPreference color)\r\n    {\r\n        return color switch\r\n        {\r\n            BoardColorPreference.White => \"white\",\r\n            BoardColorPreference.Black => \"black\",\r\n            _ => \"random\"\r\n        };\r\n    }\r\n}\r\n\r\npublic sealed class BoardGameInfo\r\n{\r\n    [JsonPropertyName(\"gameId\")]\r\n    public string GameId { get; set; }\r\n\r\n    [JsonPropertyName(\"id\")]\r\n    public string Id { get; set; }\r\n\r\n    [JsonPropertyName(\"fullId\")]\r\n    public string FullId { get; set; }\r\n\r\n    [JsonPropertyName(\"color\")]\r\n    public string Color { get; set; }\r\n\r\n    [JsonPropertyName(\"fen\")]\r\n    public string Fen { get; set; }\r\n\r\n    [JsonPropertyName(\"hasMoved\")]\r\n    public bool? HasMoved { get; set; }\r\n\r\n    [JsonPropertyName(\"isMyTurn\")]\r\n    public bool? IsMyTurn { get; set; }\r\n\r\n    [JsonPropertyName(\"lastMove\")]\r\n    public string LastMove { get; set; }\r\n\r\n    [JsonPropertyName(\"source\")]\r\n    public string Source { get; set; }\r\n\r\n    [JsonPropertyName(\"status\")]\r\n    public string Status { get; set; }\r\n\r\n    [JsonPropertyName(\"variant\")]\r\n    [JsonConverter(typeof(BoardObjectNameConverter))]\r\n    public string Variant { get; set; }\r\n\r\n    [JsonPropertyName(\"speed\")]\r\n    public string Speed { get; set; }\r\n\r\n    [JsonPropertyName(\"perf\")]\r\n    [JsonConverter(typeof(BoardObjectNameConverter))]\r\n    public string Perf { get; set; }\r\n\r\n    [JsonPropertyName(\"rated\")]\r\n    public bool? Rated { get; set; }\r\n\r\n    [JsonPropertyName(\"opponent\")]\r\n    public BoardPlayerInfo Opponent { get; set; }\r\n\r\n    [JsonPropertyName(\"white\")]\r\n    public BoardPlayerInfo White { get; set; }\r\n\r\n    [JsonPropertyName(\"black\")]\r\n    public BoardPlayerInfo Black { get; set; }\r\n\r\n    [JsonPropertyName(\"state\")]\r\n    public BoardGameState State { get; set; }\r\n\r\n    [JsonExtensionData]\r\n    public Dictionary<string, JsonElement> Extra { get; set; }\r\n\r\n    public string BestGameId => !string.IsNullOrWhiteSpace(GameId) ? GameId : Id;\r\n}\r\n\r\npublic sealed class BoardPlayerInfo\r\n{\r\n    [JsonPropertyName(\"id\")]\r\n    public string Id { get; set; }\r\n\r\n    [JsonPropertyName(\"name\")]\r\n    public string Name { get; set; }\r\n\r\n    [JsonPropertyName(\"username\")]\r\n    public string Username { get; set; }\r\n\r\n    [JsonPropertyName(\"title\")]\r\n    public string Title { get; set; }\r\n\r\n    [JsonPropertyName(\"rating\")]\r\n    public int? Rating { get; set; }\r\n\r\n    [JsonPropertyName(\"provisional\")]\r\n    public bool? Provisional { get; set; }\r\n\r\n    [JsonExtensionData]\r\n    public Dictionary<string, JsonElement> Extra { get; set; }\r\n}\r\n\r\npublic sealed class BoardClockState\r\n{\r\n    public int? WhiteTimeMilliseconds { get; set; }\r\n    public int? BlackTimeMilliseconds { get; set; }\r\n    public int? WhiteIncrementMilliseconds { get; set; }\r\n    public int? BlackIncrementMilliseconds { get; set; }\r\n\r\n    public TimeSpan? WhiteTime => WhiteTimeMilliseconds.HasValue\r\n        ? TimeSpan.FromMilliseconds(WhiteTimeMilliseconds.Value)\r\n        : null;\r\n\r\n    public TimeSpan? BlackTime => BlackTimeMilliseconds.HasValue\r\n        ? TimeSpan.FromMilliseconds(BlackTimeMilliseconds.Value)\r\n        : null;\r\n\r\n    public static BoardClockState FromState(BoardGameState state)\r\n    {\r\n        return new BoardClockState\r\n        {\r\n            WhiteTimeMilliseconds = state?.WhiteTimeMilliseconds,\r\n            BlackTimeMilliseconds = state?.BlackTimeMilliseconds,\r\n            WhiteIncrementMilliseconds = state?.WhiteIncrementMilliseconds,\r\n            BlackIncrementMilliseconds = state?.BlackIncrementMilliseconds\r\n        };\r\n    }\r\n}\r\n\r\npublic sealed class BoardGameState\r\n{\r\n    [JsonPropertyName(\"type\")]\r\n    public string Type { get; set; }\r\n\r\n    [JsonPropertyName(\"moves\")]\r\n    public string Moves { get; set; }\r\n\r\n    [JsonPropertyName(\"wtime\")]\r\n    public int? WhiteTimeMilliseconds { get; set; }\r\n\r\n    [JsonPropertyName(\"btime\")]\r\n    public int? BlackTimeMilliseconds { get; set; }\r\n\r\n    [JsonPropertyName(\"winc\")]\r\n    public int? WhiteIncrementMilliseconds { get; set; }\r\n\r\n    [JsonPropertyName(\"binc\")]\r\n    public int? BlackIncrementMilliseconds { get; set; }\r\n\r\n    [JsonPropertyName(\"status\")]\r\n    public string Status { get; set; }\r\n\r\n    [JsonPropertyName(\"winner\")]\r\n    public string Winner { get; set; }\r\n\r\n    [JsonPropertyName(\"wdraw\")]\r\n    public bool? WhiteOfferingDraw { get; set; }\r\n\r\n    [JsonPropertyName(\"bdraw\")]\r\n    public bool? BlackOfferingDraw { get; set; }\r\n\r\n    [JsonExtensionData]\r\n    public Dictionary<string, JsonElement> Extra { get; set; }\r\n\r\n    public BoardClockState Clock => BoardClockState.FromState(this);\r\n\r\n    public bool IsFinished => !string.IsNullOrWhiteSpace(Status)\r\n                              && !Status.Equals(\"started\", StringComparison.OrdinalIgnoreCase)\r\n                              && !Status.Equals(\"created\", StringComparison.OrdinalIgnoreCase);\r\n\r\n    public IReadOnlyList<string> MoveList => BoardEventParser.SplitMoves(Moves);\r\n}\r\n\r\npublic sealed class BoardGameStartEvent\r\n{\r\n    [JsonPropertyName(\"type\")]\r\n    public string Type { get; set; }\r\n\r\n    [JsonPropertyName(\"game\")]\r\n    public BoardGameInfo Game { get; set; }\r\n}\r\n\r\npublic sealed class BoardGameFinishEvent\r\n{\r\n    [JsonPropertyName(\"type\")]\r\n    public string Type { get; set; }\r\n\r\n    [JsonPropertyName(\"game\")]\r\n    public BoardGameInfo Game { get; set; }\r\n}\r\n\r\npublic sealed class BoardGameFullEvent\r\n{\r\n    [JsonPropertyName(\"type\")]\r\n    public string Type { get; set; }\r\n\r\n    [JsonPropertyName(\"id\")]\r\n    public string Id { get; set; }\r\n\r\n    [JsonPropertyName(\"initialFen\")]\r\n    public string InitialFen { get; set; }\r\n\r\n    [JsonPropertyName(\"rated\")]\r\n    public bool? Rated { get; set; }\r\n\r\n    [JsonPropertyName(\"variant\")]\r\n    [JsonConverter(typeof(BoardObjectNameConverter))]\r\n    public string Variant { get; set; }\r\n\r\n    [JsonPropertyName(\"speed\")]\r\n    public string Speed { get; set; }\r\n\r\n    [JsonPropertyName(\"perf\")]\r\n    [JsonConverter(typeof(BoardObjectNameConverter))]\r\n    public string Perf { get; set; }\r\n\r\n    [JsonPropertyName(\"createdAt\")]\r\n    public long? CreatedAt { get; set; }\r\n\r\n    [JsonPropertyName(\"white\")]\r\n    public BoardPlayerInfo White { get; set; }\r\n\r\n    [JsonPropertyName(\"black\")]\r\n    public BoardPlayerInfo Black { get; set; }\r\n\r\n    [JsonPropertyName(\"state\")]\r\n    public BoardGameState State { get; set; }\r\n\r\n    [JsonExtensionData]\r\n    public Dictionary<string, JsonElement> Extra { get; set; }\r\n}\r\n\r\npublic sealed class BoardChatLineEvent\r\n{\r\n    [JsonPropertyName(\"type\")]\r\n    public string Type { get; set; }\r\n\r\n    [JsonPropertyName(\"room\")]\r\n    public string Room { get; set; }\r\n\r\n    [JsonPropertyName(\"username\")]\r\n    public string Username { get; set; }\r\n\r\n    [JsonPropertyName(\"text\")]\r\n    public string Text { get; set; }\r\n}\r\n\r\npublic static class BoardEventParser\r\n{\r\n    public static string GetEventType(JsonElement element)\r\n    {\r\n        return element.TryGetProperty(\"type\", out var type)\r\n            ? type.GetString()\r\n            : string.Empty;\r\n    }\r\n\r\n    public static BoardGameStartEvent ParseGameStart(JsonElement element)\r\n    {\r\n        return element.Deserialize<BoardGameStartEvent>(LichessJson.Options);\r\n    }\r\n\r\n    public static BoardGameFinishEvent ParseGameFinish(JsonElement element)\r\n    {\r\n        return element.Deserialize<BoardGameFinishEvent>(LichessJson.Options);\r\n    }\r\n\r\n    public static BoardGameFullEvent ParseGameFull(JsonElement element)\r\n    {\r\n        return element.Deserialize<BoardGameFullEvent>(LichessJson.Options);\r\n    }\r\n\r\n    public static BoardGameState ParseGameState(JsonElement element)\r\n    {\r\n        return element.Deserialize<BoardGameState>(LichessJson.Options);\r\n    }\r\n\r\n    public static BoardChatLineEvent ParseChatLine(JsonElement element)\r\n    {\r\n        return element.Deserialize<BoardChatLineEvent>(LichessJson.Options);\r\n    }\r\n\r\n    public static IReadOnlyList<string> SplitMoves(string moves)\r\n    {\r\n        if (string.IsNullOrWhiteSpace(moves))\r\n            return Array.Empty<string>();\r\n\r\n        return moves.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);\r\n    }\r\n}\r\n\n\n/// <summary>\n/// Preserves legacy string properties while accepting the object form used by\n/// current Board API gameFull payloads.\n/// </summary>\npublic sealed class BoardObjectNameConverter : JsonConverter<string>\n{\n    public override string? Read(ref Utf8JsonReader reader, Type typeToConvert,\n        JsonSerializerOptions options)\n    {\n        if (reader.TokenType == JsonTokenType.String)\n            return reader.GetString();\n\n        if (reader.TokenType == JsonTokenType.Null)\n            return null;\n\n        using var document = JsonDocument.ParseValue(ref reader);\n        var value = document.RootElement;\n        if (value.ValueKind != JsonValueKind.Object)\n            return value.ToString();\n\n        foreach (var propertyName in new[] { \"key\", \"name\", \"short\" })\n        {\n            if (value.TryGetProperty(propertyName, out var property) &&\n                property.ValueKind == JsonValueKind.String)\n            {\n                return property.GetString();\n            }\n        }\n\n        return value.GetRawText();\n    }\n\n    public override void Write(Utf8JsonWriter writer, string value,\n        JsonSerializerOptions options)\n    {\n        writer.WriteStringValue(value);\n    }\n}\n"
        }
    ]
}