示例#1
0
        /// <inheritdoc />
        public override async ValueTask <Result <IRole> > TryParse(string value, CancellationToken ct)
        {
            if (!Snowflake.TryParse(value.Unmention(), out var roleID))
            {
                return(new ParsingError <IRole>(value.Unmention()));
            }

            var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct);

            if (!getChannel.IsSuccess)
            {
                return(Result <IRole> .FromError(new GenericError("Failed to get a valid channel."), getChannel));
            }

            var channel = getChannel.Entity;

            if (!channel.GuildID.HasValue)
            {
                return(new GenericError("You're not in a guild channel, so I can't get any roles."));
            }

            var getRoles = await _guildAPI.GetGuildRolesAsync(channel.GuildID.Value, ct);

            if (!getRoles.IsSuccess)
            {
                return(Result <IRole> .FromError(new GenericError("Failed to get the guild's roles."), getRoles));
            }

            var roles = getRoles.Entity;
            var role  = roles.FirstOrDefault(r => r.ID.Equals(roleID));

            return(role is not null
                ? Result <IRole> .FromSuccess(role)
                : new GenericError("No role with that ID could be found."));
        }
    public async Task <Result> RespondAsync(IMessageReactionAdd ev, CancellationToken ct = default)
    {
        if (ev.ChannelID.Value != _discordSettings.FeedbackChannelId ||
            !ev.GuildID.HasValue ||
            !ev.Member.HasValue ||
            ev.Member.Value.User.HasValue && ev.Member.Value.User.Value.IsBot.HasValue &&
            ev.Member.Value.User.Value.IsBot.Value ||
            !ev.Emoji.Name.HasValue ||
            ev.Emoji.Name.Value is null ||
            !_protectedEmojis.Contains(ev.Emoji.Name.Value)
            )
        {
            return(Result.FromSuccess());
        }

        var getChannelResult = await _channelApi.GetChannelAsync(ev.ChannelID, ct);

        if (!getChannelResult.IsSuccess)
        {
            return(Result.FromError(getChannelResult));
        }

        var getGuildRolesResult = await _guildApi.GetGuildRolesAsync(ev.GuildID.Value, ct);

        if (!getGuildRolesResult.IsSuccess)
        {
            return(Result.FromError(getGuildRolesResult));
        }

        var channel      = getChannelResult.Entity;
        var guildRoles   = getGuildRolesResult.Entity;
        var everyoneRole = guildRoles.FirstOrDefault(r => r.ID == ev.GuildID.Value);

        if (everyoneRole is null)
        {
            return(new NotFoundError("No @everyone role found."));
        }

        var memberRoles         = guildRoles.Where(r => ev.Member.Value.Roles.Contains(r.ID)).ToList();
        var computedPermissions = channel.PermissionOverwrites.HasValue
            ? DiscordPermissionSet.ComputePermissions(ev.UserID, everyoneRole, memberRoles,
                                                      channel.PermissionOverwrites.Value)
            : DiscordPermissionSet.ComputePermissions(ev.UserID, everyoneRole, memberRoles);

        return(computedPermissions.HasPermission(DiscordPermission.Administrator) ||
               computedPermissions.HasPermission(DiscordPermission.BanMembers)
            ? Result.FromSuccess()
            : await _channelApi.DeleteUserReactionAsync(ev.ChannelID, ev.MessageID, ev.Emoji.Name.Value, ev.UserID,
                                                        ct));
    }
示例#3
0
            public async Task <Result <QueryResult> > Handle(Query request, CancellationToken cancellationToken)
            {
                var getGuildRolesResult = await _guildApi.GetGuildRolesAsync(request.GuildId, cancellationToken);

                if (!getGuildRolesResult.IsSuccess)
                {
                    return(Result <QueryResult> .FromError(getGuildRolesResult));
                }

                var getGuildChannelsResult = await _guildApi.GetGuildChannelsAsync(request.GuildId, cancellationToken);

                if (!getGuildChannelsResult.IsSuccess)
                {
                    return(Result <QueryResult> .FromError(getGuildChannelsResult));
                }

                var roles = getGuildRolesResult.Entity;

                if (roles is null)
                {
                    return(new NotFoundError($"Could not find roles in guild {request.GuildId}."));
                }

                var channels = getGuildChannelsResult.Entity;

                if (channels is null)
                {
                    return(new NotFoundError($"Could not find channels in guild {request.GuildId}."));
                }

                var staffRole = roles.FirstOrDefault(r => r.Name.Equals(_roleNames.Staff));

                if (staffRole is null)
                {
                    return(new NotFoundError(
                               $"Could not find Staff role ({_roleNames.Staff}) in guild {request.GuildId}."));
                }

                var membersChannel = channels.FirstOrDefault(c => c.Name.Equals(_channelNames.MemberApps));

                if (membersChannel is null)
                {
                    return(new NotFoundError(
                               $"Could not find member apps channel (#{_channelNames.MemberApps}) in guild {request.GuildId}."));
                }

                return(new QueryResult(membersChannel.ID, staffRole.ID));
            }
