| @@ -0,0 +1,216 @@ | |||||
| # Application commands | |||||
| Application commands are a new feature thats still a work in progress, this guide will show you how to make the best of em. | |||||
| ## Getting started | |||||
| ### Configuring | |||||
| There is a new configuration setting for your DiscordSocketClient called `AlwaysAcknowledgeInteractions`, It's default value is true. | |||||
| Interactions work off of the Recieve -> Respond pipeline, meaning if you dont acknowledge the interaction within 3 seconds its gone forever. | |||||
| With `AlwaysAcknowledgeInteractions` set to true, the client will automatically acknowledge the interaction as its recieved, | |||||
| letting you wait up to 15 minutes before responding with a message. | |||||
| With `AlwaysAcknowledgeInteractions` set to false you will have to acknowledge the interaction yourself via the `InteractionCreated` event | |||||
| ### Registering commands | |||||
| While there is no "easy" way to register command right now, in the future I plan to write a command service to help with that, but right now you have to use the rest | |||||
| client to create your command: | |||||
| ```cs | |||||
| _client.Ready += RegisterCommands | |||||
| ... | |||||
| private async Task RegisterCommands() | |||||
| { | |||||
| // Creating a global command | |||||
| var myGlobalCommand = await _client.Rest.CreateGlobalCommand(new Discord.SlashCommandCreationProperties() | |||||
| { | |||||
| Name = "example", | |||||
| Description = "Runs the example command", | |||||
| Options = new List<Discord.ApplicationCommandOptionProperties>() | |||||
| { | |||||
| new ApplicationCommandOptionProperties() | |||||
| { | |||||
| Name = "Example option", | |||||
| Required = false, | |||||
| Description = "Option Description", | |||||
| Type = Discord.ApplicationCommandOptionType.String, | |||||
| } | |||||
| } | |||||
| }); | |||||
| // Creating a guild command | |||||
| var myGuildCommand = await _client.Rest.CreateGuildCommand(new Discord.SlashCommandCreationProperties() | |||||
| { | |||||
| Name = "guildExample", | |||||
| Description = "Runs the guild example command", | |||||
| Options = new List<Discord.ApplicationCommandOptionProperties>() | |||||
| { | |||||
| new ApplicationCommandOptionProperties() | |||||
| { | |||||
| Name = "Guild example option", | |||||
| Required = false, | |||||
| Description = "Guild option description", | |||||
| Type = Discord.ApplicationCommandOptionType.String, | |||||
| } | |||||
| } | |||||
| }, 1234567890); // <- the guild id | |||||
| } | |||||
| ``` | |||||
| CreateGuildCommand returns a `RestGuildCommand` class which can be used to modify/delete your command on the fly, it also contains details about your command. | |||||
| CreateGlobalCOmmand returns a `RestGlobalCommand` class which can be used to modify/delete your command on the fly, it also contains details about your command. | |||||
| ### Getting a list of all your commands | |||||
| You can fetch a list of all your global commands via rest: | |||||
| ```cs | |||||
| var commands = _client.Rest.GetGlobalApplicationCommands(); | |||||
| ``` | |||||
| This returns a `IReadOnlyCollection<RestGlobalCommand>`. | |||||
| You can also fetch guild specific commands: | |||||
| ```cs | |||||
| var commands = _client.Rest.GetGuildApplicationCommands(1234567890) | |||||
| ``` | |||||
| This returns all the application commands in that guild. | |||||
| ### Responding | |||||
| First thing we want to do is listen to the `InteractionCreated` event. This event is fired when a socket interaction is recieved via the gateway, It looks somthing like this | |||||
| ```cs | |||||
| _client.InteractionCreated += MyEventHandler; | |||||
| ... | |||||
| private async Task MyEventHandler(SocketInteraction arg) | |||||
| { | |||||
| // handle the interaction here | |||||
| } | |||||
| ``` | |||||
| A socket interaction is made up of these properties and methods: | |||||
| | Name | Description | | |||||
| |--------|--------------| | |||||
| | Guild | The `SocketGuild` this interaction was used in | | |||||
| | Channel | The `SocketTextChannel` this interaction was used in | | |||||
| | Member | The `SocketGuildUser` that executed the interaction | | |||||
| | Type | The [InteractionType](https://discord.com/developers/docs/interactions/slash-commands#interaction-interactiontype) of this interaction | | |||||
| | Data | The `SocketInteractionData` associated with this interaction | | |||||
| | Token | The token used to respond to this interaction | | |||||
| | Version | The version of this interaction | | |||||
| | CreatedAt | The time this interaction was created | | |||||
| | IsValidToken | Whether or not the token to respond to this interaction is still valid | | |||||
| | RespondAsync | Responds to the interaction | | |||||
| | FollowupAsync | Sends a followup message to the interaction | | |||||
| #### Whats the difference between `FollowupAsync` and `RespondAsync`? | |||||
| RespondAsync is the initial responce to the interaction, its used to "capture" the interaction, while followup is used to send more messages to the interaction. | |||||
| Basically, you want to first use `RespondAsync` to acknowledge the interaction, then if you need to send anything else regarding that interaction you would use `FollowupAsync` | |||||
| If you have `AlwaysAcknowledgeInteractions` set to true in your client config then it will automatically acknowledge the interaction without sending a message, | |||||
| in this case you can use either or to respond. | |||||
| #### Example ping pong command | |||||
| ```cs | |||||
| _client.InteractionCreated += MyEventHandler; | |||||
| _client.Ready += CreateCommands | |||||
| ... | |||||
| private async Task CreateCommands() | |||||
| { | |||||
| await _client.Rest.CreateGlobalCommand(new Discord.SlashCommandCreationProperties() | |||||
| { | |||||
| Name = "ping", | |||||
| Description = "ping for a pong!", | |||||
| }); | |||||
| } | |||||
| private async Task MyEventHandler(SocketInteraction arg) | |||||
| { | |||||
| switch(arg.Type) // We want to check the type of this interaction | |||||
| { | |||||
| case InteractionType.ApplicationCommand: // If it is a command | |||||
| await MySlashCommandHandler(arg); // Handle the command somewhere | |||||
| break; | |||||
| default: // We dont support it | |||||
| Console.WriteLine("Unsupported interaction type: " + arg.Type); | |||||
| break; | |||||
| } | |||||
| } | |||||
| private async Task MySlashCommandHandler(SocketInteraction arg) | |||||
| { | |||||
| switch(arg.Name) | |||||
| { | |||||
| case "ping": | |||||
| await arg.RespondAsync("Pong!"); | |||||
| break; | |||||
| } | |||||
| } | |||||
| ``` | |||||
| #### Example hug command | |||||
| ```cs | |||||
| _client.InteractionCreated += MyEventHandler; | |||||
| _client.Ready += CreateCommands; | |||||
| ... | |||||
| private async Task CreateCommands() | |||||
| { | |||||
| await _client.Rest.CreateGlobalCommand(new Discord.SlashCommandCreationProperties() | |||||
| { | |||||
| Name = "hug", | |||||
| Description = "Hugs a user!", | |||||
| Options = new List<Discord.ApplicationCommandOptionProperties>() | |||||
| { | |||||
| new ApplicationCommandOptionProperties() | |||||
| { | |||||
| Name = "User", | |||||
| Required = true, | |||||
| Description = "The user to hug", | |||||
| Type = Discord.ApplicationCommandOptionType.User, | |||||
| } | |||||
| } | |||||
| }); | |||||
| } | |||||
| private async Task MyEventHandler(SocketInteraction arg) | |||||
| { | |||||
| switch(arg.Type) // We want to check the type of this interaction | |||||
| { | |||||
| case InteractionType.ApplicationCommand: // If it is a command | |||||
| await MySlashCommandHandler(arg); // Handle the command somewhere | |||||
| break; | |||||
| default: // We dont support it | |||||
| Console.WriteLine("Unsupported interaction type: " + arg.Type); | |||||
| break; | |||||
| } | |||||
| } | |||||
| private async Task MySlashCommandHandler(SocketInteraction arg) | |||||
| { | |||||
| switch(arg.Name) | |||||
| { | |||||
| case "hug": | |||||
| // Get the user argument | |||||
| var option = arg.Data.Options.First(x => x.Name == "user"); | |||||
| // We know that the options value must be a user | |||||
| if(option.Value is SocketGuildUser user) | |||||
| { | |||||
| await arg.RespondAsync($"Hugged {user.Mention}"); | |||||
| } | |||||
| break; | |||||
| } | |||||
| } | |||||
| ``` | |||||
| @@ -0,0 +1,491 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Text.RegularExpressions; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Commands.Builders | |||||
| { | |||||
| /// <summary> | |||||
| /// A class used to build slash commands. | |||||
| /// </summary> | |||||
| public class SlashCommandBuilder | |||||
| { | |||||
| /// <summary> | |||||
| /// Returns the maximun length a commands name allowed by Discord | |||||
| /// </summary> | |||||
| public const int MaxNameLength = 32; | |||||
| /// <summary> | |||||
| /// Returns the maximum length of a commands description allowed by Discord. | |||||
| /// </summary> | |||||
| public const int MaxDescriptionLength = 100; | |||||
| /// <summary> | |||||
| /// Returns the maximum count of command options allowed by Discord | |||||
| /// </summary> | |||||
| public const int MaxOptionsCount = 10; | |||||
| /// <summary> | |||||
| /// The name of this slash command. | |||||
| /// </summary> | |||||
| public string Name | |||||
| { | |||||
| get | |||||
| { | |||||
| return _name; | |||||
| } | |||||
| set | |||||
| { | |||||
| Preconditions.NotNullOrEmpty(value, nameof(Name)); | |||||
| Preconditions.AtLeast(value.Length, 3, nameof(Name)); | |||||
| Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name)); | |||||
| // Discord updated the docs, this regex prevents special characters like @!$%(... etc, | |||||
| // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand | |||||
| if (!Regex.IsMatch(value, @"^[\w-]{3,32}$")) | |||||
| throw new ArgumentException("Command name cannot contian any special characters or whitespaces!"); | |||||
| _name = value; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// A 1-100 length description of this slash command | |||||
| /// </summary> | |||||
| public string Description | |||||
| { | |||||
| get | |||||
| { | |||||
| return _description; | |||||
| } | |||||
| set | |||||
| { | |||||
| Preconditions.AtLeast(value.Length, 1, nameof(Description)); | |||||
| Preconditions.AtMost(value.Length, MaxDescriptionLength, nameof(Description)); | |||||
| _description = value; | |||||
| } | |||||
| } | |||||
| public ulong GuildId | |||||
| { | |||||
| get | |||||
| { | |||||
| return _guildId ?? 0; | |||||
| } | |||||
| set | |||||
| { | |||||
| if (value == 0) | |||||
| { | |||||
| throw new ArgumentException("Guild ID cannot be 0!"); | |||||
| } | |||||
| _guildId = value; | |||||
| if (isGlobal) | |||||
| isGlobal = false; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets or sets the options for this command. | |||||
| /// </summary> | |||||
| public List<SlashCommandOptionBuilder> Options | |||||
| { | |||||
| get | |||||
| { | |||||
| return _options; | |||||
| } | |||||
| set | |||||
| { | |||||
| if (value != null) | |||||
| if (value.Count > MaxOptionsCount) | |||||
| throw new ArgumentException(message: $"Option count must be less than or equal to {MaxOptionsCount}.", paramName: nameof(Options)); | |||||
| _options = value; | |||||
| } | |||||
| } | |||||
| private ulong? _guildId { get; set; } | |||||
| private string _name { get; set; } | |||||
| private string _description { get; set; } | |||||
| private List<SlashCommandOptionBuilder> _options { get; set; } | |||||
| internal bool isGlobal { get; set; } | |||||
| public SlashCommandCreationProperties Build() | |||||
| { | |||||
| SlashCommandCreationProperties props = new SlashCommandCreationProperties() | |||||
| { | |||||
| Name = this.Name, | |||||
| Description = this.Description, | |||||
| }; | |||||
| if(this.Options != null || this.Options.Any()) | |||||
| { | |||||
| var options = new List<ApplicationCommandOptionProperties>(); | |||||
| this.Options.ForEach(x => options.Add(x.Build())); | |||||
| props.Options = options; | |||||
| } | |||||
| return props; | |||||
| } | |||||
| /// <summary> | |||||
| /// Makes this command a global application command . | |||||
| /// </summary> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder MakeGlobal() | |||||
| { | |||||
| this.isGlobal = true; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Makes this command a guild specific command. | |||||
| /// </summary> | |||||
| /// <param name="GuildId">The Id of the target guild.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder ForGuild(ulong GuildId) | |||||
| { | |||||
| this.GuildId = GuildId; | |||||
| return this; | |||||
| } | |||||
| public SlashCommandBuilder WithName(string Name) | |||||
| { | |||||
| this.Name = Name; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets the description of the current command. | |||||
| /// </summary> | |||||
| /// <param name="Description">The description of this command.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder WithDescription(string Description) | |||||
| { | |||||
| this.Description = Description; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds an option to the current slash command. | |||||
| /// </summary> | |||||
| /// <param name="Name">The name of the option to add.</param> | |||||
| /// <param name="Type">The type of this option.</param> | |||||
| /// <param name="Description">The description of this option.</param> | |||||
| /// <param name="Required">If this option is required for this command.</param> | |||||
| /// <param name="Default">If this option is the default option.</param> | |||||
| /// <param name="Options">The options of the option to add.</param> | |||||
| /// <param name="Choices">The choices of this option.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder AddOption(string Name, ApplicationCommandOptionType Type, | |||||
| string Description, bool Required = true, bool Default = false, List<SlashCommandOptionBuilder> Options = null, params ApplicationCommandOptionChoiceProperties[] Choices) | |||||
| { | |||||
| // Make sure the name matches the requirements from discord | |||||
| Preconditions.NotNullOrEmpty(Name, nameof(Name)); | |||||
| Preconditions.AtLeast(Name.Length, 3, nameof(Name)); | |||||
| Preconditions.AtMost(Name.Length, MaxNameLength, nameof(Name)); | |||||
| // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, | |||||
| // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand | |||||
| if (!Regex.IsMatch(Name, @"^[\w-]{3,32}$")) | |||||
| throw new ArgumentException("Command name cannot contian any special characters or whitespaces!", nameof(Name)); | |||||
| // same with description | |||||
| Preconditions.NotNullOrEmpty(Description, nameof(Description)); | |||||
| Preconditions.AtLeast(Description.Length, 3, nameof(Description)); | |||||
| Preconditions.AtMost(Description.Length, MaxDescriptionLength, nameof(Description)); | |||||
| // make sure theres only one option with default set to true | |||||
| if (Default) | |||||
| { | |||||
| if (this.Options != null) | |||||
| if (this.Options.Any(x => x.Default)) | |||||
| throw new ArgumentException("There can only be one command option with default set to true!", nameof(Default)); | |||||
| } | |||||
| SlashCommandOptionBuilder option = new SlashCommandOptionBuilder(); | |||||
| option.Name = Name; | |||||
| option.Description = Description; | |||||
| option.Required = Required; | |||||
| option.Default = Default; | |||||
| option.Options = Options; | |||||
| option.Choices = Choices != null ? new List<ApplicationCommandOptionChoiceProperties>(Choices) : null; | |||||
| return AddOption(option); | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds an option to the current slash command. | |||||
| /// </summary> | |||||
| /// <param name="Name">The name of the option to add.</param> | |||||
| /// <param name="Type">The type of this option.</param> | |||||
| /// <param name="Description">The description of this option.</param> | |||||
| /// <param name="Required">If this option is required for this command.</param> | |||||
| /// <param name="Default">If this option is the default option.</param> | |||||
| /// <param name="Choices">The choices of this option.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder AddOption(string Name, ApplicationCommandOptionType Type, | |||||
| string Description, bool Required = true, bool Default = false, params ApplicationCommandOptionChoiceProperties[] Choices) | |||||
| => AddOption(Name, Type, Description, Required, Default, null, Choices); | |||||
| /// <summary> | |||||
| /// Adds an option to the current slash command. | |||||
| /// </summary> | |||||
| /// <param name="Name">The name of the option to add.</param> | |||||
| /// <param name="Type">The type of this option.</param> | |||||
| /// <param name="Description">The sescription of this option.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder AddOption(string Name, ApplicationCommandOptionType Type, string Description) | |||||
| => AddOption(Name, Type, Description, Options: null, Choices: null); | |||||
| /// <summary> | |||||
| /// Adds an option to this slash command. | |||||
| /// </summary> | |||||
| /// <param name="Parameter">The option to add.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder AddOption(SlashCommandOptionBuilder Option) | |||||
| { | |||||
| if (this.Options == null) | |||||
| this.Options = new List<SlashCommandOptionBuilder>(); | |||||
| if (this.Options.Count >= MaxOptionsCount) | |||||
| throw new ArgumentOutOfRangeException(nameof(Options), $"Cannot have more than {MaxOptionsCount} options!"); | |||||
| if (Option == null) | |||||
| throw new ArgumentNullException(nameof(Option), "Option cannot be null"); | |||||
| this.Options.Add(Option); | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds a collection of options to the current slash command. | |||||
| /// </summary> | |||||
| /// <param name="Parameter">The collection of options to add.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder AddOptions(params SlashCommandOptionBuilder[] Options) | |||||
| { | |||||
| if (Options == null) | |||||
| throw new ArgumentNullException(nameof(Options), "Options cannot be null!"); | |||||
| if (Options.Length == 0) | |||||
| throw new ArgumentException(nameof(Options), "Options cannot be empty!"); | |||||
| if (this.Options == null) | |||||
| this.Options = new List<SlashCommandOptionBuilder>(); | |||||
| if (this.Options.Count + Options.Length > MaxOptionsCount) | |||||
| throw new ArgumentOutOfRangeException(nameof(Options), $"Cannot have more than {MaxOptionsCount} options!"); | |||||
| this.Options.AddRange(Options); | |||||
| return this; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// Represents a class used to build options for the <see cref="SlashCommandBuilder"/>. | |||||
| /// </summary> | |||||
| public class SlashCommandOptionBuilder | |||||
| { | |||||
| /// <summary> | |||||
| /// The max length of a choice's name allowed by Discord. | |||||
| /// </summary> | |||||
| public const int ChoiceNameMaxLength = 100; | |||||
| /// <summary> | |||||
| /// The maximum number of choices allowed by Discord. | |||||
| /// </summary> | |||||
| public const int MaxChoiceCount = 10; | |||||
| private string _name; | |||||
| private string _description; | |||||
| /// <summary> | |||||
| /// The name of this option. | |||||
| /// </summary> | |||||
| public string Name | |||||
| { | |||||
| get => _name; | |||||
| set | |||||
| { | |||||
| if (value?.Length > SlashCommandBuilder.MaxNameLength) | |||||
| throw new ArgumentException("Name length must be less than or equal to 32"); | |||||
| if(value?.Length < 3) | |||||
| throw new ArgumentException("Name length must at least 3 characters in length"); | |||||
| // Discord updated the docs, this regex prevents special characters like @!$%(... etc, | |||||
| // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand | |||||
| if (!Regex.IsMatch(value, @"^[\w-]{3,32}$")) | |||||
| throw new ArgumentException("Option name cannot contian any special characters or whitespaces!"); | |||||
| _name = value; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// The description of this option. | |||||
| /// </summary> | |||||
| public string Description | |||||
| { | |||||
| get => _description; | |||||
| set | |||||
| { | |||||
| if (value?.Length > SlashCommandBuilder.MaxDescriptionLength) | |||||
| throw new ArgumentException("Description length must be less than or equal to 100"); | |||||
| if (value?.Length < 1) | |||||
| throw new ArgumentException("Name length must at least 1 character in length"); | |||||
| _description = value; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// The type of this option. | |||||
| /// </summary> | |||||
| public ApplicationCommandOptionType Type { get; set; } | |||||
| /// <summary> | |||||
| /// The first required option for the user to complete. only one option can be default. | |||||
| /// </summary> | |||||
| public bool Default { get; set; } | |||||
| /// <summary> | |||||
| /// <see langword="true"/> if this option is required for this command, otherwise <see langword="false"/>. | |||||
| /// </summary> | |||||
| public bool Required { get; set; } | |||||
| /// <summary> | |||||
| /// choices for string and int types for the user to pick from. | |||||
| /// </summary> | |||||
| public List<ApplicationCommandOptionChoiceProperties> Choices { get; set; } | |||||
| /// <summary> | |||||
| /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. | |||||
| /// </summary> | |||||
| public List<SlashCommandOptionBuilder> Options { get; set; } | |||||
| /// <summary> | |||||
| /// Builds the current option. | |||||
| /// </summary> | |||||
| /// <returns>The build version of this option</returns> | |||||
| public ApplicationCommandOptionProperties Build() | |||||
| { | |||||
| bool isSubType = this.Type == ApplicationCommandOptionType.SubCommand || this.Type == ApplicationCommandOptionType.SubCommandGroup; | |||||
| if (isSubType && (Options == null || !Options.Any())) | |||||
| throw new ArgumentException(nameof(Options), "SubCommands/SubCommandGroups must have at least one option"); | |||||
| if (!isSubType && (Options != null && Options.Any())) | |||||
| throw new ArgumentException(nameof(Options), $"Cannot have options on {Type} type"); | |||||
| return new ApplicationCommandOptionProperties() | |||||
| { | |||||
| Name = this.Name, | |||||
| Description = this.Description, | |||||
| Default = this.Default, | |||||
| Required = this.Required, | |||||
| Type = this.Type, | |||||
| Options = new List<ApplicationCommandOptionProperties>(this.Options.Select(x => x.Build())), | |||||
| Choices = this.Choices | |||||
| }; | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds a sub | |||||
| /// </summary> | |||||
| /// <param name="option"></param> | |||||
| /// <returns></returns> | |||||
| public SlashCommandOptionBuilder AddOption(SlashCommandOptionBuilder option) | |||||
| { | |||||
| if (this.Options == null) | |||||
| this.Options = new List<SlashCommandOptionBuilder>(); | |||||
| if (this.Options.Count >= SlashCommandBuilder.MaxOptionsCount) | |||||
| throw new ArgumentOutOfRangeException(nameof(Choices), $"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!"); | |||||
| if (option == null) | |||||
| throw new ArgumentNullException(nameof(option), "Option cannot be null"); | |||||
| Options.Add(option); | |||||
| return this; | |||||
| } | |||||
| public SlashCommandOptionBuilder AddChoice(string Name, int Value) | |||||
| { | |||||
| if (Choices == null) | |||||
| Choices = new List<ApplicationCommandOptionChoiceProperties>(); | |||||
| if (Choices.Count >= MaxChoiceCount) | |||||
| throw new ArgumentOutOfRangeException(nameof(Choices), $"Cannot add more than {MaxChoiceCount} choices!"); | |||||
| Choices.Add(new ApplicationCommandOptionChoiceProperties() | |||||
| { | |||||
| Name = Name, | |||||
| Value = Value | |||||
| }); | |||||
| return this; | |||||
| } | |||||
| public SlashCommandOptionBuilder AddChoice(string Name, string Value) | |||||
| { | |||||
| if (Choices == null) | |||||
| Choices = new List<ApplicationCommandOptionChoiceProperties>(); | |||||
| if (Choices.Count >= MaxChoiceCount) | |||||
| throw new ArgumentOutOfRangeException(nameof(Choices), $"Cannot add more than {MaxChoiceCount} choices!"); | |||||
| Choices.Add(new ApplicationCommandOptionChoiceProperties() | |||||
| { | |||||
| Name = Name, | |||||
| Value = Value | |||||
| }); | |||||
| return this; | |||||
| } | |||||
| public SlashCommandOptionBuilder WithName(string Name, int Value) | |||||
| { | |||||
| if (Choices == null) | |||||
| Choices = new List<ApplicationCommandOptionChoiceProperties>(); | |||||
| if (Choices.Count >= MaxChoiceCount) | |||||
| throw new ArgumentOutOfRangeException(nameof(Choices), $"Cannot add more than {MaxChoiceCount} choices!"); | |||||
| Choices.Add(new ApplicationCommandOptionChoiceProperties() | |||||
| { | |||||
| Name = Name, | |||||
| Value = Value | |||||
| }); | |||||
| return this; | |||||
| } | |||||
| public SlashCommandOptionBuilder WithDescription(string Description) | |||||
| { | |||||
| this.Description = Description; | |||||
| return this; | |||||
| } | |||||
| public SlashCommandOptionBuilder WithRequired(bool value) | |||||
| { | |||||
| this.Required = value; | |||||
| return this; | |||||
| } | |||||
| public SlashCommandOptionBuilder WithDefault(bool value) | |||||
| { | |||||
| this.Default = value; | |||||
| return this; | |||||
| } | |||||
| public SlashCommandOptionBuilder WithType(ApplicationCommandOptionType Type) | |||||
| { | |||||
| this.Type = Type; | |||||
| return this; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,6 +1,6 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||
| <Import Project="../../Discord.Net.targets" /> | <Import Project="../../Discord.Net.targets" /> | ||||
| <Import Project="../../StyleAnalyzer.targets"/> | |||||
| <Import Project="../../StyleAnalyzer.targets" /> | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <AssemblyName>Discord.Net.Commands</AssemblyName> | <AssemblyName>Discord.Net.Commands</AssemblyName> | ||||
| <RootNamespace>Discord.Commands</RootNamespace> | <RootNamespace>Discord.Commands</RootNamespace> | ||||
| @@ -0,0 +1,72 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a <see cref="IApplicationCommandOption"/> for making slash commands. | |||||
| /// </summary> | |||||
| public class ApplicationCommandOptionProperties | |||||
| { | |||||
| private string _name; | |||||
| private string _description; | |||||
| /// <summary> | |||||
| /// The name of this option. | |||||
| /// </summary> | |||||
| public string Name | |||||
| { | |||||
| get => _name; | |||||
| set | |||||
| { | |||||
| if (value?.Length > 32) | |||||
| throw new ArgumentException("Name length must be less than or equal to 32"); | |||||
| _name = value; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// The description of this option. | |||||
| /// </summary> | |||||
| public string Description | |||||
| { | |||||
| get => _description; | |||||
| set | |||||
| { | |||||
| if (value?.Length > 100) | |||||
| throw new ArgumentException("Name length must be less than or equal to 32"); | |||||
| _description = value; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// The type of this option. | |||||
| /// </summary> | |||||
| public ApplicationCommandOptionType Type { get; set; } | |||||
| /// <summary> | |||||
| /// The first required option for the user to complete. only one option can be default. | |||||
| /// </summary> | |||||
| public bool? Default { get; set; } | |||||
| /// <summary> | |||||
| /// <see langword="true"/> if this option is required for this command, otherwise <see langword="false"/>. | |||||
| /// </summary> | |||||
| public bool? Required { get; set; } | |||||
| /// <summary> | |||||
| /// choices for string and int types for the user to pick from | |||||
| /// </summary> | |||||
| public List<ApplicationCommandOptionChoiceProperties> Choices { get; set; } | |||||
| /// <summary> | |||||
| /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. | |||||
| /// </summary> | |||||
| public List<ApplicationCommandOptionProperties> Options { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a choice for a <see cref="IApplicationCommandInteractionDataOption"/>. This class is used when making new commands | |||||
| /// </summary> | |||||
| public class ApplicationCommandOptionChoiceProperties | |||||
| { | |||||
| private string _name; | |||||
| private object _value; | |||||
| /// <summary> | |||||
| /// The name of this choice | |||||
| /// </summary> | |||||
| public string Name | |||||
| { | |||||
| get => _name; | |||||
| set | |||||
| { | |||||
| if(value?.Length > 100) | |||||
| throw new ArgumentException("Name length must be less than or equal to 100"); | |||||
| _name = value; | |||||
| } | |||||
| } | |||||
| // Note: discord allows strings & ints as values. how should that be handled? | |||||
| // should we make this an object and then just type check it? | |||||
| /// <summary> | |||||
| /// The value of this choice | |||||
| /// </summary> | |||||
| public object Value | |||||
| { | |||||
| get => _value; | |||||
| set | |||||
| { | |||||
| if(value != null) | |||||
| { | |||||
| if(!(value is int) && !(value is string)) | |||||
| throw new ArgumentException("The value of a choice must be a string or int!"); | |||||
| } | |||||
| _value = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,54 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// The option type of the Slash command parameter, See <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptiontype">the discord docs</see>. | |||||
| /// </summary> | |||||
| public enum ApplicationCommandOptionType : byte | |||||
| { | |||||
| /// <summary> | |||||
| /// A sub command. | |||||
| /// </summary> | |||||
| SubCommand = 1, | |||||
| /// <summary> | |||||
| /// A group of sub commands. | |||||
| /// </summary> | |||||
| SubCommandGroup = 2, | |||||
| /// <summary> | |||||
| /// A <see langword="string"/> of text. | |||||
| /// </summary> | |||||
| String = 3, | |||||
| /// <summary> | |||||
| /// An <see langword="int"/>. | |||||
| /// </summary> | |||||
| Integer = 4, | |||||
| /// <summary> | |||||
| /// A <see langword="bool"/>. | |||||
| /// </summary> | |||||
| Boolean = 5, | |||||
| /// <summary> | |||||
| /// A <see cref="IGuildUser"/>. | |||||
| /// </summary> | |||||
| User = 6, | |||||
| /// <summary> | |||||
| /// A <see cref="IGuildChannel"/>. | |||||
| /// </summary> | |||||
| Channel = 7, | |||||
| /// <summary> | |||||
| /// A <see cref="IRole"/>. | |||||
| /// </summary> | |||||
| Role = 8 | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Provides properties that are used to modify a <see cref="IApplicationCommand" /> with the specified changes. | |||||
| /// </summary> | |||||
| public class ApplicationCommandProperties | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets or sets the name of this command. | |||||
| /// </summary> | |||||
| public Optional<string> Name { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the discription of this command. | |||||
| /// </summary> | |||||
| public Optional<string> Description { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the options for this command. | |||||
| /// </summary> | |||||
| public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// The base command model that belongs to an application. see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommand"/> | |||||
| /// </summary> | |||||
| public interface IApplicationCommand : ISnowflakeEntity | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the unique id of the command. | |||||
| /// </summary> | |||||
| ulong Id { get; } | |||||
| /// <summary> | |||||
| /// Gets the unique id of the parent application. | |||||
| /// </summary> | |||||
| ulong ApplicationId { get; } | |||||
| /// <summary> | |||||
| /// The name of the command. | |||||
| /// </summary> | |||||
| string Name { get; } | |||||
| /// <summary> | |||||
| /// The description of the command. | |||||
| /// </summary> | |||||
| string Description { get; } | |||||
| /// <summary> | |||||
| /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. | |||||
| /// </summary> | |||||
| IReadOnlyCollection<IApplicationCommandOption> Options { get; } | |||||
| /// <summary> | |||||
| /// Deletes this command | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns></returns> | |||||
| Task DeleteAsync(RequestOptions options = null); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/> | |||||
| /// </summary> | |||||
| public interface IApplicationCommandInteractionData | |||||
| { | |||||
| /// <summary> | |||||
| /// The snowflake id of this command | |||||
| /// </summary> | |||||
| ulong Id { get; } | |||||
| /// <summary> | |||||
| /// The name of this command | |||||
| /// </summary> | |||||
| string Name { get; } | |||||
| /// <summary> | |||||
| /// The params + values from the user | |||||
| /// </summary> | |||||
| IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a option group for a command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondataoption"/> | |||||
| /// </summary> | |||||
| public interface IApplicationCommandInteractionDataOption | |||||
| { | |||||
| /// <summary> | |||||
| /// The name of the parameter. | |||||
| /// </summary> | |||||
| string Name { get; } | |||||
| /// <summary> | |||||
| /// The value of the pair. | |||||
| /// <note> | |||||
| /// This objects type can be any one of the option types in <see cref="ApplicationCommandOptionType"/> | |||||
| /// </note> | |||||
| /// </summary> | |||||
| object Value { get; } | |||||
| /// <summary> | |||||
| /// Present if this option is a group or subcommand. | |||||
| /// </summary> | |||||
| IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Options for the <see cref="IApplicationCommand"/>, see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoption"/>The docs</see>. | |||||
| /// </summary> | |||||
| public interface IApplicationCommandOption | |||||
| { | |||||
| /// <summary> | |||||
| /// The type of this <see cref="IApplicationCommandOption"/>. | |||||
| /// </summary> | |||||
| ApplicationCommandOptionType Type { get; } | |||||
| /// <summary> | |||||
| /// The name of this command option, 1-32 character name. | |||||
| /// </summary> | |||||
| string Name { get; } | |||||
| /// <summary> | |||||
| /// The discription of this command option, 1-100 character description. | |||||
| /// </summary> | |||||
| string Description { get; } | |||||
| /// <summary> | |||||
| /// The first required option for the user to complete--only one option can be default. | |||||
| /// </summary> | |||||
| bool? Default { get; } | |||||
| /// <summary> | |||||
| /// If the parameter is required or optional, default is <see langword="false"/>. | |||||
| /// </summary> | |||||
| bool? Required { get; } | |||||
| /// <summary> | |||||
| /// Choices for string and int types for the user to pick from. | |||||
| /// </summary> | |||||
| IReadOnlyCollection<IApplicationCommandOptionChoice>? Choices { get; } | |||||
| /// <summary> | |||||
| /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. | |||||
| /// </summary> | |||||
| IReadOnlyCollection<IApplicationCommandOption>? Options { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Specifies choices for command group. | |||||
| /// </summary> | |||||
| public interface IApplicationCommandOptionChoice | |||||
| { | |||||
| /// <summary> | |||||
| /// 1-100 character choice name. | |||||
| /// </summary> | |||||
| string Name { get; } | |||||
| /// <summary> | |||||
| /// value of the choice. | |||||
| /// </summary> | |||||
| object Value { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a discord interaction | |||||
| /// <para> | |||||
| /// An interaction is the base "thing" that is sent when a user invokes a command, and is the same for Slash Commands | |||||
| /// and other future interaction types. see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction"/>. | |||||
| /// </para> | |||||
| /// </summary> | |||||
| public interface IDiscordInteraction : ISnowflakeEntity | |||||
| { | |||||
| /// <summary> | |||||
| /// The id of the interaction. | |||||
| /// </summary> | |||||
| ulong Id { get; } | |||||
| /// <summary> | |||||
| /// The type of this <see cref="IDiscordInteraction"/>. | |||||
| /// </summary> | |||||
| InteractionType Type { get; } | |||||
| /// <summary> | |||||
| /// The command data payload. | |||||
| /// </summary> | |||||
| IApplicationCommandInteractionData? Data { get; } | |||||
| /// <summary> | |||||
| /// A continuation token for responding to the interaction. | |||||
| /// </summary> | |||||
| string Token { get; } | |||||
| /// <summary> | |||||
| /// read-only property, always 1. | |||||
| /// </summary> | |||||
| int Version { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// The response type for an <see cref="IDiscordInteraction"/>. | |||||
| /// </summary> | |||||
| public enum InteractionResponseType : byte | |||||
| { | |||||
| /// <summary> | |||||
| /// ACK a Ping. | |||||
| /// </summary> | |||||
| Pong = 1, | |||||
| /// <summary> | |||||
| /// ACK a command without sending a message, eating the user's input. | |||||
| /// </summary> | |||||
| Acknowledge = 2, | |||||
| /// <summary> | |||||
| /// Respond with a message, eating the user's input. | |||||
| /// </summary> | |||||
| ChannelMessage = 3, | |||||
| /// <summary> | |||||
| /// Respond with a message, showing the user's input. | |||||
| /// </summary> | |||||
| ChannelMessageWithSource = 4, | |||||
| /// <summary> | |||||
| /// ACK a command without sending a message, showing the user's input. | |||||
| /// </summary> | |||||
| ACKWithSource = 5 | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a type of Interaction from discord. | |||||
| /// </summary> | |||||
| public enum InteractionType : byte | |||||
| { | |||||
| /// <summary> | |||||
| /// A ping from discord. | |||||
| /// </summary> | |||||
| Ping = 1, | |||||
| /// <summary> | |||||
| /// An <see cref="IApplicationCommand"/> sent from discord. | |||||
| /// </summary> | |||||
| ApplicationCommand = 2 | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// A class used to create slash commands | |||||
| /// </summary> | |||||
| public class SlashCommandCreationProperties | |||||
| { | |||||
| /// <summary> | |||||
| /// The name of this command. | |||||
| /// </summary> | |||||
| public string Name { get; set; } | |||||
| /// <summary> | |||||
| /// The discription of this command. | |||||
| /// </summary> | |||||
| public string Description { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the options for this command. | |||||
| /// </summary> | |||||
| public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -64,5 +64,12 @@ namespace Discord | |||||
| /// Only available in API v8. | /// Only available in API v8. | ||||
| /// </remarks> | /// </remarks> | ||||
| Reply = 19, | Reply = 19, | ||||
| /// <summary> | |||||
| /// The message is an Application Command | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Only available in API v8 | |||||
| /// </remarks> | |||||
| ApplicationCommand = 20 | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,23 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class ApplicationCommand | |||||
| { | |||||
| [JsonProperty("id")] | |||||
| public ulong Id { get; set; } | |||||
| [JsonProperty("application_id")] | |||||
| public ulong ApplicationId { get; set; } | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("description")] | |||||
| public string Description { get; set; } | |||||
| [JsonProperty("options")] | |||||
| public Optional<ApplicationCommandOption[]> Options { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class ApplicationCommandInteractionData | |||||
| { | |||||
| [JsonProperty("id")] | |||||
| public ulong Id { get; set; } | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("options")] | |||||
| public Optional<ApplicationCommandInteractionDataOption[]> Options { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class ApplicationCommandInteractionDataOption | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("value")] | |||||
| public Optional<object> Value { get; set; } | |||||
| [JsonProperty("options")] | |||||
| public Optional<IEnumerable<ApplicationCommandInteractionDataOption>> Options { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class ApplicationCommandOption | |||||
| { | |||||
| [JsonProperty("type")] | |||||
| public ApplicationCommandOptionType Type { get; set; } | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("description")] | |||||
| public string Description { get; set; } | |||||
| [JsonProperty("default")] | |||||
| public Optional<bool> Default { get; set; } | |||||
| [JsonProperty("required")] | |||||
| public Optional<bool> Required { get; set; } | |||||
| [JsonProperty("choices")] | |||||
| public Optional<ApplicationCommandOptionChoice[]> Choices { get; set; } | |||||
| [JsonProperty("options")] | |||||
| public Optional<ApplicationCommandOption[]> Options { get; set; } | |||||
| public ApplicationCommandOption() { } | |||||
| public ApplicationCommandOption(IApplicationCommandOption cmd) | |||||
| { | |||||
| this.Choices = cmd.Choices.Select(x => new ApplicationCommandOptionChoice() | |||||
| { | |||||
| Name = x.Name, | |||||
| Value = x.Value | |||||
| }).ToArray(); | |||||
| this.Options = cmd.Options.Select(x => new ApplicationCommandOption(x)).ToArray(); | |||||
| this.Required = cmd.Required.HasValue | |||||
| ? cmd.Required.Value | |||||
| : Optional<bool>.Unspecified; | |||||
| this.Default = cmd.Default.HasValue | |||||
| ? cmd.Default.Value | |||||
| : Optional<bool>.Unspecified; | |||||
| this.Name = cmd.Name; | |||||
| this.Type = cmd.Type; | |||||
| this.Description = cmd.Description; | |||||
| } | |||||
| public ApplicationCommandOption(Discord.ApplicationCommandOptionProperties option) | |||||
| { | |||||
| this.Choices = option.Choices != null | |||||
| ? option.Choices.Select(x => new ApplicationCommandOptionChoice() | |||||
| { | |||||
| Name = x.Name, | |||||
| Value = x.Value | |||||
| }).ToArray() | |||||
| : Optional<ApplicationCommandOptionChoice[]>.Unspecified; | |||||
| this.Options = option.Options != null | |||||
| ? option.Options.Select(x => new ApplicationCommandOption(x)).ToArray() | |||||
| : Optional<ApplicationCommandOption[]>.Unspecified; | |||||
| this.Required = option.Required.Value; | |||||
| this.Default = option.Default.HasValue | |||||
| ? option.Default.Value | |||||
| : Optional<bool>.Unspecified; | |||||
| this.Name = option.Name; | |||||
| this.Type = option.Type; | |||||
| this.Description = option.Description; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class ApplicationCommandOptionChoice | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("value")] | |||||
| public object Value { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class InteractionApplicationCommandCallbackData | |||||
| { | |||||
| [JsonProperty("tts")] | |||||
| public Optional<bool> TTS { get; set; } | |||||
| [JsonProperty("content")] | |||||
| public Optional<string> Content { get; set; } | |||||
| [JsonProperty("embeds")] | |||||
| public Optional<Embed[]> Embeds { get; set; } | |||||
| [JsonProperty("allowed_mentions")] | |||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||||
| public InteractionApplicationCommandCallbackData() { } | |||||
| public InteractionApplicationCommandCallbackData(string text) | |||||
| { | |||||
| this.Content = text; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class InteractionFollowupMessage | |||||
| { | |||||
| public string Content { get; set; } | |||||
| public Optional<string> Username { get; set; } | |||||
| public Optional<string> AvatarUrl { get; set; } | |||||
| public Optional<bool> TTS { get; set; } | |||||
| public Optional<Stream> File { get; set; } | |||||
| public Embed[] Embeds { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class InteractionResponse | |||||
| { | |||||
| [JsonProperty("type")] | |||||
| public InteractionResponseType Type { get; set; } | |||||
| [JsonProperty("data")] | |||||
| public Optional<InteractionApplicationCommandCallbackData> Data { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| using Discord.API; | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| internal class CreateApplicationCommandParams | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("description")] | |||||
| public string Description { get; set; } | |||||
| [JsonProperty("options")] | |||||
| public Optional<ApplicationCommandOption[]> Options { get; set; } | |||||
| public CreateApplicationCommandParams() { } | |||||
| public CreateApplicationCommandParams(string name, string description, ApplicationCommandOption[] options = null) | |||||
| { | |||||
| this.Name = name; | |||||
| this.Description = description; | |||||
| this.Options = Optional.Create<ApplicationCommandOption[]>(options); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| internal class ModifyApplicationCommandParams | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public Optional<string> Name { get; set; } | |||||
| [JsonProperty("description")] | |||||
| public Optional<string> Description { get; set; } | |||||
| [JsonProperty("options")] | |||||
| public Optional<ApplicationCommandOption[]> Options { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| internal class ModifyInteractionResponseParams | |||||
| { | |||||
| [JsonProperty("content")] | |||||
| public string Content { get; set; } | |||||
| [JsonProperty("embeds")] | |||||
| public Optional<Embed[]> Embeds { get; set; } | |||||
| [JsonProperty("allowed_mentions")] | |||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -201,5 +201,24 @@ namespace Discord.Rest | |||||
| } | } | ||||
| }; | }; | ||||
| } | } | ||||
| public static async Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommands(BaseDiscordClient client, RequestOptions options) | |||||
| { | |||||
| var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(options).ConfigureAwait(false); | |||||
| if (!response.Any()) | |||||
| return new RestGlobalCommand[0]; | |||||
| return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); | |||||
| } | |||||
| public static async Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, RequestOptions options) | |||||
| { | |||||
| var response = await client.ApiClient.GetGuildApplicationCommandAsync(guildId, options).ConfigureAwait(false); | |||||
| if (!response.Any()) | |||||
| return new RestGuildCommand[0].ToImmutableArray(); | |||||
| return response.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,6 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||
| <Import Project="../../Discord.Net.targets" /> | <Import Project="../../Discord.Net.targets" /> | ||||
| <Import Project="../../StyleAnalyzer.targets"/> | |||||
| <Import Project="../../StyleAnalyzer.targets" /> | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <AssemblyName>Discord.Net.Rest</AssemblyName> | <AssemblyName>Discord.Net.Rest</AssemblyName> | ||||
| <RootNamespace>Discord.Rest</RootNamespace> | <RootNamespace>Discord.Rest</RootNamespace> | ||||
| @@ -46,8 +46,7 @@ namespace Discord.API | |||||
| internal IRestClient RestClient { get; private set; } | internal IRestClient RestClient { get; private set; } | ||||
| internal ulong? CurrentUserId { get; set; } | internal ulong? CurrentUserId { get; set; } | ||||
| public RateLimitPrecision RateLimitPrecision { get; private set; } | public RateLimitPrecision RateLimitPrecision { get; private set; } | ||||
| internal bool UseSystemClock { get; set; } | |||||
| internal bool UseSystemClock { get; set; } | |||||
| internal JsonSerializer Serializer => _serializer; | internal JsonSerializer Serializer => _serializer; | ||||
| /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
| @@ -59,7 +58,7 @@ namespace Discord.API | |||||
| DefaultRetryMode = defaultRetryMode; | DefaultRetryMode = defaultRetryMode; | ||||
| _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
| RateLimitPrecision = rateLimitPrecision; | RateLimitPrecision = rateLimitPrecision; | ||||
| UseSystemClock = useSystemClock; | |||||
| UseSystemClock = useSystemClock; | |||||
| RequestQueue = new RequestQueue(); | RequestQueue = new RequestQueue(); | ||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| @@ -263,8 +262,8 @@ namespace Discord.API | |||||
| CheckState(); | CheckState(); | ||||
| if (request.Options.RetryMode == null) | if (request.Options.RetryMode == null) | ||||
| request.Options.RetryMode = DefaultRetryMode; | request.Options.RetryMode = DefaultRetryMode; | ||||
| if (request.Options.UseSystemClock == null) | |||||
| request.Options.UseSystemClock = UseSystemClock; | |||||
| if (request.Options.UseSystemClock == null) | |||||
| request.Options.UseSystemClock = UseSystemClock; | |||||
| var stopwatch = Stopwatch.StartNew(); | var stopwatch = Stopwatch.StartNew(); | ||||
| var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false); | var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false); | ||||
| @@ -786,6 +785,163 @@ namespace Discord.API | |||||
| await SendAsync("DELETE", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); | await SendAsync("DELETE", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| //Interactions | |||||
| public async Task<ApplicationCommand[]> GetGlobalApplicationCommandsAsync(RequestOptions options = null) | |||||
| { | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{this.CurrentUserId}/commands", new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<ApplicationCommand> CreateGlobalApplicationCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(command, nameof(command)); | |||||
| Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); | |||||
| Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); | |||||
| Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); | |||||
| Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(command, nameof(command)); | |||||
| if (command.Name.IsSpecified) | |||||
| { | |||||
| Preconditions.AtMost(command.Name.Value.Length, 32, nameof(command.Name)); | |||||
| Preconditions.AtLeast(command.Name.Value.Length, 3, nameof(command.Name)); | |||||
| } | |||||
| if (command.Description.IsSpecified) | |||||
| { | |||||
| Preconditions.AtMost(command.Description.Value.Length, 100, nameof(command.Description)); | |||||
| Preconditions.AtLeast(command.Description.Value.Length, 1, nameof(command.Description)); | |||||
| } | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) | |||||
| { | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendAsync("DELETE", () => $"applications/{this.CurrentUserId}/commands/{commandId}", new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<ApplicationCommand[]> GetGuildApplicationCommandAsync(ulong guildId, RequestOptions options = null) | |||||
| { | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| var bucket = new BucketIds(guildId: guildId); | |||||
| return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", bucket, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<ApplicationCommand> CreateGuildApplicationCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(command, nameof(command)); | |||||
| Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); | |||||
| Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); | |||||
| Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); | |||||
| Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| var bucket = new BucketIds(guildId: guildId); | |||||
| return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<ApplicationCommand> ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(command, nameof(command)); | |||||
| if (command.Name.IsSpecified) | |||||
| { | |||||
| Preconditions.AtMost(command.Name.Value.Length, 32, nameof(command.Name)); | |||||
| Preconditions.AtLeast(command.Name.Value.Length, 3, nameof(command.Name)); | |||||
| } | |||||
| if (command.Description.IsSpecified) | |||||
| { | |||||
| Preconditions.AtMost(command.Description.Value.Length, 100, nameof(command.Description)); | |||||
| Preconditions.AtLeast(command.Description.Value.Length, 1, nameof(command.Description)); | |||||
| } | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| var bucket = new BucketIds(guildId: guildId); | |||||
| return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) | |||||
| { | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| var bucket = new BucketIds(guildId: guildId); | |||||
| await SendAsync<ApplicationCommand>("DELETE", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options).ConfigureAwait(false); | |||||
| } | |||||
| //Interaction Responses | |||||
| public async Task CreateInteractionResponse(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) | |||||
| { | |||||
| if(response.Data.IsSpecified && response.Data.Value.Content.IsSpecified) | |||||
| Preconditions.AtMost(response.Data.Value.Content.Value.Length, 2000, nameof(response.Data.Value.Content)); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendJsonAsync("POST", () => $"interactions/{interactionId}/{interactionToken}/callback", response, new BucketIds(), options: options); | |||||
| } | |||||
| public async Task ModifyInteractionResponse(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null) | |||||
| { | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendJsonAsync("POST", () => $"webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", args, new BucketIds(), options: options); | |||||
| } | |||||
| public async Task DeleteInteractionResponse(string interactionToken, RequestOptions options = null) | |||||
| { | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendAsync("DELETE", () => $"webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", new BucketIds(), options: options); | |||||
| } | |||||
| public async Task<Message> CreateInteractionFollowupMessage(CreateWebhookMessageParams args, string token, RequestOptions options = null) | |||||
| { | |||||
| if (!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) | |||||
| Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||||
| if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| return await SendJsonAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<Message> ModifyInteractionFollowupMessage(CreateWebhookMessageParams args, ulong id, string token, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.NotEqual(id, 0, nameof(id)); | |||||
| if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| return await SendJsonAsync<Message>("PATCH", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", args, new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task DeleteInteractionFollowupMessage(ulong id, string token, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(id, 0, nameof(id)); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendAsync("DELETE", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| //Guilds | //Guilds | ||||
| public async Task<Guild> GetGuildAsync(ulong guildId, bool withCounts, RequestOptions options = null) | public async Task<Guild> GetGuildAsync(ulong guildId, bool withCounts, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -107,6 +107,18 @@ namespace Discord.Rest | |||||
| => ClientHelper.GetVoiceRegionAsync(this, id, options); | => ClientHelper.GetVoiceRegionAsync(this, id, options); | ||||
| public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | ||||
| => ClientHelper.GetWebhookAsync(this, id, options); | => ClientHelper.GetWebhookAsync(this, id, options); | ||||
| public Task<RestGlobalCommand> CreateGlobalCommand(SlashCommandCreationProperties properties, RequestOptions options = null) | |||||
| => InteractionHelper.CreateGlobalCommand(this, properties, options); | |||||
| public Task<RestGlobalCommand> CreateGlobalCommand(Action<SlashCommandCreationProperties> func, RequestOptions options = null) | |||||
| => InteractionHelper.CreateGlobalCommand(this, func, options); | |||||
| public Task<RestGuildCommand> CreateGuildCommand(SlashCommandCreationProperties properties, ulong guildId, RequestOptions options = null) | |||||
| => InteractionHelper.CreateGuildCommand(this, guildId, properties, options); | |||||
| public Task<RestGuildCommand> CreateGuildCommand(Action<SlashCommandCreationProperties> func, ulong guildId, RequestOptions options = null) | |||||
| => InteractionHelper.CreateGuildCommand(this, guildId, func, options); | |||||
| public Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommands(RequestOptions options = null) | |||||
| => ClientHelper.GetGlobalApplicationCommands(this, options); | |||||
| public Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null) | |||||
| => ClientHelper.GetGuildApplicationCommands(this, guildId, options); | |||||
| //IDiscordClient | //IDiscordClient | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -0,0 +1,162 @@ | |||||
| using Discord.API; | |||||
| using Discord.API.Rest; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| internal static class InteractionHelper | |||||
| { | |||||
| internal static async Task<RestUserMessage> SendFollowupAsync(BaseDiscordClient client, API.Rest.CreateWebhookMessageParams args, | |||||
| string token, IMessageChannel channel, RequestOptions options = null) | |||||
| { | |||||
| var model = await client.ApiClient.CreateInteractionFollowupMessage(args, token, options).ConfigureAwait(false); | |||||
| var entity = RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||||
| return entity; | |||||
| } | |||||
| // Global commands | |||||
| internal static async Task<RestGlobalCommand> CreateGlobalCommand(BaseDiscordClient client, | |||||
| Action<SlashCommandCreationProperties> func, RequestOptions options = null) | |||||
| { | |||||
| var args = new SlashCommandCreationProperties(); | |||||
| func(args); | |||||
| return await CreateGlobalCommand(client, args, options).ConfigureAwait(false); | |||||
| } | |||||
| internal static async Task<RestGlobalCommand> CreateGlobalCommand(BaseDiscordClient client, | |||||
| SlashCommandCreationProperties args, RequestOptions options = null) | |||||
| { | |||||
| if (args.Options.IsSpecified) | |||||
| { | |||||
| if (args.Options.Value.Count > 10) | |||||
| throw new ArgumentException("Option count must be 10 or less"); | |||||
| } | |||||
| var model = new CreateApplicationCommandParams() | |||||
| { | |||||
| Name = args.Name, | |||||
| Description = args.Description, | |||||
| Options = args.Options.IsSpecified | |||||
| ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | |||||
| : Optional<Discord.API.ApplicationCommandOption[]>.Unspecified | |||||
| }; | |||||
| var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false); | |||||
| return RestGlobalCommand.Create(client, cmd); | |||||
| } | |||||
| internal static async Task<RestGlobalCommand> ModifyGlobalCommand(BaseDiscordClient client, RestGlobalCommand command, | |||||
| Action<ApplicationCommandProperties> func, RequestOptions options = null) | |||||
| { | |||||
| ApplicationCommandProperties args = new ApplicationCommandProperties(); | |||||
| func(args); | |||||
| if (args.Options.IsSpecified) | |||||
| { | |||||
| if (args.Options.Value.Count > 10) | |||||
| throw new ArgumentException("Option count must be 10 or less"); | |||||
| } | |||||
| var model = new Discord.API.Rest.ModifyApplicationCommandParams() | |||||
| { | |||||
| Name = args.Name, | |||||
| Description = args.Description, | |||||
| Options = args.Options.IsSpecified | |||||
| ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | |||||
| : Optional<Discord.API.ApplicationCommandOption[]>.Unspecified | |||||
| }; | |||||
| var msg = await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false); | |||||
| command.Update(msg); | |||||
| return command; | |||||
| } | |||||
| internal static async Task DeleteGlobalCommand(BaseDiscordClient client, RestGlobalCommand command, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(command, nameof(command)); | |||||
| Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); | |||||
| await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); | |||||
| } | |||||
| // Guild Commands | |||||
| internal static async Task<RestGuildCommand> CreateGuildCommand(BaseDiscordClient client, ulong guildId, | |||||
| Action<SlashCommandCreationProperties> func, RequestOptions options = null) | |||||
| { | |||||
| var args = new SlashCommandCreationProperties(); | |||||
| func(args); | |||||
| return await CreateGuildCommand(client, guildId, args, options).ConfigureAwait(false); | |||||
| } | |||||
| internal static async Task<RestGuildCommand> CreateGuildCommand(BaseDiscordClient client, ulong guildId, | |||||
| SlashCommandCreationProperties args, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); | |||||
| Preconditions.NotNullOrEmpty(args.Description, nameof(args.Description)); | |||||
| if (args.Options.IsSpecified) | |||||
| { | |||||
| if (args.Options.Value.Count > 10) | |||||
| throw new ArgumentException("Option count must be 10 or less"); | |||||
| foreach(var item in args.Options.Value) | |||||
| { | |||||
| Preconditions.NotNullOrEmpty(item.Name, nameof(item.Name)); | |||||
| Preconditions.NotNullOrEmpty(item.Description, nameof(item.Description)); | |||||
| } | |||||
| } | |||||
| var model = new CreateApplicationCommandParams() | |||||
| { | |||||
| Name = args.Name, | |||||
| Description = args.Description, | |||||
| Options = args.Options.IsSpecified | |||||
| ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | |||||
| : Optional<Discord.API.ApplicationCommandOption[]>.Unspecified | |||||
| }; | |||||
| var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); | |||||
| return RestGuildCommand.Create(client, cmd, guildId); | |||||
| } | |||||
| internal static async Task<RestGuildCommand> ModifyGuildCommand(BaseDiscordClient client, RestGuildCommand command, | |||||
| Action<ApplicationCommandProperties> func, RequestOptions options = null) | |||||
| { | |||||
| ApplicationCommandProperties args = new ApplicationCommandProperties(); | |||||
| func(args); | |||||
| if (args.Options.IsSpecified) | |||||
| { | |||||
| if (args.Options.Value.Count > 10) | |||||
| throw new ArgumentException("Option count must be 10 or less"); | |||||
| } | |||||
| var model = new Discord.API.Rest.ModifyApplicationCommandParams() | |||||
| { | |||||
| Name = args.Name, | |||||
| Description = args.Description, | |||||
| Options = args.Options.IsSpecified | |||||
| ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | |||||
| : Optional<Discord.API.ApplicationCommandOption[]>.Unspecified | |||||
| }; | |||||
| var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); | |||||
| command.Update(msg); | |||||
| return command; | |||||
| } | |||||
| internal static async Task DeleteGuildCommand(BaseDiscordClient client, RestGuildCommand command, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotNull(command, nameof(command)); | |||||
| Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); | |||||
| await client.ApiClient.DeleteGuildApplicationCommandAsync(command.GuildId, command.Id, options).ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommand; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a rest implementation of the <see cref="IApplicationCommand"/> | |||||
| /// </summary> | |||||
| public abstract class RestApplicationCommand : RestEntity<ulong>, IApplicationCommand | |||||
| { | |||||
| public ulong ApplicationId { get; private set; } | |||||
| public string Name { get; private set; } | |||||
| public string Description { get; private set; } | |||||
| public IReadOnlyCollection<RestApplicationCommandOption> Options { get; private set; } | |||||
| public RestApplicationCommandType CommandType { get; internal set; } | |||||
| public DateTimeOffset CreatedAt | |||||
| => SnowflakeUtils.FromSnowflake(this.Id); | |||||
| internal RestApplicationCommand(BaseDiscordClient client, ulong id) | |||||
| : base(client, id) | |||||
| { | |||||
| } | |||||
| internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, RestApplicationCommandType type, ulong guildId = 0) | |||||
| { | |||||
| if (type == RestApplicationCommandType.GlobalCommand) | |||||
| return RestGlobalCommand.Create(client, model); | |||||
| if (type == RestApplicationCommandType.GuildCommand) | |||||
| return RestGuildCommand.Create(client, model, guildId); | |||||
| return null; | |||||
| } | |||||
| internal virtual void Update(Model model) | |||||
| { | |||||
| this.ApplicationId = model.ApplicationId; | |||||
| this.Name = model.Name; | |||||
| this.Description = model.Description; | |||||
| this.Options = model.Options.IsSpecified | |||||
| ? model.Options.Value.Select(x => RestApplicationCommandOption.Create(x)).ToImmutableArray() | |||||
| : null; | |||||
| } | |||||
| IReadOnlyCollection<IApplicationCommandOption> IApplicationCommand.Options => Options; | |||||
| public virtual Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommandOptionChoice; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public class RestApplicationCommandChoice : IApplicationCommandOptionChoice | |||||
| { | |||||
| public string Name { get; } | |||||
| public object Value { get; } | |||||
| internal RestApplicationCommandChoice(Model model) | |||||
| { | |||||
| this.Name = model.Name; | |||||
| this.Value = model.Value; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,60 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommandOption; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public class RestApplicationCommandOption : IApplicationCommandOption | |||||
| { | |||||
| public ApplicationCommandOptionType Type { get; private set; } | |||||
| public string Name { get; private set; } | |||||
| public string Description { get; private set; } | |||||
| public bool? Default { get; private set; } | |||||
| public bool? Required { get; private set; } | |||||
| public IReadOnlyCollection<RestApplicationCommandChoice> Choices { get; private set; } | |||||
| public IReadOnlyCollection<RestApplicationCommandOption> Options { get; private set; } | |||||
| internal RestApplicationCommandOption() { } | |||||
| internal static RestApplicationCommandOption Create(Model model) | |||||
| { | |||||
| var options = new RestApplicationCommandOption(); | |||||
| options.Update(model); | |||||
| return options; | |||||
| } | |||||
| internal void Update(Model model) | |||||
| { | |||||
| this.Type = model.Type; | |||||
| this.Name = model.Name; | |||||
| this.Description = model.Description; | |||||
| if (model.Default.IsSpecified) | |||||
| this.Default = model.Default.Value; | |||||
| if (model.Required.IsSpecified) | |||||
| this.Required = model.Required.Value; | |||||
| this.Options = model.Options.IsSpecified | |||||
| ? model.Options.Value.Select(x => Create(x)).ToImmutableArray() | |||||
| : null; | |||||
| this.Choices = model.Choices.IsSpecified | |||||
| ? model.Choices.Value.Select(x => new RestApplicationCommandChoice(x)).ToImmutableArray() | |||||
| : null; | |||||
| } | |||||
| IReadOnlyCollection<IApplicationCommandOption> IApplicationCommandOption.Options => Options; | |||||
| IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices => Choices; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public enum RestApplicationCommandType | |||||
| { | |||||
| GlobalCommand, | |||||
| GuildCommand | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,41 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommand; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a global Slash command | |||||
| /// </summary> | |||||
| public class RestGlobalCommand : RestApplicationCommand | |||||
| { | |||||
| internal RestGlobalCommand(BaseDiscordClient client, ulong id) | |||||
| : base(client, id) | |||||
| { | |||||
| this.CommandType = RestApplicationCommandType.GlobalCommand; | |||||
| } | |||||
| internal static RestGlobalCommand Create(BaseDiscordClient client, Model model) | |||||
| { | |||||
| var entity = new RestGlobalCommand(client, model.Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| public override async Task DeleteAsync(RequestOptions options = null) | |||||
| => await InteractionHelper.DeleteGlobalCommand(Discord, this).ConfigureAwait(false); | |||||
| /// <summary> | |||||
| /// Modifies this <see cref="RestApplicationCommand"/>. | |||||
| /// </summary> | |||||
| /// <param name="func">The delegate containing the properties to modify the command with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// The modified command | |||||
| /// </returns> | |||||
| public async Task<RestGlobalCommand> ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null) | |||||
| => await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,41 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommand; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public class RestGuildCommand : RestApplicationCommand | |||||
| { | |||||
| public ulong GuildId { get; set; } | |||||
| internal RestGuildCommand(BaseDiscordClient client, ulong id, ulong guildId) | |||||
| : base(client, id) | |||||
| { | |||||
| this.CommandType = RestApplicationCommandType.GuildCommand; | |||||
| this.GuildId = guildId; | |||||
| } | |||||
| internal static RestGuildCommand Create(BaseDiscordClient client, Model model, ulong guildId) | |||||
| { | |||||
| var entity = new RestGuildCommand(client, model.Id, guildId); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| public override async Task DeleteAsync(RequestOptions options = null) | |||||
| => await InteractionHelper.DeleteGuildCommand(Discord, this).ConfigureAwait(false); | |||||
| /// <summary> | |||||
| /// Modifies this <see cref="RestApplicationCommand"/>. | |||||
| /// </summary> | |||||
| /// <param name="func">The delegate containing the properties to modify the command with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// The modified command | |||||
| /// </returns> | |||||
| public async Task<RestGuildCommand> ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null) | |||||
| => await InteractionHelper.ModifyGuildCommand(Discord, this, func, options).ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API.Gateway | |||||
| { | |||||
| internal class ApplicationCommandCreatedUpdatedEvent | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("id")] | |||||
| public ulong Id { get; set; } | |||||
| [JsonProperty("description")] | |||||
| public string Description { get; set; } | |||||
| [JsonProperty("application_id")] | |||||
| public ulong ApplicationId { get; set; } | |||||
| [JsonProperty("guild_id")] | |||||
| public ulong GuildId { get; set; } | |||||
| [JsonProperty("options")] | |||||
| public List<Discord.API.ApplicationCommandOption> Options { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| using Discord.API; | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API.Gateway | |||||
| { | |||||
| internal class InteractionCreated | |||||
| { | |||||
| [JsonProperty("id")] | |||||
| public ulong Id { get; set; } | |||||
| [JsonProperty("type")] | |||||
| public InteractionType Type { get; set; } | |||||
| [JsonProperty("data")] | |||||
| public Optional<ApplicationCommandInteractionData> Data { get; set; } | |||||
| [JsonProperty("guild_id")] | |||||
| public ulong GuildId { get; set; } | |||||
| [JsonProperty("channel_id")] | |||||
| public ulong ChannelId { get; set; } | |||||
| [JsonProperty("member")] | |||||
| public GuildMember Member { get; set; } | |||||
| [JsonProperty("token")] | |||||
| public string Token { get; set; } | |||||
| [JsonProperty("version")] | |||||
| public int Version { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -23,7 +23,7 @@ namespace Discord.WebSocket | |||||
| /// <code language="cs" region="ChannelCreated" | /// <code language="cs" region="ChannelCreated" | ||||
| /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | ||||
| /// </example> | /// </example> | ||||
| public event Func<SocketChannel, Task> ChannelCreated | |||||
| public event Func<SocketChannel, Task> ChannelCreated | |||||
| { | { | ||||
| add { _channelCreatedEvent.Add(value); } | add { _channelCreatedEvent.Add(value); } | ||||
| remove { _channelCreatedEvent.Remove(value); } | remove { _channelCreatedEvent.Remove(value); } | ||||
| @@ -45,7 +45,8 @@ namespace Discord.WebSocket | |||||
| /// <code language="cs" region="ChannelDestroyed" | /// <code language="cs" region="ChannelDestroyed" | ||||
| /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | ||||
| /// </example> | /// </example> | ||||
| public event Func<SocketChannel, Task> ChannelDestroyed { | |||||
| public event Func<SocketChannel, Task> ChannelDestroyed | |||||
| { | |||||
| add { _channelDestroyedEvent.Add(value); } | add { _channelDestroyedEvent.Add(value); } | ||||
| remove { _channelDestroyedEvent.Remove(value); } | remove { _channelDestroyedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -67,10 +68,11 @@ namespace Discord.WebSocket | |||||
| /// <code language="cs" region="ChannelUpdated" | /// <code language="cs" region="ChannelUpdated" | ||||
| /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | ||||
| /// </example> | /// </example> | ||||
| public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated { | |||||
| public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated | |||||
| { | |||||
| add { _channelUpdatedEvent.Add(value); } | add { _channelUpdatedEvent.Add(value); } | ||||
| remove { _channelUpdatedEvent.Remove(value); } | remove { _channelUpdatedEvent.Remove(value); } | ||||
| } | |||||
| } | |||||
| internal readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>(); | internal readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>(); | ||||
| //Messages | //Messages | ||||
| @@ -92,7 +94,8 @@ namespace Discord.WebSocket | |||||
| /// <code language="cs" region="MessageReceived" | /// <code language="cs" region="MessageReceived" | ||||
| /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | ||||
| /// </example> | /// </example> | ||||
| public event Func<SocketMessage, Task> MessageReceived { | |||||
| public event Func<SocketMessage, Task> MessageReceived | |||||
| { | |||||
| add { _messageReceivedEvent.Add(value); } | add { _messageReceivedEvent.Add(value); } | ||||
| remove { _messageReceivedEvent.Remove(value); } | remove { _messageReceivedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -124,7 +127,8 @@ namespace Discord.WebSocket | |||||
| /// <code language="cs" region="MessageDeleted" | /// <code language="cs" region="MessageDeleted" | ||||
| /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs" /> | /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs" /> | ||||
| /// </example> | /// </example> | ||||
| public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted { | |||||
| public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted | |||||
| { | |||||
| add { _messageDeletedEvent.Add(value); } | add { _messageDeletedEvent.Add(value); } | ||||
| remove { _messageDeletedEvent.Remove(value); } | remove { _messageDeletedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -182,7 +186,8 @@ namespace Discord.WebSocket | |||||
| /// <see cref="ISocketMessageChannel"/> parameter. | /// <see cref="ISocketMessageChannel"/> parameter. | ||||
| /// </para> | /// </para> | ||||
| /// </remarks> | /// </remarks> | ||||
| public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { | |||||
| public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated | |||||
| { | |||||
| add { _messageUpdatedEvent.Add(value); } | add { _messageUpdatedEvent.Add(value); } | ||||
| remove { _messageUpdatedEvent.Remove(value); } | remove { _messageUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -217,19 +222,22 @@ namespace Discord.WebSocket | |||||
| /// <code language="cs" region="ReactionAdded" | /// <code language="cs" region="ReactionAdded" | ||||
| /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | ||||
| /// </example> | /// </example> | ||||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { | |||||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded | |||||
| { | |||||
| add { _reactionAddedEvent.Add(value); } | add { _reactionAddedEvent.Add(value); } | ||||
| remove { _reactionAddedEvent.Remove(value); } | remove { _reactionAddedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | ||||
| /// <summary> Fired when a reaction is removed from a message. </summary> | /// <summary> Fired when a reaction is removed from a message. </summary> | ||||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { | |||||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved | |||||
| { | |||||
| add { _reactionRemovedEvent.Add(value); } | add { _reactionRemovedEvent.Add(value); } | ||||
| remove { _reactionRemovedEvent.Remove(value); } | remove { _reactionRemovedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | ||||
| /// <summary> Fired when all reactions to a message are cleared. </summary> | /// <summary> Fired when all reactions to a message are cleared. </summary> | ||||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared { | |||||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared | |||||
| { | |||||
| add { _reactionsClearedEvent.Add(value); } | add { _reactionsClearedEvent.Add(value); } | ||||
| remove { _reactionsClearedEvent.Remove(value); } | remove { _reactionsClearedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -259,19 +267,22 @@ namespace Discord.WebSocket | |||||
| //Roles | //Roles | ||||
| /// <summary> Fired when a role is created. </summary> | /// <summary> Fired when a role is created. </summary> | ||||
| public event Func<SocketRole, Task> RoleCreated { | |||||
| public event Func<SocketRole, Task> RoleCreated | |||||
| { | |||||
| add { _roleCreatedEvent.Add(value); } | add { _roleCreatedEvent.Add(value); } | ||||
| remove { _roleCreatedEvent.Remove(value); } | remove { _roleCreatedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | internal readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | ||||
| /// <summary> Fired when a role is deleted. </summary> | /// <summary> Fired when a role is deleted. </summary> | ||||
| public event Func<SocketRole, Task> RoleDeleted { | |||||
| public event Func<SocketRole, Task> RoleDeleted | |||||
| { | |||||
| add { _roleDeletedEvent.Add(value); } | add { _roleDeletedEvent.Add(value); } | ||||
| remove { _roleDeletedEvent.Remove(value); } | remove { _roleDeletedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | internal readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | ||||
| /// <summary> Fired when a role is updated. </summary> | /// <summary> Fired when a role is updated. </summary> | ||||
| public event Func<SocketRole, SocketRole, Task> RoleUpdated { | |||||
| public event Func<SocketRole, SocketRole, Task> RoleUpdated | |||||
| { | |||||
| add { _roleUpdatedEvent.Add(value); } | add { _roleUpdatedEvent.Add(value); } | ||||
| remove { _roleUpdatedEvent.Remove(value); } | remove { _roleUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -279,37 +290,43 @@ namespace Discord.WebSocket | |||||
| //Guilds | //Guilds | ||||
| /// <summary> Fired when the connected account joins a guild. </summary> | /// <summary> Fired when the connected account joins a guild. </summary> | ||||
| public event Func<SocketGuild, Task> JoinedGuild { | |||||
| public event Func<SocketGuild, Task> JoinedGuild | |||||
| { | |||||
| add { _joinedGuildEvent.Add(value); } | add { _joinedGuildEvent.Add(value); } | ||||
| remove { _joinedGuildEvent.Remove(value); } | remove { _joinedGuildEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | ||||
| /// <summary> Fired when the connected account leaves a guild. </summary> | /// <summary> Fired when the connected account leaves a guild. </summary> | ||||
| public event Func<SocketGuild, Task> LeftGuild { | |||||
| public event Func<SocketGuild, Task> LeftGuild | |||||
| { | |||||
| add { _leftGuildEvent.Add(value); } | add { _leftGuildEvent.Add(value); } | ||||
| remove { _leftGuildEvent.Remove(value); } | remove { _leftGuildEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | ||||
| /// <summary> Fired when a guild becomes available. </summary> | /// <summary> Fired when a guild becomes available. </summary> | ||||
| public event Func<SocketGuild, Task> GuildAvailable { | |||||
| public event Func<SocketGuild, Task> GuildAvailable | |||||
| { | |||||
| add { _guildAvailableEvent.Add(value); } | add { _guildAvailableEvent.Add(value); } | ||||
| remove { _guildAvailableEvent.Remove(value); } | remove { _guildAvailableEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | ||||
| /// <summary> Fired when a guild becomes unavailable. </summary> | /// <summary> Fired when a guild becomes unavailable. </summary> | ||||
| public event Func<SocketGuild, Task> GuildUnavailable { | |||||
| public event Func<SocketGuild, Task> GuildUnavailable | |||||
| { | |||||
| add { _guildUnavailableEvent.Add(value); } | add { _guildUnavailableEvent.Add(value); } | ||||
| remove { _guildUnavailableEvent.Remove(value); } | remove { _guildUnavailableEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | ||||
| /// <summary> Fired when offline guild members are downloaded. </summary> | /// <summary> Fired when offline guild members are downloaded. </summary> | ||||
| public event Func<SocketGuild, Task> GuildMembersDownloaded { | |||||
| public event Func<SocketGuild, Task> GuildMembersDownloaded | |||||
| { | |||||
| add { _guildMembersDownloadedEvent.Add(value); } | add { _guildMembersDownloadedEvent.Add(value); } | ||||
| remove { _guildMembersDownloadedEvent.Remove(value); } | remove { _guildMembersDownloadedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | ||||
| /// <summary> Fired when a guild is updated. </summary> | /// <summary> Fired when a guild is updated. </summary> | ||||
| public event Func<SocketGuild, SocketGuild, Task> GuildUpdated { | |||||
| public event Func<SocketGuild, SocketGuild, Task> GuildUpdated | |||||
| { | |||||
| add { _guildUpdatedEvent.Add(value); } | add { _guildUpdatedEvent.Add(value); } | ||||
| remove { _guildUpdatedEvent.Remove(value); } | remove { _guildUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -317,43 +334,50 @@ namespace Discord.WebSocket | |||||
| //Users | //Users | ||||
| /// <summary> Fired when a user joins a guild. </summary> | /// <summary> Fired when a user joins a guild. </summary> | ||||
| public event Func<SocketGuildUser, Task> UserJoined { | |||||
| public event Func<SocketGuildUser, Task> UserJoined | |||||
| { | |||||
| add { _userJoinedEvent.Add(value); } | add { _userJoinedEvent.Add(value); } | ||||
| remove { _userJoinedEvent.Remove(value); } | remove { _userJoinedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | ||||
| /// <summary> Fired when a user leaves a guild. </summary> | /// <summary> Fired when a user leaves a guild. </summary> | ||||
| public event Func<SocketGuildUser, Task> UserLeft { | |||||
| public event Func<SocketGuildUser, Task> UserLeft | |||||
| { | |||||
| add { _userLeftEvent.Add(value); } | add { _userLeftEvent.Add(value); } | ||||
| remove { _userLeftEvent.Remove(value); } | remove { _userLeftEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | ||||
| /// <summary> Fired when a user is banned from a guild. </summary> | /// <summary> Fired when a user is banned from a guild. </summary> | ||||
| public event Func<SocketUser, SocketGuild, Task> UserBanned { | |||||
| public event Func<SocketUser, SocketGuild, Task> UserBanned | |||||
| { | |||||
| add { _userBannedEvent.Add(value); } | add { _userBannedEvent.Add(value); } | ||||
| remove { _userBannedEvent.Remove(value); } | remove { _userBannedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | ||||
| /// <summary> Fired when a user is unbanned from a guild. </summary> | /// <summary> Fired when a user is unbanned from a guild. </summary> | ||||
| public event Func<SocketUser, SocketGuild, Task> UserUnbanned { | |||||
| public event Func<SocketUser, SocketGuild, Task> UserUnbanned | |||||
| { | |||||
| add { _userUnbannedEvent.Add(value); } | add { _userUnbannedEvent.Add(value); } | ||||
| remove { _userUnbannedEvent.Remove(value); } | remove { _userUnbannedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | ||||
| /// <summary> Fired when a user is updated. </summary> | /// <summary> Fired when a user is updated. </summary> | ||||
| public event Func<SocketUser, SocketUser, Task> UserUpdated { | |||||
| public event Func<SocketUser, SocketUser, Task> UserUpdated | |||||
| { | |||||
| add { _userUpdatedEvent.Add(value); } | add { _userUpdatedEvent.Add(value); } | ||||
| remove { _userUpdatedEvent.Remove(value); } | remove { _userUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>(); | internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>(); | ||||
| /// <summary> Fired when a guild member is updated, or a member presence is updated. </summary> | /// <summary> Fired when a guild member is updated, or a member presence is updated. </summary> | ||||
| public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated { | |||||
| public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated | |||||
| { | |||||
| add { _guildMemberUpdatedEvent.Add(value); } | add { _guildMemberUpdatedEvent.Add(value); } | ||||
| remove { _guildMemberUpdatedEvent.Remove(value); } | remove { _guildMemberUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>(); | |||||
| internal readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>(); | |||||
| /// <summary> Fired when a user joins, leaves, or moves voice channels. </summary> | /// <summary> Fired when a user joins, leaves, or moves voice channels. </summary> | ||||
| public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated { | |||||
| public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated | |||||
| { | |||||
| add { _userVoiceStateUpdatedEvent.Add(value); } | add { _userVoiceStateUpdatedEvent.Add(value); } | ||||
| remove { _userVoiceStateUpdatedEvent.Remove(value); } | remove { _userVoiceStateUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -361,30 +385,34 @@ namespace Discord.WebSocket | |||||
| /// <summary> Fired when the bot connects to a Discord voice server. </summary> | /// <summary> Fired when the bot connects to a Discord voice server. </summary> | ||||
| public event Func<SocketVoiceServer, Task> VoiceServerUpdated | public event Func<SocketVoiceServer, Task> VoiceServerUpdated | ||||
| { | { | ||||
| add { _voiceServerUpdatedEvent.Add(value); } | |||||
| add { _voiceServerUpdatedEvent.Add(value); } | |||||
| remove { _voiceServerUpdatedEvent.Remove(value); } | remove { _voiceServerUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketVoiceServer, Task>> _voiceServerUpdatedEvent = new AsyncEvent<Func<SocketVoiceServer, Task>>(); | internal readonly AsyncEvent<Func<SocketVoiceServer, Task>> _voiceServerUpdatedEvent = new AsyncEvent<Func<SocketVoiceServer, Task>>(); | ||||
| /// <summary> Fired when the connected account is updated. </summary> | /// <summary> Fired when the connected account is updated. </summary> | ||||
| public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated { | |||||
| public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated | |||||
| { | |||||
| add { _selfUpdatedEvent.Add(value); } | add { _selfUpdatedEvent.Add(value); } | ||||
| remove { _selfUpdatedEvent.Remove(value); } | remove { _selfUpdatedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | ||||
| /// <summary> Fired when a user starts typing. </summary> | /// <summary> Fired when a user starts typing. </summary> | ||||
| public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping { | |||||
| public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping | |||||
| { | |||||
| add { _userIsTypingEvent.Add(value); } | add { _userIsTypingEvent.Add(value); } | ||||
| remove { _userIsTypingEvent.Remove(value); } | remove { _userIsTypingEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>(); | internal readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>(); | ||||
| /// <summary> Fired when a user joins a group channel. </summary> | /// <summary> Fired when a user joins a group channel. </summary> | ||||
| public event Func<SocketGroupUser, Task> RecipientAdded { | |||||
| public event Func<SocketGroupUser, Task> RecipientAdded | |||||
| { | |||||
| add { _recipientAddedEvent.Add(value); } | add { _recipientAddedEvent.Add(value); } | ||||
| remove { _recipientAddedEvent.Remove(value); } | remove { _recipientAddedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | ||||
| /// <summary> Fired when a user is removed from a group channel. </summary> | /// <summary> Fired when a user is removed from a group channel. </summary> | ||||
| public event Func<SocketGroupUser, Task> RecipientRemoved { | |||||
| public event Func<SocketGroupUser, Task> RecipientRemoved | |||||
| { | |||||
| add { _recipientRemovedEvent.Add(value); } | add { _recipientRemovedEvent.Add(value); } | ||||
| remove { _recipientRemovedEvent.Remove(value); } | remove { _recipientRemovedEvent.Remove(value); } | ||||
| } | } | ||||
| @@ -431,5 +459,91 @@ namespace Discord.WebSocket | |||||
| remove { _inviteDeletedEvent.Remove(value); } | remove { _inviteDeletedEvent.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuildChannel, string, Task>> _inviteDeletedEvent = new AsyncEvent<Func<SocketGuildChannel, string, Task>>(); | internal readonly AsyncEvent<Func<SocketGuildChannel, string, Task>> _inviteDeletedEvent = new AsyncEvent<Func<SocketGuildChannel, string, Task>>(); | ||||
| //Interactions | |||||
| /// <summary> | |||||
| /// Fired when an Interaction is created. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// <para> | |||||
| /// This event is fired when an interaction is created. The event handler must return a | |||||
| /// <see cref="Task"/> and accept a <see cref="SocketInteraction"/> as its parameter. | |||||
| /// </para> | |||||
| /// <para> | |||||
| /// The interaction created will be passed into the <see cref="SocketInteraction"/> parameter. | |||||
| /// </para> | |||||
| /// </remarks> | |||||
| public event Func<SocketInteraction, Task> InteractionCreated | |||||
| { | |||||
| add { _interactionCreatedEvent.Add(value); } | |||||
| remove { _interactionCreatedEvent.Remove(value); } | |||||
| } | |||||
| internal readonly AsyncEvent<Func<SocketInteraction, Task>> _interactionCreatedEvent = new AsyncEvent<Func<SocketInteraction, Task>>(); | |||||
| /// <summary> | |||||
| /// Fired when a guild application command is created. | |||||
| ///</summary> | |||||
| ///<remarks> | |||||
| /// <para> | |||||
| /// This event is fired when an application command is created. The event handler must return a | |||||
| /// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter. | |||||
| /// </para> | |||||
| /// <para> | |||||
| /// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter. | |||||
| /// </para> | |||||
| /// <note> | |||||
| /// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b> | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public event Func<SocketApplicationCommand, Task> ApplicationCommandCreated | |||||
| { | |||||
| add { _applicationCommandCreated.Add(value); } | |||||
| remove { _applicationCommandCreated.Remove(value); } | |||||
| } | |||||
| internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandCreated = new AsyncEvent<Func<SocketApplicationCommand, Task>>(); | |||||
| /// <summary> | |||||
| /// Fired when a guild application command is updated. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// <para> | |||||
| /// This event is fired when an application command is updated. The event handler must return a | |||||
| /// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter. | |||||
| /// </para> | |||||
| /// <para> | |||||
| /// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter. | |||||
| /// </para> | |||||
| /// <note> | |||||
| /// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b> | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public event Func<SocketApplicationCommand, Task> ApplicationCommandUpdated | |||||
| { | |||||
| add { _applicationCommandUpdated.Add(value); } | |||||
| remove { _applicationCommandUpdated.Remove(value); } | |||||
| } | |||||
| internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandUpdated = new AsyncEvent<Func<SocketApplicationCommand, Task>>(); | |||||
| /// <summary> | |||||
| /// Fired when a guild application command is deleted. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// <para> | |||||
| /// This event is fired when an application command is deleted. The event handler must return a | |||||
| /// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter. | |||||
| /// </para> | |||||
| /// <para> | |||||
| /// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter. | |||||
| /// </para> | |||||
| /// <note> | |||||
| /// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b> | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public event Func<SocketApplicationCommand, Task> ApplicationCommandDeleted | |||||
| { | |||||
| add { _applicationCommandDeleted.Add(value); } | |||||
| remove { _applicationCommandDeleted.Remove(value); } | |||||
| } | |||||
| internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandDeleted = new AsyncEvent<Func<SocketApplicationCommand, Task>>(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,6 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||
| <Import Project="../../Discord.Net.targets" /> | <Import Project="../../Discord.Net.targets" /> | ||||
| <Import Project="../../StyleAnalyzer.targets"/> | |||||
| <Import Project="../../StyleAnalyzer.targets" /> | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <AssemblyName>Discord.Net.WebSocket</AssemblyName> | <AssemblyName>Discord.Net.WebSocket</AssemblyName> | ||||
| <RootNamespace>Discord.WebSocket</RootNamespace> | <RootNamespace>Discord.WebSocket</RootNamespace> | ||||
| @@ -392,6 +392,8 @@ namespace Discord.WebSocket | |||||
| client.InviteCreated += (invite) => _inviteCreatedEvent.InvokeAsync(invite); | client.InviteCreated += (invite) => _inviteCreatedEvent.InvokeAsync(invite); | ||||
| client.InviteDeleted += (channel, invite) => _inviteDeletedEvent.InvokeAsync(channel, invite); | client.InviteDeleted += (channel, invite) => _inviteDeletedEvent.InvokeAsync(channel, invite); | ||||
| client.InteractionCreated += (interaction) => _interactionCreatedEvent.InvokeAsync(interaction); | |||||
| } | } | ||||
| //IDiscordClient | //IDiscordClient | ||||
| @@ -73,6 +73,7 @@ namespace Discord.WebSocket | |||||
| internal bool AlwaysDownloadUsers { get; private set; } | internal bool AlwaysDownloadUsers { get; private set; } | ||||
| internal int? HandlerTimeout { get; private set; } | internal int? HandlerTimeout { get; private set; } | ||||
| internal bool? ExclusiveBulkDelete { get; private set; } | internal bool? ExclusiveBulkDelete { get; private set; } | ||||
| internal bool AlwaysAcknowledgeInteractions { get; private set; } | |||||
| internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -135,6 +136,7 @@ namespace Discord.WebSocket | |||||
| UdpSocketProvider = config.UdpSocketProvider; | UdpSocketProvider = config.UdpSocketProvider; | ||||
| WebSocketProvider = config.WebSocketProvider; | WebSocketProvider = config.WebSocketProvider; | ||||
| AlwaysDownloadUsers = config.AlwaysDownloadUsers; | AlwaysDownloadUsers = config.AlwaysDownloadUsers; | ||||
| AlwaysAcknowledgeInteractions = config.AlwaysAcknowledgeInteractions; | |||||
| HandlerTimeout = config.HandlerTimeout; | HandlerTimeout = config.HandlerTimeout; | ||||
| ExclusiveBulkDelete = config.ExclusiveBulkDelete; | ExclusiveBulkDelete = config.ExclusiveBulkDelete; | ||||
| State = new ClientState(0, 0); | State = new ClientState(0, 0); | ||||
| @@ -632,7 +634,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else if (_connection.CancelToken.IsCancellationRequested) | else if (_connection.CancelToken.IsCancellationRequested) | ||||
| return; | return; | ||||
| if (BaseConfig.AlwaysDownloadUsers) | if (BaseConfig.AlwaysDownloadUsers) | ||||
| _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); | _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); | ||||
| @@ -1799,7 +1801,88 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case "INTERACTION_CREATE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.InteractionCreated>(_serializer); | |||||
| if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | |||||
| { | |||||
| var guild = channel.Guild; | |||||
| if (!guild.IsSynced) | |||||
| { | |||||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| var interaction = SocketInteraction.Create(this, data); | |||||
| if (this.AlwaysAcknowledgeInteractions) | |||||
| await interaction.AcknowledgeAsync().ConfigureAwait(false); | |||||
| await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| { | |||||
| await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| } | |||||
| break; | |||||
| case "APPLICATION_COMMAND_CREATE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_CREATE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_serializer); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if(guild == null) | |||||
| { | |||||
| await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| var applicationCommand = SocketApplicationCommand.Create(this, data); | |||||
| await TimedInvokeAsync(_applicationCommandCreated, nameof(ApplicationCommandCreated), applicationCommand).ConfigureAwait(false); | |||||
| } | |||||
| break; | |||||
| case "APPLICATION_COMMAND_UPDATE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_UPDATE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_serializer); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild == null) | |||||
| { | |||||
| await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| var applicationCommand = SocketApplicationCommand.Create(this, data); | |||||
| await TimedInvokeAsync(_applicationCommandUpdated, nameof(ApplicationCommandUpdated), applicationCommand).ConfigureAwait(false); | |||||
| } | |||||
| break; | |||||
| case "APPLICATION_COMMAND_DELETE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_DELETE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_serializer); | |||||
| var guild = State.GetGuild(data.GuildId); | |||||
| if (guild == null) | |||||
| { | |||||
| await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| var applicationCommand = SocketApplicationCommand.Create(this, data); | |||||
| await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); | |||||
| } | |||||
| break; | |||||
| //Ignored (User only) | //Ignored (User only) | ||||
| case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | ||||
| @@ -105,6 +105,29 @@ namespace Discord.WebSocket | |||||
| /// </remarks> | /// </remarks> | ||||
| public bool AlwaysDownloadUsers { get; set; } = false; | public bool AlwaysDownloadUsers { get; set; } = false; | ||||
| /// <summary> | |||||
| /// Gets or sets whether or not interactions are acknowledge with source. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// <para> | |||||
| /// Discord interactions will not appear in chat until the client responds to them. With this option set to | |||||
| /// <see langword="true"/>, the client will automatically acknowledge the interaction with <see cref="InteractionResponseType.ACKWithSource"/>. | |||||
| /// See <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-interactionresponsetype">the docs</see> on | |||||
| /// responding to interactions for more info. | |||||
| /// </para> | |||||
| /// <para> | |||||
| /// With this option set to <see langword="false"/>, you will have to acknowledge the interaction with | |||||
| /// <see cref="SocketInteraction.RespondAsync(string, bool, Embed, InteractionResponseType, AllowedMentions, RequestOptions)"/>. | |||||
| /// Only after the interaction is acknowledged, the origional slash command message will be visible. | |||||
| /// </para> | |||||
| /// <note> | |||||
| /// Please note that manually acknowledging the interaction with a message reply will not provide any return data. | |||||
| /// Automatically acknowledging the interaction without sending the message will allow for follow up responses to | |||||
| /// be used; follow up responses return the message data sent. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public bool AlwaysAcknowledgeInteractions { get; set; } = true; | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the timeout for event handlers, in milliseconds, after which a warning will be logged. | /// Gets or sets the timeout for event handlers, in milliseconds, after which a warning will be logged. | ||||
| /// Setting this property to <c>null</c>disables this check. | /// Setting this property to <c>null</c>disables this check. | ||||
| @@ -1006,6 +1006,18 @@ namespace Discord.WebSocket | |||||
| public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) | ||||
| => GuildHelper.GetWebhooksAsync(this, Discord, options); | => GuildHelper.GetWebhooksAsync(this, Discord, options); | ||||
| //Interactions | |||||
| /// <summary> | |||||
| /// Gets this guilds slash commands commands | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous get operation. The task result contains a read-only collection | |||||
| /// of application commands found within the guild. | |||||
| /// </returns> | |||||
| public async Task<IReadOnlyCollection<RestApplicationCommand>> GetApplicationCommandsAsync(RequestOptions options = null) | |||||
| => await Discord.Rest.GetGuildApplicationCommands(this.Id, options); | |||||
| //Emotes | //Emotes | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) | public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) | ||||
| @@ -0,0 +1,55 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.Gateway.ApplicationCommandCreatedUpdatedEvent; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class SocketApplicationCommand : SocketEntity<ulong>, IApplicationCommand | |||||
| { | |||||
| public ulong ApplicationId { get; private set; } | |||||
| public string Name { get; private set; } | |||||
| public string Description { get; private set; } | |||||
| public IReadOnlyCollection<SocketApplicationCommandOption> Options { get; private set; } | |||||
| public DateTimeOffset CreatedAt | |||||
| => SnowflakeUtils.FromSnowflake(this.Id); | |||||
| public SocketGuild Guild | |||||
| => Discord.GetGuild(this.GuildId); | |||||
| private ulong GuildId { get; set; } | |||||
| internal SocketApplicationCommand(DiscordSocketClient client, ulong id) | |||||
| : base(client, id) | |||||
| { | |||||
| } | |||||
| internal static SocketApplicationCommand Create(DiscordSocketClient client, Model model) | |||||
| { | |||||
| var entity = new SocketApplicationCommand(client, model.Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| internal void Update(Model model) | |||||
| { | |||||
| this.ApplicationId = model.ApplicationId; | |||||
| this.Description = model.Description; | |||||
| this.Name = model.Name; | |||||
| this.GuildId = model.GuildId; | |||||
| this.Options = model.Options.Any() | |||||
| ? model.Options.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray() | |||||
| : new ImmutableArray<SocketApplicationCommandOption>(); | |||||
| } | |||||
| public Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException(); | |||||
| IReadOnlyCollection<IApplicationCommandOption> IApplicationCommand.Options => Options; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommandOptionChoice; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a choice for a <see cref="SocketApplicationCommandOption"/> | |||||
| /// </summary> | |||||
| public class SocketApplicationCommandChoice : IApplicationCommandOptionChoice | |||||
| { | |||||
| public string Name { get; private set; } | |||||
| public object Value { get; private set; } | |||||
| internal SocketApplicationCommandChoice() { } | |||||
| internal static SocketApplicationCommandChoice Create(Model model) | |||||
| { | |||||
| var entity = new SocketApplicationCommandChoice(); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| internal void Update(Model model) | |||||
| { | |||||
| this.Name = model.Name; | |||||
| this.Value = model.Value; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,70 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommandOption; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents an option for a <see cref="SocketApplicationCommand"/> | |||||
| /// </summary> | |||||
| public class SocketApplicationCommandOption : IApplicationCommandOption | |||||
| { | |||||
| public string Name { get; private set; } | |||||
| public ApplicationCommandOptionType Type { get; private set; } | |||||
| public string Description { get; private set; } | |||||
| public bool? Default { get; private set; } | |||||
| public bool? Required { get; private set; } | |||||
| /// <summary> | |||||
| /// Choices for string and int types for the user to pick from. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketApplicationCommandChoice> Choices { get; private set; } | |||||
| /// <summary> | |||||
| /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SocketApplicationCommandOption> Options { get; private set; } | |||||
| internal SocketApplicationCommandOption() { } | |||||
| internal static SocketApplicationCommandOption Create(Model model) | |||||
| { | |||||
| var entity = new SocketApplicationCommandOption(); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| internal void Update(Model model) | |||||
| { | |||||
| this.Name = model.Name; | |||||
| this.Type = model.Type; | |||||
| this.Description = model.Description; | |||||
| this.Default = model.Default.IsSpecified | |||||
| ? model.Default.Value | |||||
| : null; | |||||
| this.Required = model.Required.IsSpecified | |||||
| ? model.Required.Value | |||||
| : null; | |||||
| this.Choices = model.Choices.IsSpecified | |||||
| ? model.Choices.Value.Select(x => SocketApplicationCommandChoice.Create(x)).ToImmutableArray() | |||||
| : new ImmutableArray<SocketApplicationCommandChoice>(); | |||||
| this.Options = model.Options.IsSpecified | |||||
| ? model.Options.Value.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray() | |||||
| : new ImmutableArray<SocketApplicationCommandOption>(); | |||||
| } | |||||
| IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices => Choices; | |||||
| IReadOnlyCollection<IApplicationCommandOption> IApplicationCommandOption.Options => Options; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,215 @@ | |||||
| using Discord.Rest; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.Gateway.InteractionCreated; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents an Interaction recieved over the gateway | |||||
| /// </summary> | |||||
| public class SocketInteraction : SocketEntity<ulong>, IDiscordInteraction | |||||
| { | |||||
| /// <summary> | |||||
| /// The <see cref="SocketGuild"/> this interaction was used in | |||||
| /// </summary> | |||||
| public SocketGuild Guild | |||||
| => Discord.GetGuild(GuildId); | |||||
| /// <summary> | |||||
| /// The <see cref="SocketTextChannel"/> this interaction was used in | |||||
| /// </summary> | |||||
| public SocketTextChannel Channel | |||||
| => Guild.GetTextChannel(ChannelId); | |||||
| /// <summary> | |||||
| /// The <see cref="SocketGuildUser"/> who triggered this interaction | |||||
| /// </summary> | |||||
| public SocketGuildUser Member | |||||
| => Guild.GetUser(MemberId); | |||||
| /// <summary> | |||||
| /// The type of this interaction | |||||
| /// </summary> | |||||
| public InteractionType Type { get; private set; } | |||||
| /// <summary> | |||||
| /// The data associated with this interaction | |||||
| /// </summary> | |||||
| public SocketInteractionData Data { get; private set; } | |||||
| /// <summary> | |||||
| /// The token used to respond to this interaction | |||||
| /// </summary> | |||||
| public string Token { get; private set; } | |||||
| /// <summary> | |||||
| /// The version of this interaction | |||||
| /// </summary> | |||||
| public int Version { get; private set; } | |||||
| public DateTimeOffset CreatedAt { get; } | |||||
| /// <summary> | |||||
| /// <see langword="true"/> if the token is valid for replying to, otherwise <see langword="false"/> | |||||
| /// </summary> | |||||
| public bool IsValidToken | |||||
| => CheckToken(); | |||||
| private ulong GuildId { get; set; } | |||||
| private ulong ChannelId { get; set; } | |||||
| private ulong MemberId { get; set; } | |||||
| internal SocketInteraction(DiscordSocketClient client, ulong id) | |||||
| : base(client, id) | |||||
| { | |||||
| } | |||||
| internal static SocketInteraction Create(DiscordSocketClient client, Model model) | |||||
| { | |||||
| var entitiy = new SocketInteraction(client, model.Id); | |||||
| entitiy.Update(model); | |||||
| return entitiy; | |||||
| } | |||||
| internal void Update(Model model) | |||||
| { | |||||
| this.Data = model.Data.IsSpecified | |||||
| ? SocketInteractionData.Create(this.Discord, model.Data.Value) | |||||
| : null; | |||||
| this.GuildId = model.GuildId; | |||||
| this.ChannelId = model.ChannelId; | |||||
| this.Token = model.Token; | |||||
| this.Version = model.Version; | |||||
| this.MemberId = model.Member.User.Id; | |||||
| this.Type = model.Type; | |||||
| } | |||||
| private bool CheckToken() | |||||
| { | |||||
| // Tokens last for 15 minutes according to https://discord.com/developers/docs/interactions/slash-commands#responding-to-an-interaction | |||||
| return (DateTime.UtcNow - this.CreatedAt.UtcDateTime).TotalMinutes >= 15d; | |||||
| } | |||||
| /// <summary> | |||||
| /// Responds to an Interaction. | |||||
| /// <para> | |||||
| /// If you have <see cref="DiscordSocketConfig.AlwaysAcknowledgeInteractions"/> set to <see langword="true"/>, You should use | |||||
| /// <see cref="FollowupAsync(string, bool, Embed, InteractionResponseType, AllowedMentions, RequestOptions)"/> instead. | |||||
| /// </para> | |||||
| /// </summary> | |||||
| /// <param name="text">The text of the message to be sent</param> | |||||
| /// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/></param> | |||||
| /// <param name="embed">A <see cref="Embed"/> to send with this response</param> | |||||
| /// <param name="Type">The type of response to this Interaction</param> | |||||
| /// <param name="allowedMentions">The allowed mentions for this response</param> | |||||
| /// <param name="options">The request options for this response</param> | |||||
| /// <returns> | |||||
| /// The <see cref="IMessage"/> sent as the response. If this is the first acknowledgement, it will return null; | |||||
| /// </returns> | |||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||||
| /// <exception cref="InvalidOperationException">The parameters provided were invalid or the token was invalid</exception> | |||||
| public async Task<IMessage> RespondAsync(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType Type = InteractionResponseType.ChannelMessageWithSource, AllowedMentions allowedMentions = null, RequestOptions options = null) | |||||
| { | |||||
| if (Type == InteractionResponseType.Pong) | |||||
| throw new InvalidOperationException($"Cannot use {Type} on a send message function"); | |||||
| if (!IsValidToken) | |||||
| throw new InvalidOperationException("Interaction token is no longer valid"); | |||||
| if (Discord.AlwaysAcknowledgeInteractions) | |||||
| return await FollowupAsync(); | |||||
| Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | |||||
| Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | |||||
| // check that user flag and user Id list are exclusive, same with role flag and role Id list | |||||
| if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) | |||||
| { | |||||
| if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && | |||||
| allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) | |||||
| { | |||||
| throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); | |||||
| } | |||||
| if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && | |||||
| allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) | |||||
| { | |||||
| throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); | |||||
| } | |||||
| } | |||||
| var response = new API.InteractionResponse() | |||||
| { | |||||
| Type = Type, | |||||
| Data = new API.InteractionApplicationCommandCallbackData(text) | |||||
| { | |||||
| AllowedMentions = allowedMentions?.ToModel(), | |||||
| Embeds = embed != null | |||||
| ? new API.Embed[] { embed.ToModel() } | |||||
| : Optional<API.Embed[]>.Unspecified, | |||||
| TTS = isTTS | |||||
| } | |||||
| }; | |||||
| await Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, Token, options); | |||||
| return null; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sends a followup message for this interaction | |||||
| /// </summary> | |||||
| /// <param name="text">The text of the message to be sent</param> | |||||
| /// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/></param> | |||||
| /// <param name="embed">A <see cref="Embed"/> to send with this response</param> | |||||
| /// <param name="Type">The type of response to this Interaction</param> | |||||
| /// <param name="allowedMentions">The allowed mentions for this response</param> | |||||
| /// <param name="options">The request options for this response</param> | |||||
| /// <returns> | |||||
| /// The sent message | |||||
| /// </returns> | |||||
| public async Task<IMessage> FollowupAsync(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType Type = InteractionResponseType.ChannelMessageWithSource, | |||||
| AllowedMentions allowedMentions = null, RequestOptions options = null) | |||||
| { | |||||
| if (Type == InteractionResponseType.ACKWithSource || Type == InteractionResponseType.ACKWithSource || Type == InteractionResponseType.Pong) | |||||
| throw new InvalidOperationException($"Cannot use {Type} on a send message function"); | |||||
| if (!IsValidToken) | |||||
| throw new InvalidOperationException("Interaction token is no longer valid"); | |||||
| var args = new API.Rest.CreateWebhookMessageParams(text) | |||||
| { | |||||
| IsTTS = isTTS, | |||||
| Embeds = embed != null | |||||
| ? new API.Embed[] { embed.ToModel() } | |||||
| : Optional<API.Embed[]>.Unspecified, | |||||
| }; | |||||
| return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||||
| } | |||||
| /// <summary> | |||||
| /// Acknowledges this interaction with the <see cref="InteractionResponseType.ACKWithSource"/> | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous operation of acknowledging the interaction | |||||
| /// </returns> | |||||
| public async Task AcknowledgeAsync(RequestOptions options = null) | |||||
| { | |||||
| var response = new API.InteractionResponse() | |||||
| { | |||||
| Type = InteractionResponseType.ACKWithSource, | |||||
| }; | |||||
| await Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, Token, options).ConfigureAwait(false); | |||||
| } | |||||
| IApplicationCommandInteractionData IDiscordInteraction.Data => Data; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommandInteractionData; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class SocketInteractionData : SocketEntity<ulong>, IApplicationCommandInteractionData | |||||
| { | |||||
| public string Name { get; private set; } | |||||
| public IReadOnlyCollection<SocketInteractionDataOption> Options { get; private set; } | |||||
| internal SocketInteractionData(DiscordSocketClient client, ulong id) | |||||
| : base(client, id) | |||||
| { | |||||
| } | |||||
| internal static SocketInteractionData Create(DiscordSocketClient client, Model model) | |||||
| { | |||||
| var entity = new SocketInteractionData(client, model.Id); | |||||
| entity.Update(model); | |||||
| return entity; | |||||
| } | |||||
| internal void Update(Model model) | |||||
| { | |||||
| this.Name = model.Name; | |||||
| this.Options = model.Options.IsSpecified | |||||
| ? model.Options.Value.Select(x => new SocketInteractionDataOption(x)).ToImmutableArray() | |||||
| : null; | |||||
| } | |||||
| IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionData.Options => Options; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,28 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.ApplicationCommandInteractionDataOption; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class SocketInteractionDataOption : IApplicationCommandInteractionDataOption | |||||
| { | |||||
| public string Name { get; private set; } | |||||
| public object? Value { get; private set; } | |||||
| public IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; private set; } | |||||
| internal SocketInteractionDataOption(Model model) | |||||
| { | |||||
| this.Name = Name; | |||||
| this.Value = model.Value.IsSpecified ? model.Value.Value : null; | |||||
| this.Options = model.Options.IsSpecified | |||||
| ? model.Options.Value.Select(x => new SocketInteractionDataOption(x)).ToImmutableArray() | |||||
| : null; | |||||
| } | |||||
| } | |||||
| } | |||||