Browse Source

code cleanup and refactorings

pull/2169/head
Cenk Ergen 3 years ago
parent
commit
3e3475b5fb
12 changed files with 113 additions and 126 deletions
  1. +1
    -1
      src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs
  2. +1
    -8
      src/Discord.Net.Interactions/Info/Commands/AutocompleteCommandInfo.cs
  3. +16
    -21
      src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs
  4. +11
    -19
      src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs
  5. +10
    -18
      src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs
  6. +51
    -32
      src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs
  7. +10
    -10
      src/Discord.Net.Interactions/InteractionService.cs
  8. +5
    -5
      src/Discord.Net.Interactions/Map/TypeMap.cs
  9. +2
    -4
      src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs
  10. +2
    -4
      src/Discord.Net.Interactions/TypeReaders/EnumReader.cs
  11. +3
    -3
      src/Discord.Net.Interactions/TypeReaders/TypeReader.cs
  12. +1
    -1
      src/Discord.Net.Interactions/Utilities/ModalUtils.cs

+ 1
- 1
src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs View File

@@ -43,7 +43,7 @@ namespace Discord.Interactions.Builders
/// <returns>
/// The builder instance.
/// </returns>
public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null)
public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services)
{
base.SetParameterType(type);



+ 1
- 8
src/Discord.Net.Interactions/Info/Commands/AutocompleteCommandInfo.cs View File

@@ -41,14 +41,7 @@ namespace Discord.Interactions
if (context.Interaction is not IAutocompleteInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction");

try
{
return await RunAsync(context, Array.Empty<object>(), services).ConfigureAwait(false);
}
catch (Exception ex)
{
return ExecuteResult.FromError(ex);
}
return await RunAsync(context, Array.Empty<object>(), services).ConfigureAwait(false);
}

/// <inheritdoc/>


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

@@ -31,6 +31,8 @@ namespace Discord.Interactions
private readonly ExecuteCallback _action;
private readonly ILookup<string, PreconditionAttribute> _groupedPreconditions;

internal IReadOnlyDictionary<string, TParameter> _parameterDictionary;

/// <inheritdoc/>
public ModuleInfo Module { get; }

@@ -120,10 +122,7 @@ namespace Discord.Interactions
return moduleResult;

var commandResult = await CheckGroups(_groupedPreconditions, "Command").ConfigureAwait(false);
if (!commandResult.IsSuccess)
return commandResult;

return PreconditionResult.FromSuccess();
return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess();
}

protected async Task<IResult> RunAsync(IInteractionContext context, object[] args, IServiceProvider services)
@@ -137,8 +136,8 @@ namespace Discord.Interactions
using var scope = services?.CreateScope();
return await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false);
}
else
return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
}
case RunMode.Async:
_ = Task.Run(async () =>
@@ -167,20 +166,14 @@ namespace Discord.Interactions
{
var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false);
if (!preconditionResult.IsSuccess)
{
await InvokeModuleEvent(context, preconditionResult).ConfigureAwait(false);
return preconditionResult;
}
return await InvokeEventAndReturn(context, preconditionResult).ConfigureAwait(false);

var index = 0;
foreach (var parameter in Parameters)
{
var result = await parameter.CheckPreconditionsAsync(context, args[index++], services).ConfigureAwait(false);
if (!result.IsSuccess)
{
await InvokeModuleEvent(context, result).ConfigureAwait(false);
return result;
}
return await InvokeEventAndReturn(context, result).ConfigureAwait(false);
}

var task = _action(context, args, services, this);
@@ -189,20 +182,16 @@ namespace Discord.Interactions
{
var result = await resultTask.ConfigureAwait(false);
await InvokeModuleEvent(context, result).ConfigureAwait(false);
if (result is RuntimeResult || result is ExecuteResult)
if (result is RuntimeResult or ExecuteResult)
return result;
}
else
{
await task.ConfigureAwait(false);
var result = ExecuteResult.FromSuccess();
await InvokeModuleEvent(context, result).ConfigureAwait(false);
return result;
return await InvokeEventAndReturn(context, ExecuteResult.FromSuccess()).ConfigureAwait(false);
}

var failResult = ExecuteResult.FromError(InteractionCommandError.Unsuccessful, "Command execution failed for an unknown reason");
await InvokeModuleEvent(context, failResult).ConfigureAwait(false);
return failResult;
return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.Unsuccessful, "Command execution failed for an unknown reason")).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -231,6 +220,12 @@ namespace Discord.Interactions
}
}

