You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

SlashCommandBuilder.cs 19 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using System.Threading.Tasks;
  7. namespace Discord.SlashCommands.Builders
  8. {
  9. /// <summary>
  10. /// A class used to build slash commands.
  11. /// </summary>
  12. public class SlashCommandBuilder
  13. {
  14. /// <summary>
  15. /// Returns the maximun length a commands name allowed by Discord
  16. /// </summary>
  17. public const int MaxNameLength = 32;
  18. /// <summary>
  19. /// Returns the maximum length of a commands description allowed by Discord.
  20. /// </summary>
  21. public const int MaxDescriptionLength = 100;
  22. /// <summary>
  23. /// Returns the maximum count of command options allowed by Discord
  24. /// </summary>
  25. public const int MaxOptionsCount = 10;
  26. /// <summary>
  27. /// The name of this slash command.
  28. /// </summary>
  29. public string Name
  30. {
  31. get
  32. {
  33. return _name;
  34. }
  35. set
  36. {
  37. Preconditions.NotNullOrEmpty(value, nameof(Name));
  38. Preconditions.AtLeast(value.Length, 3, nameof(Name));
  39. Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name));
  40. // Discord updated the docs, this regex prevents special characters like @!$%(... etc,
  41. // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
  42. if (!Regex.IsMatch(value, @"^[\w-]{3,32}$"))
  43. throw new ArgumentException("Command name cannot contian any special characters or whitespaces!");
  44. _name = value;
  45. }
  46. }
  47. /// <summary>
  48. /// A 1-100 length description of this slash command
  49. /// </summary>
  50. public string Description
  51. {
  52. get
  53. {
  54. return _description;
  55. }
  56. set
  57. {
  58. Preconditions.AtLeast(value.Length, 1, nameof(Description));
  59. Preconditions.AtMost(value.Length, MaxDescriptionLength, nameof(Description));
  60. _description = value;
  61. }
  62. }
  63. public ulong GuildId
  64. {
  65. get
  66. {
  67. return _guildId ?? 0;
  68. }
  69. set
  70. {
  71. if (value == 0)
  72. {
  73. throw new ArgumentException("Guild ID cannot be 0!");
  74. }
  75. _guildId = value;
  76. if (isGlobal)
  77. isGlobal = false;
  78. }
  79. }
  80. /// <summary>
  81. /// Gets or sets the options for this command.
  82. /// </summary>
  83. public List<SlashCommandOptionBuilder> Options
  84. {
  85. get
  86. {
  87. return _options;
  88. }
  89. set
  90. {
  91. if (value != null)
  92. if (value.Count > MaxOptionsCount)
  93. throw new ArgumentException(message: $"Option count must be less than or equal to {MaxOptionsCount}.", paramName: nameof(Options));
  94. _options = value;
  95. }
  96. }
  97. private ulong? _guildId { get; set; }
  98. private string _name { get; set; }
  99. private string _description { get; set; }
  100. private List<SlashCommandOptionBuilder> _options { get; set; }
  101. internal bool isGlobal { get; set; }
  102. public SlashCommandCreationProperties Build()
  103. {
  104. SlashCommandCreationProperties props = new SlashCommandCreationProperties()
  105. {
  106. Name = this.Name,
  107. Description = this.Description,
  108. };
  109. if(this.Options != null || this.Options.Any())
  110. {
  111. var options = new List<ApplicationCommandOptionProperties>();
  112. this.Options.ForEach(x => options.Add(x.Build()));
  113. props.Options = options;
  114. }
  115. return props;
  116. }
  117. /// <summary>
  118. /// Makes this command a global application command .
  119. /// </summary>
  120. /// <returns>The current builder.</returns>
  121. public SlashCommandBuilder MakeGlobal()
  122. {
  123. this.isGlobal = true;
  124. return this;
  125. }
  126. /// <summary>
  127. /// Makes this command a guild specific command.
  128. /// </summary>
  129. /// <param name="GuildId">The Id of the target guild.</param>
  130. /// <returns>The current builder.</returns>
  131. public SlashCommandBuilder ForGuild(ulong GuildId)
  132. {
  133. this.GuildId = GuildId;
  134. return this;
  135. }
  136. public SlashCommandBuilder WithName(string Name)
  137. {
  138. this.Name = Name;
  139. return this;
  140. }
  141. /// <summary>
  142. /// Sets the description of the current command.
  143. /// </summary>
  144. /// <param name="Description">The description of this command.</param>
  145. /// <returns>The current builder.</returns>
  146. public SlashCommandBuilder WithDescription(string Description)
  147. {
  148. this.Description = Description;
  149. return this;
  150. }
  151. /// <summary>
  152. /// Adds an option to the current slash command.
  153. /// </summary>
  154. /// <param name="Name">The name of the option to add.</param>
  155. /// <param name="Type">The type of this option.</param>
  156. /// <param name="Description">The description of this option.</param>
  157. /// <param name="Required">If this option is required for this command.</param>
  158. /// <param name="Default">If this option is the default option.</param>
  159. /// <param name="Options">The options of the option to add.</param>
  160. /// <param name="Choices">The choices of this option.</param>
  161. /// <returns>The current builder.</returns>
  162. public SlashCommandBuilder AddOption(string Name, ApplicationCommandOptionType Type,
  163. string Description, bool Required = true, bool Default = false, List<SlashCommandOptionBuilder> Options = null, params ApplicationCommandOptionChoiceProperties[] Choices)
  164. {
  165. // Make sure the name matches the requirements from discord
  166. Preconditions.NotNullOrEmpty(Name, nameof(Name));
  167. Preconditions.AtLeast(Name.Length, 3, nameof(Name));
  168. Preconditions.AtMost(Name.Length, MaxNameLength, nameof(Name));
  169. // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc,
  170. // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
  171. if (!Regex.IsMatch(Name, @"^[\w-]{3,32}$"))
  172. throw new ArgumentException("Command name cannot contian any special characters or whitespaces!", nameof(Name));
  173. // same with description
  174. Preconditions.NotNullOrEmpty(Description, nameof(Description));
  175. Preconditions.AtLeast(Description.Length, 3, nameof(Description));
  176. Preconditions.AtMost(Description.Length, MaxDescriptionLength, nameof(Description));
  177. // make sure theres only one option with default set to true
  178. if (Default)
  179. {
  180. if (this.Options != null)
  181. if (this.Options.Any(x => x.Default))
  182. throw new ArgumentException("There can only be one command option with default set to true!", nameof(Default));
  183. }
  184. SlashCommandOptionBuilder option = new SlashCommandOptionBuilder();
  185. option.Name = Name;
  186. option.Description = Description;
  187. option.Required = Required;
  188. option.Default = Default;
  189. option.Options = Options;
  190. option.Choices = Choices != null ? new List<ApplicationCommandOptionChoiceProperties>(Choices) : null;
  191. return AddOption(option);
  192. }
  193. /// <summary>
  194. /// Adds an option to the current slash command.
  195. /// </summary>
  196. /// <param name="Name">The name of the option to add.</param>
  197. /// <param name="Type">The type of this option.</param>
  198. /// <param name="Description">The description of this option.</param>
  199. /// <param name="Required">If this option is required for this command.</param>
  200. /// <param name="Default">If this option is the default option.</param>
  201. /// <param name="Choices">The choices of this option.</param>
  202. /// <returns>The current builder.</returns>
  203. public SlashCommandBuilder AddOption(string Name, ApplicationCommandOptionType Type,
  204. string Description, bool Required = true, bool Default = false, params ApplicationCommandOptionChoiceProperties[] Choices)
  205. => AddOption(Name, Type, Description, Required, Default, null, Choices);
  206. /// <summary>
  207. /// Adds an option to the current slash command.
  208. /// </summary>
  209. /// <param name="Name">The name of the option to add.</param>
  210. /// <param name="Type">The type of this option.</param>
  211. /// <param name="Description">The sescription of this option.</param>
  212. /// <returns>The current builder.</returns>
  213. public SlashCommandBuilder AddOption(string Name, ApplicationCommandOptionType Type, string Description)
  214. => AddOption(Name, Type, Description, Options: null, Choices: null);
  215. /// <summary>
  216. /// Adds an option to this slash command.
  217. /// </summary>
  218. /// <param name="Parameter">The option to add.</param>
  219. /// <returns>The current builder.</returns>
  220. public SlashCommandBuilder AddOption(SlashCommandOptionBuilder Option)
  221. {
  222. if (this.Options == null)
  223. this.Options = new List<SlashCommandOptionBuilder>();
  224. if (this.Options.Count >= MaxOptionsCount)
  225. throw new ArgumentOutOfRangeException(nameof(Options), $"Cannot have more than {MaxOptionsCount} options!");
  226. if (Option == null)
  227. throw new ArgumentNullException(nameof(Option), "Option cannot be null");
  228. this.Options.Add(Option);
  229. return this;
  230. }
  231. /// <summary>
  232. /// Adds a collection of options to the current slash command.
  233. /// </summary>
  234. /// <param name="Parameter">The collection of options to add.</param>
  235. /// <returns>The current builder.</returns>
  236. public SlashCommandBuilder AddOptions(params SlashCommandOptionBuilder[] Options)
  237. {
  238. if (Options == null)
  239. throw new ArgumentNullException(nameof(Options), "Options cannot be null!");
  240. if (Options.Length == 0)
  241. throw new ArgumentException(nameof(Options), "Options cannot be empty!");
  242. if (this.Options == null)
  243. this.Options = new List<SlashCommandOptionBuilder>();
  244. if (this.Options.Count + Options.Length > MaxOptionsCount)
  245. throw new ArgumentOutOfRangeException(nameof(Options), $"Cannot have more than {MaxOptionsCount} options!");
  246. this.Options.AddRange(Options);
  247. return this;
  248. }
  249. }
  250. /// <summary>
  251. /// Represents a class used to build options for the <see cref="SlashCommandBuilder"/>.
  252. /// </summary>
  253. public class SlashCommandOptionBuilder
  254. {
  255. /// <summary>
  256. /// The max length of a choice's name allowed by Discord.
  257. /// </summary>
  258. public const int ChoiceNameMaxLength = 100;
  259. /// <summary>
  260. /// The maximum number of choices allowed by Discord.
  261. /// </summary>
  262. public const int MaxChoiceCount = 10;
  263. private string _name;
  264. private string _description;
  265. /// <summary>
  266. /// The name of this option.
  267. /// </summary>
  268. public string Name
  269. {
  270. get => _name;
  271. set
  272. {
  273. if (value?.Length > SlashCommandBuilder.MaxNameLength)
  274. throw new ArgumentException("Name length must be less than or equal to 32");
  275. if(value?.Length < 3)
  276. throw new ArgumentException("Name length must at least 3 characters in length");
  277. // Discord updated the docs, this regex prevents special characters like @!$%(... etc,
  278. // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
  279. if (!Regex.IsMatch(value, @"^[\w-]{3,32}$"))
  280. throw new ArgumentException("Option name cannot contian any special characters or whitespaces!");
  281. _name = value;
  282. }
  283. }
  284. /// <summary>
  285. /// The description of this option.
  286. /// </summary>
  287. public string Description
  288. {
  289. get => _description;
  290. set
  291. {
  292. if (value?.Length > SlashCommandBuilder.MaxDescriptionLength)
  293. throw new ArgumentException("Description length must be less than or equal to 100");
  294. if (value?.Length < 1)
  295. throw new ArgumentException("Name length must at least 1 character in length");
  296. _description = value;
  297. }
  298. }
  299. /// <summary>
  300. /// The type of this option.
  301. /// </summary>
  302. public ApplicationCommandOptionType Type { get; set; }
  303. /// <summary>
  304. /// The first required option for the user to complete. only one option can be default.
  305. /// </summary>
  306. public bool Default { get; set; }
  307. /// <summary>
  308. /// <see langword="true"/> if this option is required for this command, otherwise <see langword="false"/>.
  309. /// </summary>
  310. public bool Required { get; set; }
  311. /// <summary>
  312. /// choices for string and int types for the user to pick from.
  313. /// </summary>
  314. public List<ApplicationCommandOptionChoiceProperties> Choices { get; set; }
  315. /// <summary>
  316. /// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
  317. /// </summary>
  318. public List<SlashCommandOptionBuilder> Options { get; set; }
  319. /// <summary>
  320. /// Builds the current option.
  321. /// </summary>
  322. /// <returns>The build version of this option</returns>
  323. public ApplicationCommandOptionProperties Build()
  324. {
  325. bool isSubType = this.Type == ApplicationCommandOptionType.SubCommand || this.Type == ApplicationCommandOptionType.SubCommandGroup;
  326. if (isSubType && (Options == null || !Options.Any()))
  327. throw new ArgumentException(nameof(Options), "SubCommands/SubCommandGroups must have at least one option");
  328. if (!isSubType && (Options != null && Options.Any()))
  329. throw new ArgumentException(nameof(Options), $"Cannot have options on {Type} type");
  330. return new ApplicationCommandOptionProperties()
  331. {
  332. Name = this.Name,
  333. Description = this.Description,
  334. Default = this.Default,
  335. Required = this.Required,
  336. Type = this.Type,
  337. Options = new List<ApplicationCommandOptionProperties>(this.Options.Select(x => x.Build())),
  338. Choices = this.Choices
  339. };
  340. }
  341. /// <summary>
  342. /// Adds a sub
  343. /// </summary>
  344. /// <param name="option"></param>
  345. /// <returns></returns>
  346. public SlashCommandOptionBuilder AddOption(SlashCommandOptionBuilder option)
  347. {
  348. if (this.Options == null)
  349. this.Options = new List<SlashCommandOptionBuilder>();
  350. if (this.Options.Count >= SlashCommandBuilder.MaxOptionsCount)
  351. throw new ArgumentOutOfRangeException(nameof(Choices), $"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!");
  352. if (option == null)
  353. throw new ArgumentNullException(nameof(option), "Option cannot be null");
  354. Options.Add(option);
  355. return this;
  356. }
  357. public SlashCommandOptionBuilder AddChoice(string Name, int Value)
  358. {
  359. if (Choices == null)
  360. Choices = new List<ApplicationCommandOptionChoiceProperties>();
  361. if (Choices.Count >= MaxChoiceCount)
  362. throw new ArgumentOutOfRangeException(nameof(Choices), $"Cannot add more than {MaxChoiceCount} choices!");
  363. Choices.Add(new ApplicationCommandOptionChoiceProperties()
  364. {
  365. Name = Name,
  366. Value = Value
  367. });
  368. return this;
  369. }
  370. public SlashCommandOptionBuilder AddChoice(string Name, string Value)
  371. {
  372. if (Choices == null)
  373. Choices = new List<ApplicationCommandOptionChoiceProperties>();
  374. if (Choices.Count >= MaxChoiceCount)
  375. throw new ArgumentOutOfRangeException(nameof(Choices), $"Cannot add more than {MaxChoiceCount} choices!");
  376. Choices.Add(new ApplicationCommandOptionChoiceProperties()
  377. {
  378. Name = Name,
  379. Value = Value
  380. });
  381. return this;
  382. }
  383. public SlashCommandOptionBuilder WithName(string Name, int Value)
  384. {
  385. if (Choices == null)
  386. Choices = new List<ApplicationCommandOptionChoiceProperties>();
  387. if (Choices.Count >= MaxChoiceCount)
  388. throw new ArgumentOutOfRangeException(nameof(Choices), $"Cannot add more than {MaxChoiceCount} choices!");
  389. Choices.Add(new ApplicationCommandOptionChoiceProperties()
  390. {
  391. Name = Name,
  392. Value = Value
  393. });
  394. return this;
  395. }
  396. public SlashCommandOptionBuilder WithDescription(string Description)
  397. {
  398. this.Description = Description;
  399. return this;
  400. }
  401. public SlashCommandOptionBuilder WithRequired(bool value)
  402. {
  403. this.Required = value;
  404. return this;
  405. }
  406. public SlashCommandOptionBuilder WithDefault(bool value)
  407. {
  408. this.Default = value;
  409. return this;
  410. }
  411. public SlashCommandOptionBuilder WithType(ApplicationCommandOptionType Type)
  412. {
  413. this.Type = Type;
  414. return this;
  415. }
  416. }
  417. }