Skip to content

Commit

Permalink
Merge branch 'dev' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
BrammyS committed Aug 11, 2021
2 parents a4f17c5 + c1c8e68 commit f7f2da7
Show file tree
Hide file tree
Showing 79 changed files with 770 additions and 305 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,61 +70,92 @@ public override async Task<Result> CheckRequirementAsync(ISlashCommandContext co
else
{
var guildResult = await restGuild.GetGuildAsync(context.GuildId.Value).ConfigureAwait(false);

if (!guildResult.IsSuccessful)
{
return Result.FromError(guildResult.ErrorResult ?? new ErrorResult("Failed to get the guild"));
}

guild = guildResult.Entity;
}

if (guild is null)
{
throw new NullReferenceException("Failed to get guild");
throw new NullReferenceException("Failed to get the guild");
}

// Get the bot user.
var discordTokens = services.GetRequiredService<DiscordTokens>();
var botMember = await restGuild.GetGuildMemberAsync(context.GuildId.Value, discordTokens.ApplicationId);
if (!botMember.IsSuccessful)
var botMemberResult = await restGuild.GetGuildMemberAsync(context.GuildId.Value, discordTokens.ApplicationId);
if (!botMemberResult.IsSuccessful)
{
return Result.FromError(new SlashCommandRequireBotPermissionErrorResult("Guild member does not exist", default));
return Result.FromError(botMemberResult.ErrorResult ?? new SlashCommandRequireBotPermissionErrorResult("Guild member does not exist", default));
}

if (botMemberResult.Entity is null)
{
throw new NullReferenceException("Failed to get the bot member");
}

// Get the bot role permissions.
var botRoles = guild.Roles.Where(x => botMember.Entity!.Roles.Contains(x.Id));
var botRoles = guild.Roles.Where(x => botMemberResult.Entity.Roles.Contains(x.Id));
var rolePerms = botRoles.Aggregate(DiscordPermission.None, (current, botRole) => current | botRole.Permissions);


// Get the channel.
IDiscordChannel? channel;
if (context.Guild is not null)
// Admin overrides any potential permission overwrites.
if ((rolePerms & DiscordPermission.Administrator) == DiscordPermission.Administrator)
{
channel = context.Channel;
return Result.FromSuccess();
}
else

// Only try to deny any channel perms if there are any required.
if (_requiredPermission.HasChannelPermissions())
{
var restChannel = services.GetRequiredService<IDiscordRestChannel>();
var channelResult = await restChannel.GetChannelAsync(context.ChannelId).ConfigureAwait(false);
channel = channelResult.Entity;
}
// Get the channel.
IDiscordChannel? channel;
if (context.Guild is not null)
{
channel = context.Channel;
}
else
{
var restChannel = services.GetRequiredService<IDiscordRestChannel>();
var channelResult = await restChannel.GetChannelAsync(context.ChannelId).ConfigureAwait(false);

if (channel is null)
{
throw new NullReferenceException("Failed to get channel");
}
if (!channelResult.IsSuccessful)
{
return Result.FromError(channelResult.ErrorResult ?? new ErrorResult("Failed to get the channel"));
}

channel = channelResult.Entity;
}

if (channel.PermissionOverwrites is null)
{
throw new NullReferenceException("Missing permission overwrites");
}
if (channel is null)
{
throw new NullReferenceException("Failed to get the channel");
}

// Removed the denied permissions from the role perms.
foreach (var permissionOverwrite in channel.PermissionOverwrites)
{
if (permissionOverwrite.TargetId == discordTokens.ApplicationId || botMember.Entity!.Roles.Contains(permissionOverwrite.TargetId))
if (channel.PermissionOverwrites is null)
{
rolePerms |= permissionOverwrite.Allow;
rolePerms &= ~permissionOverwrite.Deny;
throw new NullReferenceException("Missing permission overwrites");
}

// Removed the denied permissions from the role perms.
foreach (var permissionOverwrite in channel.PermissionOverwrites)
{
if (permissionOverwrite.TargetId == discordTokens.ApplicationId || botMemberResult.Entity.Roles.Contains(permissionOverwrite.TargetId))
{
rolePerms |= permissionOverwrite.Allow;
rolePerms &= ~permissionOverwrite.Deny;
}
}
}

if ((rolePerms & _requiredPermission) == _requiredPermission)
{
return Result.FromSuccess();
}

var missingPerms = _requiredPermission.ToList().Where(requiredPerm => !rolePerms.HasFlag(requiredPerm)).ToList();
var missingPerms = _requiredPermission.ToList().Where(requiredPerm => (rolePerms & requiredPerm) != requiredPerm).ToList();
if (missingPerms.Any())
{
return Result.FromError(new SlashCommandRequireBotPermissionErrorResult("Bot did not meet permission requirements", missingPerms));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,121 @@ public class SlashCommandChoiceAttribute : Attribute
/// Initializes a new instance of <see cref="SlashCommandChoiceAttribute" />.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The raw value of the choice.</param>
/// <param name="value">The <see cref="string"/> value of the choice.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="name" /> or <paramref name="value" /> doesn't match the
/// command name requirements.
/// </exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name" /> or <paramref name="value" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="name"> or </paramref> <paramref name="value"/> is to short or to long.</exception>
public SlashCommandChoiceAttribute(string name, string value)
{
if (name.Length is < 1 or > 32)
throw new ArgumentOutOfRangeException(nameof(name.Length), "Command option choice names must be between 1 and 32 characters.");

if (value.Length is < 1 or > 100)
throw new ArgumentOutOfRangeException(nameof(value.Length), "Command option choice values must be between 1 and 100 characters.");

Name = name ?? throw new ArgumentNullException(nameof(name), "Choice name can not be null");
StringValue = value ?? throw new ArgumentNullException(nameof(value), "Choice value can not be null");
}

/// <summary>
/// Initializes a new instance of <see cref="SlashCommandChoiceAttribute" />.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The <see cref="int"/> value of the choice.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="name" /> or <paramref name="value" /> doesn't match the
/// command name requirements.
/// </exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name" />.</exception>
public SlashCommandChoiceAttribute(string name, int value)
{
if (name.Length is < 1 or > 32)
throw new ArgumentException("Command option choice names must be between 1 and 32 characters.");

if (value.Length > 100)
throw new ArgumentException("Command option choice values must contain less then 100 characters.");
Name = name ?? throw new ArgumentNullException(nameof(name), "Choice name can not be null");
IntValue = value;
}

/// <summary>
/// Initializes a new instance of <see cref="SlashCommandChoiceAttribute" />.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The <see cref="double"/> value of the choice.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="name" /> or <paramref name="value" /> doesn't match the
/// command name requirements.
/// </exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name" />.</exception>
public SlashCommandChoiceAttribute(string name, double value)
{
if (name.Length is < 1 or > 32)
throw new ArgumentException("Command option choice names must be between 1 and 32 characters.");

Name = name ?? throw new ArgumentNullException(nameof(name), "Choice name can not be null");
Value = value ?? throw new ArgumentNullException(nameof(value), "Choice value can not be null");
NumberValue = value;
}

/// <summary>
/// Initializes a new instance of <see cref="SlashCommandChoiceAttribute" />.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The <see cref="bool"/> value of the choice.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="name" /> or <paramref name="value" /> doesn't match the
/// command name requirements.
/// </exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name" />.</exception>
public SlashCommandChoiceAttribute(string name, bool value)
{
if (name.Length is < 1 or > 32)
throw new ArgumentException("Command option choice names must be between 1 and 32 characters.");

Name = name ?? throw new ArgumentNullException(nameof(name), "Choice name can not be null");
BoolValue = value;
}

/// <summary>
/// Get the <see cref="object"/> value of the set value.
/// </summary>
/// <returns>
/// The value as an <see cref="object"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown when no choice value is set.</exception>
public object ObjectValue()
{
if (StringValue is not null) return StringValue;
if (IntValue is not null) return IntValue;
if (NumberValue is not null) return NumberValue;
if (BoolValue is not null) return BoolValue;
throw new ArgumentException("No choice value provided");
}

/// <summary>
/// The name of the choice.
/// </summary>
public string Name { get; }

/// <summary>
/// The raw value of the choice.
/// The <see cref="string"/> value of the choice.
/// </summary>
public string? StringValue { get; }

/// <summary>
/// The <see cref="int"/> value of the choice.
/// </summary>
public int? IntValue { get; }

/// <summary>
/// The <see cref="double"/> value of the choice.
/// </summary>
public double? NumberValue { get; }

/// <summary>
/// The <see cref="bool"/> value of the choice.
/// </summary>
public string Value { get; }
public bool? BoolValue { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal static bool HasNewOrUpdatedOptions(this IReadOnlyCollection<DiscordAppl

// The command option choice already exists.

if (!newChoice.Value.Equals(existingChoice.Value))
if (!newChoice.Value.Equals(existingChoice.RawValue))
// Value has been updated.
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public interface ISlashCommandContext
/// <summary>
/// The Channel id of the current Channel.
/// </summary>
/// <remarks>
/// This will be the user the id when the slash command was used in DMs.
/// </remarks>
ulong ChannelId { get; set; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public interface ISlashCommandOptionInfo
/// A list of <see cref="KeyValuePair{TKey,TValue}" /> where each key is a choice name, and the value is the raw value
/// of the choice.
/// </summary>
IEnumerable<KeyValuePair<string, string>>? Choices { get; init; }
IEnumerable<KeyValuePair<string, object>>? Choices { get; init; }

/// <summary>
/// The <see cref="MethodInfo" /> containing the method of the sub command.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class SlashCommandOptionInfo : ISlashCommandOptionInfo
/// Thrown when <paramref name="name" />, <paramref name="description" /> or
/// <paramref name="type" /> is null.
/// </exception>
public SlashCommandOptionInfo(string name, string description, Type type, bool? isRequired, IEnumerable<KeyValuePair<string, string>>? choices = null,
public SlashCommandOptionInfo(string name, string description, Type type, bool? isRequired, IEnumerable<KeyValuePair<string, object>>? choices = null,
DiscordApplicationCommandOptionType? optionType = null)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Expand Down Expand Up @@ -114,7 +114,7 @@ public SlashCommandOptionInfo(string name, string description, MethodInfo comman
public bool? IsRequired { get; init; }

/// <inheritdoc />
public IEnumerable<KeyValuePair<string, string>>? Choices { get; init; }
public IEnumerable<KeyValuePair<string, object>>? Choices { get; init; }

/// <inheritdoc />
public MethodInfo? CommandMethod { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ public interface ISlashCommandOptionBuildService
/// The generated <see cref="IEnumerable{T}" /> of <see cref="DiscordApplicationCommandOptionChoiceData" />.
/// </returns>
/// <exception cref="UpdateSlashCommandException">Thrown when the command options exceeds the maximum allowed choices.</exception>
IEnumerable<DiscordApplicationCommandOptionChoiceData>? BuildChoiceData(IEnumerable<KeyValuePair<string, string>>? choicePairs);
IEnumerable<DiscordApplicationCommandOptionChoiceData>? BuildChoiceData(IEnumerable<KeyValuePair<string, object>>? choicePairs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public IEnumerable<ISlashCommandOptionInfo> GetCommandOptions(MethodInfo command
if (choiceAttributes.Any())
{
var choices = choiceAttributes
.Select(choiceAttribute => new KeyValuePair<string, string>(choiceAttribute.Name, choiceAttribute.Value))
.Select(choiceAttribute => new KeyValuePair<string, object>(choiceAttribute.Name, choiceAttribute.ObjectValue()))
.ToList();

options.Add(new SlashCommandOptionInfo(optionAttribute.Name, optionAttribute.Description, parameter.ParameterType, optionAttribute.IsRequired, choices, optionAttribute.Type));
Expand Down Expand Up @@ -86,7 +86,7 @@ public IEnumerable<ISlashCommandOptionInfo> GetCommandOptions(MethodInfo command
}

/// <inheritdoc />
public IEnumerable<DiscordApplicationCommandOptionChoiceData>? BuildChoiceData(IEnumerable<KeyValuePair<string, string>>? choicePairs)
public IEnumerable<DiscordApplicationCommandOptionChoiceData>? BuildChoiceData(IEnumerable<KeyValuePair<string, object>>? choicePairs)
{
if (choicePairs is null) return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public async Task<IDiscordInteractionResponse> HandleSlashCommandAsync(IDiscordI
Message = interaction.Message,
Member = interaction.GuildMember,
GuildId = interaction.GuildId,
ChannelId = interaction.ChannelId!.Value,
ChannelId = interaction.ChannelId ?? interaction.User?.Id ?? throw new NullReferenceException(nameof(context.User)),
ApplicationId = interaction.ApplicationId,
CommandRequest = interaction.Data,
InteractionId = interaction.Id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class SlashCommandResponseBuilder
/// <summary>
/// Interaction application command callback data flags
/// </summary>
private DiscordInteractionCommandCallbackFlags? _flags;
private DiscordInteractionCallbackFlags? _flags;

/// <summary>
/// Whether or not the response is TTS.
Expand All @@ -63,7 +63,7 @@ public SlashCommandResponseBuilder EnableTts()
/// <returns></returns>
public SlashCommandResponseBuilder MakePrivate()
{
_flags = DiscordInteractionCommandCallbackFlags.Ephemeral;
_flags = DiscordInteractionCallbackFlags.Ephemeral;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Color_Chan.Discord.Core.Common.API.DataModels.Application
{
/// <summary>
/// Represents a discord Application Command Structure API model.
/// https://discord.com/developers/docs/interactions/slash-commands#application-command-object-application-command-structure
/// https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure
/// </summary>
public record DiscordApplicationCommandData
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ namespace Color_Chan.Discord.Core.Common.API.DataModels.Application
{
/// <summary>
/// Represents a discord Application Command Option Choice Structure API model.
/// https://discord.com/developers/docs/interactions/slash-commands#application-command-object-application-command-option-choice-structure
/// https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure
/// </summary>
public record DiscordApplicationCommandOptionChoiceData
{
/// <summary>
/// 1-100 character choice name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
public string Name { get; init; } = null!;

/// <summary>
/// Value of the choice, up to 100 characters if string.
/// </summary>
[JsonPropertyName("value")]
public string Value { get; set; } = null!;
public object Value { get; init; } = null!;
}
}
Loading

0 comments on commit f7f2da7

Please sign in to comment.