feat(SelectMenu): SelectMenu v2

This commit is contained in:
March 7th
2022-11-05 20:02:27 +07:00
parent 04296bd6d1
commit 035d933771
10 changed files with 176 additions and 73 deletions

View File

@@ -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,

View File

@@ -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;

View File

@@ -1068,32 +1068,33 @@ class Message extends Base {
* @param {Array<string>} options Menu Options
* @returns {Promise<InteractionResponse>}
*/
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);
}
//
/**

View File

@@ -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,
},
};

View File

@@ -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<InteractionResponse>}
*/
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,