mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-19 21:13:30 +01:00
feat: Add Modals and Text Inputs (#7023)
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com> Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Ryan Munro <monbrey@gmail.com> Co-authored-by: Vitor <milagre.vitor@gmail.com>
This commit is contained in:
@@ -52,7 +52,7 @@
|
||||
"@discordjs/rest": "workspace:^",
|
||||
"@sapphire/snowflake": "^3.1.0",
|
||||
"@types/ws": "^8.2.2",
|
||||
"discord-api-types": "^0.27.0",
|
||||
"discord-api-types": "^0.27.3",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"undici": "^4.14.1",
|
||||
"ws": "^8.5.0"
|
||||
|
||||
@@ -6,6 +6,7 @@ const AutocompleteInteraction = require('../../structures/AutocompleteInteractio
|
||||
const ButtonInteraction = require('../../structures/ButtonInteraction');
|
||||
const ChatInputCommandInteraction = require('../../structures/ChatInputCommandInteraction');
|
||||
const MessageContextMenuCommandInteraction = require('../../structures/MessageContextMenuCommandInteraction');
|
||||
const ModalSubmitInteraction = require('../../structures/ModalSubmitInteraction');
|
||||
const SelectMenuInteraction = require('../../structures/SelectMenuInteraction');
|
||||
const UserContextMenuCommandInteraction = require('../../structures/UserContextMenuCommandInteraction');
|
||||
const Events = require('../../util/Events');
|
||||
@@ -57,6 +58,9 @@ class InteractionCreateAction extends Action {
|
||||
case InteractionType.ApplicationCommandAutocomplete:
|
||||
InteractionClass = AutocompleteInteraction;
|
||||
break;
|
||||
case InteractionType.ModalSubmit:
|
||||
InteractionClass = ModalSubmitInteraction;
|
||||
break;
|
||||
default:
|
||||
client.emit(Events.Debug, `[INTERACTION] Received interaction with unknown type: ${data.type}`);
|
||||
return;
|
||||
|
||||
@@ -141,6 +141,10 @@ const Messages = {
|
||||
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No subcommand group specified for interaction.',
|
||||
AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: 'No focused option for autocomplete interaction.',
|
||||
|
||||
MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`,
|
||||
MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) =>
|
||||
`Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`,
|
||||
|
||||
INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite',
|
||||
|
||||
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
|
||||
|
||||
@@ -124,6 +124,9 @@ exports.MessageContextMenuCommandInteraction = require('./structures/MessageCont
|
||||
exports.MessageMentions = require('./structures/MessageMentions');
|
||||
exports.MessagePayload = require('./structures/MessagePayload');
|
||||
exports.MessageReaction = require('./structures/MessageReaction');
|
||||
exports.Modal = require('./structures/Modal');
|
||||
exports.ModalSubmitInteraction = require('./structures/ModalSubmitInteraction');
|
||||
exports.ModalSubmitFieldsResolver = require('./structures/ModalSubmitFieldsResolver');
|
||||
exports.NewsChannel = require('./structures/NewsChannel');
|
||||
exports.OAuth2Guild = require('./structures/OAuth2Guild');
|
||||
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
|
||||
@@ -143,6 +146,7 @@ exports.StoreChannel = require('./structures/StoreChannel');
|
||||
exports.Team = require('./structures/Team');
|
||||
exports.TeamMember = require('./structures/TeamMember');
|
||||
exports.TextChannel = require('./structures/TextChannel');
|
||||
exports.TextInputComponent = require('./structures/TextInputComponent');
|
||||
exports.ThreadChannel = require('./structures/ThreadChannel');
|
||||
exports.ThreadMember = require('./structures/ThreadMember');
|
||||
exports.Typing = require('./structures/Typing');
|
||||
@@ -193,6 +197,7 @@ exports.RESTJSONErrorCodes = require('discord-api-types/v9').RESTJSONErrorCodes;
|
||||
exports.StageInstancePrivacyLevel = require('discord-api-types/v9').StageInstancePrivacyLevel;
|
||||
exports.StickerType = require('discord-api-types/v9').StickerType;
|
||||
exports.StickerFormatType = require('discord-api-types/v9').StickerFormatType;
|
||||
exports.TextInputStyle = require('discord-api-types/v9').TextInputStyle;
|
||||
exports.UserFlags = require('discord-api-types/v9').UserFlags;
|
||||
exports.WebhookType = require('discord-api-types/v9').WebhookType;
|
||||
exports.UnsafeButtonComponent = require('@discordjs/builders').UnsafeButtonComponent;
|
||||
|
||||
@@ -203,6 +203,7 @@ class CommandInteraction extends Interaction {
|
||||
editReply() {}
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
showModal() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(CommandInteraction, ['deferUpdate', 'update']);
|
||||
|
||||
@@ -191,6 +191,14 @@ class Interaction extends Base {
|
||||
return this.isContextMenuCommand() && this.commandType === ApplicationCommandType.Message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link ModalSubmitInteraction}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isModalSubmit() {
|
||||
return this.type === InteractionType.ModalSubmit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is an {@link AutocompleteInteraction}
|
||||
* @returns {boolean}
|
||||
|
||||
@@ -90,6 +90,7 @@ class MessageComponentInteraction extends Interaction {
|
||||
followUp() {}
|
||||
deferUpdate() {}
|
||||
update() {}
|
||||
showModal() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(MessageComponentInteraction);
|
||||
|
||||
12
packages/discord.js/src/structures/Modal.js
Normal file
12
packages/discord.js/src/structures/Modal.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { Modal: BuildersModal } = require('@discordjs/builders');
|
||||
const Transformers = require('../util/Transformers');
|
||||
|
||||
class Modal extends BuildersModal {
|
||||
constructor(data) {
|
||||
super(Transformers.toSnakeCase(data));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Modal;
|
||||
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { ComponentType } = require('discord-api-types/v9');
|
||||
const { TypeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* Represents the serialized fields from a modal submit interaction
|
||||
*/
|
||||
class ModalSubmitFieldsResolver {
|
||||
constructor(components) {
|
||||
/**
|
||||
* The components within the modal
|
||||
* @type {Array<ActionRow<ModalFieldData>>} The components in the modal
|
||||
*/
|
||||
this.components = components;
|
||||
|
||||
/**
|
||||
* The extracted fields from the modal
|
||||
* @type {Collection<string, ModalFieldData>} The fields in the modal
|
||||
*/
|
||||
this.fields = components.reduce((accumulator, next) => {
|
||||
next.components.forEach(c => accumulator.set(c.customId, c));
|
||||
return accumulator;
|
||||
}, new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a field given a custom id from a component
|
||||
* @param {string} customId The custom id of the component
|
||||
* @returns {ModalFieldData}
|
||||
*/
|
||||
getField(customId) {
|
||||
const field = this.fields.get(customId);
|
||||
if (!field) throw new TypeError('MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND', customId);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const field = this.getField(customId);
|
||||
const expectedType = ComponentType.TextInput;
|
||||
if (field.type !== expectedType) {
|
||||
throw new TypeError('MODAL_SUBMIT_INTERACTION_FIELD_TYPE', customId, field.type, expectedType);
|
||||
}
|
||||
return field.value;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModalSubmitFieldsResolver;
|
||||
93
packages/discord.js/src/structures/ModalSubmitInteraction.js
Normal file
93
packages/discord.js/src/structures/ModalSubmitInteraction.js
Normal file
@@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
const { createComponent } = require('@discordjs/builders');
|
||||
const Interaction = require('./Interaction');
|
||||
const InteractionWebhook = require('./InteractionWebhook');
|
||||
const ModalSubmitFieldsResolver = require('./ModalSubmitFieldsResolver');
|
||||
const InteractionResponses = require('./interfaces/InteractionResponses');
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalFieldData
|
||||
* @property {string} value The value of the field
|
||||
* @property {ComponentType} type The component type of the field
|
||||
* @property {string} customId The custom id of the field
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a modal interaction
|
||||
* @implements {InteractionResponses}
|
||||
*/
|
||||
class ModalSubmitInteraction extends Interaction {
|
||||
constructor(client, data) {
|
||||
super(client, data);
|
||||
/**
|
||||
* The custom id of the modal.
|
||||
* @type {string}
|
||||
*/
|
||||
this.customId = data.data.custom_id;
|
||||
|
||||
if ('message' in data) {
|
||||
/**
|
||||
* The message associated with this interaction
|
||||
* @type {?(Message|APIMessage)}
|
||||
*/
|
||||
this.message = this.channel?.messages._add(data.message) ?? data.message;
|
||||
} else {
|
||||
this.message = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The components within the modal
|
||||
* @type {ActionRow[]}
|
||||
*/
|
||||
this.components = data.data.components?.map(c => createComponent(c)) ?? [];
|
||||
|
||||
/**
|
||||
* The fields within the modal
|
||||
* @type {ModalSubmitFieldsResolver}
|
||||
*/
|
||||
this.fields = new ModalSubmitFieldsResolver(this.components);
|
||||
|
||||
/**
|
||||
* An associated interaction webhook, can be used to further interact with this interaction
|
||||
* @type {InteractionWebhook}
|
||||
*/
|
||||
this.webhook = new InteractionWebhook(this.client, this.applicationId, this.token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms component data to discord.js-compatible data
|
||||
* @param {*} rawComponent The data to transform
|
||||
* @returns {ModalFieldData[]}
|
||||
*/
|
||||
static transformComponent(rawComponent) {
|
||||
return {
|
||||
value: rawComponent.value,
|
||||
type: rawComponent.type,
|
||||
customId: rawComponent.custom_id,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is from a {@link MessageComponentInteraction}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFromMessage() {
|
||||
return Boolean(this.message);
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by InteractionResponses
|
||||
/* eslint-disable no-empty-function */
|
||||
deferReply() {}
|
||||
reply() {}
|
||||
fetchReply() {}
|
||||
editReply() {}
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
deferUpdate() {}
|
||||
update() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(ModalSubmitInteraction, 'showModal');
|
||||
|
||||
module.exports = ModalSubmitInteraction;
|
||||
12
packages/discord.js/src/structures/TextInputComponent.js
Normal file
12
packages/discord.js/src/structures/TextInputComponent.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { TextInputComponent: BuildersTextInputComponent } = require('@discordjs/builders');
|
||||
const Transformers = require('../util/Transformers');
|
||||
|
||||
class TextInputComponent extends BuildersTextInputComponent {
|
||||
constructor(data) {
|
||||
super(Transformers.toSnakeCase(data));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextInputComponent;
|
||||
@@ -1,9 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
const { isJSONEncodable } = require('@discordjs/builders');
|
||||
const { InteractionResponseType, MessageFlags, Routes } = require('discord-api-types/v9');
|
||||
const { Error } = require('../../errors');
|
||||
const Transformers = require('../../util/Transformers');
|
||||
const MessagePayload = require('../MessagePayload');
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalData
|
||||
* @property {string} title The title of the modal
|
||||
* @property {string} customId The custom id of the modal
|
||||
* @property {ActionRowData[]} components The components within this modal
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes that support shared interaction response types.
|
||||
* @interface
|
||||
@@ -225,6 +234,21 @@ class InteractionResponses {
|
||||
return options.fetchReply ? this.fetchReply() : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a modal component
|
||||
* @param {APIModal|ModalData|Modal} modal The modal to show
|
||||
*/
|
||||
async showModal(modal) {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
|
||||
body: {
|
||||
type: InteractionResponseType.Modal,
|
||||
data: isJSONEncodable(modal) ? modal.toJSON() : Transformers.toSnakeCase(modal),
|
||||
},
|
||||
});
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
static applyToClass(structure, ignore = []) {
|
||||
const props = [
|
||||
'deferReply',
|
||||
@@ -235,6 +259,7 @@ class InteractionResponses {
|
||||
'followUp',
|
||||
'deferUpdate',
|
||||
'update',
|
||||
'showModal',
|
||||
];
|
||||
|
||||
for (const prop of props) {
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
// This file contains the typedefs for camel-cased json data
|
||||
|
||||
/**
|
||||
* @typedef {Object} BaseComponentData
|
||||
* @property {ComponentType} type
|
||||
* @property {ComponentType} type The type of component
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} ActionRowData
|
||||
* @property {ComponentData[]} components
|
||||
* @property {ComponentData[]} components The components in this action row
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} ButtonComponentData
|
||||
* @property {ButtonStyle} style
|
||||
* @property {?boolean} disabled
|
||||
* @property {string} label
|
||||
* @property {?APIComponentEmoji} emoji
|
||||
* @property {?string} customId
|
||||
* @property {?string} url
|
||||
* @property {ButtonStyle} style The style of the button
|
||||
* @property {?boolean} disabled Whether this button is disabled
|
||||
* @property {string} label The label of this button
|
||||
* @property {?APIComponentEmoji} emoji The emoji on this button
|
||||
* @property {?string} customId The custom id of the button
|
||||
* @property {?string} url The URL of the button
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} SelectMenuComponentOptionData
|
||||
* @property {string} label
|
||||
* @property {string} value
|
||||
* @property {?string} description
|
||||
* @property {?APIComponentEmoji} emoji
|
||||
* @property {?boolean} default
|
||||
* @property {string} label The label of the option
|
||||
* @property {string} value The value of the option
|
||||
* @property {?string} description The description of the option
|
||||
* @property {?APIComponentEmoji} emoji The emoji on the option
|
||||
* @property {?boolean} default Whether this option is selected by default
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} SelectMenuComponentData
|
||||
* @property {string} customId
|
||||
* @property {?boolean} disabled
|
||||
* @property {?number} maxValues
|
||||
* @property {?number} minValues
|
||||
* @property {?SelectMenuComponentOptionData[]} options
|
||||
* @property {?string} placeholder
|
||||
* @property {string} customId The custom id of the select menu
|
||||
* @property {?boolean} disabled Whether the select menu is disabled or not
|
||||
* @property {?number} maxValues The maximum amount of options that can be selected
|
||||
* @property {?number} minValues The minimum amount of options that can be selected
|
||||
* @property {?SelectMenuComponentOptionData[]} options The options in this select menu
|
||||
* @property {?string} placeholder The placeholder of the select menu
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} ComponentData
|
||||
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} MessageComponentData
|
||||
/
|
||||
|
||||
/**
|
||||
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData|TextInputComponentData} ComponentData
|
||||
*/
|
||||
|
||||
@@ -2,47 +2,47 @@
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedData
|
||||
* @property {?string} title
|
||||
* @property {?EmbedType} type
|
||||
* @property {?string} description
|
||||
* @property {?string} url
|
||||
* @property {?string} timestamp
|
||||
* @property {?number} color
|
||||
* @property {?EmbedFooterData} footer
|
||||
* @property {?EmbedImageData} image
|
||||
* @property {?EmbedImageData} thumbnail
|
||||
* @property {?EmbedProviderData} provider
|
||||
* @property {?EmbedAuthorData} author
|
||||
* @property {?EmbedFieldData[]} fields
|
||||
* @property {?string} title The title of the embed
|
||||
* @property {?EmbedType} type The type of the embed
|
||||
* @property {?string} description The description of the embed
|
||||
* @property {?string} url The URL of the embed
|
||||
* @property {?string} timestamp The timestamp on the embed
|
||||
* @property {?number} color The color of the embed
|
||||
* @property {?EmbedFooterData} footer The footer of the embed
|
||||
* @property {?EmbedImageData} image The image of the embed
|
||||
* @property {?EmbedImageData} thumbnail The thumbnail of the embed
|
||||
* @property {?EmbedProviderData} provider The provider of the embed
|
||||
* @property {?EmbedAuthorData} author The author in the embed
|
||||
* @property {?EmbedFieldData[]} fields The fields in this embed
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedFooterData
|
||||
* @property {string} text
|
||||
* @property {?string} iconURL
|
||||
* @property {string} text The text of the footer
|
||||
* @property {?string} iconURL The URL of the icon
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedImageData
|
||||
* @property {?string} url
|
||||
* @property {?string} url The URL of the image
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedProviderData
|
||||
* @property {?string} name
|
||||
* @property {?string} url
|
||||
* @property {?string} name The name of the provider
|
||||
* @property {?string} url The URL of the provider
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedAuthorData
|
||||
* @property {string} name
|
||||
* @property {?string} url
|
||||
* @property {?string} iconURL
|
||||
* @property {string} name The name of the author
|
||||
* @property {?string} url The URL of the author
|
||||
* @property {?string} iconURL The icon URL of the author
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedFieldData
|
||||
* @property {string} name
|
||||
* @property {string} value
|
||||
* @property {?boolean} inline
|
||||
* @property {string} name The name of the field
|
||||
* @property {string} value The value of the field
|
||||
* @property {?boolean} inline Whether to inline this field
|
||||
*/
|
||||
|
||||
165
packages/discord.js/typings/index.d.ts
vendored
165
packages/discord.js/typings/index.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
ActionRow as BuilderActionRow,
|
||||
ActionRowComponent,
|
||||
MessageActionRowComponent,
|
||||
blockQuote,
|
||||
bold,
|
||||
ButtonComponent as BuilderButtonComponent,
|
||||
@@ -14,9 +14,11 @@ import {
|
||||
inlineCode,
|
||||
italic,
|
||||
memberNicknameMention,
|
||||
Modal as BuilderModal,
|
||||
quote,
|
||||
roleMention,
|
||||
SelectMenuComponent as BuilderSelectMenuComponent,
|
||||
TextInputComponent as BuilderTextInputComponent,
|
||||
spoiler,
|
||||
strikethrough,
|
||||
time,
|
||||
@@ -24,6 +26,7 @@ import {
|
||||
TimestampStylesString,
|
||||
underscore,
|
||||
userMention,
|
||||
ModalActionRowComponent,
|
||||
} from '@discordjs/builders';
|
||||
import { Collection } from '@discordjs/collection';
|
||||
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
|
||||
@@ -95,6 +98,13 @@ import {
|
||||
APIMessageComponentEmoji,
|
||||
EmbedType,
|
||||
APIActionRowComponentTypes,
|
||||
APIModalInteractionResponseCallbackData,
|
||||
APIModalSubmitInteraction,
|
||||
APIMessageActionRowComponent,
|
||||
TextInputStyle,
|
||||
APITextInputComponent,
|
||||
APIModalActionRowComponent,
|
||||
APIModalComponent,
|
||||
} from 'discord-api-types/v9';
|
||||
import { ChildProcess } from 'node:child_process';
|
||||
import { EventEmitter } from 'node:events';
|
||||
@@ -198,17 +208,23 @@ export interface BaseComponentData {
|
||||
type?: ComponentType;
|
||||
}
|
||||
|
||||
export type ActionRowComponentData = ButtonComponentData | SelectMenuComponentData;
|
||||
export type MessageActionRowComponentData = ButtonComponentData | SelectMenuComponentData;
|
||||
export type ModalActionRowComponentData = TextInputComponentData;
|
||||
|
||||
export interface ActionRowData extends BaseComponentData {
|
||||
components: ActionRowComponentData[];
|
||||
export interface ActionRowData<T extends MessageActionRowComponentData | ModalActionRowComponentData>
|
||||
extends BaseComponentData {
|
||||
components: T[];
|
||||
}
|
||||
|
||||
export class ActionRow<T extends ActionRowComponent = ActionRowComponent> extends BuilderActionRow<T> {
|
||||
export class ActionRow<
|
||||
T extends MessageActionRowComponent | ModalActionRowComponent = MessageActionRowComponent,
|
||||
> extends BuilderActionRow<T> {
|
||||
constructor(
|
||||
data?:
|
||||
| ActionRowData
|
||||
| (Omit<APIActionRowComponent<APIMessageComponent>, 'type'> & { type?: ComponentType.ActionRow }),
|
||||
| ActionRowData<MessageActionRowComponentData | ModalActionRowComponentData>
|
||||
| (Omit<APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>, 'type'> & {
|
||||
type?: ComponentType.ActionRow;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -336,6 +352,7 @@ export interface InteractionResponseFields<Cached extends CacheType = CacheType>
|
||||
deferReply(options?: InteractionDeferReplyOptions): Promise<void>;
|
||||
fetchReply(): Promise<GuildCacheMessage<Cached>>;
|
||||
followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
|
||||
showModal(modal: Modal): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class CommandInteraction<Cached extends CacheType = CacheType> extends Interaction<Cached> {
|
||||
@@ -374,6 +391,7 @@ export abstract class CommandInteraction<Cached extends CacheType = CacheType> e
|
||||
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
|
||||
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
|
||||
public showModal(modal: Modal): Promise<void>;
|
||||
private transformOption(
|
||||
option: APIApplicationCommandOption,
|
||||
resolved: APIApplicationCommandInteractionData['resolved'],
|
||||
@@ -490,6 +508,14 @@ export class SelectMenuComponent extends BuilderSelectMenuComponent {
|
||||
);
|
||||
}
|
||||
|
||||
export class TextInputComponent extends BuilderTextInputComponent {
|
||||
public constructor(data?: TextInputComponentData | APITextInputComponent);
|
||||
}
|
||||
|
||||
export class Modal extends BuilderModal {
|
||||
public constructor(data?: ModalData | APIModalActionRowComponent);
|
||||
}
|
||||
|
||||
export interface EmbedData {
|
||||
title?: string;
|
||||
type?: EmbedType;
|
||||
@@ -1355,6 +1381,7 @@ export class Interaction<Cached extends CacheType = CacheType> extends Base {
|
||||
public isMessageComponent(): this is MessageComponentInteraction<Cached>;
|
||||
public isSelectMenu(): this is SelectMenuInteraction<Cached>;
|
||||
public isRepliable(): this is this & InteractionResponseFields<Cached>;
|
||||
public isModalSubmit(): this is ModalSubmitInteraction<Cached>;
|
||||
}
|
||||
|
||||
export class InteractionCollector<T extends Interaction> extends Collector<Snowflake, T> {
|
||||
@@ -1447,17 +1474,19 @@ export class LimitedCollection<K, V> extends Collection<K, V> {
|
||||
public keepOverLimit: ((value: V, key: K, collection: this) => boolean) | null;
|
||||
}
|
||||
|
||||
export type MessageCollectorOptionsParams<T extends ComponentType, Cached extends boolean = boolean> =
|
||||
export type MessageComponentType = Exclude<ComponentType, ComponentType.TextInput>;
|
||||
|
||||
export type MessageCollectorOptionsParams<T extends MessageComponentType, Cached extends boolean = boolean> =
|
||||
| {
|
||||
componentType?: T;
|
||||
} & MessageComponentCollectorOptions<MappedInteractionTypes<Cached>[T]>;
|
||||
|
||||
export type MessageChannelCollectorOptionsParams<T extends ComponentType, Cached extends boolean = boolean> =
|
||||
export type MessageChannelCollectorOptionsParams<T extends MessageComponentType, Cached extends boolean = boolean> =
|
||||
| {
|
||||
componentType?: T;
|
||||
} & MessageChannelComponentCollectorOptions<MappedInteractionTypes<Cached>[T]>;
|
||||
|
||||
export type AwaitMessageCollectorOptionsParams<T extends ComponentType, Cached extends boolean = boolean> =
|
||||
export type AwaitMessageCollectorOptionsParams<T extends MessageComponentType, Cached extends boolean = boolean> =
|
||||
| { componentType?: T } & Pick<
|
||||
InteractionCollectorOptions<MappedInteractionTypes<Cached>[T]>,
|
||||
keyof AwaitMessageComponentOptions<any>
|
||||
@@ -1490,7 +1519,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
||||
public get channel(): If<Cached, GuildTextBasedChannel, TextBasedChannel>;
|
||||
public channelId: Snowflake;
|
||||
public get cleanContent(): string;
|
||||
public components: ActionRow<ActionRowComponent>[];
|
||||
public components: ActionRow<MessageActionRowComponent>[];
|
||||
public content: string;
|
||||
public get createdAt(): Date;
|
||||
public createdTimestamp: number;
|
||||
@@ -1522,12 +1551,12 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
||||
public webhookId: Snowflake | null;
|
||||
public flags: Readonly<MessageFlagsBitField>;
|
||||
public reference: MessageReference | null;
|
||||
public awaitMessageComponent<T extends ComponentType = ComponentType.ActionRow>(
|
||||
public awaitMessageComponent<T extends MessageComponentType = ComponentType.ActionRow>(
|
||||
options?: AwaitMessageCollectorOptionsParams<T, Cached>,
|
||||
): Promise<MappedInteractionTypes<Cached>[T]>;
|
||||
public awaitReactions(options?: AwaitReactionsOptions): Promise<Collection<Snowflake | string, MessageReaction>>;
|
||||
public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector;
|
||||
public createMessageComponentCollector<T extends ComponentType = ComponentType.ActionRow>(
|
||||
public createMessageComponentCollector<T extends MessageComponentType = ComponentType.ActionRow>(
|
||||
options?: MessageCollectorOptionsParams<T, Cached>,
|
||||
): InteractionCollector<MappedInteractionTypes<Cached>[T]>;
|
||||
public delete(): Promise<Message>;
|
||||
@@ -1541,7 +1570,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
||||
public react(emoji: EmojiIdentifierResolvable): Promise<MessageReaction>;
|
||||
public removeAttachments(): Promise<Message>;
|
||||
public reply(options: string | MessagePayload | ReplyMessageOptions): Promise<Message>;
|
||||
public resolveComponent(customId: string): ActionRowComponent | null;
|
||||
public resolveComponent(customId: string): MessageActionRowComponent | null;
|
||||
public startThread(options: StartThreadOptions): Promise<ThreadChannel>;
|
||||
public suppressEmbeds(suppress?: boolean): Promise<Message>;
|
||||
public toJSON(): unknown;
|
||||
@@ -1590,10 +1619,10 @@ export class MessageComponentInteraction<Cached extends CacheType = CacheType> e
|
||||
protected constructor(client: Client, data: RawMessageComponentInteractionData);
|
||||
public get component(): CacheTypeReducer<
|
||||
Cached,
|
||||
ActionRowComponent,
|
||||
Exclude<APIMessageComponent, APIActionRowComponent<APIMessageComponent>>,
|
||||
ActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageComponent>>,
|
||||
ActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageComponent>>
|
||||
MessageActionRowComponent,
|
||||
Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>>,
|
||||
MessageActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>>,
|
||||
MessageActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>>
|
||||
>;
|
||||
public componentType: Exclude<ComponentType, ComponentType.ActionRow>;
|
||||
public customId: string;
|
||||
@@ -1618,6 +1647,7 @@ export class MessageComponentInteraction<Cached extends CacheType = CacheType> e
|
||||
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
|
||||
public update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
public update(options: string | MessagePayload | InteractionUpdateOptions): Promise<void>;
|
||||
public showModal(modal: Modal): Promise<void>;
|
||||
}
|
||||
|
||||
export class MessageContextMenuCommandInteraction<
|
||||
@@ -1706,6 +1736,57 @@ export class MessageReaction {
|
||||
public toJSON(): unknown;
|
||||
}
|
||||
|
||||
export interface ModalFieldData {
|
||||
value: string;
|
||||
type: ComponentType;
|
||||
customId: string;
|
||||
}
|
||||
|
||||
export class ModalSubmitFieldsResolver {
|
||||
constructor(components: ModalFieldData[][]);
|
||||
public fields: Collection<string, ModalFieldData>;
|
||||
public getField(customId: string): ModalFieldData;
|
||||
public getTextInputValue(customId: string): string;
|
||||
}
|
||||
|
||||
export interface ModalMessageModalSubmitInteraction<Cached extends CacheType = CacheType>
|
||||
extends ModalSubmitInteraction<Cached> {
|
||||
message: GuildCacheMessage<Cached> | null;
|
||||
update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
update(options: string | MessagePayload | InteractionUpdateOptions): Promise<void>;
|
||||
deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
deferUpdate(options?: InteractionDeferUpdateOptions): Promise<void>;
|
||||
inGuild(): this is ModalMessageModalSubmitInteraction<'raw' | 'cached'>;
|
||||
inCachedGuild(): this is ModalMessageModalSubmitInteraction<'cached'>;
|
||||
inRawGuild(): this is ModalMessageModalSubmitInteraction<'raw'>;
|
||||
}
|
||||
|
||||
export interface ModalSubmitActionRow {
|
||||
type: ComponentType.ActionRow;
|
||||
components: ModalFieldData[];
|
||||
}
|
||||
|
||||
export class ModalSubmitInteraction<Cached extends CacheType = CacheType> extends Interaction<Cached> {
|
||||
private constructor(client: Client, data: APIModalSubmitInteraction);
|
||||
public readonly customId: string;
|
||||
// TODO: fix this type when #7517 is implemented
|
||||
public readonly components: ModalSubmitActionRow[];
|
||||
public readonly fields: ModalSubmitFieldsResolver;
|
||||
public readonly webhook: InteractionWebhook;
|
||||
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
|
||||
public deleteReply(): Promise<void>;
|
||||
public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<GuildCacheMessage<Cached>>;
|
||||
public deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
public deferReply(options?: InteractionDeferReplyOptions): Promise<void>;
|
||||
public fetchReply(): Promise<GuildCacheMessage<Cached>>;
|
||||
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
|
||||
public inGuild(): this is ModalSubmitInteraction<'raw' | 'cached'>;
|
||||
public inCachedGuild(): this is ModalSubmitInteraction<'cached'>;
|
||||
public inRawGuild(): this is ModalSubmitInteraction<'raw'>;
|
||||
public isFromMessage(): this is ModalMessageModalSubmitInteraction<Cached>;
|
||||
}
|
||||
|
||||
export class NewsChannel extends BaseGuildTextChannel {
|
||||
public threads: ThreadManager<AllowedThreadTypeForNewsChannel>;
|
||||
public type: ChannelType.GuildNews;
|
||||
@@ -2381,7 +2462,10 @@ export class Formatters extends null {
|
||||
public static userMention: typeof userMention;
|
||||
}
|
||||
|
||||
export type ComponentData = ActionRowComponentData | ButtonComponentData | SelectMenuComponentData;
|
||||
export type ComponentData =
|
||||
| MessageActionRowComponentData
|
||||
| ModalActionRowComponentData
|
||||
| ActionRowData<MessageActionRowComponentData | ModalActionRowComponentData>;
|
||||
|
||||
export class VoiceChannel extends BaseGuildVoiceChannel {
|
||||
public get speakable(): boolean;
|
||||
@@ -3132,8 +3216,8 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
|
||||
lastMessageId: Snowflake | null;
|
||||
get lastMessage(): Message | null;
|
||||
lastPinTimestamp: number | null;
|
||||
get lastPinAt(): Date | null;
|
||||
awaitMessageComponent<T extends ComponentType = ComponentType.ActionRow>(
|
||||
readonly lastPinAt: Date | null;
|
||||
awaitMessageComponent<T extends MessageComponentType = ComponentType.ActionRow>(
|
||||
options?: AwaitMessageCollectorOptionsParams<T, true>,
|
||||
): Promise<MappedInteractionTypes[T]>;
|
||||
awaitMessages(options?: AwaitMessagesOptions): Promise<Collection<Snowflake, Message>>;
|
||||
@@ -3141,7 +3225,7 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
|
||||
messages: Collection<Snowflake, Message> | readonly MessageResolvable[] | number,
|
||||
filterOld?: boolean,
|
||||
): Promise<Collection<Snowflake, Message>>;
|
||||
createMessageComponentCollector<T extends ComponentType = ComponentType.ActionRow>(
|
||||
createMessageComponentCollector<T extends MessageComponentType = ComponentType.ActionRow>(
|
||||
options?: MessageChannelCollectorOptionsParams<T, true>,
|
||||
): InteractionCollector<MappedInteractionTypes[T]>;
|
||||
createMessageCollector(options?: MessageCollectorOptions): MessageCollector;
|
||||
@@ -4518,7 +4602,7 @@ export type ActionRowComponentOptions =
|
||||
| (Required<BaseComponentData> & ButtonComponentData)
|
||||
| (Required<BaseComponentData> & SelectMenuComponentData);
|
||||
|
||||
export type MessageActionRowComponentResolvable = ActionRowComponent | ActionRowComponentOptions;
|
||||
export type MessageActionRowComponentResolvable = MessageActionRowComponent | ActionRowComponentOptions;
|
||||
|
||||
export interface MessageActivity {
|
||||
partyId: string;
|
||||
@@ -4548,7 +4632,7 @@ export interface MessageCollectorOptions extends CollectorOptions<[Message]> {
|
||||
maxProcessed?: number;
|
||||
}
|
||||
|
||||
export type MessageComponent = Component | ActionRow<ActionRowComponent> | ButtonComponent | SelectMenuComponent;
|
||||
export type MessageComponent = Component | ActionRow<MessageActionRowComponent> | ButtonComponent | SelectMenuComponent;
|
||||
|
||||
export type MessageComponentCollectorOptions<T extends MessageComponentInteraction> = Omit<
|
||||
InteractionCollectorOptions<T>,
|
||||
@@ -4567,7 +4651,11 @@ export interface MessageEditOptions {
|
||||
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
|
||||
flags?: BitFieldResolvable<MessageFlagsString, number>;
|
||||
allowedMentions?: MessageMentionOptions;
|
||||
components?: (ActionRow<ActionRowComponent> | (Required<BaseComponentData> & ActionRowData))[];
|
||||
components?: (
|
||||
| ActionRow<MessageActionRowComponent>
|
||||
| (Required<BaseComponentData> & ActionRowData<MessageActionRowComponentData>)
|
||||
| APIActionRowComponent<APIMessageActionRowComponent>
|
||||
)[];
|
||||
}
|
||||
|
||||
export interface MessageEvent {
|
||||
@@ -4605,9 +4693,9 @@ export interface MessageOptions {
|
||||
content?: string | null;
|
||||
embeds?: (Embed | APIEmbed)[];
|
||||
components?: (
|
||||
| ActionRow<ActionRowComponent>
|
||||
| (Required<BaseComponentData> & ActionRowData)
|
||||
| APIActionRowComponent<APIActionRowComponentTypes>
|
||||
| ActionRow<MessageActionRowComponent>
|
||||
| (Required<BaseComponentData> & ActionRowData<MessageActionRowComponentData>)
|
||||
| APIActionRowComponent<APIMessageActionRowComponent>
|
||||
)[];
|
||||
allowedMentions?: MessageMentionOptions;
|
||||
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
|
||||
@@ -4658,6 +4746,23 @@ export interface SelectMenuComponentOptionData {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface TextInputComponentData extends BaseComponentData {
|
||||
customId: string;
|
||||
style: TextInputStyle;
|
||||
label: string;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
required?: boolean;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface ModalData {
|
||||
customId: string;
|
||||
title: string;
|
||||
components: ActionRowData<ModalActionRowComponentData>[];
|
||||
}
|
||||
|
||||
export type MessageTarget =
|
||||
| Interaction
|
||||
| InteractionWebhook
|
||||
@@ -5156,6 +5261,7 @@ export {
|
||||
StageInstancePrivacyLevel,
|
||||
StickerType,
|
||||
StickerFormatType,
|
||||
TextInputStyle,
|
||||
GuildSystemChannelFlags,
|
||||
ThreadMemberFlags,
|
||||
UserFlags,
|
||||
@@ -5166,7 +5272,8 @@ export {
|
||||
UnsafeSelectMenuComponent,
|
||||
SelectMenuOption,
|
||||
UnsafeSelectMenuOption,
|
||||
ActionRowComponent,
|
||||
MessageActionRowComponent,
|
||||
UnsafeEmbed,
|
||||
ModalActionRowComponent,
|
||||
} from '@discordjs/builders';
|
||||
export { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';
|
||||
|
||||
@@ -96,7 +96,7 @@ import {
|
||||
ActionRow,
|
||||
ButtonComponent,
|
||||
SelectMenuComponent,
|
||||
ActionRowComponent,
|
||||
MessageActionRowComponent,
|
||||
InteractionResponseFields,
|
||||
ThreadChannelType,
|
||||
Events,
|
||||
@@ -104,6 +104,7 @@ import {
|
||||
Status,
|
||||
CategoryChannelChildManager,
|
||||
ActionRowData,
|
||||
MessageActionRowComponentData,
|
||||
} from '.';
|
||||
import { expectAssignable, expectDeprecated, expectNotAssignable, expectNotType, expectType } from 'tsd';
|
||||
import { Embed } from '@discordjs/builders';
|
||||
@@ -723,11 +724,14 @@ client.on('interactionCreate', async interaction => {
|
||||
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
void new ActionRow<ActionRowComponent>();
|
||||
void new ActionRow<MessageActionRowComponent>();
|
||||
|
||||
const button = new ButtonComponent();
|
||||
|
||||
const actionRow = new ActionRow<ActionRowComponent>({ type: ComponentType.ActionRow, components: [button.toJSON()] });
|
||||
const actionRow = new ActionRow<MessageActionRowComponent>({
|
||||
type: ComponentType.ActionRow,
|
||||
components: [button.toJSON()],
|
||||
});
|
||||
|
||||
await interaction.reply({ content: 'Hi!', components: [actionRow] });
|
||||
|
||||
@@ -1092,11 +1096,11 @@ client.on('interactionCreate', async interaction => {
|
||||
|
||||
if (interaction.isMessageComponent()) {
|
||||
expectType<MessageComponentInteraction>(interaction);
|
||||
expectType<ActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
|
||||
expectType<MessageActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
|
||||
expectType<Message | APIMessage>(interaction.message);
|
||||
if (interaction.inCachedGuild()) {
|
||||
expectAssignable<MessageComponentInteraction>(interaction);
|
||||
expectType<ActionRowComponent>(interaction.component);
|
||||
expectType<MessageActionRowComponent>(interaction.component);
|
||||
expectType<Message<true>>(interaction.message);
|
||||
expectType<Guild>(interaction.guild);
|
||||
expectAssignable<Promise<Message>>(interaction.reply({ fetchReply: true }));
|
||||
@@ -1108,7 +1112,7 @@ client.on('interactionCreate', async interaction => {
|
||||
expectType<Promise<APIMessage>>(interaction.reply({ fetchReply: true }));
|
||||
} else if (interaction.inGuild()) {
|
||||
expectAssignable<MessageComponentInteraction>(interaction);
|
||||
expectType<ActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
|
||||
expectType<MessageActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
|
||||
expectType<Message | APIMessage>(interaction.message);
|
||||
expectType<Guild | null>(interaction.guild);
|
||||
expectType<Promise<APIMessage | Message>>(interaction.reply({ fetchReply: true }));
|
||||
@@ -1336,7 +1340,7 @@ new ButtonComponent({
|
||||
style: ButtonStyle.Danger,
|
||||
});
|
||||
|
||||
expectNotAssignable<ActionRowData>({
|
||||
expectNotAssignable<ActionRowData<MessageActionRowComponentData>>({
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user