Details: Subcommands and Subcommand groups not yet implemented, they will require for some parts of the code to be re-done. More attributes can and should be implemented, such as [Required] and [Choice(... , ...)]. Breakdown: * Rectified line endings to LF, as per the settings of the project. * Added a new command to SlashCommandService and SlashCommandServiceHelper to register the found commands to discord. * Implemented CommandRegistrationOptions that can be used to configure the behaviour on registration - what to do with old commands, and with commands that already exist with the same name. A default version exists and can be accessed with CommandRegistrationOptions.Default * Modified the sample program to reflect the changes made to the SlashCommandService and to also register a new command that tests all 6 types of CommandOptions (except subcommand and subcommand group) * At the moment all commands are registered in my test guild, because the update for global commands is not instant. See SlashCommandServiceHelper.RegisterCommands(...) or line 221. * Modified SlashCommandInfo to parse arguments given from Interaction, unde in ExecuteAsync, and added method BuilDcommand that returns SlashCommandCreationProperties - which can be registered to Discord. * Renamed in the sample project PingCommand.cs to DevModule.cs * Added custom attribute Description for the command method's parameters. * Implemented SlashParameterInfo - and extension of the OptionBuilder that implements a method name Parse - takes DataOptions and gives out a cast object to be passed to the command Delegate. Planning on doing more with it. * Moved SlashCommandBuilder.cs to the same directory structure * Moved SlashCommandModule.cs and ISlashCommandModule.cs to its own folder.pull/1733/head^2^2
@@ -51,6 +51,7 @@ namespace SlashCommandsExample | |||
await socketClient.StartAsync(); | |||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | |||
await _commands.RegisterCommandsAsync(socketClient, CommandRegistrationOptions.Default); | |||
await Task.Delay(-1); | |||
} | |||
@@ -0,0 +1,53 @@ | |||
using Discord.Commands; | |||
using Discord.Commands.SlashCommands.Types; | |||
using Discord.SlashCommands; | |||
using Discord.WebSocket; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace SlashCommandsExample.Modules | |||
{ | |||
public class DevModule : SlashCommandModule<SocketInteraction> | |||
{ | |||
[SlashCommand("ping", "Ping the bot to see if it's alive!")] | |||
public async Task PingAsync() | |||
{ | |||
await Reply(":white_check_mark: **Bot Online**"); | |||
} | |||
[SlashCommand("echo", "I'll repeate everything you said to me, word for word.")] | |||
public async Task EchoAsync([Description("The message you want repetead")]string message) | |||
{ | |||
await Reply($"{Interaction.Member?.Nickname ?? Interaction.Member?.Username} told me to say this: \r\n{message}"); | |||
} | |||
[SlashCommand("overload","Just hit me with every type of data you got, man!")] | |||
public async Task OverloadAsync( | |||
bool boolean, | |||
int integer, | |||
string myString, | |||
SocketGuildChannel channel, | |||
SocketGuildUser user, | |||
SocketRole role | |||
) | |||
{ | |||
await Reply($"You gave me:\r\n {boolean}, {integer}, {myString}, <#{channel?.Id}>, {user?.Mention}, {role?.Mention}"); | |||
} | |||
} | |||
} | |||
/* | |||
The base way of defining a command using the regular command service: | |||
public class PingModule : ModuleBase<SocketCommandContext> | |||
{ | |||
[Command("ping")] | |||
[Summary("Pong! Check if the bot is alive.")] | |||
public async Task PingAsync() | |||
{ | |||
await ReplyAsync(":white_check_mark: **Bot Online**"); | |||
} | |||
} | |||
*/ |
@@ -1,33 +0,0 @@ | |||
using Discord.Commands; | |||
using Discord.Commands.SlashCommands.Types; | |||
using Discord.SlashCommands; | |||
using Discord.WebSocket; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace SlashCommandsExample.Modules | |||
{ | |||
public class PingCommand : SlashCommandModule<SocketInteraction> | |||
{ | |||
[SlashCommand("johnny-test", "Ping the bot to see if it is alive!")] | |||
public async Task PingAsync() | |||
{ | |||
await Interaction.FollowupAsync(":white_check_mark: **Bot Online**"); | |||
} | |||
} | |||
} | |||
/* | |||
The base way of defining a command using the regular command service: | |||
public class PingModule : ModuleBase<SocketCommandContext> | |||
{ | |||
[Command("ping")] | |||
[Summary("Pong! Check if the bot is alive.")] | |||
public async Task PingAsync() | |||
{ | |||
await ReplyAsync(":white_check_mark: **Bot Online**"); | |||
} | |||
} | |||
*/ |
@@ -0,0 +1,31 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.SlashCommands | |||
{ | |||
/// <summary> | |||
/// An Attribute that gives the command parameter a description. | |||
/// </summary> | |||
[AttributeUsage(AttributeTargets.Parameter , AllowMultiple = false)] | |||
public class Description : Attribute | |||
{ | |||
public static string DefaultDescription = "No description."; | |||
/// <summary> | |||
/// The description of this slash command parameter. | |||
/// </summary> | |||
public string description; | |||
/// <summary> | |||
/// Tells the <see cref="SlashCommandService"/> that this parameter has a description. | |||
/// </summary> | |||
/// <param name="commandName">The name of this slash command.</param> | |||
public Description(string description) | |||
{ | |||
this.description = description; | |||
} | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.SlashCommands | |||
{ | |||
/// <summary> | |||
/// The options that should be kept in mind when registering the slash commands to discord. | |||
/// </summary> | |||
public class CommandRegistrationOptions | |||
{ | |||
/// <summary> | |||
/// The options that should be kept in mind when registering the slash commands to discord. | |||
/// </summary> | |||
/// <param name="oldCommands">What to do with the old commands that are already registered with discord</param> | |||
/// <param name="existingCommands"> What to do with the old commands (if they weren't wiped) that we re-define.</param> | |||
public CommandRegistrationOptions(OldCommandOptions oldCommands, ExistingCommandOptions existingCommands) | |||
{ | |||
OldCommands = oldCommands; | |||
ExistingCommands = existingCommands; | |||
} | |||
/// <summary> | |||
/// What to do with the old commands that are already registered with discord | |||
/// </summary> | |||
public OldCommandOptions OldCommands { get; set; } | |||
/// <summary> | |||
/// What to do with the old commands (if they weren't wiped) that we re-define. | |||
/// </summary> | |||
public ExistingCommandOptions ExistingCommands { get; set; } | |||
/// <summary> | |||
/// The default, and reccomended options - Keep the old commands, and overwrite existing commands we re-defined. | |||
/// </summary> | |||
public static CommandRegistrationOptions Default => | |||
new CommandRegistrationOptions(OldCommandOptions.KEEP, ExistingCommandOptions.OVERWRITE); | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.SlashCommands | |||
{ | |||
public enum ExistingCommandOptions | |||
{ | |||
OVERWRITE, | |||
KEEP_EXISTING | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.SlashCommands | |||
{ | |||
public enum OldCommandOptions | |||
{ | |||
/// <summary> | |||
/// Keep the old commands intact - do nothing to them. | |||
/// </summary> | |||
KEEP, | |||
/// <summary> | |||
/// Delete the old commands that we won't be re-defined this time around. | |||
/// </summary> | |||
DELETE_UNUSED, | |||
/// <summary> | |||
/// Delete everything discord has. | |||
/// </summary> | |||
WIPE | |||
} | |||
} |
@@ -1,4 +1,6 @@ | |||
using Discord.Commands; | |||
using Discord.Commands.Builders; | |||
using Discord.WebSocket; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
@@ -21,6 +23,10 @@ namespace Discord.SlashCommands | |||
/// Gets the name of the command. | |||
/// </summary> | |||
public string Description { get; } | |||
/// <summary> | |||
/// The parameters we are expecting - an extension of SlashCommandOptionBuilder | |||
/// </summary> | |||
public List<SlashParameterInfo> Parameters { get; } | |||
/// <summary> | |||
/// The user method as a delegate. We need to use Delegate because there is an unknown number of parameters | |||
@@ -31,11 +37,12 @@ namespace Discord.SlashCommands | |||
/// </summary> | |||
public Func<object[], Task<IResult>> callback; | |||
public SlashCommandInfo(SlashModuleInfo module, string name, string description, Delegate userMethod) | |||
public SlashCommandInfo(SlashModuleInfo module, string name, string description,List<SlashParameterInfo> parameters , Delegate userMethod) | |||
{ | |||
Module = module; | |||
Name = name; | |||
Description = description; | |||
Parameters = parameters; | |||
this.userMethod = userMethod; | |||
this.callback = new Func<object[], Task<IResult>>(async (args) => | |||
{ | |||
@@ -56,9 +63,78 @@ namespace Discord.SlashCommands | |||
}); | |||
} | |||
public async Task<IResult> ExecuteAsync(object[] args) | |||
/// <summary> | |||
/// Execute the function based on the interaction data we get. | |||
/// </summary> | |||
/// <param name="data">Interaction data from interaction</param> | |||
public async Task<IResult> ExecuteAsync(SocketInteractionData data) | |||
{ | |||
return await callback.Invoke(args).ConfigureAwait(false); | |||
// List of arguments to be passed to the Delegate | |||
List<object> args = new List<object>(); | |||
try | |||
{ | |||
foreach (var parameter in Parameters) | |||
{ | |||
// For each parameter to try find its coresponding DataOption based on the name. | |||
// !!! names from `data` will always be lowercase regardless if we defined the command with any | |||
// number of upercase letters !!! | |||
if (TryGetInteractionDataOption(data, parameter.Name, out SocketInteractionDataOption dataOption)) | |||
{ | |||
// Parse the dataOption to one corresponding argument type, and then add it to the list of arguments | |||
args.Add(parameter.Parse(dataOption)); | |||
} | |||
else | |||
{ | |||
// There was no input from the user on this field. | |||
args.Add(null); | |||
} | |||
} | |||
} | |||
catch(Exception e) | |||
{ | |||
return ExecuteResult.FromError(e); | |||
} | |||
return await callback.Invoke(args.ToArray()).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
/// Get the interaction data from the name of the parameter we want to fill in. | |||
/// </summary> | |||
private bool TryGetInteractionDataOption(SocketInteractionData data, string name, out SocketInteractionDataOption dataOption) | |||
{ | |||
if (data.Options == null) | |||
{ | |||
dataOption = null; | |||
return false; | |||
} | |||
foreach (var option in data.Options) | |||
{ | |||
if (option.Name == name.ToLower()) | |||
{ | |||
dataOption = option; | |||
return true; | |||
} | |||
} | |||
dataOption = null; | |||
return false; | |||
} | |||
/// <summary> | |||
/// Build the command and put it in a state in which we can use to define it to Discord. | |||
/// </summary> | |||
public SlashCommandCreationProperties BuildCommand() | |||
{ | |||
SlashCommandBuilder builder = new SlashCommandBuilder(); | |||
builder.WithName(Name); | |||
builder.WithDescription(Description); | |||
builder.Options = new List<SlashCommandOptionBuilder>(); | |||
foreach (var parameter in Parameters) | |||
{ | |||
builder.AddOptions(parameter); | |||
} | |||
return builder.Build(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
using Discord.Commands.Builders; | |||
using Discord.WebSocket; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.SlashCommands | |||
{ | |||
public class SlashParameterInfo : SlashCommandOptionBuilder | |||
{ | |||
public object Parse(SocketInteractionDataOption dataOption) | |||
{ | |||
switch (Type) | |||
{ | |||
case ApplicationCommandOptionType.Boolean: | |||
return (bool)dataOption; | |||
case ApplicationCommandOptionType.Integer: | |||
return (int)dataOption; | |||
case ApplicationCommandOptionType.String: | |||
return (string)dataOption; | |||
case ApplicationCommandOptionType.Channel: | |||
return (SocketGuildChannel)dataOption; | |||
case ApplicationCommandOptionType.Role: | |||
return (SocketRole)dataOption; | |||
case ApplicationCommandOptionType.User: | |||
return (SocketGuildUser)dataOption; | |||
case ApplicationCommandOptionType.SubCommandGroup: | |||
throw new NotImplementedException(); | |||
case ApplicationCommandOptionType.SubCommand: | |||
throw new NotImplementedException(); | |||
} | |||
throw new NotImplementedException($"There is no such type of data... unless we missed it. Please report this error on the Discord.Net github page! Type: {Type}"); | |||
} | |||
} | |||
} |
@@ -34,11 +34,6 @@ namespace Discord.SlashCommands | |||
_logger = new Logger(_logManager, "SlshCommand"); | |||
} | |||
public void AddAssembly() | |||
{ | |||
} | |||
/// <summary> | |||
/// Execute a slash command. | |||
/// </summary> | |||
@@ -50,19 +45,39 @@ namespace Discord.SlashCommands | |||
SlashCommandInfo commandInfo; | |||
if (commandDefs.TryGetValue(interaction.Data.Name, out commandInfo)) | |||
{ | |||
// TODO: implement everything that has to do with parameters :) | |||
// Then, set the context in which the command will be executed | |||
commandInfo.Module.userCommandModule.SetContext(interaction); | |||
// Then run the command (with no parameters) | |||
return await commandInfo.ExecuteAsync(new object[] { }).ConfigureAwait(false); | |||
// Then run the command and pass the interaction data over to the CommandInfo class | |||
return await commandInfo.ExecuteAsync(interaction.Data).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
return SearchResult.FromError(CommandError.UnknownCommand, $"There is no registered slash command with the name {interaction.Data.Name}"); | |||
} | |||
} | |||
/// <summary> | |||
/// Registers all previously scanned commands. | |||
/// </summary> | |||
public async Task RegisterCommandsAsync(DiscordSocketClient socketClient, CommandRegistrationOptions registrationOptions) | |||
{ | |||
// First take a hold of the module lock, as to make sure we aren't editing stuff while we do our business | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
await SlashCommandServiceHelper.RegisterCommands(socketClient, commandDefs, this,registrationOptions).ConfigureAwait(false); | |||
} | |||
finally | |||
{ | |||
_moduleLock.Release(); | |||
} | |||
await _logger.InfoAsync("All commands have been registered!").ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
/// Scans the program for Attribute-based SlashCommandModules | |||
/// </summary> | |||
public async Task AddModulesAsync(Assembly assembly, IServiceProvider services) | |||
{ | |||
// First take a hold of the module lock, as to make sure we aren't editing stuff while we do our business | |||
@@ -75,9 +90,7 @@ namespace Discord.SlashCommands | |||
// Then, based on that, make an instance out of each of them, and get the resulting SlashModuleInfo s | |||
moduleDefs = await SlashCommandServiceHelper.InstantiateModules(types, this).ConfigureAwait(false); | |||
// After that, internally register all of the commands into SlashCommandInfo | |||
commandDefs = await SlashCommandServiceHelper.PrepareAsync(types,moduleDefs,this).ConfigureAwait(false); | |||
// TODO: And finally, register the commands with discord. | |||
await SlashCommandServiceHelper.RegisterCommands(commandDefs, this, services).ConfigureAwait(false); | |||
commandDefs = await SlashCommandServiceHelper.CreateCommandInfos(types,moduleDefs,this).ConfigureAwait(false); | |||
} | |||
finally | |||
{ | |||
@@ -1,3 +1,5 @@ | |||
using Discord.Commands.Builders; | |||
using Discord.WebSocket; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
@@ -63,17 +65,19 @@ namespace Discord.SlashCommands | |||
/// <summary> | |||
/// Prepare all of the commands and register them internally. | |||
/// </summary> | |||
public static async Task<Dictionary<string, SlashCommandInfo>> PrepareAsync(IReadOnlyList<TypeInfo> types, Dictionary<Type, SlashModuleInfo> moduleDefs, SlashCommandService slashCommandService) | |||
public static async Task<Dictionary<string, SlashCommandInfo>> CreateCommandInfos(IReadOnlyList<TypeInfo> types, Dictionary<Type, SlashModuleInfo> moduleDefs, SlashCommandService slashCommandService) | |||
{ | |||
// Create the resulting dictionary ahead of time | |||
var result = new Dictionary<string, SlashCommandInfo>(); | |||
// fore each user-defined module | |||
// For each user-defined module ... | |||
foreach (var userModule in types) | |||
{ | |||
// Get its associated information | |||
// Get its associated information. If there isn't any it means something went wrong, but it's not a critical error. | |||
SlashModuleInfo moduleInfo; | |||
if (moduleDefs.TryGetValue(userModule, out moduleInfo)) | |||
{ | |||
// and get all of its method, and check if they are valid, and if so create a new SlashCommandInfo for them. | |||
// TODO: handle sub-command groups | |||
// And get all of its method, and check if they are valid, and if so create a new SlashCommandInfo for them. | |||
var commandMethods = userModule.GetMethods(); | |||
List<SlashCommandInfo> commandInfos = new List<SlashCommandInfo>(); | |||
foreach (var commandMethod in commandMethods) | |||
@@ -81,13 +85,19 @@ namespace Discord.SlashCommands | |||
SlashCommand slashCommand; | |||
if (IsValidSlashCommand(commandMethod, out slashCommand)) | |||
{ | |||
// Create the delegate for the method we want to call once the user interacts with the bot. | |||
// We use a delegate because of the unknown number and type of parameters we will have. | |||
Delegate delegateMethod = CreateDelegate(commandMethod, moduleInfo.userCommandModule); | |||
SlashCommandInfo commandInfo = new SlashCommandInfo( | |||
module: moduleInfo, | |||
name: slashCommand.commandName, | |||
description: slashCommand.description, | |||
// Generate the parameters. Due to it's complicated way the algorithm has been moved to its own function. | |||
parameters: ConstructCommandParameters(commandMethod), | |||
userMethod: delegateMethod | |||
); | |||
result.Add(slashCommand.commandName, commandInfo); | |||
commandInfos.Add(commandInfo); | |||
} | |||
@@ -97,6 +107,9 @@ namespace Discord.SlashCommands | |||
} | |||
return result; | |||
} | |||
/// <summary> | |||
/// Determines wheater a method can be clasified as a slash command | |||
/// </summary> | |||
private static bool IsValidSlashCommand(MethodInfo method, out SlashCommand slashCommand) | |||
{ | |||
// Verify that we only have one [SlashCommand(...)] attribute | |||
@@ -115,6 +128,66 @@ namespace Discord.SlashCommands | |||
slashCommand = slashCommandAttributes.First() as SlashCommand; | |||
return true; | |||
} | |||
private static List<SlashParameterInfo> ConstructCommandParameters(MethodInfo method) | |||
{ | |||
// Prepare the final list of parameters | |||
List<SlashParameterInfo> finalParameters = new List<SlashParameterInfo>(); | |||
// For each mehod parameter ... | |||
// ex: ... MyCommand(string abc, int myInt) | |||
// `abc` and `myInt` are parameters | |||
foreach (var methodParameter in method.GetParameters()) | |||
{ | |||
SlashParameterInfo newParameter = new SlashParameterInfo(); | |||
// Set the parameter name to that of the method | |||
// TODO: Implement an annotation that lets the user choose a custom name | |||
newParameter.Name = methodParameter.Name; | |||
// Get to see if it has a Description Attribute. | |||
// If it has | |||
// 0 -> then use the default description | |||
// 1 -> Use the value from that attribute | |||
// 2+ -> Throw an error. This shouldn't normaly happen, but we check for sake of sanity | |||
var descriptions = methodParameter.GetCustomAttributes(typeof(Description)); | |||
if (descriptions.Count() == 0) | |||
newParameter.Description = Description.DefaultDescription; | |||
else if (descriptions.Count() > 1) | |||
throw new Exception($"Too many Description attributes on a single parameter ({method.Name} -> {methodParameter.Name}). It can only contain one!"); | |||
else | |||
newParameter.Description = (descriptions.First() as Description).description; | |||
// And get the parameter type | |||
newParameter.Type = TypeFromMethodParameter(methodParameter); | |||
// TODO: implement more attributes, such as [Required] | |||
finalParameters.Add(newParameter); | |||
} | |||
return finalParameters; | |||
} | |||
/// <summary> | |||
/// Get the type of command option from a method parameter info. | |||
/// </summary> | |||
private static ApplicationCommandOptionType TypeFromMethodParameter(ParameterInfo methodParameter) | |||
{ | |||
// Can't do switch -- who knows why? | |||
if (methodParameter.ParameterType == typeof(int)) | |||
return ApplicationCommandOptionType.Integer; | |||
if (methodParameter.ParameterType == typeof(string)) | |||
return ApplicationCommandOptionType.String; | |||
if (methodParameter.ParameterType == typeof(bool)) | |||
return ApplicationCommandOptionType.Boolean; | |||
if (methodParameter.ParameterType == typeof(SocketGuildChannel)) | |||
return ApplicationCommandOptionType.Channel; | |||
if (methodParameter.ParameterType == typeof(SocketRole)) | |||
return ApplicationCommandOptionType.Role; | |||
if (methodParameter.ParameterType == typeof(SocketGuildUser)) | |||
return ApplicationCommandOptionType.User; | |||
throw new Exception($"Got parameter type other than int, string, bool, guild, role, or user. {methodParameter.Name}"); | |||
} | |||
/// <summary> | |||
/// Creae a delegate from methodInfo. Taken from | |||
/// https://stackoverflow.com/a/40579063/8455128 | |||
@@ -143,8 +216,49 @@ namespace Discord.SlashCommands | |||
return Delegate.CreateDelegate(getType(types.ToArray()), target, methodInfo.Name); | |||
} | |||
public static async Task RegisterCommands(Dictionary<string, SlashCommandInfo> commandDefs, SlashCommandService slashCommandService, IServiceProvider services) | |||
public static async Task RegisterCommands(DiscordSocketClient socketClient, Dictionary<string, SlashCommandInfo> commandDefs, SlashCommandService slashCommandService, CommandRegistrationOptions options) | |||
{ | |||
// Get existing commmands | |||
ulong devGuild = 386658607338618891; | |||
var existingCommands = await socketClient.Rest.GetGuildApplicationCommands(devGuild).ConfigureAwait(false); | |||
List<string> existingCommandNames = new List<string>(); | |||
foreach (var existingCommand in existingCommands) | |||
{ | |||
existingCommandNames.Add(existingCommand.Name); | |||
} | |||
// Delete old ones that we want to re-implement | |||
if (options.OldCommands == OldCommandOptions.DELETE_UNUSED || | |||
options.OldCommands == OldCommandOptions.WIPE) | |||
{ | |||
foreach (var existingCommand in existingCommands) | |||
{ | |||
// If we want to wipe all commands | |||
// or if the existing command isn't re-defined (probably code deleted by user) | |||
// remove it from discord. | |||
if(options.OldCommands == OldCommandOptions.WIPE || | |||
!commandDefs.ContainsKey(existingCommand.Name)) | |||
{ | |||
await existingCommand.DeleteAsync(); | |||
} | |||
} | |||
} | |||
foreach (var entry in commandDefs) | |||
{ | |||
if (existingCommandNames.Contains(entry.Value.Name) && | |||
options.ExistingCommands == ExistingCommandOptions.KEEP_EXISTING) | |||
{ | |||
continue; | |||
} | |||
// If it's a new command or we want to overwrite an old one... | |||
else | |||
{ | |||
SlashCommandInfo slashCommandInfo = entry.Value; | |||
SlashCommandCreationProperties command = slashCommandInfo.BuildCommand(); | |||
// TODO: Implement Global and Guild Commands. | |||
await socketClient.Rest.CreateGuildCommand(command, devGuild).ConfigureAwait(false); | |||
} | |||
} | |||
return; | |||
} | |||
} | |||
@@ -1,5 +1,7 @@ | |||
using Discord.Commands.SlashCommands.Types; | |||
using Discord.WebSocket; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Discord.SlashCommands | |||
{ | |||
@@ -17,5 +19,16 @@ namespace Discord.SlashCommands | |||
var newValue = interaction as T; | |||
Interaction = newValue ?? throw new InvalidOperationException($"Invalid interaction type. Expected {typeof(T).Name}, got {interaction.GetType().Name}."); | |||
} | |||
public async Task<IMessage> Reply(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType Type = InteractionResponseType.ChannelMessageWithSource, | |||
AllowedMentions allowedMentions = null, RequestOptions options = null) | |||
{ | |||
if(Interaction is SocketInteraction) | |||
{ | |||
return await (Interaction as SocketInteraction).FollowupAsync(text, isTTS, embed, Type, allowedMentions, options); | |||
} | |||
return null; | |||
} | |||
} | |||
} |
@@ -30,7 +30,7 @@ namespace Discord.WebSocket | |||
internal SocketInteractionDataOption() { } | |||
internal SocketInteractionDataOption(Model model, DiscordSocketClient discord, ulong guild) | |||
{ | |||
this.Name = Name; | |||
this.Name = model.Name; | |||
this.Value = model.Value.IsSpecified ? model.Value.Value : null; | |||
this.discord = discord; | |||
this.guild = guild; | |||
@@ -44,14 +44,17 @@ namespace Discord.WebSocket | |||
// Converters | |||
public static explicit operator bool(SocketInteractionDataOption option) | |||
=> (bool)option.Value; | |||
// The default value is of type long, so an implementaiton of of the long option is trivial | |||
public static explicit operator int(SocketInteractionDataOption option) | |||
=> (int)option.Value; | |||
=> unchecked( | |||
(int)( (long)option.Value ) | |||
); | |||
public static explicit operator string(SocketInteractionDataOption option) | |||
=> option.Value.ToString(); | |||
public static explicit operator SocketGuildChannel(SocketInteractionDataOption option) | |||
{ | |||
if (option.Value is ulong id) | |||
if (ulong.TryParse((string)option.Value, out ulong id)) | |||
{ | |||
var guild = option.discord.GetGuild(option.guild); | |||
@@ -66,7 +69,7 @@ namespace Discord.WebSocket | |||
public static explicit operator SocketRole(SocketInteractionDataOption option) | |||
{ | |||
if (option.Value is ulong id) | |||
if (ulong.TryParse((string)option.Value, out ulong id)) | |||
{ | |||
var guild = option.discord.GetGuild(option.guild); | |||
@@ -81,7 +84,7 @@ namespace Discord.WebSocket | |||
public static explicit operator SocketGuildUser(SocketInteractionDataOption option) | |||
{ | |||
if(option.Value is ulong id) | |||
if (ulong.TryParse((string)option.Value, out ulong id)) | |||
{ | |||
var guild = option.discord.GetGuild(option.guild); | |||