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>
This commit is contained in:
@@ -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);
|
public virtual async 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)
|
|
||||||
{
|
{
|
||||||
switch (RunMode)
|
switch (RunMode)
|
||||||
{
|
{
|
||||||
case RunMode.Sync:
|
case RunMode.Sync:
|
||||||
{
|
return await ExecuteInternalAsync(context, services).ConfigureAwait(false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
case RunMode.Async:
|
case RunMode.Async:
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
if (CommandService._autoServiceScopes)
|
await ExecuteInternalAsync(context, services).ConfigureAwait(false);
|
||||||
{
|
|
||||||
using var scope = services?.CreateScope();
|
|
||||||
await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
await ExecuteInternalAsync(context, args, 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/>
|
protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
|
||||||
public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<string> wildcardCaptures, IComponentInteractionData data,
|
|
||||||
IServiceProvider services)
|
|
||||||
{
|
{
|
||||||
var paramCount = paramList.Count();
|
var captures = (context as IRouteMatchContainer)?.SegmentMatches?.ToList();
|
||||||
var captureCount = wildcardCaptures?.Count() ?? 0;
|
var captureCount = captures?.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");
|
|
||||||
|
|
||||||
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;
|
return await base.ExecuteAsync(context, services);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
|
||||||
|
{
|
||||||
|
List<IApplicationCommandInteractionDataOption> GetOptions()
|
||||||
|
{
|
||||||
|
var options = (context.Interaction as ISlashCommandInteraction).Data.Options;
|
||||||
|
|
||||||
while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup))
|
while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup))
|
||||||
options = options.ElementAt(0)?.Options;
|
options = options.ElementAt(0)?.Options;
|
||||||
|
|
||||||
return await ExecuteAsync(context, Parameters, options?.ToList(), services);
|
return options.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IResult> ExecuteAsync (IInteractionContext context, IEnumerable<SlashCommandParameterInfo> paramList,
|
var options = GetOptions();
|
||||||
List<IApplicationCommandInteractionDataOption> argList, IServiceProvider services)
|
var args = new object[Parameters.Count];
|
||||||
|
for(var i = 0; i < Parameters.Count; i++)
|
||||||
{
|
{
|
||||||
try
|
var parameter = Parameters[i];
|
||||||
{
|
var result = await ParseArgumentAsync(parameter, context, options, services).ConfigureAwait(false);
|
||||||
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)
|
if (!result.IsSuccess)
|
||||||
return await InvokeEventAndReturn(context, result).ConfigureAwait(false);
|
return await InvokeEventAndReturn(context, ParseResult.FromError(result)).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result is not ParseResult parseResult)
|
if (result is not TypeConverterResult converterResult)
|
||||||
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.");
|
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason.");
|
||||||
|
|
||||||
args[i] = parseResult.Value;
|
args[i] = converterResult.Value;
|
||||||
}
|
|
||||||
return await RunAsync(context, args, services).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch(Exception ex)
|
|
||||||
{
|
|
||||||
return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
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 readResult;
|
||||||
|
|
||||||
return ParseResult.FromSuccess(readResult.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) =>
|
public static ParseResult FromSuccess(object[] args) =>
|
||||||
new ParseResult(value, null, null);
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user