mirror of
https://github.com/discordjs/discord.js.git
synced 2026-06-02 17:10:08 +00:00
* feat: handle file upload component * chore: fix import * chore: typings * fix: `Snowflake` --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
233 lines
7.9 KiB
JavaScript
233 lines
7.9 KiB
JavaScript
'use strict';
|
|
|
|
const { Collection } = require('@discordjs/collection');
|
|
const { ComponentType } = require('discord-api-types/v10');
|
|
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
|
|
|
|
/**
|
|
* @typedef {Object} ModalSelectedMentionables
|
|
* @property {Collection<Snowflake, User>} users The selected users
|
|
* @property {Collection<Snowflake, GuildMember | APIGuildMember>} members The selected members
|
|
* @property {Collection<Snowflake, Role | APIRole>} roles The selected roles
|
|
*/
|
|
|
|
/**
|
|
* Represents the serialized fields from a modal submit interaction
|
|
*/
|
|
class ModalSubmitFields {
|
|
constructor(components, resolved) {
|
|
/**
|
|
* The components within the modal
|
|
*
|
|
* @type {Array<ActionRowModalData|LabelModalData|TextDisplayModalData>}
|
|
*/
|
|
this.components = components;
|
|
|
|
/**
|
|
* The interaction resolved data
|
|
*
|
|
* @name ModalSubmitFields#resolved
|
|
* @type {?Readonly<BaseInteractionResolvedData>}
|
|
*/
|
|
Object.defineProperty(this, 'resolved', { value: resolved ? Object.freeze(resolved) : null });
|
|
|
|
/**
|
|
* The extracted fields from the modal
|
|
* @type {Collection<string, ModalData>}
|
|
*/
|
|
this.fields = components.reduce((accumulator, next) => {
|
|
// For legacy support of action rows
|
|
if ('components' in next) {
|
|
for (const component of next.components) accumulator.set(component.customId, component);
|
|
}
|
|
|
|
// For label components
|
|
if ('component' in next) {
|
|
accumulator.set(next.component.customId, next.component);
|
|
}
|
|
|
|
return accumulator;
|
|
}, new Collection());
|
|
}
|
|
|
|
/**
|
|
* Gets a field given a custom id from a component
|
|
* @param {string} customId The custom id of the component
|
|
* @param {ComponentType} [type] The type of the component
|
|
* @returns {ModalData}
|
|
*/
|
|
getField(customId, type) {
|
|
const field = this.fields.get(customId);
|
|
if (!field) throw new DiscordjsTypeError(ErrorCodes.ModalSubmitInteractionFieldNotFound, customId);
|
|
|
|
if (type !== undefined && type !== field.type) {
|
|
throw new DiscordjsTypeError(ErrorCodes.ModalSubmitInteractionFieldType, customId, field.type, type);
|
|
}
|
|
|
|
return field;
|
|
}
|
|
|
|
/**
|
|
* Gets a component by custom id and property and checks its type.
|
|
*
|
|
* @param {string} customId The custom id of the component.
|
|
* @param {ComponentType[]} allowedTypes The allowed types of the component.
|
|
* @param {string[]} properties The properties to check for for `required`.
|
|
* @param {boolean} required Whether to throw an error if the component value(s) are not found.
|
|
* @returns {ModalData} The option, if found.
|
|
* @private
|
|
*/
|
|
_getTypedComponent(customId, allowedTypes, properties, required) {
|
|
const component = this.getField(customId);
|
|
if (!allowedTypes.includes(component.type)) {
|
|
throw new DiscordjsTypeError(
|
|
ErrorCodes.ModalSubmitInteractionFieldNotFound,
|
|
customId,
|
|
component.type,
|
|
allowedTypes.join(', '),
|
|
);
|
|
} else if (required && properties.every(prop => component[prop] === null || component[prop] === undefined)) {
|
|
throw new DiscordjsTypeError(ErrorCodes.ModalSubmitInteractionFieldEmpty, customId, component.type);
|
|
}
|
|
|
|
return component;
|
|
}
|
|
|
|
/**
|
|
* Gets the value of a text input component given a custom id
|
|
* @param {string} customId The custom id of the text input component
|
|
* @returns {string}
|
|
*/
|
|
getTextInputValue(customId) {
|
|
return this._getTypedComponent(customId, [ComponentType.TextInput]).value;
|
|
}
|
|
|
|
/**
|
|
* Gets the values of a string select component given a custom id
|
|
*
|
|
* @param {string} customId The custom id of the string select component
|
|
* @returns {string[]}
|
|
*/
|
|
getStringSelectValues(customId) {
|
|
return this._getTypedComponent(customId, [ComponentType.StringSelect]).values;
|
|
}
|
|
|
|
/**
|
|
* Gets users component
|
|
*
|
|
* @param {string} customId The custom id of the component
|
|
* @param {boolean} [required=false] Whether to throw an error if the component value is not found or empty
|
|
* @returns {?Collection<Snowflake, User>} The selected users, or null if none were selected and not required
|
|
*/
|
|
getSelectedUsers(customId, required = false) {
|
|
const component = this._getTypedComponent(
|
|
customId,
|
|
[ComponentType.UserSelect, ComponentType.MentionableSelect],
|
|
['users'],
|
|
required,
|
|
);
|
|
return component.users ?? null;
|
|
}
|
|
|
|
/**
|
|
* Gets roles component
|
|
*
|
|
* @param {string} customId The custom id of the component
|
|
* @param {boolean} [required=false] Whether to throw an error if the component value is not found or empty
|
|
* @returns {?Collection<Snowflake, Role|APIRole>} The selected roles, or null if none were selected and not required
|
|
*/
|
|
getSelectedRoles(customId, required = false) {
|
|
const component = this._getTypedComponent(
|
|
customId,
|
|
[ComponentType.RoleSelect, ComponentType.MentionableSelect],
|
|
['roles'],
|
|
required,
|
|
);
|
|
return component.roles ?? null;
|
|
}
|
|
|
|
/**
|
|
* Gets channels component
|
|
*
|
|
* @param {string} customId The custom id of the component
|
|
* @param {boolean} [required=false] Whether to throw an error if the component value is not found or empty
|
|
* @param {ChannelType[]} [channelTypes=[]] The allowed types of channels. If empty, all channel types are allowed.
|
|
* @returns {?Collection<Snowflake, GuildChannel|ThreadChannel|APIChannel>} The selected channels,
|
|
* or null if none were selected and not required
|
|
*/
|
|
getSelectedChannels(customId, required = false, channelTypes = []) {
|
|
const component = this._getTypedComponent(customId, [ComponentType.ChannelSelect], ['channels'], required);
|
|
const channels = component.channels;
|
|
if (channels && channelTypes.length > 0) {
|
|
for (const channel of channels.values()) {
|
|
if (!channelTypes.includes(channel.type)) {
|
|
throw new DiscordjsTypeError(
|
|
ErrorCodes.ModalSubmitInteractionComponentInvalidChannelType,
|
|
customId,
|
|
channel.type,
|
|
channelTypes.join(', '),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return channels ?? null;
|
|
}
|
|
|
|
/**
|
|
* Gets members component
|
|
*
|
|
* @param {string} customId The custom id of the component
|
|
* @returns {?Collection<Snowflake, GuildMember|APIGuildMember>} The selected members,
|
|
* or null if none were selected or the users were not present in the guild
|
|
*/
|
|
getSelectedMembers(customId) {
|
|
const component = this._getTypedComponent(
|
|
customId,
|
|
[ComponentType.UserSelect, ComponentType.MentionableSelect],
|
|
['members'],
|
|
false,
|
|
);
|
|
return component.members ?? null;
|
|
}
|
|
|
|
/**
|
|
* Gets mentionables component
|
|
*
|
|
* @param {string} customId The custom id of the component
|
|
* @param {boolean} [required=false] Whether to throw an error if the component value is not found or empty
|
|
* @returns {?ModalSelectedMentionables} The selected mentionables, or null if none were selected and not required
|
|
*/
|
|
getSelectedMentionables(customId, required = false) {
|
|
const component = this._getTypedComponent(
|
|
customId,
|
|
[ComponentType.MentionableSelect],
|
|
['users', 'members', 'roles'],
|
|
required,
|
|
);
|
|
|
|
if (component.users || component.members || component.roles) {
|
|
return {
|
|
users: component.users ?? new Collection(),
|
|
members: component.members ?? new Collection(),
|
|
roles: component.roles ?? new Collection(),
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets file upload component
|
|
*
|
|
* @param {string} customId The custom id of the component
|
|
* @param {boolean} [required=false] Whether to throw an error if the component value is not found or empty
|
|
* @returns {?Collection<Snowflake, Attachment>} The uploaded files, or null if none were uploaded and not required
|
|
*/
|
|
getUploadedFiles(customId, required = false) {
|
|
return this._getTypedComponent(customId, [ComponentType.FileUpload], ['attachments'], required).attachments ?? null;
|
|
}
|
|
}
|
|
|
|
module.exports = ModalSubmitFields;
|