Browse Source

Command execution code rework and TypeConverters auto-scope fix (#2306)

* command execution rework and sync service scopes for typeconverters

* replace ValueTask with Task

* fix implementation bugs

Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>
tags/3.9.0
Cenk Ergen GitHub 2 years ago
parent
commit
6869817184
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 159 additions and 172 deletions
  1. +5
    -2
      src/Discord.Net.Interactions/Info/Commands/AutocompleteCommandInfo.cs
  2. +63
    -61
      src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs
  3. +12
    -28
      src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs
  4. +1
    -1
      src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs
  5. +9
    -4
      src/Discord.Net.Interactions/Info/Commands/ContextCommands/MessageCommandInfo.cs
  6. +8
    -3
      src/Discord.Net.Interactions/Info/Commands/ContextCommands/UserCommandInfo.cs
  7. +16
    -21
      src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs
  8. +34
    -41
      src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs
  9. +3
    -3
      src/Discord.Net.Interactions/Info/ModalInfo.cs
  10. +2
    -2
      src/Discord.Net.Interactions/InteractionService.cs
  11. +6
    -6
      src/Discord.Net.Interactions/Results/ParseResult.cs

+ 5
- 2
src/Discord.Net.Interactions/Info/Commands/AutocompleteCommandInfo.cs View File

@@ -23,7 +23,7 @@ namespace Discord.Interactions
public string CommandName { get; } public string CommandName { get; }


/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; }
public override IReadOnlyList<CommandParameterInfo> Parameters { get; }


/// <inheritdoc/> /// <inheritdoc/>
public override bool SupportsWildCards => false; public override bool SupportsWildCards => false;
@@ -41,9 +41,12 @@ namespace Discord.Interactions
if (context.Interaction is not IAutocompleteInteraction) if (context.Interaction is not IAutocompleteInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction");


return await RunAsync(context, Array.Empty<object>(), services).ConfigureAwait(false);
return await base.ExecuteAsync(context, services).ConfigureAwait(false);
} }


protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
=> Task.FromResult(ParseResult.FromSuccess(Array.Empty<object>()) as IResult);

/// <inheritdoc/> /// <inheritdoc/>
protected override Task InvokeModuleEvent(IInteractionContext context, IResult result) => protected override Task InvokeModuleEvent(IInteractionContext context, IResult result) =>
CommandService._autocompleteCommandExecutedEvent.InvokeAsync(this, context, result); CommandService._autocompleteCommandExecutedEvent.InvokeAsync(this, context, result);


+ 63
- 61
src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs View File

@@ -64,7 +64,7 @@ namespace Discord.Interactions
public IReadOnlyCollection<PreconditionAttribute> Preconditions { get; } public IReadOnlyCollection<PreconditionAttribute> Preconditions { get; }


/// <inheritdoc cref="ICommandInfo.Parameters"/> /// <inheritdoc cref="ICommandInfo.Parameters"/>
public abstract IReadOnlyCollection<TParameter> Parameters { get; }
public abstract IReadOnlyList<TParameter> Parameters { get; }


internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService) internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService)
{ {
@@ -85,71 +85,16 @@ namespace Discord.Interactions
} }


/// <inheritdoc/> /// <inheritdoc/>
public abstract Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services);
protected abstract Task InvokeModuleEvent(IInteractionContext context, IResult result);
protected abstract string GetLogString(IInteractionContext context);

/// <inheritdoc/>
public async Task<PreconditionResult> CheckPreconditionsAsync(IInteractionContext context, IServiceProvider services)
{
async Task<PreconditionResult> CheckGroups(ILookup<string, PreconditionAttribute> preconditions, string type)
{
foreach (IGrouping<string, PreconditionAttribute> preconditionGroup in preconditions)
{
if (preconditionGroup.Key == null)
{
foreach (PreconditionAttribute precondition in preconditionGroup)
{
var result = await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}
}
else
{
var results = new List<PreconditionResult>();
foreach (PreconditionAttribute precondition in preconditionGroup)
results.Add(await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false));

if (!results.Any(p => p.IsSuccess))
return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results);
}
}
return PreconditionGroupResult.FromSuccess();
}

var moduleResult = await CheckGroups(Module.GroupedPreconditions, "Module").ConfigureAwait(false);
if (!moduleResult.IsSuccess)
return moduleResult;

var commandResult = await CheckGroups(_groupedPreconditions, "Command").ConfigureAwait(false);
return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess();
}