示例#4
0
        /// <inheritdoc />
        public override async ValueTask <Result <IRole> > TryParse(string value, CancellationToken ct)
        {
            var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct);

            if (!getChannel.IsSuccess)
            {
                return(Result <IRole> .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 roles."));
            }

            var getRoles = await _guildAPI.GetGuildRolesAsync(channel.GuildID.Value, ct);

            if (!getRoles.IsSuccess)
            {
                return(Result <IRole> .FromError(getRoles));
            }

            var roles = getRoles.Entity;

            if (!Snowflake.TryParse(value.Unmention(), out var roleID))
            {
                // Try a name-based lookup
                var roleByName = roles.FirstOrDefault(r => r.Name.Equals(value, StringComparison.OrdinalIgnoreCase));
                return(roleByName is not null
                    ? Result <IRole> .FromSuccess(roleByName)
                    : new ParsingError <IRole>(value.Unmention()));
            }

            var role = roles.FirstOrDefault(r => r.ID.Equals(roleID));

            return(role is not null
                ? Result <IRole> .FromSuccess(role)
                : new ParsingError <IRole>("No role with that ID could be found."));
        }
        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));
        }
示例#6
0
    /// <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));
    }
示例#8
0
        /// <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());
        }
示例#9
0
    /// <summary>
    /// Drones the given user, creating a randomized sharkdrone character for them and forcing them into that form.
    /// </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>A creation result which may or may not have succeeded.</returns>
    public async Task <Result <Character> > DroneUserAsync
    (
        Snowflake guildID,
        Snowflake userID,
        CancellationToken ct = default
    )
    {
        var createGeneratedIdentity = await GenerateDroneIdentityAsync(guildID, userID, ct);

        if (!createGeneratedIdentity.IsSuccess)
        {
            return(Result <Character> .FromError(createGeneratedIdentity));
        }

        var(name, nickname) = createGeneratedIdentity.Entity;
        var generatedCharacterResult = await _characters.CreateCharacterAsync
                                       (
            guildID,
            userID,
            name,
            _content.GetRandomDroneAvatarUri().ToString(),
            nickname,
            _content.GetRandomDroneSummary(),
            _content.GetRandomDroneDescription(),
            new FemininePronounProvider().Family,
            ct
                                       );

        if (!generatedCharacterResult.IsSuccess)
        {
            return(generatedCharacterResult);
        }

        var character = generatedCharacterResult.Entity;

        var getGuildRoles = await _guildAPI.GetGuildRolesAsync(guildID, ct);

        if (!getGuildRoles.IsSuccess)
        {
            return(Result <Character> .FromError(getGuildRoles));
        }

        var guildRoles = getGuildRoles.Entity;
        var droneRole  = guildRoles.FirstOrDefault
                         (
            r => r.Name.Contains("Drone") || r.Name.Contains("Dronies")
                         );

        if (droneRole is not null)
        {
            var getCharacterRole = await _characterRoles.GetCharacterRoleAsync(guildID, droneRole.ID, ct);

            if (getCharacterRole.IsSuccess)
            {
                var characterRole    = getCharacterRole.Entity;
                var setCharacterRole = await _characterRoles.SetCharacterRoleAsync
                                       (
                    guildID,
                    userID,
                    character,
                    characterRole,
                    ct
                                       );

                if (!setCharacterRole.IsSuccess)
                {
                    return(Result <Character> .FromError(setCharacterRole));
                }
            }
        }

        var becomeCharacterResult = await _characters.MakeCharacterCurrentAsync(guildID, userID, character, ct);

        return(!becomeCharacterResult.IsSuccess
            ? Result <Character> .FromError(becomeCharacterResult)
            : character);
    }
        public async Task <Result> ListAvailableRolesAsync()
        {
            var baseEmbed = new Embed
            {
                Colour      = _feedback.Theme.Secondary,
                Title       = "Available character roles",
                Description = "These are the roles you can apply to your characters to automatically switch you " +
                              "to that role when you assume the character.\n" +
                              "\n" +
                              "In order to avoid mentioning everyone that has the role, use the numerical ID or " +
                              "role name instead of the actual mention. The ID is listed below along with the " +
                              "role name."
            };

            var getCharacterRoles = await _characterRoles.GetCharacterRolesAsync(_context.GuildID.Value);

            if (!getCharacterRoles.IsSuccess)
            {
                return(Result.FromError(getCharacterRoles));
            }

            var characterRoles = getCharacterRoles.Entity;

            if (!characterRoles.Any())
            {
                baseEmbed = baseEmbed with
                {
                    Footer = new EmbedFooter("There aren't any character roles available in this server.")
                };

                return((Result)await _feedback.SendContextualPaginatedMessageAsync
                       (
                           _context.User.ID,
                           new[] { baseEmbed },
                           ct : this.CancellationToken
                       ));
            }

            var getGuildRoles = await _guildAPI.GetGuildRolesAsync(_context.GuildID.Value, this.CancellationToken);

            if (!getGuildRoles.IsSuccess)
            {
                return(Result.FromError(getGuildRoles));
            }

            var guildRoles = getGuildRoles.Entity;

            var fields = characterRoles.Select
                         (
                r =>
            {
                var guildRole = guildRoles.FirstOrDefault(gr => gr.ID == r.DiscordID);

                var roleStatus = r.Access == RoleAccess.Open
                        ? "open to everyone"
                        : "restricted";

                var name = guildRole is null
                        ? $"??? ({r.DiscordID} - this role appears to be deleted.)"
                        : $"{guildRole.Name} ({r.DiscordID})";

                var value = $"*This role is {roleStatus}.*";

                return(new EmbedField(name, value));
            }
                         );

            var pages = PageFactory.FromFields(fields, pageBase: baseEmbed);

            return((Result)await _feedback.SendContextualPaginatedMessageAsync
                   (
                       _context.User.ID,
                       pages,
                       ct : this.CancellationToken
                   ));
        }
示例#11
0
        /// <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));
        }
示例#12
0
    /// <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);
    }