public async Task <Result> ShowCharacterAsync([AutocompleteProvider("character::any")] Character character) { // NSFW check var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, this.CancellationToken); if (!getChannel.IsSuccess) { return(Result.FromError(getChannel)); } var channel = getChannel.Entity; if ((channel.IsNsfw.IsDefined(out var isNsfw) || !isNsfw) && character.IsNSFW) { return(new UserError("That character is NSFW, but the channel is not... naughty!")); } var createEmbed = await CreateCharacterInfoEmbedAsync(character); if (!createEmbed.IsSuccess) { return(Result.FromError(createEmbed)); } var send = await _feedback.SendContextualEmbedAsync(createEmbed.Entity, ct : this.CancellationToken); return(send.IsSuccess ? Result.FromSuccess() : Result.FromError(send)); }
/// <inheritdoc /> public async ValueTask <Result> CheckAsync(RequireContextAttribute attribute, CancellationToken ct) { var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct); if (!getChannel.IsSuccess) { return(Result.FromError(getChannel)); } var channel = getChannel.Entity; return(attribute.Context switch { ChannelContext.DM => channel.Type is DM ? Result.FromSuccess() : new ConditionNotSatisfiedError("This command can only be used in a DM."), ChannelContext.GroupDM => channel.Type is GroupDM ? Result.FromSuccess() : new ConditionNotSatisfiedError("This command can only be used in a group DM."), ChannelContext.Guild => channel.Type is GuildText or GuildVoice or GuildCategory or GuildNews or GuildStore ? Result.FromSuccess() : new ConditionNotSatisfiedError("This command can only be used in a guild."), _ => throw new ArgumentOutOfRangeException(nameof(attribute)) });
/// <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 <bool> IsThreadChannelAsync(Snowflake channelId, CancellationToken ct = default) { var channel = await _channelApi.GetChannelAsync(channelId, ct); var isThread = channel.Entity !.ThreadMetadata.HasValue; return(isThread); }
/// <inheritdoc /> public override async ValueTask <Result <IChannel> > TryParse(string value, CancellationToken ct) { if (!Snowflake.TryParse(value.Unmention(), out var channelID)) { return(new ParsingError <IChannel>(value.Unmention())); } return(await _channelAPI.GetChannelAsync(channelID.Value, ct)); }
/// <inheritdoc /> public async Task <Result> RespondAsync(IMessageCreate ev, CancellationToken ct = default) { // return if the message has no screenshot; if (ev.Attachments.Count == 0 || ev.Author.IsBot.HasValue && ev.Author.IsBot.Value || ev.Author.IsSystem.HasValue && ev.Author.IsSystem.Value || !ev.GuildID.HasValue ) { return(Result.FromSuccess()); } var getChannelNameResult = await _channelApi.GetChannelAsync(ev.ChannelID, ct); if (!getChannelNameResult.IsSuccess) { return(Result.FromError(getChannelNameResult)); } var channelName = getChannelNameResult.Entity.Name; if (!channelName.HasValue) { return(new PropertyMissingOrNullError("Channel in which a potential application was sent has no name.")); } // return if the message isn't in #member-apps; if (!channelName.Value.Equals(_discordSettings.ChannelNames.MemberApps)) { return(Result.FromSuccess()); } var createMemberAppResult = await _mediator.Send( new CreateFromDiscordMessage.Command { DiscordMessageCreatedEvent = ev }, ct); if (createMemberAppResult.IsSuccess) { _logger.LogInformation($"Added a new application for message: {ev.ID}"); } else { return(createMemberAppResult); } var sendConfirmationResult = await _channelApi.CreateMessageAsync(ev.ChannelID, "Your application has been submitted and you will be pinged once it has been processed.", messageReference : new MessageReference(ev.ID), ct : ct); return(sendConfirmationResult.IsSuccess ? Result.FromSuccess() : Result.FromError(sendConfirmationResult.Error)); }
protected override async Task Handle(TcpRequest <GenericCommandResult> request, CancellationToken cancellationToken) { var msg = request.Message; // we want an exception if failed, as the handler can't proceed if failed, hence Parse instead of TryParse; var parsedId = ulong.Parse(request.Message.DiscordChannelId); var channelSnowflake = new Snowflake(parsedId); var getChannelResult = await _channelApi.GetChannelAsync(channelSnowflake, cancellationToken); if (!getChannelResult.IsSuccess || getChannelResult.Entity is null) { throw new Exception(getChannelResult.Error?.Message ?? $"Could not get channel with ID {parsedId}"); } // because why would ColorTranslator use the established pattern of TryParse // when it can have only one method that throws if it fails to parse instead // FFS Color colour; try { colour = ColorTranslator.FromHtml(request.Message.Colour); } catch { colour = _colourPalette.Blue; } var sendCmdExecutedNotificationResult = await _channelApi.CreateMessageAsync( channelSnowflake, content : $"[{msg.ServerId}] Command `{msg.Command}` executed!", ct : cancellationToken); if (!sendCmdExecutedNotificationResult.IsSuccess) { _logger.LogError("Error while sending command output embed: {Error}", sendCmdExecutedNotificationResult.Error.Message); } var embeds = new List <Embed>(); var output = msg.CommandOutput .Chunk(1024) .Select(chars => new string(chars.ToArray())) .ToList(); embeds .AddRange(output .Select((str, i) => new Embed { Title = $"[{i + 1}/{output.Count}] Command `{msg.Command}`'s output", Fields = new List <EmbedField> { new("Output message", string.IsNullOrEmpty(str) ? "*No output*" : str !, false) },
/// <inheritdoc /> public override async ValueTask <RetrieveEntityResult <IChannel> > TryParse(string value, CancellationToken ct) { if (!Snowflake.TryParse(value.Unmention(), out var channelID)) { return(RetrieveEntityResult <IChannel> .FromError($"Failed to parse \"{value}\" as a channel ID.")); } var getEntity = await _channelAPI.GetChannelAsync(channelID.Value, ct); return(!getEntity.IsSuccess ? RetrieveEntityResult <IChannel> .FromError(getEntity) : RetrieveEntityResult <IChannel> .FromSuccess(getEntity.Entity)); }
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)); }
/// <inheritdoc /> public async Task <Result> RespondAsync(IMessageUpdate ev, CancellationToken ct = default) { if (!ev.Attachments.HasValue || ev.Attachments.Value.Count == 0 || !ev.ID.HasValue || !ev.Author.HasValue || ev.Author.Value.IsBot.HasValue && ev.Author.Value.IsBot.Value || !ev.GuildID.HasValue || !ev.ChannelID.HasValue ) { return(Result.FromSuccess()); } var getChannelNameResult = await _channelApi.GetChannelAsync(ev.ChannelID.Value, ct); if (!getChannelNameResult.IsSuccess) { return(Result.FromError(getChannelNameResult)); } var channelName = getChannelNameResult.Entity.Name; if (!channelName.HasValue) { return(new PropertyMissingOrNullError("Channel in which a potential application was sent has no name.")); } // return if the message isn't in #member-apps; if (!channelName.Value.Equals(_discordSettings.ChannelNames.MemberApps)) { return(Result.FromSuccess()); } var res = await _mediator.Send( new UpdateFromDiscordMessage.Command { DiscordMessageUpdatedEvent = ev }, ct); if (res.IsSuccess) { _logger.LogInformation($"Updated the application for the message: {ev.ID.Value}"); } return(res); }
/// <inheritdoc /> public async ValueTask <DetermineConditionResult> CheckAsync(RequireContextAttribute attribute, CancellationToken ct) { var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct); if (!getChannel.IsSuccess) { return(DetermineConditionResult.FromError(getChannel)); } var channel = getChannel.Entity; switch (attribute.Context) { case ChannelContext.DM: { return(channel.Type is DM ? DetermineConditionResult.FromSuccess() : DetermineConditionResult.FromError("This command can only be used in a DM.")); } case ChannelContext.GroupDM: { return(channel.Type is GroupDM ? DetermineConditionResult.FromSuccess() : DetermineConditionResult.FromError("This command can only be used in a group DM.")); } case ChannelContext.Guild: { return(channel.Type is GuildText or GuildVoice or GuildCategory or GuildNews or GuildStore ? DetermineConditionResult.FromSuccess() : DetermineConditionResult.FromError("This command can only be used in a guild.")); } default: { throw new ArgumentOutOfRangeException(); } } }
protected override async Task Handle(TcpRequest <GenericCommandResult> request, CancellationToken cancellationToken) { var msg = request.Message; // we want an exception if failed, as the handler can't proceed if failed, hence Parse instead of TryParse; var parsedId = ulong.Parse(request.Message.DiscordChannelId); var channelSnowflake = new Snowflake(parsedId); var getChannelResult = await _channelApi.GetChannelAsync(channelSnowflake, cancellationToken); if (!getChannelResult.IsSuccess || getChannelResult.Entity is null) { throw new Exception(getChannelResult.Error?.Message ?? $"Could not get channel with ID {parsedId}"); } // because why would ColorTranslator use the established pattern of TryParse // when it can have only one method that throws if it fails to parse instead // FFS Color colour; try { colour = ColorTranslator.FromHtml(request.Message.Colour); } catch { colour = _colourPalette.Blue; } var embeds = new List <Embed> { new() { Title = $"Command `{msg.Command}` executed!", Fields = new List <EmbedField> { new("Server", msg.ServerId, false) }, Timestamp = DateTimeOffset.UtcNow, Colour = colour } };
/// <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.")); }
/// <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.")); }
public async Task <Result <FeedbackMessage> > SassAsync() { var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, this.CancellationToken); if (!getChannel.IsSuccess) { return(Result <FeedbackMessage> .FromError(getChannel)); } var channel = getChannel.Entity; var isNsfwChannel = channel.IsNsfw.IsDefined(out var isNsfw) && isNsfw; var getSassResult = await _sass.GetSassAsync(isNsfwChannel); if (!getSassResult.IsSuccess) { return(Result <FeedbackMessage> .FromError(getSassResult)); } var sass = getSassResult.Entity; return(new FeedbackMessage(sass, _feedback.Theme.Secondary)); }
/// <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 Execute(IJobExecutionContext context) { var failures = await _dbContext.CheckHistory .Include(x => x.Notification) .Where(x => !x.Success && x.Notification == null) .ToListAsync(); if (failures.Count == 0) { return; } if (_config.Value == null) { throw new Exception("Missing or invalid Discord configuration, cannot dispatch failure notifications"); } // Don't bother if we haven't configured a channel to use if (_config.Value.FailureChannel == null) { return; } // Get channel, check it exists var channelRequest = await _channelAPI.GetChannelAsync(new Snowflake(_config.Value.FailureChannel.Value)); if (!channelRequest.IsSuccess || channelRequest.Entity == null) { throw new Exception("Failed to get Discord channel to dispatch parse failure notifications into."); } var notified = new List <NotifiedFailure>(); var channel = channelRequest.Entity; foreach (var failure in failures) { // Attach text content of response where available FileData fileData = null; var messageSuffix = "The content of the response was missing or empty."; if (failure.ResponseContent != null) { var dataStream = new MemoryStream(); var writer = new StreamWriter(dataStream); await writer.WriteAsync(failure.ResponseContent); dataStream.Seek(0, SeekOrigin.Begin); fileData = new FileData("response_content.txt", dataStream); messageSuffix = "The content of the response is attached to this message."; } var message = new StringBuilder(); if (_config.Value.FailureMention.HasValue) { message.Append($"<@{_config.Value.FailureMention}> "); } message.Append( $"Failed to parse bans for {failure.Parser} at <t:{failure.Failed.Value.ToUnixTimeSeconds()}>, exception is as follows... ```"); // Ensure that our length fits var currLength = message.Length + failure.Exception.Length + messageSuffix.Length + 3; message.Append(currLength > 2000 ? $"{failure.Exception[0..^(currLength - 2000 + 4)]}...```"
/// <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()); }
/// <summary> /// Starts the given roleplay in the current channel, or the dedicated channel if one exists. /// </summary> /// <param name="currentChannelID">The current channel.</param> /// <param name="roleplay">The roleplay.</param> /// <returns>A modification result which may or may not have succeeded.</returns> public async Task <Result> StartRoleplayAsync(Snowflake currentChannelID, Roleplay roleplay) { var getDedicatedChannelResult = DedicatedChannelService.GetDedicatedChannel(roleplay); // Identify the channel to start the RP in. Preference is given to the roleplay's dedicated channel. var channelID = getDedicatedChannelResult.IsSuccess ? getDedicatedChannelResult.Entity : currentChannelID; var getChannel = await _channelAPI.GetChannelAsync(channelID); if (!getChannel.IsSuccess) { return(Result.FromError(getChannel)); } var channel = getChannel.Entity; if (roleplay.IsNSFW && !(channel.IsNsfw.HasValue && channel.IsNsfw.Value)) { return(new UserError ( "This channel is not marked as NSFW, while your roleplay is... naughty!" )); } var getHasActiveRoleplay = await HasActiveRoleplayAsync(channelID); if (!getHasActiveRoleplay.IsSuccess) { return(Result.FromError(getHasActiveRoleplay)); } if (getHasActiveRoleplay.Entity) { var currentRoleplayResult = await GetActiveRoleplayAsync(channelID); if (!currentRoleplayResult.IsSuccess) { return(Result.FromError(currentRoleplayResult)); } var currentRoleplay = currentRoleplayResult.Entity; var timeOfLastMessage = currentRoleplay.Messages.Last().Timestamp; var currentTime = DateTimeOffset.UtcNow; if (timeOfLastMessage < currentTime.AddHours(-4)) { currentRoleplay.IsActive = false; } else { return(new UserError("There's already a roleplay active in this channel.")); } } var start = await _roleplays.StartRoleplayAsync(roleplay, channelID); if (!start.IsSuccess) { return(start); } // If the channel in question is the roleplay's dedicated channel, enable it if (!roleplay.DedicatedChannelID.HasValue) { return(Result.FromSuccess()); } var enableChannel = await _dedicatedChannels.UpdateParticipantPermissionsAsync(roleplay); if (!enableChannel.IsSuccess) { return(enableChannel); } var joinedUsers = roleplay.JoinedUsers.Select ( u => $"<@{u.User.DiscordID}>" ); var participantList = joinedUsers.Humanize(); var send = await _channelAPI.CreateMessageAsync ( roleplay.ActiveChannelID !.Value, $"Calling {participantList}!" ); return(!send.IsSuccess ? Result.FromError(send) : Result.FromSuccess()); }
/// <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(