Browse Source

Closes #8. Added DM support for buttons and slash commands. Added SlashCommandBuilder

pull/1923/head
quin lynch 4 years ago
parent
commit
689c77d476
4 changed files with 505 additions and 2 deletions
  1. +10
    -2
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs
  2. +489
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs
  3. +3
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs
  4. +3
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs

+ 10
- 2
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Discord
@@ -22,8 +23,15 @@ namespace Discord
get => _name;
set
{
if (value?.Length > 32)
throw new ArgumentException("Name length must be less than or equal to 32");
if (value == null)
throw new ArgumentNullException($"{nameof(Name)} cannot be null!");

if (value.Length > 32)
throw new ArgumentException($"{nameof(Name)} length must be less than or equal to 32");

if (!Regex.IsMatch(value, @"^[\w-]{1,32}$"))
throw new ArgumentException($"{nameof(Name)} must match the regex ^[\\w-]{{1,32}}$");

_name = value;
}
}


+ 489
- 0
src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs View File

@@ -0,0 +1,489 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Discord
{
/// <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;
}
}

/// <summary>
/// Gets or sets the options for this command.
/// </summary>
public List<SlashCommandOptionBuilder> Options
{
get => _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 string _name { get; set; }
private string _description { get; set; }
private List<SlashCommandOptionBuilder> _options { 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;

}

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 < 1)
throw new ArgumentException("Name length must at least 1 characters in length");

if (value != null)
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 built 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 option to the current option.
/// </summary>
/// <param name="option">The sub option to add.</param>
/// <returns>The current builder.</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;
}

/// <summary>
/// Adds a choice to the current option.
/// </summary>
/// <param name="Name">The name of the choice.</param>
/// <param name="Value">The value of the choice.</param>
/// <returns>The current builder.</returns>
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!");

if (Name == null)
throw new ArgumentNullException($"{nameof(Name)} cannot be null!");

Preconditions.AtLeast(Name.Length, 1, nameof(Name));
Preconditions.AtMost(Name.Length, 100, nameof(Name));

Choices.Add(new ApplicationCommandOptionChoiceProperties()
{
Name = Name,
Value = Value
});

return this;
}

/// <summary>
/// Adds a choice to the current option.
/// </summary>
/// <param name="Name">The name of the choice.</param>
/// <param name="Value">The value of the choice.</param>
/// <returns>The current builder.</returns>
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!");

if (Name == null)
throw new ArgumentNullException($"{nameof(Name)} cannot be null!");

if (Value == null)
throw new ArgumentNullException($"{nameof(Value)} cannot be null!");

Preconditions.AtLeast(Name.Length, 1, nameof(Name));
Preconditions.AtMost(Name.Length, 100, nameof(Name));

Preconditions.AtLeast(Value.Length, 1, nameof(Value));
Preconditions.AtMost(Value.Length, 100, nameof(Value));

Choices.Add(new ApplicationCommandOptionChoiceProperties()
{
Name = Name,
Value = Value
});

return this;
}

/// <summary>
/// Sets the current builders name.
/// </summary>
/// <param name="Name">The name to set the current option builder.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithName(string Name)
{
this.Name = Name;

return this;
}

/// <summary>
/// Sets the current builders description.
/// </summary>
/// <param name="Description">The description to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithDescription(string Description)
{
this.Description = Description;
return this;
}

/// <summary>
/// Sets the current builders required field.
/// </summary>
/// <param name="value">The value to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithRequired(bool value)
{
this.Required = value;
return this;
}

/// <summary>
/// Sets the current builders default field.
/// </summary>
/// <param name="value">The value to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithDefault(bool value)
{
this.Default = value;
return this;
}

/// <summary>
/// Sets the current type of this builder.
/// </summary>
/// <param name="Type">The type to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithType(ApplicationCommandOptionType Type)
{
this.Type = Type;
return this;
}
}
}

+ 3
- 0
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs View File

@@ -8,6 +8,9 @@ using Model = Discord.API.Gateway.InteractionCreated;

namespace Discord.WebSocket
{
/// <summary>
/// Represents a Websocket-based slash command received over the gateway.
/// </summary>
public class SocketSlashCommand : SocketInteraction
{
/// <summary>


+ 3
- 0
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs View File

@@ -5,6 +5,9 @@ using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.WebSocket
{
/// <summary>
/// Represents the data tied with the <see cref="SocketSlashCommand"/> interaction.
/// </summary>
public class SocketSlashCommandData : SocketEntity<ulong>, IApplicationCommandInteractionData
{
/// <inheritdoc/>


Loading…
Cancel
Save