From ed5a26151a978c6f3d674427785889583373509f Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 13:50:47 +0300 Subject: [PATCH 01/23] feat(builders): new select menus --- .../__tests__/components/actionRow.test.ts | 20 +-- .../__tests__/components/components.test.ts | 10 +- .../__tests__/components/selectMenu.test.ts | 16 +- packages/builders/package.json | 2 +- packages/builders/src/components/ActionRow.ts | 14 +- .../builders/src/components/Assertions.ts | 6 +- .../builders/src/components/Components.ts | 24 ++- .../components/selectMenu/BaseSelectMenu.ts | 64 +++++++ .../selectMenu/ChannelSelectMenu.ts | 61 +++++++ .../selectMenu/MentionableSelectMenu.ts | 31 ++++ .../components/selectMenu/RoleSelectMenu.ts | 31 ++++ .../src/components/selectMenu/SelectMenu.ts | 168 +----------------- .../components/selectMenu/SelectMenuOption.ts | 103 +---------- .../components/selectMenu/StringSelectMenu.ts | 106 +++++++++++ .../selectMenu/StringSelectMenuOption.ts | 98 ++++++++++ .../components/selectMenu/UserSelectMenu.ts | 31 ++++ packages/builders/src/index.ts | 8 + packages/discord.js/package.json | 2 +- packages/rest/package.json | 2 +- packages/voice/package.json | 2 +- packages/ws/package.json | 2 +- yarn.lock | 18 +- 22 files changed, 518 insertions(+), 301 deletions(-) create mode 100644 packages/builders/src/components/selectMenu/BaseSelectMenu.ts create mode 100644 packages/builders/src/components/selectMenu/ChannelSelectMenu.ts create mode 100644 packages/builders/src/components/selectMenu/MentionableSelectMenu.ts create mode 100644 packages/builders/src/components/selectMenu/RoleSelectMenu.ts create mode 100644 packages/builders/src/components/selectMenu/StringSelectMenu.ts create mode 100644 packages/builders/src/components/selectMenu/StringSelectMenuOption.ts create mode 100644 packages/builders/src/components/selectMenu/UserSelectMenu.ts diff --git a/packages/builders/__tests__/components/actionRow.test.ts b/packages/builders/__tests__/components/actionRow.test.ts index d0bb9f7b584f..b9f63b501529 100644 --- a/packages/builders/__tests__/components/actionRow.test.ts +++ b/packages/builders/__tests__/components/actionRow.test.ts @@ -9,8 +9,8 @@ import { ActionRowBuilder, ButtonBuilder, createComponentBuilder, - SelectMenuBuilder, - SelectMenuOptionBuilder, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, } from '../../src/index.js'; const rowWithButtonData: APIActionRowComponent = { @@ -29,7 +29,7 @@ const rowWithSelectMenuData: APIActionRowComponent type: ComponentType.ActionRow, components: [ { - type: ComponentType.SelectMenu, + type: ComponentType.StringSelect, custom_id: '1234', options: [ { @@ -73,7 +73,7 @@ describe('Action Row Components', () => { url: 'https://google.com', }, { - type: ComponentType.SelectMenu, + type: ComponentType.StringSelect, placeholder: 'test', custom_id: 'test', options: [ @@ -108,7 +108,7 @@ describe('Action Row Components', () => { type: ComponentType.ActionRow, components: [ { - type: ComponentType.SelectMenu, + type: ComponentType.StringSelect, custom_id: '1234', options: [ { @@ -134,17 +134,17 @@ describe('Action Row Components', () => { test('GIVEN valid builder options THEN valid JSON output is given 2', () => { const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123'); - const selectMenu = new SelectMenuBuilder() + const selectMenu = new StringSelectMenuBuilder() .setCustomId('1234') .setMaxValues(10) .setMinValues(12) .setOptions( - new SelectMenuOptionBuilder().setLabel('one').setValue('one'), - new SelectMenuOptionBuilder().setLabel('two').setValue('two'), + new StringSelectMenuOptionBuilder().setLabel('one').setValue('one'), + new StringSelectMenuOptionBuilder().setLabel('two').setValue('two'), ) .setOptions([ - new SelectMenuOptionBuilder().setLabel('one').setValue('one'), - new SelectMenuOptionBuilder().setLabel('two').setValue('two'), + new StringSelectMenuOptionBuilder().setLabel('one').setValue('one'), + new StringSelectMenuOptionBuilder().setLabel('two').setValue('two'), ]); expect(new ActionRowBuilder().addComponents(button).toJSON()).toEqual(rowWithButtonData); diff --git a/packages/builders/__tests__/components/components.test.ts b/packages/builders/__tests__/components/components.test.ts index 520244d438eb..fa0bd4607f65 100644 --- a/packages/builders/__tests__/components/components.test.ts +++ b/packages/builders/__tests__/components/components.test.ts @@ -13,12 +13,12 @@ import { ActionRowBuilder, ButtonBuilder, createComponentBuilder, - SelectMenuBuilder, + StringSelectMenuBuilder, TextInputBuilder, } from '../../src/index.js'; describe('createComponentBuilder', () => { - test.each([ButtonBuilder, SelectMenuBuilder, TextInputBuilder])( + test.each([ButtonBuilder, StringSelectMenuBuilder, TextInputBuilder])( 'passing an instance of %j should return itself', (Builder) => { const builder = new Builder(); @@ -45,14 +45,14 @@ describe('createComponentBuilder', () => { expect(createComponentBuilder(button)).toBeInstanceOf(ButtonBuilder); }); - test('GIVEN a select menu component THEN returns a SelectMenuBuilder', () => { + test('GIVEN a select menu component THEN returns a StringSelectMenuBuilder', () => { const selectMenu: APISelectMenuComponent = { custom_id: 'abc', options: [], - type: ComponentType.SelectMenu, + type: ComponentType.StringSelect, }; - expect(createComponentBuilder(selectMenu)).toBeInstanceOf(SelectMenuBuilder); + expect(createComponentBuilder(selectMenu)).toBeInstanceOf(StringSelectMenuBuilder); }); test('GIVEN a text input component THEN returns a TextInputBuilder', () => { diff --git a/packages/builders/__tests__/components/selectMenu.test.ts b/packages/builders/__tests__/components/selectMenu.test.ts index c5e10c53ccde..f7ab28144918 100644 --- a/packages/builders/__tests__/components/selectMenu.test.ts +++ b/packages/builders/__tests__/components/selectMenu.test.ts @@ -1,9 +1,9 @@ import { ComponentType, type APISelectMenuComponent, type APISelectMenuOption } from 'discord-api-types/v10'; import { describe, test, expect } from 'vitest'; -import { SelectMenuBuilder, SelectMenuOptionBuilder } from '../../src/index.js'; +import { StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from '../../src/index.js'; -const selectMenu = () => new SelectMenuBuilder(); -const selectMenuOption = () => new SelectMenuOptionBuilder(); +const selectMenu = () => new StringSelectMenuBuilder(); +const selectMenuOption = () => new StringSelectMenuOptionBuilder(); const longStr = 'a'.repeat(256); @@ -165,16 +165,16 @@ describe('Select Menu Components', () => { test('GIVEN valid JSON input THEN valid JSON history is correct', () => { expect( - new SelectMenuBuilder(selectMenuDataWithoutOptions) - .addOptions(new SelectMenuOptionBuilder(selectMenuOptionData)) + new StringSelectMenuBuilder(selectMenuDataWithoutOptions) + .addOptions(new StringSelectMenuOptionBuilder(selectMenuOptionData)) .toJSON(), ).toEqual(selectMenuData); expect( - new SelectMenuBuilder(selectMenuDataWithoutOptions) - .addOptions([new SelectMenuOptionBuilder(selectMenuOptionData)]) + new StringSelectMenuBuilder(selectMenuDataWithoutOptions) + .addOptions([new StringSelectMenuOptionBuilder(selectMenuOptionData)]) .toJSON(), ).toEqual(selectMenuData); - expect(new SelectMenuOptionBuilder(selectMenuOptionData).toJSON()).toEqual(selectMenuOptionData); + expect(new StringSelectMenuOptionBuilder(selectMenuOptionData).toJSON()).toEqual(selectMenuOptionData); }); }); }); diff --git a/packages/builders/package.json b/packages/builders/package.json index 7639a07f2dbb..fd481908da84 100644 --- a/packages/builders/package.json +++ b/packages/builders/package.json @@ -56,7 +56,7 @@ "dependencies": { "@discordjs/util": "workspace:^", "@sapphire/shapeshift": "^3.7.0", - "discord-api-types": "^0.37.14", + "discord-api-types": "^0.37.15", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.1", "tslib": "^2.4.0" diff --git a/packages/builders/src/components/ActionRow.ts b/packages/builders/src/components/ActionRow.ts index 1f10ddc85e8c..90dda30cae9e 100644 --- a/packages/builders/src/components/ActionRow.ts +++ b/packages/builders/src/components/ActionRow.ts @@ -11,14 +11,24 @@ import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js'; import { ComponentBuilder } from './Component.js'; import { createComponentBuilder } from './Components.js'; import type { ButtonBuilder } from './button/Button.js'; -import type { SelectMenuBuilder } from './selectMenu/SelectMenu.js'; +import type { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js'; +import type { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js'; +import type { RoleSelectMenuBuilder } from './selectMenu/RoleSelectMenu.js'; +import type { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js'; +import type { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js'; import type { TextInputBuilder } from './textInput/TextInput.js'; export type MessageComponentBuilder = | ActionRowBuilder | MessageActionRowComponentBuilder; export type ModalComponentBuilder = ActionRowBuilder | ModalActionRowComponentBuilder; -export type MessageActionRowComponentBuilder = ButtonBuilder | SelectMenuBuilder; +export type MessageActionRowComponentBuilder = + | ButtonBuilder + | ChannelSelectMenuBuilder + | MentionableSelectMenuBuilder + | RoleSelectMenuBuilder + | StringSelectMenuBuilder + | UserSelectMenuBuilder; export type ModalActionRowComponentBuilder = TextInputBuilder; export type AnyComponentBuilder = MessageActionRowComponentBuilder | ModalActionRowComponentBuilder; diff --git a/packages/builders/src/components/Assertions.ts b/packages/builders/src/components/Assertions.ts index faae51dd16b7..d95f24d4980f 100644 --- a/packages/builders/src/components/Assertions.ts +++ b/packages/builders/src/components/Assertions.ts @@ -1,7 +1,7 @@ import { s } from '@sapphire/shapeshift'; import { ButtonStyle, type APIMessageComponentEmoji } from 'discord-api-types/v10'; import { isValidationEnabled } from '../util/validation.js'; -import { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption.js'; +import { StringSelectMenuOptionBuilder } from './selectMenu/StringSelectMenuOption.js'; export const customIdValidator = s.string .lengthGreaterThanOrEqual(1) @@ -46,7 +46,7 @@ export const jsonOptionValidator = s }) .setValidationEnabled(isValidationEnabled); -export const optionValidator = s.instance(SelectMenuOptionBuilder).setValidationEnabled(isValidationEnabled); +export const optionValidator = s.instance(StringSelectMenuOptionBuilder).setValidationEnabled(isValidationEnabled); export const optionsValidator = optionValidator.array .lengthGreaterThanOrEqual(0) @@ -56,7 +56,7 @@ export const optionsLengthValidator = s.number.int .lessThanOrEqual(25) .setValidationEnabled(isValidationEnabled); -export function validateRequiredSelectMenuParameters(options: SelectMenuOptionBuilder[], customId?: string) { +export function validateRequiredSelectMenuParameters(options: StringSelectMenuOptionBuilder[], customId?: string) { customIdValidator.parse(customId); optionsValidator.parse(options); } diff --git a/packages/builders/src/components/Components.ts b/packages/builders/src/components/Components.ts index ea13e013de12..d3e635ece957 100644 --- a/packages/builders/src/components/Components.ts +++ b/packages/builders/src/components/Components.ts @@ -7,14 +7,22 @@ import { } from './ActionRow.js'; import { ComponentBuilder } from './Component.js'; import { ButtonBuilder } from './button/Button.js'; -import { SelectMenuBuilder } from './selectMenu/SelectMenu.js'; +import { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js'; +import { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js'; +import { RoleSelectMenuBuilder } from './selectMenu/RoleSelectMenu.js'; +import { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js'; +import { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js'; import { TextInputBuilder } from './textInput/TextInput.js'; export interface MappedComponentTypes { [ComponentType.ActionRow]: ActionRowBuilder; [ComponentType.Button]: ButtonBuilder; - [ComponentType.SelectMenu]: SelectMenuBuilder; + [ComponentType.StringSelect]: StringSelectMenuBuilder; [ComponentType.TextInput]: TextInputBuilder; + [ComponentType.UserSelect]: UserSelectMenuBuilder; + [ComponentType.RoleSelect]: RoleSelectMenuBuilder; + [ComponentType.MentionableSelect]: MentionableSelectMenuBuilder; + [ComponentType.ChannelSelect]: ChannelSelectMenuBuilder; } /** @@ -39,10 +47,18 @@ export function createComponentBuilder( return new ActionRowBuilder(data); case ComponentType.Button: return new ButtonBuilder(data); - case ComponentType.SelectMenu: - return new SelectMenuBuilder(data); + case ComponentType.StringSelect: + return new StringSelectMenuBuilder(data); case ComponentType.TextInput: return new TextInputBuilder(data); + case ComponentType.UserSelect: + return new UserSelectMenuBuilder(data); + case ComponentType.RoleSelect: + return new RoleSelectMenuBuilder(data); + case ComponentType.MentionableSelect: + return new MentionableSelectMenuBuilder(data); + case ComponentType.ChannelSelect: + return new ChannelSelectMenuBuilder(data); default: // @ts-expect-error: This case can still occur if we get a newer unsupported component type throw new Error(`Cannot properly serialize component type: ${data.type}`); diff --git a/packages/builders/src/components/selectMenu/BaseSelectMenu.ts b/packages/builders/src/components/selectMenu/BaseSelectMenu.ts new file mode 100644 index 000000000000..cd1a306ac8f8 --- /dev/null +++ b/packages/builders/src/components/selectMenu/BaseSelectMenu.ts @@ -0,0 +1,64 @@ +import type { APISelectMenuComponent } from 'discord-api-types/v10'; +import { customIdValidator, disabledValidator, minMaxValidator, placeholderValidator } from '../Assertions.js'; +import { ComponentBuilder } from '../Component.js'; + +export class BaseSelectMenuBuilder< + SelectMenuType extends APISelectMenuComponent, +> extends ComponentBuilder { + /** + * Sets the placeholder for this select menu + * + * @param placeholder - The placeholder to use for this select menu + */ + public setPlaceholder(placeholder: string) { + this.data.placeholder = placeholderValidator.parse(placeholder); + return this; + } + + /** + * Sets the minimum values that must be selected in the select menu + * + * @param minValues - The minimum values that must be selected + */ + public setMinValues(minValues: number) { + this.data.min_values = minMaxValidator.parse(minValues); + return this; + } + + /** + * Sets the maximum values that must be selected in the select menu + * + * @param maxValues - The maximum values that must be selected + */ + public setMaxValues(maxValues: number) { + this.data.max_values = minMaxValidator.parse(maxValues); + return this; + } + + /** + * Sets the custom id for this select menu + * + * @param customId - The custom id to use for this select menu + */ + public setCustomId(customId: string) { + this.data.custom_id = customIdValidator.parse(customId); + return this; + } + + /** + * Sets whether this select menu is disabled + * + * @param disabled - Whether this select menu is disabled + */ + public setDisabled(disabled = true) { + this.data.disabled = disabledValidator.parse(disabled); + return this; + } + + public toJSON(): SelectMenuType { + customIdValidator.parse(this.data.custom_id); + return { + ...this.data, + } as SelectMenuType; + } +} diff --git a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts new file mode 100644 index 000000000000..51e682b72c0a --- /dev/null +++ b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts @@ -0,0 +1,61 @@ +import type { APIChannelSelectComponent, ChannelType } from 'discord-api-types/v10'; +import { ComponentType } from 'discord-api-types/v10'; +import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js'; +import { customIdValidator } from '../Assertions.js'; +import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; + +export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder { + /** + * Creates a new select menu from API data + * + * @param data - The API data to create this select menu with + * @example + * Creating a select menu from an API data object + * ```ts + * const selectMenu = new ChannelSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * placeholder: 'select an option', + * max_values: 2, + * }); + * ``` + * @example + * Creating a select menu using setters and API data + * ```ts + * const selectMenu = new ChannelSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * }) + * .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement) + * .setMinValues(2) + * ``` + */ + public constructor(data?: Partial) { + const { channel_types = [], ...initData } = data ?? {}; + super({ ...initData, type: ComponentType.ChannelSelect, channel_types }); + } + + public addChannelTypes(...types: RestOrArray) { + // eslint-disable-next-line no-param-reassign + types = normalizeArray(types); + this.data.channel_types?.push(...types); + return this; + } + + public setChannelTypes(...types: RestOrArray) { + // eslint-disable-next-line no-param-reassign + types = normalizeArray(types); + + this.data.channel_types?.splice(0, this.data.channel_types.length, ...types); + return this; + } + + /** + * {@inheritDoc ComponentBuilder.toJSON} + */ + public override toJSON(): APIChannelSelectComponent { + customIdValidator.parse(this.data.custom_id); + + return { + ...this.data, + } as APIChannelSelectComponent; + } +} diff --git a/packages/builders/src/components/selectMenu/MentionableSelectMenu.ts b/packages/builders/src/components/selectMenu/MentionableSelectMenu.ts new file mode 100644 index 000000000000..c996e2b4776d --- /dev/null +++ b/packages/builders/src/components/selectMenu/MentionableSelectMenu.ts @@ -0,0 +1,31 @@ +import type { APIMentionableSelectComponent } from 'discord-api-types/v10'; +import { ComponentType } from 'discord-api-types/v10'; +import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; + +export class MentionableSelectMenuBuilder extends BaseSelectMenuBuilder { + /** + * Creates a new select menu from API data + * + * @param data - The API data to create this select menu with + * @example + * Creating a select menu from an API data object + * ```ts + * const selectMenu = new MentionableSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * placeholder: 'select an option', + * max_values: 2, + * }); + * ``` + * @example + * Creating a select menu using setters and API data + * ```ts + * const selectMenu = new MentionableSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * }) + * .setMinValues(1) + * ``` + */ + public constructor(data?: Partial) { + super({ ...data, type: ComponentType.MentionableSelect }); + } +} diff --git a/packages/builders/src/components/selectMenu/RoleSelectMenu.ts b/packages/builders/src/components/selectMenu/RoleSelectMenu.ts new file mode 100644 index 000000000000..818ef5b7763f --- /dev/null +++ b/packages/builders/src/components/selectMenu/RoleSelectMenu.ts @@ -0,0 +1,31 @@ +import type { APIRoleSelectComponent } from 'discord-api-types/v10'; +import { ComponentType } from 'discord-api-types/v10'; +import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; + +export class RoleSelectMenuBuilder extends BaseSelectMenuBuilder { + /** + * Creates a new select menu from API data + * + * @param data - The API data to create this select menu with + * @example + * Creating a select menu from an API data object + * ```ts + * const selectMenu = new RoleSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * placeholder: 'select an option', + * max_values: 2, + * }); + * ``` + * @example + * Creating a select menu using setters and API data + * ```ts + * const selectMenu = new RoleSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * }) + * .setMinValues(1) + * ``` + */ + public constructor(data?: Partial) { + super({ ...data, type: ComponentType.RoleSelect }); + } +} diff --git a/packages/builders/src/components/selectMenu/SelectMenu.ts b/packages/builders/src/components/selectMenu/SelectMenu.ts index 496138a020ac..f32431b09dba 100644 --- a/packages/builders/src/components/selectMenu/SelectMenu.ts +++ b/packages/builders/src/components/selectMenu/SelectMenu.ts @@ -1,163 +1,11 @@ -import { ComponentType, type APISelectMenuComponent, type APISelectMenuOption } from 'discord-api-types/v10'; -import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js'; -import { - customIdValidator, - disabledValidator, - jsonOptionValidator, - minMaxValidator, - optionsLengthValidator, - placeholderValidator, - validateRequiredSelectMenuParameters, -} from '../Assertions.js'; -import { ComponentBuilder } from '../Component.js'; -import { SelectMenuOptionBuilder } from './SelectMenuOption.js'; +import { StringSelectMenuBuilder } from './StringSelectMenu.js'; + +// This is simply an alias for StringSelectMenuBuilder for the purpose of backwards compat +// TODO: Remove in v2 /** - * Represents a select menu component + * Alias for {@link StringSelectMenuBuilder} + * + * @deprecated Use {@link StringSelectMenuBuilder} directly instead */ -export class SelectMenuBuilder extends ComponentBuilder { - /** - * The options within this select menu - */ - public readonly options: SelectMenuOptionBuilder[]; - - /** - * Creates a new select menu from API data - * - * @param data - The API data to create this select menu with - * @example - * Creating a select menu from an API data object - * ```ts - * const selectMenu = new SelectMenuBuilder({ - * custom_id: 'a cool select menu', - * placeholder: 'select an option', - * max_values: 2, - * options: [ - * { label: 'option 1', value: '1' }, - * { label: 'option 2', value: '2' }, - * { label: 'option 3', value: '3' }, - * ], - * }); - * ``` - * @example - * Creating a select menu using setters and API data - * ```ts - * const selectMenu = new SelectMenuBuilder({ - * custom_id: 'a cool select menu', - * }) - * .setMinValues(1) - * .addOptions({ - * label: 'Catchy', - * value: 'catch', - * }); - * ``` - */ - public constructor(data?: Partial) { - const { options, ...initData } = data ?? {}; - super({ type: ComponentType.SelectMenu, ...initData }); - this.options = options?.map((option) => new SelectMenuOptionBuilder(option)) ?? []; - } - - /** - * Sets the placeholder for this select menu - * - * @param placeholder - The placeholder to use for this select menu - */ - public setPlaceholder(placeholder: string) { - this.data.placeholder = placeholderValidator.parse(placeholder); - return this; - } - - /** - * Sets the minimum values that must be selected in the select menu - * - * @param minValues - The minimum values that must be selected - */ - public setMinValues(minValues: number) { - this.data.min_values = minMaxValidator.parse(minValues); - return this; - } - - /** - * Sets the maximum values that must be selected in the select menu - * - * @param maxValues - The maximum values that must be selected - */ - public setMaxValues(maxValues: number) { - this.data.max_values = minMaxValidator.parse(maxValues); - return this; - } - - /** - * Sets the custom id for this select menu - * - * @param customId - The custom id to use for this select menu - */ - public setCustomId(customId: string) { - this.data.custom_id = customIdValidator.parse(customId); - return this; - } - - /** - * Sets whether this select menu is disabled - * - * @param disabled - Whether this select menu is disabled - */ - public setDisabled(disabled = true) { - this.data.disabled = disabledValidator.parse(disabled); - return this; - } - - /** - * Adds options to this select menu - * - * @param options - The options to add to this select menu - * @returns - */ - public addOptions(...options: RestOrArray) { - // eslint-disable-next-line no-param-reassign - options = normalizeArray(options); - optionsLengthValidator.parse(this.options.length + options.length); - this.options.push( - ...options.map((option) => - option instanceof SelectMenuOptionBuilder - ? option - : new SelectMenuOptionBuilder(jsonOptionValidator.parse(option)), - ), - ); - return this; - } - - /** - * Sets the options on this select menu - * - * @param options - The options to set on this select menu - */ - public setOptions(...options: RestOrArray) { - // eslint-disable-next-line no-param-reassign - options = normalizeArray(options); - optionsLengthValidator.parse(options.length); - this.options.splice( - 0, - this.options.length, - ...options.map((option) => - option instanceof SelectMenuOptionBuilder - ? option - : new SelectMenuOptionBuilder(jsonOptionValidator.parse(option)), - ), - ); - return this; - } - - /** - * {@inheritDoc ComponentBuilder.toJSON} - */ - public toJSON(): APISelectMenuComponent { - validateRequiredSelectMenuParameters(this.options, this.data.custom_id); - - return { - ...this.data, - options: this.options.map((option) => option.toJSON()), - } as APISelectMenuComponent; - } -} +export class SelectMenuBuilder extends StringSelectMenuBuilder {} diff --git a/packages/builders/src/components/selectMenu/SelectMenuOption.ts b/packages/builders/src/components/selectMenu/SelectMenuOption.ts index 6654e7dba0dc..38c8a10aa261 100644 --- a/packages/builders/src/components/selectMenu/SelectMenuOption.ts +++ b/packages/builders/src/components/selectMenu/SelectMenuOption.ts @@ -1,98 +1,11 @@ -import type { JSONEncodable } from '@discordjs/util'; -import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10'; -import { - defaultValidator, - emojiValidator, - labelValueDescriptionValidator, - validateRequiredSelectMenuOptionParameters, -} from '../Assertions.js'; +import { StringSelectMenuOptionBuilder } from './StringSelectMenuOption.js'; + +// This is simply an alias for StringSelectMenuOptionBuilder for the purpose of backwards compat +// TODO: Remove in v2 /** - * Represents a option within a select menu component + * Alias for {@link StringSelectMenuOptionBuilder} + * + * @deprecated Use {@link StringSelectMenuOptionBuilder} directly instead */ -export class SelectMenuOptionBuilder implements JSONEncodable { - /** - * Creates a new select menu option from API data - * - * @param data - The API data to create this select menu option with - * @example - * Creating a select menu option from an API data object - * ```ts - * const selectMenuOption = new SelectMenuOptionBuilder({ - * label: 'catchy label', - * value: '1', - * }); - * ``` - * @example - * Creating a select menu option using setters and API data - * ```ts - * const selectMenuOption = new SelectMenuOptionBuilder({ - * default: true, - * value: '1', - * }) - * .setLabel('woah') - * ``` - */ - public constructor(public data: Partial = {}) {} - - /** - * Sets the label of this option - * - * @param label - The label to show on this option - */ - public setLabel(label: string) { - this.data.label = labelValueDescriptionValidator.parse(label); - return this; - } - - /** - * Sets the value of this option - * - * @param value - The value of this option - */ - public setValue(value: string) { - this.data.value = labelValueDescriptionValidator.parse(value); - return this; - } - - /** - * Sets the description of this option - * - * @param description - The description of this option - */ - public setDescription(description: string) { - this.data.description = labelValueDescriptionValidator.parse(description); - return this; - } - - /** - * Sets whether this option is selected by default - * - * @param isDefault - Whether this option is selected by default - */ - public setDefault(isDefault = true) { - this.data.default = defaultValidator.parse(isDefault); - return this; - } - - /** - * Sets the emoji to display on this option - * - * @param emoji - The emoji to display on this option - */ - public setEmoji(emoji: APIMessageComponentEmoji) { - this.data.emoji = emojiValidator.parse(emoji); - return this; - } - - /** - * {@inheritDoc ComponentBuilder.toJSON} - */ - public toJSON(): APISelectMenuOption { - validateRequiredSelectMenuOptionParameters(this.data.label, this.data.value); - - return { - ...this.data, - } as APISelectMenuOption; - } -} +export class SelectMenuOptionBuilder extends StringSelectMenuOptionBuilder {} diff --git a/packages/builders/src/components/selectMenu/StringSelectMenu.ts b/packages/builders/src/components/selectMenu/StringSelectMenu.ts new file mode 100644 index 000000000000..ed5f928c144b --- /dev/null +++ b/packages/builders/src/components/selectMenu/StringSelectMenu.ts @@ -0,0 +1,106 @@ +import type { APIStringSelectComponent } from 'discord-api-types/v10'; +import { ComponentType, type APISelectMenuOption } from 'discord-api-types/v10'; +import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js'; +import { jsonOptionValidator, optionsLengthValidator, validateRequiredSelectMenuParameters } from '../Assertions.js'; +import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; +import { StringSelectMenuOptionBuilder } from './StringSelectMenuOption.js'; + +/** + * Represents a string select menu component + */ +export class StringSelectMenuBuilder extends BaseSelectMenuBuilder { + /** + * The options within this select menu + */ + public readonly options: StringSelectMenuOptionBuilder[]; + + /** + * Creates a new select menu from API data + * + * @param data - The API data to create this select menu with + * @example + * Creating a select menu from an API data object + * ```ts + * const selectMenu = new StringSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * placeholder: 'select an option', + * max_values: 2, + * options: [ + * { label: 'option 1', value: '1' }, + * { label: 'option 2', value: '2' }, + * { label: 'option 3', value: '3' }, + * ], + * }); + * ``` + * @example + * Creating a select menu using setters and API data + * ```ts + * const selectMenu = new StringSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * }) + * .setMinValues(1) + * .addOptions({ + * label: 'Catchy', + * value: 'catch', + * }); + * ``` + */ + public constructor(data?: Partial) { + const { options, ...initData } = data ?? {}; + super({ ...initData, type: ComponentType.StringSelect }); + this.options = options?.map((option: APISelectMenuOption) => new StringSelectMenuOptionBuilder(option)) ?? []; + } + + /** + * Adds options to this select menu + * + * @param options - The options to add to this select menu + * @returns + */ + public addOptions(...options: RestOrArray) { + // eslint-disable-next-line no-param-reassign + options = normalizeArray(options); + optionsLengthValidator.parse(this.options.length + options.length); + this.options.push( + ...options.map((option) => + option instanceof StringSelectMenuOptionBuilder + ? option + : new StringSelectMenuOptionBuilder(jsonOptionValidator.parse(option)), + ), + ); + return this; + } + + /** + * Sets the options on this select menu + * + * @param options - The options to set on this select menu + */ + public setOptions(...options: RestOrArray) { + // eslint-disable-next-line no-param-reassign + options = normalizeArray(options); + optionsLengthValidator.parse(options.length); + this.options.splice( + 0, + this.options.length, + ...options.map((option) => + option instanceof StringSelectMenuOptionBuilder + ? option + : new StringSelectMenuOptionBuilder(jsonOptionValidator.parse(option)), + ), + ); + return this; + } + + /** + * {@inheritDoc ComponentBuilder.toJSON} + */ + public override toJSON(): APIStringSelectComponent { + validateRequiredSelectMenuParameters(this.options, this.data.custom_id); + + return { + ...this.data, + options: this.options.map((option) => option.toJSON()), + } as APIStringSelectComponent; + } +} diff --git a/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts b/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts new file mode 100644 index 000000000000..c43145463947 --- /dev/null +++ b/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts @@ -0,0 +1,98 @@ +import type { JSONEncodable } from '@discordjs/util'; +import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10'; +import { + defaultValidator, + emojiValidator, + labelValueDescriptionValidator, + validateRequiredSelectMenuOptionParameters, +} from '../Assertions.js'; + +/** + * Represents an option within a string select menu component + */ +export class StringSelectMenuOptionBuilder implements JSONEncodable { + /** + * Creates a new string select menu option from API data + * + * @param data - The API data to create this string select menu option with + * @example + * Creating a string select menu option from an API data object + * ```ts + * const selectMenuOption = new SelectMenuOptionBuilder({ + * label: 'catchy label', + * value: '1', + * }); + * ``` + * @example + * Creating a string select menu option using setters and API data + * ```ts + * const selectMenuOption = new SelectMenuOptionBuilder({ + * default: true, + * value: '1', + * }) + * .setLabel('woah') + * ``` + */ + public constructor(public data: Partial = {}) {} + + /** + * Sets the label of this option + * + * @param label - The label to show on this option + */ + public setLabel(label: string) { + this.data.label = labelValueDescriptionValidator.parse(label); + return this; + } + + /** + * Sets the value of this option + * + * @param value - The value of this option + */ + public setValue(value: string) { + this.data.value = labelValueDescriptionValidator.parse(value); + return this; + } + + /** + * Sets the description of this option + * + * @param description - The description of this option + */ + public setDescription(description: string) { + this.data.description = labelValueDescriptionValidator.parse(description); + return this; + } + + /** + * Sets whether this option is selected by default + * + * @param isDefault - Whether this option is selected by default + */ + public setDefault(isDefault = true) { + this.data.default = defaultValidator.parse(isDefault); + return this; + } + + /** + * Sets the emoji to display on this option + * + * @param emoji - The emoji to display on this option + */ + public setEmoji(emoji: APIMessageComponentEmoji) { + this.data.emoji = emojiValidator.parse(emoji); + return this; + } + + /** + * {@inheritDoc ComponentBuilder.toJSON} + */ + public toJSON(): APISelectMenuOption { + validateRequiredSelectMenuOptionParameters(this.data.label, this.data.value); + + return { + ...this.data, + } as APISelectMenuOption; + } +} diff --git a/packages/builders/src/components/selectMenu/UserSelectMenu.ts b/packages/builders/src/components/selectMenu/UserSelectMenu.ts new file mode 100644 index 000000000000..7de3cb620368 --- /dev/null +++ b/packages/builders/src/components/selectMenu/UserSelectMenu.ts @@ -0,0 +1,31 @@ +import type { APIUserSelectComponent } from 'discord-api-types/v10'; +import { ComponentType } from 'discord-api-types/v10'; +import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; + +export class UserSelectMenuBuilder extends BaseSelectMenuBuilder { + /** + * Creates a new select menu from API data + * + * @param data - The API data to create this select menu with + * @example + * Creating a select menu from an API data object + * ```ts + * const selectMenu = new UserSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * placeholder: 'select an option', + * max_values: 2, + * }); + * ``` + * @example + * Creating a select menu using setters and API data + * ```ts + * const selectMenu = new UserSelectMenuBuilder({ + * custom_id: 'a cool select menu', + * }) + * .setMinValues(1) + * ``` + */ + public constructor(data?: Partial) { + super({ ...data, type: ComponentType.UserSelect }); + } +} diff --git a/packages/builders/src/index.ts b/packages/builders/src/index.ts index 0db122bc6062..2f0671e2e776 100644 --- a/packages/builders/src/index.ts +++ b/packages/builders/src/index.ts @@ -11,8 +11,16 @@ export * from './components/textInput/TextInput.js'; export * as TextInputAssertions from './components/textInput/Assertions.js'; export * from './interactions/modals/Modal.js'; export * as ModalAssertions from './interactions/modals/Assertions.js'; + +export * from './components/selectMenu/BaseSelectMenu.js'; +export * from './components/selectMenu/ChannelSelectMenu.js'; +export * from './components/selectMenu/MentionableSelectMenu.js'; +export * from './components/selectMenu/RoleSelectMenu.js'; export * from './components/selectMenu/SelectMenu.js'; export * from './components/selectMenu/SelectMenuOption.js'; +export * from './components/selectMenu/StringSelectMenuOption.js'; +export * from './components/selectMenu/StringSelectMenu.js'; +export * from './components/selectMenu/UserSelectMenu.js'; export * as SlashCommandAssertions from './interactions/slashCommands/Assertions.js'; export * from './interactions/slashCommands/SlashCommandBuilder.js'; diff --git a/packages/discord.js/package.json b/packages/discord.js/package.json index 3e9e7dc32876..48f3461df95c 100644 --- a/packages/discord.js/package.json +++ b/packages/discord.js/package.json @@ -55,7 +55,7 @@ "@discordjs/util": "workspace:^", "@sapphire/snowflake": "^3.2.2", "@types/ws": "^8.5.3", - "discord-api-types": "^0.37.14", + "discord-api-types": "^0.37.15", "fast-deep-equal": "^3.1.3", "lodash.snakecase": "^4.1.1", "tslib": "^2.4.0", diff --git a/packages/rest/package.json b/packages/rest/package.json index 21d5d334c5cf..cb40dd28a283 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -56,7 +56,7 @@ "@discordjs/util": "workspace:^", "@sapphire/async-queue": "^1.5.0", "@sapphire/snowflake": "^3.2.2", - "discord-api-types": "^0.37.14", + "discord-api-types": "^0.37.15", "file-type": "^18.0.0", "tslib": "^2.4.0", "undici": "^5.11.0" diff --git a/packages/voice/package.json b/packages/voice/package.json index 21675f83be5f..6eacc40ac6ce 100644 --- a/packages/voice/package.json +++ b/packages/voice/package.json @@ -53,7 +53,7 @@ "homepage": "https://discord.js.org", "dependencies": { "@types/ws": "^8.5.3", - "discord-api-types": "^0.37.14", + "discord-api-types": "^0.37.15", "prism-media": "^1.3.4", "tslib": "^2.4.0", "ws": "^8.9.0" diff --git a/packages/ws/package.json b/packages/ws/package.json index ed96837a8b7f..f768c6430f60 100644 --- a/packages/ws/package.json +++ b/packages/ws/package.json @@ -58,7 +58,7 @@ "@sapphire/async-queue": "^1.5.0", "@types/ws": "^8.5.3", "@vladfrangu/async_event_emitter": "^2.1.2", - "discord-api-types": "^0.37.14", + "discord-api-types": "^0.37.15", "tslib": "^2.4.0", "ws": "^8.9.0" }, diff --git a/yarn.lock b/yarn.lock index 4ad919155112..972837c67d03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2063,7 +2063,7 @@ __metadata: "@types/node": 16.11.68 "@vitest/coverage-c8": ^0.24.3 cross-env: ^7.0.3 - discord-api-types: ^0.37.14 + discord-api-types: ^0.37.15 esbuild-plugin-version-injector: ^1.0.0 eslint: ^8.25.0 eslint-config-neon: ^0.1.39 @@ -2250,7 +2250,7 @@ __metadata: "@types/node": 16.11.68 "@vitest/coverage-c8": ^0.24.3 cross-env: ^7.0.3 - discord-api-types: ^0.37.14 + discord-api-types: ^0.37.15 esbuild-plugin-version-injector: ^1.0.0 eslint: ^8.25.0 eslint-config-neon: ^0.1.39 @@ -2354,7 +2354,7 @@ __metadata: "@types/node": 16.11.68 "@types/ws": ^8.5.3 cross-env: ^7.0.3 - discord-api-types: ^0.37.14 + discord-api-types: ^0.37.15 esbuild-plugin-version-injector: ^1.0.0 eslint: ^8.25.0 eslint-config-neon: ^0.1.39 @@ -2444,7 +2444,7 @@ __metadata: "@vitest/coverage-c8": ^0.24.3 "@vladfrangu/async_event_emitter": ^2.1.2 cross-env: ^7.0.3 - discord-api-types: ^0.37.14 + discord-api-types: ^0.37.15 esbuild-plugin-version-injector: ^1.0.0 eslint: ^8.25.0 eslint-config-neon: ^0.1.39 @@ -8256,10 +8256,10 @@ __metadata: languageName: node linkType: hard -"discord-api-types@npm:^0.37.14": - version: 0.37.14 - resolution: "discord-api-types@npm:0.37.14" - checksum: 8f45f202e66acfd7b25624c8f4d225b363d9d8991d766959bcf246761548b99e21c12d9f7eafe00903913af66058595e5e56329dfb219eab8bb75a84f6413983 +"discord-api-types@npm:^0.37.15": + version: 0.37.15 + resolution: "discord-api-types@npm:0.37.15" + checksum: c54d2feeb8074509bdda430fb8ec0f6ff315512e7327d47399e0e7a78bbd0a6f0f0dcfc4b5e39825eb6141a13f33efa942711af89c9a5936a721cfc1e1d69d19 languageName: node linkType: hard @@ -8276,7 +8276,7 @@ __metadata: "@sapphire/snowflake": ^3.2.2 "@types/node": 16.11.68 "@types/ws": ^8.5.3 - discord-api-types: ^0.37.14 + discord-api-types: ^0.37.15 dtslint: ^4.2.1 eslint: ^8.25.0 eslint-formatter-pretty: ^4.1.0 From 71e28710d72ec89c2b48fa814119f3cf9a0d84cb Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 14:17:22 +0300 Subject: [PATCH 02/23] chore: better re-exporting of deprecated classes --- .../src/components/selectMenu/SelectMenu.ts | 11 ----------- .../components/selectMenu/SelectMenuOption.ts | 11 ----------- packages/builders/src/index.ts | 17 ++++++++++++++--- 3 files changed, 14 insertions(+), 25 deletions(-) delete mode 100644 packages/builders/src/components/selectMenu/SelectMenu.ts delete mode 100644 packages/builders/src/components/selectMenu/SelectMenuOption.ts diff --git a/packages/builders/src/components/selectMenu/SelectMenu.ts b/packages/builders/src/components/selectMenu/SelectMenu.ts deleted file mode 100644 index f32431b09dba..000000000000 --- a/packages/builders/src/components/selectMenu/SelectMenu.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { StringSelectMenuBuilder } from './StringSelectMenu.js'; - -// This is simply an alias for StringSelectMenuBuilder for the purpose of backwards compat -// TODO: Remove in v2 - -/** - * Alias for {@link StringSelectMenuBuilder} - * - * @deprecated Use {@link StringSelectMenuBuilder} directly instead - */ -export class SelectMenuBuilder extends StringSelectMenuBuilder {} diff --git a/packages/builders/src/components/selectMenu/SelectMenuOption.ts b/packages/builders/src/components/selectMenu/SelectMenuOption.ts deleted file mode 100644 index 38c8a10aa261..000000000000 --- a/packages/builders/src/components/selectMenu/SelectMenuOption.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { StringSelectMenuOptionBuilder } from './StringSelectMenuOption.js'; - -// This is simply an alias for StringSelectMenuOptionBuilder for the purpose of backwards compat -// TODO: Remove in v2 - -/** - * Alias for {@link StringSelectMenuOptionBuilder} - * - * @deprecated Use {@link StringSelectMenuOptionBuilder} directly instead - */ -export class SelectMenuOptionBuilder extends StringSelectMenuOptionBuilder {} diff --git a/packages/builders/src/index.ts b/packages/builders/src/index.ts index 2f0671e2e776..9f644d5b064c 100644 --- a/packages/builders/src/index.ts +++ b/packages/builders/src/index.ts @@ -16,10 +16,21 @@ export * from './components/selectMenu/BaseSelectMenu.js'; export * from './components/selectMenu/ChannelSelectMenu.js'; export * from './components/selectMenu/MentionableSelectMenu.js'; export * from './components/selectMenu/RoleSelectMenu.js'; -export * from './components/selectMenu/SelectMenu.js'; -export * from './components/selectMenu/SelectMenuOption.js'; -export * from './components/selectMenu/StringSelectMenuOption.js'; export * from './components/selectMenu/StringSelectMenu.js'; +// TODO: Remove those aliases in v2 +export { + /** + * @deprecated Will be removed in the next major version, use {@link StringSelectMenuBuilder} instead. + */ + StringSelectMenuBuilder as SelectMenuBuilder, +} from './components/selectMenu/StringSelectMenu.js'; +export { + /** + * @deprecated Will be removed in the next major version, use {@link StringSelectMenuOptionBuilder} instead. + */ + StringSelectMenuOptionBuilder as SelectMenuOptionBuilder, +} from './components/selectMenu/StringSelectMenuOption.js'; +export * from './components/selectMenu/StringSelectMenuOption.js'; export * from './components/selectMenu/UserSelectMenu.js'; export * as SlashCommandAssertions from './interactions/slashCommands/Assertions.js'; From 1d4055a8490bde0ddaedd59b07d71245e7c18c6a Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 16:05:16 +0300 Subject: [PATCH 03/23] feat: new select menus --- .../src/client/actions/InteractionCreate.js | 22 +++++- packages/discord.js/src/index.js | 17 ++++ .../src/structures/BaseInteraction.js | 47 ++++++++++- .../src/structures/BaseSelectMenuComponent.js | 56 +++++++++++++ .../structures/ChannelSelectMenuBuilder.js | 33 ++++++++ .../structures/ChannelSelectMenuComponent.js | 20 +++++ .../ChannelSelectMenuInteraction.js | 25 ++++++ .../MentionableSelectMenuBuilder.js | 33 ++++++++ .../MentionableSelectMenuComponent.js | 11 +++ .../MentionableSelectMenuInteraction.js | 55 +++++++++++++ .../src/structures/RoleSelectMenuBuilder.js | 33 ++++++++ .../src/structures/RoleSelectMenuComponent.js | 11 +++ .../structures/RoleSelectMenuInteraction.js | 25 ++++++ .../src/structures/SelectMenuBuilder.js | 74 +----------------- .../src/structures/SelectMenuComponent.js | 61 +-------------- .../src/structures/StringSelectMenuBuilder.js | 78 +++++++++++++++++++ .../structures/StringSelectMenuComponent.js | 20 +++++ .../structures/StringSelectMenuInteraction.js | 11 +++ .../src/structures/UserSelectMenuBuilder.js | 33 ++++++++ .../src/structures/UserSelectMenuComponent.js | 11 +++ .../structures/UserSelectMenuInteraction.js | 38 +++++++++ packages/discord.js/src/util/Components.js | 32 +++++++- packages/discord.js/src/util/Constants.js | 20 ++++- 23 files changed, 627 insertions(+), 139 deletions(-) create mode 100644 packages/discord.js/src/structures/BaseSelectMenuComponent.js create mode 100644 packages/discord.js/src/structures/ChannelSelectMenuBuilder.js create mode 100644 packages/discord.js/src/structures/ChannelSelectMenuComponent.js create mode 100644 packages/discord.js/src/structures/ChannelSelectMenuInteraction.js create mode 100644 packages/discord.js/src/structures/MentionableSelectMenuBuilder.js create mode 100644 packages/discord.js/src/structures/MentionableSelectMenuComponent.js create mode 100644 packages/discord.js/src/structures/MentionableSelectMenuInteraction.js create mode 100644 packages/discord.js/src/structures/RoleSelectMenuBuilder.js create mode 100644 packages/discord.js/src/structures/RoleSelectMenuComponent.js create mode 100644 packages/discord.js/src/structures/RoleSelectMenuInteraction.js create mode 100644 packages/discord.js/src/structures/StringSelectMenuBuilder.js create mode 100644 packages/discord.js/src/structures/StringSelectMenuComponent.js create mode 100644 packages/discord.js/src/structures/StringSelectMenuInteraction.js create mode 100644 packages/discord.js/src/structures/UserSelectMenuBuilder.js create mode 100644 packages/discord.js/src/structures/UserSelectMenuComponent.js create mode 100644 packages/discord.js/src/structures/UserSelectMenuInteraction.js diff --git a/packages/discord.js/src/client/actions/InteractionCreate.js b/packages/discord.js/src/client/actions/InteractionCreate.js index 9d2b12dceb8f..c079121c7f6f 100644 --- a/packages/discord.js/src/client/actions/InteractionCreate.js +++ b/packages/discord.js/src/client/actions/InteractionCreate.js @@ -4,11 +4,15 @@ const { InteractionType, ComponentType, ApplicationCommandType } = require('disc const Action = require('./Action'); const AutocompleteInteraction = require('../../structures/AutocompleteInteraction'); const ButtonInteraction = require('../../structures/ButtonInteraction'); +const ChannelSelectMenuInteraction = require('../../structures/ChannelSelectMenuInteraction'); const ChatInputCommandInteraction = require('../../structures/ChatInputCommandInteraction'); +const MentionableSelectMenuInteraction = require('../../structures/MentionableSelectMenuInteraction'); const MessageContextMenuCommandInteraction = require('../../structures/MessageContextMenuCommandInteraction'); const ModalSubmitInteraction = require('../../structures/ModalSubmitInteraction'); -const SelectMenuInteraction = require('../../structures/SelectMenuInteraction'); +const RoleSelectMenuInteraction = require('../../structures/RoleSelectMenuInteraction'); +const StringSelectMenuInteraction = require('../../structures/StringSelectMenuInteraction'); const UserContextMenuCommandInteraction = require('../../structures/UserContextMenuCommandInteraction'); +const UserSelectMenuInteraction = require('../../structures/UserSelectMenuInteraction'); const Events = require('../../util/Events'); class InteractionCreateAction extends Action { @@ -49,8 +53,20 @@ class InteractionCreateAction extends Action { case ComponentType.Button: InteractionClass = ButtonInteraction; break; - case ComponentType.SelectMenu: - InteractionClass = SelectMenuInteraction; + case ComponentType.StringSelect: + InteractionClass = StringSelectMenuInteraction; + break; + case ComponentType.UserSelect: + InteractionClass = UserSelectMenuInteraction; + break; + case ComponentType.RoleSelect: + InteractionClass = RoleSelectMenuInteraction; + break; + case ComponentType.MentionableSelect: + InteractionClass = MentionableSelectMenuInteraction; + break; + case ComponentType.ChannelSelect: + InteractionClass = ChannelSelectMenuInteraction; break; default: client.emit( diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 1be0d3f7c0eb..8a32f9fe208f 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -154,8 +154,25 @@ exports.ReactionEmoji = require('./structures/ReactionEmoji'); exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets; exports.Role = require('./structures/Role').Role; exports.SelectMenuBuilder = require('./structures/SelectMenuBuilder'); +exports.ChannelSelectMenuBuilder = require('./structures/ChannelSelectMenuBuilder'); +exports.MentionableSelectMenuBuilder = require('./structures/MentionableSelectMenuBuilder'); +exports.RoleSelectMenuBuilder = require('./structures/RoleSelectMenuBuilder'); +exports.StringSelectMenuBuilder = require('./structures/StringSelectMenuBuilder'); +exports.UserSelectMenuBuilder = require('./structures/UserSelectMenuBuilder'); +exports.BaseSelectMenuComponent = require('./structures/BaseSelectMenuComponent'); exports.SelectMenuComponent = require('./structures/SelectMenuComponent'); +exports.ChannelSelectMenuComponent = require('./structures/ChannelSelectMenuComponent'); +exports.MentionableSelectMenuComponent = require('./structures/MentionableSelectMenuComponent'); +exports.RoleSelectMenuComponent = require('./structures/RoleSelectMenuComponent'); +exports.StringSelectMenuComponent = require('./structures/StringSelectMenuComponent'); +exports.UserSelectMenuComponent = require('./structures/UserSelectMenuComponent'); exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction'); +exports.ChannelSelectMenuInteraction = require('./structures/ChannelSelectMenuInteraction'); +exports.MentionableSelectMenuInteraction = require('./structures/MentionableSelectMenuInteraction'); +exports.MentionableSelectMenuInteraction = require('./structures/MentionableSelectMenuInteraction'); +exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteraction'); +exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction'); +exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction'); exports.SelectMenuOptionBuilder = require('./structures/SelectMenuOptionBuilder'); exports.StageChannel = require('./structures/StageChannel'); exports.StageInstance = require('./structures/StageInstance').StageInstance; diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js index 5278c6781381..c8ca894a37d4 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -268,12 +268,55 @@ class BaseInteraction extends Base { return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.Button; } + // TODO: Remove in next major /** - * Indicates whether this interaction is a {@link SelectMenuInteraction}. + * Indicates whether this interaction is a {@link StringSelectMenuInteraction}. * @returns {boolean} + * + * @deprecated Use {@link Interaction#isStringSelectMenu} instead */ isSelectMenu() { - return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.SelectMenu; + return this.isStringSelectMenu(); + } + + /** + * Indicates whether this interaction is a {@link StringSelectMenuInteraction}. + * @returns {boolean} + */ + isStringSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.StringSelect; + } + + /** + * Indicates whether this interaction is a {@link UserSelectMenuInteraction} + * @returns {boolean} + */ + isUserSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.UserSelect; + } + + /** + * Indicates whether this interaction is a {@link RoleSelectMenuInteraction} + * @returns {boolean} + */ + isRoleSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.RoleSelect; + } + + /** + * Indicates whether this interaction is a {@link ChannelSelectMenuInteraction} + * @returns {boolean} + */ + isChannelSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.ChannelSelect; + } + + /** + * Indicates whether this interaction is a {@link MenionableSelectMenuInteraction} + * @returns {boolean} + */ + isMentionableSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.MentionableSelect; } /** diff --git a/packages/discord.js/src/structures/BaseSelectMenuComponent.js b/packages/discord.js/src/structures/BaseSelectMenuComponent.js new file mode 100644 index 000000000000..bb08087630a6 --- /dev/null +++ b/packages/discord.js/src/structures/BaseSelectMenuComponent.js @@ -0,0 +1,56 @@ +'use strict'; + +const Component = require('./Component'); + +/** + * Represents a select menu component + * @extends {Component} + */ +class BaseSelectMenuComponent extends Component { + /** + * The placeholder for this select menu + * @type {?string} + * @readonly + */ + get placeholder() { + return this.data.placeholder ?? null; + } + + /** + * The maximum amount of options that can be selected + * @type {?number} + * @readonly + */ + get maxValues() { + return this.data.max_values ?? null; + } + + /** + * The minimum amount of options that must be selected + * @type {?number} + * @readonly + */ + get minValues() { + return this.data.min_values ?? null; + } + + /** + * The custom id of this select menu + * @type {string} + * @readonly + */ + get customId() { + return this.data.custom_id; + } + + /** + * Whether this select menu is disabled + * @type {?boolean} + * @readonly + */ + get disabled() { + return this.data.disabled ?? null; + } +} + +module.exports = BaseSelectMenuComponent; diff --git a/packages/discord.js/src/structures/ChannelSelectMenuBuilder.js b/packages/discord.js/src/structures/ChannelSelectMenuBuilder.js new file mode 100644 index 000000000000..324f70b3e27f --- /dev/null +++ b/packages/discord.js/src/structures/ChannelSelectMenuBuilder.js @@ -0,0 +1,33 @@ +'use strict'; + +const { ChannelSelectMenuBuilder: BuildersChannelSelectMenu, isJSONEncodable } = require('@discordjs/builders'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersChannelSelectMenu} + */ +class ChannelSelectMenuBuilder extends BuildersChannelSelectMenu { + constructor(data = {}) { + super(toSnakeCase(data)); + } + + /** + * Creates a new select menu builder from json data + * @param {JSONEncodable | APISelectMenuComponent} other The other data + * @returns {ChannelSelectMenuBuilder} + */ + static from(other) { + if (isJSONEncodable(other)) { + return new this(other.toJSON()); + } + return new this(other); + } +} + +module.exports = ChannelSelectMenuBuilder; + +/** + * @external BuildersChannelSelectMenu + * @see {@link https://discord.js.org/#/docs/builders/main/class/ChannelSelectMenuBuilder} + */ diff --git a/packages/discord.js/src/structures/ChannelSelectMenuComponent.js b/packages/discord.js/src/structures/ChannelSelectMenuComponent.js new file mode 100644 index 000000000000..90a706315fb0 --- /dev/null +++ b/packages/discord.js/src/structures/ChannelSelectMenuComponent.js @@ -0,0 +1,20 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a channel select menu component + * @extends {BaseSelectMenuComponent} + */ +class ChannelSelectMenuComponent extends BaseSelectMenuComponent { + /** + * The options in this select menu + * @type {?(ChannelType[])} + * @readonly + */ + get channelTypes() { + return this.data.channel_types ?? null; + } +} + +module.exports = ChannelSelectMenuComponent; diff --git a/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js b/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js new file mode 100644 index 000000000000..c7398e9abe1f --- /dev/null +++ b/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js @@ -0,0 +1,25 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const SelectMenuInteraction = require('./SelectMenuInteraction'); + +/** + * Represents a {@link ComponentType.ChannelSelect} select menu interaction. + * @extends {SelectMenuInteraction} + */ +class ChannelSelectMenuInteraction extends SelectMenuInteraction { + constructor(client, data) { + super(client, data); + + /** + * Collection of the selected channels + * @type {Collection} + */ + this.channels = new Collection(); + for (const channel of Object.values(data.data.resolved.channels)) { + this.channels.set(channel.id, this.client.channels._add(channel, this.guild) ?? channel); + } + } +} + +module.exports = ChannelSelectMenuInteraction; diff --git a/packages/discord.js/src/structures/MentionableSelectMenuBuilder.js b/packages/discord.js/src/structures/MentionableSelectMenuBuilder.js new file mode 100644 index 000000000000..d5673db6865e --- /dev/null +++ b/packages/discord.js/src/structures/MentionableSelectMenuBuilder.js @@ -0,0 +1,33 @@ +'use strict'; + +const { MentionableSelectMenuBuilder: BuildersMentionableSelectMenu, isJSONEncodable } = require('@discordjs/builders'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersMentionableSelectMenu} + */ +class MentionableSelectMenuBuilder extends BuildersMentionableSelectMenu { + constructor(data = {}) { + super(toSnakeCase(data)); + } + + /** + * Creates a new select menu builder from json data + * @param {JSONEncodable | APISelectMenuComponent} other The other data + * @returns {MentionableSelectMenuBuilder} + */ + static from(other) { + if (isJSONEncodable(other)) { + return new this(other.toJSON()); + } + return new this(other); + } +} + +module.exports = MentionableSelectMenuBuilder; + +/** + * @external BuildersMentionableSelectMenu + * @see {@link https://discord.js.org/#/docs/builders/main/class/MentionableSelectMenuBuilder} + */ diff --git a/packages/discord.js/src/structures/MentionableSelectMenuComponent.js b/packages/discord.js/src/structures/MentionableSelectMenuComponent.js new file mode 100644 index 000000000000..d0f75c356e0c --- /dev/null +++ b/packages/discord.js/src/structures/MentionableSelectMenuComponent.js @@ -0,0 +1,11 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a mentionable select menu component + * @extends {BaseSelectMenuComponent} + */ +class MentionableSelectMenuComponent extends BaseSelectMenuComponent {} + +module.exports = MentionableSelectMenuComponent; diff --git a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js new file mode 100644 index 000000000000..d7ce27289204 --- /dev/null +++ b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js @@ -0,0 +1,55 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const SelectMenuInteraction = require('./SelectMenuInteraction'); + +/** + * Represents a {@link ComponentType.MentionableSelect} select menu interaction. + * @extends {SelectMenuInteraction} + */ +class MentionableSelectMenuInteraction extends SelectMenuInteraction { + constructor(client, data) { + super(client, data); + + const { members, users, roles } = data.data.resolved || {}; + + /** + * Collection of the selected users + * @type {Collection} + */ + this.members = new Collection(); + + /** + * Collection of the selected users + * @type {Collection} + */ + this.users = new Collection(); + + /** + * Collection of the selected roles + * @type {Collection} + */ + this.roles = new Collection(); + + if (members) { + for (const [id, member] of Object.entries(members)) { + const user = users[id]; + this.members.set(id, this.guild?.members._add({ user, ...member }) ?? member); + } + } + + if (users) { + for (const user of Object.values(users)) { + this.users.set(user.id, this.client.users._add(user)); + } + } + + if (roles) { + for (const role of Object.values(roles)) { + this.roles.set(role.id, this.guild?.roles._add(role) ?? role); + } + } + } +} + +module.exports = MentionableSelectMenuInteraction; diff --git a/packages/discord.js/src/structures/RoleSelectMenuBuilder.js b/packages/discord.js/src/structures/RoleSelectMenuBuilder.js new file mode 100644 index 000000000000..a42b436fa2c8 --- /dev/null +++ b/packages/discord.js/src/structures/RoleSelectMenuBuilder.js @@ -0,0 +1,33 @@ +'use strict'; + +const { RoleSelectMenuBuilder: BuildersRoleSelectMenu, isJSONEncodable } = require('@discordjs/builders'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersRoleSelectMenu} + */ +class RoleSelectMenuBuilder extends BuildersRoleSelectMenu { + constructor(data = {}) { + super(toSnakeCase(data)); + } + + /** + * Creates a new select menu builder from json data + * @param {JSONEncodable | APISelectMenuComponent} other The other data + * @returns {RoleSelectMenuBuilder} + */ + static from(other) { + if (isJSONEncodable(other)) { + return new this(other.toJSON()); + } + return new this(other); + } +} + +module.exports = RoleSelectMenuBuilder; + +/** + * @external BuildersRoleSelectMenu + * @see {@link https://discord.js.org/#/docs/builders/main/class/RoleSelectMenuBuilder} + */ diff --git a/packages/discord.js/src/structures/RoleSelectMenuComponent.js b/packages/discord.js/src/structures/RoleSelectMenuComponent.js new file mode 100644 index 000000000000..1b279428d57c --- /dev/null +++ b/packages/discord.js/src/structures/RoleSelectMenuComponent.js @@ -0,0 +1,11 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a role select menu component + * @extends {BaseSelectMenuComponent} + */ +class RoleSelectMenuComponent extends BaseSelectMenuComponent {} + +module.exports = RoleSelectMenuComponent; diff --git a/packages/discord.js/src/structures/RoleSelectMenuInteraction.js b/packages/discord.js/src/structures/RoleSelectMenuInteraction.js new file mode 100644 index 000000000000..5bc7805213c9 --- /dev/null +++ b/packages/discord.js/src/structures/RoleSelectMenuInteraction.js @@ -0,0 +1,25 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const SelectMenuInteraction = require('./SelectMenuInteraction'); + +/** + * Represents a {@link ComponentType.RoleSelect} select menu interaction. + * @extends {SelectMenuInteraction} + */ +class RoleSelectMenuInteraction extends SelectMenuInteraction { + constructor(client, data) { + super(client, data); + + /** + * Collection of the selected roles + * @type {Collection} + */ + this.roles = new Collection(); + for (const role of Object.values(data.data.resolved.roles)) { + this.roles.set(role.id, this.guild?.roles._add(role) ?? role); + } + } +} + +module.exports = RoleSelectMenuInteraction; diff --git a/packages/discord.js/src/structures/SelectMenuBuilder.js b/packages/discord.js/src/structures/SelectMenuBuilder.js index 738a18fe2950..db2d06ec2de9 100644 --- a/packages/discord.js/src/structures/SelectMenuBuilder.js +++ b/packages/discord.js/src/structures/SelectMenuBuilder.js @@ -1,78 +1,10 @@ 'use strict'; -const { SelectMenuBuilder: BuildersSelectMenu, isJSONEncodable, normalizeArray } = require('@discordjs/builders'); -const { toSnakeCase } = require('../util/Transformers'); -const { resolvePartialEmoji } = require('../util/Util'); +const { StringSelectMenuBuilder } = require('./StringSelectMenuBuilder'); /** - * Class used to build select menu components to be sent through the API - * @extends {BuildersSelectMenu} + * @deprecated Use {@link StringSelectMenuBuilder} instead. */ -class SelectMenuBuilder extends BuildersSelectMenu { - constructor({ options, ...data } = {}) { - super( - toSnakeCase({ - ...data, - options: options?.map(({ emoji, ...option }) => ({ - ...option, - emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, - })), - }), - ); - } - - /** - * Normalizes a select menu option emoji - * @param {SelectMenuOptionData|JSONEncodable} selectMenuOption The option to normalize - * @returns {Array} - * @private - */ - static normalizeEmoji(selectMenuOption) { - if (isJSONEncodable(selectMenuOption)) { - return selectMenuOption; - } - - const { emoji, ...option } = selectMenuOption; - return { - ...option, - emoji: typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, - }; - } - - /** - * Adds options to this select menu - * @param {RestOrArray} options The options to add to this select menu - * @returns {SelectMenuBuilder} - */ - addOptions(...options) { - return super.addOptions(normalizeArray(options).map(option => SelectMenuBuilder.normalizeEmoji(option))); - } - - /** - * Sets the options on this select menu - * @param {RestOrArray} options The options to set on this select menu - * @returns {SelectMenuBuilder} - */ - setOptions(...options) { - return super.setOptions(normalizeArray(options).map(option => SelectMenuBuilder.normalizeEmoji(option))); - } - - /** - * Creates a new select menu builder from json data - * @param {JSONEncodable | APISelectMenuComponent} other The other data - * @returns {SelectMenuBuilder} - */ - static from(other) { - if (isJSONEncodable(other)) { - return new this(other.toJSON()); - } - return new this(other); - } -} +class SelectMenuBuilder extends StringSelectMenuBuilder {} module.exports = SelectMenuBuilder; - -/** - * @external BuildersSelectMenu - * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuBuilder} - */ diff --git a/packages/discord.js/src/structures/SelectMenuComponent.js b/packages/discord.js/src/structures/SelectMenuComponent.js index 1d3e80bc77d0..2e1998923171 100644 --- a/packages/discord.js/src/structures/SelectMenuComponent.js +++ b/packages/discord.js/src/structures/SelectMenuComponent.js @@ -1,65 +1,10 @@ 'use strict'; -const Component = require('./Component'); +const { StringSelectMenuComponent } = require('./StringSelectMenuComponent'); /** - * Represents a select menu component - * @extends {Component} + * @deprecated Use {@link StringSelectMenuComponent} instead. */ -class SelectMenuComponent extends Component { - /** - * The placeholder for this select menu - * @type {?string} - * @readonly - */ - get placeholder() { - return this.data.placeholder ?? null; - } - - /** - * The maximum amount of options that can be selected - * @type {?number} - * @readonly - */ - get maxValues() { - return this.data.max_values ?? null; - } - - /** - * The minimum amount of options that must be selected - * @type {?number} - * @readonly - */ - get minValues() { - return this.data.min_values ?? null; - } - - /** - * The custom id of this select menu - * @type {string} - * @readonly - */ - get customId() { - return this.data.custom_id; - } - - /** - * Whether this select menu is disabled - * @type {?boolean} - * @readonly - */ - get disabled() { - return this.data.disabled ?? null; - } - - /** - * The options in this select menu - * @type {APISelectMenuOption[]} - * @readonly - */ - get options() { - return this.data.options; - } -} +class SelectMenuComponent extends StringSelectMenuComponent {} module.exports = SelectMenuComponent; diff --git a/packages/discord.js/src/structures/StringSelectMenuBuilder.js b/packages/discord.js/src/structures/StringSelectMenuBuilder.js new file mode 100644 index 000000000000..ecba962862ec --- /dev/null +++ b/packages/discord.js/src/structures/StringSelectMenuBuilder.js @@ -0,0 +1,78 @@ +'use strict'; + +const { SelectMenuBuilder: BuildersSelectMenu, isJSONEncodable, normalizeArray } = require('@discordjs/builders'); +const { toSnakeCase } = require('../util/Transformers'); +const { resolvePartialEmoji } = require('../util/Util'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersSelectMenu} + */ +class StringSelectMenuBuilder extends BuildersSelectMenu { + constructor({ options, ...data } = {}) { + super( + toSnakeCase({ + ...data, + options: options?.map(({ emoji, ...option }) => ({ + ...option, + emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, + })), + }), + ); + } + + /** + * Normalizes a select menu option emoji + * @param {SelectMenuOptionData|JSONEncodable} selectMenuOption The option to normalize + * @returns {Array} + * @private + */ + static normalizeEmoji(selectMenuOption) { + if (isJSONEncodable(selectMenuOption)) { + return selectMenuOption; + } + + const { emoji, ...option } = selectMenuOption; + return { + ...option, + emoji: typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, + }; + } + + /** + * Adds options to this select menu + * @param {RestOrArray} options The options to add to this select menu + * @returns {StringSelectMenuBuilder} + */ + addOptions(...options) { + return super.addOptions(normalizeArray(options).map(option => StringSelectMenuBuilder.normalizeEmoji(option))); + } + + /** + * Sets the options on this select menu + * @param {RestOrArray} options The options to set on this select menu + * @returns {StringSelectMenuBuilder} + */ + setOptions(...options) { + return super.setOptions(normalizeArray(options).map(option => StringSelectMenuBuilder.normalizeEmoji(option))); + } + + /** + * Creates a new select menu builder from json data + * @param {JSONEncodable | APISelectMenuComponent} other The other data + * @returns {StringSelectMenuBuilder} + */ + static from(other) { + if (isJSONEncodable(other)) { + return new this(other.toJSON()); + } + return new this(other); + } +} + +module.exports = StringSelectMenuBuilder; + +/** + * @external BuildersSelectMenu + * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuBuilder} + */ diff --git a/packages/discord.js/src/structures/StringSelectMenuComponent.js b/packages/discord.js/src/structures/StringSelectMenuComponent.js new file mode 100644 index 000000000000..e008ae5f2b08 --- /dev/null +++ b/packages/discord.js/src/structures/StringSelectMenuComponent.js @@ -0,0 +1,20 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a string select menu component + * @extends {BaseSelectMenuComponent} + */ +class StringSelectMenuComponent extends BaseSelectMenuComponent { + /** + * The options in this select menu + * @type {APISelectMenuOption[]} + * @readonly + */ + get options() { + return this.data.options; + } +} + +module.exports = StringSelectMenuComponent; diff --git a/packages/discord.js/src/structures/StringSelectMenuInteraction.js b/packages/discord.js/src/structures/StringSelectMenuInteraction.js new file mode 100644 index 000000000000..156388fc1029 --- /dev/null +++ b/packages/discord.js/src/structures/StringSelectMenuInteraction.js @@ -0,0 +1,11 @@ +'use strict'; + +const SelectMenuInteraction = require('./SelectMenuInteraction'); + +/** + * Represents a {@link ComponentType.StringSelect} select menu interaction. + * @extends {SelectMenuInteraction} + */ +class StringSelectMenuInteraction extends SelectMenuInteraction {} + +module.exports = StringSelectMenuInteraction; diff --git a/packages/discord.js/src/structures/UserSelectMenuBuilder.js b/packages/discord.js/src/structures/UserSelectMenuBuilder.js new file mode 100644 index 000000000000..39db60fff326 --- /dev/null +++ b/packages/discord.js/src/structures/UserSelectMenuBuilder.js @@ -0,0 +1,33 @@ +'use strict'; + +const { UserSelectMenuBuilder: BuildersUserSelectMenu, isJSONEncodable } = require('@discordjs/builders'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersUserSelectMenu} + */ +class UserSelectMenuBuilder extends BuildersUserSelectMenu { + constructor(data = {}) { + super(toSnakeCase(data)); + } + + /** + * Creates a new select menu builder from json data + * @param {JSONEncodable | APISelectMenuComponent} other The other data + * @returns {UserSelectMenuBuilder} + */ + static from(other) { + if (isJSONEncodable(other)) { + return new this(other.toJSON()); + } + return new this(other); + } +} + +module.exports = UserSelectMenuBuilder; + +/** + * @external BuildersUserSelectMenu + * @see {@link https://discord.js.org/#/docs/builders/main/class/UserSelectMenuBuilder} + */ diff --git a/packages/discord.js/src/structures/UserSelectMenuComponent.js b/packages/discord.js/src/structures/UserSelectMenuComponent.js new file mode 100644 index 000000000000..0acacdfab2e5 --- /dev/null +++ b/packages/discord.js/src/structures/UserSelectMenuComponent.js @@ -0,0 +1,11 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a user select menu component + * @extends {BaseSelectMenuComponent} + */ +class UserSelectMenuComponent extends BaseSelectMenuComponent {} + +module.exports = UserSelectMenuComponent; diff --git a/packages/discord.js/src/structures/UserSelectMenuInteraction.js b/packages/discord.js/src/structures/UserSelectMenuInteraction.js new file mode 100644 index 000000000000..baf34ccb31e2 --- /dev/null +++ b/packages/discord.js/src/structures/UserSelectMenuInteraction.js @@ -0,0 +1,38 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const SelectMenuInteraction = require('./SelectMenuInteraction'); + +/** + * Represents a {@link ComponentType.UserSelect} select menu interaction. + * @extends {SelectMenuInteraction} + */ +class UserSelectMenuInteraction extends SelectMenuInteraction { + constructor(client, data) { + super(client, data); + + /** + * Collection of the selected users + * @type {Collection} + */ + this.users = new Collection(); + + /** + * Collection of the selected users + * @type {Collection} + */ + this.members = new Collection(); + + for (const user of Object.values(data.data.resolved.users)) { + this.users.set(user.id, this.client.users._add(user)); + } + + if (data.data.resolved.members) { + for (const member of Object.values(data.data.resolved.members)) { + this.members.set(member.id, this.guild?.members._add(member) ?? member); + } + } + } +} + +module.exports = UserSelectMenuInteraction; diff --git a/packages/discord.js/src/util/Components.js b/packages/discord.js/src/util/Components.js index 02411f42b11c..8d9a4d2d0e0a 100644 --- a/packages/discord.js/src/util/Components.js +++ b/packages/discord.js/src/util/Components.js @@ -82,10 +82,18 @@ function createComponent(data) { return new ActionRow(data); case ComponentType.Button: return new ButtonComponent(data); - case ComponentType.SelectMenu: - return new SelectMenuComponent(data); + case ComponentType.StringSelect: + return new StringSelectMenuComponent(data); case ComponentType.TextInput: return new TextInputComponent(data); + case ComponentType.UserSelect: + return new UserSelectMenuComponent(data); + case ComponentType.RoleSelect: + return new RoleSelectMenuComponent(data); + case ComponentType.MentionableSelect: + return new MentionableSelectMenuComponent(data); + case ComponentType.ChannelSelect: + return new ChannelSelectMenuComponent(data); default: return new Component(data); } @@ -106,10 +114,18 @@ function createComponentBuilder(data) { return new ActionRowBuilder(data); case ComponentType.Button: return new ButtonBuilder(data); - case ComponentType.SelectMenu: + case ComponentType.StringSelect: return new SelectMenuBuilder(data); case ComponentType.TextInput: return new TextInputBuilder(data); + case ComponentType.UserSelect: + return new UserSelectMenuBuilder(data); + case ComponentType.RoleSelect: + return new RoleSelectMenuBuilder(data); + case ComponentType.MentionableSelect: + return new MentionableSelectMenuBuilder(data); + case ComponentType.ChannelSelect: + return new ChannelSelectMenuBuilder(data); default: return new ComponentBuilder(data); } @@ -121,11 +137,19 @@ const ActionRow = require('../structures/ActionRow'); const ActionRowBuilder = require('../structures/ActionRowBuilder'); const ButtonBuilder = require('../structures/ButtonBuilder'); const ButtonComponent = require('../structures/ButtonComponent'); +const ChannelSelectMenuBuilder = require('../structures/ChannelSelectMenuBuilder'); +const ChannelSelectMenuComponent = require('../structures/ChannelSelectMenuComponent'); const Component = require('../structures/Component'); +const MentionableSelectMenuBuilder = require('../structures/MentionableSelectMenuBuilder'); +const MentionableSelectMenuComponent = require('../structures/MentionableSelectMenuComponent'); +const RoleSelectMenuBuilder = require('../structures/RoleSelectMenuBuilder'); +const RoleSelectMenuComponent = require('../structures/RoleSelectMenuComponent'); const SelectMenuBuilder = require('../structures/SelectMenuBuilder'); -const SelectMenuComponent = require('../structures/SelectMenuComponent'); +const StringSelectMenuComponent = require('../structures/StringSelectMenuComponent'); const TextInputBuilder = require('../structures/TextInputBuilder'); const TextInputComponent = require('../structures/TextInputComponent'); +const UserSelectMenuBuilder = require('../structures/UserSelectMenuBuilder'); +const UserSelectMenuComponent = require('../structures/UserSelectMenuComponent'); /** * @external JSONEncodable diff --git a/packages/discord.js/src/util/Constants.js b/packages/discord.js/src/util/Constants.js index 61a5d191eac7..a2d98c1788af 100644 --- a/packages/discord.js/src/util/Constants.js +++ b/packages/discord.js/src/util/Constants.js @@ -1,6 +1,6 @@ 'use strict'; -const { ChannelType, MessageType } = require('discord-api-types/v10'); +const { ChannelType, MessageType, ComponentType } = require('discord-api-types/v10'); /** * The name of an item to be swept in Sweepers @@ -113,6 +113,23 @@ exports.ThreadChannelTypes = [ChannelType.AnnouncementThread, ChannelType.Public */ exports.VoiceBasedChannelTypes = [ChannelType.GuildVoice, ChannelType.GuildStageVoice]; +/** + * The types of select menus. The available types are: + * * {@link ComponentType.StringSelect} + * * {@link ComponentType.UserSelect} + * * {@link ComponentType.RoleSelect} + * * {@link ComponentType.MentionableSelect} + * * {@link ComponentType.ChannelSelect} + * @typedef {ComponentType[]} SelectMenuTypes + */ +exports.SelectMenuTypes = [ + ComponentType.StringSelect, + ComponentType.UserSelect, + ComponentType.RoleSelect, + ComponentType.MentionableSelect, + ComponentType.ChannelSelect, +]; + /** * @typedef {Object} Constants Constants that can be used in an enum or object-like way. * @property {SweeperKey[]} SweeperKeys The possible names of items that can be swept in sweepers @@ -120,4 +137,5 @@ exports.VoiceBasedChannelTypes = [ChannelType.GuildVoice, ChannelType.GuildStage * @property {TextBasedChannelTypes} TextBasedChannelTypes The types of channels that are text-based * @property {ThreadChannelTypes} ThreadChannelTypes The types of channels that are threads * @property {VoiceBasedChannelTypes} VoiceBasedChannelTypes The types of channels that are voice-based + * @property {SelectMenuTypes} SelectMenuTypes The types of components that are select menus. */ From aecbc03a8a45ea47d6ff15f056da627f22918dcb Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 16:58:12 +0300 Subject: [PATCH 04/23] chore: typings --- packages/discord.js/src/index.js | 1 + .../ChannelSelectMenuInteraction.js | 6 +- .../MentionableSelectMenuInteraction.js | 14 +- .../structures/RoleSelectMenuInteraction.js | 6 +- .../src/structures/SelectMenuInteraction.js | 17 +- .../src/structures/SelectMenuOptionBuilder.js | 46 +-- .../structures/StringSelectMenuInteraction.js | 16 +- .../StringSelectMenuOptionBuilder.js | 50 ++++ .../structures/UserSelectMenuInteraction.js | 6 +- packages/discord.js/typings/index.d.ts | 270 +++++++++++++++--- packages/discord.js/typings/index.test-d.ts | 3 +- 11 files changed, 324 insertions(+), 111 deletions(-) create mode 100644 packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 8a32f9fe208f..61927e2b964c 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -174,6 +174,7 @@ exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteract exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction'); exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction'); exports.SelectMenuOptionBuilder = require('./structures/SelectMenuOptionBuilder'); +exports.StringSelectMenuOptionBuilder = require('./structures/StringSelectMenuOptionBuilder'); exports.StageChannel = require('./structures/StageChannel'); exports.StageInstance = require('./structures/StageInstance').StageInstance; exports.Sticker = require('./structures/Sticker').Sticker; diff --git a/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js b/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js index c7398e9abe1f..04d076e599f0 100644 --- a/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js @@ -1,13 +1,13 @@ 'use strict'; const { Collection } = require('@discordjs/collection'); -const SelectMenuInteraction = require('./SelectMenuInteraction'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); /** * Represents a {@link ComponentType.ChannelSelect} select menu interaction. - * @extends {SelectMenuInteraction} + * @extends {MessageComponentInteraction} */ -class ChannelSelectMenuInteraction extends SelectMenuInteraction { +class ChannelSelectMenuInteraction extends MessageComponentInteraction { constructor(client, data) { super(client, data); diff --git a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js index d7ce27289204..251899cbb354 100644 --- a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js @@ -1,13 +1,13 @@ 'use strict'; const { Collection } = require('@discordjs/collection'); -const SelectMenuInteraction = require('./SelectMenuInteraction'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); /** * Represents a {@link ComponentType.MentionableSelect} select menu interaction. - * @extends {SelectMenuInteraction} + * @extends {MessageComponentInteraction} */ -class MentionableSelectMenuInteraction extends SelectMenuInteraction { +class MentionableSelectMenuInteraction extends MessageComponentInteraction { constructor(client, data) { super(client, data); @@ -15,15 +15,15 @@ class MentionableSelectMenuInteraction extends SelectMenuInteraction { /** * Collection of the selected users - * @type {Collection} + * @type {Collection} */ - this.members = new Collection(); + this.users = new Collection(); /** * Collection of the selected users - * @type {Collection} + * @type {Collection} */ - this.users = new Collection(); + this.members = new Collection(); /** * Collection of the selected roles diff --git a/packages/discord.js/src/structures/RoleSelectMenuInteraction.js b/packages/discord.js/src/structures/RoleSelectMenuInteraction.js index 5bc7805213c9..b45d356a2828 100644 --- a/packages/discord.js/src/structures/RoleSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/RoleSelectMenuInteraction.js @@ -1,13 +1,13 @@ 'use strict'; const { Collection } = require('@discordjs/collection'); -const SelectMenuInteraction = require('./SelectMenuInteraction'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); /** * Represents a {@link ComponentType.RoleSelect} select menu interaction. - * @extends {SelectMenuInteraction} + * @extends {MessageComponentInteraction} */ -class RoleSelectMenuInteraction extends SelectMenuInteraction { +class RoleSelectMenuInteraction extends MessageComponentInteraction { constructor(client, data) { super(client, data); diff --git a/packages/discord.js/src/structures/SelectMenuInteraction.js b/packages/discord.js/src/structures/SelectMenuInteraction.js index 42ef0c1069ff..03e2fa4ff812 100644 --- a/packages/discord.js/src/structures/SelectMenuInteraction.js +++ b/packages/discord.js/src/structures/SelectMenuInteraction.js @@ -1,21 +1,10 @@ 'use strict'; -const MessageComponentInteraction = require('./MessageComponentInteraction'); +const { StringSelectMenuInteraction } = require('./StringSelectMenuInteraction'); /** - * Represents a select menu interaction. - * @extends {MessageComponentInteraction} + * @deprecated Use {@link StringSelectMenuInteraction} instead. */ -class SelectMenuInteraction extends MessageComponentInteraction { - constructor(client, data) { - super(client, data); - - /** - * The values selected, if the component which was interacted with was a select menu - * @type {string[]} - */ - this.values = data.data.values ?? []; - } -} +class SelectMenuInteraction extends StringSelectMenuInteraction {} module.exports = SelectMenuInteraction; diff --git a/packages/discord.js/src/structures/SelectMenuOptionBuilder.js b/packages/discord.js/src/structures/SelectMenuOptionBuilder.js index 8c9d87a22779..48cf8ec9d7fe 100644 --- a/packages/discord.js/src/structures/SelectMenuOptionBuilder.js +++ b/packages/discord.js/src/structures/SelectMenuOptionBuilder.js @@ -1,50 +1,10 @@ 'use strict'; -const { SelectMenuOptionBuilder: BuildersSelectMenuOption, isJSONEncodable } = require('@discordjs/builders'); -const { toSnakeCase } = require('../util/Transformers'); -const { resolvePartialEmoji } = require('../util/Util'); +const { StringSelectMenuOptionBuilder } = require('./StringSelectMenuOptionBuilder'); /** - * Represents a select menu option builder. - * @extends {BuildersSelectMenuOption} + * @deprecated Use {@link StringSelectMenuOptionBuilder} instead. */ -class SelectMenuOptionBuilder extends BuildersSelectMenuOption { - constructor({ emoji, ...data } = {}) { - super( - toSnakeCase({ - ...data, - emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, - }), - ); - } - /** - * Sets the emoji to display on this option - * @param {ComponentEmojiResolvable} emoji The emoji to display on this option - * @returns {SelectMenuOptionBuilder} - */ - setEmoji(emoji) { - if (typeof emoji === 'string') { - return super.setEmoji(resolvePartialEmoji(emoji)); - } - return super.setEmoji(emoji); - } - - /** - * Creates a new select menu option builder from JSON data - * @param {JSONEncodable|APISelectMenuOption} other The other data - * @returns {SelectMenuOptionBuilder} - */ - static from(other) { - if (isJSONEncodable(other)) { - return new this(other.toJSON()); - } - return new this(other); - } -} +class SelectMenuOptionBuilder extends StringSelectMenuOptionBuilder {} module.exports = SelectMenuOptionBuilder; - -/** - * @external BuildersSelectMenuOption - * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuOptionBuilder} - */ diff --git a/packages/discord.js/src/structures/StringSelectMenuInteraction.js b/packages/discord.js/src/structures/StringSelectMenuInteraction.js index 156388fc1029..1db8c28f6711 100644 --- a/packages/discord.js/src/structures/StringSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/StringSelectMenuInteraction.js @@ -1,11 +1,21 @@ 'use strict'; -const SelectMenuInteraction = require('./SelectMenuInteraction'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); /** * Represents a {@link ComponentType.StringSelect} select menu interaction. - * @extends {SelectMenuInteraction} + * @extends {MessageComponentInteraction} */ -class StringSelectMenuInteraction extends SelectMenuInteraction {} +class StringSelectMenuInteraction extends MessageComponentInteraction { + constructor(client, data) { + super(client, data); + + /** + * The values selected + * @type {string[]} + */ + this.values = data.data.values ?? []; + } +} module.exports = StringSelectMenuInteraction; diff --git a/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js b/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js new file mode 100644 index 000000000000..67e0eb875999 --- /dev/null +++ b/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js @@ -0,0 +1,50 @@ +'use strict'; + +const { SelectMenuOptionBuilder: BuildersSelectMenuOption, isJSONEncodable } = require('@discordjs/builders'); +const { toSnakeCase } = require('../util/Transformers'); +const { resolvePartialEmoji } = require('../util/Util'); + +/** + * Represents a select menu option builder. + * @extends {BuildersSelectMenuOption} + */ +class StringSelectMenuOptionBuilder extends BuildersSelectMenuOption { + constructor({ emoji, ...data } = {}) { + super( + toSnakeCase({ + ...data, + emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, + }), + ); + } + /** + * Sets the emoji to display on this option + * @param {ComponentEmojiResolvable} emoji The emoji to display on this option + * @returns {StringSelectMenuOptionBuilder} + */ + setEmoji(emoji) { + if (typeof emoji === 'string') { + return super.setEmoji(resolvePartialEmoji(emoji)); + } + return super.setEmoji(emoji); + } + + /** + * Creates a new select menu option builder from JSON data + * @param {JSONEncodable|APISelectMenuOption} other The other data + * @returns {StringSelectMenuOptionBuilder} + */ + static from(other) { + if (isJSONEncodable(other)) { + return new this(other.toJSON()); + } + return new this(other); + } +} + +module.exports = StringSelectMenuOptionBuilder; + +/** + * @external BuildersSelectMenuOption + * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuOptionBuilder} + */ diff --git a/packages/discord.js/src/structures/UserSelectMenuInteraction.js b/packages/discord.js/src/structures/UserSelectMenuInteraction.js index baf34ccb31e2..fe08a469dbd1 100644 --- a/packages/discord.js/src/structures/UserSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/UserSelectMenuInteraction.js @@ -1,13 +1,13 @@ 'use strict'; const { Collection } = require('@discordjs/collection'); -const SelectMenuInteraction = require('./SelectMenuInteraction'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); /** * Represents a {@link ComponentType.UserSelect} select menu interaction. - * @extends {SelectMenuInteraction} + * @extends {MessageComponentInteraction} */ -class UserSelectMenuInteraction extends SelectMenuInteraction { +class UserSelectMenuInteraction extends MessageComponentInteraction { constructor(client, data) { super(client, data); diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 1447430e39a8..41ca135e0628 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -14,14 +14,17 @@ import { italic, quote, roleMention, - SelectMenuBuilder as BuilderSelectMenuComponent, + ChannelSelectMenuBuilder as BuilderChannelSelectMenuComponent, + MentionableSelectMenuBuilder as BuilderMentionableSelectMenuComponent, + RoleSelectMenuBuilder as BuilderRoleSelectMenuComponent, + StringSelectMenuBuilder as BuilderStringSelectMenuComponent, + UserSelectMenuBuilder as BuilderUserSelectMenuComponent, TextInputBuilder as BuilderTextInputComponent, SelectMenuOptionBuilder as BuildersSelectMenuOption, spoiler, strikethrough, time, TimestampStyles, - TimestampStylesString, underscore, userMention, ModalActionRowComponentBuilder, @@ -35,7 +38,6 @@ import { Collection } from '@discordjs/collection'; import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest'; import { APIActionRowComponent, - APIApplicationCommand, APIApplicationCommandInteractionData, APIApplicationCommandOption, APIAuditLogChange, @@ -126,6 +128,17 @@ import { TextChannelType, ChannelFlags, SortOrderType, + APIMessageStringSelectInteractionData, + APIMessageUserSelectInteractionData, + APIStringSelectComponent, + APIUserSelectComponent, + APIRoleSelectComponent, + APIMentionableSelectComponent, + APIChannelSelectComponent, + APIGuildMember, + APIMessageRoleSelectInteractionData, + APIMessageMentionableSelectInteractionData, + APIMessageChannelSelectInteractionData, } from 'discord-api-types/v10'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -158,13 +171,11 @@ import { RawInviteData, RawInviteGuildData, RawInviteStageInstance, - RawAttachmentData, RawMessageButtonInteractionData, RawMessageComponentInteractionData, RawMessageData, RawMessagePayloadData, RawMessageReactionData, - RawMessageSelectMenuInteractionData, RawOAuth2GuildData, RawPartialGroupDMChannelData, RawPartialMessageData, @@ -242,7 +253,11 @@ export interface BaseComponentData { export type MessageActionRowComponentData = | JSONEncodable | ButtonComponentData - | SelectMenuComponentData; + | StringSelectMenuComponentData + | UserSelectMenuComponentData + | RoleSelectMenuComponentData + | MentionableSelectMenuComponentData + | ChannelSelectMenuComponentData; export type ModalActionRowComponentData = JSONEncodable | TextInputComponentData; @@ -269,7 +284,13 @@ export class ActionRowBuilder extends Component< @@ -604,8 +625,8 @@ export class ButtonBuilder extends BuilderButtonComponent { public override setEmoji(emoji: ComponentEmojiResolvable): this; } -export class SelectMenuBuilder extends BuilderSelectMenuComponent { - public constructor(data?: Partial); +export class StringSelectMenuBuilder extends BuilderStringSelectMenuComponent { + public constructor(data?: Partial); private static normalizeEmoji( selectMenuOption: JSONEncodable | SelectMenuComponentOptionData, ): (APISelectMenuOption | SelectMenuOptionBuilder)[]; @@ -615,7 +636,31 @@ export class SelectMenuBuilder extends BuilderSelectMenuComponent { public override setOptions( ...options: RestOrArray ): this; - public static from(other: JSONEncodable | APISelectMenuComponent): SelectMenuBuilder; + public static from(other: JSONEncodable | APISelectMenuComponent): StringSelectMenuBuilder; +} + +export { StringSelectMenuBuilder as SelectMenuBuilder }; + +export class UserSelectMenuBuilder extends BuilderUserSelectMenuComponent { + public constructor(data?: Partial); + public static from(other: JSONEncodable | APISelectMenuComponent): UserSelectMenuBuilder; +} + +export class RoleSelectMenuBuilder extends BuilderUserSelectMenuComponent { + public constructor(data?: Partial); + public static from(other: JSONEncodable | APISelectMenuComponent): RoleSelectMenuBuilder; +} + +export class MentionableSelectMenuBuilder extends BuilderUserSelectMenuComponent { + public constructor(data?: Partial); + public static from( + other: JSONEncodable | APISelectMenuComponent, + ): MentionableSelectMenuBuilder; +} + +export class ChannelSelectMenuBuilder extends BuilderUserSelectMenuComponent { + public constructor(data?: Partial); + public static from(other: JSONEncodable | APISelectMenuComponent): ChannelSelectMenuBuilder; } export class SelectMenuOptionBuilder extends BuildersSelectMenuOption { @@ -639,16 +684,31 @@ export class TextInputComponent extends Component { public get value(): string; } -export class SelectMenuComponent extends Component { - private constructor(data: APISelectMenuComponent); +export class BaseSelectMenuComponent extends Component { + protected constructor(data: Data); public get placeholder(): string | null; public get maxValues(): number | null; public get minValues(): number | null; public get customId(): string; public get disabled(): boolean | null; +} + +export class StringSelectMenuComponent extends BaseSelectMenuComponent { public get options(): APISelectMenuOption[]; } +export { StringSelectMenuComponent as SelectMenuComponent }; + +export class UserSelectMenuComponent extends BaseSelectMenuComponent {} + +export class RoleSelectMenuComponent extends BaseSelectMenuComponent {} + +export class MentionableSelectMenuComponent extends BaseSelectMenuComponent {} + +export class ChannelSelectMenuComponent extends BaseSelectMenuComponent { + public getChannelTypes(): ChannelType[] | null; +} + export interface EmbedData { title?: string; type?: EmbedType; @@ -1501,7 +1561,7 @@ export type Interaction = | ChatInputCommandInteraction | MessageContextMenuCommandInteraction | UserContextMenuCommandInteraction - | SelectMenuInteraction + | AnySelectMenuInteraction | ButtonInteraction | AutocompleteInteraction | ModalSubmitInteraction; @@ -1550,7 +1610,13 @@ export class BaseInteraction extends Base public isMessageContextMenuCommand(): this is MessageContextMenuCommandInteraction; public isModalSubmit(): this is ModalSubmitInteraction; public isUserContextMenuCommand(): this is UserContextMenuCommandInteraction; - public isSelectMenu(): this is SelectMenuInteraction; + /** @deprecated */ + public isSelectMenu(): this is StringSelectMenuInteraction; + public isStringSelectMenu(): this is StringSelectMenuInteraction; + public isUserSelectMenu(): this is UserSelectMenuInteraction; + public isRoleSelectMenu(): this is RoleSelectMenuInteraction; + public isMentionableSelectMenu(): this is MentionableSelectMenuInteraction; + public isChannelSelectMenu(): this is ChannelSelectMenuInteraction; public isRepliable(): this is RepliableInteraction; } @@ -1673,7 +1739,11 @@ export type AwaitMessageCollectorOptionsParams { Button: ButtonInteraction; - SelectMenu: SelectMenuInteraction; + StringSelectMenu: StringSelectMenuInteraction; + UserSelectMenu: UserSelectMenuInteraction; + RoleSelectMenu: RoleSelectMenuInteraction; + MentionableSelectMenu: MentionableSelectMenuInteraction; + ChannelSelectMenu: ChannelSelectMenuInteraction; ActionRow: MessageComponentInteraction; } @@ -1681,7 +1751,11 @@ export type WrapBooleanCache = If; export interface MappedInteractionTypes { [ComponentType.Button]: ButtonInteraction>; - [ComponentType.SelectMenu]: SelectMenuInteraction>; + [ComponentType.StringSelect]: StringSelectMenuInteraction>; + [ComponentType.UserSelect]: UserSelectMenuInteraction>; + [ComponentType.RoleSelect]: RoleSelectMenuInteraction>; + [ComponentType.MentionableSelect]: MentionableSelectMenuInteraction>; + [ComponentType.ChannelSelect]: ChannelSelectMenuInteraction>; } export class Message extends Base { @@ -2254,22 +2328,108 @@ export class Role extends Base { public toString(): RoleMention; } -export class SelectMenuInteraction extends MessageComponentInteraction { - public constructor(client: Client, data: RawMessageSelectMenuInteractionData); +export class StringSelectMenuInteraction< + Cached extends CacheType = CacheType, +> extends MessageComponentInteraction { + public constructor(client: Client, data: APIMessageStringSelectInteractionData); public get component(): CacheTypeReducer< Cached, - SelectMenuComponent, - APISelectMenuComponent, - SelectMenuComponent | APISelectMenuComponent, - SelectMenuComponent | APISelectMenuComponent + StringSelectMenuComponent, + APIStringSelectComponent, + StringSelectMenuComponent | APIStringSelectComponent, + StringSelectMenuComponent | APIStringSelectComponent >; - public componentType: ComponentType.SelectMenu; + public componentType: ComponentType.StringSelect; public values: string[]; - public inGuild(): this is SelectMenuInteraction<'raw' | 'cached'>; - public inCachedGuild(): this is SelectMenuInteraction<'cached'>; - public inRawGuild(): this is SelectMenuInteraction<'raw'>; + public inGuild(): this is StringSelectMenuInteraction<'raw' | 'cached'>; + public inCachedGuild(): this is StringSelectMenuInteraction<'cached'>; + public inRawGuild(): this is StringSelectMenuInteraction<'raw'>; +} + +export { StringSelectMenuInteraction as SelectMenuInteraction }; + +export class UserSelectMenuInteraction< + Cached extends CacheType = CacheType, +> extends MessageComponentInteraction { + public constructor(client: Client, data: APIMessageUserSelectInteractionData); + public get component(): CacheTypeReducer< + Cached, + UserSelectMenuComponent, + APIUserSelectComponent, + UserSelectMenuComponent | APIUserSelectComponent, + UserSelectMenuComponent | APIUserSelectComponent + >; + public componentType: ComponentType.UserSelect; + public users: Collection; + public members: Collection>; + public inGuild(): this is UserSelectMenuInteraction<'raw' | 'cached'>; + public inCachedGuild(): this is UserSelectMenuInteraction<'cached'>; + public inRawGuild(): this is UserSelectMenuInteraction<'raw'>; } +export class RoleSelectMenuInteraction< + Cached extends CacheType = CacheType, +> extends MessageComponentInteraction { + public constructor(client: Client, data: APIMessageRoleSelectInteractionData); + public get component(): CacheTypeReducer< + Cached, + RoleSelectMenuComponent, + APIRoleSelectComponent, + RoleSelectMenuComponent | APIRoleSelectComponent, + RoleSelectMenuComponent | APIRoleSelectComponent + >; + public componentType: ComponentType.RoleSelect; + public roles: Collection>; + public inGuild(): this is RoleSelectMenuInteraction<'raw' | 'cached'>; + public inCachedGuild(): this is RoleSelectMenuInteraction<'cached'>; + public inRawGuild(): this is RoleSelectMenuInteraction<'raw'>; +} + +export class MentionableSelectMenuInteraction< + Cached extends CacheType = CacheType, +> extends MessageComponentInteraction { + public constructor(client: Client, data: APIMessageMentionableSelectInteractionData); + public get component(): CacheTypeReducer< + Cached, + MentionableSelectMenuComponent, + APIMentionableSelectComponent, + MentionableSelectMenuComponent | APIMentionableSelectComponent, + MentionableSelectMenuComponent | APIMentionableSelectComponent + >; + public componentType: ComponentType.MentionableSelect; + public users: Collection; + public members: Collection>; + public roles: Collection>; + public inGuild(): this is MentionableSelectMenuInteraction<'raw' | 'cached'>; + public inCachedGuild(): this is MentionableSelectMenuInteraction<'cached'>; + public inRawGuild(): this is MentionableSelectMenuInteraction<'raw'>; +} + +export class ChannelSelectMenuInteraction< + Cached extends CacheType = CacheType, +> extends MessageComponentInteraction { + public constructor(client: Client, data: APIMessageChannelSelectInteractionData); + public get component(): CacheTypeReducer< + Cached, + ChannelSelectMenuComponent, + APIChannelSelectComponent, + ChannelSelectMenuComponent | APIChannelSelectComponent, + ChannelSelectMenuComponent | APIChannelSelectComponent + >; + public componentType: ComponentType.ChannelSelect; + public channels: Collection>; + public inGuild(): this is ChannelSelectMenuInteraction<'raw' | 'cached'>; + public inCachedGuild(): this is ChannelSelectMenuInteraction<'cached'>; + public inRawGuild(): this is ChannelSelectMenuInteraction<'raw'>; +} + +export type AnySelectMenuInteraction = + | StringSelectMenuInteraction + | UserSelectMenuInteraction + | RoleSelectMenuInteraction + | MentionableSelectMenuInteraction + | ChannelSelectMenuInteraction; + export interface ShardEventTypes { death: [process: ChildProcess | Worker]; disconnect: []; @@ -2770,14 +2930,22 @@ export function parseWebhookURL(url: string): WebhookClientDataIdWithToken | nul export interface MappedComponentBuilderTypes { [ComponentType.Button]: ButtonBuilder; - [ComponentType.SelectMenu]: SelectMenuBuilder; + [ComponentType.StringSelect]: StringSelectMenuBuilder; + [ComponentType.UserSelect]: UserSelectMenuBuilder; + [ComponentType.RoleSelect]: RoleSelectMenuBuilder; + [ComponentType.MentionableSelect]: MentionableSelectMenuBuilder; + [ComponentType.ChannelSelect]: ChannelSelectMenuBuilder; [ComponentType.ActionRow]: ActionRowBuilder; [ComponentType.TextInput]: TextInputBuilder; } export interface MappedComponentTypes { [ComponentType.Button]: ButtonComponent; - [ComponentType.SelectMenu]: SelectMenuComponent; + [ComponentType.StringSelect]: StringSelectMenuComponent; + [ComponentType.UserSelect]: UserSelectMenuComponent; + [ComponentType.RoleSelect]: RoleSelectMenuComponent; + [ComponentType.MentionableSelect]: MentionableSelectMenuComponent; + [ComponentType.ChannelSelect]: ChannelSelectMenuComponent; [ComponentType.ActionRow]: ActionRowComponent; [ComponentType.TextInput]: TextInputComponent; } @@ -5143,7 +5311,11 @@ export interface IntegrationAccount { export type IntegrationType = 'twitch' | 'youtube' | 'discord'; export type CollectedInteraction = - | SelectMenuInteraction + | StringSelectMenuInteraction + | UserSelectMenuInteraction + | RoleSelectMenuInteraction + | MentionableSelectMenuInteraction + | ChannelSelectMenuInteraction | ButtonInteraction | ModalSubmitInteraction; @@ -5215,7 +5387,13 @@ export interface MakeErrorOptions { stack: string; } -export type ActionRowComponentOptions = ButtonComponentData | SelectMenuComponentData; +export type ActionRowComponentOptions = + | ButtonComponentData + | StringSelectMenuComponentData + | UserSelectMenuComponentData + | RoleSelectMenuComponentData + | MentionableSelectMenuComponentData + | ChannelSelectMenuComponentData; export type MessageActionRowComponentResolvable = MessageActionRowComponent | ActionRowComponentOptions; @@ -5252,7 +5430,11 @@ export type MessageComponent = | Component | ActionRowBuilder | ButtonComponent - | SelectMenuComponent; + | StringSelectMenuComponent + | UserSelectMenuComponent + | RoleSelectMenuComponent + | MentionableSelectMenuComponent + | ChannelSelectMenuComponent; export type CollectedMessageInteraction = Exclude< CollectedInteraction, @@ -5350,16 +5532,36 @@ export interface MessageReference { export type MessageResolvable = Message | Snowflake; -export interface SelectMenuComponentData extends BaseComponentData { - type: ComponentType.SelectMenu; +export interface BaseSelectMenuComponentData extends BaseComponentData { customId: string; disabled?: boolean; maxValues?: number; minValues?: number; - options?: SelectMenuComponentOptionData[]; placeholder?: string; } +export interface StringSelectMenuComponentData extends BaseSelectMenuComponentData { + type: ComponentType.StringSelect; + options?: SelectMenuComponentOptionData[]; +} + +export interface UserSelectMenuComponentData extends BaseSelectMenuComponentData { + type: ComponentType.UserSelect; +} + +export interface RoleSelectMenuComponentData extends BaseSelectMenuComponentData { + type: ComponentType.RoleSelect; +} + +export interface MentionableSelectMenuComponentData extends BaseSelectMenuComponentData { + type: ComponentType.MentionableSelect; +} + +export interface ChannelSelectMenuComponentData extends BaseSelectMenuComponentData { + type: ComponentType.ChannelSelect; + channelTypes?: ChannelType[]; +} + export interface MessageSelectOption { default: boolean; description: string | null; diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 48022353dab0..d5403dcff6d4 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -141,6 +141,7 @@ import { ChannelFlagsBitField, GuildForumThreadManager, GuildTextThreadManager, + AnySelectMenuInteraction, } from '.'; import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders'; @@ -1508,7 +1509,7 @@ if (interaction.inGuild()) { client.on('interactionCreate', async interaction => { if (interaction.type === InteractionType.MessageComponent) { - expectType(interaction); + expectType(interaction); expectType(interaction.component); expectType(interaction.message); if (interaction.inCachedGuild()) { From 85a6cc195d87a8a2cd20af30216e536f0b50837e Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 17:08:59 +0300 Subject: [PATCH 05/23] chore: add missing todo comment --- packages/discord.js/typings/index.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 41ca135e0628..1f8954319b22 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -2423,6 +2423,9 @@ export class ChannelSelectMenuInteraction< public inRawGuild(): this is ChannelSelectMenuInteraction<'raw'>; } +// Ideally this should be named SelectMenuInteraction, but that's the name of the "old" StringSelectMenuInteraction, meaning +// the type name is reserved as a re-export to prevent a breaking change from being made, as such: +// TODO: Rename this to SelectMenuInteraction in the next major export type AnySelectMenuInteraction = | StringSelectMenuInteraction | UserSelectMenuInteraction From e6eeb887999292c84d29665a68ebd2f90df2996b Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 17:14:29 +0300 Subject: [PATCH 06/23] chore: finish updating tests --- packages/discord.js/typings/index.test-d.ts | 53 +++++++++++---------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index d5403dcff6d4..82a8bdf17e87 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -24,6 +24,7 @@ import { APIEmbed, ApplicationCommandType, APIMessage, + APIStringSelectComponent, } from 'discord-api-types/v10'; import { ApplicationCommand, @@ -72,7 +73,6 @@ import { ReactionCollector, Role, RoleManager, - SelectMenuInteraction, Serialized, ShardClientUtil, ShardingManager, @@ -142,6 +142,8 @@ import { GuildForumThreadManager, GuildTextThreadManager, AnySelectMenuInteraction, + StringSelectMenuInteraction, + StringSelectMenuComponent, } from '.'; import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders'; @@ -362,14 +364,14 @@ client.on('messageCreate', async message => { expectAssignable>(buttonCollector); // Verify that select menus interaction are inferred. - const selectMenuCollector = message.createMessageComponentCollector({ componentType: ComponentType.SelectMenu }); - expectAssignable>( - message.awaitMessageComponent({ componentType: ComponentType.SelectMenu }), + const selectMenuCollector = message.createMessageComponentCollector({ componentType: ComponentType.StringSelect }); + expectAssignable>( + message.awaitMessageComponent({ componentType: ComponentType.StringSelect }), ); - expectAssignable>( - channel.awaitMessageComponent({ componentType: ComponentType.SelectMenu }), + expectAssignable>( + channel.awaitMessageComponent({ componentType: ComponentType.StringSelect }), ); - expectAssignable>(selectMenuCollector); + expectAssignable>(selectMenuCollector); // Verify that message component interactions are default collected types. const defaultCollector = message.createMessageComponentCollector(); @@ -406,9 +408,9 @@ client.on('messageCreate', async message => { }); message.createMessageComponentCollector({ - componentType: ComponentType.SelectMenu, + componentType: ComponentType.StringSelect, filter: i => { - expectType(i); + expectType(i); return true; }, }); @@ -429,9 +431,9 @@ client.on('messageCreate', async message => { }); message.awaitMessageComponent({ - componentType: ComponentType.SelectMenu, + componentType: ComponentType.StringSelect, filter: i => { - expectType(i); + expectType(i); return true; }, }); @@ -465,9 +467,9 @@ client.on('messageCreate', async message => { }); channel.awaitMessageComponent({ - componentType: ComponentType.SelectMenu, + componentType: ComponentType.StringSelect, filter: i => { - expectType>(i); + expectType>(i); return true; }, }); @@ -492,7 +494,7 @@ client.on('messageCreate', async message => { components: [ new SelectMenuBuilder(), { - type: ComponentType.SelectMenu, + type: ComponentType.StringSelect, label: 'select menu', options: [{ label: 'test', value: 'test' }], customId: 'test', @@ -1123,7 +1125,7 @@ client.on('guildCreate', async g => { new ButtonBuilder(), { type: ComponentType.Button, style: ButtonStyle.Primary, label: 'string', customId: 'foo' }, { type: ComponentType.Button, style: ButtonStyle.Link, label: 'test', url: 'test' }, - { type: ComponentType.SelectMenu, customId: 'foo' }, + { type: ComponentType.StringSelect, customId: 'foo' }, new SelectMenuBuilder(), // @ts-expect-error { type: ComponentType.TextInput, style: TextInputStyle.Paragraph, customId: 'foo', label: 'test' }, @@ -1137,7 +1139,7 @@ client.on('guildCreate', async g => { components: [ { type: ComponentType.Button, style: ButtonStyle.Primary, label: 'string', customId: 'foo' }, { type: ComponentType.Button, style: ButtonStyle.Link, label: 'test', url: 'test' }, - { type: ComponentType.SelectMenu, customId: 'foo' }, + { type: ComponentType.StringSelect, customId: 'foo' }, ], }); @@ -1641,25 +1643,28 @@ client.on('interactionCreate', async interaction => { } } - if (interaction.type === InteractionType.MessageComponent && interaction.componentType === ComponentType.SelectMenu) { - expectType(interaction); - expectType(interaction.component); + if ( + interaction.type === InteractionType.MessageComponent && + interaction.componentType === ComponentType.StringSelect + ) { + expectType(interaction); + expectType(interaction.component); expectType(interaction.message); if (interaction.inCachedGuild()) { - expectAssignable(interaction); + expectAssignable(interaction); expectType(interaction.component); expectType>(interaction.message); expectType(interaction.guild); expectType>>(interaction.reply({ fetchReply: true })); } else if (interaction.inRawGuild()) { - expectAssignable(interaction); - expectType(interaction.component); + expectAssignable(interaction); + expectType(interaction.component); expectType>(interaction.message); expectType(interaction.guild); expectType>>(interaction.reply({ fetchReply: true })); } else if (interaction.inGuild()) { - expectAssignable(interaction); - expectType(interaction.component); + expectAssignable(interaction); + expectType(interaction.component); expectType(interaction.message); expectType(interaction.guild); expectType>(interaction.reply({ fetchReply: true })); From 05226bd7a47aafc463dfa71b2e89af6b780a50ff Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 18:01:29 +0300 Subject: [PATCH 07/23] chore: add runtime deprecation warnings --- .../src/structures/BaseInteraction.js | 6 ++++++ .../src/structures/SelectMenuBuilder.js | 17 ++++++++++++++++- .../src/structures/SelectMenuComponent.js | 17 ++++++++++++++++- .../src/structures/SelectMenuInteraction.js | 17 ++++++++++++++++- .../src/structures/SelectMenuOptionBuilder.js | 17 ++++++++++++++++- .../structures/StringSelectMenuOptionBuilder.js | 1 + 6 files changed, 71 insertions(+), 4 deletions(-) diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js index c8ca894a37d4..db72682480a2 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -1,5 +1,6 @@ 'use strict'; +const { deprecate } = require('util'); const { DiscordSnowflake } = require('@sapphire/snowflake'); const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10'); const Base = require('./Base'); @@ -328,4 +329,9 @@ class BaseInteraction extends Base { } } +BaseInteraction.prototype.isSelectMenu = deprecate( + BaseInteraction.prototype.isSelectMenu, + 'BaseInteraction#isSelectMenu is deprecated. Use BaseInteraction#isStringSelectMenu instead.', +); + module.exports = BaseInteraction; diff --git a/packages/discord.js/src/structures/SelectMenuBuilder.js b/packages/discord.js/src/structures/SelectMenuBuilder.js index db2d06ec2de9..5615ec526e76 100644 --- a/packages/discord.js/src/structures/SelectMenuBuilder.js +++ b/packages/discord.js/src/structures/SelectMenuBuilder.js @@ -1,10 +1,25 @@ 'use strict'; +const { process } = require('node:process'); const { StringSelectMenuBuilder } = require('./StringSelectMenuBuilder'); +let deprecationEmitted = false; + /** * @deprecated Use {@link StringSelectMenuBuilder} instead. */ -class SelectMenuBuilder extends StringSelectMenuBuilder {} +class SelectMenuBuilder extends StringSelectMenuBuilder { + constructor(...params) { + super(...params); + + if (!deprecationEmitted) { + process.emitWarning( + 'The SelectMenuBuilder class is deprecated, use StringSelectMenuBuilder instead.', + 'DeprecationWarning', + ); + deprecationEmitted = true; + } + } +} module.exports = SelectMenuBuilder; diff --git a/packages/discord.js/src/structures/SelectMenuComponent.js b/packages/discord.js/src/structures/SelectMenuComponent.js index 2e1998923171..487a529a82e1 100644 --- a/packages/discord.js/src/structures/SelectMenuComponent.js +++ b/packages/discord.js/src/structures/SelectMenuComponent.js @@ -1,10 +1,25 @@ 'use strict'; +const { process } = require('node:process'); const { StringSelectMenuComponent } = require('./StringSelectMenuComponent'); +let deprecationEmitted = false; + /** * @deprecated Use {@link StringSelectMenuComponent} instead. */ -class SelectMenuComponent extends StringSelectMenuComponent {} +class SelectMenuComponent extends StringSelectMenuComponent { + constructor(...params) { + super(...params); + + if (!deprecationEmitted) { + process.emitWarning( + 'The SelectMenuComponent class is deprecated, use StringSelectMenuComponent instead.', + 'DeprecationWarning', + ); + deprecationEmitted = true; + } + } +} module.exports = SelectMenuComponent; diff --git a/packages/discord.js/src/structures/SelectMenuInteraction.js b/packages/discord.js/src/structures/SelectMenuInteraction.js index 03e2fa4ff812..4a8e475a3680 100644 --- a/packages/discord.js/src/structures/SelectMenuInteraction.js +++ b/packages/discord.js/src/structures/SelectMenuInteraction.js @@ -1,10 +1,25 @@ 'use strict'; +const { process } = require('node:process'); const { StringSelectMenuInteraction } = require('./StringSelectMenuInteraction'); +let deprecationEmitted = false; + /** * @deprecated Use {@link StringSelectMenuInteraction} instead. */ -class SelectMenuInteraction extends StringSelectMenuInteraction {} +class SelectMenuInteraction extends StringSelectMenuInteraction { + constructor(...params) { + super(...params); + + if (!deprecationEmitted) { + process.emitWarning( + 'The SelectMenuInteraction class is deprecated, use StringSelectMenuInteraction instead.', + 'DeprecationWarning', + ); + deprecationEmitted = true; + } + } +} module.exports = SelectMenuInteraction; diff --git a/packages/discord.js/src/structures/SelectMenuOptionBuilder.js b/packages/discord.js/src/structures/SelectMenuOptionBuilder.js index 48cf8ec9d7fe..7c61006081a1 100644 --- a/packages/discord.js/src/structures/SelectMenuOptionBuilder.js +++ b/packages/discord.js/src/structures/SelectMenuOptionBuilder.js @@ -1,10 +1,25 @@ 'use strict'; +const { process } = require('node:process'); const { StringSelectMenuOptionBuilder } = require('./StringSelectMenuOptionBuilder'); +let deprecationEmitted = false; + /** * @deprecated Use {@link StringSelectMenuOptionBuilder} instead. */ -class SelectMenuOptionBuilder extends StringSelectMenuOptionBuilder {} +class SelectMenuOptionBuilder extends StringSelectMenuOptionBuilder { + constructor(...params) { + super(...params); + + if (!deprecationEmitted) { + process.emitWarning( + 'The SelectMenuOptionBuilder class is deprecated, use StringSelectMenuOptionBuilder instead.', + 'DeprecationWarning', + ); + deprecationEmitted = true; + } + } +} module.exports = SelectMenuOptionBuilder; diff --git a/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js b/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js index 67e0eb875999..f5fa6d9e6884 100644 --- a/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js +++ b/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js @@ -17,6 +17,7 @@ class StringSelectMenuOptionBuilder extends BuildersSelectMenuOption { }), ); } + /** * Sets the emoji to display on this option * @param {ComponentEmojiResolvable} emoji The emoji to display on this option From 40703b424d500864246eade91ed9cd8edf617f3e Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 18:05:46 +0300 Subject: [PATCH 08/23] chore: format deprecation warning --- packages/discord.js/src/structures/BaseInteraction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js index db72682480a2..5e2a2f3bddb1 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -331,7 +331,7 @@ class BaseInteraction extends Base { BaseInteraction.prototype.isSelectMenu = deprecate( BaseInteraction.prototype.isSelectMenu, - 'BaseInteraction#isSelectMenu is deprecated. Use BaseInteraction#isStringSelectMenu instead.', + 'BaseInteraction#isSelectMenu() is deprecated. Use BaseInteraction#isStringSelectMenu() instead.', ); module.exports = BaseInteraction; From 03be08f3c586b9e601408d179f01c7bc44f65c3e Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 22:27:54 +0300 Subject: [PATCH 09/23] feat(BaseInteraction): isAnySelectMenu --- packages/discord.js/src/structures/BaseInteraction.js | 11 ++++++++++- packages/discord.js/src/util/Components.js | 4 ++-- packages/discord.js/typings/index.d.ts | 3 +++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js index 5e2a2f3bddb1..e8a25bbd2814 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -4,6 +4,7 @@ const { deprecate } = require('util'); const { DiscordSnowflake } = require('@sapphire/snowflake'); const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10'); const Base = require('./Base'); +const { SelectMenuTypes } = require('../util/Constants'); const PermissionsBitField = require('../util/PermissionsBitField'); /** @@ -269,7 +270,7 @@ class BaseInteraction extends Base { return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.Button; } - // TODO: Remove in next major + // TODO: Replace with isAnySelectMenu in the next major - get rid of isAnySelectMenu /** * Indicates whether this interaction is a {@link StringSelectMenuInteraction}. * @returns {boolean} @@ -280,6 +281,14 @@ class BaseInteraction extends Base { return this.isStringSelectMenu(); } + /** + * Indicates whether this interaction is a select menu of any known type. + * @returns {boolean} + */ + isAnySelectMenu() { + return this.type === InteractionType.MessageComponent && SelectMenuTypes.includes(this.componentType); + } + /** * Indicates whether this interaction is a {@link StringSelectMenuInteraction}. * @returns {boolean} diff --git a/packages/discord.js/src/util/Components.js b/packages/discord.js/src/util/Components.js index 8d9a4d2d0e0a..42bee7f2afa0 100644 --- a/packages/discord.js/src/util/Components.js +++ b/packages/discord.js/src/util/Components.js @@ -115,7 +115,7 @@ function createComponentBuilder(data) { case ComponentType.Button: return new ButtonBuilder(data); case ComponentType.StringSelect: - return new SelectMenuBuilder(data); + return new StringSelectMenuBuilder(data); case ComponentType.TextInput: return new TextInputBuilder(data); case ComponentType.UserSelect: @@ -144,7 +144,7 @@ const MentionableSelectMenuBuilder = require('../structures/MentionableSelectMen const MentionableSelectMenuComponent = require('../structures/MentionableSelectMenuComponent'); const RoleSelectMenuBuilder = require('../structures/RoleSelectMenuBuilder'); const RoleSelectMenuComponent = require('../structures/RoleSelectMenuComponent'); -const SelectMenuBuilder = require('../structures/SelectMenuBuilder'); +const StringSelectMenuBuilder = require('../structures/StringSelectMenuBuilder'); const StringSelectMenuComponent = require('../structures/StringSelectMenuComponent'); const TextInputBuilder = require('../structures/TextInputBuilder'); const TextInputComponent = require('../structures/TextInputComponent'); diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 1f8954319b22..5e4c36f94e3e 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -2433,6 +2433,8 @@ export type AnySelectMenuInteraction = | MentionableSelectMenuInteraction | ChannelSelectMenuInteraction; +export type SelectMenuType = APISelectMenuComponent['type']; + export interface ShardEventTypes { death: [process: ChildProcess | Worker]; disconnect: []; @@ -3286,6 +3288,7 @@ export const Constants: { TextBasedChannelTypes: TextBasedChannelTypes[]; ThreadChannelTypes: ThreadChannelType[]; VoiceBasedChannelTypes: VoiceBasedChannelTypes[]; + SelectMenuTypes: SelectMenuType[]; }; export const version: string; From 4ee4aa052bf6fa5d3cde2238e354f45993d6bae5 Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 22:55:25 +0300 Subject: [PATCH 10/23] chore: requested changes --- packages/discord.js/src/structures/StringSelectMenuBuilder.js | 2 +- packages/discord.js/typings/index.d.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/discord.js/src/structures/StringSelectMenuBuilder.js b/packages/discord.js/src/structures/StringSelectMenuBuilder.js index ecba962862ec..3dd645e3cb53 100644 --- a/packages/discord.js/src/structures/StringSelectMenuBuilder.js +++ b/packages/discord.js/src/structures/StringSelectMenuBuilder.js @@ -24,7 +24,7 @@ class StringSelectMenuBuilder extends BuildersSelectMenu { /** * Normalizes a select menu option emoji * @param {SelectMenuOptionData|JSONEncodable} selectMenuOption The option to normalize - * @returns {Array} + * @returns {SelectMenuOptionBuilder|APISelectMenuOption} * @private */ static normalizeEmoji(selectMenuOption) { diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 5e4c36f94e3e..7ab26f1affb3 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -639,6 +639,7 @@ export class StringSelectMenuBuilder extends BuilderStringSelectMenuComponent { public static from(other: JSONEncodable | APISelectMenuComponent): StringSelectMenuBuilder; } +/** @deprecated */ export { StringSelectMenuBuilder as SelectMenuBuilder }; export class UserSelectMenuBuilder extends BuilderUserSelectMenuComponent { @@ -697,6 +698,7 @@ export class StringSelectMenuComponent extends BaseSelectMenuComponent {} @@ -1612,6 +1614,7 @@ export class BaseInteraction extends Base public isUserContextMenuCommand(): this is UserContextMenuCommandInteraction; /** @deprecated */ public isSelectMenu(): this is StringSelectMenuInteraction; + public isAnySelectMenu(): this is AnySelectMenuInteraction; public isStringSelectMenu(): this is StringSelectMenuInteraction; public isUserSelectMenu(): this is UserSelectMenuInteraction; public isRoleSelectMenu(): this is RoleSelectMenuInteraction; @@ -2346,6 +2349,7 @@ export class StringSelectMenuInteraction< public inRawGuild(): this is StringSelectMenuInteraction<'raw'>; } +/** @deprecated */ export { StringSelectMenuInteraction as SelectMenuInteraction }; export class UserSelectMenuInteraction< From 20580d146fd7bfd69538e35c7c62f1c12f007b2b Mon Sep 17 00:00:00 2001 From: didinele Date: Fri, 28 Oct 2022 23:00:27 +0300 Subject: [PATCH 11/23] fix: deprecation comments --- packages/discord.js/typings/index.d.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 7ab26f1affb3..457ec8a994e6 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -639,8 +639,10 @@ export class StringSelectMenuBuilder extends BuilderStringSelectMenuComponent { public static from(other: JSONEncodable | APISelectMenuComponent): StringSelectMenuBuilder; } -/** @deprecated */ -export { StringSelectMenuBuilder as SelectMenuBuilder }; +export { + /** @deprecated */ + StringSelectMenuBuilder as SelectMenuBuilder, +}; export class UserSelectMenuBuilder extends BuilderUserSelectMenuComponent { public constructor(data?: Partial); @@ -698,8 +700,10 @@ export class StringSelectMenuComponent extends BaseSelectMenuComponent {} @@ -2349,8 +2353,10 @@ export class StringSelectMenuInteraction< public inRawGuild(): this is StringSelectMenuInteraction<'raw'>; } -/** @deprecated */ -export { StringSelectMenuInteraction as SelectMenuInteraction }; +export { + /** @deprecated */ + StringSelectMenuInteraction as SelectMenuInteraction, +}; export class UserSelectMenuInteraction< Cached extends CacheType = CacheType, From 8becd974bc52eaff7ba6b5cfb0b1fe86c1d38932 Mon Sep 17 00:00:00 2001 From: didinele Date: Sat, 29 Oct 2022 12:56:08 +0300 Subject: [PATCH 12/23] chore: update @deprecated comments in typings --- packages/discord.js/typings/index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 457ec8a994e6..c0b72ec6fa8f 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -640,7 +640,7 @@ export class StringSelectMenuBuilder extends BuilderStringSelectMenuComponent { } export { - /** @deprecated */ + /** @deprecated Use {@link StringSelectMenuBuilder} instead */ StringSelectMenuBuilder as SelectMenuBuilder, }; @@ -701,7 +701,7 @@ export class StringSelectMenuComponent extends BaseSelectMenuComponent extends Base public isMessageContextMenuCommand(): this is MessageContextMenuCommandInteraction; public isModalSubmit(): this is ModalSubmitInteraction; public isUserContextMenuCommand(): this is UserContextMenuCommandInteraction; - /** @deprecated */ + /** @deprecated Use {@link BaseInteraction#isStringSelectMenu} instead */ public isSelectMenu(): this is StringSelectMenuInteraction; public isAnySelectMenu(): this is AnySelectMenuInteraction; public isStringSelectMenu(): this is StringSelectMenuInteraction; @@ -2354,7 +2354,7 @@ export class StringSelectMenuInteraction< } export { - /** @deprecated */ + /** @deprecated Use {@link StringSelectMenuInteraction} instead */ StringSelectMenuInteraction as SelectMenuInteraction, }; From 41ad75e72a6458a77a29666284063eb299791e81 Mon Sep 17 00:00:00 2001 From: didinele Date: Sat, 29 Oct 2022 13:01:39 +0300 Subject: [PATCH 13/23] chore: add tests for select menu type narrowing --- packages/discord.js/typings/index.test-d.ts | 37 +++++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 82a8bdf17e87..c78de7c6715d 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -113,7 +113,7 @@ import { ButtonBuilder, EmbedBuilder, MessageActionRowComponent, - SelectMenuBuilder, + StringSelectMenuBuilder, TextInputBuilder, TextInputComponent, Embed, @@ -144,6 +144,10 @@ import { AnySelectMenuInteraction, StringSelectMenuInteraction, StringSelectMenuComponent, + UserSelectMenuInteraction, + RoleSelectMenuInteraction, + ChannelSelectMenuInteraction, + MentionableSelectMenuInteraction, } from '.'; import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders'; @@ -492,7 +496,7 @@ client.on('messageCreate', async message => { const selectsRow: ActionRowData = { type: ComponentType.ActionRow, components: [ - new SelectMenuBuilder(), + new StringSelectMenuBuilder(), { type: ComponentType.StringSelect, label: 'select menu', @@ -1126,7 +1130,7 @@ client.on('guildCreate', async g => { { type: ComponentType.Button, style: ButtonStyle.Primary, label: 'string', customId: 'foo' }, { type: ComponentType.Button, style: ButtonStyle.Link, label: 'test', url: 'test' }, { type: ComponentType.StringSelect, customId: 'foo' }, - new SelectMenuBuilder(), + new StringSelectMenuBuilder(), // @ts-expect-error { type: ComponentType.TextInput, style: TextInputStyle.Paragraph, customId: 'foo', label: 'test' }, // @ts-expect-error @@ -1888,7 +1892,7 @@ const button = new ButtonBuilder({ customId: 'test', }); -const selectMenu = new SelectMenuBuilder({ +const selectMenu = new StringSelectMenuBuilder({ maxValues: 10, minValues: 2, customId: 'test', @@ -1898,7 +1902,7 @@ new ActionRowBuilder({ components: [selectMenu.toJSON(), button.toJSON()], }); -new SelectMenuBuilder({ +new StringSelectMenuBuilder({ customId: 'foo', }); @@ -1957,10 +1961,10 @@ chatInputInteraction.showModal({ }); declare const selectMenuData: APISelectMenuComponent; -SelectMenuBuilder.from(selectMenuData); +StringSelectMenuBuilder.from(selectMenuData); declare const selectMenuComp: SelectMenuComponent; -SelectMenuBuilder.from(selectMenuComp); +StringSelectMenuBuilder.from(selectMenuComp); declare const buttonData: APIButtonComponent; ButtonBuilder.from(buttonData); @@ -2031,3 +2035,22 @@ expectType>(categoryChannel.flags); expectType>(threadChannel.flags); expectType(partialGroupDMChannel.flags); + +// Select menu type narrowing +if (interaction.isAnySelectMenu()) { + expectType(interaction); +} + +declare const anySelectMenu: AnySelectMenuInteraction; + +if (anySelectMenu.isStringSelectMenu()) { + expectType(anySelectMenu); +} else if (anySelectMenu.isUserSelectMenu()) { + expectType(anySelectMenu); +} else if (anySelectMenu.isRoleSelectMenu()) { + expectType(anySelectMenu); +} else if (anySelectMenu.isChannelSelectMenu()) { + expectType(anySelectMenu); +} else if (anySelectMenu.isMentionableSelectMenu()) { + expectType(anySelectMenu); +} From e559511f6b8b89147e3db4764b67bbb74b2d106b Mon Sep 17 00:00:00 2001 From: DD Date: Mon, 31 Oct 2022 14:24:17 +0200 Subject: [PATCH 14/23] fix: bad auto imports Co-authored-by: Julian Vennen --- packages/discord.js/src/structures/SelectMenuBuilder.js | 4 ++-- packages/discord.js/src/structures/SelectMenuComponent.js | 4 ++-- packages/discord.js/src/structures/SelectMenuInteraction.js | 4 ++-- packages/discord.js/src/structures/SelectMenuOptionBuilder.js | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/discord.js/src/structures/SelectMenuBuilder.js b/packages/discord.js/src/structures/SelectMenuBuilder.js index 5615ec526e76..caa971b468e9 100644 --- a/packages/discord.js/src/structures/SelectMenuBuilder.js +++ b/packages/discord.js/src/structures/SelectMenuBuilder.js @@ -1,7 +1,7 @@ 'use strict'; -const { process } = require('node:process'); -const { StringSelectMenuBuilder } = require('./StringSelectMenuBuilder'); +const process = require('node:process'); +const StringSelectMenuBuilder = require('./StringSelectMenuBuilder'); let deprecationEmitted = false; diff --git a/packages/discord.js/src/structures/SelectMenuComponent.js b/packages/discord.js/src/structures/SelectMenuComponent.js index 487a529a82e1..850c985275c2 100644 --- a/packages/discord.js/src/structures/SelectMenuComponent.js +++ b/packages/discord.js/src/structures/SelectMenuComponent.js @@ -1,7 +1,7 @@ 'use strict'; -const { process } = require('node:process'); -const { StringSelectMenuComponent } = require('./StringSelectMenuComponent'); +const process = require('node:process'); +const StringSelectMenuComponent = require('./StringSelectMenuComponent'); let deprecationEmitted = false; diff --git a/packages/discord.js/src/structures/SelectMenuInteraction.js b/packages/discord.js/src/structures/SelectMenuInteraction.js index 4a8e475a3680..9ee092d008b1 100644 --- a/packages/discord.js/src/structures/SelectMenuInteraction.js +++ b/packages/discord.js/src/structures/SelectMenuInteraction.js @@ -1,7 +1,7 @@ 'use strict'; -const { process } = require('node:process'); -const { StringSelectMenuInteraction } = require('./StringSelectMenuInteraction'); +const process = require('node:process'); +const StringSelectMenuInteraction = require('./StringSelectMenuInteraction'); let deprecationEmitted = false; diff --git a/packages/discord.js/src/structures/SelectMenuOptionBuilder.js b/packages/discord.js/src/structures/SelectMenuOptionBuilder.js index 7c61006081a1..ac4d265379d7 100644 --- a/packages/discord.js/src/structures/SelectMenuOptionBuilder.js +++ b/packages/discord.js/src/structures/SelectMenuOptionBuilder.js @@ -1,7 +1,7 @@ 'use strict'; -const { process } = require('node:process'); -const { StringSelectMenuOptionBuilder } = require('./StringSelectMenuOptionBuilder'); +const process = require('node:process'); +const StringSelectMenuOptionBuilder = require('./StringSelectMenuOptionBuilder'); let deprecationEmitted = false; From f7c9df8eb740657bfeb76bc48c118d3021016aef Mon Sep 17 00:00:00 2001 From: didinele Date: Mon, 31 Oct 2022 20:31:16 +0200 Subject: [PATCH 15/23] fix: properly handle resolved members --- .../MentionableSelectMenuInteraction.js | 12 +++++++++++- .../src/structures/UserSelectMenuInteraction.js | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js index 251899cbb354..ec9cb2725732 100644 --- a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js @@ -2,6 +2,7 @@ const { Collection } = require('@discordjs/collection'); const MessageComponentInteraction = require('./MessageComponentInteraction'); +const Events = require('../util/Events'); /** * Represents a {@link ComponentType.MentionableSelect} select menu interaction. @@ -34,7 +35,16 @@ class MentionableSelectMenuInteraction extends MessageComponentInteraction { if (members) { for (const [id, member] of Object.entries(members)) { const user = users[id]; - this.members.set(id, this.guild?.members._add({ user, ...member }) ?? member); + if (!user) { + this.client.emit( + Events.Debug, + `[MentionableSelectMenuInteraction] Recieved a member without a user, skipping ${id}`, + ); + + continue; + } + + this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member }); } } diff --git a/packages/discord.js/src/structures/UserSelectMenuInteraction.js b/packages/discord.js/src/structures/UserSelectMenuInteraction.js index fe08a469dbd1..f7c9f6dd20dd 100644 --- a/packages/discord.js/src/structures/UserSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/UserSelectMenuInteraction.js @@ -2,6 +2,7 @@ const { Collection } = require('@discordjs/collection'); const MessageComponentInteraction = require('./MessageComponentInteraction'); +const Events = require('../util/Events'); /** * Represents a {@link ComponentType.UserSelect} select menu interaction. @@ -28,8 +29,18 @@ class UserSelectMenuInteraction extends MessageComponentInteraction { } if (data.data.resolved.members) { - for (const member of Object.values(data.data.resolved.members)) { - this.members.set(member.id, this.guild?.members._add(member) ?? member); + for (const [id, member] of Object.entries(data.data.resolved.members)) { + const user = data.data.resolved.users[id]; + if (!user) { + this.client.emit( + Events.Debug, + `[UserSelectMenuInteraction] Recieved a member without a user, skipping ${id}`, + ); + + continue; + } + + this.members.set(member.id, this.guild?.members._add({ user, ...member }) ?? { user, ...member }); } } } From ad908c26a222afbae5802fb6e04f17e2dc54c865 Mon Sep 17 00:00:00 2001 From: didinele Date: Mon, 31 Oct 2022 20:56:16 +0200 Subject: [PATCH 16/23] fix: collectors --- .../discord.js/src/structures/InteractionCollector.js | 9 ++++++++- .../src/structures/UserSelectMenuInteraction.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/discord.js/src/structures/InteractionCollector.js b/packages/discord.js/src/structures/InteractionCollector.js index 92f4d63a9ee4..6a243debedc3 100644 --- a/packages/discord.js/src/structures/InteractionCollector.js +++ b/packages/discord.js/src/structures/InteractionCollector.js @@ -147,10 +147,17 @@ class InteractionCollector extends Collector { * @event InteractionCollector#collect * @param {BaseInteraction} interaction The interaction that was collected */ + if (this.interactionType && interaction.type !== this.interactionType) return null; if (this.componentType && interaction.componentType !== this.componentType) return null; if (this.messageId && interaction.message?.id !== this.messageId) return null; - if (this.messageInteractionId && interaction.message?.interaction?.id !== this.messageInteractionId) return null; + if ( + this.messageInteractionId && + interaction.message?.interaction?.id && + interaction.message?.interaction?.id !== this.messageInteractionId + ) { + return null; + } if (this.channelId && interaction.channelId !== this.channelId) return null; if (this.guildId && interaction.guildId !== this.guildId) return null; diff --git a/packages/discord.js/src/structures/UserSelectMenuInteraction.js b/packages/discord.js/src/structures/UserSelectMenuInteraction.js index f7c9f6dd20dd..34d625e25fee 100644 --- a/packages/discord.js/src/structures/UserSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/UserSelectMenuInteraction.js @@ -40,7 +40,7 @@ class UserSelectMenuInteraction extends MessageComponentInteraction { continue; } - this.members.set(member.id, this.guild?.members._add({ user, ...member }) ?? { user, ...member }); + this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member }); } } } From 74a0ce638ecef0eef26358f5cc00cbe7a42de414 Mon Sep 17 00:00:00 2001 From: DD Date: Mon, 31 Oct 2022 21:20:41 +0200 Subject: [PATCH 17/23] chore: suggested changes Co-authored-by: Almeida --- .../src/structures/MentionableSelectMenuInteraction.js | 2 +- .../discord.js/src/structures/UserSelectMenuInteraction.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js index ec9cb2725732..717696b94b85 100644 --- a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js @@ -38,7 +38,7 @@ class MentionableSelectMenuInteraction extends MessageComponentInteraction { if (!user) { this.client.emit( Events.Debug, - `[MentionableSelectMenuInteraction] Recieved a member without a user, skipping ${id}`, + `[MentionableSelectMenuInteraction] Received a member without a user, skipping ${id}`, ); continue; diff --git a/packages/discord.js/src/structures/UserSelectMenuInteraction.js b/packages/discord.js/src/structures/UserSelectMenuInteraction.js index 34d625e25fee..d2af4176a5e7 100644 --- a/packages/discord.js/src/structures/UserSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/UserSelectMenuInteraction.js @@ -19,7 +19,7 @@ class UserSelectMenuInteraction extends MessageComponentInteraction { this.users = new Collection(); /** - * Collection of the selected users + * Collection of the selected members * @type {Collection} */ this.members = new Collection(); @@ -34,7 +34,7 @@ class UserSelectMenuInteraction extends MessageComponentInteraction { if (!user) { this.client.emit( Events.Debug, - `[UserSelectMenuInteraction] Recieved a member without a user, skipping ${id}`, + `[UserSelectMenuInteraction] Received a member without a user, skipping ${id}`, ); continue; From 9149eacebca5602b4243c72ea81d2a3bcdf4c6e6 Mon Sep 17 00:00:00 2001 From: didinele Date: Tue, 1 Nov 2022 15:42:01 +0200 Subject: [PATCH 18/23] fix(typings): bad class extends --- packages/discord.js/typings/index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index c0b72ec6fa8f..dd23cb3c4a05 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -649,19 +649,19 @@ export class UserSelectMenuBuilder extends BuilderUserSelectMenuComponent { public static from(other: JSONEncodable | APISelectMenuComponent): UserSelectMenuBuilder; } -export class RoleSelectMenuBuilder extends BuilderUserSelectMenuComponent { +export class RoleSelectMenuBuilder extends BuilderRoleSelectMenuComponent { public constructor(data?: Partial); public static from(other: JSONEncodable | APISelectMenuComponent): RoleSelectMenuBuilder; } -export class MentionableSelectMenuBuilder extends BuilderUserSelectMenuComponent { +export class MentionableSelectMenuBuilder extends BuilderMentionableSelectMenuComponent { public constructor(data?: Partial); public static from( other: JSONEncodable | APISelectMenuComponent, ): MentionableSelectMenuBuilder; } -export class ChannelSelectMenuBuilder extends BuilderUserSelectMenuComponent { +export class ChannelSelectMenuBuilder extends BuilderChannelSelectMenuComponent { public constructor(data?: Partial); public static from(other: JSONEncodable | APISelectMenuComponent): ChannelSelectMenuBuilder; } From a31647550a8e51e1e3332ed250f0834bfb4582dd Mon Sep 17 00:00:00 2001 From: didinele Date: Tue, 1 Nov 2022 16:08:50 +0200 Subject: [PATCH 19/23] feat(ChannelSelectMenuBuilder): validation --- packages/builders/src/components/Assertions.ts | 4 +++- .../src/components/selectMenu/ChannelSelectMenu.ts | 7 ++++--- packages/discord.js/src/structures/BaseInteraction.js | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/builders/src/components/Assertions.ts b/packages/builders/src/components/Assertions.ts index d95f24d4980f..c3e4a873351e 100644 --- a/packages/builders/src/components/Assertions.ts +++ b/packages/builders/src/components/Assertions.ts @@ -1,5 +1,5 @@ import { s } from '@sapphire/shapeshift'; -import { ButtonStyle, type APIMessageComponentEmoji } from 'discord-api-types/v10'; +import { ButtonStyle, ChannelType, type APIMessageComponentEmoji } from 'discord-api-types/v10'; import { isValidationEnabled } from '../util/validation.js'; import { StringSelectMenuOptionBuilder } from './selectMenu/StringSelectMenuOption.js'; @@ -68,6 +68,8 @@ export function validateRequiredSelectMenuOptionParameters(label?: string, value labelValueDescriptionValidator.parse(value); } +export const channelTypesValidator = s.nativeEnum(ChannelType).array; + export const urlValidator = s.string .url({ allowedProtocols: ['http:', 'https:', 'discord:'], diff --git a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts index 51e682b72c0a..f9bbdbd610a2 100644 --- a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts +++ b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts @@ -1,7 +1,7 @@ import type { APIChannelSelectComponent, ChannelType } from 'discord-api-types/v10'; import { ComponentType } from 'discord-api-types/v10'; import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js'; -import { customIdValidator } from '../Assertions.js'; +import { channelTypesValidator, customIdValidator } from '../Assertions.js'; import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder { @@ -36,7 +36,8 @@ export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder) { // eslint-disable-next-line no-param-reassign types = normalizeArray(types); - this.data.channel_types?.push(...types); + + this.data.channel_types?.push(...channelTypesValidator.parse(types)); return this; } @@ -44,7 +45,7 @@ export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder Date: Tue, 1 Nov 2022 17:05:46 +0200 Subject: [PATCH 20/23] chore: update todo comment --- packages/discord.js/src/structures/BaseInteraction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js index e8b2fe7521b8..df98cf2e1c85 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -270,7 +270,7 @@ class BaseInteraction extends Base { return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.Button; } - // TODO: Replace with isAnySelectMenu in the next major - get rid of isAnySelectMenu + // TODO: Get rid of this in the next major /** * Indicates whether this interaction is a {@link StringSelectMenuInteraction}. * @returns {boolean} From 62f524020a18ff958472006d08dcdabbf1629d61 Mon Sep 17 00:00:00 2001 From: didinele Date: Tue, 1 Nov 2022 17:50:27 +0200 Subject: [PATCH 21/23] refactor(ChannelSelectMenu): better handling of channel_types state --- .../src/components/selectMenu/ChannelSelectMenu.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts index f9bbdbd610a2..b7fb9f18acd0 100644 --- a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts +++ b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts @@ -29,15 +29,14 @@ export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder) { - const { channel_types = [], ...initData } = data ?? {}; - super({ ...initData, type: ComponentType.ChannelSelect, channel_types }); + super({ ...data, type: ComponentType.ChannelSelect }); } public addChannelTypes(...types: RestOrArray) { // eslint-disable-next-line no-param-reassign types = normalizeArray(types); - this.data.channel_types?.push(...channelTypesValidator.parse(types)); + (this.data.channel_types ??= []).push(...channelTypesValidator.parse(types)); return this; } @@ -45,7 +44,7 @@ export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder Date: Tue, 1 Nov 2022 18:40:05 +0200 Subject: [PATCH 22/23] chore: style nit --- .../builders/src/components/selectMenu/ChannelSelectMenu.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts index b7fb9f18acd0..a2d46f35e65c 100644 --- a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts +++ b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts @@ -36,7 +36,8 @@ export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder Date: Tue, 1 Nov 2022 19:25:23 +0200 Subject: [PATCH 23/23] chore: suggested nits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aura Román --- packages/builders/src/components/Assertions.ts | 2 +- packages/discord.js/src/structures/InteractionCollector.js | 2 +- .../src/structures/MentionableSelectMenuInteraction.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/builders/src/components/Assertions.ts b/packages/builders/src/components/Assertions.ts index c3e4a873351e..960efd706c7c 100644 --- a/packages/builders/src/components/Assertions.ts +++ b/packages/builders/src/components/Assertions.ts @@ -68,7 +68,7 @@ export function validateRequiredSelectMenuOptionParameters(label?: string, value labelValueDescriptionValidator.parse(value); } -export const channelTypesValidator = s.nativeEnum(ChannelType).array; +export const channelTypesValidator = s.nativeEnum(ChannelType).array.setValidationEnabled(isValidationEnabled); export const urlValidator = s.string .url({ diff --git a/packages/discord.js/src/structures/InteractionCollector.js b/packages/discord.js/src/structures/InteractionCollector.js index 6a243debedc3..0e63c7d1c20b 100644 --- a/packages/discord.js/src/structures/InteractionCollector.js +++ b/packages/discord.js/src/structures/InteractionCollector.js @@ -154,7 +154,7 @@ class InteractionCollector extends Collector { if ( this.messageInteractionId && interaction.message?.interaction?.id && - interaction.message?.interaction?.id !== this.messageInteractionId + interaction.message.interaction.id !== this.messageInteractionId ) { return null; } diff --git a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js index 717696b94b85..bd294a04a74a 100644 --- a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js +++ b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js @@ -12,7 +12,7 @@ class MentionableSelectMenuInteraction extends MessageComponentInteraction { constructor(client, data) { super(client, data); - const { members, users, roles } = data.data.resolved || {}; + const { members, users, roles } = data.data.resolved ?? {}; /** * Collection of the selected users