public override Task <PreconditionResult> CheckPermissionsAsync( ICommandContext context, CommandInfo command, IServiceProvider serviceProvider) { Verify.IsNotNull(context, nameof(context)); GlobalTournamentsManager globalManager = serviceProvider.GetService <GlobalTournamentsManager>(); TournamentsManager manager = globalManager.GetOrAdd(context.Guild.Id, CreateTournamentsManager); // TD is only allowed to run commands when they are a director of the current tournament. Result <bool> result = manager.TryReadActionOnCurrentTournament(currentTournament => currentTournament.GuildId == context.Guild.Id && CanActAsTournamentDirector(context, currentTournament) ); if (result.Success && result.Value) { return(Task.FromResult(PreconditionResult.FromSuccess())); } else if (command.Name == SetupCommandName && context.Message.Content.Length > SetupCommand.Length) { // TODO: We should investigate if there's a better place to make this check, because the attribute // now knows about "setup" string tournamentName = context.Message.Content.Substring(SetupCommand.Length).Trim(); if (IsAdminUser(context) || (manager.TryGetTournament(tournamentName, out ITournamentState tournament) && CanActAsTournamentDirector(context, tournament))) { return(Task.FromResult(PreconditionResult.FromSuccess())); } } return(Task.FromResult(PreconditionResult.FromError("User did not have tournament director privileges."))); }
public void TrySetCurrentTournamentWithNoCurrentTournament() { TournamentsManager manager = new TournamentsManager() { GuildId = 1 }; TournamentState state = new TournamentState(1, DefaultTournamentName); TournamentState otherState = new TournamentState(1, DefaultTournamentName + "2"); manager.AddOrUpdateTournament(DefaultTournamentName, state, (name, oldState) => oldState); Assert.IsTrue( manager.TrySetCurrentTournament(DefaultTournamentName, out string errorMessage), "Couldn't set current tournament."); manager.TryReadActionOnCurrentTournament(currentState => { Assert.AreEqual(DefaultTournamentName, currentState.Name, "Unexpected tournament in TryReadAction"); return(Task.CompletedTask); }); manager.TryReadWriteActionOnCurrentTournament(currentState => { Assert.AreEqual( DefaultTournamentName, currentState.Name, "Unexpected tournament in TryReadWriteAction (Action)"); }); manager.TryReadWriteActionOnCurrentTournament(currentState => { Assert.AreEqual( DefaultTournamentName, currentState.Name, "Unexpected tournament in TryReadWriteAction (Func)"); return(Task.CompletedTask); }); }
public static async Task DoReadActionOnCurrentTournamentForMemberAsync( this TournamentsManager manager, IUser user, Func <IReadOnlyTournamentState, Task> action) { Result <Task> result = manager.TryReadActionOnCurrentTournament(action); if (result.Success) { await result.Value; return; } IDMChannel channel = await user.GetOrCreateDMChannelAsync(); await channel.SendMessageAsync(BotStrings.UnableToPerformCommand(result.ErrorMessage)); }
// TODO: Test these methods internal async Task HandleOnMessageReceived(SocketMessage message) { if (message.Content.TrimStart().StartsWith("!", StringComparison.InvariantCulture)) { // Ignore commands this.Logger.Verbose("Message ignored because it's a command"); return; } if (message.Author.Id == this.Client.CurrentUser.Id) { this.Logger.Verbose("Message ignored because it's from the bot"); return; } // Only pay attention to messages in a guild channel if (!(message is IUserMessage userMessage && userMessage.Channel is IGuildChannel guildChannel && userMessage.Author is IGuildUser guildUser)) { this.Logger.Verbose("Message ignored because it's not in a guild"); return; } // TODO: See if there's a cheaper way to check if we have a current tournament without making it public. // This is a read-only lock, so it shouldn't be too bad, but it'll be blocked during write operations. // Don't use the helper method because we don't want to message the user each time if there's no tournament. // We also want to split this check and the TD check because we don't need to get the Discord member for // this check. TournamentsManager manager = this.GetTournamentsManager(guildChannel.Guild); Result <bool> currentTournamentExists = manager.TryReadActionOnCurrentTournament(currentTournament => true); if (!currentTournamentExists.Success) { this.Logger.Verbose("Message ignored because no current tournament is running"); return; } // We want to do the basic access checks before using the more expensive write lock. Result <bool> hasDirectorPrivileges = manager.TryReadActionOnCurrentTournament( currentTournament => HasTournamentDirectorPrivileges(currentTournament, guildChannel, guildUser)); if (!(hasDirectorPrivileges.Success && hasDirectorPrivileges.Value)) { this.Logger.Verbose( "Message ignored because user {id} does not have tournament director privileges", guildUser.Id); return; } await manager.DoReadWriteActionOnCurrentTournamentForMemberAsync( guildUser, async currentTournament => { // TODO: We need to have locks on these. Need to check the stages between locks, and ignore the message if // it changes. // Issue is that we should really rely on a command for this case. Wouldn't quite work with locks. // But that would be something like !start, which would stop the rest of the interaction. switch (currentTournament.Stage) { case TournamentStage.AddReaders: await this.HandleAddReadersStage(currentTournament, guildChannel.Guild, message); break; case TournamentStage.SetRoundRobins: await this.HandleSetRoundRobinsStage(currentTournament, message); break; case TournamentStage.AddTeams: await this.HandleAddTeamsStage(currentTournament, message); break; case TournamentStage.Rebracketing: await this.HandleRebracketingStage(currentTournament, guildChannel.Guild, message); break; default: this.Logger.Verbose( "Message ignored because it is during the stage {stage}", currentTournament.Stage); break; } }); }