| @@ -1,4 +1,5 @@ | |||
| using System; | |||
| using System.Runtime.CompilerServices; | |||
| namespace Discord.Interactions | |||
| { | |||
| @@ -28,6 +29,11 @@ namespace Discord.Interactions | |||
| /// </summary> | |||
| public RunMode RunMode { get; } | |||
| /// <summary> | |||
| /// Gets or sets whether the <see cref="CustomId"/> should be treated as a raw Regex pattern. | |||
| /// </summary> | |||
| public bool TreatAsRegex { get; set; } = false; | |||
| /// <summary> | |||
| /// Create a command for component interaction handling. | |||
| /// </summary> | |||
| @@ -28,6 +28,11 @@ namespace Discord.Interactions | |||
| /// </summary> | |||
| public RunMode RunMode { get; } | |||
| /// <summary> | |||
| /// Gets or sets whether the <see cref="CustomId"/> should be treated as a raw Regex pattern. | |||
| /// </summary> | |||
| public bool TreatAsRegex { get; set; } = false; | |||
| /// <summary> | |||
| /// Create a command for modal interaction handling. | |||
| /// </summary> | |||
| @@ -35,6 +35,9 @@ namespace Discord.Interactions.Builders | |||
| /// <inheritdoc/> | |||
| public bool IgnoreGroupNames { get; set; } | |||
| /// <inheritdoc/> | |||
| public bool TreatNameAsRegex { get; set; } | |||
| /// <inheritdoc/> | |||
| public RunMode RunMode { get; set; } | |||
| @@ -117,6 +120,19 @@ namespace Discord.Interactions.Builders | |||
| return Instance; | |||
| } | |||
| /// <summary> | |||
| /// Sets <see cref="TreatNameAsRegex"/>. | |||
| /// </summary> | |||
| /// <param name="value">New value of the <see cref="TreatNameAsRegex"/>.</param> | |||
| /// <returns> | |||
| /// The builder instance. | |||
| /// </returns> | |||
| public TBuilder WithNameAsRegex (bool value) | |||
| { | |||
| TreatNameAsRegex = value; | |||
| return Instance; | |||
| } | |||
| /// <summary> | |||
| /// Adds parameter builders to <see cref="Parameters"/>. | |||
| /// </summary> | |||
| @@ -163,6 +179,10 @@ namespace Discord.Interactions.Builders | |||
| ICommandBuilder ICommandBuilder.SetRunMode (RunMode runMode) => | |||
| SetRunMode(runMode); | |||
| /// <inheritdoc/> | |||
| ICommandBuilder ICommandBuilder.WithNameAsRegex(bool value) => | |||
| WithNameAsRegex(value); | |||
| /// <inheritdoc/> | |||
| ICommandBuilder ICommandBuilder.AddParameters (params IParameterBuilder[] parameters) => | |||
| AddParameters(parameters as TParamBuilder); | |||
| @@ -34,6 +34,11 @@ namespace Discord.Interactions.Builders | |||
| /// </summary> | |||
| bool IgnoreGroupNames { get; set; } | |||
| /// <summary> | |||
| /// Gets or sets whether the <see cref="Name"/> should be directly used as a Regex pattern. | |||
| /// </summary> | |||
| bool TreatNameAsRegex { get; set; } | |||
| /// <summary> | |||
| /// Gets or sets the run mode this command gets executed with. | |||
| /// </summary> | |||
| @@ -90,6 +95,15 @@ namespace Discord.Interactions.Builders | |||
| /// </returns> | |||
| ICommandBuilder SetRunMode (RunMode runMode); | |||
| /// <summary> | |||
| /// Sets <see cref="TreatNameAsRegex"/>. | |||
| /// </summary> | |||
| /// <param name="value">New value of the <see cref="TreatNameAsRegex"/>.</param> | |||
| /// <returns> | |||
| /// The builder instance. | |||
| /// </returns> | |||
| ICommandBuilder WithNameAsRegex(bool value); | |||
| /// <summary> | |||
| /// Adds parameter builders to <see cref="Parameters"/>. | |||
| /// </summary> | |||
| @@ -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: | |||
| @@ -66,6 +66,8 @@ namespace Discord.Interactions | |||
| /// <inheritdoc cref="ICommandInfo.Parameters"/> | |||
| public abstract IReadOnlyList<TParameter> 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); | |||
| @@ -65,6 +65,8 @@ namespace Discord.Interactions | |||
| /// </summary> | |||
| IReadOnlyCollection<IParameterInfo> Parameters { get; } | |||
| bool TreatNameAsRegex { get; } | |||
| /// <summary> | |||
| /// Executes the command with the provided context. | |||
| /// </summary> | |||
| @@ -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<T> where T : class, ICommandInfo | |||
| { | |||
| private const string RegexWildCardExp = "(\\S+)?"; | |||
| { | |||
| private readonly string _wildCardStr = "*"; | |||
| private readonly ConcurrentDictionary<string, CommandMapNode<T>> _nodes; | |||
| private readonly ConcurrentDictionary<string, T> _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)) | |||
| @@ -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, $@"(?<!\\){escapedWildCard}|(?<!\\){{[0-9]+(?:,[0-9]*)?(?<!\\)}}"); | |||
| return match.Count; | |||
| } | |||
| internal static bool TryBuildRegexPattern<T>(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}(?<delimiter>[^{escapedWildCard}]?)", | |||
| @"([^\n\t${delimiter}]+)${delimiter}"); | |||
| var quantified = Regex.Replace(unquantified, $@"(?<!\\){{(?<start>[0-9]+)(?<end>,[0-9]*)?(?<!\\)}}(?<delimiter>[^{escapedWildCard}]?)", | |||
| @"([^\n\t${delimiter}]{${start}${end}})${delimiter}"); | |||
| pattern = "\\A" + quantified + "\\Z"; | |||
| return true; | |||
| } | |||
| } | |||
| } | |||