feat: new select menu (v2)

#9174 djs
This commit is contained in:
Elysia
2023-02-28 13:42:35 +07:00
parent ba22a5f550
commit 808adeb016
8 changed files with 392 additions and 81 deletions

View File

@@ -269,16 +269,71 @@ class Interaction extends Base {
* Indicates whether this interaction is a {@link SelectMenuInteraction}.
* @returns {boolean}
*/
isAnySelectMenu() {
return InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT && typeof this.values !== 'undefined';
}
/**
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `STRING_SELECT` type.
* @returns {boolean}
* @deprecated Use {@link Interaction#isStringSelect()} instead
*/
isSelectMenu() {
return this.isStringSelect();
}
/**
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `STRING_SELECT` type.
* @returns {boolean}
*/
isStringSelect() {
return (
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
[
MessageComponentTypes.STRING_SELECT,
MessageComponentTypes.USER_SELECT,
MessageComponentTypes.ROLE_SELECT,
MessageComponentTypes.MENTIONABLE_SELECT,
MessageComponentTypes.CHANNEL_SELECT,
].includes(MessageComponentTypes[this.componentType])
MessageComponentTypes[this.componentType] === MessageComponentTypes.STRING_SELECT
);
}
/**
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `USER_SELECT` type.
* @returns {boolean}
*/
isUserSelect() {
return (
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
MessageComponentTypes[this.componentType] === MessageComponentTypes.USER_SELECT
);
}
/**
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `ROLE_SELECT` type.
* @returns {boolean}
*/
isRoleSelect() {
return (
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
MessageComponentTypes[this.componentType] === MessageComponentTypes.ROLE_SELECT
);
}
/**
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `MENTIONABLE_SELECT` type.
* @returns {boolean}
*/
isMentionableSelect() {
return (
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
MessageComponentTypes[this.componentType] === MessageComponentTypes.MENTIONABLE_SELECT
);
}
/**
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `CHANNEL_SELECT` type.
* @returns {boolean}
*/
isChannelSelect() {
return (
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
MessageComponentTypes[this.componentType] === MessageComponentTypes.CHANNEL_SELECT
);
}

View File

@@ -2,11 +2,16 @@
const { setTimeout } = require('node:timers');
const BaseMessageComponent = require('./BaseMessageComponent');
const { MessageComponentTypes, InteractionTypes, ChannelTypes } = require('../util/Constants');
const {
ChannelTypes,
MessageComponentTypes,
SelectMenuComponentTypes,
InteractionTypes,
} = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil');
const Util = require('../util/Util');
const { lazy } = require('../util/Util');
const Message = lazy(() => require('./Message').Message);
const Util = require('../util/Util');
/**
* Represents a select menu message component
@@ -98,28 +103,6 @@ class MessageSelectMenu extends BaseMessageComponent {
) ?? [];
}
/**
* @typedef {string} SelectMenuTypes
* Must be one of:
* * `STRING_SELECT`
* * `USER_SELECT`
* * `ROLE_SELECT`
* * `MENTIONABLE_SELECT`
* * `CHANNEL_SELECT`
*/
/**
* Set type of select menu
* @param {SelectMenuTypes} type Type of select menu
* @returns {MessageSelectMenu}
*/
setType(type) {
if (!type) type = MessageComponentTypes.STRING_SELECT;
this.type = MessageSelectMenu.resolveType(type);
return this;
}
/**
* Adds the channel types to the select menu
* @param {...ChannelType[]} channelTypes Added channel types
@@ -201,6 +184,17 @@ class MessageSelectMenu extends BaseMessageComponent {
return this;
}
/**
* Sets the type of the select menu
* @param {SelectMenuComponentType} type Type of the select menu
* @returns {MessageSelectMenu}
*/
setType(type) {
if (!SelectMenuComponentTypes[type]) throw new TypeError('INVALID_TYPE', 'type', 'SelectMenuComponentType');
this.type = BaseMessageComponent.resolveType(type);
return this;
}
/**
* Adds options to the select menu.
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to add
@@ -239,13 +233,13 @@ class MessageSelectMenu extends BaseMessageComponent {
*/
toJSON() {
return {
channel_types: this.channelTypes.map(type => (typeof type === 'string' ? ChannelTypes[type] : type)),
custom_id: this.customId,
disabled: this.disabled,
placeholder: this.placeholder,
min_values: this.minValues,
max_values: this.maxValues ?? (this.minValues ? this.options.length : undefined),
options: this.options,
channel_types: this.channelTypes.map(type => (typeof type === 'string' ? ChannelTypes[type] : type)),
type: typeof this.type === 'string' ? MessageComponentTypes[this.type] : this.type,
};
}
@@ -266,10 +260,6 @@ 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
@@ -278,6 +268,7 @@ class MessageSelectMenu extends BaseMessageComponent {
static normalizeOptions(...options) {
return options.flat(Infinity).map(option => this.normalizeOption(option));
}
// Add
/**
* Mesage select menu

View File

@@ -1,9 +1,11 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const MessageComponentInteraction = require('./MessageComponentInteraction');
const { Events } = require('../util/Constants');
/**
* Represents a select menu interaction.
* Represents any select menu interaction.
* @extends {MessageComponentInteraction}
*/
class SelectMenuInteraction extends MessageComponentInteraction {
@@ -18,4 +20,151 @@ class SelectMenuInteraction extends MessageComponentInteraction {
}
}
module.exports = SelectMenuInteraction;
module.exports.SelectMenuInteraction = SelectMenuInteraction;
/**
* Represents a CHANNEL_SELECT interaction.
* @extends {SelectMenuInteraction}
*/
class ChannelSelectInteraction extends SelectMenuInteraction {
constructor(client, data) {
super(client, data);
const { channels } = data.data.resolved ?? {};
/**
* Collection of the selected channels
* @type {Collection<Snowflake, Channel|APIChannel>}
*/
this.channels = new Collection();
for (const channel of Object.values(channels)) {
this.channel.set(channel.id, this.client.channels._add(channel));
}
}
}
module.exports.ChannelSelectInteraction = ChannelSelectInteraction;
/**
* Represents a ROLE_SELECT interaction.
* @extends {SelectMenuInteraction}
*/
class RoleSelectInteraction extends SelectMenuInteraction {
constructor(client, data) {
super(client, data);
const { roles } = data.data.resolved ?? {};
/**
* Collection of the selected roles
* @type {Collection<Snowflake, Role|APIRole>}
*/
this.roles = new Collection();
for (const role of Object.values(roles)) {
this.roles.set(role.id, this.guild?.roles._add(role) ?? role);
}
}
}
module.exports.RoleSelectInteraction = RoleSelectInteraction;
/**
* Represents a USER_SELECT interaction.
* @extends {SelectMenuInteraction}
*/
class UserSelectInteraction extends SelectMenuInteraction {
constructor(client, data) {
super(client, data);
const { members, users } = data.data.resolved ?? {};
/**
* Collection of the selected users
* @type {Collection<Snowflake, User>}
*/
this.users = new Collection();
for (const user of Object.values(users)) {
this.users.set(user.id, this.client.users._add(user));
}
if (members) {
/**
* Collection of the selected members
* @type {Collection<Snowflake, GuildMember|APIGuildMember>}
*/
this.members = new Collection();
for (const [id, member] of Object.entries(members)) {
const user = users[id];
if (!user) {
this.client.emit(Events.DEBUG, `[SelectMenuInteraction] Received a member without a user, skipping ${id}`);
continue;
}
this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member });
}
}
}
}
module.exports.UserSelectInteraction = UserSelectInteraction;
/**
* Represents a MENTIONABLE_SELECT interaction.
* @extends {SelectMenuInteraction}
*/
class MentionableSelectInteraction extends SelectMenuInteraction {
constructor(client, data) {
super(client, data);
const { members, users, roles, channels } = data.data.resolved ?? {};
if (channels) {
/**
* Collection of the selected channels
* @type {Collection<Snowflake, Channel|APIChannel>}
*/
this.channels = new Collection();
for (const channel of Object.values(channels)) {
this.channels.set(channel.id, this.client?.channels._add(channel) ?? channel);
}
}
if (members) {
/**
* Collection of the selected members
* @type {Collection<Snowflake, GuildMember|APIGuildMember>}
*/
this.members = new Collection();
for (const [id, member] of Object.entries(members)) {
const user = users[id];
if (!user) {
this.client.emit(Events.DEBUG, `[SelectMenuInteraction] Received a member without a user, skipping ${id}`);
continue;
}
this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member });
}
}
if (roles) {
/**
* Collection of the selected roles
* @type {Collection<Snowflake, Role|APIRole>}
*/
this.roles = new Collection();
for (const role of Object.values(roles)) {
this.roles.set(role.id, this.guild?.roles._add(role) ?? role);
}
}
if (users) {
/**
* Collection of the selected users
* @type {Collection<Snowflake, User>}
*/
this.users = new Collection();
for (const user of Object.values(users)) {
this.users.set(user.id, this.client.users._add(user));
}
}
}
}
module.exports.MentionableSelectInteraction = MentionableSelectInteraction;