diff --git a/package.json b/package.json index a1fba1e57176..ca36e51bd90c 100644 --- a/package.json +++ b/package.json @@ -62,5 +62,8 @@ "apps/*", "packages/*" ], - "packageManager": "yarn@3.2.4" + "packageManager": "yarn@3.2.4", + "dependencies": { + "discord-api-types": "0.37.15" + } } diff --git a/packages/builders/__tests__/components/actionRow.test.ts b/packages/builders/__tests__/components/actionRow.test.ts index d0bb9f7b584f..1101212334ff 100644 --- a/packages/builders/__tests__/components/actionRow.test.ts +++ b/packages/builders/__tests__/components/actionRow.test.ts @@ -9,7 +9,7 @@ import { ActionRowBuilder, ButtonBuilder, createComponentBuilder, - SelectMenuBuilder, + StringSelectMenuBuilder, SelectMenuOptionBuilder, } from '../../src/index.js'; @@ -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,7 +134,7 @@ 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) 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..6743a9cf0bce 100644 --- a/packages/builders/__tests__/components/selectMenu.test.ts +++ b/packages/builders/__tests__/components/selectMenu.test.ts @@ -1,8 +1,8 @@ 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, SelectMenuOptionBuilder } from '../../src/index.js'; -const selectMenu = () => new SelectMenuBuilder(); +const selectMenu = () => new StringSelectMenuBuilder(); const selectMenuOption = () => new SelectMenuOptionBuilder(); const longStr = 'a'.repeat(256); @@ -16,7 +16,7 @@ const selectMenuOptionData: APISelectMenuOption = { }; const selectMenuDataWithoutOptions = { - type: ComponentType.SelectMenu, + type: ComponentType.StringSelect, custom_id: 'test', max_values: 10, min_values: 3, @@ -165,12 +165,12 @@ describe('Select Menu Components', () => { test('GIVEN valid JSON input THEN valid JSON history is correct', () => { expect( - new SelectMenuBuilder(selectMenuDataWithoutOptions) + new StringSelectMenuBuilder(selectMenuDataWithoutOptions) .addOptions(new SelectMenuOptionBuilder(selectMenuOptionData)) .toJSON(), ).toEqual(selectMenuData); expect( - new SelectMenuBuilder(selectMenuDataWithoutOptions) + new StringSelectMenuBuilder(selectMenuDataWithoutOptions) .addOptions([new SelectMenuOptionBuilder(selectMenuOptionData)]) .toJSON(), ).toEqual(selectMenuData); 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/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..4bd13836fb82 --- /dev/null +++ b/packages/builders/src/components/selectMenu/BaseSelectMenu.ts @@ -0,0 +1,62 @@ +import type { APISelectMenuComponent } from 'discord-api-types/v10'; +import { customIdValidator, disabledValidator, minMaxValidator, placeholderValidator } from '../Assertions.js'; +import { ComponentBuilder } from '../Component.js'; + +export class BaseSelectMenuBuilder 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(): APISelectMenuComponent { + customIdValidator.parse(this.data.custom_id); + return { + ...this.data, + } as APISelectMenuComponent; + } +} diff --git a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts new file mode 100644 index 000000000000..0aca6266bdb9 --- /dev/null +++ b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts @@ -0,0 +1,65 @@ +import type { ChannelType } from 'discord-api-types/v10'; +import { ComponentType, type APIChannelSelectComponent } 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 { + public channel_types: ChannelType[]; + + /** + * 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 }); + this.channel_types = channel_types; + } + + public addChannelTypes(...types: RestOrArray) { + // eslint-disable-next-line no-param-reassign + types = normalizeArray(types); + this.channel_types?.push(...types); + return this; + } + + public setChannelTypes(...types: RestOrArray) { + // eslint-disable-next-line no-param-reassign + types = normalizeArray(types); + + this.channel_types.splice(0, this.channel_types.length, ...types); + return this; + } + + /** + * {@inheritDoc ComponentBuilder.toJSON} + */ + public override toJSON(): APIChannelSelectComponent { + customIdValidator.parse(this.data.custom_id); + + return { + ...this.data, + channel_types: this.channel_types + } 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..22e004a3b317 --- /dev/null +++ b/packages/builders/src/components/selectMenu/MentionableSelectMenu.ts @@ -0,0 +1,30 @@ +import { ComponentType, type APIMentionableSelectComponent } 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..a2cb68d8b928 --- /dev/null +++ b/packages/builders/src/components/selectMenu/RoleSelectMenu.ts @@ -0,0 +1,30 @@ +import { ComponentType, type APIRoleSelectComponent } 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/StringSelectMenu.ts similarity index 52% rename from packages/builders/src/components/selectMenu/SelectMenu.ts rename to packages/builders/src/components/selectMenu/StringSelectMenu.ts index 496138a020ac..65bb3c518320 100644 --- a/packages/builders/src/components/selectMenu/SelectMenu.ts +++ b/packages/builders/src/components/selectMenu/StringSelectMenu.ts @@ -1,21 +1,10 @@ -import { ComponentType, type APISelectMenuComponent, type APISelectMenuOption } from 'discord-api-types/v10'; +import { ComponentType, type APIStringSelectComponent, 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 { jsonOptionValidator, optionsLengthValidator, validateRequiredSelectMenuParameters } from '../Assertions.js'; +import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; import { SelectMenuOptionBuilder } from './SelectMenuOption.js'; -/** - * Represents a select menu component - */ -export class SelectMenuBuilder extends ComponentBuilder { +export class StringSelectMenuBuilder extends BaseSelectMenuBuilder { /** * The options within this select menu */ @@ -28,7 +17,7 @@ export class SelectMenuBuilder extends ComponentBuilder * @example * Creating a select menu from an API data object * ```ts - * const selectMenu = new SelectMenuBuilder({ + * const selectMenu = new StringSelectMenuBuilder({ * custom_id: 'a cool select menu', * placeholder: 'select an option', * max_values: 2, @@ -42,7 +31,7 @@ export class SelectMenuBuilder extends ComponentBuilder * @example * Creating a select menu using setters and API data * ```ts - * const selectMenu = new SelectMenuBuilder({ + * const selectMenu = new StringSelectMenuBuilder({ * custom_id: 'a cool select menu', * }) * .setMinValues(1) @@ -52,60 +41,10 @@ export class SelectMenuBuilder extends ComponentBuilder * }); * ``` */ - public constructor(data?: Partial) { + 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; + super({ ...initData, type: ComponentType.StringSelect }); + this.options = options?.map((option: APISelectMenuOption) => new SelectMenuOptionBuilder(option)) ?? []; } /** @@ -152,12 +91,17 @@ export class SelectMenuBuilder extends ComponentBuilder /** * {@inheritDoc ComponentBuilder.toJSON} */ - public toJSON(): APISelectMenuComponent { + public override toJSON(): APIStringSelectComponent { validateRequiredSelectMenuParameters(this.options, this.data.custom_id); return { ...this.data, options: this.options.map((option) => option.toJSON()), - } as APISelectMenuComponent; + } as APIStringSelectComponent; } } + +/** + * @deprecated Use {@link StringSelectMenuBuilder} instead. + */ +export const SelectMenuBuilder = StringSelectMenuBuilder; diff --git a/packages/builders/src/components/selectMenu/UserSelectMenu.ts b/packages/builders/src/components/selectMenu/UserSelectMenu.ts new file mode 100644 index 000000000000..33baa61f43d6 --- /dev/null +++ b/packages/builders/src/components/selectMenu/UserSelectMenu.ts @@ -0,0 +1,30 @@ +import { ComponentType, type APIUserSelectComponent } 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..62979ad1f12e 100644 --- a/packages/builders/src/index.ts +++ b/packages/builders/src/index.ts @@ -11,7 +11,13 @@ 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/SelectMenu.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/StringSelectMenu.js'; +export * from './components/selectMenu/UserSelectMenu.js'; + export * from './components/selectMenu/SelectMenuOption.js'; export * as SlashCommandAssertions from './interactions/slashCommands/Assertions.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/discord.js/src/client/actions/InteractionCreate.js b/packages/discord.js/src/client/actions/InteractionCreate.js index 9d2b12dceb8f..960cbbd01053 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.ChannelSelect: + InteractionClass = ChannelSelectMenuInteraction; + break; + case ComponentType.MentionableSelect: + InteractionClass = MentionableSelectMenuInteraction; + break; + case ComponentType.RoleSelect: + InteractionClass = RoleSelectMenuInteraction; + break; + case ComponentType.StringSelect: + InteractionClass = StringSelectMenuInteraction; + break; + case ComponentType.UserSelect: + InteractionClass = UserSelectMenuInteraction; break; default: client.emit( diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 1be0d3f7c0eb..31ec8684d7e6 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -153,9 +153,21 @@ exports.ReactionCollector = require('./structures/ReactionCollector'); 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'); +/** @deprecated Use {@link StringSelectMenuBuilder} instead. */ +exports.SelectMenuBuilder = exports.StringSelectMenuBuilder; +exports.UserSelectMenuBuilder = require('./structures/UserSelectMenuBuilder'); exports.SelectMenuComponent = require('./structures/SelectMenuComponent'); exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction'); +exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction'); +exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteraction'); +exports.ChannelSelectMenuInteraction = require('./structures/ChannelSelectMenuInteraction'); +exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction'); +exports.MentionableSelectMenuInteraction = require('./structures/MentionableSelectMenuInteraction'); + 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..40b6487666e5 100644 --- a/packages/discord.js/src/structures/BaseInteraction.js +++ b/packages/discord.js/src/structures/BaseInteraction.js @@ -3,6 +3,7 @@ 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,13 +270,47 @@ class BaseInteraction extends Base { } /** - * Indicates whether this interaction is a {@link SelectMenuInteraction}. + * Indicates whether this interaction is a base {@link SelectMenuInteraction}. * @returns {boolean} */ isSelectMenu() { - return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.SelectMenu; + return this.type === InteractionType.MessageComponent && SelectMenuTypes.includes(this.componentType); + } + /** + * 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; } - /** * Indicates whether this interaction can be replied to. * @returns {boolean} 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/ChannelSelectMenuInteraction.js b/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js new file mode 100644 index 000000000000..548c0c4da8a0 --- /dev/null +++ b/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js @@ -0,0 +1,21 @@ +'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/MentionableSelectMenuInteraction.js b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js new file mode 100644 index 000000000000..faad3f0593eb --- /dev/null +++ b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js @@ -0,0 +1,54 @@ +'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 || {}; + + if (members) { + /** + * Collection of the selected users + * @type {Collection?} + */ + this.members = new Collection(); + for (const [id, member] of Object.entries(members)) { + const user = users[id]; + this.members.set(id, this.guild?.members._add({ user, ...member }) ?? member); + } + } else { + this.members = null; + } + + if (users) { + /** + * Collection of the selected users + * @type {Collection?} + */ + this.users = new Collection(); + for (const user of Object.values(users)) { + this.users.set(user.id, this.client.users._add(user)); + } + } else { + this.users = null; + } + + if (roles) { + /** + * Collection of the selected roles + * @type {Collection?} + */ + this.roles = new Collection(); + for (const role of Object.values(roles)) { + this.roles.set(role.id, this.guild?.roles._add(role) ?? role); + } + } else { + this.roles = null; + } + } +} +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/RoleSelectMenuInteraction.js b/packages/discord.js/src/structures/RoleSelectMenuInteraction.js new file mode 100644 index 000000000000..8738a6ddf532 --- /dev/null +++ b/packages/discord.js/src/structures/RoleSelectMenuInteraction.js @@ -0,0 +1,21 @@ +'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/SelectMenuComponent.js b/packages/discord.js/src/structures/SelectMenuComponent.js index 1d3e80bc77d0..1f525452a13d 100644 --- a/packages/discord.js/src/structures/SelectMenuComponent.js +++ b/packages/discord.js/src/structures/SelectMenuComponent.js @@ -54,11 +54,20 @@ class SelectMenuComponent extends Component { /** * The options in this select menu - * @type {APISelectMenuOption[]} + * @type {?(APISelectMenuOption[])} * @readonly */ get options() { - return this.data.options; + return this.data.options ?? null; + } + + /** + * The options in this select menu + * @type {?(ChannelType[])} + * @readonly + */ + get channelTypes() { + return this.data.channel_types ?? null; } } diff --git a/packages/discord.js/src/structures/SelectMenuInteraction.js b/packages/discord.js/src/structures/SelectMenuInteraction.js index 42ef0c1069ff..52223f24c486 100644 --- a/packages/discord.js/src/structures/SelectMenuInteraction.js +++ b/packages/discord.js/src/structures/SelectMenuInteraction.js @@ -3,13 +3,12 @@ const MessageComponentInteraction = require('./MessageComponentInteraction'); /** - * Represents a select menu interaction. + * Represents a base select menu interaction. * @extends {MessageComponentInteraction} */ 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[]} diff --git a/packages/discord.js/src/structures/SelectMenuBuilder.js b/packages/discord.js/src/structures/StringSelectMenuBuilder.js similarity index 80% rename from packages/discord.js/src/structures/SelectMenuBuilder.js rename to packages/discord.js/src/structures/StringSelectMenuBuilder.js index 738a18fe2950..1d066831cb04 100644 --- a/packages/discord.js/src/structures/SelectMenuBuilder.js +++ b/packages/discord.js/src/structures/StringSelectMenuBuilder.js @@ -1,6 +1,6 @@ 'use strict'; -const { SelectMenuBuilder: BuildersSelectMenu, isJSONEncodable, normalizeArray } = require('@discordjs/builders'); +const { StringSelectMenuBuilder: BuildersSelectMenu, isJSONEncodable, normalizeArray } = require('@discordjs/builders'); const { toSnakeCase } = require('../util/Transformers'); const { resolvePartialEmoji } = require('../util/Util'); @@ -8,7 +8,7 @@ const { resolvePartialEmoji } = require('../util/Util'); * Class used to build select menu components to be sent through the API * @extends {BuildersSelectMenu} */ -class SelectMenuBuilder extends BuildersSelectMenu { +class StringSelectMenuBuilder extends BuildersSelectMenu { constructor({ options, ...data } = {}) { super( toSnakeCase({ @@ -42,25 +42,25 @@ class SelectMenuBuilder extends BuildersSelectMenu { /** * Adds options to this select menu * @param {RestOrArray} options The options to add to this select menu - * @returns {SelectMenuBuilder} + * @returns {StringSelectMenuBuilder} */ addOptions(...options) { - return super.addOptions(normalizeArray(options).map(option => SelectMenuBuilder.normalizeEmoji(option))); + 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 {SelectMenuBuilder} + * @returns {StringSelectMenuBuilder} */ setOptions(...options) { - return super.setOptions(normalizeArray(options).map(option => SelectMenuBuilder.normalizeEmoji(option))); + 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 {SelectMenuBuilder} + * @returns {StringSelectMenuBuilder} */ static from(other) { if (isJSONEncodable(other)) { @@ -70,9 +70,9 @@ class SelectMenuBuilder extends BuildersSelectMenu { } } -module.exports = SelectMenuBuilder; +module.exports = StringSelectMenuBuilder; /** * @external BuildersSelectMenu - * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuBuilder} + * @see {@link https://discord.js.org/#/docs/builders/main/class/StringSelectMenuBuilder} */ diff --git a/packages/discord.js/src/structures/StringSelectMenuInteraction.js b/packages/discord.js/src/structures/StringSelectMenuInteraction.js new file mode 100644 index 000000000000..56875a6117e2 --- /dev/null +++ b/packages/discord.js/src/structures/StringSelectMenuInteraction.js @@ -0,0 +1,9 @@ +'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/UserSelectMenuInteraction.js b/packages/discord.js/src/structures/UserSelectMenuInteraction.js new file mode 100644 index 000000000000..a73902491673 --- /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(); + for (const user of Object.values(data.data.resolved.users)) { + this.users.set(user.id, this.client.users._add(user) ?? user); + } + + if (data.data.resolved.members) { + this.members = new Collection() + /** + * Collection of the selected users + * @type {Collection?} + */ + for (const [id, member] of Object.entries(data.data.resolved.members)) { + const user = data.data.resolved.users[id]; + this.members.set(id, this.guild?.members._add({ user, ...member }) ?? member); + } + } else { + this.members = null; + } + } +} + +module.exports = UserSelectMenuInteraction; diff --git a/packages/discord.js/src/util/Components.js b/packages/discord.js/src/util/Components.js index 02411f42b11c..6ca54c43f4da 100644 --- a/packages/discord.js/src/util/Components.js +++ b/packages/discord.js/src/util/Components.js @@ -39,6 +39,7 @@ const { ComponentType } = require('discord-api-types/v10'); * @property {?number} maxValues The maximum amount of options that can be selected * @property {?number} minValues The minimum amount of options that can be selected * @property {?SelectMenuComponentOptionData[]} options The options in this select menu + * @property {?ChannelType[]} channelTypes The available channel types of options * @property {?string} placeholder The placeholder of the select menu */ @@ -82,7 +83,11 @@ function createComponent(data) { return new ActionRow(data); case ComponentType.Button: return new ButtonComponent(data); - case ComponentType.SelectMenu: + case ComponentType.StringSelect: + case ComponentType.UserSelect: + case ComponentType.RoleSelect: + case ComponentType.MentionableSelect: + case ComponentType.ChannelSelect: return new SelectMenuComponent(data); case ComponentType.TextInput: return new TextInputComponent(data); @@ -106,10 +111,18 @@ function createComponentBuilder(data) { 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: return new ComponentBuilder(data); } @@ -121,11 +134,15 @@ 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 Component = require('../structures/Component'); -const SelectMenuBuilder = require('../structures/SelectMenuBuilder'); +const MentionableSelectMenuBuilder = require('../structures/MentionableSelectMenuBuilder'); +const RoleSelectMenuBuilder = require('../structures/RoleSelectMenuBuilder'); const SelectMenuComponent = require('../structures/SelectMenuComponent'); +const StringSelectMenuBuilder = require('../structures/StringSelectMenuBuilder'); const TextInputBuilder = require('../structures/TextInputBuilder'); const TextInputComponent = require('../structures/TextInputComponent'); +const UserSelectMenuBuilder = require('../structures/UserSelectMenuBuilder'); /** * @external JSONEncodable diff --git a/packages/discord.js/src/util/Constants.js b/packages/discord.js/src/util/Constants.js index 61a5d191eac7..6be161cf7d6b 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,22 @@ 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 +136,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. */ diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index e2586331a58d..28dffc9a9fdf 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -14,7 +14,11 @@ 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, @@ -126,6 +130,7 @@ import { TextChannelType, ChannelFlags, SortOrderType, + APIGuildMember, } from 'discord-api-types/v10'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -604,8 +609,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 +620,25 @@ 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 class UserSelectMenuBuilder extends BuilderUserSelectMenuComponent { + public constructor(data?: Partial); + public static from(other: JSONEncodable | APISelectMenuComponent): UserSelectMenuBuilder; +} +export class RoleSelectMenuBuilder extends BuilderRoleSelectMenuComponent { + public constructor(data?: Partial); + public static from(other: JSONEncodable | APISelectMenuComponent): RoleSelectMenuBuilder; +} +export class MentionableSelectMenuBuilder extends BuilderMentionableSelectMenuComponent { + public constructor(data?: Partial); + public static from( + other: JSONEncodable | APISelectMenuComponent, + ): MentionableSelectMenuBuilder; +} +export class ChannelSelectMenuBuilder extends BuilderChannelSelectMenuComponent { + public constructor(data?: Partial); + public static from(other: JSONEncodable | APISelectMenuComponent): ChannelSelectMenuBuilder; } export class SelectMenuOptionBuilder extends BuildersSelectMenuOption { @@ -646,7 +669,8 @@ export class SelectMenuComponent extends Component { public get minValues(): number | null; public get customId(): string; public get disabled(): boolean | null; - public get options(): APISelectMenuOption[]; + public get options(): APISelectMenuOption[] | null; + public get channelTypes(): ChannelType | null; } export interface EmbedData { @@ -1500,7 +1524,7 @@ export type Interaction = | ChatInputCommandInteraction | MessageContextMenuCommandInteraction | UserContextMenuCommandInteraction - | SelectMenuInteraction + | AnySelectMenuInteraction | ButtonInteraction | AutocompleteInteraction | ModalSubmitInteraction; @@ -1549,7 +1573,12 @@ 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; + public isSelectMenu(): this is AnySelectMenuInteraction; + public isStringSelectMenu(): this is StringSelectMenuInteraction; + public isUserSelectMenu(): this is UserSelectMenuInteraction; + public isRoleSelectMenu(): this is RoleSelectMenuInteraction; + public isChannelSelectMenu(): this is ChannelSelectMenuInteraction; + public isMentionableSelectMenu(): this is MentiobnableSelectMenuInteraction; public isRepliable(): this is RepliableInteraction; } @@ -1680,7 +1709,11 @@ export type WrapBooleanCache = If; export interface MappedInteractionTypes { [ComponentType.Button]: ButtonInteraction>; - [ComponentType.SelectMenu]: SelectMenuInteraction>; + [ComponentType.StringSelect]: SelectMenuInteraction>; + [ComponentType.UserSelect]: SelectMenuInteraction>; + [ComponentType.RoleSelect]: SelectMenuInteraction>; + [ComponentType.MentionableSelect]: SelectMenuInteraction>; + [ComponentType.ChannelSelect]: SelectMenuInteraction>; } export class Message extends Base { @@ -2253,6 +2286,19 @@ export class Role extends Base { public toString(): RoleMention; } +export type SelectMenuType = + | ComponentType.StringSelect + | ComponentType.UserSelect + | ComponentType.RoleSelect + | ComponentType.MentionableSelect + | ComponentType.ChannelSelect; +export type AnySelectMenuInteraction = + | StringSelectMenuInteraction + | ChannelSelectMenuInteraction + | UserSelectMenuInteraction + | RoleSelectMenuInteraction + | MentiobnableSelectMenuInteraction; + export class SelectMenuInteraction extends MessageComponentInteraction { public constructor(client: Client, data: RawMessageSelectMenuInteractionData); public get component(): CacheTypeReducer< @@ -2262,13 +2308,51 @@ export class SelectMenuInteraction extends SelectMenuComponent | APISelectMenuComponent, SelectMenuComponent | APISelectMenuComponent >; - public componentType: ComponentType.SelectMenu; + public componentType: SelectMenuType; public values: string[]; public inGuild(): this is SelectMenuInteraction<'raw' | 'cached'>; public inCachedGuild(): this is SelectMenuInteraction<'cached'>; public inRawGuild(): this is SelectMenuInteraction<'raw'>; } - +export class RoleSelectMenuInteraction extends SelectMenuInteraction { + 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 UserSelectMenuInteraction extends SelectMenuInteraction { + public componentType: ComponentType.UserSelect; + public users: Collection; + public members: CacheTypeReducer>; + public inGuild(): this is UserSelectMenuInteraction<'raw' | 'cached'>; + public inCachedGuild(): this is UserSelectMenuInteraction<'cached'>; + public inRawGuild(): this is UserSelectMenuInteraction<'raw'>; +} +export class ChannelSelectMenuInteraction extends SelectMenuInteraction { + 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 class MentiobnableSelectMenuInteraction< + Cached extends CacheType = CacheType, +> extends SelectMenuInteraction { + public componentType: ComponentType.MentionableSelect; + public roles: Collection; + public users: Collection; + public members: CacheTypeReducer>; + public inGuild(): this is MentiobnableSelectMenuInteraction<'raw' | 'cached'>; + public inCachedGuild(): this is MentiobnableSelectMenuInteraction<'cached'>; + public inRawGuild(): this is MentiobnableSelectMenuInteraction<'raw'>; +} +export class StringSelectMenuInteraction extends SelectMenuInteraction { + public componentType: ComponentType.StringSelect; + public inGuild(): this is StringSelectMenuInteraction<'raw' | 'cached'>; + public inCachedGuild(): this is StringSelectMenuInteraction<'cached'>; + public inRawGuild(): this is StringSelectMenuInteraction<'raw'>; +} export interface ShardEventTypes { death: [process: ChildProcess | Worker]; disconnect: []; @@ -2769,14 +2853,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]: SelectMenuComponent; + [ComponentType.UserSelect]: SelectMenuComponent; + [ComponentType.RoleSelect]: SelectMenuComponent; + [ComponentType.MentionableSelect]: SelectMenuComponent; + [ComponentType.ChannelSelect]: SelectMenuComponent; [ComponentType.ActionRow]: ActionRowComponent; [ComponentType.TextInput]: TextInputComponent; } @@ -3114,6 +3206,7 @@ export const Constants: { TextBasedChannelTypes: TextBasedChannelTypes[]; ThreadChannelTypes: ThreadChannelType[]; VoiceBasedChannelTypes: VoiceBasedChannelTypes[]; + SelectMenuTypes: SelectMenuType[]; }; export const version: string; @@ -5348,16 +5441,40 @@ export interface MessageReference { export type MessageResolvable = Message | Snowflake; -export interface SelectMenuComponentData extends BaseComponentData { - type: ComponentType.SelectMenu; +export interface BaseSelectMenuComponentData extends BaseComponentData { + type: SelectMenuType; customId: string; disabled?: boolean; maxValues?: number; minValues?: number; - options?: SelectMenuComponentOptionData[]; placeholder?: string; } +export interface StringSelectMenuComponentData extends BaseSelectMenuComponentData { + type: ComponentType.StringSelect; + options?: SelectMenuComponentOptionData[]; +} +export interface ChannelSelectMenuComponentData extends BaseSelectMenuComponentData { + type: ComponentType.ChannelSelect; + channelTypes?: ChannelType[]; +} + +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 type SelectMenuComponentData = + | StringSelectMenuComponentData + | ChannelSelectMenuComponentData + | UserSelectMenuComponentData + | RoleSelectMenuComponentData + | MentiobnableSelectMenuInteraction; 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..957de8455dba 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, @@ -361,12 +361,12 @@ 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 }), + channel.awaitMessageComponent({ componentType: ComponentType.StringSelect }), ); expectAssignable>(selectMenuCollector); @@ -405,7 +405,7 @@ client.on('messageCreate', async message => { }); message.createMessageComponentCollector({ - componentType: ComponentType.SelectMenu, + componentType: ComponentType.StringSelect, filter: i => { expectType(i); return true; @@ -428,7 +428,7 @@ client.on('messageCreate', async message => { }); message.awaitMessageComponent({ - componentType: ComponentType.SelectMenu, + componentType: ComponentType.StringSelect, filter: i => { expectType(i); return true; @@ -464,7 +464,7 @@ client.on('messageCreate', async message => { }); channel.awaitMessageComponent({ - componentType: ComponentType.SelectMenu, + componentType: ComponentType.StringSelect, filter: i => { expectType>(i); return true; @@ -489,9 +489,9 @@ client.on('messageCreate', async message => { const selectsRow: ActionRowData = { type: ComponentType.ActionRow, components: [ - new SelectMenuBuilder(), + new StringSelectMenuBuilder(), { - type: ComponentType.SelectMenu, + type: ComponentType.StringSelect, label: 'select menu', options: [{ label: 'test', value: 'test' }], customId: 'test', @@ -1122,8 +1122,8 @@ 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' }, - new SelectMenuBuilder(), + { type: ComponentType.StringSelect, customId: 'foo' }, + new StringSelectMenuBuilder(), // @ts-expect-error { type: ComponentType.TextInput, style: TextInputStyle.Paragraph, customId: 'foo', label: 'test' }, // @ts-expect-error @@ -1136,7 +1136,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' }, ], }); @@ -1640,7 +1640,10 @@ client.on('interactionCreate', async interaction => { } } - if (interaction.type === InteractionType.MessageComponent && interaction.componentType === ComponentType.SelectMenu) { + if ( + interaction.type === InteractionType.MessageComponent && + interaction.componentType === ComponentType.StringSelect + ) { expectType(interaction); expectType(interaction.component); expectType(interaction.message); @@ -1882,7 +1885,7 @@ const button = new ButtonBuilder({ customId: 'test', }); -const selectMenu = new SelectMenuBuilder({ +const selectMenu = new StringSelectMenuBuilder({ maxValues: 10, minValues: 2, customId: 'test', @@ -1892,7 +1895,7 @@ new ActionRowBuilder({ components: [selectMenu.toJSON(), button.toJSON()], }); -new SelectMenuBuilder({ +new StringSelectMenuBuilder({ customId: 'foo', }); @@ -1951,10 +1954,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); 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..c2e74ece8be5 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 @@ -2107,6 +2107,7 @@ __metadata: "@favware/cliff-jumper": ^1.8.8 "@favware/npm-deprecate": ^1.0.5 conventional-changelog-cli: ^2.2.2 + discord-api-types: 0.37.15 husky: ^8.0.1 is-ci: ^3.0.1 lint-staged: ^13.0.3 @@ -2250,7 +2251,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 +2355,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 +2445,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 +8257,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, 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 +8277,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