protected async Task<IResult> RunAsync(IInteractionContext context, object[] args, IServiceProvider services)
public virtual async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
{ {
switch (RunMode) switch (RunMode)
{ {
case RunMode.Sync: case RunMode.Sync:
{
if (CommandService._autoServiceScopes)
{
using var scope = services?.CreateScope();
return await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false);
}

return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
}
return await ExecuteInternalAsync(context, services).ConfigureAwait(false);
case RunMode.Async: case RunMode.Async:
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
if (CommandService._autoServiceScopes)
{
using var scope = services?.CreateScope();
await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false);
}
else
await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
await ExecuteInternalAsync(context, services).ConfigureAwait(false);
}); });
break; break;
default: default:
@@ -159,16 +104,33 @@ namespace Discord.Interactions
return ExecuteResult.FromSuccess(); return ExecuteResult.FromSuccess();
} }


private async Task<IResult> ExecuteInternalAsync(IInteractionContext context, object[] args, IServiceProvider services)
protected abstract Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services);

private async Task<IResult> ExecuteInternalAsync(IInteractionContext context, IServiceProvider services)
{ {
await CommandService._cmdLogger.DebugAsync($"Executing {GetLogString(context)}").ConfigureAwait(false); await CommandService._cmdLogger.DebugAsync($"Executing {GetLogString(context)}").ConfigureAwait(false);


using var scope = services?.CreateScope();
if (CommandService._autoServiceScopes)
services = scope?.ServiceProvider ?? EmptyServiceProvider.Instance;

try try
{ {
var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false); var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false);
if (!preconditionResult.IsSuccess) if (!preconditionResult.IsSuccess)
return await InvokeEventAndReturn(context, preconditionResult).ConfigureAwait(false); return await InvokeEventAndReturn(context, preconditionResult).ConfigureAwait(false);


var argsResult = await ParseArgumentsAsync(context, services).ConfigureAwait(false);

if (!argsResult.IsSuccess)
return await InvokeEventAndReturn(context, argsResult).ConfigureAwait(false);

if(argsResult is not ParseResult parseResult)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason.");

var args = parseResult.Args;

var index = 0; var index = 0;
foreach (var parameter in Parameters) foreach (var parameter in Parameters)
{ {
@@ -221,7 +183,47 @@ namespace Discord.Interactions
} }
} }


protected async ValueTask<IResult> InvokeEventAndReturn(IInteractionContext context, IResult result)
protected abstract Task InvokeModuleEvent(IInteractionContext context, IResult result);
protected abstract string GetLogString(IInteractionContext context);

/// <inheritdoc/>
public async Task<PreconditionResult> CheckPreconditionsAsync(IInteractionContext context, IServiceProvider services)
{
async Task<PreconditionResult> CheckGroups(ILookup<string, PreconditionAttribute> preconditions, string type)
{
foreach (IGrouping<string, PreconditionAttribute> preconditionGroup in preconditions)
{
if (preconditionGroup.Key == null)
{
foreach (PreconditionAttribute precondition in preconditionGroup)
{
var result = await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}
}
else
{
var results = new List<PreconditionResult>();
foreach (PreconditionAttribute precondition in preconditionGroup)
results.Add(await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false));

if (!results.Any(p => p.IsSuccess))
return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results);
}
}
return PreconditionGroupResult.FromSuccess();
}

var moduleResult = await CheckGroups(Module.GroupedPreconditions, "Module").ConfigureAwait(false);
if (!moduleResult.IsSuccess)
return moduleResult;

var commandResult = await CheckGroups(_groupedPreconditions, "Command").ConfigureAwait(false);
return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess();
}

