@@ -1,4 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Runtime.CompilerServices; | |||||
namespace Discord.Interactions | namespace Discord.Interactions | ||||
{ | { | ||||
@@ -28,6 +29,11 @@ namespace Discord.Interactions | |||||
/// </summary> | /// </summary> | ||||
public RunMode RunMode { get; } | 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> | /// <summary> | ||||
/// Create a command for component interaction handling. | /// Create a command for component interaction handling. | ||||
/// </summary> | /// </summary> | ||||
@@ -28,6 +28,11 @@ namespace Discord.Interactions | |||||
/// </summary> | /// </summary> | ||||
public RunMode RunMode { get; } | 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> | /// <summary> | ||||
/// Create a command for modal interaction handling. | /// Create a command for modal interaction handling. | ||||
/// </summary> | /// </summary> | ||||
@@ -35,6 +35,9 @@ namespace Discord.Interactions.Builders | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public bool IgnoreGroupNames { get; set; } | public bool IgnoreGroupNames { get; set; } | ||||
/// <inheritdoc/> | |||||
public bool TreatNameAsRegex { get; set; } | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public RunMode RunMode { get; set; } | public RunMode RunMode { get; set; } | ||||
@@ -117,6 +120,19 @@ namespace Discord.Interactions.Builders | |||||
return Instance; | 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> | /// <summary> | ||||
/// Adds parameter builders to <see cref="Parameters"/>. | /// Adds parameter builders to <see cref="Parameters"/>. | ||||
/// </summary> | /// </summary> | ||||
@@ -163,6 +179,10 @@ namespace Discord.Interactions.Builders | |||||
ICommandBuilder ICommandBuilder.SetRunMode (RunMode runMode) => | ICommandBuilder ICommandBuilder.SetRunMode (RunMode runMode) => | ||||
SetRunMode(runMode); | SetRunMode(runMode); | ||||
/// <inheritdoc/> | |||||
ICommandBuilder ICommandBuilder.WithNameAsRegex(bool value) => | |||||
WithNameAsRegex(value); | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
ICommandBuilder ICommandBuilder.AddParameters (params IParameterBuilder[] parameters) => | ICommandBuilder ICommandBuilder.AddParameters (params IParameterBuilder[] parameters) => | ||||
AddParameters(parameters as TParamBuilder); | AddParameters(parameters as TParamBuilder); | ||||
@@ -34,6 +34,11 @@ namespace Discord.Interactions.Builders | |||||
/// </summary> | /// </summary> | ||||
bool IgnoreGroupNames { get; set; } | 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> | /// <summary> | ||||
/// Gets or sets the run mode this command gets executed with. | /// Gets or sets the run mode this command gets executed with. | ||||
/// </summary> | /// </summary> | ||||
@@ -90,6 +95,15 @@ namespace Discord.Interactions.Builders | |||||
/// </returns> | /// </returns> | ||||
ICommandBuilder SetRunMode (RunMode runMode); | 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> | /// <summary> | ||||
/// Adds parameter builders to <see cref="Parameters"/>. | /// Adds parameter builders to <see cref="Parameters"/>. | ||||
/// </summary> | /// </summary> | ||||
@@ -274,6 +274,7 @@ namespace Discord.Interactions.Builders | |||||
builder.Name = interaction.CustomId; | builder.Name = interaction.CustomId; | ||||
builder.RunMode = interaction.RunMode; | builder.RunMode = interaction.RunMode; | ||||
builder.IgnoreGroupNames = interaction.IgnoreGroupNames; | builder.IgnoreGroupNames = interaction.IgnoreGroupNames; | ||||
builder.TreatNameAsRegex = interaction.TreatAsRegex; | |||||
} | } | ||||
break; | break; | ||||
case PreconditionAttribute precondition: | case PreconditionAttribute precondition: | ||||
@@ -287,7 +288,7 @@ namespace Discord.Interactions.Builders | |||||
var parameters = methodInfo.GetParameters(); | 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) | foreach (var parameter in parameters) | ||||
builder.AddParameter(x => BuildComponentParameter(x, parameter, parameter.Position >= wildCardCount)); | builder.AddParameter(x => BuildComponentParameter(x, parameter, parameter.Position >= wildCardCount)); | ||||
@@ -355,6 +356,7 @@ namespace Discord.Interactions.Builders | |||||
builder.Name = modal.CustomId; | builder.Name = modal.CustomId; | ||||
builder.RunMode = modal.RunMode; | builder.RunMode = modal.RunMode; | ||||
builder.IgnoreGroupNames = modal.IgnoreGroupNames; | builder.IgnoreGroupNames = modal.IgnoreGroupNames; | ||||
builder.TreatNameAsRegex = modal.TreatAsRegex; | |||||
} | } | ||||
break; | break; | ||||
case PreconditionAttribute precondition: | case PreconditionAttribute precondition: | ||||
@@ -66,6 +66,8 @@ namespace Discord.Interactions | |||||
/// <inheritdoc cref="ICommandInfo.Parameters"/> | /// <inheritdoc cref="ICommandInfo.Parameters"/> | ||||
public abstract IReadOnlyList<TParameter> Parameters { get; } | public abstract IReadOnlyList<TParameter> Parameters { get; } | ||||
public bool TreatNameAsRegex { get; } | |||||
internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService) | internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService) | ||||
{ | { | ||||
CommandService = commandService; | CommandService = commandService; | ||||
@@ -78,6 +80,7 @@ namespace Discord.Interactions | |||||
RunMode = builder.RunMode != RunMode.Default ? builder.RunMode : commandService._runMode; | RunMode = builder.RunMode != RunMode.Default ? builder.RunMode : commandService._runMode; | ||||
Attributes = builder.Attributes.ToImmutableArray(); | Attributes = builder.Attributes.ToImmutableArray(); | ||||
Preconditions = builder.Preconditions.ToImmutableArray(); | Preconditions = builder.Preconditions.ToImmutableArray(); | ||||
TreatNameAsRegex = builder.TreatNameAsRegex && SupportsWildCards; | |||||
_action = builder.Callback; | _action = builder.Callback; | ||||
_groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); | _groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); | ||||
@@ -65,6 +65,8 @@ namespace Discord.Interactions | |||||
/// </summary> | /// </summary> | ||||
IReadOnlyCollection<IParameterInfo> Parameters { get; } | IReadOnlyCollection<IParameterInfo> Parameters { get; } | ||||
bool TreatNameAsRegex { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Executes the command with the provided context. | /// Executes the command with the provided context. | ||||
/// </summary> | /// </summary> | ||||
@@ -2,14 +2,13 @@ using System; | |||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | |||||
using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||
namespace Discord.Interactions | namespace Discord.Interactions | ||||
{ | { | ||||
internal class CommandMapNode<T> where T : class, ICommandInfo | internal class CommandMapNode<T> where T : class, ICommandInfo | ||||
{ | |||||
private const string RegexWildCardExp = "(\\S+)?"; | |||||
{ | |||||
private readonly string _wildCardStr = "*"; | private readonly string _wildCardStr = "*"; | ||||
private readonly ConcurrentDictionary<string, CommandMapNode<T>> _nodes; | private readonly ConcurrentDictionary<string, CommandMapNode<T>> _nodes; | ||||
private readonly ConcurrentDictionary<string, T> _commands; | private readonly ConcurrentDictionary<string, T> _commands; | ||||
@@ -35,10 +34,8 @@ namespace Discord.Interactions | |||||
{ | { | ||||
if (keywords.Count == index + 1) | 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); | var regex = new Regex(patternStr, RegexOptions.Singleline | RegexOptions.Compiled); | ||||
if (!_wildCardCommands.TryAdd(regex, commandInfo)) | if (!_wildCardCommands.TryAdd(regex, commandInfo)) | ||||
@@ -1,3 +1,4 @@ | |||||
using Discord.Interactions; | |||||
using System; | using System; | ||||
using System.Linq; | using System.Linq; | ||||
@@ -81,5 +82,37 @@ namespace System.Text.RegularExpressions | |||||
{ | { | ||||
return (ch <= '|' && _category[ch] >= E); | 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; | |||||
} | |||||
} | } | ||||
} | } |