This commit is contained in:
Elysia 2024-01-09 20:15:49 +07:00
parent 26c71d7777
commit 02fcfb881f
21 changed files with 52 additions and 529 deletions

View File

@ -1,16 +1,19 @@
'use strict'; 'use strict';
const { Events } = require('../../../util/Constants'); const { Events, Status } = require('../../../util/Constants');
module.exports = (client, packet) => { module.exports = (client, packet, shard) => {
/**
* Emitted whenever a recipient is added from a group DM.
* @event Client#channelRecipientAdd
* @param {PartialGroupDMChannel} channel Group DM channel
* @param {User} user User
*/
const channel = client.channels.cache.get(packet.d.channel_id); const channel = client.channels.cache.get(packet.d.channel_id);
if (!channel) return; if (channel) {
if (!channel._recipients) channel._recipients = []; if (!channel._recipients) channel._recipients = [];
channel._recipients.push(packet.d.user); channel._recipients.push(packet.d.user);
const user = client.users._add(packet.d.user); const user = client.users._add(packet.d.user);
client.emit(Events.CHANNEL_RECIPIENT_ADD, channel, user); if (shard.status == Status.READY) {
/**
* Emitted whenever a recipient is added from a group DM.
* @event Client#channelRecipientAdd
* @param {GroupDMChannel} channel Group DM channel
* @param {User} user User
*/
client.emit(Events.CHANNEL_RECIPIENT_ADD, channel, user);
}
}
}; };

View File

@ -1,16 +1,16 @@
'use strict'; 'use strict';
const { Events } = require('../../../util/Constants'); const { Events } = require('../../../util/Constants');
module.exports = (client, packet) => { module.exports = (client, packet) => {
/**
* Emitted whenever a recipient is removed from a group DM.
* @event Client#channelRecipientRemove
* @param {PartialGroupDMChannel} channel Group DM channel
* @param {User} user User
*/
const channel = client.channels.cache.get(packet.d.channel_id); const channel = client.channels.cache.get(packet.d.channel_id);
if (!channel) return; if (channel) {
if (!channel._recipients) channel._recipients = []; if (!channel._recipients) channel._recipients = [];
channel._recipients = channel._recipients.filter(r => r !== packet.d.user.id); channel._recipients = channel._recipients.filter(u => u.id !== packet.d.user.id);
const user = client.users._add(packet.d.user); /**
client.emit(Events.CHANNEL_RECIPIENT_REMOVE, channel, user); * Emitted whenever a recipient is removed from a group DM.
* @event Client#channelRecipientRemove
* @param {GroupDMChannel} channel Group DM channel
* @param {User} user User
*/
client.emit(Events.CHANNEL_RECIPIENT_REMOVE, channel, client.users._add(packet.d.user));
}
}; };

View File

@ -1,11 +0,0 @@
'use strict';
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
for (const command of data.application_commands) {
const user = client.users.cache.get(command.application_id);
if (!user || !user.bot) continue;
user.application?.commands?._add(command, true);
}
client.emit(Events.GUILD_APPLICATION_COMMANDS_UPDATE, data);
};

View File

@ -1,16 +0,0 @@
'use strict';
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
const channel = client.channels.cache.get(data.channel_id);
/**
* Emitted whenever message is acknowledged (mark read / unread)
* @event Client#messageAck
* @param {TextChannel} channel Channel
* @param {Snowflake} message_id Message ID
* @param {boolean} isRead Whether the message is read
* @param {Object} raw Raw data
*/
client.emit(Events.MESSAGE_ACK, channel, data.message_id, !data.manual, data);
};

View File

@ -1,5 +1,5 @@
'use strict'; 'use strict';
module.exports = (client, { d: data }) => { module.exports = (client, { d: data }) => {
client.user.notes.set(data.id, data.note); client.notes.cache.set(data.id, data.note);
}; };

View File