protected async Task<T> InvokeEventAndReturn<T>(IInteractionContext context, T result) where T : IResult
{ {
await InvokeModuleEvent(context, result).ConfigureAwait(false); await InvokeModuleEvent(context, result).ConfigureAwait(false);
return result; return result;


+ 12
- 28
src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs View File

@@ -13,7 +13,7 @@ namespace Discord.Interactions
public class ComponentCommandInfo : CommandInfo<ComponentCommandParameterInfo> public class ComponentCommandInfo : CommandInfo<ComponentCommandParameterInfo>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<ComponentCommandParameterInfo> Parameters { get; }
public override IReadOnlyList<ComponentCommandParameterInfo> Parameters { get; }


/// <inheritdoc/> /// <inheritdoc/>
public override bool SupportsWildCards => true; public override bool SupportsWildCards => true;
@@ -25,48 +25,32 @@ namespace Discord.Interactions


/// <inheritdoc/> /// <inheritdoc/>
public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
=> await ExecuteAsync(context, services, null).ConfigureAwait(false);

/// <summary>
/// Execute this command using dependency injection.
/// </summary>
/// <param name="context">Context that will be injected to the <see cref="InteractionModuleBase{T}"/>.</param>
/// <param name="services">Services that will be used while initializing the <see cref="InteractionModuleBase{T}"/>.</param>
/// <param name="additionalArgs">Provide additional string parameters to the method along with the auto generated parameters.</param>
/// <returns>
/// A task representing the asynchronous command execution process.
/// </returns>
public async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services, params string[] additionalArgs)
{ {
if (context.Interaction is not IComponentInteraction componentInteraction)
if (context.Interaction is not IComponentInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction");


return await ExecuteAsync(context, Parameters, additionalArgs, componentInteraction.Data, services);
return await base.ExecuteAsync(context, services).ConfigureAwait(false);
} }


/// <inheritdoc/>
public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<string> wildcardCaptures, IComponentInteractionData data,
IServiceProvider services)
protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{ {
var paramCount = paramList.Count();
var captureCount = wildcardCaptures?.Count() ?? 0;

if (context.Interaction is not IComponentInteraction messageComponent)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Component Command Interaction");
var captures = (context as IRouteMatchContainer)?.SegmentMatches?.ToList();
var captureCount = captures?.Count() ?? 0;


try try
{ {
var args = new object[paramCount];
var data = (context.Interaction as IComponentInteraction).Data;
var args = new object[Parameters.Count];


for (var i = 0; i < paramCount; i++)
for(var i = 0; i < Parameters.Count; i++)
{ {
var parameter = Parameters.ElementAt(i);
var parameter = Parameters[i];
var isCapture = i < captureCount; var isCapture = i < captureCount;


if (isCapture ^ parameter.IsRouteSegmentParameter) if (isCapture ^ parameter.IsRouteSegmentParameter)
return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Argument type and parameter type didn't match (Wild Card capture/Component value)")).ConfigureAwait(false); return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Argument type and parameter type didn't match (Wild Card capture/Component value)")).ConfigureAwait(false);


var readResult = isCapture ? await parameter.TypeReader.ReadAsync(context, wildcardCaptures.ElementAt(i), services).ConfigureAwait(false) :
var readResult = isCapture ? await parameter.TypeReader.ReadAsync(context, captures[i].Value, services).ConfigureAwait(false) :
await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false); await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false);


if (!readResult.IsSuccess) if (!readResult.IsSuccess)
@@ -75,7 +59,7 @@ namespace Discord.Interactions
args[i] = readResult.Value; args[i] = readResult.Value;
} }


return await RunAsync(context, args, services).ConfigureAwait(false);
return ParseResult.FromSuccess(args);
} }
catch (Exception ex) catch (Exception ex)
{ {


+ 1
- 1
src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs View File

@@ -24,7 +24,7 @@ namespace Discord.Interactions
public GuildPermission? DefaultMemberPermissions { get; } public GuildPermission? DefaultMemberPermissions { get; }


/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; }
public override IReadOnlyList<CommandParameterInfo> Parameters { get; }


/// <inheritdoc/> /// <inheritdoc/>
public override bool SupportsWildCards => false; public override bool SupportsWildCards => false;


+ 9
- 4
src/Discord.Net.Interactions/Info/Commands/ContextCommands/MessageCommandInfo.cs View File

@@ -14,18 +14,23 @@ namespace Discord.Interactions
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
{ {
if (context.Interaction is not IMessageCommandInteraction messageCommand)
if (context.Interaction is not IMessageCommandInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation");


return await base.ExecuteAsync(context, services).ConfigureAwait(false);
}

protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{
try try
{ {
object[] args = new object[1] { messageCommand.Data.Message };
object[] args = new object[1] { (context.Interaction as IMessageCommandInteraction).Data.Message };


return await RunAsync(context, args, services).ConfigureAwait(false);
return Task.FromResult(ParseResult.FromSuccess(args) as IResult);
} }
catch (Exception ex) catch (Exception ex)
{ {
return ExecuteResult.FromError(ex);
return Task.FromResult(ParseResult.FromError(ex) as IResult);
} }
} }




+ 8
- 3
src/Discord.Net.Interactions/Info/Commands/ContextCommands/UserCommandInfo.cs View File

@@ -17,15 +17,20 @@ namespace Discord.Interactions
if (context.Interaction is not IUserCommandInteraction userCommand) if (context.Interaction is not IUserCommandInteraction userCommand)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation");


return await base.ExecuteAsync(context, services).ConfigureAwait(false);
}

protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{
try try
{ {
object[] args = new object[1] { userCommand.Data.User };
object[] args = new object[1] { (context.Interaction as IUserCommandInteraction).Data.User };


return await RunAsync(context, args, services).ConfigureAwait(false);
return Task.FromResult(ParseResult.FromSuccess(args) as IResult);
} }
catch (Exception ex) catch (Exception ex)
{ {
return ExecuteResult.FromError(ex);
return Task.FromResult(ParseResult.FromError(ex) as IResult);
} }
} }




+ 16
- 21
src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.Tracing;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Discord.Interactions namespace Discord.Interactions
@@ -20,7 +19,7 @@ namespace Discord.Interactions
public override bool SupportsWildCards => true; public override bool SupportsWildCards => true;


/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<ModalCommandParameterInfo> Parameters { get; }
public override IReadOnlyList<ModalCommandParameterInfo> Parameters { get; }


internal ModalCommandInfo(Builders.ModalCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) internal ModalCommandInfo(Builders.ModalCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService)
{ {
@@ -30,34 +29,29 @@ namespace Discord.Interactions


/// <inheritdoc/> /// <inheritdoc/>
public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
=> await ExecuteAsync(context, services, null).ConfigureAwait(false);

/// <summary>
/// Execute this command using dependency injection.
/// </summary>
/// <param name="context">Context that will be injected to the <see cref="InteractionModuleBase{T}"/>.</param>
/// <param name="services">Services that will be used while initializing the <see cref="InteractionModuleBase{T}"/>.</param>
/// <param name="additionalArgs">Provide additional string parameters to the method along with the auto generated parameters.</param>
/// <returns>
/// A task representing the asynchronous command execution process.
/// </returns>
public async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services, params string[] additionalArgs)
{ {
if (context.Interaction is not IModalInteraction modalInteraction) if (context.Interaction is not IModalInteraction modalInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Modal Interaction."); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Modal Interaction.");


return await base.ExecuteAsync(context, services).ConfigureAwait(false);
}

protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{
var captures = (context as IRouteMatchContainer)?.SegmentMatches?.ToList();
var captureCount = captures?.Count() ?? 0;

try try
{ {
var args = new object[Parameters.Count]; var args = new object[Parameters.Count];
var captureCount = additionalArgs?.Length ?? 0;


for(var i = 0; i < Parameters.Count; i++)
for (var i = 0; i < Parameters.Count; i++)
{ {
var parameter = Parameters.ElementAt(i); var parameter = Parameters.ElementAt(i);


if(i < captureCount)
if (i < captureCount)
{ {
var readResult = await parameter.TypeReader.ReadAsync(context, additionalArgs[i], services).ConfigureAwait(false);
var readResult = await parameter.TypeReader.ReadAsync(context, captures[i].Value, services).ConfigureAwait(false);
if (!readResult.IsSuccess) if (!readResult.IsSuccess)
return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false); return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false);


@@ -69,13 +63,14 @@ namespace Discord.Interactions
if (!modalResult.IsSuccess) if (!modalResult.IsSuccess)
return await InvokeEventAndReturn(context, modalResult).ConfigureAwait(false); return await InvokeEventAndReturn(context, modalResult).ConfigureAwait(false);


if (modalResult is not ParseResult parseResult)
if (modalResult is not TypeConverterResult converterResult)
return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.")); return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."));


args[i] = parseResult.Value;
args[i] = converterResult.Value;
} }
} }
return await RunAsync(context, args, services);

return ParseResult.FromSuccess(args);
} }
catch (Exception ex) catch (Exception ex)
{ {


+ 34
- 41
src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs View File

@@ -33,7 +33,7 @@ namespace Discord.Interactions
public GuildPermission? DefaultMemberPermissions { get; } public GuildPermission? DefaultMemberPermissions { get; }


/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<SlashCommandParameterInfo> Parameters { get; }
public override IReadOnlyList<SlashCommandParameterInfo> Parameters { get; }


/// <inheritdoc/> /// <inheritdoc/>
public override bool SupportsWildCards => false; public override bool SupportsWildCards => false;
@@ -41,9 +41,9 @@ namespace Discord.Interactions
/// <summary> /// <summary>
/// Gets the flattened collection of command parameters and complex parameter fields. /// Gets the flattened collection of command parameters and complex parameter fields.
/// </summary> /// </summary>
public IReadOnlyCollection<SlashCommandParameterInfo> FlattenedParameters { get; }
public IReadOnlyList<SlashCommandParameterInfo> FlattenedParameters { get; }


internal SlashCommandInfo (Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService)
internal SlashCommandInfo(Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService)
{ {
Description = builder.Description; Description = builder.Description;
DefaultPermission = builder.DefaultPermission; DefaultPermission = builder.DefaultPermission;
@@ -60,49 +60,45 @@ namespace Discord.Interactions
} }


/// <inheritdoc/> /// <inheritdoc/>
public override async Task<IResult> ExecuteAsync (IInteractionContext context, IServiceProvider services)
public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
{ {
if(context.Interaction is not ISlashCommandInteraction slashCommand)
if (context.Interaction is not ISlashCommandInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Slash Command Interaction"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Slash Command Interaction");


var options = slashCommand.Data.Options;

while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup))
options = options.ElementAt(0)?.Options;

return await ExecuteAsync(context, Parameters, options?.ToList(), services);
return await base.ExecuteAsync(context, services);
} }


