diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs index 7405e924a7..c55d67a486 100644 --- a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs +++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs @@ -18,6 +18,7 @@ using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.Enums; +using DisCatSharp.Enums.Core; using DisCatSharp.EventArgs; using DisCatSharp.Exceptions; @@ -85,16 +86,6 @@ public sealed class ApplicationCommandsExtension : BaseExtension /// private static bool s_errored { get; set; } - /// - /// Gets the cooldown buckets for slash commands. - /// - public static ConcurrentDictionary SlashCommandBuckets { get; } = []; - - /// - /// Gets the cooldown buckets for context menu commands. - /// - public static ConcurrentDictionary ContextMenuCommandBuckets { get; } = []; - /// /// Gets a list of registered commands. The key is the guild id (null if global). /// @@ -1091,7 +1082,11 @@ private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs GuildLocale = e.Interaction.GuildLocale, AppPermissions = e.Interaction.AppPermissions, Entitlements = e.Interaction.Entitlements, - EntitlementSkuIds = e.Interaction.EntitlementSkuIds + EntitlementSkuIds = e.Interaction.EntitlementSkuIds, + UserId = e.Interaction.User.Id, + GuildId = e.Interaction.GuildId, + MemberId = e.Interaction.GuildId is not null ? e.Interaction.User.Id : null, + ChannelId = e.Interaction.ChannelId }; try @@ -1118,6 +1113,7 @@ private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs var method = methods.First().Method; context.SubCommandName = null; context.SubSubCommandName = null; + context.CommandGroupingType = DisCatSharpCommandGroupingType.Command; if (DebugEnabled) this.Client.Logger.LogDebug("Executing {cmd}", method.Name); var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options).ConfigureAwait(false); @@ -1131,6 +1127,7 @@ private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs var method = groups.First().Methods.First(x => x.Key == command.Name).Value; context.SubCommandName = command.Name; context.SubSubCommandName = null; + context.CommandGroupingType = DisCatSharpCommandGroupingType.SubCommand; if (DebugEnabled) this.Client.Logger.LogDebug("Executing {cmd}", method.Name); var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options[0].Options).ConfigureAwait(false); @@ -1146,6 +1143,7 @@ private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs var method = group.Methods.First(x => x.Key == command.Options[0].Name).Value; context.SubCommandName = command.Name; context.SubSubCommandName = command.Options[0].Name; + context.CommandGroupingType = DisCatSharpCommandGroupingType.SubGroupCommand; if (DebugEnabled) this.Client.Logger.LogDebug("Executing {cmd}", method.Name); @@ -1350,7 +1348,12 @@ private Task ContextMenuHandler(DiscordClient client, ContextMenuInteractionCrea _ = Task.Run(async () => { //Creates the context - var context = new ContextMenuContext + var context = new ContextMenuContext(e.Type switch + { + ApplicationCommandType.User => DisCatSharpCommandType.UserCommand, + ApplicationCommandType.Message => DisCatSharpCommandType.MessageCommand, + _ => throw new ArgumentOutOfRangeException(nameof(e.Type), "Unknown context menu type") + }) { Interaction = e.Interaction, Channel = e.Interaction.Channel, @@ -1369,7 +1372,12 @@ private Task ContextMenuHandler(DiscordClient client, ContextMenuInteractionCrea GuildLocale = e.Interaction.GuildLocale, AppPermissions = e.Interaction.AppPermissions, Entitlements = e.Interaction.Entitlements, - EntitlementSkuIds = e.Interaction.EntitlementSkuIds + EntitlementSkuIds = e.Interaction.EntitlementSkuIds, + CommandGroupingType = DisCatSharpCommandGroupingType.Command, + UserId = e.Interaction.User.Id, + GuildId = e.Interaction.GuildId, + MemberId = e.Interaction.GuildId is not null ? e.Interaction.User.Id : null, + ChannelId = e.Interaction.ChannelId }; try diff --git a/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuCooldownAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuCooldownAttribute.cs index b52a4676e8..9ba4b58b42 100644 --- a/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuCooldownAttribute.cs +++ b/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuCooldownAttribute.cs @@ -1,11 +1,12 @@ using System; -using System.Collections.Concurrent; -using System.ComponentModel.Design; +using System.Globalization; using System.Threading.Tasks; using DisCatSharp.ApplicationCommands.Context; -using DisCatSharp.ApplicationCommands.Entities; -using DisCatSharp.ApplicationCommands.Enums; +using DisCatSharp.Entities; +using DisCatSharp.Entities.Core; +using DisCatSharp.Enums; +using DisCatSharp.Enums.Core; namespace DisCatSharp.ApplicationCommands.Attributes; @@ -13,7 +14,7 @@ namespace DisCatSharp.ApplicationCommands.Attributes; /// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public sealed class ContextMenuCooldownAttribute : ApplicationCommandCheckBaseAttribute, ICooldown +public sealed class ContextMenuCooldownAttribute : ApplicationCommandCheckBaseAttribute, ICooldown { /// /// Gets the maximum number of uses before this command triggers a cooldown for its bucket. @@ -48,10 +49,10 @@ public ContextMenuCooldownAttribute(int maxUses, double resetAfter, CooldownBuck /// /// Command context to get cooldown bucket for. /// Requested cooldown bucket, or null if one wasn't present. - public ContextMenuCooldownBucket GetBucket(BaseContext ctx) + public CooldownBucket GetBucket(BaseContext ctx) { var bid = this.GetBucketId(ctx, out _, out _, out _, out _); - ApplicationCommandsExtension.ContextMenuCommandBuckets.TryGetValue(bid, out var bucket); + ctx.Client.CommandCooldownBuckets.TryGetValue(bid, out var bucket); return bucket!; } @@ -97,7 +98,7 @@ private string GetBucketId(BaseContext ctx, out ulong userId, out ulong channelI if (ctx.Guild is not null && ctx.Member is not null && (this.BucketType & CooldownBucketType.Member) != 0) memberId = ctx.Member.Id; - var bid = CooldownBucket.MakeId(ctx.Interaction.Data.Id, ctx.FullCommandName, userId, channelId, guildId, memberId); + var bid = CooldownBucket.MakeId(ctx.FullCommandName, ctx.Interaction.Data.Id.ToString(CultureInfo.InvariantCulture), userId, channelId, guildId, memberId); return bid; } @@ -108,40 +109,26 @@ private string GetBucketId(BaseContext ctx, out ulong userId, out ulong channelI public override async Task ExecuteChecksAsync(BaseContext ctx) { var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld, out var mem); - if (ApplicationCommandsExtension.ContextMenuCommandBuckets.TryGetValue(bid, out var bucket)) - return await bucket.DecrementUseAsync(ctx).ConfigureAwait(false); + if (ctx.Client.CommandCooldownBuckets.TryGetValue(bid, out var bucket)) + return await this.RespondRatelimitHitAsync(ctx, await bucket.DecrementUseAsync(ctx), bucket); - bucket = new(this.MaxUses, this.Reset, ctx.Interaction.Data.Id, ctx.FullCommandName, usr, chn, gld, mem); - ApplicationCommandsExtension.ContextMenuCommandBuckets.AddOrUpdate(bid, bucket, (k, v) => bucket); + bucket = new(this.MaxUses, this.Reset, ctx.FullCommandName, ctx.Interaction.Data.Id.ToString(CultureInfo.InvariantCulture), usr, chn, gld, mem); + ctx.Client.CommandCooldownBuckets.AddOrUpdate(bid, bucket, (k, v) => bucket); - return await bucket.DecrementUseAsync(ctx).ConfigureAwait(false); + return await this.RespondRatelimitHitAsync(ctx, await bucket.DecrementUseAsync(ctx), bucket); } -} -/// -/// Represents a cooldown bucket for context menu commands. -/// -public sealed class ContextMenuCooldownBucket : CooldownBucket -{ - /// - /// Creates a new context menu command cooldown bucket. - /// - /// Maximum number of uses for this bucket. - /// Time after which this bucket resets. - /// ID of the command - /// Name of the command. - /// ID of the user with which this cooldown is associated. - /// ID of the channel with which this cooldown is associated. - /// ID of the guild with which this cooldown is associated. - /// ID of the member with which this cooldown is associated. - internal ContextMenuCooldownBucket(int maxUses, TimeSpan resetAfter, ulong commandId, string commandName, ulong userId = 0, ulong channelId = 0, ulong guildId = 0, ulong memberId = 0) - : base(maxUses, resetAfter, commandId, commandName, userId, channelId, guildId, memberId) - { } + /// + public async Task RespondRatelimitHitAsync(BaseContext ctx, bool noHit, CooldownBucket bucket) + { + if (noHit) + return true; - /// - /// Returns a string representation of this command cooldown bucket. - /// - /// String representation of this command cooldown bucket. - public override string ToString() - => $"Context Menu Command bucket {this.BucketId}"; + if (ApplicationCommandsExtension.Configuration.AutoDefer) + await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"Error: Ratelimit hit\nTry again in {bucket.ResetsAt.Timestamp()}")); + else + await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent($"Error: Ratelimit hit\nTry again in {bucket.ResetsAt.Timestamp()}").AsEphemeral()); + + return false; + } } diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandCooldownAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandCooldownAttribute.cs index 929f08db3c..b6762e1434 100644 --- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandCooldownAttribute.cs +++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandCooldownAttribute.cs @@ -1,10 +1,12 @@ using System; -using System.Collections.Concurrent; +using System.Globalization; using System.Threading.Tasks; using DisCatSharp.ApplicationCommands.Context; -using DisCatSharp.ApplicationCommands.Entities; -using DisCatSharp.ApplicationCommands.Enums; +using DisCatSharp.Entities; +using DisCatSharp.Entities.Core; +using DisCatSharp.Enums; +using DisCatSharp.Enums.Core; namespace DisCatSharp.ApplicationCommands.Attributes; @@ -12,7 +14,7 @@ namespace DisCatSharp.ApplicationCommands.Attributes; /// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public sealed class SlashCommandCooldownAttribute : ApplicationCommandCheckBaseAttribute, ICooldown +public sealed class SlashCommandCooldownAttribute : ApplicationCommandCheckBaseAttribute, ICooldown { /// /// Gets the maximum number of uses before this command triggers a cooldown for its bucket. @@ -47,10 +49,10 @@ public SlashCommandCooldownAttribute(int maxUses, double resetAfter, CooldownBuc /// /// Command context to get cooldown bucket for. /// Requested cooldown bucket, or null if one wasn't present. - public SlashCommandCooldownBucket GetBucket(BaseContext ctx) + public CooldownBucket GetBucket(BaseContext ctx) { var bid = this.GetBucketId(ctx, out _, out _, out _, out _); - ApplicationCommandsExtension.SlashCommandBuckets.TryGetValue(bid, out var bucket); + ctx.Client.CommandCooldownBuckets.TryGetValue(bid, out var bucket); return bucket!; } @@ -96,7 +98,7 @@ private string GetBucketId(BaseContext ctx, out ulong userId, out ulong channelI if (ctx.Guild is not null && ctx.Member is not null && this.BucketType.HasFlag(CooldownBucketType.Member)) memberId = ctx.Member.Id; - var bid = CooldownBucket.MakeId(ctx.Interaction.Data.Id, ctx.FullCommandName, userId, channelId, guildId, memberId); + var bid = CooldownBucket.MakeId(ctx.FullCommandName, ctx.Interaction.Data.Id.ToString(CultureInfo.InvariantCulture), userId, channelId, guildId, memberId); return bid; } @@ -107,40 +109,26 @@ private string GetBucketId(BaseContext ctx, out ulong userId, out ulong channelI public override async Task ExecuteChecksAsync(BaseContext ctx) { var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld, out var mem); - if (ApplicationCommandsExtension.SlashCommandBuckets.TryGetValue(bid, out var bucket)) - return await bucket.DecrementUseAsync(ctx).ConfigureAwait(false); + if (ctx.Client.CommandCooldownBuckets.TryGetValue(bid, out var bucket)) + return await this.RespondRatelimitHitAsync(ctx, await bucket.DecrementUseAsync(ctx), bucket); - bucket = new(this.MaxUses, this.Reset, ctx.Interaction.Data.Id, ctx.FullCommandName, usr, chn, gld, mem); - ApplicationCommandsExtension.SlashCommandBuckets.AddOrUpdate(bid, bucket, (k, v) => bucket); + bucket = new(this.MaxUses, this.Reset, ctx.FullCommandName, ctx.Interaction.Data.Id.ToString(CultureInfo.InvariantCulture), usr, chn, gld, mem); + ctx.Client.CommandCooldownBuckets.AddOrUpdate(bid, bucket, (k, v) => bucket); - return await bucket.DecrementUseAsync(ctx).ConfigureAwait(false); + return await this.RespondRatelimitHitAsync(ctx, await bucket.DecrementUseAsync(ctx), bucket); } -} -/// -/// Represents a cooldown bucket for slash commands. -/// -public sealed class SlashCommandCooldownBucket : CooldownBucket -{ - /// - /// Creates a new slash command cooldown bucket. - /// - /// Maximum number of uses for this bucket. - /// Time after which this bucket resets. - /// ID of the command - /// Name of the command. - /// ID of the user with which this cooldown is associated. - /// ID of the channel with which this cooldown is associated. - /// ID of the guild with which this cooldown is associated. - /// ID of the member with which this cooldown is associated. - internal SlashCommandCooldownBucket(int maxUses, TimeSpan resetAfter, ulong commandId, string commandName, ulong userId = 0, ulong channelId = 0, ulong guildId = 0, ulong memberId = 0) - : base(maxUses, resetAfter, commandId, commandName, userId, channelId, guildId, memberId) - { } + /// + public async Task RespondRatelimitHitAsync(BaseContext ctx, bool noHit, CooldownBucket bucket) + { + if (noHit) + return true; - /// - /// Returns a string representation of this command cooldown bucket. - /// - /// String representation of this command cooldown bucket. - public override string ToString() - => $"Slash Command bucket {this.BucketId}"; + if (ApplicationCommandsExtension.Configuration.AutoDefer) + await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"Error: Ratelimit hit\nTry again in {bucket.ResetsAt.Timestamp()}")); + else + await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent($"Error: Ratelimit hit\nTry again in {bucket.ResetsAt.Timestamp()}").AsEphemeral()); + + return false; + } } diff --git a/DisCatSharp.ApplicationCommands/Context/BaseContext.cs b/DisCatSharp.ApplicationCommands/Context/BaseContext.cs index cf173a171a..1c60ddd27e 100644 --- a/DisCatSharp.ApplicationCommands/Context/BaseContext.cs +++ b/DisCatSharp.ApplicationCommands/Context/BaseContext.cs @@ -4,7 +4,9 @@ using DisCatSharp.Attributes; using DisCatSharp.Entities; +using DisCatSharp.Entities.Core; using DisCatSharp.Enums; +using DisCatSharp.Enums.Core; using Microsoft.Extensions.DependencyInjection; @@ -13,18 +15,13 @@ namespace DisCatSharp.ApplicationCommands.Context; /// /// Represents a base context for application command contexts. /// -public class BaseContext +public class BaseContext : DisCatSharpCommandContext { /// /// Gets the interaction that was created. /// public DiscordInteraction Interaction { get; internal init; } - /// - /// Gets the client for this interaction. - /// - public DiscordClient Client { get; internal init; } - /// /// Gets the guild this interaction was executed in. /// @@ -61,25 +58,10 @@ public DiscordMember? Member /// public ulong InteractionId { get; internal set; } - /// - /// Gets the name of the command. - /// - public string CommandName { get; internal init; } - - /// - /// Gets the name of the sub command. - /// - public string? SubCommandName { get; internal set; } - - /// - /// Gets the name of the sub command. - /// - public string? SubSubCommandName { get; internal set; } - /// /// Gets the full command string, including the subcommand. /// - public string FullCommandName + public override string FullCommandName => $"{this.CommandName}{(string.IsNullOrWhiteSpace(this.SubCommandName) ? "" : $" {this.SubCommandName}")}{(string.IsNullOrWhiteSpace(this.SubSubCommandName) ? "" : $" {this.SubSubCommandName}")}"; /// @@ -125,6 +107,15 @@ public string FullCommandName /// public IServiceProvider Services { get; internal set; } = new ServiceCollection().BuildServiceProvider(true); + /// + /// Initializes a new instance of the class. + /// + /// The command type. + /// The command grouping type. + internal BaseContext(DisCatSharpCommandType type, DisCatSharpCommandGroupingType groupingType = DisCatSharpCommandGroupingType.Command) + : base(type, groupingType) + { } + /// /// Creates a response to this interaction. /// You must create a response within 3 seconds of this interaction being executed; if the command has the potential to take more than 3 seconds, create a at the start, and edit the response later. diff --git a/DisCatSharp.ApplicationCommands/Context/ContextMenuContext.cs b/DisCatSharp.ApplicationCommands/Context/ContextMenuContext.cs index 7b03989840..fb405d7294 100644 --- a/DisCatSharp.ApplicationCommands/Context/ContextMenuContext.cs +++ b/DisCatSharp.ApplicationCommands/Context/ContextMenuContext.cs @@ -1,11 +1,12 @@ using DisCatSharp.Entities; +using DisCatSharp.Enums.Core; namespace DisCatSharp.ApplicationCommands.Context; /// /// Represents a context for a context menu. /// -public sealed class ContextMenuContext : BaseContext +public sealed class ContextMenuContext(DisCatSharpCommandType type) : BaseContext(type) { /// /// The user this command targets, if applicable. @@ -15,7 +16,7 @@ public sealed class ContextMenuContext : BaseContext /// /// The member this command targets, if applicable. /// - public DiscordMember TargetMember + public DiscordMember? TargetMember => this.TargetUser is DiscordMember member ? member : null; /// diff --git a/DisCatSharp.ApplicationCommands/Context/InteractionContext.cs b/DisCatSharp.ApplicationCommands/Context/InteractionContext.cs index 1000b43014..c425cb79e6 100644 --- a/DisCatSharp.ApplicationCommands/Context/InteractionContext.cs +++ b/DisCatSharp.ApplicationCommands/Context/InteractionContext.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using DisCatSharp.Entities; +using DisCatSharp.Enums.Core; namespace DisCatSharp.ApplicationCommands.Context; /// /// Represents a context for an interaction. /// -public sealed class InteractionContext : BaseContext +public sealed class InteractionContext() : BaseContext(DisCatSharpCommandType.SlashCommand) { /// /// Gets the users mentioned in the command parameters. diff --git a/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs b/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs index a6745c1928..ddb6c4d21d 100644 --- a/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs +++ b/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs @@ -1,16 +1,17 @@ using System; -using System.Collections.Concurrent; -using System.Globalization; -using System.Threading; using System.Threading.Tasks; +using DisCatSharp.Entities; +using DisCatSharp.Entities.Core; +using DisCatSharp.Enums.Core; + namespace DisCatSharp.CommandsNext.Attributes; /// /// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public sealed class CooldownAttribute : CheckBaseAttribute +public sealed class CooldownAttribute : CheckBaseAttribute, ICooldown { /// /// Gets the maximum number of uses before this command triggers a cooldown for its bucket. @@ -45,10 +46,10 @@ public CooldownAttribute(int maxUses, double resetAfter, CooldownBucketType buck /// /// Command context to get cooldown bucket for. /// Requested cooldown bucket, or null if one wasn't present. - public CommandCooldownBucket GetBucket(CommandContext ctx) + public CooldownBucket GetBucket(CommandContext ctx) { var bid = this.GetBucketId(ctx, out _, out _, out _); - CommandsNextExtension.CommandCooldownBuckets.TryGetValue(bid, out var bucket); + ctx.Client.CommandCooldownBuckets.TryGetValue(bid, out var bucket); return bucket!; } @@ -91,7 +92,7 @@ private string GetBucketId(CommandContext ctx, out ulong userId, out ulong chann if (ctx.Guild is not null && this.BucketType.HasFlag(CooldownBucketType.Guild)) guildId = ctx.Guild.Id; - var bid = CommandCooldownBucket.MakeId(ctx.Command.QualifiedName, userId, channelId, guildId); + var bid = CooldownBucket.MakeId(ctx.Command.QualifiedName, "text", userId, channelId, guildId); return bid; } @@ -106,213 +107,23 @@ public override async Task ExecuteCheckAsync(CommandContext ctx, bool help return true; var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld); - if (CommandsNextExtension.CommandCooldownBuckets.TryGetValue(bid, out var bucket)) - return await bucket.DecrementUseAsync().ConfigureAwait(false); - - bucket = new(this.MaxUses, this.Reset, ctx.Command.QualifiedName, usr, chn, gld); - CommandsNextExtension.CommandCooldownBuckets.AddOrUpdate(bid, bucket, (k, v) => bucket); - - return await bucket.DecrementUseAsync().ConfigureAwait(false); - } -} - -/// -/// Defines how are command cooldowns applied. -/// -[Flags] -public enum CooldownBucketType -{ - /// - /// Denotes that the command will have its cooldown applied per-user. - /// - User = 1, - - /// - /// Denotes that the command will have its cooldown applied per-channel. - /// - Channel = 2, - - /// - /// Denotes that the command will have its cooldown applied per-guild. In DMs, this applies the cooldown per-channel. - /// - Guild = 4, - - /// - /// Denotes that the command will have its cooldown applied globally. - /// - Global = 0 -} - -/// -/// Represents a cooldown bucket for commands. -/// -public sealed class CommandCooldownBucket : IEquatable -{ - /// - /// Gets the ID of the user with whom this cooldown is associated. - /// - public ulong UserId { get; } - - /// - /// Gets the ID of the channel with which this cooldown is associated. - /// - public ulong ChannelId { get; } - - /// - /// Gets the ID of the guild with which this cooldown is associated. - /// - public ulong GuildId { get; } - - /// - /// Gets the ID of the bucket. This is used to distinguish between cooldown buckets. - /// - public string BucketId { get; } - - /// - /// Gets the remaining number of uses before the cooldown is triggered. - /// - public int RemainingUses - => Volatile.Read(ref this._remainingUses); - - /// - /// Gets the remaining number of uses before the cooldown is triggered. - /// - private int _remainingUses; - - /// - /// Gets the maximum number of times this command can be used in given timespan. - /// - public int MaxUses { get; } - - /// - /// Gets the date and time at which the cooldown resets. - /// - public DateTimeOffset ResetsAt { get; internal set; } + if (ctx.Client.CommandCooldownBuckets.TryGetValue(bid, out var bucket)) + return await bucket.DecrementUseAsync(ctx).ConfigureAwait(false); - /// - /// Gets the time after which this cooldown resets. - /// - public TimeSpan Reset { get; internal set; } - - /// - /// Gets the semaphore used to lock the use value. - /// - private readonly SemaphoreSlim _usageSemaphore; + bucket = new(this.MaxUses, this.Reset, ctx.Command.QualifiedName, "text", usr, chn, gld); + ctx.Client.CommandCooldownBuckets.AddOrUpdate(bid, bucket, (k, v) => bucket); - /// - /// Creates a new command cooldown bucket. - /// - /// Name of the command. - /// Maximum number of uses for this bucket. - /// Time after which this bucket resets. - /// ID of the user with which this cooldown is associated. - /// ID of the channel with which this cooldown is associated. - /// ID of the guild with which this cooldown is associated. - internal CommandCooldownBucket(int maxUses, TimeSpan resetAfter, string commandName, ulong userId = 0, ulong channelId = 0, ulong guildId = 0) - { - this._remainingUses = maxUses; - this.MaxUses = maxUses; - this.ResetsAt = DateTimeOffset.UtcNow + resetAfter; - this.Reset = resetAfter; - this.UserId = userId; - this.ChannelId = channelId; - this.GuildId = guildId; - this.BucketId = MakeId(commandName, userId, channelId, guildId); - this._usageSemaphore = new(1, 1); + return await bucket.DecrementUseAsync(ctx).ConfigureAwait(false); } - /// - /// Decrements the remaining use counter. - /// - /// Whether decrement succeeded or not. - internal async Task DecrementUseAsync() + /// + public async Task RespondRatelimitHitAsync(CommandContext ctx, bool noHit, CooldownBucket bucket) { - await this._usageSemaphore.WaitAsync().ConfigureAwait(false); - - // if we're past reset time... - var now = DateTimeOffset.UtcNow; - if (now >= this.ResetsAt) - { - // ...do the reset and set a new reset time - Interlocked.Exchange(ref this._remainingUses, this.MaxUses); - this.ResetsAt = now + this.Reset; - } - - // check if we have any uses left, if we do... - var success = false; - if (this.RemainingUses > 0) - { - // ...decrement, and return success... - Interlocked.Decrement(ref this._remainingUses); - success = true; - } - - // ...otherwise just fail - this._usageSemaphore.Release(); - return success; - } - - /// - /// Returns a string representation of this command cooldown bucket. - /// - /// String representation of this command cooldown bucket. - public override string ToString() - => $"Command bucket {this.BucketId}"; - - /// - /// Checks whether this is equal to another object. - /// - /// Object to compare to. - /// Whether the object is equal to this . - public override bool Equals(object? obj) - => this.Equals(obj as CommandCooldownBucket); - - /// - /// Checks whether this is equal to another . - /// - /// to compare to. - /// Whether the is equal to this . - public bool Equals(CommandCooldownBucket? other) - => other is not null && (ReferenceEquals(this, other) || (this.UserId == other.UserId && this.ChannelId == other.ChannelId && this.GuildId == other.GuildId)); - - /// - /// Gets the hash code for this . - /// - /// The hash code for this . - public override int GetHashCode() - => HashCode.Combine(this.UserId, this.ChannelId, this.GuildId); + if (noHit) + return true; - /// - /// Gets whether the two objects are equal. - /// - /// First bucket to compare. - /// Second bucket to compare. - /// Whether the two buckets are equal. - public static bool operator ==(CommandCooldownBucket? bucket1, CommandCooldownBucket? bucket2) - { - var null1 = bucket1 is null; - var null2 = bucket2 is null; + await ctx.Message.CreateReactionAsync(DiscordEmoji.FromName(ctx.Client, ":x:", false)); - return (null1 && null2) || (null1 == null2 && null1.Equals(null2)); + return false; } - - /// - /// Gets whether the two objects are not equal. - /// - /// First bucket to compare. - /// Second bucket to compare. - /// Whether the two buckets are not equal. - public static bool operator !=(CommandCooldownBucket? bucket1, CommandCooldownBucket? bucket2) - => !(bucket1 == bucket2); - - /// - /// Creates a bucket ID from given bucket parameters. - /// - /// Name of the command. - /// ID of the user with which this cooldown is associated. - /// ID of the channel with which this cooldown is associated. - /// ID of the guild with which this cooldown is associated. - /// Generated bucket ID. - public static string MakeId(string commandName, ulong userId = 0, ulong channelId = 0, ulong guildId = 0) - => $"{commandName}::{userId.ToString(CultureInfo.InvariantCulture)}:{channelId.ToString(CultureInfo.InvariantCulture)}:{guildId.ToString(CultureInfo.InvariantCulture)}"; } diff --git a/DisCatSharp.CommandsNext/CommandsNextExtension.cs b/DisCatSharp.CommandsNext/CommandsNextExtension.cs index aa4f26bcf3..f052de5bc7 100644 --- a/DisCatSharp.CommandsNext/CommandsNextExtension.cs +++ b/DisCatSharp.CommandsNext/CommandsNextExtension.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -50,11 +49,6 @@ public class CommandsNextExtension : BaseExtension /// internal Dictionary ArgumentConverters { get; } - /// - /// Gets the cooldown buckets for this command. - /// - public static ConcurrentDictionary CommandCooldownBuckets { get; } = []; - /// /// Gets the service provider this CommandsNext module was configured with. /// @@ -264,7 +258,7 @@ private async Task HandleCommandsAsync(DiscordClient sender, MessageCreateEventA /// Qualified name of the command, optionally with arguments. /// Separated arguments. /// Found command or null if none was found. - public Command FindCommand(string commandString, out string rawArguments) + public Command FindCommand(string commandString, out string? rawArguments) { rawArguments = null; @@ -329,7 +323,7 @@ public Command FindCommand(string commandString, out string rawArguments) /// Command to execute. /// Raw arguments to pass to command. /// Created command execution context. - public CommandContext CreateContext(DiscordMessage msg, string prefix, Command cmd, string rawArguments = null) + public CommandContext CreateContext(DiscordMessage msg, string prefix, Command cmd, string? rawArguments = null) { var ctx = new CommandContext { @@ -340,7 +334,11 @@ public CommandContext CreateContext(DiscordMessage msg, string prefix, Command c RawArgumentString = rawArguments ?? "", Prefix = prefix, CommandsNext = this, - Services = this.Services + Services = this.Services, + UserId = msg.Author.Id, + GuildId = msg.GuildId, + MemberId = msg.GuildId is not null ? msg.Author.Id : null, + ChannelId = msg.ChannelId }; if (cmd != null && (cmd.Module is TransientCommandModule || cmd.Module == null)) @@ -845,7 +843,8 @@ public CommandContext CreateFakeContext(DiscordUser actor, DiscordChannel channe AttachmentsInternal = [], EmbedsInternal = [], TimestampRaw = now.ToString("yyyy-MM-ddTHH:mm:sszzz"), - ReactionsInternal = [] + ReactionsInternal = [], + GuildId = channel.GuildId }; var mentionedUsers = new List(); @@ -877,10 +876,14 @@ public CommandContext CreateFakeContext(DiscordUser actor, DiscordChannel channe RawArgumentString = rawArguments ?? "", Prefix = prefix, CommandsNext = this, - Services = this.Services + Services = this.Services, + UserId = msg.Author.Id, + GuildId = msg.GuildId, + MemberId = msg.GuildId is not null ? msg.Author.Id : null, + ChannelId = msg.ChannelId }; - if (cmd != null && (cmd.Module is TransientCommandModule || cmd.Module == null)) + if (cmd != null && cmd.Module is TransientCommandModule or null) { var scope = ctx.Services.CreateScope(); ctx.ServiceScopeContext = new(ctx.Services, scope); diff --git a/DisCatSharp.CommandsNext/Entities/CommandGroup.cs b/DisCatSharp.CommandsNext/Entities/CommandGroup.cs index 5930de264f..063fc1039c 100644 --- a/DisCatSharp.CommandsNext/Entities/CommandGroup.cs +++ b/DisCatSharp.CommandsNext/Entities/CommandGroup.cs @@ -56,7 +56,11 @@ public override async Task ExecuteAsync(CommandContext ctx) RawArgumentString = ctx.RawArgumentString[findPos..], Prefix = ctx.Prefix, CommandsNext = ctx.CommandsNext, - Services = ctx.Services + Services = ctx.Services, + UserId = ctx.Message.Author.Id, + GuildId = ctx.Message.GuildId, + MemberId = ctx.Message.GuildId is not null ? ctx.Message.Author.Id : null, + ChannelId = ctx.Message.ChannelId }; var fchecks = await cmd.RunChecksAsync(xctx, false).ConfigureAwait(false); diff --git a/DisCatSharp.CommandsNext/EventArgs/CommandContext.cs b/DisCatSharp.CommandsNext/EventArgs/CommandContext.cs index 7a71858143..64a64c512f 100644 --- a/DisCatSharp.CommandsNext/EventArgs/CommandContext.cs +++ b/DisCatSharp.CommandsNext/EventArgs/CommandContext.cs @@ -3,6 +3,8 @@ using System.Threading.Tasks; using DisCatSharp.Entities; +using DisCatSharp.Entities.Core; +using DisCatSharp.Enums.Core; using Microsoft.Extensions.DependencyInjection; @@ -11,7 +13,7 @@ namespace DisCatSharp.CommandsNext; /// /// Represents a context in which a command is executed. /// -public sealed class CommandContext +public sealed class CommandContext : DisCatSharpCommandContext { /// /// Gets the client which received the message. @@ -98,6 +100,7 @@ public DiscordMember Member /// Initializes a new instance of the class. /// internal CommandContext() + : base(DisCatSharpCommandType.TextCommand) { this._lazyMember = new(() => this.Guild is not null && this.Guild.Members.TryGetValue(this.User.Id, out var member) ? member : this.Guild?.GetMemberAsync(this.User.Id).ConfigureAwait(false).GetAwaiter().GetResult()); } diff --git a/DisCatSharp/Clients/DiscordClient.cs b/DisCatSharp/Clients/DiscordClient.cs index f42f01344c..b531722c6c 100644 --- a/DisCatSharp/Clients/DiscordClient.cs +++ b/DisCatSharp/Clients/DiscordClient.cs @@ -11,6 +11,7 @@ using DisCatSharp.Attributes; using DisCatSharp.Entities; +using DisCatSharp.Entities.Core; using DisCatSharp.Enums; using DisCatSharp.Exceptions; using DisCatSharp.Net; @@ -135,6 +136,11 @@ public IReadOnlyDictionary EmbeddedActivities internal Dictionary EmbeddedActivitiesInternal = []; private Lazy> _embeddedActivitiesLazy; + /// + /// Gets the cooldown buckets for commands. + /// + public ConcurrentDictionary CommandCooldownBuckets { get; } = []; + #endregion #region Constructor/Internal Setup @@ -1697,6 +1703,8 @@ public override void Dispose() this.ApiClient.Rest.Dispose(); this.CurrentUser = null; + this.CommandCooldownBuckets.Clear(); + var extensions = this._extensions; // prevent _extensions being modified during dispose this._extensions.Clear(); foreach (var extension in extensions) diff --git a/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs b/DisCatSharp/Entities/Core/CooldownBucket.cs similarity index 81% rename from DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs rename to DisCatSharp/Entities/Core/CooldownBucket.cs index 2def3891ab..de21ef767b 100644 --- a/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs +++ b/DisCatSharp/Entities/Core/CooldownBucket.cs @@ -3,13 +3,9 @@ using System.Threading; using System.Threading.Tasks; -using DisCatSharp.ApplicationCommands.Context; -using DisCatSharp.Entities; -using DisCatSharp.Enums; - using Microsoft.Extensions.Logging; -namespace DisCatSharp.ApplicationCommands.Entities; +namespace DisCatSharp.Entities.Core; /// /// Represents a cooldown bucket. @@ -83,7 +79,7 @@ public int RemainingUses /// ID of the channel with which this cooldown is associated. /// ID of the guild with which this cooldown is associated. /// ID of the member with which this cooldown is associated. - internal CooldownBucket(int maxUses, TimeSpan resetAfter, ulong commandId, string commandName, ulong userId = 0, ulong channelId = 0, ulong guildId = 0, ulong memberId = 0) + internal CooldownBucket(int maxUses, TimeSpan resetAfter, string commandName, string commandId, ulong userId = 0, ulong channelId = 0, ulong guildId = 0, ulong memberId = 0) { this.RemainingUsesInternal = maxUses; this.MaxUses = maxUses; @@ -100,42 +96,35 @@ internal CooldownBucket(int maxUses, TimeSpan resetAfter, ulong commandId, strin /// /// Decrements the remaining use counter. /// + /// The context. /// Whether decrement succeeded or not. - internal async Task DecrementUseAsync(BaseContext ctx) + internal async Task DecrementUseAsync(DisCatSharpCommandContext ctx) { await this.UsageSemaphore.WaitAsync().ConfigureAwait(false); - ctx.Client.Logger.LogDebug($"[Cooldown::check({ctx.FullCommandName})]:\n\tRemaining: {this.RemainingUses}/{this.MaxUses}\n\tResets: {this.ResetsAt}\n\tNow: {DateTimeOffset.UtcNow}\n\tVars[u,c,g,m]: {this.UserId} {this.ChannelId} {this.GuildId} {this.MemberId}\n\tId: {this.BucketId}"); + ctx.Client.Logger.LogDebug($"[Cooldown::prev_check({ctx.FullCommandName})]:\n\tRemaining: {this.RemainingUses}/{this.MaxUses}\n\tResets: {this.ResetsAt}\n\tNow: {DateTimeOffset.UtcNow}\n\tVars[u,c,g,m]: {this.UserId} {this.ChannelId} {this.GuildId} {this.MemberId}\n\tId: {this.BucketId}"); - // if we're past reset time... var now = DateTimeOffset.UtcNow; if (now >= this.ResetsAt) { - // ...do the reset and set a new reset time Interlocked.Exchange(ref this.RemainingUsesInternal, this.MaxUses); this.ResetsAt = now + this.Reset; } - // check if we have any uses left, if we do... + ctx.Client.Logger.LogDebug($"[Cooldown::check({ctx.FullCommandName})]:\n\tRemaining: {this.RemainingUses}/{this.MaxUses}\n\tResets: {this.ResetsAt}\n\tNow: {DateTimeOffset.UtcNow}\n\tVars[u,c,g,m]: {this.UserId} {this.ChannelId} {this.GuildId} {this.MemberId}\n\tId: {this.BucketId}"); + var success = false; if (this.RemainingUses > 0) { - // ...decrement, and return success... Interlocked.Decrement(ref this.RemainingUsesInternal); success = true; } - // ...otherwise just fail this.UsageSemaphore.Release(); if (success) return success; ctx.Client.Logger.LogWarning($"[Cooldown::hit({ctx.FullCommandName})]:\n\tRemaining: {this.RemainingUses}/{this.MaxUses}\n\tResets: {this.ResetsAt}\n\tNow: {DateTimeOffset.UtcNow}\n\tVars[u,c,g,m]: {this.UserId} {this.ChannelId} {this.GuildId} {this.MemberId}\n\tId: {this.BucketId}"); - if (ApplicationCommandsExtension.Configuration.AutoDefer) - await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"Error: Ratelimit hit. Try again in {this.ResetsAt.Timestamp()}")); - else - await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral().WithContent($"Error: Ratelimit hit. Try again in {this.ResetsAt.Timestamp()}")); - return success; } @@ -195,6 +184,6 @@ public override int GetHashCode() /// ID of the guild with which this cooldown is associated. /// ID of the member with which this cooldown is associated. /// Generated bucket ID. - public static string MakeId(ulong commandId, string commandName, ulong userId = 0, ulong channelId = 0, ulong guildId = 0, ulong memberId = 0) - => $"{commandId.ToString(CultureInfo.InvariantCulture)}:{commandName}::{userId.ToString(CultureInfo.InvariantCulture)}:{channelId.ToString(CultureInfo.InvariantCulture)}:{guildId.ToString(CultureInfo.InvariantCulture)}:{memberId.ToString(CultureInfo.InvariantCulture)}"; + public static string MakeId(string commandId, string commandName, ulong userId = 0, ulong channelId = 0, ulong guildId = 0, ulong memberId = 0) + => $"{commandId}:{commandName}::{userId.ToString(CultureInfo.InvariantCulture)}:{channelId.ToString(CultureInfo.InvariantCulture)}:{guildId.ToString(CultureInfo.InvariantCulture)}:{memberId.ToString(CultureInfo.InvariantCulture)}"; } diff --git a/DisCatSharp/Entities/Core/DisCatSharpCommandContext.cs b/DisCatSharp/Entities/Core/DisCatSharpCommandContext.cs new file mode 100644 index 0000000000..cc5300e6b7 --- /dev/null +++ b/DisCatSharp/Entities/Core/DisCatSharpCommandContext.cs @@ -0,0 +1,80 @@ +using DisCatSharp.Enums.Core; + +namespace DisCatSharp.Entities.Core; + +/// +/// Interface for various command types like slash commands, user commands, message commands, text commands, etc. +/// +public class DisCatSharpCommandContext +{ + /// + /// Gets the client. + /// + public DiscordClient Client { get; internal init; } + + /// + /// Gets the id of the user who executes this command. + /// + public ulong UserId { get; internal set; } + + /// + /// Gets the id of the channel this command gets executed in. + /// + public ulong ChannelId { get; internal set; } + + /// + /// Gets the id of the guild this command gets executed in. + /// + public ulong? GuildId { get; internal set; } + + /// + /// Gets the id of the member who executes this command. + /// + public ulong? MemberId { get; internal set; } + + /// + /// Gets the id of the command. + /// + public ulong? CommandId { get; internal set; } + + /// + /// Gets the name of the command. + /// + public string CommandName { get; internal set; } = string.Empty; + + /// + /// Gets the name of the sub command. + /// + public string? SubCommandName { get; internal set; } + + /// + /// Gets the name of the sub command within a sub group. + /// + public string? SubSubCommandName { get; internal set; } + + /// + /// Gets the fully qualified name of the command. + /// + public virtual string FullCommandName { get; internal set; } = string.Empty; + + /// + /// Gets the type of the command. + /// + public DisCatSharpCommandType CommandType { get; internal set; } + + /// + /// Gets the command grouping type of the command. + /// + public DisCatSharpCommandGroupingType CommandGroupingType { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The command type. + /// The command grouping type. + internal DisCatSharpCommandContext(DisCatSharpCommandType type, DisCatSharpCommandGroupingType groupingType = DisCatSharpCommandGroupingType.None) + { + this.CommandType = type; + this.CommandGroupingType = groupingType; + } +} diff --git a/DisCatSharp.ApplicationCommands/Entities/IBucket.cs b/DisCatSharp/Entities/Core/IBucket.cs similarity index 94% rename from DisCatSharp.ApplicationCommands/Entities/IBucket.cs rename to DisCatSharp/Entities/Core/IBucket.cs index fa10df5bb7..3ff9bd923c 100644 --- a/DisCatSharp.ApplicationCommands/Entities/IBucket.cs +++ b/DisCatSharp/Entities/Core/IBucket.cs @@ -1,6 +1,7 @@ using System; +using System.Threading.Tasks; -namespace DisCatSharp.ApplicationCommands.Entities; +namespace DisCatSharp.Entities.Core; /// /// Defines the standard contract for bucket feature diff --git a/DisCatSharp.ApplicationCommands/Entities/ICooldown.cs b/DisCatSharp/Entities/Core/ICooldown.cs similarity index 69% rename from DisCatSharp.ApplicationCommands/Entities/ICooldown.cs rename to DisCatSharp/Entities/Core/ICooldown.cs index ae3747f725..0b9c7dde88 100644 --- a/DisCatSharp.ApplicationCommands/Entities/ICooldown.cs +++ b/DisCatSharp/Entities/Core/ICooldown.cs @@ -1,17 +1,17 @@ using System; +using System.Threading.Tasks; -using DisCatSharp.ApplicationCommands.Context; -using DisCatSharp.ApplicationCommands.Enums; +using DisCatSharp.Enums.Core; -namespace DisCatSharp.ApplicationCommands.Entities; +namespace DisCatSharp.Entities.Core; /// /// Cooldown feature contract /// -/// Type of in which this cooldown handles +/// Type of in which this cooldown handles /// Type of Cooldown bucket public interface ICooldown - where TContextType : BaseContext + where TContextType : DisCatSharpCommandContext where TBucketType : CooldownBucket { /// @@ -42,4 +42,11 @@ public interface ICooldown /// Command context to get cooldown bucket for. /// Requested cooldown bucket, or null if one wasn't present TBucketType GetBucket(TContextType ctx); + + /// + /// Responds to a ratelimit hit. + /// + /// The command context. + /// Whether the ratelimit wasn't it. + Task RespondRatelimitHitAsync(TContextType ctx, bool noHit, CooldownBucket bucket); } diff --git a/DisCatSharp/Entities/Core/IDisCatSharpCommand.cs b/DisCatSharp/Entities/Core/IDisCatSharpCommand.cs deleted file mode 100644 index a7c6f01d54..0000000000 --- a/DisCatSharp/Entities/Core/IDisCatSharpCommand.cs +++ /dev/null @@ -1,49 +0,0 @@ -using DisCatSharp.Enums.Core; - -namespace DisCatSharp.Entities.Core; - -/// -/// Interface for various command types like slash commands, user commands, message commands, text commands, etc. -/// -internal interface IDisCatSharpCommand -{ - /// - /// Gets the id of the user who executes this command. - /// - ulong UserId { get; internal set; } - - /// - /// Gets the id of the channel this command gets executed in. - /// - ulong ChannelId { get; internal set; } - - /// - /// Gets the id of the guild this command gets executed in. - /// - ulong? GuildId { get; internal set; } - - /// - /// Gets the id of the member who executes this command. - /// - ulong? MemberId { get; internal set; } - - /// - /// Gets the id of the command. - /// - ulong? CommandId { get; internal set; } - - /// - /// Gets the name of the command if is not available. - /// - string? CommandName { get; internal set; } - - /// - /// Gets the type of the command. - /// - DisCatSharpCommandType CommandType { get; internal set; } - - /// - /// Gets the command grouping type of the command. - /// - DisCatSharpCommandGroupingType CommandGroupingType { get; internal set; } -} diff --git a/DisCatSharp.ApplicationCommands/Enums/CooldownBucketType.cs b/DisCatSharp/Enums/Core/CooldownBucketType.cs similarity index 94% rename from DisCatSharp.ApplicationCommands/Enums/CooldownBucketType.cs rename to DisCatSharp/Enums/Core/CooldownBucketType.cs index dad7acc93c..f50fecab0d 100644 --- a/DisCatSharp.ApplicationCommands/Enums/CooldownBucketType.cs +++ b/DisCatSharp/Enums/Core/CooldownBucketType.cs @@ -1,6 +1,6 @@ using System; -namespace DisCatSharp.ApplicationCommands.Enums; +namespace DisCatSharp.Enums.Core; /// /// Defines how are command cooldowns applied. diff --git a/DisCatSharp/Enums/Core/DisCatSharpCommandGroupingType.cs b/DisCatSharp/Enums/Core/DisCatSharpCommandGroupingType.cs index e48e2e2d86..6debe497b4 100644 --- a/DisCatSharp/Enums/Core/DisCatSharpCommandGroupingType.cs +++ b/DisCatSharp/Enums/Core/DisCatSharpCommandGroupingType.cs @@ -3,25 +3,25 @@ namespace DisCatSharp.Enums.Core; /// /// Represents the grouping type of a command. /// -internal enum DisCatSharpCommandGroupingType +public enum DisCatSharpCommandGroupingType { /// - /// The command is not part of a group. + /// This is a special type and not a command. /// None, /// - /// The command is a group. + /// The command is not part of a group. /// - Group, + Command, /// - /// The command is a subgroup. + /// The command is a sub command. /// - SubGroup, + SubCommand, /// - /// The command is a subcommand. + /// The command is a sub group command. /// - SubCommand + SubGroupCommand } diff --git a/DisCatSharp/Enums/Core/DisCatSharpCommandType.cs b/DisCatSharp/Enums/Core/DisCatSharpCommandType.cs index b7394f1eca..22619fd78f 100644 --- a/DisCatSharp/Enums/Core/DisCatSharpCommandType.cs +++ b/DisCatSharp/Enums/Core/DisCatSharpCommandType.cs @@ -3,7 +3,7 @@ namespace DisCatSharp.Enums.Core; /// /// Represents the type of a command. /// -internal enum DisCatSharpCommandType +public enum DisCatSharpCommandType { /// /// A text command. @@ -26,7 +26,7 @@ internal enum DisCatSharpCommandType MessageCommand, /// - /// A special component command. + /// A special type. /// - ComponentCommand + Special }