| @@ -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; | |||||
| } | |||||
| } | |||||
| } | |||||