public async Task <ITextChannel> CreateChannelsForFinals( ISelfUser botUser, ITournamentState state, Game finalsGame, int finalsRoundNumber, int roomIndex) { Verify.IsNotNull(this.Guild, "guild"); Verify.IsNotNull(state, nameof(state)); Verify.IsNotNull(finalsGame, nameof(finalsGame)); TournamentRoles roles = this.GetTournamentRoles(state); ICategoryChannel finalsCategoryChannel = await this.Guild.CreateCategoryAsync("Finals"); ITextChannel channel = await this.CreateTextChannelAsync( botUser, finalsCategoryChannel, finalsGame, roles, finalsRoundNumber, roomIndex); state.ChannelIds = state.ChannelIds .Concat(new ulong[] { channel.Id }) .Concat(new ulong[] { finalsCategoryChannel.Id }); return(channel); }
private async Task <ITextChannel> CreateTextChannelAsync( ISelfUser botUser, ICategoryChannel parent, Game game, TournamentRoles roles, int roundNumber, int roomNumber) { Verify.IsNotNull(this.Guild, "guild"); Verify.IsNotNull(parent, nameof(parent)); Verify.IsNotNull(game, nameof(game)); Verify.IsNotNull(roles, nameof(roles)); // The room and role names will be the same. this.Logger.Debug("Creating text channel for room {0} in round {1}", roomNumber, roundNumber); string name = GetTextRoomName(game.Reader, roundNumber); ITextChannel channel = await this.Guild.CreateTextChannelAsync( name, channelProps => { channelProps.CategoryId = parent.Id; }, RequestOptionsSettings.Default); this.Logger.Debug("Text channel for room {0} in round {1} created", roomNumber, roundNumber); // We need to add the bot's permissions first before we disable permissions for everyone. await channel.AddPermissionOverwriteAsync( botUser, PrivilegedOverwritePermissions, RequestOptionsSettings.Default); this.Logger.Debug("Adding permissions to text channel for room {0} in round {1}", roomNumber, roundNumber); await channel.AddPermissionOverwriteAsync( this.Guild.EveryoneRole, EveryonePermissions, RequestOptionsSettings.Default); await channel.AddPermissionOverwriteAsync( roles.DirectorRole, PrivilegedOverwritePermissions, RequestOptionsSettings.Default); if (roles.RoomReaderRoles.TryGetValue(game.Reader, out IRole readerRole)) { await channel.AddPermissionOverwriteAsync( readerRole, PrivilegedOverwritePermissions, RequestOptionsSettings.Default); } else { this.Logger.Warning("Could not find a reader role for a reader with ID {0}.", game.Reader?.Id); } List <Task> addTeamRolesToChannel = new List <Task>(); foreach (Team team in game.Teams) { if (!roles.TeamRoles.TryGetValue(team, out IRole role)) { this.Logger.Warning("Team {name} did not have a role defined.", team.Name); continue; } // TODO: Investigate if it's possible to parallelize this. Other attempts to do so (Task.WhenAll, // AsyncEnumerable's ParallelForEachAsync) have had bugs where roles sometimes aren't assigned to a // channel. Adding an await in the loop seems to be the only thing that await this.AddPermission(channel, role); } await Task.WhenAll(addTeamRolesToChannel); this.Logger.Debug("Added permissions to text channel for room {0} in round {1}", roomNumber, roundNumber); return(channel); }
public async Task SetupFinalsAsync(IGuildUser readerUser, string rawTeamNameParts) { ITextChannel channel = null; await this.DoReadWriteActionOnCurrentTournamentAsync( async currentTournament => { if (currentTournament?.Stage != TournamentStage.RunningTournament) { this.Logger.Debug("Could not start finals in stage {stage}", currentTournament?.Stage); await this.SendChannelMessage(BotStrings.ErrorFinalsOnlySetDuringPrelimsOrPlayoffs); return; } if (!currentTournament.TryGetReader(readerUser.Id, out Reader reader)) { this.Logger.Debug("Could not start finals because {1} is not a reader", readerUser.Id); await this.SendChannelMessage(BotStrings.ErrorGivenUserIsntAReader); return; } if (rawTeamNameParts == null) { this.Logger.Debug( "Could not start finals because no teams were specified"); await this.SendChannelMessage(BotStrings.ErrorNoTeamsSpecified); return; } string combinedTeamNames = string.Join(" ", rawTeamNameParts).Trim(); if (!TeamNameParser.TryGetTeamNamesFromParts( combinedTeamNames, out IList <string> teamNames, out string errorMessage)) { this.Logger.Debug( "Could not start finals because of this error: {errorMessage}", errorMessage); await this.SendChannelMessage(BotStrings.ErrorGenericMessage(errorMessage)); return; } if (teamNames.Count != 2) { this.Logger.Debug( "Could not start finals because {count} teams were specified", teamNames.Count); await this.SendChannelMessage(BotStrings.ErrorTwoTeamsMustBeSpecifiedFinals(teamNames.Count)); return; } Team[] teams = teamNames.Select(name => new Team() { Name = name }) .ToArray(); if (currentTournament.Teams.Intersect(teams).Count() != teams.Length) { this.Logger.Debug( "Could not start finals because some teams were not in the tournament", teamNames.Count); await this.SendChannelMessage( BotStrings.ErrorAtLeastOneTeamNotInTournament(string.Join(", ", teamNames))); return; } // Create a finals channel and give access to the teams and readers Game finalsGame = new Game() { Reader = reader, Teams = teams }; int finalsRoundNumber = currentTournament.Schedule.Rounds.Count + 1; IList <Game> finalGames = currentTournament.Schedule.Rounds[currentTournament.Schedule.Rounds.Count - 1].Games; int roomIndex = 0; foreach (Game game in finalGames) { if (game.Reader.Equals(reader)) { break; } roomIndex++; } if (roomIndex >= finalGames.Count) { // Need to have a fall-back somehow. For now default to the first room. roomIndex = 0; } // TODO: Look into creating the channels after the update stage so we can release the lock // sooner. However, this does mean that a failure to create channels will leave us in a bad // state. channel = await this.ChannelManager.CreateChannelsForFinals( this.Context.Client.CurrentUser, currentTournament, finalsGame, finalsRoundNumber, roomIndex); currentTournament.UpdateStage( TournamentStage.Finals, out string nextStageTitle, out string nextStageInstructions); }); if (channel != null) { this.Logger.Debug("Finals started successfully"); await this.Context.Channel.SendMessageAsync( BotStrings.FinalsParticipantsPleaseJoin(channel.Mention), options : RequestOptionsSettings.Default); } }