diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
index 74a17d0f2..31d7ede60 100644
--- a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
+++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
@@ -1,1378 +1,1579 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Attributes;
using DisCatSharp.ApplicationCommands.EventArgs;
using DisCatSharp.Common.Utilities;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Exceptions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
namespace DisCatSharp.ApplicationCommands
{
///
/// A class that handles slash commands for a client.
///
public sealed class ApplicationCommandsExtension : BaseExtension
{
///
/// A list of methods for top level commands.
///
private static List _commandMethods { get; set; } = new List();
///
/// List of groups.
///
private static List _groupCommands { get; set; } = new List();
///
/// List of groups with subgroups.
///
private static List _subGroupCommands { get; set; } = new List();
///
/// List of context menus.
///
private static List _contextMenuCommands { get; set; } = new List();
///
/// Singleton modules.
///
private static List _singletonModules { get; set; } = new List();
///
/// List of modules to register.
///
private List> _updateList { get; set; } = new List>();
///
/// Configuration for Discord.
///
private readonly ApplicationCommandsConfiguration _configuration;
///
/// Set to true if anything fails when registering.
///
private static bool _errored { get; set; } = false;
///
/// Gets a list of registered commands. The key is the guild id (null if global).
///
public IReadOnlyList>> RegisteredCommands
=> _registeredCommands;
private static List>> _registeredCommands = new();
///
/// Initializes a new instance of the class.
///
/// The configuration.
internal ApplicationCommandsExtension(ApplicationCommandsConfiguration configuration)
{
this._configuration = configuration;
}
///
/// Runs setup. DO NOT RUN THIS MANUALLY. DO NOT DO ANYTHING WITH THIS.
///
/// The client to setup on.
protected internal override void Setup(DiscordClient client)
{
if (this.Client != null)
throw new InvalidOperationException("What did I tell you?");
this.Client = client;
this._slashError = new AsyncEvent("SLASHCOMMAND_ERRORED", TimeSpan.Zero, null);
this._slashExecuted = new AsyncEvent("SLASHCOMMAND_EXECUTED", TimeSpan.Zero, null);
this._contextMenuErrored = new AsyncEvent("CONTEXTMENU_ERRORED", TimeSpan.Zero, null);
this._contextMenuExecuted = new AsyncEvent("CONTEXTMENU_EXECUTED", TimeSpan.Zero, null);
this.Client.Ready += this.Update;
this.Client.InteractionCreated += this.InteractionHandler;
this.Client.ContextMenuInteractionCreated += this.ContextMenuHandler;
}
///
/// Registers a command class.
///
/// The command class to register.
/// The guild id to register it on. If you want global commands, leave it null.
public void RegisterCommands(ulong? guildId = null) where T : ApplicationCommandsModule
{
if (this.Client.ShardId == 0)
this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T))));
}
///
/// Registers a command class.
///
/// The of the command class to register.
/// The guild id to register it on. If you want global commands, leave it null.
public void RegisterCommands(Type type, ulong? guildId = null)
{
if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
//If sharding, only register for shard 0
if (this.Client.ShardId == 0)
this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type)));
}
///
- /// Registers a command class with permission setup.
+ /// Registers a command class with permission and translation setup.
///
/// The command class to register.
/// The guild id to register it on.
/// A callback to setup permissions with.
- public void RegisterCommands(ulong guildId, Action permissionSetup = null) where T : ApplicationCommandsModule
+ /// A callback to setup translations with.
+ public void RegisterCommands(ulong guildId, Action permissionSetup = null, Action translationSetup = null) where T : ApplicationCommandsModule
{
if (this.Client.ShardId == 0)
- this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T), permissionSetup)));
+ this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T), permissionSetup, translationSetup)));
}
///
- /// Registers a command class with permission setup.
+ /// Registers a command class with permission and translation setup.
///
/// The of the command class to register.
/// The guild id to register it on.
/// A callback to setup permissions with.
- public void RegisterCommands(Type type, ulong guildId, Action permissionSetup = null)
+ /// A callback to setup translations with.
+ public void RegisterCommands(Type type, ulong guildId, Action permissionSetup = null, Action translationSetup = null)
{
if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
//If sharding, only register for shard 0
if (this.Client.ShardId == 0)
- this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type, permissionSetup)));
+ this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type, permissionSetup, translationSetup)));
}
+
/*
///
/// Registers a command class with permission setup but without a guild id.
///
/// The command class to register.
/// A callback to setup permissions with.
public void RegisterCommands(Action permissionSetup = null) where T : ApplicationCommandsModule
{
if (this.Client.ShardId == 0)
this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(typeof(T), permissionSetup)));
}
///
/// Registers a command class with permission setup but without a guild id.
///
/// The of the command class to register.
/// A callback to setup permissions with.
public void RegisterCommands(Type type, Action permissionSetup = null)
{
if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
//If sharding, only register for shard 0
if (this.Client.ShardId == 0)
this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(type, permissionSetup)));
}
*/
+
///
/// To be run on ready.
///
/// The client.
/// The ready event args.
internal Task Update(DiscordClient client, ReadyEventArgs e)
=> this.Update();
///
/// Actual method for registering, used for RegisterCommands and on Ready.
///
internal Task Update()
{
//Only update for shard 0
if (this.Client.ShardId == 0)
{
//Groups commands by guild id or global
foreach (var key in this._updateList.Select(x => x.Key).Distinct())
{
this.RegisterCommands(this._updateList.Where(x => x.Key == key).Select(x => x.Value), key);
}
}
return Task.CompletedTask;
}
///
/// Method for registering commands for a target from modules.
///
/// The types.
/// The optional guild id.
private void RegisterCommands(IEnumerable types, ulong? guildid)
{
//Initialize empty lists to be added to the global ones at the end
var commandMethods = new List();
var groupCommands = new List();
var subGroupCommands = new List();
var contextMenuCommands = new List();
var updateList = new List();
var commandTypeSources = new List>();
_ = Task.Run(async () =>
{
//Iterates over all the modules
foreach (var config in types)
{
var type = config.Type;
try
{
var module = type.GetTypeInfo();
var classes = new List();
//Add module to classes list if it's a group
if (module.GetCustomAttribute() != null)
{
classes.Add(module);
}
else
{
//Otherwise add the nested groups
classes = module.DeclaredNestedTypes.Where(x => x.GetCustomAttribute() != null).ToList();
}
//Handles groups
foreach (var subclassinfo in classes)
{
+ var ctx = new ApplicationCommandsTranslationContext(type, module.FullName);
+ config.Translations?.Invoke(ctx);
+
+ List translations = null;
+
+ if (!string.IsNullOrEmpty(ctx.Translations))
+ {
+ translations = JsonConvert.DeserializeObject>(ctx.Translations);
+ }
+
//Gets the attribute and methods in the group
var groupAttribute = subclassinfo.GetCustomAttribute();
var submethods = subclassinfo.DeclaredMethods.Where(x => x.GetCustomAttribute() != null);
var subclasses = subclassinfo.DeclaredNestedTypes.Where(x => x.GetCustomAttribute() != null);
if (subclasses.Any() && submethods.Any())
{
throw new ArgumentException("Slash command groups cannot have both subcommands and subgroups!");
}
+ DiscordApplicationCommandLocalization NameLocalizations = null;
+ DiscordApplicationCommandLocalization DescriptionLocalizations = null;
+
+ if (translations != null)
+ {
+ var command_translation = translations.Single(c => c.Name == groupAttribute.Name);
+ if (command_translation != null)
+ {
+ NameLocalizations = command_translation.NameTranslations;
+ DescriptionLocalizations = command_translation.DescriptionTranslations;
+ }
+ }
+
//Initializes the command
- var payload = new DiscordApplicationCommand(groupAttribute.Name, groupAttribute.Description, default_permission: groupAttribute.DefaultPermission);
+ var payload = new DiscordApplicationCommand(groupAttribute.Name, groupAttribute.Description, default_permission: groupAttribute.DefaultPermission, nameLocalizations: NameLocalizations, descriptionLocalizations: DescriptionLocalizations);
commandTypeSources.Add(new KeyValuePair(type, type));
var commandmethods = new List>();
//Handles commands in the group
foreach (var submethod in submethods)
{
var commandAttribute = submethod.GetCustomAttribute();
//Gets the paramaters and accounts for InteractionContext
var parameters = submethod.GetParameters();
if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.First().ParameterType, typeof(InteractionContext)))
throw new ArgumentException($"The first argument must be an InteractionContext!");
parameters = parameters.Skip(1).ToArray();
var options = await this.ParseParameters(parameters, guildid);
+ DiscordApplicationCommandLocalization SubNameLocalizations = null;
+ DiscordApplicationCommandLocalization SubDescriptionLocalizations = null;
+ List LocalizisedOptions = null;
+
+ if (translations != null)
+ {
+ var command_translation = translations.Single(c => c.Name == payload.Name);
+
+ if (command_translation.Commands != null)
+ {
+
+ var sub_command_translation = command_translation.Commands.Single(sc => sc.Name == commandAttribute.Name);
+ if (sub_command_translation.Options != null)
+ {
+ LocalizisedOptions = new(options.Count);
+ foreach (var option in options)
+ {
+ List choices = option.Choices != null ? new(option.Choices.Count()) : null;
+ if (option.Choices != null)
+ {
+ foreach (var choice in option.Choices)
+ {
+ choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, sub_command_translation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations));
+ }
+ }
+
+ LocalizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required,
+ choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue,
+ sub_command_translation.Options.Single(o => o.Name == option.Name).NameTranslations, sub_command_translation.Options.Single(o => o.Name == option.Name).DescriptionTranslations
+ ));
+ }
+ }
+
+ SubNameLocalizations = sub_command_translation.NameTranslations;
+ SubDescriptionLocalizations = sub_command_translation.DescriptionTranslations;
+ }
+ }
+
+
//Creates the subcommand and adds it to the main command
- var subpayload = new DiscordApplicationCommandOption(commandAttribute.Name, commandAttribute.Description, ApplicationCommandOptionType.SubCommand, null, null, options);
- payload = new DiscordApplicationCommand(payload.Name, payload.Description, payload.Options?.Append(subpayload) ?? new[] { subpayload }, payload.DefaultPermission);
+ var subpayload = new DiscordApplicationCommandOption(commandAttribute.Name, commandAttribute.Description, ApplicationCommandOptionType.SubCommand, null, null, LocalizisedOptions ?? options, nameLocalizations: SubNameLocalizations, descriptionLocalizations: SubDescriptionLocalizations);
+ payload = new DiscordApplicationCommand(payload.Name, payload.Description, payload.Options?.Append(subpayload) ?? new[] { subpayload }, payload.DefaultPermission, nameLocalizations: payload.NameLocalizations, descriptionLocalizations: payload.DescriptionLocalizations);
commandTypeSources.Add(new KeyValuePair(subclassinfo, type));
//Adds it to the method lists
commandmethods.Add(new KeyValuePair(commandAttribute.Name, submethod));
groupCommands.Add(new GroupCommand { Name = groupAttribute.Name, Methods = commandmethods });
}
var command = new SubGroupCommand { Name = groupAttribute.Name };
//Handles subgroups
foreach (var subclass in subclasses)
{
var subGroupAttribute = subclass.GetCustomAttribute();
- //I couldn't think of more creative naming
var subsubmethods = subclass.DeclaredMethods.Where(x => x.GetCustomAttribute() != null);
var options = new List();
var currentMethods = new List>();
+
+ DiscordApplicationCommandLocalization SubNameLocalizations = null;
+ DiscordApplicationCommandLocalization SubDescriptionLocalizations = null;
+
+ if (translations != null)
+ {
+ var command_translation = translations.Single(c => c.Name == payload.Name);
+ if (command_translation != null && command_translation.SubGroups != null)
+ {
+
+ var sub_command_translation = command_translation.SubGroups.Single(sc => sc.Name == subGroupAttribute.Name);
+
+ if (sub_command_translation != null)
+ {
+ SubNameLocalizations = sub_command_translation.NameTranslations;
+ SubDescriptionLocalizations = sub_command_translation.DescriptionTranslations;
+ }
+ }
+ }
+
//Similar to the one for regular groups
foreach (var subsubmethod in subsubmethods)
{
var suboptions = new List();
var commatt = subsubmethod.GetCustomAttribute();
var parameters = subsubmethod.GetParameters();
if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.First().ParameterType, typeof(InteractionContext)))
throw new ArgumentException($"The first argument must be an InteractionContext!");
+
parameters = parameters.Skip(1).ToArray();
suboptions = suboptions.Concat(await this.ParseParameters(parameters, guildid)).ToList();
- var subsubpayload = new DiscordApplicationCommandOption(commatt.Name, commatt.Description, ApplicationCommandOptionType.SubCommand, null, null, suboptions);
+ DiscordApplicationCommandLocalization SubSubNameLocalizations = null;
+ DiscordApplicationCommandLocalization SubSubDescriptionLocalizations = null;
+ List LocalizisedOptions = null;
+
+ if (translations != null)
+ {
+ var command_translation = translations.Single(c => c.Name == payload.Name);
+
+ if (command_translation.SubGroups != null)
+ {
+ var sub_command_translation = command_translation.SubGroups.Single(sc => sc.Name == commatt.Name);
+
+ if (sub_command_translation != null)
+ {
+ var sub_sub_command_translation = sub_command_translation.Commands.Single(sc => sc.Name == commatt.Name);
+
+ if(sub_sub_command_translation != null)
+ {
+ if(sub_sub_command_translation.Options != null)
+ {
+ LocalizisedOptions = new(suboptions.Count);
+ foreach (var option in suboptions)
+ {
+ List choices = option.Choices != null ? new(option.Choices.Count()) : null;
+ if (option.Choices != null)
+ {
+ foreach (var choice in option.Choices)
+ {
+ choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, sub_sub_command_translation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations));
+ }
+ }
+
+ LocalizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required,
+ choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue,
+ sub_sub_command_translation.Options.Single(o => o.Name == option.Name).NameTranslations, sub_sub_command_translation.Options.Single(o => o.Name == option.Name).DescriptionTranslations
+ ));
+ }
+ }
+
+ SubSubNameLocalizations = sub_sub_command_translation.NameTranslations;
+ SubSubDescriptionLocalizations = sub_sub_command_translation.DescriptionTranslations;
+ }
+ }
+ }
+ }
+
+ var subsubpayload = new DiscordApplicationCommandOption(commatt.Name, commatt.Description, ApplicationCommandOptionType.SubCommand, null, null, LocalizisedOptions ?? suboptions, nameLocalizations: SubSubNameLocalizations, descriptionLocalizations: SubSubDescriptionLocalizations);
options.Add(subsubpayload);
commandmethods.Add(new KeyValuePair(commatt.Name, subsubmethod));
currentMethods.Add(new KeyValuePair(commatt.Name, subsubmethod));
}
//Adds the group to the command and method lists
- var subpayload = new DiscordApplicationCommandOption(subGroupAttribute.Name, subGroupAttribute.Description, ApplicationCommandOptionType.SubCommandGroup, null, null, options);
+ var subpayload = new DiscordApplicationCommandOption(subGroupAttribute.Name, subGroupAttribute.Description, ApplicationCommandOptionType.SubCommandGroup, null, null, options, nameLocalizations: SubNameLocalizations, descriptionLocalizations: SubDescriptionLocalizations);
command.SubCommands.Add(new GroupCommand { Name = subGroupAttribute.Name, Methods = currentMethods });
- payload = new DiscordApplicationCommand(payload.Name, payload.Description, payload.Options?.Append(subpayload) ?? new[] { subpayload }, payload.DefaultPermission);
+ payload = new DiscordApplicationCommand(payload.Name, payload.Description, payload.Options?.Append(subpayload) ?? new[] { subpayload }, payload.DefaultPermission, nameLocalizations: payload.NameLocalizations, descriptionLocalizations: payload.DescriptionLocalizations);
commandTypeSources.Add(new KeyValuePair(subclass, type));
//Accounts for lifespans for the sub group
if (subclass.GetCustomAttribute() != null)
{
if (subclass.GetCustomAttribute().Lifespan == ApplicationCommandModuleLifespan.Singleton)
{
_singletonModules.Add(this.CreateInstance(subclass, this._configuration?.ServiceProvider));
}
}
}
if (command.SubCommands.Any()) subGroupCommands.Add(command);
updateList.Add(payload);
//Accounts for lifespans
if (subclassinfo.GetCustomAttribute() != null)
{
if (subclassinfo.GetCustomAttribute().Lifespan == ApplicationCommandModuleLifespan.Singleton)
{
_singletonModules.Add(this.CreateInstance(subclassinfo, this._configuration?.ServiceProvider));
}
}
}
//Handles methods and context menus, only if the module isn't a group itself
if (module.GetCustomAttribute() == null)
{
+
+ var ctx = new ApplicationCommandsTranslationContext(type, module.FullName);
+ config.Translations?.Invoke(ctx);
+
+ List translations = null;
+
+ if (!string.IsNullOrEmpty(ctx.Translations))
+ {
+ translations = JsonConvert.DeserializeObject>(ctx.Translations);
+ }
+
//Slash commands (again, similar to the one for groups)
var methods = module.DeclaredMethods.Where(x => x.GetCustomAttribute() != null);
foreach (var method in methods)
{
var commandattribute = method.GetCustomAttribute();
var parameters = method.GetParameters();
if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.FirstOrDefault()?.ParameterType, typeof(InteractionContext)))
throw new ArgumentException($"The first argument must be an InteractionContext!");
parameters = parameters.Skip(1).ToArray();
var options = await this.ParseParameters(parameters, guildid);
commandMethods.Add(new CommandMethod { Method = method, Name = commandattribute.Name });
- var payload = new DiscordApplicationCommand(commandattribute.Name, commandattribute.Description, options, commandattribute.DefaultPermission);
+ DiscordApplicationCommandLocalization NameLocalizations = null;
+ DiscordApplicationCommandLocalization DescriptionLocalizations = null;
+ List LocalizisedOptions = null;
+
+ if (translations != null)
+ {
+ var command_translation = translations.Single(c => c.Name == commandattribute.Name && c.Type == ApplicationCommandType.ChatInput);
+
+ if (command_translation != null)
+ {
+ if (command_translation.Options != null)
+ {
+ LocalizisedOptions = new(options.Count);
+ foreach (var option in options)
+ {
+ List choices = option.Choices != null ? new(option.Choices.Count()) : null;
+ if (option.Choices != null)
+ {
+ foreach (var choice in option.Choices)
+ {
+ choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, command_translation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations));
+ }
+ }
+
+ LocalizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required,
+ choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue,
+ command_translation.Options.Single(o => o.Name == option.Name).NameTranslations, command_translation.Options.Single(o => o.Name == option.Name).DescriptionTranslations
+ ));
+ }
+ }
+
+ NameLocalizations = command_translation.NameTranslations;
+ DescriptionLocalizations = command_translation.DescriptionTranslations;
+ }
+ }
+
+ var payload = new DiscordApplicationCommand(commandattribute.Name, commandattribute.Description, LocalizisedOptions ?? options, commandattribute.DefaultPermission, ApplicationCommandType.ChatInput, NameLocalizations, DescriptionLocalizations);
updateList.Add(payload);
commandTypeSources.Add(new KeyValuePair(type, type));
}
//Context Menus
var contextMethods = module.DeclaredMethods.Where(x => x.GetCustomAttribute() != null);
foreach (var contextMethod in contextMethods)
{
var contextAttribute = contextMethod.GetCustomAttribute();
- var command = new DiscordApplicationCommand(contextAttribute.Name, null, type: contextAttribute.Type, default_permission: contextAttribute.DefaultPermission);
+
+ DiscordApplicationCommandLocalization NameLocalizations = null;
+
+ if (translations != null)
+ {
+ var command_translation = translations.Single(c => c.Name == contextAttribute.Name && c.Type == contextAttribute.Type);
+ if (command_translation != null)
+ NameLocalizations = command_translation.NameTranslations;
+ }
+
+ var command = new DiscordApplicationCommand(contextAttribute.Name, null, null, contextAttribute.DefaultPermission, contextAttribute.Type, NameLocalizations);
var parameters = contextMethod.GetParameters();
if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.FirstOrDefault()?.ParameterType, typeof(ContextMenuContext)))
throw new ArgumentException($"The first argument must be a ContextMenuContext!");
if (parameters.Length > 1)
throw new ArgumentException($"A context menu cannot have parameters!");
contextMenuCommands.Add(new ContextMenuCommand { Method = contextMethod, Name = contextAttribute.Name });
updateList.Add(command);
commandTypeSources.Add(new KeyValuePair(type, type));
}
//Accounts for lifespans
if (module.GetCustomAttribute() != null)
{
if (module.GetCustomAttribute().Lifespan == ApplicationCommandModuleLifespan.Singleton)
{
_singletonModules.Add(this.CreateInstance(module, this._configuration?.ServiceProvider));
}
}
}
}
catch (Exception ex)
{
//This isn't really much more descriptive but I added a separate case for it anyway
if (ex is BadRequestException brex)
this.Client.Logger.LogCritical(brex, $"There was an error registering application commands: {brex.JsonMessage}");
else
this.Client.Logger.LogCritical(ex, $"There was an error registering application commands");
_errored = true;
}
}
if (!_errored)
{
try
{
async Task UpdateCommandPermission(ulong commandId, string commandName, Type commandDeclaringType, Type commandRootType)
{
- if (guildid == null)
+ if (!guildid.HasValue)
{
- //throw new NotImplementedException("You can't set global permissions till yet. See https://discord.com/developers/docs/interactions/application-commands#permissions");
+ this.Client.Logger.LogTrace("You can't set global permissions till yet. See https://discord.com/developers/docs/interactions/application-commands#permissions");
}
else
{
var ctx = new ApplicationCommandsPermissionContext(commandDeclaringType, commandName);
var conf = types.First(t => t.Type == commandRootType);
conf.Setup?.Invoke(ctx);
if (ctx.Permissions.Count == 0)
return;
await this.Client.OverwriteGuildApplicationCommandPermissionsAsync(guildid.Value, commandId, ctx.Permissions);
}
}
async Task UpdateCommandPermissionGroup(GroupCommand groupCommand)
{
foreach (var com in groupCommand.Methods)
{
var source = commandTypeSources.FirstOrDefault(f => f.Key == com.Value.DeclaringType);
await UpdateCommandPermission(groupCommand.CommandId, com.Key, source.Key, source.Value);
}
}
var commands = guildid == null
? await this.Client.BulkOverwriteGlobalApplicationCommandsAsync(updateList)
: (IEnumerable)await this.Client.BulkOverwriteGuildApplicationCommandsAsync(guildid.Value, updateList);
//Creates a guild command if a guild id is specified, otherwise global
//Checks against the ids and adds them to the command method lists
foreach (var command in commands)
{
if (commandMethods.Any(x => x.Name == command.Name))
{
var com = commandMethods.First(x => x.Name == command.Name);
com.CommandId = command.Id;
var source = commandTypeSources.FirstOrDefault(f => f.Key == com.Method.DeclaringType);
await UpdateCommandPermission(command.Id, com.Name, source.Value, source.Key);
}
else if (groupCommands.Any(x => x.Name == command.Name))
{
var com = groupCommands.First(x => x.Name == command.Name);
com.CommandId = command.Id;
await UpdateCommandPermissionGroup(com);
}
else if (subGroupCommands.Any(x => x.Name == command.Name))
{
var com = subGroupCommands.First(x => x.Name == command.Name);
com.CommandId = command.Id;
foreach (var groupComs in com.SubCommands)
await UpdateCommandPermissionGroup(groupComs);
}
else if (contextMenuCommands.Any(x => x.Name == command.Name))
{
var com = contextMenuCommands.First(x => x.Name == command.Name);
com.CommandId = command.Id;
var source = commandTypeSources.First(f => f.Key == com.Method.DeclaringType);
await UpdateCommandPermission(command.Id, com.Name, source.Value, source.Key);
}
}
//Adds to the global lists finally
_commandMethods.AddRange(commandMethods);
_groupCommands.AddRange(groupCommands);
_subGroupCommands.AddRange(subGroupCommands);
_contextMenuCommands.AddRange(contextMenuCommands);
_registeredCommands.Add(new KeyValuePair>(guildid, commands.ToList()));
foreach (var command in commandMethods)
{
var app = types.First(t => t.Type == command.Method.DeclaringType);
}
}
catch (Exception ex)
{
if (ex is BadRequestException brex)
this.Client.Logger.LogCritical(brex, $"There was an error registering application commands: {brex.JsonMessage}");
else
this.Client.Logger.LogCritical(ex, $"There was an error registering application commands");
_errored = true;
}
}
});
}
///
/// Interaction handler.
///
/// The client.
/// The event args.
private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs e)
{
_ = Task.Run(async () =>
{
if (e.Interaction.Type == InteractionType.ApplicationCommand)
{
//Creates the context
var context = new InteractionContext
{
Interaction = e.Interaction,
Channel = e.Interaction.Channel,
Guild = e.Interaction.Guild,
User = e.Interaction.User,
Client = client,
ApplicationCommandsExtension = this,
CommandName = e.Interaction.Data.Name,
InteractionId = e.Interaction.Id,
Token = e.Interaction.Token,
Services = this._configuration?.ServiceProvider,
ResolvedUserMentions = e.Interaction.Data.Resolved?.Users?.Values.ToList(),
ResolvedRoleMentions = e.Interaction.Data.Resolved?.Roles?.Values.ToList(),
ResolvedChannelMentions = e.Interaction.Data.Resolved?.Channels?.Values.ToList(),
ResolvedAttachments = e.Interaction.Data.Resolved?.Attachments?.Values.ToList(),
- Type = ApplicationCommandType.ChatInput
+ Type = ApplicationCommandType.ChatInput,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
};
try
{
if (_errored)
throw new InvalidOperationException("Slash commands failed to register properly on startup.");
var methods = _commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id);
var groups = _groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
var subgroups = _subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
if (!methods.Any() && !groups.Any() && !subgroups.Any())
throw new InvalidOperationException("A slash command was executed, but no command was registered for it.");
if (methods.Any())
{
var method = methods.First().Method;
var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options);
await this.RunCommandAsync(context, method, args);
}
else if (groups.Any())
{
var command = e.Interaction.Data.Options.First();
var method = groups.First().Methods.First(x => x.Key == command.Name).Value;
var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options.First().Options);
await this.RunCommandAsync(context, method, args);
}
else if (subgroups.Any())
{
var command = e.Interaction.Data.Options.First();
var group = subgroups.First().SubCommands.First(x => x.Name == command.Name);
var method = group.Methods.First(x => x.Key == command.Options.First().Name).Value;
var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options.First().Options.First().Options);
await this.RunCommandAsync(context, method, args);
}
await this._slashExecuted.InvokeAsync(this, new SlashCommandExecutedEventArgs(this.Client.ServiceProvider) { Context = context });
}
catch (Exception ex)
{
await this._slashError.InvokeAsync(this, new SlashCommandErrorEventArgs(this.Client.ServiceProvider) { Context = context, Exception = ex });
}
}
else if (e.Interaction.Type == InteractionType.AutoComplete)
{
if (_errored)
throw new InvalidOperationException("Slash commands failed to register properly on startup.");
var methods = _commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id);
var groups = _groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
var subgroups = _subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
if (!methods.Any() && !groups.Any() && !subgroups.Any())
throw new InvalidOperationException("An autocomplete interaction was created, but no command was registered for it.");
try
{
if (methods.Any())
{
var focusedOption = e.Interaction.Data.Options.First(o => o.Focused);
var method = methods.First().Method;
var option = method.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name);
var provider = option.GetCustomAttribute().ProviderType;
var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
var providerInstance = Activator.CreateInstance(provider);
var context = new AutocompleteContext
{
Interaction = e.Interaction,
Client = this.Client,
Services = this._configuration?.ServiceProvider,
ApplicationCommandsExtension = this,
Guild = e.Interaction.Guild,
Channel = e.Interaction.Channel,
User = e.Interaction.User,
Options = e.Interaction.Data.Options.ToList(),
- FocusedOption = focusedOption
+ FocusedOption = focusedOption,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
};
var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
}
else if (groups.Any())
{
var command = e.Interaction.Data.Options.First();
var group = groups.First().Methods.First(x => x.Key == command.Name).Value;
var focusedOption = command.Options.First(o => o.Focused);
var option = group.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name);
var provider = option.GetCustomAttribute().ProviderType;
var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
var providerInstance = Activator.CreateInstance(provider);
var context = new AutocompleteContext
{
Interaction = e.Interaction,
Services = this._configuration?.ServiceProvider,
ApplicationCommandsExtension = this,
Guild = e.Interaction.Guild,
Channel = e.Interaction.Channel,
User = e.Interaction.User,
Options = command.Options.ToList(),
- FocusedOption = focusedOption
+ FocusedOption = focusedOption,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
};
var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
}
/*else if (subgroups.Any())
{
var command = e.Interaction.Data.Options.First();
var method = methods.First().Method;
var group = subgroups.First().SubCommands.First(x => x.Name == command.Name);
var focusedOption = command.Options.First(x => x.Name == group.Name).Options.First(o => o.Focused);
this.Client.Logger.LogDebug("SUBGROUP::" + focusedOption.Name + ": " + focusedOption.RawValue);
var option = group.Methods.First(p => p.Value.GetCustomAttribute().Name == focusedOption.Name).Value;
var provider = option.GetCustomAttribute().ProviderType;
var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
var providerInstance = Activator.CreateInstance(provider);
var context = new AutocompleteContext
{
Interaction = e.Interaction,
Services = this._configuration?.Services,
ApplicationCommandsExtension = this,
Guild = e.Interaction.Guild,
Channel = e.Interaction.Channel,
User = e.Interaction.User,
Options = command.Options.First(x => x.Name == group.Name).Options.ToList(),
FocusedOption = focusedOption
};
var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
}*/
}
catch (Exception ex)
{
this.Client.Logger.LogError(ex, "Error in autocomplete interaction");
}
}
});
return Task.CompletedTask;
}
///
/// Context menu handler.
///
/// The client.
/// The event args.
private Task ContextMenuHandler(DiscordClient client, ContextMenuInteractionCreateEventArgs e)
{
_ = Task.Run(async () =>
{
//Creates the context
var context = new ContextMenuContext
{
Interaction = e.Interaction,
Channel = e.Interaction.Channel,
Client = client,
Services = this._configuration?.ServiceProvider,
CommandName = e.Interaction.Data.Name,
ApplicationCommandsExtension = this,
Guild = e.Interaction.Guild,
InteractionId = e.Interaction.Id,
User = e.Interaction.User,
Token = e.Interaction.Token,
TargetUser = e.TargetUser,
TargetMessage = e.TargetMessage,
- Type = e.Type
+ Type = e.Type,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
};
try
{
if (_errored)
throw new InvalidOperationException("Context menus failed to register properly on startup.");
//Gets the method for the command
var method = _contextMenuCommands.FirstOrDefault(x => x.CommandId == e.Interaction.Data.Id);
if (method == null)
throw new InvalidOperationException("A context menu was executed, but no command was registered for it.");
await this.RunCommandAsync(context, method.Method, new[] { context });
await this._contextMenuExecuted.InvokeAsync(this, new ContextMenuExecutedEventArgs(this.Client.ServiceProvider) { Context = context });
}
catch (Exception ex)
{
await this._contextMenuErrored.InvokeAsync(this, new ContextMenuErrorEventArgs(this.Client.ServiceProvider) { Context = context, Exception = ex });
}
});
return Task.CompletedTask;
}
///
/// Runs a command.
///
/// The base context.
/// The method info.
/// The arguments.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "")]
internal async Task RunCommandAsync(BaseContext context, MethodInfo method, IEnumerable args)
{
object classInstance;
//Accounts for lifespans
var moduleLifespan = (method.DeclaringType.GetCustomAttribute() != null ? method.DeclaringType.GetCustomAttribute()?.Lifespan : ApplicationCommandModuleLifespan.Transient) ?? ApplicationCommandModuleLifespan.Transient;
switch (moduleLifespan)
{
case ApplicationCommandModuleLifespan.Scoped:
//Accounts for static methods and adds DI
classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(this._configuration?.ServiceProvider.CreateScope().ServiceProvider, method.DeclaringType) : this.CreateInstance(method.DeclaringType, this._configuration?.ServiceProvider.CreateScope().ServiceProvider);
break;
case ApplicationCommandModuleLifespan.Transient:
//Accounts for static methods and adds DI
classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(this._configuration?.ServiceProvider, method.DeclaringType) : this.CreateInstance(method.DeclaringType, this._configuration?.ServiceProvider);
break;
//If singleton, gets it from the singleton list
case ApplicationCommandModuleLifespan.Singleton:
classInstance = _singletonModules.First(x => ReferenceEquals(x.GetType(), method.DeclaringType));
break;
default:
throw new Exception($"An unknown {nameof(ApplicationCommandModuleLifespanAttribute)} scope was specified on command {context.CommandName}");
}
ApplicationCommandsModule module = null;
if (classInstance is ApplicationCommandsModule mod)
module = mod;
// Slash commands
if (context is InteractionContext slashContext)
{
await this.RunPreexecutionChecksAsync(method, slashContext);
var shouldExecute = await (module?.BeforeSlashExecutionAsync(slashContext) ?? Task.FromResult(true));
if (shouldExecute)
{
await (Task)method.Invoke(classInstance, args.ToArray());
await (module?.AfterSlashExecutionAsync(slashContext) ?? Task.CompletedTask);
}
}
// Context menus
if (context is ContextMenuContext contextMenuContext)
{
await this.RunPreexecutionChecksAsync(method, contextMenuContext);
var shouldExecute = await (module?.BeforeContextMenuExecutionAsync(contextMenuContext) ?? Task.FromResult(true));
if (shouldExecute)
{
await (Task)method.Invoke(classInstance, args.ToArray());
await (module?.AfterContextMenuExecutionAsync(contextMenuContext) ?? Task.CompletedTask);
}
}
}
///
/// Property injection copied over from CommandsNext
///
/// The type.
/// The services.
internal object CreateInstance(Type t, IServiceProvider services)
{
var ti = t.GetTypeInfo();
var constructors = ti.DeclaredConstructors
.Where(xci => xci.IsPublic)
.ToArray();
if (constructors.Length != 1)
throw new ArgumentException("Specified type does not contain a public constructor or contains more than one public constructor.");
var constructor = constructors[0];
var constructorArgs = constructor.GetParameters();
var args = new object[constructorArgs.Length];
if (constructorArgs.Length != 0 && services == null)
throw new InvalidOperationException("Dependency collection needs to be specified for parameterized constructors.");
// inject via constructor
if (constructorArgs.Length != 0)
for (var i = 0; i < args.Length; i++)
args[i] = services.GetRequiredService(constructorArgs[i].ParameterType);
var moduleInstance = Activator.CreateInstance(t, args);
// inject into properties
var props = t.GetRuntimeProperties().Where(xp => xp.CanWrite && xp.SetMethod != null && !xp.SetMethod.IsStatic && xp.SetMethod.IsPublic);
foreach (var prop in props)
{
if (prop.GetCustomAttribute() != null)
continue;
var service = services.GetService(prop.PropertyType);
if (service == null)
continue;
prop.SetValue(moduleInstance, service);
}
// inject into fields
var fields = t.GetRuntimeFields().Where(xf => !xf.IsInitOnly && !xf.IsStatic && xf.IsPublic);
foreach (var field in fields)
{
if (field.GetCustomAttribute() != null)
continue;
var service = services.GetService(field.FieldType);
if (service == null)
continue;
field.SetValue(moduleInstance, service);
}
return moduleInstance;
}
///
/// Resolves the slash command parameters.
///
/// The event arguments.
/// The interaction context.
/// The method info.
/// The options.
private async Task> ResolveInteractionCommandParameters(InteractionCreateEventArgs e, InteractionContext context, MethodInfo method, IEnumerable options)
{
var args = new List { context };
var parameters = method.GetParameters().Skip(1);
for (var i = 0; i < parameters.Count(); i++)
{
var parameter = parameters.ElementAt(i);
//Accounts for optional arguments without values given
if (parameter.IsOptional && (options == null ||
(!options?.Any(x => x.Name == parameter.GetCustomAttribute().Name.ToLower()) ?? true)))
args.Add(parameter.DefaultValue);
else
{
var option = options.Single(x => x.Name == parameter.GetCustomAttribute().Name.ToLower());
if (parameter.ParameterType == typeof(string))
args.Add(option.Value.ToString());
else if (parameter.ParameterType.IsEnum)
args.Add(Enum.Parse(parameter.ParameterType, (string)option.Value));
else if (parameter.ParameterType == typeof(long) || parameter.ParameterType == typeof(long?))
args.Add((long?)option.Value);
else if (parameter.ParameterType == typeof(bool) || parameter.ParameterType == typeof(bool?))
args.Add((bool?)option.Value);
else if (parameter.ParameterType == typeof(double) || parameter.ParameterType == typeof(double?))
args.Add((double?)option.Value);
else if(parameter.ParameterType == typeof(int) || parameter.ParameterType == typeof(int?))
args.Add((int?)option.Value);
else if (parameter.ParameterType == typeof(DiscordAttachment))
{
//Checks through resolved
if (e.Interaction.Data.Resolved.Attachments != null &&
e.Interaction.Data.Resolved.Attachments.TryGetValue((ulong)option.Value, out var attachment))
args.Add(attachment);
}
else if (parameter.ParameterType == typeof(DiscordUser))
{
//Checks through resolved
if (e.Interaction.Data.Resolved.Members != null &&
e.Interaction.Data.Resolved.Members.TryGetValue((ulong)option.Value, out var member))
args.Add(member);
else if (e.Interaction.Data.Resolved.Users != null &&
e.Interaction.Data.Resolved.Users.TryGetValue((ulong)option.Value, out var user))
args.Add(user);
else
args.Add(await this.Client.GetUserAsync((ulong)option.Value));
}
else if (parameter.ParameterType == typeof(DiscordChannel))
{
//Checks through resolved
if (e.Interaction.Data.Resolved.Channels != null &&
e.Interaction.Data.Resolved.Channels.TryGetValue((ulong)option.Value, out var channel))
args.Add(channel);
else
args.Add(e.Interaction.Guild.GetChannel((ulong)option.Value));
}
else if (parameter.ParameterType == typeof(DiscordRole))
{
//Checks through resolved
if (e.Interaction.Data.Resolved.Roles != null &&
e.Interaction.Data.Resolved.Roles.TryGetValue((ulong)option.Value, out var role))
args.Add(role);
else
args.Add(e.Interaction.Guild.GetRole((ulong)option.Value));
}
else if (parameter.ParameterType == typeof(SnowflakeObject))
{
//Checks through resolved
if (e.Interaction.Data.Resolved.Roles != null && e.Interaction.Data.Resolved.Roles.TryGetValue((ulong)option.Value, out var role))
args.Add(role);
else if (e.Interaction.Data.Resolved.Members != null && e.Interaction.Data.Resolved.Members.TryGetValue((ulong)option.Value, out var member))
args.Add(member);
else if (e.Interaction.Data.Resolved.Users != null && e.Interaction.Data.Resolved.Users.TryGetValue((ulong)option.Value, out var user))
args.Add(user);
else if (e.Interaction.Data.Resolved.Attachments != null && e.Interaction.Data.Resolved.Attachments.TryGetValue((ulong)option.Value, out var attachment))
args.Add(attachment);
else
throw new ArgumentException("Error resolving mentionable option.");
}
else
throw new ArgumentException($"Error resolving interaction.");
}
}
return args;
}
///
/// Runs the preexecution checks.
///
/// The method info.
- /// The basecontext.
+ /// The base context.
private async Task RunPreexecutionChecksAsync(MethodInfo method, BaseContext context)
{
if (context is InteractionContext ctx)
{
//Gets all attributes from parent classes as well and stuff
var attributes = new List();
attributes.AddRange(method.GetCustomAttributes(true));
attributes.AddRange(method.DeclaringType.GetCustomAttributes());
if (method.DeclaringType.DeclaringType != null)
{
attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes());
if (method.DeclaringType.DeclaringType.DeclaringType != null)
{
attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes());
}
}
var dict = new Dictionary();
foreach (var att in attributes)
{
//Runs the check and adds the result to a list
var result = await att.ExecuteChecksAsync(ctx);
dict.Add(att, result);
}
//Checks if any failed, and throws an exception
if (dict.Any(x => x.Value == false))
throw new SlashExecutionChecksFailedException { FailedChecks = dict.Where(x => x.Value == false).Select(x => x.Key).ToList() };
}
if (context is ContextMenuContext CMctx)
{
var attributes = new List();
attributes.AddRange(method.GetCustomAttributes(true));
attributes.AddRange(method.DeclaringType.GetCustomAttributes());
if (method.DeclaringType.DeclaringType != null)
{
attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes());
if (method.DeclaringType.DeclaringType.DeclaringType != null)
{
attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes());
}
}
var dict = new Dictionary();
foreach (var att in attributes)
{
//Runs the check and adds the result to a list
var result = await att.ExecuteChecksAsync(CMctx);
dict.Add(att, result);
}
//Checks if any failed, and throws an exception
if (dict.Any(x => x.Value == false))
throw new ContextMenuExecutionChecksFailedException { FailedChecks = dict.Where(x => x.Value == false).Select(x => x.Key).ToList() };
}
}
///
/// Gets the choice attributes from choice provider.
///
/// The custom attributes.
- ///
+ /// The optional guild id
private async Task> GetChoiceAttributesFromProvider(IEnumerable customAttributes, ulong? guildId = null)
{
var choices = new List();
foreach (var choiceProviderAttribute in customAttributes)
{
var method = choiceProviderAttribute.ProviderType.GetMethod(nameof(IChoiceProvider.Provider));
if (method == null)
throw new ArgumentException("ChoiceProviders must inherit from IChoiceProvider.");
else
{
var instance = Activator.CreateInstance(choiceProviderAttribute.ProviderType);
// Abstract class offers more properties that can be set
if (choiceProviderAttribute.ProviderType.IsSubclassOf(typeof(ChoiceProvider)))
{
choiceProviderAttribute.ProviderType.GetProperty(nameof(ChoiceProvider.GuildId))
?.SetValue(instance, guildId);
choiceProviderAttribute.ProviderType.GetProperty(nameof(ChoiceProvider.Services))
?.SetValue(instance, _configuration.ServiceProvider);
}
//Gets the choices from the method
var result = await (Task>)method.Invoke(instance, null);
if (result.Any())
{
choices.AddRange(result);
}
}
}
return choices;
}
///
/// Gets the choice attributes from enum parameter.
///
/// The enum parameter.
private static List GetChoiceAttributesFromEnumParameter(Type enumParam)
{
var choices = new List();
foreach (Enum enumValue in Enum.GetValues(enumParam))
{
choices.Add(new DiscordApplicationCommandOptionChoice(enumValue.GetName(), enumValue.ToString()));
}
return choices;
}
///
/// Gets the parameter type.
///
/// The type.
private ApplicationCommandOptionType GetParameterType(Type type)
{
var parametertype = type == typeof(string)
? ApplicationCommandOptionType.String
: type == typeof(long) || type == typeof(long?)
? ApplicationCommandOptionType.Integer
: type == typeof(int) || type == typeof(int?)
? ApplicationCommandOptionType.Integer
: type == typeof(bool) || type == typeof(bool?)
? ApplicationCommandOptionType.Boolean
: type == typeof(double) || type == typeof(double?)
? ApplicationCommandOptionType.Number
: type == typeof(DiscordChannel)
? ApplicationCommandOptionType.Channel
: type == typeof(DiscordUser)
? ApplicationCommandOptionType.User
: type == typeof(DiscordRole)
? ApplicationCommandOptionType.Role
: type == typeof(SnowflakeObject)
? ApplicationCommandOptionType.Mentionable
: type == typeof(DiscordAttachment)
? ApplicationCommandOptionType.Attachment
: type.IsEnum
? ApplicationCommandOptionType.String
: throw new ArgumentException("Cannot convert type! Argument types must be string, int, long, bool, double, DiscordChannel, DiscordUser, DiscordRole, SnowflakeObject, DiscordAttachment or an Enum.");
return parametertype;
}
///
/// Gets the choice attributes from parameter.
///
/// The choice attributes.
private List GetChoiceAttributesFromParameter(IEnumerable choiceattributes)
{
return !choiceattributes.Any()
? null
: choiceattributes.Select(att => new DiscordApplicationCommandOptionChoice(att.Name, att.Value)).ToList();
}
///
/// Parses the parameters.
///
/// The parameters.
- /// The guild id.
- /// A Task.
+ /// The optional guild id.
private async Task> ParseParameters(ParameterInfo[] parameters, ulong? guildId)
{
var options = new List();
foreach (var parameter in parameters)
{
//Gets the attribute
var optionattribute = parameter.GetCustomAttribute();
if (optionattribute == null)
throw new ArgumentException("Arguments must have the Option attribute!");
var minimumValue = parameter.GetCustomAttribute()?.Value ?? null;
var maximumValue = parameter.GetCustomAttribute()?.Value ?? null;
var autocompleteAttribute = parameter.GetCustomAttribute();
if (optionattribute.Autocomplete && autocompleteAttribute == null)
throw new ArgumentException("Autocomplete options must have the Autocomplete attribute!");
if (!optionattribute.Autocomplete && autocompleteAttribute != null)
throw new ArgumentException("Setting an autocomplete provider requires the option to have autocomplete set to true!");
//Sets the type
var type = parameter.ParameterType;
var parametertype = this.GetParameterType(type);
//Handles choices
//From attributes
var choices = this.GetChoiceAttributesFromParameter(parameter.GetCustomAttributes());
//From enums
if (parameter.ParameterType.IsEnum)
{
choices = GetChoiceAttributesFromEnumParameter(parameter.ParameterType);
}
//From choice provider
var choiceProviders = parameter.GetCustomAttributes();
if (choiceProviders.Any())
{
choices = await this.GetChoiceAttributesFromProvider(choiceProviders, guildId);
}
var channelTypes = parameter.GetCustomAttribute()?.ChannelTypes ?? null;
options.Add(new DiscordApplicationCommandOption(optionattribute.Name, optionattribute.Description, parametertype, !parameter.IsOptional, choices, null, channelTypes, optionattribute.Autocomplete, minimumValue, maximumValue));
}
return options;
}
///
/// Refreshes your commands, used for refreshing choice providers or applying commands registered after the ready event on the discord client.
/// Should only be run on the slash command extension linked to shard 0 if sharding.
/// Not recommended and should be avoided since it can make slash commands be unresponsive for a while.
///
public async Task RefreshCommandsAsync()
{
_commandMethods.Clear();
_groupCommands.Clear();
_subGroupCommands.Clear();
_registeredCommands.Clear();
_contextMenuCommands.Clear();
await this.Update();
}
///
/// Fires when the execution of a slash command fails.
///
public event AsyncEventHandler SlashCommandErrored
{
add { this._slashError.Register(value); }
remove { this._slashError.Unregister(value); }
}
private AsyncEvent _slashError;
///
/// Fires when the execution of a slash command is successful.
///
public event AsyncEventHandler SlashCommandExecuted
{
add { this._slashExecuted.Register(value); }
remove { this._slashExecuted.Unregister(value); }
}
private AsyncEvent _slashExecuted;
///
/// Fires when the execution of a context menu fails.
///
public event AsyncEventHandler ContextMenuErrored
{
add { this._contextMenuErrored.Register(value); }
remove { this._contextMenuErrored.Unregister(value); }
}
private AsyncEvent _contextMenuErrored;
///
/// Fire when the execution of a context menu is successful.
///
public event AsyncEventHandler ContextMenuExecuted
{
add { this._contextMenuExecuted.Register(value); }
remove { this._contextMenuExecuted.Unregister(value); }
}
private AsyncEvent _contextMenuExecuted;
}
///
/// Holds configuration data for setting up an application command.
///
internal class ApplicationCommandsModuleConfiguration
{
///
/// The type of the command module.
///
public Type Type { get; }
///
/// The permission setup.
///
public Action Setup { get; }
+ public Action Translations { get; }
+
///
/// Creates a new command configuration.
///
/// The type of the command module.
/// The permission setup callback.
- public ApplicationCommandsModuleConfiguration(Type type, Action setup = null)
+ /// The translation setup callback.
+ public ApplicationCommandsModuleConfiguration(Type type, Action setup = null, Action translations = null)
{
this.Type = type;
this.Setup = setup;
+ this.Translations = translations;
}
}
///
/// Links a command to its original command module.
///
internal class ApplicationCommandSourceLink
{
///
/// The command.
///
public DiscordApplicationCommand ApplicationCommand { get; set; }
///
/// The base/root module the command is contained in.
///
public Type RootCommandContainerType { get; set; }
///
/// The direct group the command is contained in.
///
public Type CommandContainerType { get; set; }
}
///
/// The command method.
///
internal class CommandMethod
{
///
/// Gets or sets the command id.
///
public ulong CommandId { get; set; }
///
/// Gets or sets the name.
///
public string Name { get; set; }
///
/// Gets or sets the method.
///
public MethodInfo Method { get; set; }
}
///
/// The group command.
///
internal class GroupCommand
{
///
/// Gets or sets the command id.
///
public ulong CommandId { get; set; }
///
/// Gets or sets the name.
///
public string Name { get; set; }
///
/// Gets or sets the methods.
///
public List> Methods { get; set; } = null;
}
///
/// The sub group command.
///
internal class SubGroupCommand
{
///
/// Gets or sets the command id.
///
public ulong CommandId { get; set; }
///
/// Gets or sets the name.
///
public string Name { get; set; }
///
/// Gets or sets the sub commands.
///
public List SubCommands { get; set; } = new List();
}
///
/// The context menu command.
///
internal class ContextMenuCommand
{
///
/// Gets or sets the command id.
///
public ulong CommandId { get; set; }
///
/// Gets or sets the name.
///
public string Name { get; set; }
///
/// Gets or sets the method.
///
public MethodInfo Method { get; set; }
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuAttribute.cs
index 4c7b5d335..df8186c6d 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuAttribute.cs
@@ -1,66 +1,67 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
+using DisCatSharp.Entities;
using DisCatSharp.Enums;
namespace DisCatSharp.ApplicationCommands
{
///
/// Marks this method as a context menu.
///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ContextMenuAttribute : Attribute
{
///
/// Gets the name of this context menu.
///
public string Name { get; internal set; }
///
/// Gets the type of this context menu.
///
public ApplicationCommandType Type { get; internal set; }
///
/// Gets whether this command is enabled by default.
///
- public bool DefaultPermission { get; internal set;}
+ public bool DefaultPermission { get; internal set; }
///
/// Marks this method as a context menu.
///
/// The type of the context menu.
/// The name of the context menu.
/// The default permission of the context menu.
public ContextMenuAttribute(ApplicationCommandType type, string name, bool defaultPermission = true)
{
if (type == ApplicationCommandType.ChatInput)
throw new ArgumentException("Context menus cannot be of type ChatInput (Slash).");
this.Type = type;
this.Name = name;
this.DefaultPermission = defaultPermission;
}
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceAttribute.cs
index a6322a23b..c9c53e96a 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceAttribute.cs
@@ -1,87 +1,87 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
namespace DisCatSharp.ApplicationCommands
{
///
/// Adds a choice for this slash command option
///
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public class ChoiceAttribute : Attribute
{
///
/// Gets the name of the choice
///
public string Name { get; }
///
/// Gets the value of the choice
///
public object Value { get; }
///
/// Adds a choice to the slash command option
///
- /// The name of the choice
- /// The value of the choice
+ /// The name of the choice.
+ /// The value of the choice.
public ChoiceAttribute(string name, string value)
{
this.Name = name;
this.Value = value;
}
///
/// Adds a choice to the slash command option
///
- /// The name of the choice
- /// The value of the choice
+ /// The name of the choice.
+ /// The value of the choice.
public ChoiceAttribute(string name, long value)
{
this.Name = name;
this.Value = value;
}
///
/// Adds a choice to the slash command option
///
- /// The name of the choice
- /// The value of the choice
+ /// The name of the choice.
+ /// The value of the choice.
public ChoiceAttribute(string name, int value)
{
this.Name = name;
this.Value = value;
}
///
/// Adds a choice to the slash command option
///
- /// The name of the choice
- /// The value of the choice
+ /// The name of the choice.
+ /// The value of the choice.
public ChoiceAttribute(string name, double value)
{
this.Name = name;
this.Value = value;
}
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
index fc417c8b3..2141f8e06 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
@@ -1,47 +1,47 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
namespace DisCatSharp.ApplicationCommands
{
///
/// Sets the name for this enum choice.
///
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class ChoiceNameAttribute : Attribute
{
///
/// The name.
///
public string Name { get; set; }
///
/// Sets the name for this enum choice.
///
/// The name for this enum choice.
public ChoiceNameAttribute(string name)
{
this.Name = name;
}
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/MinimumMaximumAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/MinimumMaximumAttribute.cs
index b100c6c59..30eef1f0f 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/MinimumMaximumAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/MinimumMaximumAttribute.cs
@@ -1,83 +1,99 @@
// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
namespace DisCatSharp.ApplicationCommands.Attributes
{
///
- /// Sets a minimum value for this slash command option. Only valid for or parameters.
+ /// Sets a minimum value for this slash command option. Only valid for , or parameters.
///
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class MinimumAttribute : Attribute
{
///
/// The value.
///
public object Value { get; internal set; }
///
- /// Sets a minimum value for this slash command option. Only valid for or parameters.
+ /// Sets a minimum value for this slash command option. Only valid for , or parameters.
+ ///
+ public MinimumAttribute(int value)
+ {
+ this.Value = value;
+ }
+
+ ///
+ /// Sets a minimum value for this slash command option. Only valid for , or parameters.
///
public MinimumAttribute(long value)
{
this.Value = value;
}
///
- /// Sets a minimum value for this slash command option. Only valid for or parameters.
+ /// Sets a minimum value for this slash command option. Only valid for , or parameters.
///
public MinimumAttribute(double value)
{
this.Value = value;
}
}
///
- /// Sets a maximum value for this slash command option. Only valid for or parameters.
+ /// Sets a maximum value for this slash command option. Only valid for , or parameters.
///
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class MaximumAttribute : Attribute
{
///
/// The value.
///
public object Value { get; internal set; }
///
- /// Sets a maximum value for this slash command option. Only valid for or parameters.
+ /// Sets a maximum value for this slash command option. Only valid for , or parameters.
+ ///
+ public MaximumAttribute(int value)
+ {
+ this.Value = value;
+ }
+
+ ///
+ /// Sets a maximum value for this slash command option. Only valid for , or parameters.
///
public MaximumAttribute(long value)
{
this.Value = value;
}
///
- /// Sets a maximum value for this slash command option. Only valid for or parameters.
+ /// Sets a maximum value for this slash command option. Only valid for , or parameters.
///
public MaximumAttribute(double value)
{
this.Value = value;
}
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs
index 7a26cb93c..29a221f6b 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs
@@ -1,61 +1,61 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
namespace DisCatSharp.ApplicationCommands
{
///
/// Marks this method as a slash command
///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class SlashCommandAttribute : Attribute
{
///
/// Gets the name of this command
///
public string Name { get; }
///
/// Gets the description of this command
///
public string Description { get; }
///
/// Gets the default permission of this command
///
- public bool DefaultPermission { get; set; }
+ public bool DefaultPermission { get; }
///
/// Marks this method as a slash command
///
- /// The name of this slash command
- /// The description of this slash command
+ /// The name of this slash command.
+ /// The description of this slash command.
/// Whether everyone can execute this command.
public SlashCommandAttribute(string name, string description, bool default_permission = true)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultPermission = default_permission;
}
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
index 9b3231830..aea710da2 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
@@ -1,61 +1,61 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
namespace DisCatSharp.ApplicationCommands
{
///
/// Marks this class a slash command group
///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class SlashCommandGroupAttribute : Attribute
{
///
/// Gets the name of this slash command group
///
public string Name { get; set; }
///
/// Gets the description of this slash command group
///
public string Description { get; set; }
///
/// Gets the default permission of this slash command group
///
public bool DefaultPermission { get; set; }
///
/// Marks this class as a slash command group
///
- /// The name of this slash command group
- /// The description of this slash command group
+ /// The name of this slash command group.
+ /// The description of this slash command group.
/// Whether everyone can execute this command.
public SlashCommandGroupAttribute(string name, string description, bool default_permission = true)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultPermission = default_permission;
}
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs
similarity index 59%
copy from DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
copy to DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs
index fc417c8b3..f9e3da8a9 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs
@@ -1,47 +1,61 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
namespace DisCatSharp.ApplicationCommands
{
///
- /// Sets the name for this enum choice.
+ /// The application commands translation context.
///
- [AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
- public class ChoiceNameAttribute : Attribute
+ public class ApplicationCommandsTranslationContext
{
///
- /// The name.
+ /// Gets the type.
///
- public string Name { get; set; }
+ public Type Type { get; }
///
- /// Sets the name for this enum choice.
+ /// Gets the name.
///
- /// The name for this enum choice.
- public ChoiceNameAttribute(string name)
+ public string Name { get; }
+
+ ///
+ /// Gets the translation json.
+ ///
+ internal string Translations { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ /// The name.
+ internal ApplicationCommandsTranslationContext(Type type, string name)
{
+ this.Type = type;
this.Name = name;
}
+
+ public void AddTranslation(string translation_json)
+ => this.Translations = translation_json;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationResolver.cs b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationResolver.cs
new file mode 100644
index 000000000..12c5905b8
--- /dev/null
+++ b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationResolver.cs
@@ -0,0 +1,153 @@
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
+//
+// Copyright (c) 2021 AITSYS
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using System.Collections.Generic;
+using DisCatSharp.Entities;
+
+namespace DisCatSharp.ApplicationCommands
+{
+ ///
+ /// Represents a application commands translation resolver.
+ ///
+ public class ApplicationCommandsTranslationResolver
+ {
+ #pragma warning disable IDE1006
+ ///
+ /// Gets the group translations.
+ ///
+ public IReadOnlyDictionary GroupTranslations => this._groupTranslations;
+ private Dictionary _groupTranslations => new();
+
+ ///
+ /// Gets the sub group translations.
+ ///
+ public IReadOnlyDictionary SubGroupTranslations => this._subGroupTranslations;
+ private Dictionary _subGroupTranslations => new();
+
+ ///
+ /// Gets the command translations.
+ ///
+ public IReadOnlyDictionary CommandTranslations => this._commandTranslations;
+ private Dictionary _commandTranslations => new();
+
+ #pragma warning restore IDE1006
+ }
+
+ public class GroupTranslation
+ {
+ ///
+ /// Gets the group name.
+ ///
+ public string GroupName { get; set; }
+
+ public DiscordApplicationCommandLocalization GroupNameLocalizations { get; set; }
+
+ public DiscordApplicationCommandLocalization GroupDescriptionLocalizations { get; set; }
+
+ ///
+ /// Gets the sub group translations.
+ ///
+ public Dictionary SubGroupTranslations { get; set; }
+
+ ///
+ /// Gets the command translations.
+ ///
+ public Dictionary CommandTranslations { get; set; }
+ }
+
+ public class SubGroupTranslation
+ {
+ ///
+ /// Gets the sub group name.
+ ///
+ public string SubGroupName { get; set; }
+
+ public DiscordApplicationCommandLocalization SubGroupNameLocalizations { get; set; }
+
+ public DiscordApplicationCommandLocalization SubGroupDescriptionLocalizations { get; set; }
+
+ ///
+ /// Gets the command translations.
+ ///
+ public Dictionary CommandTranslations { get; set; }
+ }
+
+ public class CommandTranslation
+ {
+ ///
+ /// Gets the command name.
+ ///
+ public string CommandName { get; set; }
+
+ public DiscordApplicationCommandLocalization CommandNameLocalizations { get; set; }
+
+ public DiscordApplicationCommandLocalization CommandDescriptionLocalizations { get; set; }
+
+ ///
+ /// Gets the option translations.
+ ///
+ public Dictionary OptionTranslations { get; set; }
+ }
+
+ public class OptionTranslation
+ {
+ ///
+ /// Gets the option name.
+ ///
+ public string OptionName { get; set; }
+
+ public DiscordApplicationCommandLocalization OptionNameLocalizations { get; set; }
+
+ public DiscordApplicationCommandLocalization OptionDescriptionLocalizations { get; set; }
+
+ ///
+ /// Gets the choice translations
+ ///
+ public Dictionary ChoiceTranslations { get; set; }
+
+ ///
+ /// Gets the auto complete choice translations
+ ///
+ public Dictionary AutocompleteChoiceTranslations { get; set; }
+ }
+
+ public class AutocompleteChoiceTranslation
+ {
+ ///
+ /// Gets the autocomplete choice name.
+ ///
+ public string AutocompleteChoiceName { get; set; }
+
+ public DiscordApplicationCommandLocalization AutocompleteNameLocalizations { get; set; }
+
+ }
+
+ public class ChoiceTranslation
+ {
+ ///
+ /// Gets the choice name
+ ///
+ public string ChoiceName { get; set; }
+
+ public DiscordApplicationCommandLocalization ChoiceNameLocalizations { get; set; }
+ }
+}
diff --git a/DisCatSharp.ApplicationCommands/Context/AutocompleteContext.cs b/DisCatSharp.ApplicationCommands/Context/AutocompleteContext.cs
index 20d08fb91..fb09da961 100644
--- a/DisCatSharp.ApplicationCommands/Context/AutocompleteContext.cs
+++ b/DisCatSharp.ApplicationCommands/Context/AutocompleteContext.cs
@@ -1,88 +1,98 @@
// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.ApplicationCommands
{
///
/// Represents a context for an autocomplete interaction.
///
public class AutocompleteContext
{
///
/// The interaction created.
///
public DiscordInteraction Interaction { get; internal set; }
///
/// Gets the client for this interaction.
///
public DiscordClient Client { get; internal set; }
///
/// Gets the guild this interaction was executed in.
///
public DiscordGuild Guild { get; internal set; }
///
/// Gets the channel this interaction was executed in.
///
public DiscordChannel Channel { get; internal set; }
///
/// Gets the user which executed this interaction.
///
public DiscordUser User { get; internal set; }
///
/// Gets the member which executed this interaction, or null if the command is in a DM.
///
public DiscordMember Member
=> this.User is DiscordMember member ? member : null;
+ ///
+ /// Gets the invoking user locale.
+ ///
+ public string Locale { get; internal set; }
+
+ ///
+ /// Gets the guild locale if applicable.
+ ///
+ public string GuildLocale { get; internal set; }
+
///
/// Gets the slash command module this interaction was created in.
///
public ApplicationCommandsExtension ApplicationCommandsExtension { get; internal set; }
///
/// Gets the service provider.
/// This allows passing data around without resorting to static members.
/// Defaults to null.
///
public IServiceProvider Services { get; internal set; } = new ServiceCollection().BuildServiceProvider(true);
///
/// The options already provided.
///
public IReadOnlyList Options { get; internal set; }
///
/// The option to autocomplete.
///
public DiscordInteractionDataOption FocusedOption { get; internal set; }
}
}
diff --git a/DisCatSharp.ApplicationCommands/Context/BaseContext.cs b/DisCatSharp.ApplicationCommands/Context/BaseContext.cs
index c4aea5d85..a7443f35f 100644
--- a/DisCatSharp.ApplicationCommands/Context/BaseContext.cs
+++ b/DisCatSharp.ApplicationCommands/Context/BaseContext.cs
@@ -1,170 +1,180 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.ApplicationCommands
{
///
/// Respresents a base context for application command contexts.
///
public class BaseContext
{
///
/// Gets the interaction that was created.
///
public DiscordInteraction Interaction { get; internal set; }
///
/// Gets the client for this interaction.
///
public DiscordClient Client { get; internal set; }
///
/// Gets the guild this interaction was executed in.
///
public DiscordGuild Guild { get; internal set; }
///
/// Gets the channel this interaction was executed in.
///
public DiscordChannel Channel { get; internal set; }
///
/// Gets the user which executed this interaction.
///
public DiscordUser User { get; internal set; }
///
/// Gets the member which executed this interaction, or null if the command is in a DM.
///
public DiscordMember Member
=> this.User is DiscordMember member ? member : null;
///
/// Gets the application command module this interaction was created in.
///
public ApplicationCommandsExtension ApplicationCommandsExtension { get; internal set; }
///
/// Gets the token for this interaction.
///
public string Token { get; internal set; }
///
/// Gets the id for this interaction.
///
public ulong InteractionId { get; internal set; }
///
/// Gets the name of the command.
///
public string CommandName { get; internal set; }
+ ///
+ /// Gets the invoking user locale.
+ ///
+ public string Locale { get; internal set; }
+
+ ///
+ /// Gets the guild locale if applicable.
+ ///
+ public string GuildLocale { get; internal set; }
+
///
/// Gets the type of this interaction.
///
public ApplicationCommandType Type { get; internal set;}
///
/// Gets the service provider.
/// This allows passing data around without resorting to static members.
/// Defaults to null.
///
public IServiceProvider Services { get; internal set; } = new ServiceCollection().BuildServiceProvider(true);
///
/// Creates a response to this interaction.
/// You must create a response within 3 seconds of this interaction being executed; if the command has the potential to take more than 3 seconds, create a at the start, and edit the response later.
///
/// The type of the response.
/// The data to be sent, if any.
///
public Task CreateResponseAsync(InteractionResponseType type, DiscordInteractionResponseBuilder builder = null)
=> this.Interaction.CreateResponseAsync(type, builder);
///
/// Creates a modal response to this interaction.
///
/// The data to send.
public Task CreateModalResponseAsync(DiscordInteractionModalBuilder builder) =>
this.Interaction.CreateInteractionModalResponseAsync(builder);
///
/// Edits the interaction response.
///
/// The data to edit the response with.
///
public Task EditResponseAsync(DiscordWebhookBuilder builder)
=> this.Interaction.EditOriginalResponseAsync(builder);
///
/// Deletes the interaction response.
///
///
public Task DeleteResponseAsync()
=> this.Interaction.DeleteOriginalResponseAsync();
///
/// Creates a follow up message to the interaction.
///
/// The message to be sent, in the form of a webhook.
/// The created message.
public Task FollowUpAsync(DiscordFollowupMessageBuilder builder)
=> this.Interaction.CreateFollowupMessageAsync(builder);
///
/// Edits a followup message.
///
/// The id of the followup message to edit.
/// The webhook builder.
///
public Task EditFollowupAsync(ulong followupMessageId, DiscordWebhookBuilder builder)
=> this.Interaction.EditFollowupMessageAsync(followupMessageId, builder);
///
/// Deletes a followup message.
///
/// The id of the followup message to delete.
///
public Task DeleteFollowupAsync(ulong followupMessageId)
=> this.Interaction.DeleteFollowupMessageAsync(followupMessageId);
///
/// Gets the followup message.
///
/// The followup message id.
public Task GetFollowupMessageAsync(ulong followupMessageId)
=> this.Interaction.GetFollowupMessageAsync(followupMessageId);
///
/// Gets the original interaction response.
///
/// The original interaction response.
public Task GetOriginalResponseAsync()
=> this.Interaction.GetOriginalResponseAsync();
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs b/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs
similarity index 71%
copy from DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
copy to DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs
index fc417c8b3..5e29739fe 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs
@@ -1,47 +1,46 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
+using System.Collections.Generic;
+using DisCatSharp.Entities;
+using Newtonsoft.Json;
namespace DisCatSharp.ApplicationCommands
{
- ///
- /// Sets the name for this enum choice.
- ///
- [AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
- public class ChoiceNameAttribute : Attribute
+ public class ChoiceTranslator
{
///
- /// The name.
+ /// Gets the choice name.
///
+ [JsonProperty("name")]
public string Name { get; set; }
///
- /// Sets the name for this enum choice.
+ /// Gets the choice name translations.
///
- /// The name for this enum choice.
- public ChoiceNameAttribute(string name)
- {
- this.Name = name;
- }
+ [JsonProperty("name_translations")]
+ internal Dictionary NT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization NameTranslations
+ => new(this.NT);
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs b/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs
similarity index 52%
copy from DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
copy to DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs
index 9b3231830..1c249ee49 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs
@@ -1,61 +1,72 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
+using System.Collections.Generic;
+using DisCatSharp.Entities;
+using DisCatSharp.Enums;
+using Newtonsoft.Json;
namespace DisCatSharp.ApplicationCommands
{
///
- /// Marks this class a slash command group
+ /// Represents a command translator.
///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
- public class SlashCommandGroupAttribute : Attribute
+ internal class CommandTranslator
{
///
- /// Gets the name of this slash command group
+ /// Gets the command name.
///
+ [JsonProperty("name")]
public string Name { get; set; }
///
- /// Gets the description of this slash command group
+ /// Gets the application command type.
+ /// Used to determine whether it is an translator for context menu or not.
///
- public string Description { get; set; }
+ [JsonProperty("type")]
+ public ApplicationCommandType Type { get; set; }
///
- /// Gets the default permission of this slash command group
+ /// Gets the command name translations.
///
- public bool DefaultPermission { get; set; }
+ [JsonProperty("name_translations")]
+ internal Dictionary NT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization NameTranslations
+ => new(this.NT);
///
- /// Marks this class as a slash command group
+ /// Gets the command description translations.
///
- /// The name of this slash command group
- /// The description of this slash command group
- /// Whether everyone can execute this command.
- public SlashCommandGroupAttribute(string name, string description, bool default_permission = true)
- {
- this.Name = name.ToLower();
- this.Description = description;
- this.DefaultPermission = default_permission;
- }
+ [JsonProperty("description_translations")]
+ internal Dictionary DT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization DescriptionTranslations
+ => new(this.DT);
+
+ ///
+ /// Gets the option translators, if applicable.
+ ///
+ [JsonProperty("options")]
+ public List Options { get; set; }
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs b/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs
similarity index 54%
copy from DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
copy to DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs
index 9b3231830..6f0d49384 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs
@@ -1,61 +1,70 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
+using System.Collections.Generic;
+using DisCatSharp.Entities;
+using Newtonsoft.Json;
namespace DisCatSharp.ApplicationCommands
{
///
- /// Marks this class a slash command group
+ /// Represents a group translator.
///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
- public class SlashCommandGroupAttribute : Attribute
+ internal class GroupTranslator
{
///
- /// Gets the name of this slash command group
+ /// Gets the group name.
///
+ [JsonProperty("name")]
public string Name { get; set; }
///
- /// Gets the description of this slash command group
+ /// Gets the group name translations.
///
- public string Description { get; set; }
+ [JsonProperty("name_translations")]
+ internal Dictionary NT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization NameTranslations
+ => new(this.NT);
///
- /// Gets the default permission of this slash command group
+ /// Gets the group description translations.
///
- public bool DefaultPermission { get; set; }
+ [JsonProperty("description_translations")]
+ internal Dictionary DT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization DescriptionTranslations
+ => new(this.DT);
///
- /// Marks this class as a slash command group
+ /// Gets the sub group translators, if applicable.
///
- /// The name of this slash command group
- /// The description of this slash command group
- /// Whether everyone can execute this command.
- public SlashCommandGroupAttribute(string name, string description, bool default_permission = true)
- {
- this.Name = name.ToLower();
- this.Description = description;
- this.DefaultPermission = default_permission;
- }
+ [JsonProperty("groups")]
+ public List SubGroups { get; set; }
+
+ ///
+ /// Gets the command translators, if applicable.
+ ///
+ [JsonProperty("commands")]
+ public List Commands { get; set; }
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs b/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs
similarity index 55%
copy from DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
copy to DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs
index fc417c8b3..47924211c 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs
@@ -1,47 +1,61 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
+using System.Collections.Generic;
+using DisCatSharp.Entities;
+using Newtonsoft.Json;
namespace DisCatSharp.ApplicationCommands
{
- ///
- /// Sets the name for this enum choice.
- ///
- [AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
- public class ChoiceNameAttribute : Attribute
+ public class OptionTranslator
{
///
- /// The name.
+ /// Gets the option name.
///
+ [JsonProperty("name")]
public string Name { get; set; }
///
- /// Sets the name for this enum choice.
+ /// Gets the option name translations.
///
- /// The name for this enum choice.
- public ChoiceNameAttribute(string name)
- {
- this.Name = name;
- }
+ [JsonProperty("name_translations")]
+ internal Dictionary NT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization NameTranslations
+ => new(this.NT);
+
+ ///
+ /// Gets the option description translations.
+ ///
+ [JsonProperty("description_translations")]
+ internal Dictionary DT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization DescriptionTranslations
+ => new(this.DT);
+
+ ///
+ /// Gets the choice translators, if applicable.
+ ///
+ [JsonProperty("choices")]
+ public List Choices { get; set; }
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs b/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs
similarity index 56%
copy from DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs
copy to DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs
index 7a26cb93c..ee612b271 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs
@@ -1,61 +1,64 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
+using System.Collections.Generic;
+using DisCatSharp.Entities;
+using Newtonsoft.Json;
namespace DisCatSharp.ApplicationCommands
{
///
- /// Marks this method as a slash command
+ /// Represents a sub group translator.
///
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
- public class SlashCommandAttribute : Attribute
+ internal class SubGroupTranslator
{
///
- /// Gets the name of this command
+ /// Gets the sub group name.
///
- public string Name { get; }
+ [JsonProperty("name")]
+ public string Name { get; set; }
///
- /// Gets the description of this command
+ /// Gets the sub group name translations.
///
- public string Description { get; }
+ [JsonProperty("name_translations")]
+ internal Dictionary NT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization NameTranslations
+ => new(this.NT);
///
- /// Gets the default permission of this command
+ /// Gets the sub group description translations.
///
- public bool DefaultPermission { get; set; }
+ [JsonProperty("description_translations")]
+ internal Dictionary DT { get; set; }
+ [JsonIgnore]
+ public DiscordApplicationCommandLocalization DescriptionTranslations
+ => new(this.DT);
///
- /// Marks this method as a slash command
+ /// Gets the command translators.
///
- /// The name of this slash command
- /// The description of this slash command
- /// Whether everyone can execute this command.
- public SlashCommandAttribute(string name, string description, bool default_permission = true)
- {
- this.Name = name.ToLower();
- this.Description = description;
- this.DefaultPermission = default_permission;
- }
+ [JsonProperty("commands")]
+ public List Commands { get; set; }
}
}
diff --git a/DisCatSharp.Docs/articles/application_commands/events.md b/DisCatSharp.Docs/articles/application_commands/events.md
index ad59a4e97..3e434135f 100644
--- a/DisCatSharp.Docs/articles/application_commands/events.md
+++ b/DisCatSharp.Docs/articles/application_commands/events.md
@@ -1,94 +1,94 @@
---
-uid: appcommands_events
+uid: application_commands_events
title: Application Commands Events
---
# Application Commands events
Sometimes we need to add a variety of actions and checks before and after executing a command.
We can do this in the commands itself, or we can use special events for this.
## Before execution
The simplest example in this case: checking if the command was executed within the guild.
Suppose we have a certain class with commands that must be executed ONLY in the guilds:
```cs
public class MyGuildCommands : ApplicationCommandsModule
{
[SlashCommand("mute", "Mute user.")]
public static async Task Mute(InteractionContext context)
{
}
[SlashCommand("kick", "Kick user.")]
public static async Task Kick(InteractionContext context)
{
}
[SlashCommand("ban", "Ban user.")]
public static async Task Ban(InteractionContext context)
{
}
}
```
In this case, the easiest way would be to override the method from [ApplicationCommandsModule](xref:DisCatSharp.ApplicationCommands.ApplicationCommandsModule).
```cs
public class MyGuildCommands : ApplicationCommandsModule
{
public override async Task BeforeSlashExecutionAsync(InteractionContext ctx)
{
if (ctx.Guild == null)
return false;
}
[SlashCommand("mute", "Mute user.")]
public static async Task Mute(InteractionContext context)
{
}
[SlashCommand("kick", "Kick user.")]
public static async Task Kick(InteractionContext context)
{
}
[SlashCommand("ban", "Ban user.")]
public static async Task Ban(InteractionContext context)
{
}
}
```
Now, before executing any of these commands, the `BeforeSlashExecutionAsync` method will be executed. You can do anything in it, for example, special logging.
If you return `true`, then the command method will be executed after that, otherwise the execution will end there.
## After execution
If you want to create actions after executing the command, then you need to do the same, but override a different method:
```cs
public override async Task AfterSlashExecutionAsync(InteractionContext ctx)
{
// some actions
}
```
## Context menus
You can also add similar actions for the context menus. But this time, you need to override the other methods:
```cs
public class MyGuildCommands : ApplicationCommandsModule
{
public override async Task BeforeContextMenuExecutionAsync(ContextMenuContext ctx)
{
if (ctx.Guild == null)
return false;
}
public override async Task AfterContextMenuExecutionAsync(ContextMenuContext ctx)
{
// some actions
}
}
```
diff --git a/DisCatSharp.Docs/articles/application_commands/intro.md b/DisCatSharp.Docs/articles/application_commands/intro.md
index 45c4d15a2..401f2a524 100644
--- a/DisCatSharp.Docs/articles/application_commands/intro.md
+++ b/DisCatSharp.Docs/articles/application_commands/intro.md
@@ -1,133 +1,133 @@
---
-uid: appcommands_intro
+uid: application_commands_intro
title: Application Commands Introduction
---
>[!NOTE]
> This article assumes you've recently read the article on *[writing your first bot](xref:basics_first_bot)*.
# Introduction to App Commands
Discord provides built-in commands called: *Application Commands*.
Be sure to install the `DisCatSharp.ApplicationCommands` package from NuGet before continuing.
At the moment it is possible to create such commands:
- Slash commands
- User context menu commands
- Message context menu commands
## Writing an Application Commands
### Creation of the first commands
>[!NOTE]
> In order for the bot to be able to create commands in the guild, it must be added to a guild with `applications.commands` scope.
Each command is a method with the attribute [SlashCommand](xref:DisCatSharp.ApplicationCommands.SlashCommandAttribute) or [ContextMenu](xref:DisCatSharp.ApplicationCommands.ContextMenuAttribute). They must be in classes that inherit from [ApplicationCommandsModule](xref:DisCatSharp.ApplicationCommands.ApplicationCommandsModule).
Also, the first argument to the method must be [InteractionContext](xref:DisCatSharp.ApplicationCommands.InteractionContext) or [ContextMenuContext](xref:DisCatSharp.ApplicationCommands.ContextMenuContext).
Simple slash command:
```cs
public class MyCommand : ApplicationCommandsModule
{
[SlashCommand("my_command", "This is decription of the command.")]
public async Task MySlashCommand(InteractionContext context)
{
}
}
```
Simple context menu command:
```cs
public class MySecondCommand : ApplicationCommandsModule
{
[ContextMenu(ApplicationCommandType.User, "My Command")]
public async Task MyContextMenuCommand(ContextMenuContext context)
{
}
}
```
Now let's add some actions to the commands, for example, send a reply:
```cs
await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
{
Content = "Hello :3"
});
```
If the command will be executed for more than 3 seconds, we must response at the beginning of execution and edit it at the end.
```cs
await context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder());
await Task.Delay(5000); // Simulating a long command execution.
await ctx.EditResponseAsync(new DiscordWebhookBuilder()
{
Content = "Hello :3"
});
```
>[!NOTE]
> Note that you can make your commands static, but then you cannot use [Dependency Injection](xref:commands_dependency_injection) in them.
### Registration of commands
After writing the commands, we must register them. For this we need a [DiscordClient](xref:DisCatSharp.DiscordClient).
```cs
var appCommands = client.UseApplicationCommands();
appCommands.RegisterCommands();
appCommands.RegisterCommands();
```
Simple, isn't it? You can register global and guild commands.
Global commands will be available on all guilds of which the bot is a member. Guild commands will only appear in a specific guild.
>[!NOTE]
>Global commands are updated within an hour, so it is recommended to use guild commands for testing and development.
To register guild commands, it is enough to specify the Id of the guild as the first argument of the registration method.
```cs
var appCommands = client.UseApplicationCommands();
appCommands.RegisterCommands();
appCommands.RegisterCommands();
```
## Command Groups
Sometimes we may need to combine slash commands into groups.
In this case, we need to wrap our class with commands in another class and add the [SlashCommandGroup](xref:DisCatSharp.ApplicationCommands.SlashCommandGroupAttribute) attribute.
```cs
public class MyCommand : ApplicationCommandsModule
{
[SlashCommandGroup("my_command", "This is decription of the command group.")]
public class MyCommandGroup : ApplicationCommandsModule
{
[SlashCommand("first", "This is decription of the command.")]
public async Task MySlashCommand(InteractionContext context)
{
await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
{
Content = "This is first subcommand."
});
}
[SlashCommand("second", "This is decription of the command.")]
public async Task MySecondCommand(InteractionContext context)
{
await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
{
Content = "This is second subcommand."
});
}
}
}
```
Commands will now be available via `/my_command first` and `/my_command second`.
Also, note that both classes must inherit [ApplicationCommandsModule](xref:DisCatSharp.ApplicationCommands.ApplicationCommandsModule).
diff --git a/DisCatSharp.Docs/articles/application_commands/options.md b/DisCatSharp.Docs/articles/application_commands/options.md
index 383e3dbe5..c1ebeb001 100644
--- a/DisCatSharp.Docs/articles/application_commands/options.md
+++ b/DisCatSharp.Docs/articles/application_commands/options.md
@@ -1,217 +1,236 @@
---
-uid: appcommands_options
+uid: application_commands_options
title: Application Commands Options
---
# Slash Commands options
For slash commands, you can create options. They allow users to submit additional information to commands.
Command options can be of the following types:
- string
+- int
- long
+- double
- bool
- [DiscordUser](xref:DisCatSharp.Entities.DiscordUser)
- [DiscordRole](xref:DisCatSharp.Entities.DiscordRole)
- [DiscordChannel](xref:DisCatSharp.Entities.DiscordChannel)
+- [DiscordAttachment](xref:DisCatSharp.Entities.DiscordAttachment)
+- mentionable (ulong)
- Enum
## Basic usage
>[!NOTE]
>Options can only be added in the slash commands. Context menus do not support this!
All of options must contain the [Option](xref:DisCatSharp.ApplicationCommands.OptionAttribute) attribute.
They should be after [InteractionContext](xref:DisCatSharp.ApplicationCommands.InteractionContext).
```cs
public class MyCommand : ApplicationCommandsModule
{
[SlashCommand("my_command", "This is decription of the command.")]
public static async Task MySlashCommand(InteractionContext context, [Option("argument", "This is decription of the option.")] string firstParam)
{
await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
{
Content = firstParam
});
}
}
```
## Choices
Sometimes, we need to allow users to choose from several pre-created options.
We can of course add a string or long parameter and let users guess the options, but why when we can make things more convenient?
We have 3 ways to make choices:
- Enums
- [Choice Attribute](xref:DisCatSharp.ApplicationCommands.ChoiceAttribute)
- [Choice Providers](xref:DisCatSharp.ApplicationCommands.IChoiceProvider)
### Enums
This is the easiest option. We just need to specify the required Enum as a command parameter.
```cs
public class MyCommand : ApplicationCommandsModule
{
[SlashCommand("my_command", "This is decription of the command.")]
public static async Task MySlashCommand(InteractionContext context, [Option("enum_param", "Description")] MyEnum enumParameter)
{
}
}
public enum MyEnum
{
FirstOption,
SecondOption
}
```
In this case, the user will be shown this as options: `FirstOption` and `SecondOption`.
Therefore, if you want to define different names for options without changing the Enum, you can add a special attribute:
```cs
public enum MyEnum
{
[ChoiceName("First option")]
FirstOption,
[ChoiceName("Second option")]
SecondOption
}
```
### Choice Attribute
With this way, you can get rid of unnecessary conversions within the command.
To do this, you need to add one or more [Choice Attributes](xref:DisCatSharp.ApplicationCommands.ChoiceAttribute) before the [Option](xref:DisCatSharp.ApplicationCommands.OptionAttribute) attribute
```cs
[SlashCommand("my_command", "This is decription of the command.")]
public static async Task MySlashCommand(InteractionContext context, [Choice("First option", 1)] [Choice("Second option", 2)] [Option("option", "Description")] long firstParam)
{
}
```
As the first parameter, [Choice](xref:DisCatSharp.ApplicationCommands.ChoiceAttribute) takes a name that will be visible to the user, and the second - a value that will be passed to the command.
You can also use strings.
### Choice Provider
Perhaps the most difficult way. It consists in writing a method that will generate a list of options when registering commands.
This way we don't have to list all of them in the code when there are many of them.
To create your own provider, you need to create a class that inherits [IChoiceProvider](xref:DisCatSharp.ApplicationCommands.IChoiceProvider) and contains the `Provider()` method.
```cs
public class MyChoiceProvider : IChoiceProvider
{
public Task> Provider()
{
}
}
```
As seen above, the method should return a list of [DiscordApplicationCommandOptionChoice](xref:DisCatSharp.Entities.DiscordApplicationCommandOptionChoice).
Now we need to create a list and add items to it:
```cs
var options = new List
{
new DiscordApplicationCommandOptionChoice("First option", 1),
new DiscordApplicationCommandOptionChoice("Second option", 2)
};
return Task.FromResult(options.AsEnumerable());
```
Of course you can generate this list as you like. The main thing is that the method should return this list.
Now let's add our new provider to the command.
```cs
[SlashCommand("my_command", "This is decription of the command.")]
public static async Task MySlashCommand(InteractionContext context, [ChoiceProvider(typeof(MyChoiceProvider))] [Option("option", "Description")] long option)
{
}
```
All the code that we got:
```cs
public class MyCommand : ApplicationCommandsModule
{
[SlashCommand("my_command", "This is decription of the command.")]
public static async Task MySlashCommand(InteractionContext context, [ChoiceProvider(typeof(MyChoiceProvider))] [Option("option", "Description")] long option)
{
}
}
public class MyChoiceProvider : IChoiceProvider
{
public Task> Provider()
{
var options = new List
{
new DiscordApplicationCommandOptionChoice("First option", 1),
new DiscordApplicationCommandOptionChoice("Second option", 2)
};
return Task.FromResult(options.AsEnumerable());
}
}
```
That's all, for a better example for [ChoiceProvider](xref:DisCatSharp.ApplicationCommands.IChoiceProvider) refer to the examples.
## Autocomplete
Autocomplete works in the same way as ChoiceProvider, with one difference:
the method that creates the list of choices is triggered not once when the commands are registered, but whenever the user types a command.
It is advisable to use this method exactly when you have a list that will be updated while the bot is running.
In other cases, when the choices will not change, it is advisable to use the previous methods.
Creating an autocomplete is similar to creating a ChoiceProvider with a few changes:
```cs
public class MyAutocompleteProvider : IAutocompleteProvider
{
public async Task> Provider(AutocompleteContext ctx)
{
var options = new List
{
new DiscordApplicationCommandAutocompleteChoice("First option", 1),
new DiscordApplicationCommandAutocompleteChoice("Second option", 2)
};
return Task.FromResult(options.AsEnumerable());
}
}
```
The changes are that instead of [IChoiceProvider](xref:DisCatSharp.ApplicationCommands.IChoiceProvider), the class inherits [IAutocompleteProvider](xref:DisCatSharp.ApplicationCommands.Attributes.IAutocompleteProvider), and the Provider method should return a list with [DiscordApplicationCommandAutocompleteChoice](xref:DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice).
Now we add it to the command:
```cs
[SlashCommand("my_command", "This is decription of the command.")]
public static async Task MySlashCommand(InteractionContext context, [Autocomplete(typeof(MyAutocompleteProvider))] [Option("option", "Description", true)] long option)
{
}
```
Note that we have not only replaced [ChoiceProvider](xref:DisCatSharp.ApplicationCommands.ChoiceProviderAttribute) with [Autocomplete](xref:DisCatSharp.ApplicationCommands.Attributes.AutocompleteAttribute), but also added `true` to [Option](xref:DisCatSharp.ApplicationCommands.OptionAttribute).
## Channel types
Sometimes we may need to give users the ability to select only a certain type of channels, for example, only text, or voice channels.
This can be done by adding the [ChannelTypes](xref:DisCatSharp.ApplicationCommands.Attributes.ChannelTypesAttribute) attribute to the option with the [DiscordChannel](xref:DisCatSharp.Entities.DiscordChannel) type.
```cs
[SlashCommand("my_command", "This is decription of the command.")]
public static async Task MySlashCommand(InteractionContext context, [Option("channel", "You can select only text channels."), ChannelTypes(ChannelType.Text)] DiscordChannel channel)
{
}
```
This will make it possible to select only text channels.
+
+## Minimum / Maximum Attribute
+
+Sometimes we may need to give users the ability to select only a certain range of values.
+
+This can be done by adding the [Minimum](xref:DisCatSharp.ApplicationCommands.Attributes.MaximumAttribute) and [Maximum](xref:DisCatSharp.ApplicationCommands.Attributes.MaximumAttribute) attribute to the option.
+It can be used only for the types `double`, `int` and `long` tho.
+
+```cs
+[SlashCommand("my_command", "This is decription of the command.")]
+public static async Task MySlashCommand(InteractionContext context, [Option("number", "You can select only a certain range."), Minimum(50), Maximum(100)] int numbers)
+{
+
+}
+```
diff --git a/DisCatSharp.Docs/articles/application_commands/translations/reference.md b/DisCatSharp.Docs/articles/application_commands/translations/reference.md
new file mode 100644
index 000000000..7add6b3d3
--- /dev/null
+++ b/DisCatSharp.Docs/articles/application_commands/translations/reference.md
@@ -0,0 +1,110 @@
+---
+uid: application_commands_translations_reference
+title: Translation Reference
+---
+
+# Translation Reference
+
+> [!NOTE]
+ > DisCatSharp uses [JSON](https://www.json.org) to inject the translations of [Application Commands](https://discord.com/developers/docs/interactions/application-commands).
+
+
+## Command Object
+
+| Key | Value | Description |
+| ------------------------ | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
+| name | string | name of the application command |
+| type | int | [type](#application-command-type) of application command, used to map command types |
+| name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command name |
+| description_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command description, only valid for slash commands |
+| options | array of [Option Objects](#option-object) | array of option objects containing translations |
+
+### Application Command Type
+
+| Type | Value |
+| ---------------------------- | ----- |
+| Slash Command | 1 |
+| User Context Menu Command | 2 |
+| Message Context Menu Command | 3 |
+
+## Command Group Object
+
+| Key | Value | Description |
+| ------------------------ | --------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
+| name | string | name of the application command group |
+| name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command group name |
+| description_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command group description |
+| commands | array of [Command Objects](#command-object) | array of command objects containing translations |
+| groups | array of [Sub Command Group Objects](#sub-command-group-object) | array of sub command group objects containing translations |
+
+## Sub Command Group Object
+
+| Key | Value | Description |
+| ------------------------ | --------------------------------------------- | -------------------------------------------------------------------------------------- |
+| name | string | name of the application command sub group |
+| name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command sub group name |
+| description_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command sub group description |
+| commands | array of [Command Objects](#command-object) | array of command objects containing translations |
+
+## Option Object
+
+| Key | Value | Description |
+| ------------------------ | ------------------------------------------------------- | ----------------------------------------------------------------------------------- |
+| name | string | name of the application command option |
+| name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command option name |
+| description_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command option description |
+| choices | array of [Option Choice Objects](#option-choice-object) | array of option choice objects containing translations |
+
+## Option Choice Object
+
+| Key | Value | Description |
+| ------------------------ | --------------------------------------------- | ----------------------------------------------------------------------------------- |
+| name | string | name of the application command option choice |
+| name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command option choice name |
+
+## Translation KVP
+
+A translation object is a key-value-pair of `"locale": "value"`.
+
+### Example Translation Array:
+```json
+{
+ "en-US": "Hello",
+ "de": "Hallo"
+}
+```
+
+## Valid Locales
+
+| Locale | Language |
+| ------ | --------------------- |
+| da | Danish |
+| de | German |
+| en-GB | English, UK |
+| en-US | English, US |
+| es-ES | Spanish |
+| fr | French |
+| hr | Croatioan |
+| it | Italian |
+| lt | Lithuanian |
+| hu | Hungarian |
+| nl | Dutch |
+| no | Norwegian |
+| pl | Polish |
+| pt-BR | Portuguese, Brazilian |
+| ro | Romanian, Romania |
+| fi | Finnish |
+| sv-SE | Swedish |
+| vi | Vietnamese |
+| tr | Turkish |
+| cs | Czech |
+| el | Greek |
+| bg | Bulgarian |
+| ru | Russian |
+| uk | Ukrainian |
+| hi | Hindi |
+| th | Thai |
+| zh-CN | Chinese, China |
+| ja | Japanese |
+| zh-TW | Chinese, Taiwan |
+| ko | Korean |
diff --git a/DisCatSharp.Docs/articles/application_commands/translations/using.md b/DisCatSharp.Docs/articles/application_commands/translations/using.md
new file mode 100644
index 000000000..c45c4ffc0
--- /dev/null
+++ b/DisCatSharp.Docs/articles/application_commands/translations/using.md
@@ -0,0 +1,197 @@
+---
+uid: application_commands_translations_using
+title: Using Translations
+---
+
+# Using Translations
+
+## Why Do We Outsource Translation In External JSON Files
+
+Pretty simple: It's common to have translations external stored.
+This makes it easier to modify them, while keeping the code itself clean.
+
+## Adding Translations
+
+Translations are added the same way like permissions are added to Application Commands:
+```cs
+const string TRANSLATION_PATH = "translations/";
+
+Client.GetShard(0).GetApplicationCommands().RegisterCommands(1215484634894646844, perms => {
+ perms.AddRole(915747497668915230, true);
+}, translations =>
+{
+ string json = File.ReadAllText(TRANSLATION_PATH + "my_command.json");
+
+ translations.AddTranslation(json);
+});
+
+Client.GetShard(0).GetApplicationCommands().RegisterCommands(1215484634894646844, perms => {
+ perms.AddRole(915747497668915230, true);
+}, translations =>
+{
+ string json = File.ReadAllText(TRANSLATION_PATH + "my_simple_command.json");
+
+ translations.AddTranslation(json);
+});
+```
+
+## Creating The Translation JSON
+
+We split the translation in two categories.
+One for slash command groups and one for normal slash commands and context menu commands.
+The `name` key in the JSON will be mapped to the command / option / choice names internally.
+
+### Translation For Slash Command Groups
+
+Imagine, your class look like the following example:
+```cs
+public class MyCommand : ApplicationCommandsModule
+{
+ [SlashCommandGroup("my_command", "This is decription of the command group.")]
+ public class MyCommandGroup : ApplicationCommandsModule
+ {
+ [SlashCommand("first", "This is decription of the command.")]
+ public async Task MySlashCommand(InteractionContext context)
+ {
+ await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
+ {
+ Content = "This is first subcommand."
+ });
+ }
+ [SlashCommand("second", "This is decription of the command.")]
+ public async Task MySecondCommand(InteractionContext context, [Option("value", "Some string value.")] string value)
+ {
+ await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
+ {
+ Content = "This is second subcommand. The value was " + value
+ });
+ }
+ }
+}
+```
+
+The translation json is a object of [Command Group Objects](xref:application_commands_translations_reference#command-group-object)
+A correct translation json for english and german would look like that:
+```json
+[
+ {
+ "name": "my_command",
+ "name_translations": {
+ "en-US": "my_command",
+ "de": "mein_befehl"
+ },
+ "description_translations": {
+ "en-US": "This is decription of the command group.",
+ "de": "Das ist die Beschreibung der Befehl Gruppe."
+ },
+ "commands": [
+ {
+ "name": "first",
+ "type": 1, // Type 1 for slash command
+ "name_translations": {
+ "en-US": "first",
+ "de": "erste"
+ },
+ "description_translations": {
+ "en-US": "This is decription of the command.",
+ "de": "Das ist die Beschreibung des Befehls."
+ }
+ },
+ {
+ "name": "second",
+ "type": 1, // Type 1 for slash command
+ "name_translations": {
+ "en-US": "second",
+ "de": "zweite"
+ },
+ "description_translations": {
+ "en-US": "This is decription of the command.",
+ "de": "Das ist die Beschreibung des Befehls."
+ },
+ "options": [
+ {
+ "name": "value",
+ "name_translations": {
+ "en-US": "value",
+ "de": "wert"
+ },
+ "description_translations": {
+ "en-US": "Some string value.",
+ "de": "Ein string Wert."
+ }
+ }
+ ]
+ }
+ ]
+ }
+]
+```
+
+### Translation For Slash Commands & Context Menu Commands
+
+Now imagine, that your class look like this example:
+```cs
+public class MySimpleCommands : ApplicationCommandsModule
+{
+ [SlashCommand("my_command", "This is decription of the command.")]
+ public async Task MySlashCommand(InteractionContext context)
+ {
+
+ }
+
+ [ContextMenu(ApplicationCommandType.User, "My Command")]
+ public async Task MyContextMenuCommand(ContextMenuContext context)
+ {
+
+ }
+}
+```
+
+The slash command is a simple [Command Object](xref:application_commands_translations_reference#command-object).
+Same goes for the context menu command, but note that it can't have a description.
+
+Slash Commands has the [type](xref:application_commands_translations_reference#application-command-type) `1` and context menu commands the [type](xref:application_commands_translations_reference#application-command-type) `2` or `3`.
+We use this to determine, where the translation belongs to.
+
+A correct json for this example would look like that:
+```json
+[
+ {
+ "name":"my_command",
+ "type": 1, // Type 1 for slash command
+ "name_translations":{
+ "en-US":"my_command",
+ "de":"mein_befehl"
+ },
+ "description_translations":{
+ "en-US":"This is decription of the command.",
+ "de":"Das ist die Beschreibung des Befehls."
+ }
+ },
+ {
+ "name":"My Command",
+ "type": 2, // Type 2 for user context menu command
+ "name_translations":{
+ "en-US":"My Command",
+ "de":"Mein Befehl"
+ }
+ }
+]
+```
+
+
+## Available Locales
+
+Discord has a limited choice of locales, in particular, the ones you can select in the client.
+To see the available locales, visit [this](xref:application_commands_translations_reference#valid-locales) page.
+
+## Can We Get The User And Guild Locale?
+
+Yes, you can!
+Discord sends the user on all [interaction types](xref:DisCatSharp.InteractionType), except `Ping`.
+
+We introduced two new properties `Locale` and `GuildLocale` on [InteractionContext](xref:DisCatSharp.ApplicationCommands.InteractionContext), [ContextMenuContext](xref:DisCatSharp.ApplicationCommands.ContextMenuContext), [AutoCompleteContext](xref:DisCatSharp.ApplicationCommands.AutocompleteContext) and [DiscordInteraction](xref:DisCatSharp.Entities.DiscordInteraction).
+`Locale` is the locale of the user and always represented.
+`GuildLocale` is only represented, when the interaction is **not** in a DM.
+
+Furthermore we cache known user locales on the [DiscordUser](xref:DisCatSharp.Entities.DiscordUser#DisCatSharp_Entities_DiscordUser_Locale) object.
diff --git a/DisCatSharp.Docs/articles/toc.yml b/DisCatSharp.Docs/articles/toc.yml
index 0a8ab2712..7377f24d2 100644
--- a/DisCatSharp.Docs/articles/toc.yml
+++ b/DisCatSharp.Docs/articles/toc.yml
@@ -1,97 +1,103 @@
- name: Preamble
href: preamble.md
- name: Important Changes
items:
- name: Version 9.8.4
href: important_changes/9_8_4.md
- name: Version 9.8.3
href: important_changes/9_8_3.md
- name: Version 9.8.2
href: important_changes/9_8_2.md
- name: The Basics
items:
- name: Creating a Bot Account
href: basics/bot_account.md
- name: Writing Your First Bot
href: basics/first_bot.md
- name: Bot as Hosted Service
href: basics/web_app.md
- name: Project Templates
href: basics/templates.md
- name: Beyond Basics
items:
- name: Events
href: beyond_basics/events.md
- name: Logging
href: beyond_basics/logging/default.md
items:
- name: The Default Logger
href: beyond_basics/logging/default.md
- name: Third Party Loggers
href: beyond_basics/logging/third_party.md
- name: Intents
href: beyond_basics/intents.md
- name: Sharding
href: beyond_basics/sharding.md
- name: Message Builder
href: beyond_basics/messagebuilder.md
- name: Components
items:
- name: Buttons
href: beyond_basics/components/buttons.md
- name: Select Menu
href: beyond_basics/components/select_menus.md
- name: Workarounds
href: beyond_basics/workarounds.md
- name: Application Commands
items:
- name: Introduction
href: application_commands/intro.md
- name: Options
href: application_commands/options.md
- name: Events
href: application_commands/events.md
+ - name: Translations
+ items:
+ - name: Using Translations
+ href: application_commands/translations/using.md
+ - name: Translation Reference
+ href: application_commands/translations/reference.md
- name: Commands
items:
- name: Introduction
href: commands/intro.md
- name: Command Attributes
href: commands/command_attributes.md
- name: Dependency Injection
href: commands/dependency_injection.md
- name: Customization
items:
- name: Help Formatter
href: commands/help_formatter.md
- name: Argument Converters
href: commands/argument_converters.md
- name: Command Handler
href: commands/command_handler.md
- name: Audio
items:
- name: Lavalink
items:
- name: Setup
href: audio/lavalink/setup.md
- name: Configuration
href: audio/lavalink/configuration.md
- name: Music Commands
href: audio/lavalink/music_commands.md
- name: VoiceNext
items:
- name: Prerequisites
href: audio/voicenext/prerequisites.md
- name: Transmitting
href: audio/voicenext/transmit.md
- name: Receiving
href: audio/voicenext/receive.md
- name: Interactivity
href: interactivity.md
- name: Hosting
href: hosting.md
- name: Miscellaneous
items:
- name: Nightly Builds
href: misc/nightly_builds.md
- name: Reporting Issues
href: misc/reporting_issues.md
diff --git a/DisCatSharp/Clients/DiscordClient.Dispatch.cs b/DisCatSharp/Clients/DiscordClient.Dispatch.cs
index ecff611e0..1650c76f1 100644
--- a/DisCatSharp/Clients/DiscordClient.Dispatch.cs
+++ b/DisCatSharp/Clients/DiscordClient.Dispatch.cs
@@ -1,3225 +1,3226 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Common.Utilities;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Exceptions;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Serialization;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace DisCatSharp
{
///
/// Represents a discord client.
///
public sealed partial class DiscordClient
{
#region Private Fields
private string _sessionId;
private bool _guildDownloadCompleted = false;
#endregion
#region Dispatch Handler
///
/// Handles the dispatch.
///
/// The payload.
internal async Task HandleDispatchAsync(GatewayPayload payload)
{
if (payload.Data is not JObject dat)
{
this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Invalid payload body (this message is probably safe to ignore); opcode: {0} event: {1}; payload: {2}", payload.OpCode, payload.EventName, payload.Data);
return;
}
await this._payloadReceived.InvokeAsync(this, new(this.ServiceProvider)
{
EventName = payload.EventName,
PayloadObject = dat
}).ConfigureAwait(false);
DiscordChannel chn;
ulong gid;
ulong cid;
ulong uid;
DiscordStageInstance stg = default;
DiscordIntegration itg = default;
DiscordThreadChannel trd = default;
DiscordThreadChannelMember trdm = default;
DiscordScheduledEvent gse = default;
TransportUser usr = default;
TransportMember mbr = default;
TransportUser refUsr = default;
TransportMember refMbr = default;
JToken rawMbr = default;
var rawRefMsg = dat["referenced_message"];
switch (payload.EventName.ToLowerInvariant())
{
#region Gateway Status
case "ready":
var glds = (JArray)dat["guilds"];
await this.OnReadyEventAsync(dat.ToObject(), glds).ConfigureAwait(false);
break;
case "resumed":
await this.OnResumedAsync().ConfigureAwait(false);
break;
#endregion
#region Channel
case "channel_create":
chn = dat.ToObject();
await this.OnChannelCreateEventAsync(chn).ConfigureAwait(false);
break;
case "channel_update":
await this.OnChannelUpdateEventAsync(dat.ToObject()).ConfigureAwait(false);
break;
case "channel_delete":
chn = dat.ToObject();
await this.OnChannelDeleteEventAsync(chn.IsPrivate ? dat.ToObject() : chn).ConfigureAwait(false);
break;
case "channel_pins_update":
cid = (ulong)dat["channel_id"];
var ts = (string)dat["last_pin_timestamp"];
await this.OnChannelPinsUpdateAsync((ulong?)dat["guild_id"], cid, ts != null ? DateTimeOffset.Parse(ts, CultureInfo.InvariantCulture) : default(DateTimeOffset?)).ConfigureAwait(false);
break;
#endregion
#region Guild
case "guild_create":
await this.OnGuildCreateEventAsync(dat.ToDiscordObject(), (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false);
break;
case "guild_update":
await this.OnGuildUpdateEventAsync(dat.ToDiscordObject(), (JArray)dat["members"]).ConfigureAwait(false);
break;
case "guild_delete":
await this.OnGuildDeleteEventAsync(dat.ToDiscordObject()).ConfigureAwait(false);
break;
case "guild_sync":
gid = (ulong)dat["id"];
await this.OnGuildSyncEventAsync(this._guilds[gid], (bool)dat["large"], (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false);
break;
case "guild_emojis_update":
gid = (ulong)dat["guild_id"];
var ems = dat["emojis"].ToObject>();
await this.OnGuildEmojisUpdateEventAsync(this._guilds[gid], ems).ConfigureAwait(false);
break;
case "guild_stickers_update":
var strs = dat["stickers"].ToDiscordObject>();
await this.OnStickersUpdatedAsync(strs, dat).ConfigureAwait(false);
break;
case "guild_integrations_update":
gid = (ulong)dat["guild_id"];
// discord fires this event inconsistently if the current user leaves a guild.
if (!this._guilds.ContainsKey(gid))
return;
await this.OnGuildIntegrationsUpdateEventAsync(this._guilds[gid]).ConfigureAwait(false);
break;
/*
Ok soooo.. this isn't documented yet
It seems to be part of the next version of membership screening (https://discord.com/channels/641574644578648068/689591708962652289/845836910991507486)
advaith said the following (https://discord.com/channels/641574644578648068/689591708962652289/845838160047112202):
> iirc it happens when a user leaves a server where they havent completed screening yet
We have to wait till it's documented, but the fields are:
{ "user_id": "snowflake_user", "guild_id": "snowflake_guild" }
We could handle it rn, but due to the fact that it isn't documented, it's not an good idea.
*/
case "guild_join_request_delete":
break;
#endregion
#region Guild Ban
case "guild_ban_add":
usr = dat["user"].ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildBanAddEventAsync(usr, this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_ban_remove":
usr = dat["user"].ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildBanRemoveEventAsync(usr, this._guilds[gid]).ConfigureAwait(false);
break;
#endregion
#region Guild Event
case "guild_scheduled_event_create":
gse = dat.ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventCreateEventAsync(gse, this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_update":
gse = dat.ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventUpdateEventAsync(gse, this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_delete":
gse = dat.ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventDeleteEventAsync(gse, this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_user_add":
gid = (ulong)dat["guild_id"];
uid = (ulong)dat["user_id"];
await this.OnGuildScheduledEventUserAddedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_user_remove":
gid = (ulong)dat["guild_id"];
uid = (ulong)dat["user_id"];
await this.OnGuildScheduledEventUserRemovedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this._guilds[gid]).ConfigureAwait(false);
break;
#endregion
#region Guild Integration
case "integration_create":
gid = (ulong)dat["guild_id"];
itg = dat.ToObject();
// discord fires this event inconsistently if the current user leaves a guild.
if (!this._guilds.ContainsKey(gid))
return;
await this.OnGuildIntegrationCreateEventAsync(this._guilds[gid], itg).ConfigureAwait(false);
break;
case "integration_update":
gid = (ulong)dat["guild_id"];
itg = dat.ToObject();
// discord fires this event inconsistently if the current user leaves a guild.
if (!this._guilds.ContainsKey(gid))
return;
await this.OnGuildIntegrationUpdateEventAsync(this._guilds[gid], itg).ConfigureAwait(false);
break;
case "integration_delete":
gid = (ulong)dat["guild_id"];
// discord fires this event inconsistently if the current user leaves a guild.
if (!this._guilds.ContainsKey(gid))
return;
await this.OnGuildIntegrationDeleteEventAsync(this._guilds[gid], (ulong)dat["id"], (ulong?)dat["application_id"]).ConfigureAwait(false);
break;
#endregion
#region Guild Member
case "guild_member_add":
gid = (ulong)dat["guild_id"];
await this.OnGuildMemberAddEventAsync(dat.ToObject(), this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_member_remove":
gid = (ulong)dat["guild_id"];
usr = dat["user"].ToObject();
if (!this._guilds.ContainsKey(gid))
{
// discord fires this event inconsistently if the current user leaves a guild.
if (usr.Id != this.CurrentUser.Id)
this.Logger.LogError(LoggerEvents.WebSocketReceive, "Could not find {0} in guild cache", gid);
return;
}
await this.OnGuildMemberRemoveEventAsync(usr, this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_member_update":
gid = (ulong)dat["guild_id"];
await this.OnGuildMemberUpdateEventAsync(dat.ToDiscordObject(), this._guilds[gid], dat["roles"].ToObject>(), (string)dat["nick"], (bool?)dat["pending"]).ConfigureAwait(false);
break;
case "guild_members_chunk":
await this.OnGuildMembersChunkEventAsync(dat).ConfigureAwait(false);
break;
#endregion
#region Guild Role
case "guild_role_create":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleCreateEventAsync(dat["role"].ToObject(), this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_role_update":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleUpdateEventAsync(dat["role"].ToObject(), this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_role_delete":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleDeleteEventAsync((ulong)dat["role_id"], this._guilds[gid]).ConfigureAwait(false);
break;
#endregion
#region Invite
case "invite_create":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnInviteCreateEventAsync(cid, gid, dat.ToObject()).ConfigureAwait(false);
break;
case "invite_delete":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnInviteDeleteEventAsync(cid, gid, dat).ConfigureAwait(false);
break;
#endregion
#region Message
case "message_ack":
cid = (ulong)dat["channel_id"];
var mid = (ulong)dat["message_id"];
await this.OnMessageAckEventAsync(this.InternalGetCachedChannel(cid), mid).ConfigureAwait(false);
break;
case "message_create":
rawMbr = dat["member"];
if (rawMbr != null)
mbr = rawMbr.ToObject();
if (rawRefMsg != null && rawRefMsg.HasValues)
{
if (rawRefMsg.SelectToken("author") != null)
{
refUsr = rawRefMsg.SelectToken("author").ToObject();
}
if (rawRefMsg.SelectToken("member") != null)
{
refMbr = rawRefMsg.SelectToken("member").ToObject();
}
}
await this.OnMessageCreateEventAsync(dat.ToDiscordObject(), dat["author"].ToObject(), mbr, refUsr, refMbr).ConfigureAwait(false);
break;
case "message_update":
rawMbr = dat["member"];
if (rawMbr != null)
mbr = rawMbr.ToObject();
if (rawRefMsg != null && rawRefMsg.HasValues)
{
if (rawRefMsg.SelectToken("author") != null)
{
refUsr = rawRefMsg.SelectToken("author").ToObject();
}
if (rawRefMsg.SelectToken("member") != null)
{
refMbr = rawRefMsg.SelectToken("member").ToObject();
}
}
await this.OnMessageUpdateEventAsync(dat.ToDiscordObject(), dat["author"]?.ToObject(), mbr, refUsr, refMbr).ConfigureAwait(false);
break;
// delete event does *not* include message object
case "message_delete":
await this.OnMessageDeleteEventAsync((ulong)dat["id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "message_delete_bulk":
await this.OnMessageBulkDeleteEventAsync(dat["ids"].ToObject(), (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
#endregion
#region Message Reaction
case "message_reaction_add":
rawMbr = dat["member"];
if (rawMbr != null)
mbr = rawMbr.ToObject();
await this.OnMessageReactionAddAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], mbr, dat["emoji"].ToObject()).ConfigureAwait(false);
break;
case "message_reaction_remove":
await this.OnMessageReactionRemoveAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], dat["emoji"].ToObject()).ConfigureAwait(false);
break;
case "message_reaction_remove_all":
await this.OnMessageReactionRemoveAllAsync((ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "message_reaction_remove_emoji":
await this.OnMessageReactionRemoveEmojiAsync((ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong)dat["guild_id"], dat["emoji"]).ConfigureAwait(false);
break;
#endregion
#region Stage Instance
case "stage_instance_create":
stg = dat.ToObject();
await this.OnStageInstanceCreateEventAsync(stg).ConfigureAwait(false);
break;
case "stage_instance_update":
stg = dat.ToObject();
await this.OnStageInstanceUpdateEventAsync(stg).ConfigureAwait(false);
break;
case "stage_instance_delete":
stg = dat.ToObject();
await this.OnStageInstanceDeleteEventAsync(stg).ConfigureAwait(false);
break;
#endregion
#region Thread
case "thread_create":
trd = dat.ToObject();
await this.OnThreadCreateEventAsync(trd).ConfigureAwait(false);
break;
case "thread_update":
trd = dat.ToObject();
await this.OnThreadUpdateEventAsync(trd).ConfigureAwait(false);
break;
case "thread_delete":
trd = dat.ToObject();
await this.OnThreadDeleteEventAsync(trd).ConfigureAwait(false);
break;
case "thread_list_sync":
gid = (ulong)dat["guild_id"]; //get guild
await this.OnThreadListSyncEventAsync(this._guilds[gid], dat["channel_ids"].ToObject>(), dat["threads"].ToObject>(), dat["members"].ToObject>()).ConfigureAwait(false);
break;
case "thread_member_update":
trdm = dat.ToObject();
await this.OnThreadMemberUpdateEventAsync(trdm).ConfigureAwait(false);
break;
case "thread_members_update":
gid = (ulong)dat["guild_id"];
await this.OnThreadMembersUpdateEventAsync(this._guilds[gid], (ulong)dat["id"], (JArray)dat["added_members"], (JArray)dat["removed_member_ids"], (int)dat["member_count"]).ConfigureAwait(false);
break;
#endregion
#region Activities
case "embedded_activity_update":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnEmbeddedActivityUpdateAsync((JObject)dat["embedded_activity"], this._guilds[gid], cid, (JArray)dat["users"], (ulong)dat["embedded_activity"]["application_id"]).ConfigureAwait(false);
break;
#endregion
#region User/Presence Update
case "presence_update":
await this.OnPresenceUpdateEventAsync(dat, (JObject)dat["user"]).ConfigureAwait(false);
break;
case "user_settings_update":
await this.OnUserSettingsUpdateEventAsync(dat.ToObject()).ConfigureAwait(false);
break;
case "user_update":
await this.OnUserUpdateEventAsync(dat.ToObject