예제 #1
0
        public async Task <IActionResult> IssueChallenge(int?id)
        {
            if (id is null)
            {
                return(NotFound());
            }

            var user = await _userManager.GetUserAsync(User);

            var game = await _context.Game.Include(g => g.HomePlayer.User)
                       .Include(g => g.AwayPlayer.User)
                       .Include(g => g.Match)
                       .FirstOrDefaultAsync(g => g.GameId == id);

            if (game.HomePlayer == null || game.AwayPlayer == null || game.HomePlayer.User.Id != user.Id)
            {
                return(NotFound());
            }

            var awayUser = await _userManager.FindByIdAsync(game.AwayPlayer.UserId + "");

            bool userRefreshed = false, awayRefreshed = false;

            userRefreshed = await _tokenRefresher.RefreshTokens(user, false);

            awayRefreshed = await _tokenRefresher.RefreshTokens(awayUser, false);

            if (!userRefreshed)
            {
                Log.Information("User access token expired");

                await _signInManager.SignOutAsync();

                return(RedirectToAction(nameof(AdminController.Index), "Home"));
            }

            GameJson gameJson = new GameJson
            {
                GameId           = game.GameId.ToString(),
                ChallengeId      = game.ChallengeId,
                Fen              = game.CurrentFen,
                ChallengeUrl     = game.ChallengeUrl,
                MatchId          = game.MatchId,
                Moves            = new List <string>(),
                Completed        = false,
                Result           = "",
                BlackPlayerId    = game.BoardPosition % 2 == 1 ? game.HomePlayer.User.LichessId : game.AwayPlayer.User.LichessId,
                WhitePlayerId    = game.BoardPosition % 2 == 0 ? game.HomePlayer.User.LichessId : game.AwayPlayer.User.LichessId,
                HomePlayerRating = game.Completed ? $"{game.HomePlayerRatingBefore} -> {game.HomePlayerRatingAfter}" : $"{game.HomePlayerRatingAfter}",
                AwayPlayerRating = game.Completed ? $"{game.AwayPlayerRatingBefore} -> {game.AwayPlayerRatingAfter}" : $"{game.AwayPlayerRatingAfter}",
                HomePoints       = game.HomePoints.ToString(),
                AwayPoints       = game.AwayPoints.ToString()
            };

            // If JV, colors are swapped
            if (game.BoardPosition > 7)
            {
                string temp = gameJson.BlackPlayerId;
                gameJson.BlackPlayerId = gameJson.WhitePlayerId;
                gameJson.WhitePlayerId = temp;
            }

            bool challengeCreated = false;

            LichessApi.LichessApiClient client = new LichessApiClient(user.AccessToken);

            if (awayRefreshed)
            {
                CreateGameRequest request = new CreateGameRequest
                {
                    Rated          = true,
                    ClockLimit     = game.Match.ClockTimeLimit,
                    ClockIncrement = game.Match.ClockIncrement,
                    Color          = game.BoardPosition % 2 == 0 ? Color.White : Color.Black,
                    Variant        = GameVariant.Standard,
                    Fen            = game.CurrentFen,
                    Message        = "Your EPC team game with {opponent} is ready: {game}."
                };

                // Jv board colors are swapped
                if (game.BoardPosition > 7)
                {
                    if (request.Color == Color.White)
                    {
                        request.Color = Color.Black;
                    }
                    else
                    {
                        request.Color = Color.White;
                    }
                }

                try
                {
                    var response = await client.Challenges.CreateGame(awayUser.LichessId, awayUser.AccessToken, request);

                    game.CurrentFen    = response.Game.InitialFen;
                    game.ChallengeUrl  = response.Game.Url;
                    game.ChallengeId   = response.Game.Id;
                    game.ChallengeJson = JsonConvert.SerializeObject(response);
                    game.IsStarted     = true;

                    gameJson.Status    = response.Game.Status.Name;
                    gameJson.IsStarted = true;

                    challengeCreated = true;
                }
                catch (Exception e)
                {
                    Log.Error(e, "Unable to create challenge");
                }
            }
            else // This has to be a pure challenge request
            {
                ChallengeRequest request = new ChallengeRequest
                {
                    Rated          = true,
                    ClockLimit     = game.Match.ClockTimeLimit,
                    ClockIncrement = game.Match.ClockIncrement,
                    Color          = game.BoardPosition % 2 == 0 ? Color.White : Color.Black,
                    Variant        = GameVariant.Standard,
                    Fen            = game.CurrentFen,
                    Message        = "Your EPC team game with {opponent} is ready: {game}."
                };

                // Jv board colors are swapped
                if (game.BoardPosition > 7)
                {
                    if (request.Color == Color.White)
                    {
                        request.Color = Color.Black;
                    }
                    else
                    {
                        request.Color = Color.White;
                    }
                }

                try
                {
                    var response = await client.Challenges.CreateChallenge(awayUser.LichessId, request);

                    game.ChallengeUrl  = response.Challenge.Url;
                    game.ChallengeId   = response.Challenge.Id;
                    game.ChallengeJson = JsonConvert.SerializeObject(response);
                    game.IsStarted     = true;

                    gameJson.Status    = response.Challenge.Status;
                    gameJson.IsStarted = true;

                    challengeCreated = true;
                }
                catch (Exception e)
                {
                    Log.Error(e, "Unable to create challenge");
                }
            }

            if (challengeCreated)
            {
                MatchUpdateViewModel vm = new MatchUpdateViewModel
                {
                    MatchId = game.MatchId,
                    Games   = new List <GameJson>(new GameJson[] { gameJson })
                };

                // Issue an immediate match update
                await _hubContext.Clients.Groups("match_" + game.Match.MatchId).SendAsync("UpdateMatches", vm);

                _context.Game.Update(game);
                await _context.SaveChangesAsync();
            }
            else
            {
                Log.Information("Challenge Url was empty");

                // This likely happens if the user AccessToken expires and Refresh fails

                await _signInManager.SignOutAsync();

                return(RedirectToAction(nameof(AdminController.Index), "Home"));
            }

            return(Redirect(game.ChallengeUrl));
        }