@ -130,7 +130,7 @@ class GuildBanManager extends CachedManager {
* @typedef {Object} BanOptions * @typedef {Object} BanOptions
* @property {number} [days=0] Number of days of messages to delete, must be between 0 and 7, inclusive * @property {number} [days=0] Number of days of messages to delete, must be between 0 and 7, inclusive
* <warn>This property is deprecated. Use `deleteMessageSeconds` instead.</warn> * <warn>This property is deprecated. Use `deleteMessageSeconds` instead.</warn>
* @property {number} [deleteMessageSeconds=0] Number of seconds of messages to delete, * @property {number} [deleteMessageSeconds] Number of seconds of messages to delete,
* must be between 0 and 604800 (7 days), inclusive * must be between 0 and 604800 (7 days), inclusive
* @property {string} [reason] The reason for the ban * @property {string} [reason] The reason for the ban
*/ */

View File

@ -89,7 +89,7 @@ class PermissionOverwriteManager extends CachedManager {
* @private * @private
*/ */
async upsert(userOrRole, options, overwriteOptions = {}, existing) { async upsert(userOrRole, options, overwriteOptions = {}, existing) {
const userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole); let userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole);
let { type, reason } = overwriteOptions; let { type, reason } = overwriteOptions;
if (typeof type !== 'number') { if (typeof type !== 'number') {
userOrRole = this.channel.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole); userOrRole = this.channel.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole);

View File

@ -179,7 +179,7 @@ class ThreadMemberManager extends CachedManager {
const id = this.resolveId(member); const id = this.resolveId(member);
return id return id
? this._fetchOne(id, options) ? this._fetchOne(id, options)
: this._fetchMany(typeof member !== 'boolean' ? member : { ...options, cache: member }); : this._fetchMany(typeof member === 'boolean' ? { ...options, cache: member } : options);
} }
} }

View File