private async Task<IResult> ExecuteAsync (IInteractionContext context, IEnumerable<SlashCommandParameterInfo> paramList,
List<IApplicationCommandInteractionDataOption> argList, IServiceProvider services)
protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{ {
try
List<IApplicationCommandInteractionDataOption> GetOptions()
{ {
var slashCommandParameterInfos = paramList.ToList();
var args = new object[slashCommandParameterInfos.Count];

for (var i = 0; i < slashCommandParameterInfos.Count; i++)
{
var parameter = slashCommandParameterInfos[i];
var result = await ParseArgument(parameter, context, argList, services).ConfigureAwait(false);

if (!result.IsSuccess)
return await InvokeEventAndReturn(context, result).ConfigureAwait(false);
var options = (context.Interaction as ISlashCommandInteraction).Data.Options;


if (result is not ParseResult parseResult)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.");
while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup))
options = options.ElementAt(0)?.Options;


args[i] = parseResult.Value;
}
return await RunAsync(context, args, services).ConfigureAwait(false);
return options.ToList();
} }
catch(Exception ex)

var options = GetOptions();
var args = new object[Parameters.Count];
for(var i = 0; i < Parameters.Count; i++)
{ {
return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false);
var parameter = Parameters[i];
var result = await ParseArgumentAsync(parameter, context, options, services).ConfigureAwait(false);

if (!result.IsSuccess)
return await InvokeEventAndReturn(context, ParseResult.FromError(result)).ConfigureAwait(false);

if (result is not TypeConverterResult converterResult)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason.");

args[i] = converterResult.Value;
} }
return ParseResult.FromSuccess(args);
} }


