diff --git a/src/Discord.Net.Interactions/Attributes/Commands/ComponentInteractionAttribute.cs b/src/Discord.Net.Interactions/Attributes/Commands/ComponentInteractionAttribute.cs index 70bc285fc..823410cdf 100644 --- a/src/Discord.Net.Interactions/Attributes/Commands/ComponentInteractionAttribute.cs +++ b/src/Discord.Net.Interactions/Attributes/Commands/ComponentInteractionAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace Discord.Interactions { @@ -28,6 +29,11 @@ namespace Discord.Interactions /// public RunMode RunMode { get; } + /// + /// Gets or sets whether the should be treated as a raw Regex pattern. + /// + public bool TreatAsRegex { get; set; } = false; + /// /// Create a command for component interaction handling. /// diff --git a/src/Discord.Net.Interactions/Attributes/Commands/ModalInteractionAttribute.cs b/src/Discord.Net.Interactions/Attributes/Commands/ModalInteractionAttribute.cs index a0ce91cda..f5df950bd 100644 --- a/src/Discord.Net.Interactions/Attributes/Commands/ModalInteractionAttribute.cs +++ b/src/Discord.Net.Interactions/Attributes/Commands/ModalInteractionAttribute.cs @@ -28,6 +28,11 @@ namespace Discord.Interactions /// public RunMode RunMode { get; } + /// + /// Gets or sets whether the should be treated as a raw Regex pattern. + /// + public bool TreatAsRegex { get; set; } = false; + /// /// Create a command for modal interaction handling. /// diff --git a/src/Discord.Net.Interactions/Builders/Commands/CommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/CommandBuilder.cs index 5c35e8871..d7f90678d 100644 --- a/src/Discord.Net.Interactions/Builders/Commands/CommandBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Commands/CommandBuilder.cs @@ -35,6 +35,9 @@ namespace Discord.Interactions.Builders /// public bool IgnoreGroupNames { get; set; } + /// + public bool TreatNameAsRegex { get; set; } + /// public RunMode RunMode { get; set; } @@ -117,6 +120,19 @@ namespace Discord.Interactions.Builders return Instance; } + /// + /// Sets . + /// + /// New value of the . + /// + /// The builder instance. + /// + public TBuilder WithNameAsRegex (bool value) + { + TreatNameAsRegex = value; + return Instance; + } + /// /// Adds parameter builders to . /// @@ -163,6 +179,10 @@ namespace Discord.Interactions.Builders ICommandBuilder ICommandBuilder.SetRunMode (RunMode runMode) => SetRunMode(runMode); + /// + ICommandBuilder ICommandBuilder.WithNameAsRegex(bool value) => + WithNameAsRegex(value); + /// ICommandBuilder ICommandBuilder.AddParameters (params IParameterBuilder[] parameters) => AddParameters(parameters as TParamBuilder); diff --git a/src/Discord.Net.Interactions/Builders/Commands/ICommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/ICommandBuilder.cs index 95007296c..97bc1e8e9 100644 --- a/src/Discord.Net.Interactions/Builders/Commands/ICommandBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Commands/ICommandBuilder.cs @@ -34,6 +34,11 @@ namespace Discord.Interactions.Builders /// bool IgnoreGroupNames { get; set; } + /// + /// Gets or sets whether the should be directly used as a Regex pattern. + /// + bool TreatNameAsRegex { get; set; } + /// /// Gets or sets the run mode this command gets executed with. /// @@ -90,6 +95,15 @@ namespace Discord.Interactions.Builders /// ICommandBuilder SetRunMode (RunMode runMode); + /// + /// Sets . + /// + /// New value of the . + /// + /// The builder instance. + /// + ICommandBuilder WithNameAsRegex(bool value); + /// /// Adds parameter builders to . /// diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs index 35126a674..82acd800d 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs @@ -274,6 +274,7 @@ namespace Discord.Interactions.Builders builder.Name = interaction.CustomId; builder.RunMode = interaction.RunMode; builder.IgnoreGroupNames = interaction.IgnoreGroupNames; + builder.TreatNameAsRegex = interaction.TreatAsRegex; } break; case PreconditionAttribute precondition: @@ -287,7 +288,7 @@ namespace Discord.Interactions.Builders var parameters = methodInfo.GetParameters(); - var wildCardCount = Regex.Matches(Regex.Escape(builder.Name), Regex.Escape(commandService._wildCardExp)).Count; + var wildCardCount = RegexUtils.GetWildCardCount(builder.Name, commandService._wildCardExp); foreach (var parameter in parameters) builder.AddParameter(x => BuildComponentParameter(x, parameter, parameter.Position >= wildCardCount)); @@ -355,6 +356,7 @@ namespace Discord.Interactions.Builders builder.Name = modal.CustomId; builder.RunMode = modal.RunMode; builder.IgnoreGroupNames = modal.IgnoreGroupNames; + builder.TreatNameAsRegex = modal.TreatAsRegex; } break; case PreconditionAttribute precondition: diff --git a/src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs index 99895d3ed..92e2f30bb 100644 --- a/src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs @@ -66,6 +66,8 @@ namespace Discord.Interactions /// public abstract IReadOnlyList Parameters { get; } + public bool TreatNameAsRegex { get; } + internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService) { CommandService = commandService; @@ -78,6 +80,7 @@ namespace Discord.Interactions RunMode = builder.RunMode != RunMode.Default ? builder.RunMode : commandService._runMode; Attributes = builder.Attributes.ToImmutableArray(); Preconditions = builder.Preconditions.ToImmutableArray(); + TreatNameAsRegex = builder.TreatNameAsRegex && SupportsWildCards; _action = builder.Callback; _groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); diff --git a/src/Discord.Net.Interactions/Info/ICommandInfo.cs b/src/Discord.Net.Interactions/Info/ICommandInfo.cs index 843d5198b..1de6e0df7 100644 --- a/src/Discord.Net.Interactions/Info/ICommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/ICommandInfo.cs @@ -65,6 +65,8 @@ namespace Discord.Interactions /// IReadOnlyCollection Parameters { get; } + bool TreatNameAsRegex { get; } + /// /// Executes the command with the provided context. /// diff --git a/src/Discord.Net.Interactions/Map/CommandMapNode.cs b/src/Discord.Net.Interactions/Map/CommandMapNode.cs index c866fe00e..3dec30f4a 100644 --- a/src/Discord.Net.Interactions/Map/CommandMapNode.cs +++ b/src/Discord.Net.Interactions/Map/CommandMapNode.cs @@ -2,14 +2,13 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Text.RegularExpressions; namespace Discord.Interactions { internal class CommandMapNode where T : class, ICommandInfo - { - private const string RegexWildCardExp = "(\\S+)?"; - + { private readonly string _wildCardStr = "*"; private readonly ConcurrentDictionary> _nodes; private readonly ConcurrentDictionary _commands; @@ -35,10 +34,8 @@ namespace Discord.Interactions { if (keywords.Count == index + 1) { - if (commandInfo.SupportsWildCards && commandInfo.Name.Contains(_wildCardStr)) + if (commandInfo.SupportsWildCards && RegexUtils.TryBuildRegexPattern(commandInfo, _wildCardStr, out var patternStr)) { - var escapedStr = RegexUtils.EscapeExcluding(commandInfo.Name, _wildCardStr.ToArray()); - var patternStr = "\\A" + escapedStr.Replace(_wildCardStr, RegexWildCardExp) + "\\Z"; var regex = new Regex(patternStr, RegexOptions.Singleline | RegexOptions.Compiled); if (!_wildCardCommands.TryAdd(regex, commandInfo)) diff --git a/src/Discord.Net.Interactions/Utilities/RegexUtils.cs b/src/Discord.Net.Interactions/Utilities/RegexUtils.cs index 82ba944f8..b3316106c 100644 --- a/src/Discord.Net.Interactions/Utilities/RegexUtils.cs +++ b/src/Discord.Net.Interactions/Utilities/RegexUtils.cs @@ -1,3 +1,4 @@ +using Discord.Interactions; using System; using System.Linq; @@ -81,5 +82,37 @@ namespace System.Text.RegularExpressions { return (ch <= '|' && _category[ch] >= E); } + + internal static int GetWildCardCount(string input, string wildCardExpression) + { + var escapedWildCard = Regex.Escape(wildCardExpression); + var match = Regex.Matches(input, $@"(?(T commandInfo, string wildCardStr, out string pattern) where T: class, ICommandInfo + { + if (commandInfo.TreatNameAsRegex) + { + pattern = commandInfo.Name; + return true; + } + + if (GetWildCardCount(commandInfo.Name, wildCardStr) == 0) + { + pattern = null; + return false; + } + + var escapedWildCard = Regex.Escape(wildCardStr); + var unquantified = Regex.Replace(commandInfo.Name, $@"(?[^{escapedWildCard}]?)", + @"([^\n\t${delimiter}]+)${delimiter}"); + + var quantified = Regex.Replace(unquantified, $@"(?[0-9]+)(?,[0-9]*)?(?[^{escapedWildCard}]?)", + @"([^\n\t${delimiter}]{${start}${end}})${delimiter}"); + + pattern = "\\A" + quantified + "\\Z"; + return true; + } } }