예제 #2
0
        /*
         * public async Task TrackChallenge(ApplicationDbContext context, Game game)
         * {
         *  if (_challengeTasks.ContainsKey(game.ChallengeId))
         *      return;
         * }
         *
         * protected async Task<EventType> LichessMonitor (Game game)
         * {
         *  LichessApiClient client = new LichessApiClient(game.HomePlayer.User.AccessToken);
         *
         *  CancellationTokenSource cts = new CancellationTokenSource();
         *  _challengeTokens.Add(game.ChallengeId, cts);
         *
         *  await foreach (EventStreamResponse evt in client.Challenges.StreamIncomingEvents(cts.Token))
         *  {
         *      if (evt.Type == EventType.ChallengeCanceled ||
         *          evt.Type == EventType.ChallengeDeclined)
         *          return evt.Type;
         *  }
         *
         * }
         */

        protected override async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            while (true)
            {
                using (IServiceScope scope = _provider.CreateScope())
                {
                    var context     = scope.ServiceProvider.GetRequiredService <ApplicationDbContext>();
                    var config      = scope.ServiceProvider.GetRequiredService <IConfiguration> ();
                    var hubContext  = scope.ServiceProvider.GetRequiredService <IHubContext <GameHub> >();
                    var dataService = scope.ServiceProvider.GetRequiredService <DataService>();

                    try
                    {
                        // Get the API access token
                        var lichessAuthNSection = config.GetSection("Authentication:Lichess");
                        var appToken            = lichessAuthNSection["AppToken"];

                        // Create a client that can use the token
                        var client = new LichessApiClient(appToken);

                        // Get games from the last week that have not been completed
                        var matches = await context.Match.Where(m => m.IsVirtual && m.MatchStarted && !m.Completed)
                                      .Include(m => m.Games).ThenInclude(g => g.HomePlayer).ThenInclude(p => p.User)
                                      .Include(m => m.Games).ThenInclude(g => g.AwayPlayer).ThenInclude(p => p.User)
                                      .ToListAsync();

                        if (matches is not null)
                        {
                            foreach (var match in matches)
                            {
                                MatchUpdateViewModel vm = new MatchUpdateViewModel
                                {
                                    MatchId = match.MatchId,
                                    Games   = new List <GameJson>()
                                };

                                List <string> gameIds = new List <string>();

                                foreach (var game in match.Games)
                                {
                                    if (!game.Completed && !String.IsNullOrEmpty(game.ChallengeId))
                                    {
                                        gameIds.Add(game.ChallengeId);
                                    }
                                    else if (game.Completed)
                                    {
                                        vm.Games.Add(MapGameToJson(game));
                                    }
                                }

                                if (gameIds.Count > 0)
                                {
                                    CancellationTokenSource cts = new CancellationTokenSource();

                                    ExportGamesByIdsRequest request = new ExportGamesByIdsRequest
                                    {
                                        PgnInJson = true,
                                        Moves     = true,
                                        Clocks    = true,
                                        Evals     = false,
                                        Opening   = true
                                    };

                                    try
                                    {
                                        await foreach (LichessApi.Web.Models.Game ligame in client.Games
                                                       .ExportGamesByIds(request, gameIds, cts.Token))
                                        {
                                            // Remove this from the list
                                            gameIds.Remove(ligame.Id);

                                            Chess chess = new Chess();

                                            if (!String.IsNullOrEmpty(ligame.Moves))
                                            {
                                                try
                                                {
                                                    chess.loadSAN(ligame.Moves);
                                                }
                                                catch (Exception e)
                                                {
                                                    Log.Error(e, "Error loading SAN {0}", ligame.Moves);
                                                }
                                            }

                                            var game = match.Games.FirstOrDefault(g => g.ChallengeId.Equals(ligame.Id));

                                            if (game != null)
                                            {
                                                string fen = chess.Fen();

                                                game.CurrentFen      = fen;
                                                game.LastMove        = ligame.LastMoveAt;
                                                game.ChallengeStatus = ligame.Status.ToEnumString();
                                                game.ChallengeMoves  = ligame.Moves ?? "";

                                                if (ligame.Status == GameStatus.Timeout ||
                                                    ligame.Status == GameStatus.Aborted)
                                                {
                                                    ResetGame(game);
                                                }
                                                else if (ligame.Status == GameStatus.Started)
                                                {
                                                    game.IsStarted = true;
                                                }
                                                else if (ligame.Status == GameStatus.Draw ||
                                                         ligame.Status == GameStatus.Stalemate)
                                                {
                                                    // Result: 0 = Draw, 1 = Home Win, 2 = Away Win, 3 = Reset
                                                    GameResult result = GameResult.Draw;

                                                    await dataService.UpdateAndLogRatingCalculations(game, result);

                                                    game.Completed     = true;
                                                    game.CompletedDate = DateTime.Now;
                                                }
                                                else if (ligame.Status == GameStatus.OutOfTime ||
                                                         ligame.Status == GameStatus.Resign ||
                                                         ligame.Status == GameStatus.Mate)
                                                {
                                                    GameResult result = GameResult.Draw;

                                                    if (game.BoardPosition % 2 == 1)
                                                    {
                                                        // black player is home, white player is away
                                                        result = ligame.Winner.Equals("black")
                                                            ? GameResult.Player1Wins
                                                            : GameResult.Player2Wins;
                                                    }
                                                    else
                                                    {
                                                        // white player is home, black player is away
                                                        result = ligame.Winner.Equals("white")
                                                            ? GameResult.Player1Wins
                                                            : GameResult.Player2Wins;
                                                    }

                                                    // Everything beyond board 7 has flipped colors
                                                    if (game.BoardPosition > 7)
                                                    {
                                                        if (result == GameResult.Player1Wins)
                                                        {
                                                            result = GameResult.Player2Wins;
                                                        }
                                                        else
                                                        {
                                                            result = GameResult.Player1Wins;
                                                        }
                                                    }

                                                    await dataService.UpdateAndLogRatingCalculations(game, result);

                                                    game.Completed     = true;
                                                    game.CompletedDate = DateTime.Now;
                                                }
                                                else if (ligame.Status == GameStatus.Cheat)
                                                {
                                                    game.CheatingDetected = true;
                                                }

                                                context.Game.Update(game);
                                                await context.SaveChangesAsync();

                                                // Add game to json output
                                                vm.Games.Add(MapGameToJson(game));
                                            }
                                        }
                                    }
                                    catch (ApiInvalidRequestException e)
                                    {
                                        Log.Error(e, "GameMonitor : Invalid Api request");
                                    }
                                    catch (ApiUnauthorizedException e)
                                    {
                                        Log.Error(e, "GameMonitor : Unauthorized request");
                                    }
                                    catch (ApiRateLimitExceededException e)
                                    {
                                        Log.Error(e, "GameMonitor: All requests are rate limited using various strategies, to ensure the API remains responsive for everyone. Only make one request at a time. If you receive an HTTP response with a 429 status, please wait a full minute before resuming API usage.");
                                        await Task.Delay(60 * 1000);  // Wait 60 seconds due to rate limit exceeded
                                    }
                                    catch (Exception e)
                                    {
                                        Log.Error(e, "GameMonitor : Error occurred");
                                    }


                                    // Are there any games that we didn't get responses back for?
                                    if (gameIds.Count > 0)
                                    {
                                        foreach (string gameId in gameIds)
                                        {
                                            var game = match.Games.FirstOrDefault(g => g.ChallengeId.Equals(gameId));

                                            if (game != null)
                                            {
                                                // This is to check for challenge games that are created but we would normally have to monitor
                                                // by streaming events for every user

                                                // Game is created but we aren't getting status updates on it
                                                var response = await client.Connector.SendRawRequest(new Uri(game.ChallengeUrl), HttpMethod.Get);

                                                var body = response.Body.ToString();

                                                if (body.Contains("Challenge canceled") ||
                                                    body.Contains("Challenge declined"))
                                                {
                                                    ResetGame(game);

                                                    context.Game.Update(game);

                                                    await context.SaveChangesAsync();

                                                    // Add game to json output
                                                    vm.Games.Add(MapGameToJson(game));
                                                }
                                            }
                                        }
                                    }

                                    await hubContext.Clients.Groups("match_" + match.MatchId).SendAsync("UpdateMatches", vm);

                                    await Task.Delay(500);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex, ex.Message);

                        // if the DB is not currently connected, wait 15 seconds and try again
                        await Task.Delay(TimeSpan.FromSeconds(15));

                        continue;
                    }
                }

                var task = Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
                try
                {
                    await task;
                }
                catch (TaskCanceledException)
                {
                    return;
                }
            }
        }