private async Task<IResult> ParseArgument(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> argList,
private async ValueTask<IResult> ParseArgumentAsync(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> argList,
IServiceProvider services) IServiceProvider services)
{ {
if (parameterInfo.IsComplexParameter) if (parameterInfo.IsComplexParameter)
@@ -111,32 +107,29 @@ namespace Discord.Interactions


for (var i = 0; i < ctorArgs.Length; i++) for (var i = 0; i < ctorArgs.Length; i++)
{ {
var result = await ParseArgument(parameterInfo.ComplexParameterFields.ElementAt(i), context, argList, services).ConfigureAwait(false);
var result = await ParseArgumentAsync(parameterInfo.ComplexParameterFields.ElementAt(i), context, argList, services).ConfigureAwait(false);


if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;


if (result is not ParseResult parseResult)
if (result is not TypeConverterResult converterResult)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason.");


ctorArgs[i] = parseResult.Value;
ctorArgs[i] = converterResult.Value;
} }


return ParseResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs));
return TypeConverterResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs));
} }


var arg = argList?.Find(x => string.Equals(x.Name, parameterInfo.Name, StringComparison.OrdinalIgnoreCase)); var arg = argList?.Find(x => string.Equals(x.Name, parameterInfo.Name, StringComparison.OrdinalIgnoreCase));


