description: If your issue is related to an exception make sure the error was thrown by Discord.Net, and not your code or another library.
If you get an `HttpException` with the error code `401`, then the error is caused by your bot's permissions, not dnet.
If you get an `HttpException` with the error code `401`, then the error is caused by your bot's permissions, not dnet.
If you have a issue that does directly relate to an API bug, feel free to open a [Q&A Discussion](https://github.com/discord-net/Discord.Net/discussions)
options:
- label: I verified the issue was caused by Discord.Net.
required: true
@@ -75,3 +76,11 @@ body:
```
validations:
required: false
- type: textarea
id: packages
attributes:
label: Packages
description: Please list all 3rd party packages in use if applicable, including their versions.
placeholder: Discord.Addons.Hosting V5.1.0, Discord.InteractivityAddon V2.4.0, etc.
## `InteractionService.ExecuteAsync()` always returns a successful result, how do i access the failed command execution results?
If you are using `RunMode.Async` you need to setup your post-execution pipeline around `CommandExecuted` events.
If you are using `RunMode.Async` you need to setup your post-execution pipeline around
`..Executed` events exposed by the Interaction Service.
## How do I check if the executing user has * permission?
Refer to the [documentation about preconditions]
[documentation about preconditions]: xref:Guides.ChatCommands.Preconditions
## How do I send the HTTP Response from inside the command modules.
Set the `RestResponseCallback` property of [InteractionServiceConfig] with a delegate for handling HTTP Responses and use
`RestInteractionModuleBase` to create your command modules. `RespondAsync()` and `DeferAsync()` methods of this module base will use the
`RestInteractionModuleBase` to create your command modules. `RespondWithModalAsync()`, `RespondAsync()` and `DeferAsync()` methods of this module base will use the
`RestResponseCallback` to create interaction responses.
## Is there a cleaner way of creating parameter choices other than using `[Choice]`?
@@ -49,4 +69,3 @@ It compares the _target base type_ key of the
This section answers basic questions and common mistakes in handling application commands, and responding to them.
This chapter mostly refers to interactions in general,
and will include questions that are common among users of the Interaction Framework
as well as users that register and handle commands manually.
## What's the difference between RespondAsync, DeferAsync and FollowupAsync?
@@ -24,33 +26,20 @@ DeferAsync will not send out a response, RespondAsync will.
## Im getting System.TimeoutException: 'Cannot respond to an interaction after 3 seconds!'
This happens because your computers clock is out of sync or your trying to respond after 3 seconds. If your clock is out of sync and you cant fix it, you can set the `UseInteractionSnowflakeDate` to false in the config.
This happens because your computer's clock is out of sync or you're trying to respond after 3 seconds.
If your clock is out of sync and you can't fix it, you can set the `UseInteractionSnowflakeDate` to false in the [DiscordSocketConfig].
## Bad form Exception when I try to create my commands, why do I get this?
@@ -143,6 +143,21 @@ In this case, user can only input Stage Channels and Text Channels to this param
You can specify the permitted max/min value for a number type parameter using the [MaxValueAttribute] and [MinValueAttribute].
#### Complex Parameters
This allows users to create slash command options using an object's constructor allowing complex objects to be created which cannot be infered from only one input value.
Constructor methods support every attribute type that can be used with the regular slash commands ([Autocomplete], [Summary] etc. ).
Preferred constructor of a Type can be specified either by passing a `Type[]` to the `[ComplexParameterAttribute]` or tagging a type constructor with the `[ComplexParameterCtorAttribute]`. If nothing is specified, the InteractionService defaults to the only public constructor of the type.
TypeConverter pattern is used to parse the constructor methods objects.
To use EFCore, you need a DbContext to access everything in your database. The DbContext will look like this. Here is an example entity to show you how you can add more entities yourself later on.
> To learn more about creating the EFCore model, visit the following [link](https://docs.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli#create-the-model)
## Adding the DbContext to your Dependency Injection container
To add your newly created DbContext to your Dependency Injection container, simply use the extension method provided by EFCore to add the context to your container. It should look something like this
> You can find out how to get your connection string [here](https://www.connectionstrings.com/npgsql/standard/)
## Migrations
Before you can start using your DbContext, you have to migrate the changes you've made in your code to your actual database.
To learn more about migrations, visit the official Microsoft documentation [here](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli)
## Using the DbContext
You can now use the DbContext wherever you can inject it. Here's an example on injecting it into an interaction command module.
[!code-csharp[DBContext injected into interaction module](samples/InteractionModuleDISample.cs)]
## Using a different database provider
Here's a couple of popular database providers for EFCore and links to tutorials on how to set them up. The only thing that usually changes is the provider inside of your `DbContextOptions`
| Provider | Link |
|--|--|
| MySQL | [link](https://dev.mysql.com/doc/connector-net/en/connector-net-entityframework-core-example.html) |
For Serilog to log Discord events correctly, we have to map the Discord `LogSeverity` to the Serilog `LogEventLevel`. You can modify your log method to look like this.
[!code-csharp[Modifying your log method](samples/ModifyLogMethod.cs)]
## Testing
If you run your application now, you should see something similar to this

## Using your new logger in other places
Now that you have set up Serilog, you can use it everywhere in your application by simply calling
> Depending on your configured log level, the log messages may or may not show up in your console. Refer to [Serilog's github page](https://github.com/serilog/serilog/wiki/Configuration-Basics#minimum-level) for more information about log levels.
throw new InvalidOperationException($"No constructor found for \"{typeInfo.FullName}\".");
if (complexParameter.PrioritizedCtorSignature is not null)
{
var ctor = typeInfo.GetConstructor(complexParameter.PrioritizedCtorSignature);
if (ctor is null)
throw new InvalidOperationException($"No constructor was found with the signature: {string.Join(",", complexParameter.PrioritizedCtorSignature.Select(x => x.Name))}");
return ctor;
}
var prioritizedCtors = ctors.Where(x => x.IsDefined(typeof(ComplexParameterCtorAttribute), true));
switch (prioritizedCtors.Count())
{
case > 1:
throw new InvalidOperationException($"{nameof(ComplexParameterCtorAttribute)} can only be used once in a type.");
case 1:
return prioritizedCtors.First();
}
switch (ctors.Length)
{
case > 1:
throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\".");
/// Represents a builder for creating <see cref="ComponentCommandParameterInfo"/>.
/// </summary>
public class ComponentCommandParameterBuilder : ParameterBuilder<ComponentCommandParameterInfo, ComponentCommandParameterBuilder>
{
/// <summary>
/// Get the <see cref="ComponentTypeConverter"/> assigned to this parameter, if <see cref="IsRouteSegmentParameter"/> is <see langword="false"/>.
/// </summary>
public ComponentTypeConverter TypeConverter { get; private set; }
/// <summary>
/// Get the <see cref="Discord.Interactions.TypeReader"/> assigned to this parameter, if <see cref="IsRouteSegmentParameter"/> is <see langword="true"/>.
/// </summary>
public TypeReader TypeReader { get; private set; }
/// <summary>
/// Gets whether this parameter is a CustomId segment or a Component value parameter.
/// </summary>
public bool IsRouteSegmentParameter { get; private set; }
/// <param name="type">New value of the <see cref="ParameterBuilder{TInfo, TBuilder}.ParameterType"/>.</param>
/// <param name="services">Service container to be used to resolve the dependencies of this parameters <see cref="Interactions.TypeConverter"/>.</param>
/// <returns>
/// The builder instance.
/// </returns>
public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services)
if (context.Interaction is not IAutocompleteInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction");
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");
try
{
var strCount = Parameters.Count(x => x.ParameterType == typeof(string));
var args = new object[paramCount];
for (var i = 0; i < paramCount; i++)
{
var parameter = Parameters.ElementAt(i);
var isCapture = i < captureCount;
if (strCount > values?.Count())
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters");
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);
var componentValues = messageComponent.Data?.Values;
var readResult = isCapture ? await parameter.TypeReader.ReadAsync(context, wildcardCaptures.ElementAt(i), services).ConfigureAwait(false) :
return ExecuteResult.FromError(InteractionCommandError.BadArgs, $"Select Menu Interaction handlers must accept a {typeof(string[]).FullName} as its last parameter");
for (var i = 0; i < FlattenedParameters.Count - 1; i++)
if (!FlattenedParameters.ElementAt(i).IsRequired && FlattenedParameters.ElementAt(i + 1).IsRequired)
throw new InvalidOperationException("Optional parameters must appear after all required parameters, ComplexParameters with optional parameters must be located at the end.");
_flattenedParameterDictionary = FlattenedParameters?.ToDictionary(x => x.Name, x => x).ToImmutableDictionary();
/// 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>
var parameter = autocompleteHandlerResult.Command.Parameters.FirstOrDefault(x => string.Equals(x.Name, interaction.Data.Current.Name, StringComparison.Ordinal));
if(parameter?.AutocompleteHandler is not null)
if (autocompleteHandlerResult.Command._flattenedParameterDictionary.TryGetValue(interaction.Data.Current.Name, out var parameter) && parameter?.AutocompleteHandler is not null)
public void AddTypeConverter (Type type, TypeConverter converter)
{
if (!converter.CanConvertTo(type))
throw new ArgumentException($"This {converter.GetType().FullName} cannot read {type.FullName} and cannot be registered as its {nameof(TypeConverter)}");
_typeConverters[type] = converter;
}
public void AddTypeConverter(Type type, TypeConverter converter) =>
_typeConverterMap.AddConcrete(type, converter);
/// <summary>
/// Add a generic type <see cref="TypeConverter{T}"/>.
/// Gets or sets the string expression that will be treated as a wild card.
/// </summary>
public string WildCardExpression { get; set; }
public string WildCardExpression { get; set; } = "*";
/// <summary>
/// Gets or sets the option to use compiled lambda expressions to create module instances and execute commands. This method improves performance at the cost of memory.
throw new ArgumentException($"No type {typeof(TConverter).Name} is defined for this {type.FullName}", nameof(type));
}
public void AddConcrete<TTarget>(TConverter converter) =>
AddConcrete(typeof(TTarget), converter);
public void AddConcrete(Type type, TConverter converter)
{
if (!converter.CanConvertTo(type))
throw new ArgumentException($"This {converter.GetType().FullName} cannot read {type.FullName} and cannot be registered as its {nameof(TypeConverter)}");
_concretes[type] = converter;
}
public void AddGeneric<TTarget>(Type converterType) =>
AddGeneric(typeof(TTarget), converterType);
public void AddGeneric(Type targetType, Type converterType)
{
if (!converterType.IsGenericTypeDefinition)
throw new ArgumentException($"{converterType.FullName} is not generic.");
var genericArguments = converterType.GetGenericArguments();
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());
if (!constraints.Any(x => x.IsAssignableFrom(targetType)))
throw new InvalidOperationException($"This generic class does not support type {targetType.FullName}");
_generics[targetType] = converterType;
}
private Type GetMostSpecific(Type type)
{
if (_generics.TryGetValue(type, out var matching))
return matching;
if (type.IsGenericType && _generics.TryGetValue(type.GetGenericTypeDefinition(), out var genericDefinition))
return genericDefinition;
var typeInterfaces = type.GetInterfaces();
var candidates = _generics.Where(x => x.Key.IsAssignableFrom(type))
public override async Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services)
{
if (!ulong.TryParse(option, out var snowflake))
return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} isn't a valid snowflake thus cannot be converted into {typeof(T).Name}");
var result = await GetEntity(snowflake, context).ConfigureAwait(false);
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, IServiceProvider services) => Task.FromResult((obj as ISnowflakeEntity)?.Id.ToString());
}
internal sealed class DefaultUserReader<T> : DefaultSnowflakeReader<T>
internal sealed class EnumReader<T> : TypeReader<T>
where T : struct, Enum
{
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services)
{
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, IServiceProvider services)
{
var name = Enum.GetName(typeof(T), obj);
if (name is null)
throw new ArgumentException($"Enum name cannot be parsed from {obj}");
Thank you for your continuous support to the Openl Qizhi Community AI Collaboration Platform. In order to protect your usage rights and ensure network security, we updated the Openl Qizhi Community AI Collaboration Platform Usage Agreement in January 2024. The updated agreement specifies that users are prohibited from using intranet penetration tools. After you click "Agree and continue", you can continue to use our services. Thank you for your cooperation and understanding.