/// <summary> /// Generates a drone name. /// </summary> /// <param name="guildID">The ID of the guild the user is on.</param> /// <param name="userID">The ID of the user.</param> /// <param name="ct">The cancellation token in use.</param> /// <returns>The generated identity.</returns> private async Task <Result <(string Name, string Nickname)> > GenerateDroneIdentityAsync ( Snowflake guildID, Snowflake userID, CancellationToken ct = default ) { var getCharacters = await _characters.GetUserCharactersAsync(guildID, userID, ct); if (!getCharacters.IsSuccess) { return(Result <(string Name, string Nickname)> .FromError(getCharacters)); } var characters = getCharacters.Entity; var characterNames = characters.Select(c => c.Name).ToList(); var getMember = await _guildAPI.GetGuildMemberAsync(guildID, userID, ct); if (!getMember.IsSuccess) { return(Result <(string Name, string Nickname)> .FromError(getMember)); } var member = getMember.Entity; if (!member.User.IsDefined(out var user)) { throw new InvalidOperationException(); } string?characterName; string?displayName; while (true) { var serialNumber = _random.Next(0, 9999); var firstLetter = user.Username.First(); var generatedName = $"sharkdrone-{char.ToLowerInvariant(firstLetter)}{serialNumber}"; if (characterNames.Contains(generatedName)) { continue; } characterName = generatedName; displayName = $"D-V: {char.ToUpperInvariant(firstLetter)}{serialNumber:D4}"; break; } return(characterName, displayName); }
public async Task <Result> ListUnconfirmedUsersAsync ( [DiscordTypeHint(TypeHint.Role)] AutoroleConfiguration autorole ) { var getUsers = await _autoroles.GetUnconfirmedUsersAsync(autorole); if (!getUsers.IsSuccess) { return(Result.FromError(getUsers)); } var users = getUsers.Entity.ToList(); var getDiscordUsers = await Task.WhenAll ( users.Select(u => _guildAPI.GetGuildMemberAsync(_context.GuildID.Value, u.DiscordID)) ); var discordUsers = getDiscordUsers .Where(r => r.IsSuccess) .Select(r => r.Entity) .Where(u => u.User.HasValue) .ToList(); var pages = PaginatedEmbedFactory.SimpleFieldsFromCollection ( discordUsers, u => !u.Nickname.HasValue ? $"{u.User.Value.Username}#{u.User.Value.Discriminator} | {u.User.Value.ID}" : $"{u.Nickname} ({u.User.Value.Username}#{u.User.Value.Discriminator} | {u.User.Value.ID})", _ => "Not confirmed", "There are no users that haven't been confirmed for that role." ); return((Result)await _feedback.SendContextualPaginatedMessageAsync ( _context.User.ID, pages, ct : this.CancellationToken )); }
/// <inheritdoc /> public override async ValueTask <RetrieveEntityResult <IGuildMember> > TryParse(string value, CancellationToken ct) { if (!Snowflake.TryParse(value.Unmention(), out var guildMemberID)) { return(RetrieveEntityResult <IGuildMember> .FromError ( $"Failed to parse \"{value}\" as a guild member ID." )); } var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct); if (!getChannel.IsSuccess) { return(RetrieveEntityResult <IGuildMember> .FromError(getChannel)); } var channel = getChannel.Entity; if (!channel.GuildID.HasValue) { return(RetrieveEntityResult <IGuildMember> .FromError ( "You're not in a guild channel, so I can't get any guild members." )); } var getGuildMember = await _guildAPI.GetGuildMemberAsync(channel.GuildID.Value, guildMemberID.Value, ct); if (!getGuildMember.IsSuccess) { return(RetrieveEntityResult <IGuildMember> .FromError(getGuildMember)); } return(getGuildMember.IsSuccess ? RetrieveEntityResult <IGuildMember> .FromSuccess(getGuildMember.Entity) : RetrieveEntityResult <IGuildMember> .FromError("No guild member with that ID could be found.")); }
/// <inheritdoc /> public override async ValueTask <Result <IGuildMember> > TryParse(string value, CancellationToken ct) { if (!Snowflake.TryParse(value.Unmention(), out var guildMemberID)) { return(new ParsingError <IGuildMember>(value)); } var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct); if (!getChannel.IsSuccess) { return(Result <IGuildMember> .FromError(getChannel)); } var channel = getChannel.Entity; if (!channel.GuildID.HasValue) { return(new InvalidOperationError("You're not in a guild channel, so I can't get any guild members.")); } return(await _guildAPI.GetGuildMemberAsync(channel.GuildID.Value, guildMemberID.Value, ct)); }
public async Task <Result <FeedbackMessage> > SetCharacterRoleAsync ( [RequireEntityOwner] [AutocompleteProvider("character::owned")] Character character, IRole discordRole ) { var getRoleResult = await _characterRoles.GetCharacterRoleAsync ( _context.GuildID.Value, discordRole.ID, this.CancellationToken ); if (!getRoleResult.IsSuccess) { return(Result <FeedbackMessage> .FromError(getRoleResult)); } // Get a bunch of stuff for permission checking... var getMember = await _guildAPI.GetGuildMemberAsync ( _context.GuildID.Value, _context.User.ID, this.CancellationToken ); if (!getMember.IsSuccess) { return(Result <FeedbackMessage> .FromError(getMember)); } var member = getMember.Entity; var getGuildRoles = await _guildAPI.GetGuildRolesAsync(_context.GuildID.Value, this.CancellationToken); if (!getGuildRoles.IsSuccess) { return(Result <FeedbackMessage> .FromError(getGuildRoles)); } var guildRoles = getGuildRoles.Entity; var everyoneRole = guildRoles.First(r => r.ID == _context.GuildID.Value); var memberRoles = guildRoles.Where(r => member.Roles.Contains(r.ID)).ToList(); // We ignore channel overrides here; the user should have it on a guild level var computedPermissions = DiscordPermissionSet.ComputePermissions ( _context.User.ID, everyoneRole, memberRoles ); var characterRole = getRoleResult.Entity; if (characterRole.Access == RoleAccess.Restricted) { if (!computedPermissions.HasPermission(DiscordPermission.ManageRoles)) { return(new UserError ( "That role is restricted, and you must be able to manage roles to use it." )); } } var setRoleResult = await _characterRoles.SetCharacterRoleAsync ( _context.GuildID.Value, _context.User.ID, character, characterRole, this.CancellationToken ); return(!setRoleResult.IsSuccess ? Result <FeedbackMessage> .FromError(setRoleResult) : new FeedbackMessage("Character role set.", _feedback.Theme.Secondary)); }
/// <inheritdoc /> public async ValueTask <Result> CheckHasRequiredPermission( DiscordPermission permission, Snowflake channelId, IUser userToCheck, CancellationToken ct = default ) { var getChannel = await _channelApi.GetChannelAsync(channelId, ct); if (!getChannel.IsSuccess) { return(Result.FromError(getChannel)); } var channel = getChannel.Entity; if (!channel.GuildID.HasValue) { return(new ConditionNotSatisfiedError( "Command requires a guild permission but was executed outside of a guild.")); } var guildId = channel.GuildID.Value; var getGuildMember = await _guildApi.GetGuildMemberAsync(guildId, userToCheck.ID, ct); if (!getGuildMember.IsSuccess) { return(Result.FromError(getGuildMember)); } var getGuildRoles = await _guildApi.GetGuildRolesAsync(guildId, ct); if (!getGuildRoles.IsSuccess) { return(Result.FromError(getGuildRoles)); } var guildRoles = getGuildRoles.Entity; var everyoneRole = guildRoles.FirstOrDefault(r => r.Name.Equals("@everyone")); if (everyoneRole is null) { return(new NotFoundError("No @everyone role found.")); } var user = getGuildMember.Entity; if (user is null) { return(new NotFoundError("Executing user not found")); } var getGuild = await _guildApi.GetGuildAsync(guildId, ct : ct); if (!getGuild.IsSuccess) { return(Result.FromError(getGuild)); } var guildOwnerId = getGuild.Entity.OwnerID; // succeed if the user is the Owner of the guild if (guildOwnerId.Equals(userToCheck.ID)) { return(Result.FromSuccess()); } var memberRoles = guildRoles.Where(r => user.Roles.Contains(r.ID)).ToList(); var computedPermissions = channel.PermissionOverwrites switch { { HasValue : true, Value : { } overwrites } => DiscordPermissionSet.ComputePermissions(
/// <summary> /// Posts a notification that a message was deleted. /// </summary> /// <param name="message">The deleted message.</param> /// <param name="guildID">The ID of the guild in which the message was.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public async Task <Result> NotifyMessageDeletedAsync(IMessage message, Snowflake guildID) { // We don't care about bot messages var isNonFeedbackMessage = (message.Author.IsBot.IsDefined(out var isBot) && isBot) || (message.Author.IsSystem.IsDefined(out var isSystem) && isSystem); if (isNonFeedbackMessage) { return(Result.FromSuccess()); } var getChannel = await GetMonitoringChannelAsync(guildID); if (!getChannel.IsSuccess) { return(Result.FromError(getChannel)); } var getSelf = await _userAPI.GetCurrentUserAsync(); if (!getSelf.IsSuccess) { return(Result.FromError(getSelf)); } var self = getSelf.Entity; var channel = getChannel.Entity; var eb = new Embed { Colour = _feedback.Theme.Warning, Title = "Message Deleted" }; var getGuildRoles = await _guildAPI.GetGuildRolesAsync(guildID); if (!getGuildRoles.IsSuccess) { return(Result.FromError(getGuildRoles)); } var guildRoles = getGuildRoles.Entity; var everyoneRole = guildRoles.First(r => r.ID == guildID); var getGuildMember = await _guildAPI.GetGuildMemberAsync(guildID, self.ID); if (!getGuildMember.IsSuccess) { return(Result.FromError(getGuildMember)); } var botGuildMember = getGuildMember.Entity; var botRoles = guildRoles.Where(r => botGuildMember.Roles.Contains(r.ID)).ToList(); var botPermissions = DiscordPermissionSet.ComputePermissions ( self.ID, everyoneRole, botRoles ); var extra = string.Empty; if (botPermissions.HasPermission(DiscordPermission.ViewAuditLog)) { var getMostProbableDeleter = await FindMostProbableDeleterAsync(message, guildID); if (!getMostProbableDeleter.IsSuccess) { return(Result.FromError(getMostProbableDeleter)); } var userID = getMostProbableDeleter.Entity; var getUser = await _userAPI.GetUserAsync(userID); if (!getUser.IsSuccess) { return(Result.FromError(getUser)); } var mostProbableDeleter = getUser.Entity; var isNonUserDeleter = (mostProbableDeleter.IsBot.IsDefined(out var isDeleterBot) && isDeleterBot) || (mostProbableDeleter.IsSystem.IsDefined(out var isDeleterSystem) && isDeleterSystem); // We don't care about bot deletions if (!isNonUserDeleter) { extra = $" (probably) by <@{mostProbableDeleter.ID}>"; } } eb = eb with { Description = $"A message was deleted from <#{message.ChannelID}>{extra}." }; var quote = QuoteService.CreateMessageQuote(message, self.ID); var sendResult = await _feedback.SendEmbedAsync(channel, eb); if (!sendResult.IsSuccess) { return(Result.FromError(sendResult)); } sendResult = await _feedback.SendEmbedAsync(channel, quote); return(sendResult.IsSuccess ? Result.FromSuccess() : Result.FromError(sendResult)); }
/// <inheritdoc /> public async ValueTask <Result> CheckHasRequiredPermission( DiscordPermission permission, Snowflake channelId, IUser userToCheck, CancellationToken ct = default ) { var getChannel = await _channelApi.GetChannelAsync(channelId, ct); if (!getChannel.IsSuccess) { return(Result.FromError(getChannel)); } var channel = getChannel.Entity; if (!channel.GuildID.HasValue) { return(new ConditionNotSatisfiedError( "Command requires a guild permission but was executed outside of a guild.")); } var guildId = channel.GuildID.Value; var getGuildMember = await _guildApi.GetGuildMemberAsync(guildId, userToCheck.ID, ct); if (!getGuildMember.IsSuccess) { return(Result.FromError(getGuildMember)); } var getGuildRoles = await _guildApi.GetGuildRolesAsync(guildId, ct); if (!getGuildRoles.IsSuccess) { return(Result.FromError(getGuildRoles)); } var guildRoles = getGuildRoles.Entity; var everyoneRole = guildRoles.FirstOrDefault(r => r.Name.Equals("@everyone")); if (everyoneRole is null) { return(new NotFoundError("No @everyone role found.")); } var user = getGuildMember.Entity; if (user is null) { return(new NotFoundError("Executing user not found")); } var getGuild = await _guildApi.GetGuildAsync(guildId, ct : ct); if (!getGuild.IsSuccess) { return(Result.FromError(getGuild)); } var guildOwnerId = getGuild.Entity.OwnerID; // succeed if the user is the Owner of the guild if (guildOwnerId.Equals(userToCheck.ID)) { return(Result.FromSuccess()); } var memberRoles = guildRoles.Where(r => user.Roles.Contains(r.ID)).ToList(); IDiscordPermissionSet computedPermissions; if (channel.PermissionOverwrites.HasValue) { computedPermissions = DiscordPermissionSet.ComputePermissions( userToCheck.ID, everyoneRole, memberRoles, channel.PermissionOverwrites.Value ); } else { computedPermissions = DiscordPermissionSet.ComputePermissions( userToCheck.ID, everyoneRole, memberRoles ); } // succeed if the user is an Administrator of the guild if (computedPermissions.HasPermission(DiscordPermission.Administrator)) { return(Result.FromSuccess()); } var hasPermission = computedPermissions.HasPermission(permission); return(!hasPermission ? new ConditionNotSatisfiedError( $"Guild User requesting the command does not have the required {permission.ToString()} permission") : Result.FromSuccess()); }
public async Task <Result> HasPermissionAsync ( Snowflake discordServer, Snowflake discordUser, IPermission requiredPermission, PermissionTarget target, CancellationToken ct = default ) { var getDiscordServer = await _guildAPI.GetGuildAsync(discordServer, ct : ct); if (!getDiscordServer.IsSuccess) { return(Result.FromError(getDiscordServer)); } var guild = getDiscordServer.Entity; // The server owner always has all permissions by default if (guild.OwnerID == discordUser) { return(Result.FromSuccess()); } // Special handling for the All target if (target == PermissionTarget.All) { var hasSelf = await HasPermissionAsync ( discordServer, discordUser, requiredPermission, PermissionTarget.Self, ct ); var hasOther = await HasPermissionAsync ( discordServer, discordUser, requiredPermission, PermissionTarget.Other, ct ); if (hasSelf.IsSuccess && hasOther.IsSuccess) { return(Result.FromSuccess()); } return(new UserError("Permission denied.")); } var hasPermission = false; var getGuildMember = await _guildAPI.GetGuildMemberAsync(discordServer, discordUser, ct); if (!getGuildMember.IsSuccess) { return(Result.FromError(getGuildMember)); } var member = getGuildMember.Entity; // Check if the user is part of any roles which this permission applies to var rolePermissions = await GetApplicableRolePermissionsAsync(member.Roles, ct); var rolePermission = rolePermissions.FirstOrDefault ( p => p.Permission == requiredPermission.UniqueIdentifier && p.Target == target ); if (rolePermission is not null) { hasPermission = rolePermission.IsGranted; } // Check if the user has the permission applied to themselves var userPermission = await _database.UserPermissions.ServersideQueryAsync ( q => q .Where(p => p.ServerID == discordServer) .Where(p => p.UserID == discordUser) .Where(p => p.Permission == requiredPermission.UniqueIdentifier) .Where(p => p.Target == target) .SingleOrDefaultAsync(ct) ); if (userPermission is not null) { hasPermission = userPermission.IsGranted; } if (rolePermission is null && userPermission is null) { // Use the permission's default value hasPermission = requiredPermission.IsGrantedByDefaultTo(target); } return(hasPermission ? Result.FromSuccess() : new UserError("Permission denied.")); }
/// <summary> /// Consumes a message, adding it to the active roleplay in its channel if the author is a participant. /// </summary> /// <param name="message">The received message.</param> /// <param name="guildID">The ID of the guild in which the message was received.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public async Task <Result> ConsumeMessageAsync(IPartialMessage message, Snowflake guildID) { if (!message.ID.HasValue) { return(new InvalidOperationError("Unable to process messages without IDs.")); } if (!message.Timestamp.HasValue) { return(new InvalidOperationError("Unable to process messages without timestamps.")); } if (!message.Content.HasValue) { return(new InvalidOperationError("Unable to process messages without content.")); } if (!message.ChannelID.HasValue) { return(new InvalidOperationError("Unable to process messages without channel IDs.")); } if (!message.Author.HasValue) { return(new InvalidOperationError("Unable to process messages without authors.")); } var hasActive = await HasActiveRoleplayAsync(message.ChannelID.Value); if (!hasActive.IsSuccess) { return(Result.FromError(hasActive)); } if (!hasActive.Entity) { return(Result.FromSuccess()); } var result = await GetActiveRoleplayAsync(message.ChannelID.Value); if (!result.IsSuccess) { return(Result.FromError(result)); } var roleplay = result.Entity; if (!roleplay.HasJoined(message.Author.Value)) { return(new UserError("The given message was not authored by a participant of the roleplay.")); } var userNick = message.Author.Value.Username; var getMember = await _guildAPI.GetGuildMemberAsync(guildID, message.Author.Value.ID); if (getMember.IsSuccess) { var member = getMember.Entity; if (member.Nickname.HasValue && member.Nickname.Value is not null) { userNick = member.Nickname.Value; } } var getAuthor = await _users.GetOrRegisterUserAsync(message.Author.Value.ID); if (!getAuthor.IsSuccess) { return(Result.FromError(getAuthor)); } var author = getAuthor.Entity; return(await _roleplays.AddOrUpdateMessageInRoleplayAsync ( roleplay, author, message.ID.Value, message.Timestamp.Value, userNick, message.Content.Value )); }
/// <summary> /// Updates the user's current nickname based on their character. /// </summary> /// <param name="guildID">The ID of the guild the user is on.</param> /// <param name="userID">The ID of the discord user.</param> /// <param name="ct">The cancellation token in use.</param> /// <returns>A modification result which may or may not have succeeded.</returns> private async Task <Result> UpdateUserNickname ( Snowflake guildID, Snowflake userID, CancellationToken ct = default ) { var getUser = await _users.GetOrRegisterUserAsync(userID, ct); if (!getUser.IsSuccess) { return(Result.FromError(getUser)); } var getServer = await _servers.GetOrRegisterServerAsync(guildID, ct); if (!getServer.IsSuccess) { return(Result.FromError(getServer)); } var user = getUser.Entity; var server = getServer.Entity; var getMember = await _guildAPI.GetGuildMemberAsync(guildID, userID, ct); if (!getMember.IsSuccess) { return(Result.FromError(getMember)); } var member = getMember.Entity; if (!member.User.IsDefined(out var discordUser)) { throw new InvalidOperationException(); } string newNick; var getNewCharacter = await _characters.GetCurrentCharacterAsync(user, server, ct); if (getNewCharacter.IsSuccess) { var newCharacter = getNewCharacter.Entity; newNick = newCharacter.Nickname.IsNullOrWhitespace() ? discordUser.Username : newCharacter.Nickname; } else { newNick = member.User.Value.Username; } var modifyNickname = await _guildAPI.ModifyGuildMemberAsync(guildID, userID, newNick, ct : ct); if (modifyNickname.IsSuccess) { return(modifyNickname); } if (modifyNickname.Error is RestResultError <RestError> rre) { return(rre.Error.Code is not DiscordError.MissingPermission ? modifyNickname : Result.FromSuccess()); // squelch missing permission errors } if (modifyNickname.Error is not HttpResultError hre) { return(modifyNickname); } if (hre.StatusCode is not HttpStatusCode.Forbidden) { return(modifyNickname); } return(new UserError ( "I'm forbidden from setting the user's nickname - typically, this means the target was the " + "server owner. This is a Discord limitation, and can't be fixed." )); }
/// <inheritdoc /> public async Task <Result <MemberApplication> > Handle(Command request, CancellationToken cancellationToken) { var app = await _context.MemberApplications .FirstOrDefaultAsync( a => a.MemberApplicationId == request.Id && a.GuildId == request.GuildId.Value, cancellationToken); if (app is null) { return(new NotFoundError($"Application with ID `{request.Id}` does not exist")); } var getRoles = await _guildApi.GetGuildRolesAsync(request.GuildId, cancellationToken); if (!getRoles.IsSuccess) { return(Result <MemberApplication> .FromError(getRoles.Error)); } var role = getRoles.Entity .FirstOrDefault(r => r.Name.Contains($"[{request.ServerPrefix.ToUpper()}]")); if (role is null) { return(new NotFoundError( $"Could not find a role corresponding to the server prefix: `{request.ServerPrefix}`.")); } var userId = new Snowflake(app.AuthorDiscordId); var getUserToPromoteResult = await _guildApi.GetGuildMemberAsync(request.GuildId, userId, cancellationToken); if (!getUserToPromoteResult.IsSuccess) { return(Result <MemberApplication> .FromError(getUserToPromoteResult.Error)); } if (getUserToPromoteResult.Entity is null) { return(new NotFoundError($"Could not find a user with ID: `{app.AuthorDiscordId}`.")); } foreach (var ign in request.Igns) { var proto = new GenericCommand { DiscordChannelId = request.ChannelId.ToString(), DiscordCommandName = "promote", DefaultCommand = "ranks add $1 member", Args = { ign } }; var id = request.ServerPrefix.ToUpperInvariant(); var server = _ps.GetOnlineServerOrDefault(id); if (server is null) { return(Result <MemberApplication> .FromError( new NotFoundError($"Could not find server with ID {id}"))); } await _ps.SendMessage(server, proto); } var addRoleResult = await _guildApi.AddGuildMemberRoleAsync(request.GuildId, userId, role.ID, new(), cancellationToken); if (!addRoleResult.IsSuccess) { return(Result <MemberApplication> .FromError(addRoleResult)); } app.AppStatus = ApplicationStatus.Approved; await _context.SaveChangesAsync(cancellationToken); return(Result <MemberApplication> .FromSuccess(app)); }
/// <summary> /// Applies the given autorole to the given user, if it is applicable. If the user no longer qualifies, /// the autorole is removed. /// </summary> /// <param name="autorole">The autorole.</param> /// <param name="guildID">The ID of the guild the user is on.</param> /// <param name="userID">The ID of the user.</param> /// <param name="ct">The cancellation token in use.</param> /// <returns>A modification result which may or may not have succeeded.</returns> public async Task <Result <AutoroleUpdateStatus> > UpdateAutoroleForUserAsync ( AutoroleConfiguration autorole, Snowflake guildID, Snowflake userID, CancellationToken ct = default ) { if (!autorole.IsEnabled) { return(Disabled); } if (!autorole.Conditions.Any()) { return(Unconditional); } var getRoles = await _guildAPI.GetGuildRolesAsync(guildID, ct); if (!getRoles.IsSuccess) { return(Result <AutoroleUpdateStatus> .FromError(getRoles)); } var roles = getRoles.Entity; if (roles.All(r => r.ID != autorole.DiscordRoleID)) { // If the role can't be found any longer, we disable it var disableAutoroleAsync = await _autoroles.DisableAutoroleAsync(autorole, ct); return(!disableAutoroleAsync.IsSuccess ? Result <AutoroleUpdateStatus> .FromError(disableAutoroleAsync) : Disabled); } var getIsUserQualified = await _autoroles.IsUserQualifiedForAutoroleAsync(autorole, userID, ct); if (!getIsUserQualified.IsSuccess) { return(Result <AutoroleUpdateStatus> .FromError(getIsUserQualified)); } var isUserQualified = getIsUserQualified.Entity; var getMember = await _guildAPI.GetGuildMemberAsync(guildID, userID, ct); if (!getMember.IsSuccess) { return(Result <AutoroleUpdateStatus> .FromError(getMember)); } var member = getMember.Entity; if (!member.User.IsDefined(out var user)) { return(Unqualified); } if (user.IsBot.IsDefined(out var isBot) && isBot) { return(Unqualified); } var userHasRole = member.Roles.Contains(autorole.DiscordRoleID); switch (isUserQualified) { case true when userHasRole: { return(Unchanged); } case false when userHasRole: { var removeRole = await _guildAPI.RemoveGuildMemberRoleAsync ( guildID, userID, autorole.DiscordRoleID, ct : ct ); if (!removeRole.IsSuccess) { return(Result <AutoroleUpdateStatus> .FromError(removeRole)); } var getConfirmation = await _autoroles.GetOrCreateAutoroleConfirmationAsync ( autorole, userID, ct ); if (!getConfirmation.IsSuccess) { return(Removed); } // Remove any existing affirmation var confirmation = getConfirmation.Entity; var removeConfirmation = await _autoroles.RemoveAutoroleConfirmationAsync(confirmation, ct); return(!removeConfirmation.IsSuccess ? Result <AutoroleUpdateStatus> .FromError(removeConfirmation) : Removed); } case false: { // At this point, the user doesn't have the role, and either is or is not qualified. // We consider a no-op for an unqualified user a success. return(Unqualified); } } if (autorole.RequiresConfirmation) { var getConfirmation = await _autoroles.GetOrCreateAutoroleConfirmationAsync ( autorole, userID, ct ); if (!getConfirmation.IsSuccess) { return(Result <AutoroleUpdateStatus> .FromError(getConfirmation)); } var confirmation = getConfirmation.Entity; if (!confirmation.IsConfirmed) { // We consider a no-op for an qualified but not affirmed user a success. return(RequiresAffirmation); } } var addRole = await _guildAPI.AddGuildMemberRoleAsync(guildID, userID, autorole.DiscordRoleID, ct : ct); return(!addRole.IsSuccess ? Result <AutoroleUpdateStatus> .FromError(addRole) : Applied); }