| @@ -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); | |||
| @@ -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/> | |||
| @@ -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; | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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); | |||
| @@ -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)]; | |||
| @@ -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()); | |||
| @@ -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()); | |||
| @@ -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) | |||
| @@ -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()); | |||
| } | |||
| @@ -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(); | |||