* 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>pull/2520/head
@@ -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); | ||||
@@ -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; | ||||
@@ -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) | ||||
{ | { | ||||
@@ -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; | ||||
@@ -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); | |||||
} | } | ||||
} | } | ||||
@@ -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); | |||||
} | } | ||||
} | } | ||||
@@ -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) | ||||
{ | { | ||||
@@ -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) | ||||
@@ -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)); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -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) | ||||
@@ -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); | ||||