diff --git a/src/client/actions/InteractionCreate.js b/src/client/actions/InteractionCreate.js index b928912..dff58e0 100644 --- a/src/client/actions/InteractionCreate.js +++ b/src/client/actions/InteractionCreate.js @@ -47,7 +47,11 @@ class InteractionCreateAction extends Action { case MessageComponentTypes.BUTTON: InteractionType = ButtonInteraction; break; - case MessageComponentTypes.SELECT_MENU: + case MessageComponentTypes.STRING_SELECT_MENU: + case MessageComponentTypes.USER_SELECT_MENU: + case MessageComponentTypes.ROLE_SELECT_MENU: + case MessageComponentTypes.MENTIONABLE_SELECT_MENU: + case MessageComponentTypes.CHANNEL_SELECT_MENU: InteractionType = SelectMenuInteraction; break; default: diff --git a/src/structures/ApplicationCommand.js b/src/structures/ApplicationCommand.js index 8dc3dc1..24c4231 100644 --- a/src/structures/ApplicationCommand.js +++ b/src/structures/ApplicationCommand.js @@ -5,7 +5,13 @@ const { findBestMatch } = require('string-similarity'); const Base = require('./Base'); const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager'); const MessageAttachment = require('../structures/MessageAttachment'); -const { ApplicationCommandOptionTypes, ApplicationCommandTypes, ChannelTypes, Events } = require('../util/Constants'); +const { + ApplicationCommandOptionTypes, + ApplicationCommandTypes, + ChannelTypes, + Events, + InteractionTypes, +} = require('../util/Constants'); const DataResolver = require('../util/DataResolver'); const Permissions = require('../util/Permissions'); const SnowflakeUtil = require('../util/SnowflakeUtil'); @@ -797,14 +803,12 @@ class ApplicationCommand extends Base { dataAdd = [dataAdd]; } const data = { - type: autocomplete ? 4 : 2, // Slash command, context menu - // Type: 4: Auto-complete + type: autocomplete ? InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE : InteractionTypes.APPLICATION_COMMAND, application_id: this.applicationId, guild_id: message.guildId, channel_id: message.channelId, session_id: this.client.session_id, data: { - // ApplicationCommandData version: this.version, id: this.id, name: this.name, @@ -932,13 +936,12 @@ class ApplicationCommand extends Base { if (this.type == 'CHAT_INPUT') return false; const nonce = SnowflakeUtil.generate(); const data = { - type: 2, // Slash command, context menu + type: InteractionTypes.APPLICATION_COMMAND, application_id: this.applicationId, guild_id: message.guildId, channel_id: message.channelId, session_id: this.client.session_id, data: { - // ApplicationCommandData version: this.version, id: this.id, name: this.name, diff --git a/src/structures/BaseMessageComponent.js b/src/structures/BaseMessageComponent.js index 4075827..6570d7b 100644 --- a/src/structures/BaseMessageComponent.js +++ b/src/structures/BaseMessageComponent.js @@ -76,7 +76,11 @@ class BaseMessageComponent { component = data instanceof MessageButton ? data : new MessageButton(data); break; } - case MessageComponentTypes.SELECT_MENU: { + case MessageComponentTypes.STRING_SELECT_MENU: + case MessageComponentTypes.USER_SELECT_MENU: + case MessageComponentTypes.ROLE_SELECT_MENU: + case MessageComponentTypes.MENTIONABLE_SELECT_MENU: + case MessageComponentTypes.CHANNEL_SELECT_MENU: { const MessageSelectMenu = require('./MessageSelectMenu'); component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data); break; diff --git a/src/structures/Message.js b/src/structures/Message.js index e65535d..1b7135f 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -1068,32 +1068,33 @@ class Message extends Base { * @param {Array} options Menu Options * @returns {Promise} */ - async selectMenu(menuID, options = []) { + selectMenu(menuID, options = []) { if (!this.components[0]) throw new TypeError('MESSAGE_NO_COMPONENTS'); - let menuFirst; - let menuCorrect; - let menuCount = 0; - await Promise.all( - this.components.map(row => { - const firstElement = row.components[0]; // Because 1 row has only 1 menu; - if (firstElement.type == 'SELECT_MENU') { - menuCount++; - if (firstElement.customId == menuID) { - menuCorrect = firstElement; - } else if (!menuFirst) { - menuFirst = firstElement; - } + const menuAll = []; + for (const row of this.components) { + for (const component of row.components) { + if ( + [ + 'STRING_SELECT_MENU', + 'USER_SELECT_MENU', + 'ROLE_SELECT_MENU', + 'MENTIONABLE_SELECT_MENU', + 'CHANNEL_SELECT_MENU', + ].includes(component.type) + ) { + menuAll.push(component); } - return true; - }), - ); - if (menuCount == 0) throw new TypeError('MENU_NOT_FOUND'); - if (!menuCorrect) { - if (menuCount == 1) menuCorrect = menuFirst; - else if (typeof menuID !== 'string') throw new TypeError('MENU_ID_NOT_STRING'); - else throw new TypeError('MENU_ID_NOT_FOUND'); + } + } + if (menuAll.length == 0) throw new TypeError('MENU_NOT_FOUND'); + if (menuAll.length == 1) { + return menuAll[0].select(this, Array.isArray(menuID) ? menuID : options); + } else { + if (typeof menuID !== 'string') throw new TypeError('MENU_ID_NOT_STRING'); + const menuCorrect = menuAll.find(menu => menu.customId == menuID); + if (!menuCorrect) throw new TypeError('MENU_NOT_FOUND'); + return menuCorrect.select(this, Array.isArray(menuID) ? menuID : options); } - return menuCorrect.select(this, Array.isArray(menuID) ? menuID : options); } // /** diff --git a/src/structures/MessageButton.js b/src/structures/MessageButton.js index bd6da9a..325a33c 100644 --- a/src/structures/MessageButton.js +++ b/src/structures/MessageButton.js @@ -3,7 +3,7 @@ const { setTimeout } = require('node:timers'); const BaseMessageComponent = require('./BaseMessageComponent'); const { RangeError } = require('../errors'); -const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants'); +const { MessageButtonStyles, MessageComponentTypes, InteractionTypes } = require('../util/Constants'); const SnowflakeUtil = require('../util/SnowflakeUtil'); const Util = require('../util/Util'); const { lazy } = require('../util/Util'); @@ -175,16 +175,16 @@ class MessageButton extends BaseMessageComponent { if (!(message instanceof Message())) throw new Error('[UNKNOWN_MESSAGE] Please pass a valid Message'); if (!this.customId || this.style == 5 || this.disabled) return false; // Button URL, Disabled const data = { - type: 3, // ? + type: InteractionTypes.MESSAGE_COMPONENT, nonce, - guild_id: message.guild?.id ?? null, // In DMs + guild_id: message.guild?.id ?? null, channel_id: message.channel.id, message_id: message.id, application_id: message.applicationId ?? message.author.id, session_id: message.client.session_id, message_flags: message.flags.bitfield, data: { - component_type: 2, // Button + component_type: MessageComponentTypes.BUTTON, custom_id: this.customId, }, }; diff --git a/src/structures/MessageSelectMenu.js b/src/structures/MessageSelectMenu.js index e27685e..a893365 100644 --- a/src/structures/MessageSelectMenu.js +++ b/src/structures/MessageSelectMenu.js @@ -3,7 +3,7 @@ const { setTimeout } = require('node:timers'); const BaseMessageComponent = require('./BaseMessageComponent'); const { Message } = require('./Message'); -const { MessageComponentTypes } = require('../util/Constants'); +const { MessageComponentTypes, InteractionTypes } = require('../util/Constants'); const SnowflakeUtil = require('../util/SnowflakeUtil'); const Util = require('../util/Util'); @@ -44,7 +44,7 @@ class MessageSelectMenu extends BaseMessageComponent { * @param {MessageSelectMenu|MessageSelectMenuOptions} [data={}] MessageSelectMenu to clone or raw data */ constructor(data = {}) { - super({ type: 'SELECT_MENU' }); + super({ type: data?.type ? MessageComponentTypes[data.type] : 'STRING_SELECT_MENU' }); this.setup(data); } @@ -87,6 +87,28 @@ class MessageSelectMenu extends BaseMessageComponent { this.disabled = data.disabled ?? false; } + /** + * @typedef {string} SelectMenuTypes + * Must be one of: + * * `STRING_SELECT_MENU` + * * `USER_SELECT_MENU` + * * `ROLE_SELECT_MENU` + * * `MENTIONABLE_SELECT_MENU` + * * `CHANNEL_SELECT_MENU` + */ + + /** + * Set type of select menu + * @param {SelectMenuTypes} type Type of select menu + * @returns {MessageSelectMenu} + */ + + setType(type) { + if (!type) type = MessageComponentTypes.STRING_SELECT_MENU; + this.type = MessageSelectMenu.resolveType(type); + return this; + } + /** * Sets the custom id of this select menu * @param {string} customId A unique string to be sent in the interaction when clicked @@ -202,6 +224,10 @@ class MessageSelectMenu extends BaseMessageComponent { return { label, value, description, emoji, default: option.default ?? false }; } + static resolveType(type) { + return typeof type === 'string' ? type : MessageComponentTypes[type]; + } + /** * Normalizes option input and resolves strings and emojis. * @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The select menu options to normalize @@ -218,11 +244,9 @@ class MessageSelectMenu extends BaseMessageComponent { * @returns {Promise} */ async select(message, values = []) { - // Github copilot is the best :)) - // POST data from https://github.com/phamleduy04 if (!(message instanceof Message)) throw new Error('[UNKNOWN_MESSAGE] Please pass a valid Message'); if (!Array.isArray(values)) throw new TypeError('[INVALID_VALUES] Please pass an array of values'); - if (!this.customId || this.disabled || values.length == 0) return false; // Disabled or null customID or [] array + if (!this.customId || this.disabled) return false; // Disabled or null customID // Check value is invalid [Max options is 20] => For loop if (values.length < this.minValues) { throw new RangeError(`[SELECT_MENU_MIN_VALUES] The minimum number of values is ${this.minValues}`); @@ -230,30 +254,68 @@ class MessageSelectMenu extends BaseMessageComponent { if (values.length > this.maxValues) { throw new RangeError(`[SELECT_MENU_MAX_VALUES] The maximum number of values is ${this.maxValues}`); } - const validValue = this.options.map(obj => obj.value); - const check_ = await values.find(element => { - if (typeof element !== 'string') return true; - if (!validValue.includes(element)) return true; - return false; + const enableCheck = {}; + this.options.forEach(obj => { + enableCheck[obj.value] = obj.default; }); - if (check_) { - throw new RangeError( - `[SELECT_MENU_INVALID_VALUE] The value ${check_} is invalid. Please use a valid value ${validValue.join(', ')}`, - ); + const parseValues = value => { + switch (this.type) { + case 'STRING_SELECT_MENU': { + if (typeof value !== 'string') throw new TypeError('[INVALID_VALUE] Please pass a string value'); + const value_ = this.options.find(obj => obj.value === value || obj.label === value); + if (!value_) throw new Error('[INVALID_VALUE] Please pass a valid value'); + return value_.value; + } + case 'USER_SELECT_MENU': { + const userId = this.client.users.resolveId(value); + if (!userId) throw new Error('[INVALID_VALUE] Please pass a valid user'); + return userId; + } + case 'ROLE_SELECT_MENU': { + const roleId = this.client.roles.resolveId(value); + if (!roleId) throw new Error('[INVALID_VALUE] Please pass a valid role'); + return roleId; + } + case 'MENTIONABLE_SELECT_MENU': { + const mentionableId = this.client.users.resolveId(value) || this.client.roles.resolveId(value); + if (!mentionableId) throw new Error('[INVALID_VALUE] Please pass a valid mentionable'); + return mentionableId; + } + case 'CHANNEL_SELECT_MENU': { + const channelId = this.client.channels.resolveId(value); + if (!channelId) throw new Error('[INVALID_VALUE] Please pass a valid channel'); + return channelId; + } + default: { + throw new Error(`[INVALID_TYPE] Please pass a valid select menu type (Got ${this.type})`); + } + } + }; + + for (const value of values) { + const value_ = parseValues(value); + if (value_ in enableCheck) { + enableCheck[value_] = !enableCheck[value_]; + } else { + enableCheck[value_] = true; + } } + + values = values?.length ? Object.keys(enableCheck).filter(key => enableCheck[key]) : []; + const nonce = SnowflakeUtil.generate(); const data = { - type: 3, // ? - guild_id: message.guild?.id ?? null, // In DMs + type: InteractionTypes.MESSAGE_COMPONENT, + guild_id: message.guild?.id ?? null, channel_id: message.channel.id, message_id: message.id, application_id: message.applicationId ?? message.author.id, session_id: message.client.session_id, message_flags: message.flags.bitfield, data: { - component_type: 3, // Select Menu + component_type: MessageComponentTypes[this.type], custom_id: this.customId, - type: 3, // Select Menu + type: MessageComponentTypes[this.type], values, }, nonce, diff --git a/src/util/Constants.js b/src/util/Constants.js index 1bf691e..32f453b 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -6,28 +6,28 @@ const Package = (exports.Package = require('../../package.json')); const { Error, RangeError, TypeError } = require('../errors'); // #88: https://jnrbsn.github.io/user-agents/user-agents.json const listUserAgent = [ - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', - 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12.6; rv:106.0) Gecko/20100101 Firefox/106.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.0; rv:106.0) Gecko/20100101 Firefox/106.0', 'Mozilla/5.0 (X11; Linux i686; rv:106.0) Gecko/20100101 Firefox/106.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0', 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:106.0) Gecko/20100101 Firefox/106.0', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0', 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12.6; rv:102.0) Gecko/20100101 Firefox/102.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.0; rv:102.0) Gecko/20100101 Firefox/102.0', 'Mozilla/5.0 (X11; Linux i686; rv:102.0) Gecko/20100101 Firefox/102.0', 'Mozilla/5.0 (Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0', 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:102.0) Gecko/20100101 Firefox/102.0', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0', 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.52', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.52', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35', ]; /** @@ -1513,12 +1513,26 @@ exports.InteractionResponseTypes = createEnum([ * The type of a message component * * ACTION_ROW * * BUTTON - * * SELECT_MENU + * * STRING_SELECT_MENU * * TEXT_INPUT + * * USER_SELECT_MENU + * * ROLE_SELECT_MENU + * * MENTIONABLE_SELECT_MENU + * * CHANNEL_SELECT_MENU * @typedef {string} MessageComponentType * @see {@link https://discord.com/developers/docs/interactions/message-components#component-object-component-types} */ -exports.MessageComponentTypes = createEnum([null, 'ACTION_ROW', 'BUTTON', 'SELECT_MENU', 'TEXT_INPUT']); +exports.MessageComponentTypes = createEnum([ + null, + 'ACTION_ROW', + 'BUTTON', + 'STRING_SELECT_MENU', + 'TEXT_INPUT', + 'USER_SELECT_MENU', + 'ROLE_SELECT_MENU', + 'MENTIONABLE_SELECT_MENU', + 'CHANNEL_SELECT_MENU', +]); /** * The style of a message button diff --git a/src/util/Options.js b/src/util/Options.js index 20e84da..f92af78 100644 --- a/src/util/Options.js +++ b/src/util/Options.js @@ -181,14 +181,14 @@ class Options extends null { browser: 'Chrome', device: '', system_locale: 'en-US', - browser_version: '105.0.0.0', + browser_version: '107.0.0.0', os_version: '10', referrer: '', referring_domain: '', referrer_current: '', referring_domain_current: '', release_channel: 'stable', - client_build_number: 154186, + client_build_number: 156668, client_event_source: null, }, // ? capabilities: 1021, diff --git a/typings/enums.d.ts b/typings/enums.d.ts index 659eaf4..5e8b966 100644 --- a/typings/enums.d.ts +++ b/typings/enums.d.ts @@ -218,8 +218,12 @@ export const enum MessageButtonStyles { export const enum MessageComponentTypes { ACTION_ROW = 1, BUTTON = 2, - SELECT_MENU = 3, + STRING_SELECT_MENU = 3, TEXT_INPUT = 4, + USER_SELECT_MENU = 5, + ROLE_SELECT_MENU = 6, + MENTIONABLE_SELECT_MENU = 7, + CHANNEL_SELECT_MENU = 8, } export const enum ModalComponentTypes { diff --git a/typings/index.d.ts b/typings/index.d.ts index 3cd908f..5c939f6 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1863,8 +1863,12 @@ export type AwaitMessageCollectorOptionsParams< export interface StringMappedInteractionTypes { BUTTON: ButtonInteraction; - SELECT_MENU: SelectMenuInteraction; + STRING_SELECT_MENU: SelectMenuInteraction; ACTION_ROW: MessageComponentInteraction; + USER_SELECT_MENU: SelectMenuInteraction; + ROLE_SELECT_MENU: SelectMenuInteraction; + MENTIONABLE_SELECT_MENU: SelectMenuInteraction; + CHANNEL_SELECT_MENU: SelectMenuInteraction; } export type WrapBooleanCache = If; @@ -1873,9 +1877,13 @@ export type MappedInteractionTypes = EnumValue typeof MessageComponentTypes, { BUTTON: ButtonInteraction>; - SELECT_MENU: SelectMenuInteraction>; + STRING_SELECT_MENU: SelectMenuInteraction>; ACTION_ROW: MessageComponentInteraction>; TEXT_INPUT: ModalSubmitInteraction>; + USER_SELECT_MENU: SelectMenuInteraction>; + ROLE_SELECT_MENU: SelectMenuInteraction>; + MENTIONABLE_SELECT_MENU: SelectMenuInteraction>; + CHANNEL_SELECT_MENU: SelectMenuInteraction>; } >; @@ -2248,9 +2256,10 @@ export class MessageSelectMenu extends BaseMessageComponent { public minValues: number | null; public options: MessageSelectOption[]; public placeholder: string | null; - public type: 'SELECT_MENU'; + public type: SelectMenuTypes; public addOptions(...options: MessageSelectOptionData[] | MessageSelectOptionData[][]): this; public setOptions(...options: MessageSelectOptionData[] | MessageSelectOptionData[][]): this; + public setType(type: SelectMenuTypes): this; public setCustomId(customId: string): this; public setDisabled(disabled?: boolean): this; public setMaxValues(maxValues: number): this; @@ -2262,7 +2271,7 @@ export class MessageSelectMenu extends BaseMessageComponent { ...options: MessageSelectOptionData[] | MessageSelectOptionData[][] ): this; public toJSON(): APISelectMenuComponent; - public select(message: Message, values: string[]): Promise; + public select(message: Message, values?: string[]): Promise; } // Todo @@ -2544,6 +2553,8 @@ export class Role extends Base { public static comparePositions(role1: Role, role2: Role): number; } +export type SelectMenuTypes = 'STRING_SELECT_MENU' | 'USER_SELECT_MENU' | 'ROLE_SELECT_MENU' | 'MENTIONABLE_SELECT_MENU' | 'CHANNEL_SELECT_MENU'; + export class SelectMenuInteraction extends MessageComponentInteraction { public constructor(client: Client, data: RawMessageSelectMenuInteractionData); public readonly component: CacheTypeReducer< @@ -2553,7 +2564,7 @@ export class SelectMenuInteraction extends MessageSelectMenu | APISelectMenuComponent, MessageSelectMenu | APISelectMenuComponent >; - public componentType: 'SELECT_MENU'; + public componentType: SelectMenuTypes; public values: string[]; public inGuild(): this is SelectMenuInteraction<'raw' | 'cached'>; public inCachedGuild(): this is SelectMenuInteraction<'cached'>;