protected async ValueTask<IResult> InvokeEventAndReturn(IInteractionContext context, IResult result)
{
await InvokeModuleEvent(context, result).ConfigureAwait(false);
return result;
}

private static bool CheckTopLevel(ModuleInfo parent)
{
var currentParent = parent;


+ 11
- 19
src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs View File

@@ -48,6 +48,11 @@ namespace Discord.Interactions
public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<string> wildcardCaptures, IComponentInteractionData data,
IServiceProvider services)
{
var paramCount = paramList.Count();
var captureCount = wildcardCaptures?.Count() ?? 0;

if (paramCount < captureCount + 1)
return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too many parameters")).ConfigureAwait(false);
if (context.Interaction is not IComponentInteraction messageComponent)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Component Command Interaction");

@@ -58,27 +63,16 @@ namespace Discord.Interactions
for (var i = 0; i < paramCount; i++)
{
var parameter = Parameters.ElementAt(i);
bool isCapture = i < captureCount;
var isCapture = i < captureCount;

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

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

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

if (!readResult.IsSuccess)
{
await InvokeModuleEvent(context, readResult).ConfigureAwait(false);
return readResult;
}
return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false);

args[i] = readResult.Value;
}
@@ -87,9 +81,7 @@ namespace Discord.Interactions
}
catch (Exception ex)
{
var result = ExecuteResult.FromError(ex);
await InvokeModuleEvent(context, result).ConfigureAwait(false);
return result;
return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false);
}
}



+ 10
- 18
src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Threading.Tasks;
namespace Discord.Interactions
@@ -57,37 +58,28 @@ namespace Discord.Interactions
if(i < captureCount)
{
var readResult = await parameter.TypeReader.ReadAsync(context, additionalArgs[i], services).ConfigureAwait(false);
if (!readResult.IsSuccess)
return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false);

if(!readResult.IsSuccess)
{
await InvokeModuleEvent(context, readResult).ConfigureAwait(false);
return readResult;
}
args[i] = readResult.Value;
}
else
{
var modalResult = await Modal.CreateModalAsync(context, services, Module.CommandService._exitOnMissingModalField).ConfigureAwait(false);

if (!modalResult.IsSuccess)
{
await InvokeModuleEvent(context, modalResult).ConfigureAwait(false);
return modalResult;
}
return await InvokeEventAndReturn(context, modalResult).ConfigureAwait(false);
if (modalResult is not ParseResult parseResult)
return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."));

if (modalResult is ParseResult parseResult)
args[i] = parseResult.Value;
else
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.");
args[i] = parseResult.Value;
}
}
return await RunAsync(context, args.ToArray(), services);
return await RunAsync(context, args, services);
}
catch (Exception ex)
{
var result = ExecuteResult.FromError(ex);
await InvokeModuleEvent(context, result).ConfigureAwait(false);
return result;
return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false);
}
}



+ 51
- 32
src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs View File

@@ -56,48 +56,67 @@ namespace Discord.Interactions
{
try
{
if (paramList?.Count() < argList?.Count())
return ExecuteResult.FromError(InteractionCommandError.BadArgs ,"Command was invoked with too many parameters");
var slashCommandParameterInfos = paramList.ToList();
var args = new object[slashCommandParameterInfos.Count];

var args = new object[paramList.Count()];

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

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

if (arg == default)
{
if (parameter.IsRequired)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters");
else
args[i] = parameter.DefaultValue;
}
else
{
var typeConverter = parameter.TypeConverter;

var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false);

if (!readResult.IsSuccess)
{
await InvokeModuleEvent(context, readResult).ConfigureAwait(false);
return readResult;
}

args[i] = readResult.Value;
}
}
var parameter = slashCommandParameterInfos[i];
var result = await ParseArgument(parameter, context, argList, services).ConfigureAwait(false);

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

if (result is not ParseResult parseResult)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.");

args[i] = parseResult.Value;
}
return await RunAsync(context, args, services).ConfigureAwait(false);
}
catch (Exception ex)
{
return ExecuteResult.FromError(ex);
return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false);
}
}

private async Task<IResult> ParseArgument(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> argList,
IServiceProvider services)
{
if (parameterInfo.IsComplexParameter)
{
var ctorArgs = new object[parameterInfo.ComplexParameterFields.Count];

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

if (!result.IsSuccess)
return result;

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

ctorArgs[i] = parseResult.Value;
}

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

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

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

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

return ParseResult.FromSuccess(readResult.Value);
}

protected override Task InvokeModuleEvent (IInteractionContext context, IResult result)
=> CommandService._slashCommandExecutedEvent.InvokeAsync(this, context, result);



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

