2022-05-14 08:06:15 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const BaseMessageComponent = require('./BaseMessageComponent');
|
2022-06-13 16:53:43 +00:00
|
|
|
const User = require('./User');
|
|
|
|
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
2022-05-14 08:06:15 +00:00
|
|
|
const Util = require('../util/Util');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a modal (form) to be shown in response to an interaction
|
|
|
|
*/
|
|
|
|
class Modal {
|
|
|
|
/**
|
|
|
|
* @typedef {Object} ModalOptions
|
|
|
|
* @property {string} [customId] A unique string to be sent in the interaction when clicked
|
|
|
|
* @property {string} [title] The title to be displayed on this modal
|
|
|
|
* @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
|
|
|
|
* Action rows containing interactive components for the modal (text input components)
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Modal|ModalOptions} data Modal to clone or raw data
|
|
|
|
* @param {Client} client The client constructing this Modal, if provided
|
|
|
|
*/
|
|
|
|
constructor(data = {}, client = null) {
|
|
|
|
/**
|
|
|
|
* A list of MessageActionRows in the modal
|
|
|
|
* @type {MessageActionRow[]}
|
|
|
|
*/
|
|
|
|
this.components = data.components?.map(c => BaseMessageComponent.create(c, client)) ?? [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A unique string to be sent in the interaction when submitted
|
|
|
|
* @type {?string}
|
|
|
|
*/
|
|
|
|
this.customId = data.custom_id ?? data.customId ?? null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The title to be displayed on this modal
|
|
|
|
* @type {?string}
|
|
|
|
*/
|
|
|
|
this.title = data.title ?? null;
|
2022-06-13 16:53:43 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Timestamp (Discord epoch) of when this modal was created
|
|
|
|
* @type {?Snowflake}
|
|
|
|
*/
|
|
|
|
this.nonce = data.nonce ?? null;
|
|
|
|
|
|
|
|
/**
|
2022-09-28 15:03:20 +00:00
|
|
|
* ID slash / button / menu when modal is displayed
|
2022-06-13 16:53:43 +00:00
|
|
|
* @type {?Snowflake}
|
|
|
|
*/
|
|
|
|
this.id = data.id ?? null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Application sending the modal
|
|
|
|
* @type {?Object}
|
|
|
|
*/
|
|
|
|
this.application = data.application
|
|
|
|
? {
|
|
|
|
...data.application,
|
2022-10-09 12:37:04 +00:00
|
|
|
bot: data.application.bot ? new User(client, data.application.bot, data.application) : null,
|
2022-06-13 16:53:43 +00:00
|
|
|
}
|
|
|
|
: null;
|
|
|
|
|
|
|
|
this.client = client;
|
2022-05-14 08:06:15 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 12:37:04 +00:00
|
|
|
/**
|
|
|
|
* Get Interaction Response
|
|
|
|
* @type {?InteractionResponse}
|
|
|
|
* @readonly
|
|
|
|
*/
|
|
|
|
get sendFromInteraction() {
|
|
|
|
if (this.id && this.nonce && this.client) {
|
|
|
|
const cache = this.client._interactionCache.get(this.nonce);
|
|
|
|
const channel = cache.guildId
|
|
|
|
? this.client.guilds.cache.get(cache.guildId)?.channels.cache.get(cache.channelId)
|
|
|
|
: this.client.channels.cache.get(cache.channelId);
|
|
|
|
return channel.interactions.cache.get(this.id);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-05-14 08:06:15 +00:00
|
|
|
/**
|
|
|
|
* Adds components to the modal.
|
|
|
|
* @param {...MessageActionRowResolvable[]} components The components to add
|
|
|
|
* @returns {Modal}
|
|
|
|
*/
|
|
|
|
addComponents(...components) {
|
|
|
|
this.components.push(...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the components of the modal.
|
|
|
|
* @param {...MessageActionRowResolvable[]} components The components to set
|
|
|
|
* @returns {Modal}
|
|
|
|
*/
|
|
|
|
setComponents(...components) {
|
|
|
|
this.spliceComponents(0, this.components.length, components);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the custom id for this modal
|
|
|
|
* @param {string} customId A unique string to be sent in the interaction when submitted
|
|
|
|
* @returns {Modal}
|
|
|
|
*/
|
|
|
|
setCustomId(customId) {
|
|
|
|
this.customId = Util.verifyString(customId, RangeError, 'MODAL_CUSTOM_ID');
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes, replaces, and inserts components in the modal.
|
|
|
|
* @param {number} index The index to start at
|
|
|
|
* @param {number} deleteCount The number of components to remove
|
|
|
|
* @param {...MessageActionRowResolvable[]} [components] The replacing components
|
|
|
|
* @returns {Modal}
|
|
|
|
*/
|
|
|
|
spliceComponents(index, deleteCount, ...components) {
|
|
|
|
this.components.splice(index, deleteCount, ...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the title of this modal
|
|
|
|
* @param {string} title The title to be displayed on this modal
|
|
|
|
* @returns {Modal}
|
|
|
|
*/
|
|
|
|
setTitle(title) {
|
|
|
|
this.title = Util.verifyString(title, RangeError, 'MODAL_TITLE');
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
toJSON() {
|
|
|
|
return {
|
|
|
|
components: this.components.map(c => c.toJSON()),
|
|
|
|
custom_id: this.customId,
|
|
|
|
title: this.title,
|
2022-06-13 16:53:43 +00:00
|
|
|
id: this.id,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-09-29 12:45:22 +00:00
|
|
|
* @typedef {Object} TextInputComponentReplyData
|
2022-06-13 16:53:43 +00:00
|
|
|
* @property {string} [customId] TextInputComponent custom id
|
|
|
|
* @property {string} [value] TextInputComponent value
|
|
|
|
*/
|
|
|
|
|
2022-09-29 12:45:22 +00:00
|
|
|
/**
|
|
|
|
* @typedef {Object} ModalReplyData
|
2022-10-09 12:37:04 +00:00
|
|
|
* @property {?GuildResolvable} [guild] Guild to send the modal to
|
|
|
|
* @property {?TextChannelResolvable} [channel] User to send the modal to
|
2022-09-29 12:45:22 +00:00
|
|
|
* @property {TextInputComponentReplyData[]} [data] Reply data
|
|
|
|
*/
|
|
|
|
|
2022-06-13 16:53:43 +00:00
|
|
|
/**
|
|
|
|
* Reply to this modal with data. (Event only)
|
2022-09-29 12:45:22 +00:00
|
|
|
* @param {ModalReplyData} data Data to send with the modal
|
2022-10-09 12:37:04 +00:00
|
|
|
* @returns {Promise<InteractionResponse>}
|
2022-06-13 17:03:51 +00:00
|
|
|
* @example
|
|
|
|
* client.on('interactionModalCreate', modal => {
|
2022-10-09 12:37:04 +00:00
|
|
|
* modal.reply({
|
|
|
|
* data: [
|
|
|
|
* {
|
|
|
|
* customId: 'code',
|
|
|
|
* value: '1+1'
|
|
|
|
* }, {
|
|
|
|
* customId: 'message',
|
|
|
|
* value: 'hello'
|
|
|
|
* }
|
2022-12-08 08:14:17 +00:00
|
|
|
* ],
|
|
|
|
* channel: 'id', // optional
|
|
|
|
* guild: 'id', // optional
|
2022-06-13 17:03:51 +00:00
|
|
|
* })
|
|
|
|
* })
|
2022-06-13 16:53:43 +00:00
|
|
|
*/
|
2022-09-29 12:45:22 +00:00
|
|
|
async reply(data) {
|
|
|
|
if (typeof data !== 'object') throw new TypeError('ModalReplyData must be an object');
|
|
|
|
if (!Array.isArray(data.data)) throw new TypeError('ModalReplyData.data must be an array');
|
2022-07-08 14:27:43 +00:00
|
|
|
if (!this.application) throw new Error('Modal cannot reply (Missing Application)');
|
2022-10-09 12:37:04 +00:00
|
|
|
const data_cache = this.sendFromInteraction;
|
|
|
|
const guild = this.client.guilds.resolveId(data.guild) || data_cache.guildId || null;
|
|
|
|
const channel = this.client.channels.resolveId(data.channel) || data_cache.channelId;
|
2022-12-08 08:14:17 +00:00
|
|
|
if (!channel) throw new Error('Modal cannot reply (Missing data)');
|
2022-06-13 16:53:43 +00:00
|
|
|
// Add data to components
|
|
|
|
// this.components = [ MessageActionRow.components = [ TextInputComponent ] ]
|
|
|
|
// 5 MessageActionRow / Modal, 1 TextInputComponent / 1 MessageActionRow
|
|
|
|
for (let i = 0; i < this.components.length; i++) {
|
2022-09-29 12:45:22 +00:00
|
|
|
const value = data.data.find(d => d.customId == this.components[i].components[0].customId);
|
2022-12-08 08:14:17 +00:00
|
|
|
if (this.components[i].components[0].required == true && !value) {
|
|
|
|
throw new Error(
|
|
|
|
'MODAL_REQUIRED_FIELD_MISSING\n' +
|
|
|
|
`Required fieldId ${this.components[i].components[0].customId} missing value`,
|
|
|
|
);
|
2022-06-13 16:53:43 +00:00
|
|
|
}
|
|
|
|
if (value) {
|
2022-12-08 08:14:17 +00:00
|
|
|
if (value?.value?.includes('\n') && this.components[i].components[0].style == 'SHORT') {
|
|
|
|
throw new Error(
|
|
|
|
'MODAL_REPLY_DATA_INVALID\n' +
|
|
|
|
`value must be a single line, got multiple lines [Custom ID: ${value.customId}]`,
|
2022-06-13 16:53:43 +00:00
|
|
|
);
|
|
|
|
}
|
2022-12-08 08:14:17 +00:00
|
|
|
this.components[i].components[0].setValue(value.value);
|
2022-06-13 16:53:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Get Object
|
|
|
|
const dataFinal = this.toJSON();
|
2022-12-08 08:14:17 +00:00
|
|
|
dataFinal.components = dataFinal.components
|
|
|
|
.map(c => {
|
|
|
|
delete c.components[0].max_length;
|
|
|
|
delete c.components[0].min_length;
|
|
|
|
delete c.components[0].required;
|
|
|
|
delete c.components[0].placeholder;
|
|
|
|
delete c.components[0].label;
|
|
|
|
delete c.components[0].style;
|
|
|
|
return c;
|
|
|
|
})
|
|
|
|
.filter(c => c.components[0].value && c.components[0].value !== '');
|
2022-06-13 16:53:43 +00:00
|
|
|
delete dataFinal.title;
|
2022-06-15 16:07:24 +00:00
|
|
|
const nonce = SnowflakeUtil.generate();
|
2022-06-13 16:53:43 +00:00
|
|
|
const postData = {
|
2022-06-15 11:52:06 +00:00
|
|
|
type: 5, // Modal
|
2022-06-13 16:53:43 +00:00
|
|
|
application_id: this.application.id,
|
2022-09-29 12:45:22 +00:00
|
|
|
guild_id: guild || null,
|
|
|
|
channel_id: channel,
|
2022-06-13 16:53:43 +00:00
|
|
|
data: dataFinal,
|
2022-06-15 16:07:24 +00:00
|
|
|
nonce,
|
2022-06-13 16:53:43 +00:00
|
|
|
session_id: this.client.session_id,
|
2022-05-14 08:06:15 +00:00
|
|
|
};
|
2022-06-13 16:53:43 +00:00
|
|
|
await this.client.api.interactions.post({
|
|
|
|
data: postData,
|
|
|
|
});
|
2022-09-29 12:45:22 +00:00
|
|
|
return {
|
|
|
|
nonce,
|
|
|
|
id: this.id,
|
|
|
|
};
|
2022-05-14 08:06:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Modal;
|