Update to Labs 3.5.0 (#1971)

* Merge https://github.com/Discord-Net-Labs/Discord.Net-Labs into patch/labs3.5.0

* Add missing periods
This commit is contained in:
Quin Lynch
2021-12-19 03:41:58 -04:00
committed by GitHub
parent 52e2019990
commit 6c7502da68
41 changed files with 1097 additions and 272 deletions

View File

@@ -0,0 +1,84 @@
using System;
using System.Threading.Tasks;
namespace Discord.Interactions
{
/// <summary>
/// Requires the bot to have a specific permission in the channel a command is invoked in.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequireBotPermissionAttribute : PreconditionAttribute
{
/// <summary>
/// Gets the specified <see cref="Discord.GuildPermission" /> of the precondition.
/// </summary>
public GuildPermission? GuildPermission { get; }
/// <summary>
/// Gets the specified <see cref="Discord.ChannelPermission" /> of the precondition.
/// </summary>
public ChannelPermission? ChannelPermission { get; }
/// <summary>
/// Gets or sets the error message if the precondition
/// fails due to being run outside of a Guild channel.
/// </summary>
public string NotAGuildErrorMessage { get; set; }
/// <summary>
/// Requires the bot account to have a specific <see cref="Discord.GuildPermission"/>.
/// </summary>
/// <remarks>
/// This precondition will always fail if the command is being invoked in a <see cref="IPrivateChannel"/>.
/// </remarks>
/// <param name="permission">
/// The <see cref="Discord.GuildPermission"/> that the bot must have. Multiple permissions can be specified
/// by ORing the permissions together.
/// </param>
public RequireBotPermissionAttribute(GuildPermission permission)
{
GuildPermission = permission;
ChannelPermission = null;
}
/// <summary>
/// Requires that the bot account to have a specific <see cref="Discord.ChannelPermission"/>.
/// </summary>
/// <param name="permission">
/// The <see cref="Discord.ChannelPermission"/> that the bot must have. Multiple permissions can be
/// specified by ORing the permissions together.
/// </param>
public RequireBotPermissionAttribute(ChannelPermission permission)
{
ChannelPermission = permission;
GuildPermission = null;
}
/// <inheritdoc />
public override async Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo command, IServiceProvider services)
{
IGuildUser guildUser = null;
if (context.Guild != null)
guildUser = await context.Guild.GetCurrentUserAsync().ConfigureAwait(false);
if (GuildPermission.HasValue)
{
if (guildUser == null)
return PreconditionResult.FromError(NotAGuildErrorMessage ?? "Command must be used in a guild channel.");
if (!guildUser.GuildPermissions.Has(GuildPermission.Value))
return PreconditionResult.FromError(ErrorMessage ?? $"Bot requires guild permission {GuildPermission.Value}.");
}
if (ChannelPermission.HasValue)
{
ChannelPermissions perms;
if (context.Channel is IGuildChannel guildChannel)
perms = guildUser.GetPermissions(guildChannel);
else
perms = ChannelPermissions.All(context.Channel);
if (!perms.Has(ChannelPermission.Value))
return PreconditionResult.FromError(ErrorMessage ?? $"Bot requires channel permission {ChannelPermission.Value}.");
}
return PreconditionResult.FromSuccess();
}
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Threading.Tasks;
namespace Discord.Interactions
{
/// <summary>
/// Defines the type of command context (i.e. where the command is being executed).
/// </summary>
[Flags]
public enum ContextType
{
/// <summary>
/// Specifies the command to be executed within a guild.
/// </summary>
Guild = 0x01,
/// <summary>
/// Specifies the command to be executed within a DM.
/// </summary>
DM = 0x02,
/// <summary>
/// Specifies the command to be executed within a group.
/// </summary>
Group = 0x04
}
/// <summary>
/// Requires the command to be invoked in a specified context (e.g. in guild, DM).
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequireContextAttribute : PreconditionAttribute
{
/// <summary>
/// Gets the context required to execute the command.
/// </summary>
public ContextType Contexts { get; }
/// <summary> Requires the command to be invoked in the specified context. </summary>
/// <param name="contexts">The type of context the command can be invoked in. Multiple contexts can be specified by ORing the contexts together.</param>
/// <example>
/// <code language="cs">
/// [Command("secret")]
/// [RequireContext(ContextType.DM | ContextType.Group)]
/// public Task PrivateOnlyAsync()
/// {
/// return ReplyAsync("shh, this command is a secret");
/// }
/// </code>
/// </example>
public RequireContextAttribute(ContextType contexts)
{
Contexts = contexts;
}
/// <inheritdoc />
public override Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo command, IServiceProvider services)
{
bool isValid = false;
if ((Contexts & ContextType.Guild) != 0)
isValid = context.Channel is IGuildChannel;
if ((Contexts & ContextType.DM) != 0)
isValid = isValid || context.Channel is IDMChannel;
if ((Contexts & ContextType.Group) != 0)
isValid = isValid || context.Channel is IGroupChannel;
if (isValid)
return Task.FromResult(PreconditionResult.FromSuccess());
else
return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? $"Invalid context for command; accepted contexts: {Contexts}."));
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
namespace Discord.Interactions
{
/// <summary>
/// Requires the command to be invoked in a channel marked NSFW.
/// </summary>
/// <remarks>
/// The precondition will restrict the access of the command or module to be accessed within a guild channel
/// that has been marked as mature or NSFW. If the channel is not of type <see cref="ITextChannel"/> or the
/// channel is not marked as NSFW, the precondition will fail with an erroneous <see cref="PreconditionResult"/>.
/// </remarks>
/// <example>
/// The following example restricts the command <c>too-cool</c> to an NSFW-enabled channel only.
/// <code language="cs">
/// public class DankModule : ModuleBase
/// {
/// [Command("cool")]
/// public Task CoolAsync()
/// => ReplyAsync("I'm cool for everyone.");
///
/// [RequireNsfw]
/// [Command("too-cool")]
/// public Task TooCoolAsync()
/// => ReplyAsync("You can only see this if you're cool enough.");
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequireNsfwAttribute : PreconditionAttribute
{
/// <inheritdoc />
public override Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo command, IServiceProvider services)
{
if (context.Channel is ITextChannel text && text.IsNsfw)
return Task.FromResult(PreconditionResult.FromSuccess());
else
return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? "This command may only be invoked in an NSFW channel."));
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
namespace Discord.Interactions
{
/// <summary>
/// Requires the command to be invoked by the owner of the bot.
/// </summary>
/// <remarks>
/// This precondition will restrict the access of the command or module to the owner of the Discord application.
/// If the precondition fails to be met, an erroneous <see cref="PreconditionResult"/> will be returned with the
/// message "Command can only be run by the owner of the bot."
/// <note>
/// This precondition will only work if the account has a <see cref="TokenType"/> of <see cref="TokenType.Bot"/>
/// ;otherwise, this precondition will always fail.
/// </note>
/// </remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequireOwnerAttribute : PreconditionAttribute
{
/// <inheritdoc />
public override async Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo command, IServiceProvider services)
{
switch (context.Client.TokenType)
{
case TokenType.Bot:
var application = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false);
if (context.User.Id != application.Owner.Id)
return PreconditionResult.FromError(ErrorMessage ?? "Command can only be run by the owner of the bot.");
return PreconditionResult.FromSuccess();
default:
return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}.");
}
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Discord.Interactions
{
/// <summary>
/// Requires the user invoking the command to have a specified role.
/// </summary>
public class RequireRoleAttribute : PreconditionAttribute
{
/// <summary>
/// Gets the specified Role name of the precondition.
/// </summary>
public string RoleName { get; }
/// <summary>
/// Gets the specified Role ID of the precondition.
/// </summary>
public ulong? RoleId { get; }
/// <summary>
/// Gets or sets the error message if the precondition
/// fails due to being run outside of a Guild channel.
/// </summary>
public string NotAGuildErrorMessage { get; set; }
/// <summary>
/// Requires that the user invoking the command to have a specific Role.
/// </summary>
/// <param name="roleId">Id of the role that the user must have.</param>
public RequireRoleAttribute(ulong roleId)
{
RoleId = roleId;
}
/// <summary>
/// Requires that the user invoking the command to have a specific Role.
/// </summary>
/// <param name="roleName">Name of the role that the user must have.</param>
public RequireRoleAttribute(string roleName)
{
RoleName = roleName;
}
/// <inheritdoc />
public override Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services)
{
if (context.User is not IGuildUser guildUser)
return Task.FromResult(PreconditionResult.FromError(NotAGuildErrorMessage ?? "Command must be used in a guild channel."));
if (RoleId.HasValue)
{
if (guildUser.RoleIds.Contains(RoleId.Value))
return Task.FromResult(PreconditionResult.FromSuccess());
else
Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? $"User requires guild role {context.Guild.GetRole(RoleId.Value).Name}."));
}
if (!string.IsNullOrEmpty(RoleName))
{
if (guildUser.Guild.Roles.Any(x => x.Name == RoleName))
return Task.FromResult(PreconditionResult.FromSuccess());
else
Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? $"User requires guild role {RoleName}."));
}
return Task.FromResult(PreconditionResult.FromSuccess());
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Threading.Tasks;
namespace Discord.Interactions
{
/// <summary>
/// Requires the user invoking the command to have a specified permission.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequireUserPermissionAttribute : PreconditionAttribute
{
/// <summary>
/// Gets the specified <see cref="Discord.GuildPermission" /> of the precondition.
/// </summary>
public GuildPermission? GuildPermission { get; }
/// <summary>
/// Gets the specified <see cref="Discord.ChannelPermission" /> of the precondition.
/// </summary>
public ChannelPermission? ChannelPermission { get; }
/// <summary>
/// Gets or sets the error message if the precondition
/// fails due to being run outside of a Guild channel.
/// </summary>
public string NotAGuildErrorMessage { get; set; }
/// <summary>
/// Requires that the user invoking the command to have a specific <see cref="Discord.GuildPermission"/>.
/// </summary>
/// <remarks>
/// This precondition will always fail if the command is being invoked in a <see cref="IPrivateChannel"/>.
/// </remarks>
/// <param name="permission">
/// The <see cref="Discord.GuildPermission" /> that the user must have. Multiple permissions can be
/// specified by ORing the permissions together.
/// </param>
public RequireUserPermissionAttribute(GuildPermission guildPermission)
{
GuildPermission = guildPermission;
}
/// <summary>
/// Requires that the user invoking the command to have a specific <see cref="Discord.ChannelPermission"/>.
/// </summary>
/// <param name="permission">
/// The <see cref="Discord.ChannelPermission"/> that the user must have. Multiple permissions can be
/// specified by ORing the permissions together.
/// </param>
public RequireUserPermissionAttribute(ChannelPermission channelPermission)
{
ChannelPermission = channelPermission;
}
/// <inheritdoc />
public override Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services)
{
var guildUser = context.User as IGuildUser;
if (GuildPermission.HasValue)
{
if (guildUser == null)
return Task.FromResult(PreconditionResult.FromError(NotAGuildErrorMessage ?? "Command must be used in a guild channel."));
if (!guildUser.GuildPermissions.Has(GuildPermission.Value))
return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? $"User requires guild permission {GuildPermission.Value}."));
}
if (ChannelPermission.HasValue)
{
ChannelPermissions perms;
if (context.Channel is IGuildChannel guildChannel)
perms = guildUser.GetPermissions(guildChannel);
else
perms = ChannelPermissions.All(context.Channel);
if (!perms.Has(ChannelPermission.Value))
return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? $"User requires channel permission {ChannelPermission.Value}."));
}
return Task.FromResult(PreconditionResult.FromSuccess());
}
}
}

