feat: allow RestOrArray for command option builders (#10175)

* feat(builders): allow RestOrArray for command option builders

change ApplicationCommandOption methods to allow both rest and array params,
which previously wasn't consistent with other builders.

* chore: merge imports

---------

Co-authored-by: almeidx <github@almeidx.dev>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
LeCarbonator 2024-03-24 19:12:05 +01:00 committed by GitHub
parent ddc927fabd
commit a1a3a95c94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 24 additions and 19 deletions

View file

@ -144,23 +144,22 @@ describe('Slash Commands', () => {
integer integer
.setName('iscool') .setName('iscool')
.setDescription('Are we cool or what?') .setDescription('Are we cool or what?')
.addChoices({ name: 'Very cool', value: 1_000 }), .addChoices({ name: 'Very cool', value: 1_000 })
.addChoices([{ name: 'Even cooler', value: 2_000 }]),
) )
.addNumberOption((number) => .addNumberOption((number) =>
number number
.setName('iscool') .setName('iscool')
.setDescription('Are we cool or what?') .setDescription('Are we cool or what?')
.addChoices({ name: 'Very cool', value: 1.5 }), .addChoices({ name: 'Very cool', value: 1.5 })
.addChoices([{ name: 'Even cooler', value: 2.5 }]),
) )
.addStringOption((string) => .addStringOption((string) =>
string string
.setName('iscool') .setName('iscool')
.setDescription('Are we cool or what?') .setDescription('Are we cool or what?')
.addChoices( .addChoices({ name: 'Fancy Pants', value: 'fp_1' }, { name: 'Fancy Shoes', value: 'fs_1' })
{ name: 'Fancy Pants', value: 'fp_1' }, .addChoices([{ name: 'The Whole shebang', value: 'all' }]),
{ name: 'Fancy Shoes', value: 'fs_1' },
{ name: 'The Whole shebang', value: 'all' },
),
) )
.addIntegerOption((integer) => .addIntegerOption((integer) =>
integer.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true), integer.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
@ -229,7 +228,9 @@ describe('Slash Commands', () => {
test('GIVEN a builder with valid channel options and channel_types THEN does not throw an error', () => { test('GIVEN a builder with valid channel options and channel_types THEN does not throw an error', () => {
expect(() => expect(() =>
getBuilder().addChannelOption(getChannelOption().addChannelTypes(ChannelType.GuildText)), getBuilder().addChannelOption(
getChannelOption().addChannelTypes(ChannelType.GuildText).addChannelTypes([ChannelType.GuildVoice]),
),
).not.toThrowError(); ).not.toThrowError();
expect(() => { expect(() => {

View file

@ -1,5 +1,6 @@
import { s } from '@sapphire/shapeshift'; import { s } from '@sapphire/shapeshift';
import { ChannelType } from 'discord-api-types/v10'; import { ChannelType } from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../../../util/normalizeArray';
/** /**
* The allowed channel types used for a channel option in a slash command builder. * The allowed channel types used for a channel option in a slash command builder.
@ -41,12 +42,12 @@ export class ApplicationCommandOptionChannelTypesMixin {
* *
* @param channelTypes - The channel types * @param channelTypes - The channel types
*/ */
public addChannelTypes(...channelTypes: ApplicationCommandOptionAllowedChannelTypes[]) { public addChannelTypes(...channelTypes: RestOrArray<ApplicationCommandOptionAllowedChannelTypes>) {
if (this.channel_types === undefined) { if (this.channel_types === undefined) {
Reflect.set(this, 'channel_types', []); Reflect.set(this, 'channel_types', []);
} }
this.channel_types!.push(...channelTypesPredicate.parse(channelTypes)); this.channel_types!.push(...channelTypesPredicate.parse(normalizeArray(channelTypes)));
return this; return this;
} }

View file

@ -1,5 +1,6 @@
import { s } from '@sapphire/shapeshift'; import { s } from '@sapphire/shapeshift';
import { ApplicationCommandOptionType, type APIApplicationCommandOptionChoice } from 'discord-api-types/v10'; import { ApplicationCommandOptionType, type APIApplicationCommandOptionChoice } from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../../../util/normalizeArray.js';
import { localizationMapPredicate, validateChoicesLength } from '../Assertions.js'; import { localizationMapPredicate, validateChoicesLength } from '../Assertions.js';
const stringPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100); const stringPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
@ -31,20 +32,21 @@ export class ApplicationCommandOptionWithChoicesMixin<ChoiceType extends number
* *
* @param choices - The choices to add * @param choices - The choices to add
*/ */
public addChoices(...choices: APIApplicationCommandOptionChoice<ChoiceType>[]): this { public addChoices(...choices: RestOrArray<APIApplicationCommandOptionChoice<ChoiceType>>): this {
if (choices.length > 0 && 'autocomplete' in this && this.autocomplete) { const normalizedChoices = normalizeArray(choices);
if (normalizedChoices.length > 0 && 'autocomplete' in this && this.autocomplete) {
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.'); throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
} }
choicesPredicate.parse(choices); choicesPredicate.parse(normalizedChoices);
if (this.choices === undefined) { if (this.choices === undefined) {
Reflect.set(this, 'choices', []); Reflect.set(this, 'choices', []);
} }
validateChoicesLength(choices.length, this.choices); validateChoicesLength(normalizedChoices.length, this.choices);
for (const { name, name_localizations, value } of choices) { for (const { name, name_localizations, value } of normalizedChoices) {
// Validate the value // Validate the value
if (this.type === ApplicationCommandOptionType.String) { if (this.type === ApplicationCommandOptionType.String) {
stringPredicate.parse(value); stringPredicate.parse(value);
@ -63,15 +65,16 @@ export class ApplicationCommandOptionWithChoicesMixin<ChoiceType extends number
* *
* @param choices - The choices to set * @param choices - The choices to set
*/ */
public setChoices<Input extends APIApplicationCommandOptionChoice<ChoiceType>[]>(...choices: Input): this { public setChoices<Input extends APIApplicationCommandOptionChoice<ChoiceType>>(...choices: RestOrArray<Input>): this {
if (choices.length > 0 && 'autocomplete' in this && this.autocomplete) { const normalizedChoices = normalizeArray(choices);
if (normalizedChoices.length > 0 && 'autocomplete' in this && this.autocomplete) {
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.'); throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
} }
choicesPredicate.parse(choices); choicesPredicate.parse(normalizedChoices);
Reflect.set(this, 'choices', []); Reflect.set(this, 'choices', []);
this.addChoices(...choices); this.addChoices(normalizedChoices);
return this; return this;
} }