Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CustomID Template Improvements #2528

Merged
merged 2 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;

namespace Discord.Interactions
{
Expand Down Expand Up @@ -28,6 +29,11 @@ public class ComponentInteractionAttribute : Attribute
/// </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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public sealed class ModalInteractionAttribute : Attribute
/// </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>
Expand Down
20 changes: 20 additions & 0 deletions src/Discord.Net.Interactions/Builders/Commands/CommandBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public abstract class CommandBuilder<TInfo, TBuilder, TParamBuilder> : ICommandB
/// <inheritdoc/>
public bool IgnoreGroupNames { get; set; }

/// <inheritdoc/>
public bool TreatNameAsRegex { get; set; }

/// <inheritdoc/>
public RunMode RunMode { get; set; }

Expand Down Expand Up @@ -117,6 +120,19 @@ public TBuilder SetRunMode (RunMode runMode)
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>
Expand Down Expand Up @@ -163,6 +179,10 @@ ICommandBuilder ICommandBuilder.WithAttributes (params Attribute[] attributes) =
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);
Expand Down
14 changes: 14 additions & 0 deletions src/Discord.Net.Interactions/Builders/Commands/ICommandBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public interface ICommandBuilder
/// </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>
Expand Down Expand Up @@ -90,6 +95,15 @@ public interface ICommandBuilder
/// </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>
Expand Down
4 changes: 3 additions & 1 deletion src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ private static void BuildComponentCommand (ComponentCommandBuilder builder, Func
builder.Name = interaction.CustomId;
builder.RunMode = interaction.RunMode;
builder.IgnoreGroupNames = interaction.IgnoreGroupNames;
builder.TreatNameAsRegex = interaction.TreatAsRegex;
}
break;
case PreconditionAttribute precondition:
Expand All @@ -287,7 +288,7 @@ private static void BuildComponentCommand (ComponentCommandBuilder builder, Func

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));
Expand Down Expand Up @@ -355,6 +356,7 @@ private static void BuildModalCommand(ModalCommandBuilder builder, Func<IService
builder.Name = modal.CustomId;
builder.RunMode = modal.RunMode;
builder.IgnoreGroupNames = modal.IgnoreGroupNames;
builder.TreatNameAsRegex = modal.TreatAsRegex;
}
break;
case PreconditionAttribute precondition:
Expand Down
3 changes: 3 additions & 0 deletions src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public abstract class CommandInfo<TParameter> : ICommandInfo where TParameter :
/// <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;
Expand All @@ -78,6 +80,7 @@ internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, Intera
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);
Expand Down
2 changes: 2 additions & 0 deletions src/Discord.Net.Interactions/Info/ICommandInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public interface ICommandInfo
/// </summary>
IReadOnlyCollection<IParameterInfo> Parameters { get; }

bool TreatNameAsRegex { get; }

/// <summary>
/// Executes the command with the provided context.
/// </summary>
Expand Down
9 changes: 3 additions & 6 deletions src/Discord.Net.Interactions/Map/CommandMapNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
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;
Expand All @@ -35,10 +34,8 @@ public void AddCommand (IList<string> keywords, int index, T commandInfo)
{
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))
Expand Down
33 changes: 33 additions & 0 deletions src/Discord.Net.Interactions/Utilities/RegexUtils.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Discord.Interactions;
using System;
using System.Linq;

Expand Down Expand Up @@ -81,5 +82,37 @@ internal static bool IsMetachar(char ch)
{
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;
}
}
}