using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Discord.SlashCommands.Builders
{
///
/// A class used to build slash commands.
///
public class SlashCommandBuilder
{
///
/// Returns the maximun length a commands name allowed by Discord
///
public const int MaxNameLength = 32;
///
/// Returns the maximum length of a commands description allowed by Discord.
///
public const int MaxDescriptionLength = 100;
///
/// Returns the maximum count of command options allowed by Discord
///
public const int MaxOptionsCount = 10;
///
/// The name of this slash command.
///
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;
}
}
///
/// A 1-100 length description of this slash command
///
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;
}
}
///
/// Gets or sets the options for this command.
///
public List 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 _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();
this.Options.ForEach(x => options.Add(x.Build()));
props.Options = options;
}
return props;
}
///
/// Makes this command a global application command .
///
/// The current builder.
public SlashCommandBuilder MakeGlobal()
{
this.isGlobal = true;
return this;
}
///
/// Makes this command a guild specific command.
///
/// The Id of the target guild.
/// The current builder.
public SlashCommandBuilder ForGuild(ulong GuildId)
{
this.GuildId = GuildId;
return this;
}
public SlashCommandBuilder WithName(string Name)
{
this.Name = Name;
return this;
}
///
/// Sets the description of the current command.
///
/// The description of this command.
/// The current builder.
public SlashCommandBuilder WithDescription(string Description)
{
this.Description = Description;
return this;
}
///
/// Adds an option to the current slash command.
///
/// The name of the option to add.
/// The type of this option.
/// The description of this option.
/// If this option is required for this command.
/// If this option is the default option.
/// The options of the option to add.
/// The choices of this option.
/// The current builder.
public SlashCommandBuilder AddOption(string Name, ApplicationCommandOptionType Type,
string Description, bool Required = true, bool Default = false, List 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(Choices) : null;
return AddOption(option);
}
///
/// Adds an option to the current slash command.
///
/// The name of the option to add.
/// The type of this option.
/// The description of this option.
/// If this option is required for this command.
/// If this option is the default option.
/// The choices of this option.
/// The current builder.
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);
///
/// Adds an option to the current slash command.
///
/// The name of the option to add.
/// The type of this option.
/// The sescription of this option.
/// The current builder.
public SlashCommandBuilder AddOption(string Name, ApplicationCommandOptionType Type, string Description)
=> AddOption(Name, Type, Description, Options: null, Choices: null);
///
/// Adds an option to this slash command.
///
/// The option to add.
/// The current builder.
public SlashCommandBuilder AddOption(SlashCommandOptionBuilder Option)
{
if (this.Options == null)
this.Options = new List();
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;
}
///
/// Adds a collection of options to the current slash command.
///
/// The collection of options to add.
/// The current builder.
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();
if (this.Options.Count + Options.Length > MaxOptionsCount)
throw new ArgumentOutOfRangeException(nameof(Options), $"Cannot have more than {MaxOptionsCount} options!");
this.Options.AddRange(Options);
return this;
}
}
///
/// Represents a class used to build options for the .
///
public class SlashCommandOptionBuilder
{
///
/// The max length of a choice's name allowed by Discord.
///
public const int ChoiceNameMaxLength = 100;
///
/// The maximum number of choices allowed by Discord.
///
public const int MaxChoiceCount = 10;
private string _name;
private string _description;
///
/// The name of this option.
///
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;
}
}
///
/// The description of this option.
///
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;
}
}
///
/// The type of this option.
///
public ApplicationCommandOptionType Type { get; set; }
///
/// The first required option for the user to complete. only one option can be default.
///
public bool Default { get; set; }
///
/// if this option is required for this command, otherwise .
///
public bool Required { get; set; }
///
/// choices for string and int types for the user to pick from.
///
public List Choices { get; set; }
///
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
///
public List Options { get; set; }
///
/// Builds the current option.
///
/// The build version of this option
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(this.Options.Select(x => x.Build())),
Choices = this.Choices
};
}
///
/// Adds a sub
///
///
///
public SlashCommandOptionBuilder AddOption(SlashCommandOptionBuilder option)
{
if (this.Options == null)
this.Options = new List();
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();
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();
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();
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;
}
}
}