View File

@@ -60,7 +60,11 @@ namespace Discord.Interactions
{
case RestAutocompleteInteraction restAutocomplete:
var payload = restAutocomplete.Respond(result.Suggestions);
await InteractionService._restResponseCallback(context, payload).ConfigureAwait(false);
if (context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null)
await restContext.InteractionResponseCallback.Invoke(payload).ConfigureAwait(false);
else
await InteractionService._restResponseCallback(context, payload).ConfigureAwait(false);
break;
case SocketAutocompleteInteraction socketAutocomplete:
await socketAutocomplete.RespondAsync(result.Suggestions).ConfigureAwait(false);

View File

@@ -6,7 +6,6 @@
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net6.0;net5.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<RootNamespace>Discord.Interactions</RootNamespace>
<AssemblyName>Discord.Net.Interactions</AssemblyName>
<PackageId>Discord.Net.Interactions</PackageId>
<Description>A Discord.Net extension adding support for Application Commands.</Description>
</PropertyGroup>

View File

@@ -30,7 +30,12 @@ namespace Discord.Interactions
if (Context.Interaction is not RestInteraction restInteraction)
throw new InvalidOperationException($"Invalid interaction type. Interaction must be a type of {nameof(RestInteraction)} in order to execute this method");
await InteractionService._restResponseCallback(Context, restInteraction.Defer(ephemeral, options)).ConfigureAwait(false);
var payload = restInteraction.Defer(ephemeral, options);
if (Context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null)
await restContext.InteractionResponseCallback.Invoke(payload).ConfigureAwait(false);
else
await InteractionService._restResponseCallback(Context, payload).ConfigureAwait(false);
}
/// <summary>
@@ -53,7 +58,12 @@ namespace Discord.Interactions
if (Context.Interaction is not RestInteraction restInteraction)
throw new InvalidOperationException($"Invalid interaction type. Interaction must be a type of {nameof(RestInteraction)} in order to execute this method");
await InteractionService._restResponseCallback(Context, restInteraction.Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options)).ConfigureAwait(false);
var payload = restInteraction.Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options);
if (Context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null)
await restContext.InteractionResponseCallback.Invoke(payload).ConfigureAwait(false);
else
await InteractionService._restResponseCallback(Context, payload).ConfigureAwait(false);
}
}
}