@@ -193,7 +193,7 @@ namespace Discord.Interactions
[typeof(IMentionable)] = typeof(DefaultMentionableConverter<>),
[typeof(IConvertible)] = typeof(DefaultValueConverter<>),
[typeof(Enum)] = typeof(EnumConverter<>),
[typeof(Nullable<>)] = typeof(NullableConverter<>),
[typeof(Nullable<>)] = typeof(NullableConverter<>)
});

_compTypeConverterMap = new TypeMap<ComponentTypeConverter, IComponentInteractionData>(this, new ConcurrentDictionary<Type, ComponentTypeConverter>(),
@@ -209,7 +209,7 @@ namespace Discord.Interactions
[typeof(IChannel)] = typeof(DefaultChannelReader<>),
[typeof(IRole)] = typeof(DefaultRoleReader<>),
[typeof(IUser)] = typeof(DefaultUserReader<>),
[typeof(IMessage)] = typeof(DefaultUserReader<>),
[typeof(IMessage)] = typeof(DefaultMessageReader<>),
[typeof(IConvertible)] = typeof(DefaultValueReader<>),
[typeof(Enum)] = typeof(EnumReader<>)
});
@@ -311,7 +311,7 @@ namespace Discord.Interactions
public async Task<ModuleInfo> AddModuleAsync (Type type, IServiceProvider services)
{
if (!typeof(IInteractionModuleBase).IsAssignableFrom(type))
throw new ArgumentException("Type parameter must be a type of Slash Module", "T");
throw new ArgumentException("Type parameter must be a type of Slash Module", nameof(type));

services ??= EmptyServiceProvider.Instance;

@@ -344,7 +344,7 @@ namespace Discord.Interactions
}

/// <summary>
/// Register Application Commands from <see cref="ContextCommands"/> and <see cref="SlashCommands"/> to a guild.
/// Register Application Commands from <see cref="ContextCommands"/> and <see cref="SlashCommands"/> to a guild.
/// </summary>
/// <param name="guildId">Id of the target guild.</param>
/// <param name="deleteMissing">If <see langword="false"/>, this operation will not delete the commands that are missing from <see cref="InteractionService"/>.</param>
@@ -440,7 +440,7 @@ namespace Discord.Interactions
}

/// <summary>
/// Register Application Commands from modules provided in <paramref name="modules"/> to a guild.
/// Register Application Commands from modules provided in <paramref name="modules"/> to a guild.
/// </summary>
/// <param name="guild">The target guild.</param>
/// <param name="modules">Modules to be registered to Discord.</param>
@@ -467,7 +467,7 @@ namespace Discord.Interactions
}