if (arg == default) if (arg == default)
return parameterInfo.IsRequired ? ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters") : return parameterInfo.IsRequired ? ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters") :
ParseResult.FromSuccess(parameterInfo.DefaultValue);
TypeConverterResult.FromSuccess(parameterInfo.DefaultValue);


var typeConverter = parameterInfo.TypeConverter; var typeConverter = parameterInfo.TypeConverter;
var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false);
if (!readResult.IsSuccess)
return readResult;

return ParseResult.FromSuccess(readResult.Value);
return readResult;
} }


protected override Task InvokeModuleEvent (IInteractionContext context, IResult result) protected override Task InvokeModuleEvent (IInteractionContext context, IResult result)


+ 3
- 3
src/Discord.Net.Interactions/Info/ModalInfo.cs View File

@@ -103,7 +103,7 @@ namespace Discord.Interactions
public async Task<IResult> CreateModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false) public async Task<IResult> CreateModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false)
{ {
if (context.Interaction is not IModalInteraction modalInteraction) if (context.Interaction is not IModalInteraction modalInteraction)
return ParseResult.FromError(InteractionCommandError.Unsuccessful, "Provided context doesn't belong to a Modal Interaction.");
return TypeConverterResult.FromError(InteractionCommandError.Unsuccessful, "Provided context doesn't belong to a Modal Interaction.");


services ??= EmptyServiceProvider.Instance; services ??= EmptyServiceProvider.Instance;


@@ -120,7 +120,7 @@ namespace Discord.Interactions
if (!throwOnMissingField) if (!throwOnMissingField)
args[i] = input.DefaultValue; args[i] = input.DefaultValue;
else else
return ParseResult.FromError(InteractionCommandError.BadArgs, $"Modal interaction is missing the required field: {input.CustomId}");
return TypeConverterResult.FromError(InteractionCommandError.BadArgs, $"Modal interaction is missing the required field: {input.CustomId}");
} }
else else
{ {
@@ -133,7 +133,7 @@ namespace Discord.Interactions
} }
} }


return ParseResult.FromSuccess(_initializer(args));
return TypeConverterResult.FromSuccess(_initializer(args));
} }
} }
} }

+ 2
- 2
src/Discord.Net.Interactions/InteractionService.cs View File

@@ -822,7 +822,7 @@ namespace Discord.Interactions


SetMatchesIfApplicable(context, result); SetMatchesIfApplicable(context, result);


return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false);
return await result.Command.ExecuteAsync(context, services).ConfigureAwait(false);
} }


private async Task<IResult> ExecuteAutocompleteAsync (IInteractionContext context, IAutocompleteInteraction interaction, IServiceProvider services ) private async Task<IResult> ExecuteAutocompleteAsync (IInteractionContext context, IAutocompleteInteraction interaction, IServiceProvider services )
@@ -869,7 +869,7 @@ namespace Discord.Interactions


SetMatchesIfApplicable(context, result); SetMatchesIfApplicable(context, result);


return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false);
return await result.Command.ExecuteAsync(context, services).ConfigureAwait(false);
} }


private static void SetMatchesIfApplicable<T>(IInteractionContext context, SearchResult<T> searchResult) private static void SetMatchesIfApplicable<T>(IInteractionContext context, SearchResult<T> searchResult)


+ 6
- 6
src/Discord.Net.Interactions/Results/ParseResult.cs View File

@@ -2,9 +2,9 @@ using System;


namespace Discord.Interactions namespace Discord.Interactions
{ {
internal struct ParseResult : IResult
public struct ParseResult : IResult
{ {
public object Value { get; }
public object[] Args { get; }


public InteractionCommandError? Error { get; } public InteractionCommandError? Error { get; }


@@ -12,15 +12,15 @@ namespace Discord.Interactions


public bool IsSuccess => !Error.HasValue; public bool IsSuccess => !Error.HasValue;


private ParseResult(object value, InteractionCommandError? error, string reason)
private ParseResult(object[] args, InteractionCommandError? error, string reason)
{ {
Value = value;
Args = args;
Error = error; Error = error;
ErrorReason = reason; ErrorReason = reason;
} }


public static ParseResult FromSuccess(object value) =>
new ParseResult(value, null, null);
public static ParseResult FromSuccess(object[] args) =>
new ParseResult(args, null, null);


public static ParseResult FromError(Exception exception) => public static ParseResult FromError(Exception exception) =>
new ParseResult(null, InteractionCommandError.Exception, exception.Message); new ParseResult(null, InteractionCommandError.Exception, exception.Message);


Loading…
Cancel
Save