@ -2,6 +2,9 @@
const { ApplicationRoleConnectionMetadataTypes } = require('../util/Constants'); const { ApplicationRoleConnectionMetadataTypes } = require('../util/Constants');
/**
* Role connection metadata object for an application.
*/
class ApplicationRoleConnectionMetadata { class ApplicationRoleConnectionMetadata {
constructor(data) { constructor(data) {
/** /**

View File

@ -194,7 +194,7 @@ class AutoModerationRule extends Base {
* @returns {Promise<AutoModerationRule>} * @returns {Promise<AutoModerationRule>}
*/ */
setKeywordFilter(keywordFilter, reason) { setKeywordFilter(keywordFilter, reason) {
return this.edit({ triggerMetadata: { keywordFilter }, reason }); return this.edit({ triggerMetadata: { ...this.triggerMetadata, keywordFilter }, reason });
} }
/** /**
@ -205,7 +205,7 @@ class AutoModerationRule extends Base {
* @returns {Promise<AutoModerationRule>} * @returns {Promise<AutoModerationRule>}
*/ */
setRegexPatterns(regexPatterns, reason) { setRegexPatterns(regexPatterns, reason) {
return this.edit({ triggerMetadata: { regexPatterns }, reason }); return this.edit({ triggerMetadata: { ...this.triggerMetadata, regexPatterns }, reason });
} }
/** /**
@ -215,7 +215,7 @@ class AutoModerationRule extends Base {
* @returns {Promise<AutoModerationRule>} * @returns {Promise<AutoModerationRule>}
*/ */
setPresets(presets, reason) { setPresets(presets, reason) {
return this.edit({ triggerMetadata: { presets }, reason }); return this.edit({ triggerMetadata: { ...this.triggerMetadata, presets }, reason });
} }
/** /**
@ -225,7 +225,7 @@ class AutoModerationRule extends Base {
* @returns {Promise<AutoModerationRule>} * @returns {Promise<AutoModerationRule>}
*/ */
setAllowList(allowList, reason) { setAllowList(allowList, reason) {
return this.edit({ triggerMetadata: { allowList }, reason }); return this.edit({ triggerMetadata: { ...this.triggerMetadata, allowList }, reason });
} }
/** /**
@ -235,7 +235,7 @@ class AutoModerationRule extends Base {
* @returns {Promise<AutoModerationRule>} * @returns {Promise<AutoModerationRule>}
*/ */
setMentionTotalLimit(mentionTotalLimit, reason) { setMentionTotalLimit(mentionTotalLimit, reason) {
return this.edit({ triggerMetadata: { mentionTotalLimit }, reason }); return this.edit({ triggerMetadata: { ...this.triggerMetadata, mentionTotalLimit }, reason });
} }
/** /**

View File

@ -2,6 +2,7 @@
const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver'); const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
const Interaction = require('./Interaction'); const Interaction = require('./Interaction');
const { Error } = require('../errors');
const { InteractionResponseTypes, ApplicationCommandOptionTypes } = require('../util/Constants'); const { InteractionResponseTypes, ApplicationCommandOptionTypes } = require('../util/Constants');
/** /**

View File

@ -76,7 +76,11 @@ class BaseMessageComponent {
component = data instanceof MessageButton ? data : new MessageButton(data); component = data instanceof MessageButton ? data : new MessageButton(data);
break; break;
} }
case MessageComponentTypes.SELECT_MENU: { case MessageComponentTypes.STRING_SELECT:
case MessageComponentTypes.USER_SELECT:
case MessageComponentTypes.ROLE_SELECT:
case MessageComponentTypes.MENTIONABLE_SELECT:
case MessageComponentTypes.CHANNEL_SELECT: {
const MessageSelectMenu = require('./MessageSelectMenu'); const MessageSelectMenu = require('./MessageSelectMenu');
component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data); component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data);
break; break;

View File

@ -53,7 +53,6 @@ class Channel extends Base {
if ('flags' in data) { if ('flags' in data) {
/** /**
* The flags that are applied to the channel. * The flags that are applied to the channel.
* <info>This is only `null` in a {@link PartialGroupDMChannel}. In all other cases, it is not `null`.</info>
* @type {?Readonly<ChannelFlags>} * @type {?Readonly<ChannelFlags>}
*/ */
this.flags = new ChannelFlags(data.flags).freeze(); this.flags = new ChannelFlags(data.flags).freeze();
@ -203,8 +202,8 @@ class Channel extends Base {
if ((data.recipients && data.type !== ChannelTypes.GROUP_DM) || data.type === ChannelTypes.DM) { if ((data.recipients && data.type !== ChannelTypes.GROUP_DM) || data.type === ChannelTypes.DM) {
channel = new DMChannel(client, data); channel = new DMChannel(client, data);
} else if (data.type === ChannelTypes.GROUP_DM) { } else if (data.type === ChannelTypes.GROUP_DM) {
const PartialGroupDMChannel = require('./PartialGroupDMChannel'); const GroupDMChannel = require('./GroupDMChannel');
channel = new PartialGroupDMChannel(client, data); channel = new GroupDMChannel(client, data);
} }
} else { } else {
guild ??= client.guilds.cache.get(data.guild_id); guild ??= client.guilds.cache.get(data.guild_id);

View File

@ -22,7 +22,7 @@ class ContextMenuInteraction extends BaseCommandInteraction {
); );
/** /**
* The id of the target of the interaction * The id of the target of this interaction
* @type {Snowflake} * @type {Snowflake}
*/ */
this.targetId = data.data.target_id; this.targetId = data.data.target_id;

View File

@ -3,7 +3,6 @@
const GuildChannel = require('./GuildChannel'); const GuildChannel = require('./GuildChannel');
const TextBasedChannel = require('./interfaces/TextBasedChannel'); const TextBasedChannel = require('./interfaces/TextBasedChannel');
const GuildForumThreadManager = require('../managers/GuildForumThreadManager'); const GuildForumThreadManager = require('../managers/GuildForumThreadManager');
const InteractionManager = require('../managers/InteractionManager');
const { SortOrderTypes, ForumLayoutTypes } = require('../util/Constants'); const { SortOrderTypes, ForumLayoutTypes } = require('../util/Constants');
const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require('../util/Util'); const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require('../util/Util');
@ -46,12 +45,6 @@ class ForumChannel extends GuildChannel {
constructor(guild, data, client) { constructor(guild, data, client) {
super(guild, data, client, false); super(guild, data, client, false);
/**
* A manager of the interactions sent to this channel
* @type {InteractionManager}
*/
this.interactions = new InteractionManager(this);
/** /**
* A manager of the threads belonging to this channel * A manager of the threads belonging to this channel
* @type {GuildForumThreadManager} * @type {GuildForumThreadManager}
@ -260,12 +253,9 @@ TextBasedChannel.applyToClass(ForumChannel, true, [
'send', 'send',
'lastMessage', 'lastMessage',
'lastPinAt', 'lastPinAt',
'bulkDelete',
'sendTyping', 'sendTyping',
'createMessageCollector', 'createMessageCollector',
'awaitMessages', 'awaitMessages',
'createMessageComponentCollector',
'awaitMessageComponent',
]); ]);
module.exports = ForumChannel; module.exports = ForumChannel;

View File

@ -299,8 +299,6 @@ class GroupDMChannel extends Channel {
sendTyping() {} sendTyping() {}
createMessageCollector() {} createMessageCollector() {}
awaitMessages() {} awaitMessages() {}
createMessageComponentCollector() {}
awaitMessageComponent() {}
// Doesn't work on DM channels; setRateLimitPerUser() {} // Doesn't work on DM channels; setRateLimitPerUser() {}
// Doesn't work on DM channels; setNSFW() {} // Doesn't work on DM channels; setNSFW() {}
} }

View File

@ -269,71 +269,10 @@ class Interaction extends Base {
* Indicates whether this interaction is a {@link SelectMenuInteraction}. * Indicates whether this interaction is a {@link SelectMenuInteraction}.
* @returns {boolean} * @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() { isSelectMenu() {
return this.isStringSelect();
}
/**
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `STRING_SELECT` type.
* @returns {boolean}
*/
isStringSelect() {
return ( return (
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT && InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
MessageComponentTypes[this.componentType] === MessageComponentTypes.STRING_SELECT MessageComponentTypes[this.componentType] === MessageComponentTypes.SELECT_MENU
);
}
/**
* 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

@ -1,13 +1,9 @@
'use strict'; 'use strict';
const { setTimeout } = require('node:timers');
const BaseMessageComponent = require('./BaseMessageComponent'); const BaseMessageComponent = require('./BaseMessageComponent');
const { RangeError } = require('../errors'); const { RangeError } = require('../errors');
const { MessageButtonStyles, MessageComponentTypes, InteractionTypes } = require('../util/Constants'); const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil');
const Util = require('../util/Util'); const Util = require('../util/Util');
const { lazy } = require('../util/Util');
const Message = lazy(() => require('../structures/Message').Message);
/** /**
* Represents a button message component. * Represents a button message component.
@ -164,68 +160,6 @@ class MessageButton extends BaseMessageComponent {
static resolveStyle(style) { static resolveStyle(style) {
return typeof style === 'string' ? style : MessageButtonStyles[style]; return typeof style === 'string' ? style : MessageButtonStyles[style];
} }
// Patch Click
/**
* Click the button
* @param {Message} message Discord Message
* @returns {Promise<InteractionResponse>}
*/
async click(message) {
const nonce = SnowflakeUtil.generate();
if (!(message instanceof Message())) throw new Error('[UNKNOWN_MESSAGE] Please pass a valid Message');
if (!this.customId || this.style == MessageButtonStyles.LINK || this.disabled) return false;
const data = {
type: InteractionTypes.MESSAGE_COMPONENT,
nonce,
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.sessionId,
message_flags: message.flags.bitfield,
data: {
component_type: MessageComponentTypes.BUTTON,
custom_id: this.customId,
},
};
await message.client.api.interactions.post({
data,
});
message.client._interactionCache.set(nonce, {
channelId: message.channelId,
guildId: message.guildId,
metadata: data,
});
return new Promise((resolve, reject) => {
const handler = data => {
timeout.refresh();
if (data.metadata?.nonce !== nonce) return;
clearTimeout(timeout);
message.client.removeListener('interactionResponse', handler);
message.client.decrementMaxListeners();
if (data.status) {
resolve(data.metadata);
} else {
reject(
new Error('INTERACTION_ERROR', {
cause: data,
}),
);
}
};
const timeout = setTimeout(() => {
message.client.removeListener('interactionResponse', handler);
message.client.decrementMaxListeners();
reject(
new Error('INTERACTION_TIMEOUT', {
cause: data,
}),
);
}, message.client.options.interactionTimeout).unref();
message.client.incrementMaxListeners();
message.client.on('interactionResponse', handler);
});
}
} }
module.exports = MessageButton; module.exports = MessageButton;

View File

@ -1,16 +1,7 @@
'use strict'; 'use strict';
const { setTimeout } = require('node:timers');
const BaseMessageComponent = require('./BaseMessageComponent'); const BaseMessageComponent = require('./BaseMessageComponent');
const { const { ChannelTypes, MessageComponentTypes } = require('../util/Constants');
ChannelTypes,
MessageComponentTypes,
SelectMenuComponentTypes,
InteractionTypes,
} = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil');
const { lazy } = require('../util/Util');
const Message = lazy(() => require('./Message').Message);
const Util = require('../util/Util'); const Util = require('../util/Util');
/** /**
@ -103,130 +94,6 @@ class MessageSelectMenu extends BaseMessageComponent {
) ?? []; ) ?? [];
} }
/**
* Adds the channel types to the select menu
* @param {...ChannelType[]} channelTypes Added channel types
* @returns {MessageSelectMenu}
*/
addChannelTypes(...channelTypes) {
if (!channelTypes.every(channelType => ChannelTypes[channelType])) {
throw new TypeError('INVALID_TYPE', 'channelTypes', 'Rest<ChannelTypes[]>');
}
this.channelTypes.push(
...channelTypes.map(channelType => (typeof channelType === 'string' ? channelType : ChannelTypes[channelType])),
);
return this;
}
/**
* Sets the channel types of the select menu
* @param {...ChannelType[]} channelTypes An array of new channel types
* @returns {MessageSelectMenu}
*/
setChannelTypes(...channelTypes) {
if (!channelTypes.every(channelType => ChannelTypes[channelType])) {
throw new TypeError('INVALID_TYPE', 'channelTypes', 'Rest<ChannelTypes[]>');
}
this.channelTypes = channelTypes.map(channelType =>
typeof channelType === 'string' ? channelType : ChannelTypes[channelType],
);
return this;
}
/**
* Sets the custom id of this select menu
* @param {string} customId A unique string to be sent in the interaction when clicked
* @returns {MessageSelectMenu}
*/
setCustomId(customId) {
this.customId = Util.verifyString(customId, RangeError, 'SELECT_MENU_CUSTOM_ID');
return this;
}
/**
* Sets the interactive status of the select menu
* @param {boolean} [disabled=true] Whether this select menu should be disabled
* @returns {MessageSelectMenu}
*/
setDisabled(disabled = true) {
this.disabled = disabled;
return this;
}
/**
* Sets the maximum number of selections allowed for this select menu
* @param {number} maxValues Number of selections to be allowed
* @returns {MessageSelectMenu}
*/
setMaxValues(maxValues) {
this.maxValues = maxValues;
return this;
}
/**
* Sets the minimum number of selections required for this select menu
* <info>This will default the maxValues to the number of options, unless manually set</info>
* @param {number} minValues Number of selections to be required
* @returns {MessageSelectMenu}
*/
setMinValues(minValues) {
this.minValues = minValues;
return this;
}
/**
* Sets the placeholder of this select menu
* @param {string} placeholder Custom placeholder text to display when nothing is selected
* @returns {MessageSelectMenu}
*/
setPlaceholder(placeholder) {
this.placeholder = Util.verifyString(placeholder, RangeError, 'SELECT_MENU_PLACEHOLDER');
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
* @returns {MessageSelectMenu}
*/
addOptions(...options) {
this.options.push(...this.constructor.normalizeOptions(options));
return this;
}
/**
* Sets the options of the select menu.
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to set
* @returns {MessageSelectMenu}
*/
setOptions(...options) {
this.spliceOptions(0, this.options.length, options);
return this;
}
/**
* Removes, replaces, and inserts options in the select menu.
* @param {number} index The index to start at
* @param {number} deleteCount The number of options to remove
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} [options] The replacing option objects
* @returns {MessageSelectMenu}
*/
spliceOptions(index, deleteCount, ...options) {
this.options.splice(index, deleteCount, ...this.constructor.normalizeOptions(...options));
return this;
}
/** /**
* Transforms the select menu into a plain object * Transforms the select menu into a plain object
* @returns {APIMessageSelectMenu} The raw data of this select menu * @returns {APIMessageSelectMenu} The raw data of this select menu
@ -268,124 +135,6 @@ class MessageSelectMenu extends BaseMessageComponent {
static normalizeOptions(...options) { static normalizeOptions(...options) {
return options.flat(Infinity).map(option => this.normalizeOption(option)); return options.flat(Infinity).map(option => this.normalizeOption(option));
} }
// Add
/**
* Mesage select menu
* @param {Message} message The message this select menu is for
* @param {Array<any>} values The values of the select menu
* @returns {Promise<InteractionResponse>}
*/
async select(message, values) {
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) {
throw new Error('[INVALID_MENU] Menu does not contain Id or has been disabled');
} // Disabled or null customID
if (values.length < this.minValues) {
throw new RangeError(`[SELECT_MENU_MIN_VALUES] The minimum number of values is ${this.minValues}`);
}
if (values.length > this.maxValues) {
throw new RangeError(`[SELECT_MENU_MAX_VALUES] The maximum number of values is ${this.maxValues}`);
}
const parseValues = value => {
switch (this.type) {
case 'SELECT_MENU':
case 'STRING_SELECT': {
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': {
const userId = this.client.users.resolveId(value);
if (!userId) throw new Error('[INVALID_VALUE] Please pass a valid user');
return userId;
}
case 'ROLE_SELECT': {
const roleId = this.client.roles.resolveId(value);
if (!roleId) throw new Error('[INVALID_VALUE] Please pass a valid role');
return roleId;
}
case 'MENTIONABLE_SELECT': {
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': {
const channel = this.client.channels.resolve(value);
if (!channel) throw new Error('[INVALID_VALUE] Please pass a valid channel');
if (!this.channelTypes.includes(channel.type)) {
throw new Error(
`[INVALID_VALUE] Please pass a valid channel type (Got: ${channel.type}, allow: ${this.channelTypes.join(
', ',
)})`,
);
}
return channel.id;
}
default: {
throw new Error(`[INVALID_TYPE] Please pass a valid select menu type (Got ${this.type})`);
}
}
};
const nonce = SnowflakeUtil.generate();
const data = {
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.sessionId,
message_flags: message.flags.bitfield,
data: {
component_type: MessageComponentTypes[this.type],
custom_id: this.customId,
type: MessageComponentTypes[this.type],
values: values?.length ? values.map(parseValues) : [],
},
nonce,
};
await message.client.api.interactions.post({
data,
});
message.client._interactionCache.set(nonce, {
channelId: message.channelId,
guildId: message.guildId,
metadata: data,
});
return new Promise((resolve, reject) => {
const handler = data => {
timeout.refresh();
if (data.metadata?.nonce !== nonce) return;
clearTimeout(timeout);
message.client.removeListener('interactionResponse', handler);
message.client.decrementMaxListeners();
if (data.status) {
resolve(data.metadata);
} else {
reject(
new Error('INTERACTION_ERROR', {
cause: data,
}),
);
}
};
const timeout = setTimeout(() => {
message.client.removeListener('interactionResponse', handler);
message.client.decrementMaxListeners();
reject(
new Error('INTERACTION_TIMEOUT', {
cause: data,
}),
);
}, message.client.options.interactionTimeout).unref();
message.client.incrementMaxListeners();
message.client.on('interactionResponse', handler);
});
}
} }
module.exports = MessageSelectMenu; module.exports = MessageSelectMenu;

View File

@ -138,7 +138,7 @@ class Modal {
data: postData, data: postData,
}); });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeoutMs = 15_000; const timeoutMs = 5_000;
// Waiting for MsgCreate / ModalCreate // Waiting for MsgCreate / ModalCreate
const handler = data => { const handler = data => {
if (data.nonce !== nonce) return; if (data.nonce !== nonce) return;

View File

@ -82,76 +82,6 @@ class TextInputComponent extends BaseMessageComponent {
this.value = data.value ?? null; this.value = data.value ?? null;
} }
/**
* Sets the custom id of this text input component
* @param {string} customId A unique string to be sent in the interaction when submitted
* @returns {TextInputComponent}
*/
setCustomId(customId) {
this.customId = Util.verifyString(customId, RangeError, 'TEXT_INPUT_CUSTOM_ID');
return this;
}
/**
* Sets the label of this text input component
* @param {string} label The text to be displayed above this text input component
* @returns {TextInputComponent}
*/
setLabel(label) {
this.label = Util.verifyString(label, RangeError, 'TEXT_INPUT_LABEL');
return this;
}
/**
* Sets the text input component to be required for modal submission
* @param {boolean} [required=true] Whether this text input component is required
* @returns {TextInputComponent}
*/
setRequired(required = true) {
this.required = required;
return this;
}
/**
* Sets the maximum length of text input required in this text input component
* @param {number} maxLength Maximum length of text to be required
* @returns {TextInputComponent}
*/
setMaxLength(maxLength) {
this.maxLength = maxLength;
return this;
}
/**
* Sets the minimum length of text input required in this text input component
* @param {number} minLength Minimum length of text to be required
* @returns {TextInputComponent}
*/
setMinLength(minLength) {
this.minLength = minLength;
return this;
}
/**
* Sets the placeholder of this text input component
* @param {string} placeholder Custom placeholder text to display when no text is entered
* @returns {TextInputComponent}
*/
setPlaceholder(placeholder) {
this.placeholder = Util.verifyString(placeholder, RangeError, 'TEXT_INPUT_PLACEHOLDER');
return this;
}
/**
* Sets the style of this text input component
* @param {TextInputStyleResolvable} style The style of this text input component
* @returns {TextInputComponent}
*/
setStyle(style) {
this.style = TextInputComponent.resolveStyle(style);
return this;
}
/** /**
* Sets the value of this text input component * Sets the value of this text input component
* @param {string} value Value of this text input component * @param {string} value Value of this text input component