/// <summary>
/// Register Application Commands from modules provided in <paramref name="modules"/> as global commands.
/// Register Application Commands from modules provided in <paramref name="modules"/> as global commands.
/// </summary>
/// <param name="modules">Modules to be registered to Discord.</param>
/// <returns>
@@ -695,7 +695,7 @@ namespace Discord.Interactions
public async Task<IResult> ExecuteCommandAsync (IInteractionContext context, IServiceProvider services)
{
var interaction = context.Interaction;
return interaction switch
{
ISlashCommandInteraction slashCommand => await ExecuteSlashCommandAsync(context, slashCommand, services).ConfigureAwait(false),
@@ -865,7 +865,7 @@ namespace Discord.Interactions
/// Add a concrete type <see cref="TypeReader"/>.
/// </summary>
/// <typeparam name="T">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</typeparam>
/// <param name="converter">The <see cref="TypeReader"/> instance.</param>
/// <param name="reader">The <see cref="TypeReader"/> instance.</param>
public void AddTypeReader<T>(TypeReader reader) =>
AddTypeReader(typeof(T), reader);

@@ -873,7 +873,7 @@ namespace Discord.Interactions
/// Add a concrete type <see cref="TypeReader"/>.
/// </summary>
/// <param name="type">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</param>
/// <param name="converter">The <see cref="TypeReader"/> instance.</param>
/// <param name="reader">The <see cref="TypeReader"/> instance.</param>
public void AddTypeReader(Type type, TypeReader reader) =>
_typeReaderMap.AddConcrete(type, reader);

@@ -1066,7 +1066,7 @@ namespace Discord.Interactions
public ModuleInfo GetModuleInfo<TModule> ( ) where TModule : class
{
if (!typeof(IInteractionModuleBase).IsAssignableFrom(typeof(TModule)))
throw new ArgumentException("Type parameter must be a type of Slash Module", "TModule");
throw new ArgumentException("Type parameter must be a type of Slash Module", nameof(TModule));

var module = _typedModuleDefs[typeof(TModule)];



+ 5
- 5
src/Discord.Net.Interactions/Map/TypeMap.cs View File

@@ -25,8 +25,8 @@ namespace Discord.Interactions
if (_concretes.TryGetValue(type, out var specific))
return specific;

else if (_generics.Any(x => x.Key.IsAssignableFrom(type)
|| (x.Key.IsGenericTypeDefinition && type.IsGenericType && x.Key.GetGenericTypeDefinition() == type.GetGenericTypeDefinition())))
if (_generics.Any(x => x.Key.IsAssignableFrom(type)
|| x.Key.IsGenericTypeDefinition && type.IsGenericType && x.Key.GetGenericTypeDefinition() == type.GetGenericTypeDefinition()))
{
services ??= EmptyServiceProvider.Instance;

@@ -36,10 +36,10 @@ namespace Discord.Interactions
return converter;
}

else if (_concretes.Any(x => x.Value.CanConvertTo(type)))
if (_concretes.Any(x => x.Value.CanConvertTo(type)))
return _concretes.First(x => x.Value.CanConvertTo(type)).Value;

throw new ArgumentException($"No type {typeof(TConverter).Name} is defined for this {type.FullName}", "type");
throw new ArgumentException($"No type {typeof(TConverter).Name} is defined for this {type.FullName}", nameof(type));
}

public void AddConcrete<TTarget>(TConverter converter) =>
@@ -63,7 +63,7 @@ namespace Discord.Interactions

var genericArguments = converterType.GetGenericArguments();

if (genericArguments.Count() > 1)
if (genericArguments.Length > 1)
throw new InvalidOperationException($"Valid generic {converterType.FullName}s cannot have more than 1 generic type parameter");

var constraints = genericArguments.SelectMany(x => x.GetGenericParameterConstraints());


+ 2
- 4
src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs View File

@@ -15,10 +15,8 @@ namespace Discord.Interactions

var result = await GetEntity(snowflake, context).ConfigureAwait(false);

if (result is not null)
return TypeConverterResult.FromSuccess(result);
else
return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} must be a valid {typeof(T).Name} snowflake to be parsed.");
return result is not null ?
TypeConverterResult.FromSuccess(result) : TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} must be a valid {typeof(T).Name} snowflake to be parsed.");
}

public override Task<string> SerializeAsync(object obj) => Task.FromResult((obj as ISnowflakeEntity)?.Id.ToString());


+ 2
- 4
src/Discord.Net.Interactions/TypeReaders/EnumReader.cs View File

@@ -8,10 +8,8 @@ namespace Discord.Interactions
{
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services)
{
if (Enum.TryParse<T>(option, out var result))
return Task.FromResult(TypeConverterResult.FromSuccess(result));
else
return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {option} cannot be converted to {nameof(T)}"));
return Task.FromResult(Enum.TryParse<T>(option, out var result) ?
TypeConverterResult.FromSuccess(result) : TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {option} cannot be converted to {nameof(T)}"));
}

public override Task<string> SerializeAsync(object obj)


+ 3
- 3
src/Discord.Net.Interactions/TypeReaders/TypeReader.cs View File

@@ -20,8 +20,8 @@ namespace Discord.Interactions
/// <summary>
/// Will be used to read the incoming payload before executing the method body.
/// </summary>
/// <param name="context">Command exexution context.</param>
/// <param name="option">Recieved option payload.</param>
/// <param name="context">Command execution context.</param>
/// <param name="option">Received option payload.</param>
/// <param name="services">Service provider that will be used to initialize the command module.</param>
/// <returns>The result of the read process.</returns>
public abstract Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services);
@@ -31,7 +31,7 @@ namespace Discord.Interactions
/// </summary>
/// <param name="obj">Object to be serialized.</param>
/// <returns>
/// A task represting the conversion process. The result of the task contains the conversion result.
/// A task representing the conversion process. The result of the task contains the conversion result.
/// </returns>
public virtual Task<string> SerializeAsync(object obj) => Task.FromResult(obj.ToString());
}


+ 1
- 1
src/Discord.Net.Interactions/Utilities/ModalUtils.cs View File

@@ -7,7 +7,7 @@ namespace Discord.Interactions
{
internal static class ModalUtils
{
private static ConcurrentDictionary<Type, ModalInfo> _modalInfos = new();
private static readonly ConcurrentDictionary<Type, ModalInfo> _modalInfos = new();

public static IReadOnlyCollection<ModalInfo> Modals => _modalInfos.Values.ToReadOnlyCollection();



Loading…
Cancel
Save