Discord.js v13.7
This commit is contained in:
@@ -64,6 +64,16 @@ class AnonymousGuild extends BaseGuild {
|
||||
*/
|
||||
this.nsfwLevel = NSFWLevels[data.nsfw_level];
|
||||
}
|
||||
|
||||
if ('premium_subscription_count' in data) {
|
||||
/**
|
||||
* The total number of boosts for this server
|
||||
* @type {?number}
|
||||
*/
|
||||
this.premiumSubscriptionCount = data.premium_subscription_count;
|
||||
} else {
|
||||
this.premiumSubscriptionCount ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,6 +65,26 @@ class ApplicationCommand extends Base {
|
||||
this.name = data.name;
|
||||
}
|
||||
|
||||
if ('name_localizations' in data) {
|
||||
/**
|
||||
* The name localizations for this command
|
||||
* @type {?Object<Locale, string>}
|
||||
*/
|
||||
this.nameLocalizations = data.name_localizations;
|
||||
} else {
|
||||
this.nameLocalizations ??= null;
|
||||
}
|
||||
|
||||
if ('name_localized' in data) {
|
||||
/**
|
||||
* The localized name for this command
|
||||
* @type {?string}
|
||||
*/
|
||||
this.nameLocalized = data.name_localized;
|
||||
} else {
|
||||
this.nameLocalized ??= null;
|
||||
}
|
||||
|
||||
if ('description' in data) {
|
||||
/**
|
||||
* The description of this command
|
||||
@@ -73,6 +93,26 @@ class ApplicationCommand extends Base {
|
||||
this.description = data.description;
|
||||
}
|
||||
|
||||
if ('description_localizations' in data) {
|
||||
/**
|
||||
* The description localizations for this command
|
||||
* @type {?Object<Locale, string>}
|
||||
*/
|
||||
this.descriptionLocalizations = data.description_localizations;
|
||||
} else {
|
||||
this.descriptionLocalizations ??= null;
|
||||
}
|
||||
|
||||
if ('description_localized' in data) {
|
||||
/**
|
||||
* The localized description for this command
|
||||
* @type {?string}
|
||||
*/
|
||||
this.descriptionLocalized = data.description_localized;
|
||||
} else {
|
||||
this.descriptionLocalized ??= null;
|
||||
}
|
||||
|
||||
if ('options' in data) {
|
||||
/**
|
||||
* The options of this command
|
||||
@@ -131,7 +171,9 @@ class ApplicationCommand extends Base {
|
||||
* Data for creating or editing an application command.
|
||||
* @typedef {Object} ApplicationCommandData
|
||||
* @property {string} name The name of the command
|
||||
* @property {Object<Locale, string>} [nameLocalizations] The localizations for the command name
|
||||
* @property {string} description The description of the command
|
||||
* @property {Object<Locale, string>} [descriptionLocalizations] The localizations for the command description
|
||||
* @property {ApplicationCommandType} [type] The type of the command
|
||||
* @property {ApplicationCommandOptionData[]} [options] Options for the command
|
||||
* @property {boolean} [defaultPermission] Whether the command is enabled by default when the app is added to a guild
|
||||
@@ -146,10 +188,12 @@ class ApplicationCommand extends Base {
|
||||
* @typedef {Object} ApplicationCommandOptionData
|
||||
* @property {ApplicationCommandOptionType|number} type The type of the option
|
||||
* @property {string} name The name of the option
|
||||
* @property {Object<Locale, string>} [nameLocalizations] The name localizations for the option
|
||||
* @property {string} description The description of the option
|
||||
* @property {Object<Locale, string>} [descriptionLocalizations] The description localizations for the option
|
||||
* @property {boolean} [autocomplete] Whether the option is an autocomplete option
|
||||
* @property {boolean} [required] Whether the option is required
|
||||
* @property {ApplicationCommandOptionChoice[]} [choices] The choices of the option for the user to pick from
|
||||
* @property {ApplicationCommandOptionChoiceData[]} [choices] The choices of the option for the user to pick from
|
||||
* @property {ApplicationCommandOptionData[]} [options] Additional options if this option is a subcommand (group)
|
||||
* @property {ChannelType[]|number[]} [channelTypes] When the option type is channel,
|
||||
* the allowed types of channels that can be selected
|
||||
@@ -157,6 +201,13 @@ class ApplicationCommand extends Base {
|
||||
* @property {number} [maxValue] The maximum value for an `INTEGER` or `NUMBER` option
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ApplicationCommandOptionChoiceData
|
||||
* @property {string} name The name of the choice
|
||||
* @property {Object<Locale, string>} [nameLocalizations] The localized names for this choice
|
||||
* @property {string|number} value The value of the choice
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edits this application command.
|
||||
* @param {ApplicationCommandData} data The data to update the command with
|
||||
@@ -182,6 +233,23 @@ class ApplicationCommand extends Base {
|
||||
return this.edit({ name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the localized names of this ApplicationCommand
|
||||
* @param {Object<Locale, string>} nameLocalizations The new localized names for the command
|
||||
* @returns {Promise<ApplicationCommand>}
|
||||
* @example
|
||||
* // Edit the name localizations of this command
|
||||
* command.setLocalizedNames({
|
||||
* 'en-GB': 'test',
|
||||
* 'pt-BR': 'teste',
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error)
|
||||
*/
|
||||
setNameLocalizations(nameLocalizations) {
|
||||
return this.edit({ nameLocalizations });
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the description of this ApplicationCommand
|
||||
* @param {string} description The new description of the command
|
||||
@@ -191,6 +259,23 @@ class ApplicationCommand extends Base {
|
||||
return this.edit({ description });
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the localized descriptions of this ApplicationCommand
|
||||
* @param {Object<Locale, string>} descriptionLocalizations The new localized descriptions for the command
|
||||
* @returns {Promise<ApplicationCommand>}
|
||||
* @example
|
||||
* // Edit the description localizations of this command
|
||||
* command.setLocalizedDescriptions({
|
||||
* 'en-GB': 'A test command',
|
||||
* 'pt-BR': 'Um comando de teste',
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error)
|
||||
*/
|
||||
setDescriptionLocalizations(descriptionLocalizations) {
|
||||
return this.edit({ descriptionLocalizations });
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the default permission of this ApplicationCommand
|
||||
* @param {boolean} [defaultPermission=true] The default permission for this command
|
||||
@@ -349,7 +434,11 @@ class ApplicationCommand extends Base {
|
||||
* @typedef {Object} ApplicationCommandOption
|
||||
* @property {ApplicationCommandOptionType} type The type of the option
|
||||
* @property {string} name The name of the option
|
||||
* @property {Object<string, string>} [nameLocalizations] The localizations for the option name
|
||||
* @property {string} [nameLocalized] The localized name for this option
|
||||
* @property {string} description The description of the option
|
||||
* @property {Object<string, string>} [descriptionLocalizations] The localizations for the option description
|
||||
* @property {string} [descriptionLocalized] The localized description for this option
|
||||
* @property {boolean} [required] Whether the option is required
|
||||
* @property {boolean} [autocomplete] Whether the option is an autocomplete option
|
||||
* @property {ApplicationCommandOptionChoice[]} [choices] The choices of the option for the user to pick from
|
||||
@@ -364,12 +453,14 @@ class ApplicationCommand extends Base {
|
||||
* A choice for an application command option.
|
||||
* @typedef {Object} ApplicationCommandOptionChoice
|
||||
* @property {string} name The name of the choice
|
||||
* @property {?string} nameLocalized The localized name of the choice in the provided locale, if any
|
||||
* @property {?Object<string, string>} [nameLocalizations] The localized names for this choice
|
||||
* @property {string|number} value The value of the choice
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms an {@link ApplicationCommandOptionData} object into something that can be used with the API.
|
||||
* @param {ApplicationCommandOptionData} option The option to transform
|
||||
* @param {ApplicationCommandOptionData|ApplicationCommandOption} option The option to transform
|
||||
* @param {boolean} [received] Whether this option has been received from Discord
|
||||
* @returns {APIApplicationCommandOption}
|
||||
* @private
|
||||
@@ -379,14 +470,27 @@ class ApplicationCommand extends Base {
|
||||
const channelTypesKey = received ? 'channelTypes' : 'channel_types';
|
||||
const minValueKey = received ? 'minValue' : 'min_value';
|
||||
const maxValueKey = received ? 'maxValue' : 'max_value';
|
||||
const nameLocalizationsKey = received ? 'nameLocalizations' : 'name_localizations';
|
||||
const nameLocalizedKey = received ? 'nameLocalized' : 'name_localized';
|
||||
const descriptionLocalizationsKey = received ? 'descriptionLocalizations' : 'description_localizations';
|
||||
const descriptionLocalizedKey = received ? 'descriptionLocalized' : 'description_localized';
|
||||
return {
|
||||
type: typeof option.type === 'number' && !received ? option.type : ApplicationCommandOptionTypes[option.type],
|
||||
name: option.name,
|
||||
[nameLocalizationsKey]: option.nameLocalizations ?? option.name_localizations,
|
||||
[nameLocalizedKey]: option.nameLocalized ?? option.name_localized,
|
||||
description: option.description,
|
||||
[descriptionLocalizationsKey]: option.descriptionLocalizations ?? option.description_localizations,
|
||||
[descriptionLocalizedKey]: option.descriptionLocalized ?? option.description_localized,
|
||||
required:
|
||||
option.required ?? (stringType === 'SUB_COMMAND' || stringType === 'SUB_COMMAND_GROUP' ? undefined : false),
|
||||
autocomplete: option.autocomplete,
|
||||
choices: option.choices,
|
||||
choices: option.choices?.map(choice => ({
|
||||
name: choice.name,
|
||||
[nameLocalizedKey]: choice.nameLocalized ?? choice.name_localized,
|
||||
[nameLocalizationsKey]: choice.nameLocalizations ?? choice.name_localizations,
|
||||
value: choice.value,
|
||||
})),
|
||||
options: option.options?.map(o => this.transformOption(o, received)),
|
||||
[channelTypesKey]: received
|
||||
? option.channel_types?.map(type => ChannelTypes[type])
|
||||
|
||||
@@ -76,7 +76,7 @@ class AutocompleteInteraction extends Interaction {
|
||||
|
||||
/**
|
||||
* Sends results for the autocomplete of this interaction.
|
||||
* @param {ApplicationCommandOptionChoice[]} options The options for the autocomplete
|
||||
* @param {ApplicationCommandOptionChoiceData[]} options The options for the autocomplete
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // respond to autocomplete interaction
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const Interaction = require('./Interaction');
|
||||
const InteractionWebhook = require('./InteractionWebhook');
|
||||
const MessageAttachment = require('./MessageAttachment');
|
||||
const InteractionResponses = require('./interfaces/InteractionResponses');
|
||||
const { ApplicationCommandOptionTypes } = require('../util/Constants');
|
||||
|
||||
@@ -76,6 +77,7 @@ class BaseCommandInteraction extends Interaction {
|
||||
* @property {Collection<Snowflake, Role|APIRole>} [roles] The resolved roles
|
||||
* @property {Collection<Snowflake, Channel|APIChannel>} [channels] The resolved channels
|
||||
* @property {Collection<Snowflake, Message|APIMessage>} [messages] The resolved messages
|
||||
* @property {Collection<Snowflake, MessageAttachment>} [attachments] The resolved attachments
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -84,7 +86,7 @@ class BaseCommandInteraction extends Interaction {
|
||||
* @returns {CommandInteractionResolvedData}
|
||||
* @private
|
||||
*/
|
||||
transformResolved({ members, users, channels, roles, messages }) {
|
||||
transformResolved({ members, users, channels, roles, messages, attachments }) {
|
||||
const result = {};
|
||||
|
||||
if (members) {
|
||||
@@ -123,6 +125,14 @@ class BaseCommandInteraction extends Interaction {
|
||||
}
|
||||
}
|
||||
|
||||
if (attachments) {
|
||||
result.attachments = new Collection();
|
||||
for (const attachment of Object.values(attachments)) {
|
||||
const patched = new MessageAttachment(attachment.url, attachment.filename, attachment);
|
||||
result.attachments.set(attachment.id, patched);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -139,6 +149,7 @@ class BaseCommandInteraction extends Interaction {
|
||||
* @property {GuildMember|APIGuildMember} [member] The resolved member
|
||||
* @property {GuildChannel|ThreadChannel|APIChannel} [channel] The resolved channel
|
||||
* @property {Role|APIRole} [role] The resolved role
|
||||
* @property {MessageAttachment} [attachment] The resolved attachment
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -169,6 +180,9 @@ class BaseCommandInteraction extends Interaction {
|
||||
|
||||
const role = resolved.roles?.[option.value];
|
||||
if (role) result.role = this.guild?.roles._add(role) ?? role;
|
||||
|
||||
const attachment = resolved.attachments?.[option.value];
|
||||
if (attachment) result.attachment = new MessageAttachment(attachment.url, attachment.filename, attachment);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -182,6 +196,8 @@ class BaseCommandInteraction extends Interaction {
|
||||
editReply() {}
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
showModal() {}
|
||||
awaitModalSubmit() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(BaseCommandInteraction, ['deferUpdate', 'update']);
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const GuildChannel = require('./GuildChannel');
|
||||
const Webhook = require('./Webhook');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const ThreadManager = require('../managers/ThreadManager');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
|
||||
/**
|
||||
* Represents a text-based guild channel on Discord.
|
||||
@@ -72,7 +69,7 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
if ('default_auto_archive_duration' in data) {
|
||||
/**
|
||||
* The default auto archive duration for newly created threads in this channel
|
||||
* @type {?ThreadAutoArchiveDuration}
|
||||
* @type {?number}
|
||||
*/
|
||||
this.defaultAutoArchiveDuration = data.default_auto_archive_duration;
|
||||
}
|
||||
@@ -121,11 +118,8 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async fetchWebhooks() {
|
||||
const data = await this.client.api.channels[this.id].webhooks.get();
|
||||
const hooks = new Collection();
|
||||
for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
|
||||
return hooks;
|
||||
fetchWebhooks() {
|
||||
return this.guild.channels.fetchWebhooks(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,18 +143,8 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
* .then(console.log)
|
||||
* .catch(console.error)
|
||||
*/
|
||||
async createWebhook(name, { avatar, reason } = {}) {
|
||||
if (typeof avatar === 'string' && !avatar.startsWith('data:')) {
|
||||
avatar = await DataResolver.resolveImage(avatar);
|
||||
}
|
||||
const data = await this.client.api.channels[this.id].webhooks.post({
|
||||
data: {
|
||||
name,
|
||||
avatar,
|
||||
},
|
||||
reason,
|
||||
});
|
||||
return new Webhook(this.client, data);
|
||||
createWebhook(name, options = {}) {
|
||||
return this.guild.channels.createWebhook(this.id, name, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,6 +162,14 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
return this.edit({ topic }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to an Application. This can be:
|
||||
* * An Application
|
||||
* * An Activity with associated Application
|
||||
* * A Snowflake
|
||||
* @typedef {Application|Snowflake} ApplicationResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options used to create an invite to a guild channel.
|
||||
* @typedef {Object} CreateInviteOptions
|
||||
|
||||
@@ -82,17 +82,18 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
|
||||
/**
|
||||
* Sets the RTC region of the channel.
|
||||
* @param {?string} region The new region of the channel. Set to `null` to remove a specific region for the channel
|
||||
* @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel
|
||||
* @param {string} [reason] The reason for modifying this region.
|
||||
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||
* @example
|
||||
* // Set the RTC region to europe
|
||||
* channel.setRTCRegion('europe');
|
||||
* // Set the RTC region to sydney
|
||||
* channel.setRTCRegion('sydney');
|
||||
* @example
|
||||
* // Remove a fixed region for this channel - let Discord decide automatically
|
||||
* channel.setRTCRegion(null);
|
||||
* channel.setRTCRegion(null, 'We want to let Discord decide.');
|
||||
*/
|
||||
setRTCRegion(region) {
|
||||
return this.edit({ rtcRegion: region });
|
||||
setRTCRegion(rtcRegion, reason) {
|
||||
return this.edit({ rtcRegion }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ const { TypeError } = require('../errors');
|
||||
const { MessageComponentTypes, Events } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents an interactive component of a Message. It should not be necessary to construct this directly.
|
||||
* Represents an interactive component of a Message or Modal. It should not be necessary to construct this directly.
|
||||
* See {@link MessageComponent}
|
||||
*/
|
||||
class BaseMessageComponent {
|
||||
@@ -15,18 +15,20 @@ class BaseMessageComponent {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved into options for a MessageComponent. This can be:
|
||||
* Data that can be resolved into options for a component. This can be:
|
||||
* * MessageActionRowOptions
|
||||
* * MessageButtonOptions
|
||||
* * MessageSelectMenuOptions
|
||||
* * TextInputComponentOptions
|
||||
* @typedef {MessageActionRowOptions|MessageButtonOptions|MessageSelectMenuOptions} MessageComponentOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Components that can be sent in a message. These can be:
|
||||
* Components that can be sent in a payload. These can be:
|
||||
* * MessageActionRow
|
||||
* * MessageButton
|
||||
* * MessageSelectMenu
|
||||
* * TextInputComponent
|
||||
* @typedef {MessageActionRow|MessageButton|MessageSelectMenu} MessageComponent
|
||||
* @see {@link https://discord.com/developers/docs/interactions/message-components#component-object-component-types}
|
||||
*/
|
||||
@@ -51,10 +53,10 @@ class BaseMessageComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessageComponent based on the type of the incoming data
|
||||
* Constructs a component based on the type of the incoming data
|
||||
* @param {MessageComponentOptions} data Data for a MessageComponent
|
||||
* @param {Client|WebhookClient} [client] Client constructing this component
|
||||
* @returns {?MessageComponent}
|
||||
* @returns {?(MessageComponent|ModalComponent)}
|
||||
* @private
|
||||
*/
|
||||
static create(data, client) {
|
||||
@@ -79,6 +81,11 @@ class BaseMessageComponent {
|
||||
component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data);
|
||||
break;
|
||||
}
|
||||
case MessageComponentTypes.TEXT_INPUT: {
|
||||
const TextInputComponent = require('./TextInputComponent');
|
||||
component = data instanceof TextInputComponent ? data : new TextInputComponent(data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (client) {
|
||||
client.emit(Events.DEBUG, `[BaseMessageComponent] Received component with unknown type: ${data.type}`);
|
||||
@@ -90,7 +97,7 @@ class BaseMessageComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the type of a MessageComponent
|
||||
* Resolves the type of a component
|
||||
* @param {MessageComponentTypeResolvable} type The type to resolve
|
||||
* @returns {MessageComponentType}
|
||||
* @private
|
||||
|
||||
@@ -10,6 +10,7 @@ let StoreChannel;
|
||||
let TextChannel;
|
||||
let ThreadChannel;
|
||||
let VoiceChannel;
|
||||
let DirectoryChannel;
|
||||
const { ChannelTypes, ThreadChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
// Const { ApplicationCommand } = require('discord.js-selfbot-v13'); - Not being used in this file, not necessary.
|
||||
@@ -165,6 +166,14 @@ class Channel extends Base {
|
||||
return ThreadChannelTypes.includes(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this channel is a {@link DirectoryChannel}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isDirectory() {
|
||||
return this.type === 'GUILD_DIRECTORY';
|
||||
}
|
||||
|
||||
static create(client, data, guild, { allowUnknownGuild, fromInteraction } = {}) {
|
||||
CategoryChannel ??= require('./CategoryChannel');
|
||||
DMChannel ??= require('./DMChannel');
|
||||
@@ -174,6 +183,7 @@ class Channel extends Base {
|
||||
TextChannel ??= require('./TextChannel');
|
||||
ThreadChannel ??= require('./ThreadChannel');
|
||||
VoiceChannel ??= require('./VoiceChannel');
|
||||
DirectoryChannel ??= require('./DirectoryChannel');
|
||||
|
||||
let channel;
|
||||
if (!data.guild_id && !guild) {
|
||||
@@ -219,6 +229,9 @@ class Channel extends Base {
|
||||
if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_DIRECTORY:
|
||||
channel = new DirectoryChannel(client, data);
|
||||
break;
|
||||
}
|
||||
if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
const Team = require('./Team');
|
||||
const Application = require('./interfaces/Application');
|
||||
const { Error } = require('../errors/DJSError');
|
||||
const ApplicationCommandManager = require('../managers/ApplicationCommandManager');
|
||||
const ApplicationFlags = require('../util/ApplicationFlags');
|
||||
const Permissions = require('../util/Permissions');
|
||||
|
||||
/**
|
||||
* @typedef {Object} ClientApplicationInstallParams
|
||||
* @property {InviteScope[]} scopes The scopes to add the application to the server with
|
||||
* @property {Readonly<Permissions>} permissions The permissions this bot will request upon joining
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a Client OAuth2 Application.
|
||||
@@ -17,12 +25,41 @@ class ClientApplication extends Application {
|
||||
* The application command manager for this application
|
||||
* @type {ApplicationCommandManager}
|
||||
*/
|
||||
this.commands = null; // Selfbot
|
||||
this.commands = new ApplicationCommandManager(this.client);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
/**
|
||||
* The tags this application has (max of 5)
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.tags = data.tags ?? [];
|
||||
|
||||
if ('install_params' in data) {
|
||||
/**
|
||||
* Settings for this application's default in-app authorization
|
||||
* @type {?ClientApplicationInstallParams}
|
||||
*/
|
||||
this.installParams = {
|
||||
scopes: data.install_params.scopes,
|
||||
permissions: new Permissions(data.install_params.permissions).freeze(),
|
||||
};
|
||||
} else {
|
||||
this.installParams ??= null;
|
||||
}
|
||||
|
||||
if ('custom_install_url' in data) {
|
||||
/**
|
||||
* This application's custom installation URL
|
||||
* @type {?string}
|
||||
*/
|
||||
this.customInstallURL = data.custom_install_url;
|
||||
} else {
|
||||
this.customInstallURL = null;
|
||||
}
|
||||
|
||||
if ('flags' in data) {
|
||||
/**
|
||||
* The flags this application has
|
||||
|
||||
@@ -251,6 +251,17 @@ class CommandInteractionOptionResolver {
|
||||
if (!focusedOption) throw new TypeError('AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION');
|
||||
return getFull ? focusedOption : focusedOption.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an attachment option.
|
||||
* @param {string} name The name of the option.
|
||||
* @param {boolean} [required=false] Whether to throw an error if the option is not found.
|
||||
* @returns {?MessageAttachment} The value of the option, or null if not set and not required.
|
||||
*/
|
||||
getAttachment(name, required = false) {
|
||||
const option = this._getTypedOption(name, 'ATTACHMENT', ['attachment'], required);
|
||||
return option?.attachment ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CommandInteractionOptionResolver;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const { Channel } = require('./Channel');
|
||||
|
||||
/**
|
||||
* Represents a channel that displays a directory of guilds
|
||||
*/
|
||||
class DirectoryChannel extends Channel {
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
/**
|
||||
* The channel's name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DirectoryChannel;
|
||||
+19
-27
@@ -283,14 +283,6 @@ class Guild extends AnonymousGuild {
|
||||
this.premiumTier = PremiumTiers[data.premium_tier];
|
||||
}
|
||||
|
||||
if ('premium_subscription_count' in data) {
|
||||
/**
|
||||
* The total number of boosts for this server
|
||||
* @type {?number}
|
||||
*/
|
||||
this.premiumSubscriptionCount = data.premium_subscription_count;
|
||||
}
|
||||
|
||||
if ('widget_enabled' in data) {
|
||||
/**
|
||||
* Whether widget images are enabled on this guild
|
||||
@@ -416,8 +408,8 @@ class Guild extends AnonymousGuild {
|
||||
if ('preferred_locale' in data) {
|
||||
/**
|
||||
* The preferred locale of the guild, defaults to `en-US`
|
||||
* @type {string}
|
||||
* @see {@link https://discord.com/developers/docs/dispatch/field-values#predefined-field-values-accepted-locales}
|
||||
* @type {Locale}
|
||||
* @see {@link https://discord.com/developers/docs/reference#locales}
|
||||
*/
|
||||
this.preferredLocale = data.preferred_locale;
|
||||
}
|
||||
@@ -874,24 +866,24 @@ class Guild extends AnonymousGuild {
|
||||
* The data for editing a guild.
|
||||
* @typedef {Object} GuildEditData
|
||||
* @property {string} [name] The name of the guild
|
||||
* @property {VerificationLevel|number} [verificationLevel] The verification level of the guild
|
||||
* @property {ExplicitContentFilterLevel|number} [explicitContentFilter] The level of the explicit content filter
|
||||
* @property {VoiceChannelResolvable} [afkChannel] The AFK channel of the guild
|
||||
* @property {TextChannelResolvable} [systemChannel] The system channel of the guild
|
||||
* @property {?(VerificationLevel|number)} [verificationLevel] The verification level of the guild
|
||||
* @property {?(ExplicitContentFilterLevel|number)} [explicitContentFilter] The level of the explicit content filter
|
||||
* @property {?VoiceChannelResolvable} [afkChannel] The AFK channel of the guild
|
||||
* @property {?TextChannelResolvable} [systemChannel] The system channel of the guild
|
||||
* @property {number} [afkTimeout] The AFK timeout of the guild
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the guild
|
||||
* @property {GuildMemberResolvable} [owner] The owner of the guild
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [splash] The invite splash image of the guild
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [discoverySplash] The discovery splash image of the guild
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [banner] The banner of the guild
|
||||
* @property {DefaultMessageNotificationLevel|number} [defaultMessageNotifications] The default message notification
|
||||
* @property {?(DefaultMessageNotificationLevel|number)} [defaultMessageNotifications] The default message notification
|
||||
* level of the guild
|
||||
* @property {SystemChannelFlagsResolvable} [systemChannelFlags] The system channel flags of the guild
|
||||
* @property {TextChannelResolvable} [rulesChannel] The rules channel of the guild
|
||||
* @property {TextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild
|
||||
* @property {string} [preferredLocale] The preferred locale of the guild
|
||||
* @property {?TextChannelResolvable} [rulesChannel] The rules channel of the guild
|
||||
* @property {?TextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild
|
||||
* @property {?string} [preferredLocale] The preferred locale of the guild
|
||||
* @property {boolean} [premiumProgressBarEnabled] Whether the guild's premium progress bar is enabled
|
||||
* @property {string} [description] The discovery description of the guild
|
||||
* @property {?string} [description] The discovery description of the guild
|
||||
* @property {Features[]} [features] The features of the guild
|
||||
*/
|
||||
|
||||
@@ -978,7 +970,7 @@ class Guild extends AnonymousGuild {
|
||||
if (typeof data.description !== 'undefined') {
|
||||
_data.description = data.description;
|
||||
}
|
||||
if (data.preferredLocale) _data.preferred_locale = data.preferredLocale;
|
||||
if (typeof data.preferredLocale !== 'undefined') _data.preferred_locale = data.preferredLocale;
|
||||
if ('premiumProgressBarEnabled' in data) {
|
||||
_data.premium_progress_bar_enabled = data.premiumProgressBarEnabled;
|
||||
}
|
||||
@@ -1058,7 +1050,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Edits the level of the explicit content filter.
|
||||
* @param {ExplicitContentFilterLevel|number} explicitContentFilter The new level of the explicit content filter
|
||||
* @param {?(ExplicitContentFilterLevel|number)} explicitContentFilter The new level of the explicit content filter
|
||||
* @param {string} [reason] Reason for changing the level of the guild's explicit content filter
|
||||
* @returns {Promise<Guild>}
|
||||
*/
|
||||
@@ -1105,7 +1097,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Edits the verification level of the guild.
|
||||
* @param {VerificationLevel|number} verificationLevel The new verification level of the guild
|
||||
* @param {(VerificationLevel|number)} verificationLevel The new verification level of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's verification level
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@@ -1120,7 +1112,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Edits the AFK channel of the guild.
|
||||
* @param {VoiceChannelResolvable} afkChannel The new AFK channel
|
||||
* @param {?VoiceChannelResolvable} afkChannel The new AFK channel
|
||||
* @param {string} [reason] Reason for changing the guild's AFK channel
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@@ -1135,7 +1127,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Edits the system channel of the guild.
|
||||
* @param {TextChannelResolvable} systemChannel The new system channel
|
||||
* @param {?TextChannelResolvable} systemChannel The new system channel
|
||||
* @param {string} [reason] Reason for changing the guild's system channel
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@@ -1240,7 +1232,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Edits the rules channel of the guild.
|
||||
* @param {TextChannelResolvable} rulesChannel The new rules channel
|
||||
* @param {?TextChannelResolvable} rulesChannel The new rules channel
|
||||
* @param {string} [reason] Reason for changing the guild's rules channel
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@@ -1296,7 +1288,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Edits the community updates channel of the guild.
|
||||
* @param {TextChannelResolvable} publicUpdatesChannel The new community updates channel
|
||||
* @param {?TextChannelResolvable} publicUpdatesChannel The new community updates channel
|
||||
* @param {string} [reason] Reason for changing the guild's community updates channel
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@@ -1311,7 +1303,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Edits the preferred locale of the guild.
|
||||
* @param {string} preferredLocale The new preferred locale of the guild
|
||||
* @param {?string} preferredLocale The new preferred locale of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's preferred locale
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const { Channel } = require('./Channel');
|
||||
const PermissionOverwrites = require('./PermissionOverwrites');
|
||||
const { Error } = require('../errors');
|
||||
const PermissionOverwriteManager = require('../managers/PermissionOverwriteManager');
|
||||
const { ChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
|
||||
const { VoiceBasedChannelTypes } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents a guild channel from any of the following:
|
||||
@@ -262,27 +260,6 @@ class GuildChannel extends Channel {
|
||||
return this.guild.members.cache.filter(m => this.permissionsFor(m).has(Permissions.FLAGS.VIEW_CHANNEL, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* The data for a guild channel.
|
||||
* @typedef {Object} ChannelData
|
||||
* @property {string} [name] The name of the channel
|
||||
* @property {ChannelType} [type] The type of the channel (only conversion between text and news is supported)
|
||||
* @property {number} [position] The position of the channel
|
||||
* @property {string} [topic] The topic of the text channel
|
||||
* @property {boolean} [nsfw] Whether the channel is NSFW
|
||||
* @property {number} [bitrate] The bitrate of the voice channel
|
||||
* @property {number} [userLimit] The user limit of the voice channel
|
||||
* @property {?CategoryChannelResolvable} [parent] The parent of the channel
|
||||
* @property {boolean} [lockPermissions]
|
||||
* Lock the permissions of the channel to what the parent's permissions are
|
||||
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
|
||||
* Permission overwrites for the channel
|
||||
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the channel in seconds
|
||||
* @property {ThreadAutoArchiveDuration} [defaultAutoArchiveDuration]
|
||||
* The default auto archive duration for all new threads in this channel
|
||||
* @property {?string} [rtcRegion] The RTC region of the channel
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edits the channel.
|
||||
* @param {ChannelData} data The new data for the channel
|
||||
@@ -294,64 +271,8 @@ class GuildChannel extends Channel {
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async edit(data, reason) {
|
||||
data.parent &&= this.client.channels.resolveId(data.parent);
|
||||
|
||||
if (typeof data.position !== 'undefined') {
|
||||
const updatedChannels = await Util.setPosition(
|
||||
this,
|
||||
data.position,
|
||||
false,
|
||||
this.guild._sortedChannels(this),
|
||||
this.client.api.guilds(this.guild.id).channels,
|
||||
reason,
|
||||
);
|
||||
this.client.actions.GuildChannelsPositionUpdate.handle({
|
||||
guild_id: this.guild.id,
|
||||
channels: updatedChannels,
|
||||
});
|
||||
}
|
||||
|
||||
let permission_overwrites;
|
||||
|
||||
if (data.permissionOverwrites) {
|
||||
permission_overwrites = data.permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild));
|
||||
}
|
||||
|
||||
if (data.lockPermissions) {
|
||||
if (data.parent) {
|
||||
const newParent = this.guild.channels.resolve(data.parent);
|
||||
if (newParent?.type === 'GUILD_CATEGORY') {
|
||||
permission_overwrites = newParent.permissionOverwrites.cache.map(o =>
|
||||
PermissionOverwrites.resolve(o, this.guild),
|
||||
);
|
||||
}
|
||||
} else if (this.parent) {
|
||||
permission_overwrites = this.parent.permissionOverwrites.cache.map(o =>
|
||||
PermissionOverwrites.resolve(o, this.guild),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const newData = await this.client.api.channels(this.id).patch({
|
||||
data: {
|
||||
name: (data.name ?? this.name).trim(),
|
||||
type: ChannelTypes[data.type],
|
||||
topic: data.topic,
|
||||
nsfw: data.nsfw,
|
||||
bitrate: data.bitrate ?? this.bitrate,
|
||||
user_limit: data.userLimit ?? this.userLimit,
|
||||
rtc_region: data.rtcRegion ?? this.rtcRegion,
|
||||
parent_id: data.parent,
|
||||
lock_permissions: data.lockPermissions,
|
||||
rate_limit_per_user: data.rateLimitPerUser,
|
||||
default_auto_archive_duration: data.defaultAutoArchiveDuration,
|
||||
permission_overwrites,
|
||||
},
|
||||
reason,
|
||||
});
|
||||
|
||||
return this.client.actions.ChannelUpdate.handle(newData).updated;
|
||||
edit(data, reason) {
|
||||
return this.guild.channels.edit(this, data, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,30 +336,10 @@ class GuildChannel extends Channel {
|
||||
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async setPosition(position, { relative, reason } = {}) {
|
||||
const updatedChannels = await Util.setPosition(
|
||||
this,
|
||||
position,
|
||||
relative,
|
||||
this.guild._sortedChannels(this),
|
||||
this.client.api.guilds(this.guild.id).channels,
|
||||
reason,
|
||||
);
|
||||
this.client.actions.GuildChannelsPositionUpdate.handle({
|
||||
guild_id: this.guild.id,
|
||||
channels: updatedChannels,
|
||||
});
|
||||
return this;
|
||||
setPosition(position, options = {}) {
|
||||
return this.guild.channels.setPosition(this, position, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to an Application. This can be:
|
||||
* * An Application
|
||||
* * An Activity with associated Application
|
||||
* * A Snowflake
|
||||
* @typedef {Application|Snowflake} ApplicationResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options used to clone a guild channel.
|
||||
* @typedef {GuildChannelCreateOptions} GuildChannelCloneOptions
|
||||
@@ -544,7 +445,7 @@ class GuildChannel extends Channel {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete(reason) {
|
||||
await this.client.api.channels(this.id).delete({ reason });
|
||||
await this.guild.channels.delete(this.id, reason);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,18 +72,8 @@ class GuildEmoji extends BaseGuildEmoji {
|
||||
* Fetches the author for this emoji
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async fetchAuthor() {
|
||||
if (this.managed) {
|
||||
throw new Error('EMOJI_MANAGED');
|
||||
} else {
|
||||
if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
|
||||
if (!this.guild.me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS_AND_STICKERS)) {
|
||||
throw new Error('MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION', this.guild);
|
||||
}
|
||||
}
|
||||
const data = await this.client.api.guilds(this.guild.id).emojis(this.id).get();
|
||||
this._patch(data);
|
||||
return this.author;
|
||||
fetchAuthor() {
|
||||
return this.guild.emojis.fetchAuthor(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +127,7 @@ class GuildEmoji extends BaseGuildEmoji {
|
||||
* @returns {Promise<GuildEmoji>}
|
||||
*/
|
||||
async delete(reason) {
|
||||
await this.client.api.guilds(this.guild.id).emojis(this.id).delete({ reason });
|
||||
await this.guild.emojis.delete(this, reason);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -300,7 +300,11 @@ class GuildMember extends Base {
|
||||
* @readonly
|
||||
*/
|
||||
get moderatable() {
|
||||
return this.manageable && (this.guild.me?.permissions.has(Permissions.FLAGS.MODERATE_MEMBERS) ?? false);
|
||||
return (
|
||||
!this.permissions.has(Permissions.FLAGS.ADMINISTRATOR) &&
|
||||
this.manageable &&
|
||||
(this.guild.me?.permissions.has(Permissions.FLAGS.MODERATE_MEMBERS) ?? false)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const Base = require('./Base');
|
||||
const GuildPreviewEmoji = require('./GuildPreviewEmoji');
|
||||
const { Sticker } = require('./Sticker');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
|
||||
/**
|
||||
@@ -103,6 +104,15 @@ class GuildPreview extends Base {
|
||||
for (const emoji of data.emojis) {
|
||||
this.emojis.set(emoji.id, new GuildPreviewEmoji(this.client, emoji, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of stickers belonging to this guild
|
||||
* @type {Collection<Snowflake, Sticker>}
|
||||
*/
|
||||
this.stickers = data.stickers.reduce(
|
||||
(stickers, sticker) => stickers.set(sticker.id, new Sticker(this.client, sticker)),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
/**
|
||||
* The timestamp this guild was created at
|
||||
|
||||
@@ -156,6 +156,25 @@ class GuildScheduledEvent extends Base {
|
||||
} else {
|
||||
this.entityMetadata ??= null;
|
||||
}
|
||||
|
||||
if ('image' in data) {
|
||||
/**
|
||||
* The cover image hash for this scheduled event
|
||||
* @type {?string}
|
||||
*/
|
||||
this.image = data.image;
|
||||
} else {
|
||||
this.image ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of this scheduled event's cover image
|
||||
* @param {StaticImageURLOptions} [options={}] Options for image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
coverImageURL({ format, size } = {}) {
|
||||
return this.image && this.client.rest.cdn.guildScheduledEventCover(this.id, this.image, format, size);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,6 +54,7 @@ class IntegrationApplication extends Application {
|
||||
/**
|
||||
* The application's summary
|
||||
* @type {?string}
|
||||
* @deprecated This property is no longer being sent by the API.
|
||||
*/
|
||||
this.summary = data.summary;
|
||||
} else {
|
||||
|
||||
@@ -75,16 +75,51 @@ class Interaction extends Base {
|
||||
*/
|
||||
this.memberPermissions = data.member?.permissions ? new Permissions(data.member.permissions).freeze() : null;
|
||||
|
||||
/**
|
||||
* A Discord locale string, possible values are:
|
||||
* * en-US (English, US)
|
||||
* * en-GB (English, UK)
|
||||
* * bg (Bulgarian)
|
||||
* * zh-CN (Chinese, China)
|
||||
* * zh-TW (Chinese, Taiwan)
|
||||
* * hr (Croatian)
|
||||
* * cs (Czech)
|
||||
* * da (Danish)
|
||||
* * nl (Dutch)
|
||||
* * fi (Finnish)
|
||||
* * fr (French)
|
||||
* * de (German)
|
||||
* * el (Greek)
|
||||
* * hi (Hindi)
|
||||
* * hu (Hungarian)
|
||||
* * it (Italian)
|
||||
* * ja (Japanese)
|
||||
* * ko (Korean)
|
||||
* * lt (Lithuanian)
|
||||
* * no (Norwegian)
|
||||
* * pl (Polish)
|
||||
* * pt-BR (Portuguese, Brazilian)
|
||||
* * ro (Romanian, Romania)
|
||||
* * ru (Russian)
|
||||
* * es-ES (Spanish)
|
||||
* * sv-SE (Swedish)
|
||||
* * th (Thai)
|
||||
* * tr (Turkish)
|
||||
* * uk (Ukrainian)
|
||||
* * vi (Vietnamese)
|
||||
* @see {@link https://discord.com/developers/docs/reference#locales}
|
||||
* @typedef {string} Locale
|
||||
*/
|
||||
|
||||
/**
|
||||
* The locale of the user who invoked this interaction
|
||||
* @type {string}
|
||||
* @see {@link https://discord.com/developers/docs/dispatch/field-values#predefined-field-values-accepted-locales}
|
||||
* @type {Locale}
|
||||
*/
|
||||
this.locale = data.locale;
|
||||
|
||||
/**
|
||||
* The preferred locale from the guild this interaction was sent in
|
||||
* @type {?string}
|
||||
* @type {?Locale}
|
||||
*/
|
||||
this.guildLocale = data.guild_locale ?? null;
|
||||
}
|
||||
@@ -173,6 +208,14 @@ class Interaction extends Base {
|
||||
return InteractionTypes[this.type] === InteractionTypes.APPLICATION_COMMAND && typeof this.targetId !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link ModalSubmitInteraction}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isModalSubmit() {
|
||||
return InteractionTypes[this.type] === InteractionTypes.MODAL_SUBMIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link UserContextMenuInteraction}
|
||||
* @returns {boolean}
|
||||
@@ -226,6 +269,16 @@ class Interaction extends Base {
|
||||
MessageComponentTypes[this.componentType] === MessageComponentTypes.SELECT_MENU
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction can be replied to.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isRepliable() {
|
||||
return ![InteractionTypes.PING, InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE].includes(
|
||||
InteractionTypes[this.type],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Interaction;
|
||||
|
||||
@@ -7,9 +7,9 @@ const { InteractionTypes, MessageComponentTypes } = require('../util/Constants')
|
||||
|
||||
/**
|
||||
* @typedef {CollectorOptions} InteractionCollectorOptions
|
||||
* @property {TextBasedChannels} [channel] The channel to listen to interactions from
|
||||
* @property {TextBasedChannelsResolvable} [channel] The channel to listen to interactions from
|
||||
* @property {MessageComponentType} [componentType] The type of component to listen for
|
||||
* @property {Guild} [guild] The guild to listen to interactions from
|
||||
* @property {GuildResolvable} [guild] The guild to listen to interactions from
|
||||
* @property {InteractionType} [interactionType] The type of interaction to listen for
|
||||
* @property {number} [max] The maximum total amount of interactions to collect
|
||||
* @property {number} [maxComponents] The maximum number of components to collect
|
||||
|
||||
@@ -197,8 +197,11 @@ class Invite extends Base {
|
||||
this.createdTimestamp ??= null;
|
||||
}
|
||||
|
||||
if ('expires_at' in data) this._expiresTimestamp = new Date(data.expires_at).getTime();
|
||||
else this._expiresTimestamp ??= null;
|
||||
if ('expires_at' in data) {
|
||||
this._expiresTimestamp = data.expires_at && Date.parse(data.expires_at);
|
||||
} else {
|
||||
this._expiresTimestamp ??= null;
|
||||
}
|
||||
|
||||
if ('stage_instance' in data) {
|
||||
/**
|
||||
|
||||
@@ -725,6 +725,7 @@ class Message extends Base {
|
||||
|
||||
/**
|
||||
* Pins this message to the channel's pinned messages.
|
||||
* @param {string} [reason] Reason for pinning
|
||||
* @returns {Promise<Message>}
|
||||
* @example
|
||||
* // Pin a message
|
||||
@@ -732,14 +733,15 @@ class Message extends Base {
|
||||
* .then(console.log)
|
||||
* .catch(console.error)
|
||||
*/
|
||||
async pin() {
|
||||
async pin(reason) {
|
||||
if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
|
||||
await this.channel.messages.pin(this.id);
|
||||
await this.channel.messages.pin(this.id, reason);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpins this message from the channel's pinned messages.
|
||||
* @param {string} [reason] Reason for unpinning
|
||||
* @returns {Promise<Message>}
|
||||
* @example
|
||||
* // Unpin a message
|
||||
@@ -747,9 +749,9 @@ class Message extends Base {
|
||||
* .then(console.log)
|
||||
* .catch(console.error)
|
||||
*/
|
||||
async unpin() {
|
||||
async unpin(reason) {
|
||||
if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
|
||||
await this.channel.messages.unpin(this.id);
|
||||
await this.channel.messages.unpin(this.id, reason);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@ class MessageActionRow extends BaseMessageComponent {
|
||||
* Components that can be placed in an action row
|
||||
* * MessageButton
|
||||
* * MessageSelectMenu
|
||||
* @typedef {MessageButton|MessageSelectMenu} MessageActionRowComponent
|
||||
* * TextInputComponent
|
||||
* @typedef {MessageButton|MessageSelectMenu|TextInputComponent} MessageActionRowComponent
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for components that can be placed in an action row
|
||||
* * MessageButtonOptions
|
||||
* * MessageSelectMenuOptions
|
||||
* @typedef {MessageButtonOptions|MessageSelectMenuOptions} MessageActionRowComponentOptions
|
||||
* * TextInputComponentOptions
|
||||
* @typedef {MessageButtonOptions|MessageSelectMenuOptions|TextInputComponentOptions} MessageActionRowComponentOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -124,7 +124,7 @@ class MessageAttachment {
|
||||
|
||||
if ('content_type' in data) {
|
||||
/**
|
||||
* This media type of this attachment
|
||||
* The media type of this attachment
|
||||
* @type {?string}
|
||||
*/
|
||||
this.contentType = data.content_type;
|
||||
|
||||
@@ -101,6 +101,8 @@ class MessageComponentInteraction extends Interaction {
|
||||
followUp() {}
|
||||
deferUpdate() {}
|
||||
update() {}
|
||||
showModal() {}
|
||||
awaitModalSubmit() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(MessageComponentInteraction);
|
||||
|
||||
@@ -209,7 +209,7 @@ class MessageEmbed {
|
||||
this.provider = data.provider
|
||||
? {
|
||||
name: data.provider.name,
|
||||
url: data.provider.name,
|
||||
url: data.provider.url,
|
||||
}
|
||||
: null;
|
||||
|
||||
@@ -430,7 +430,7 @@ class MessageEmbed {
|
||||
*/
|
||||
setFooter(options, deprecatedIconURL) {
|
||||
if (options === null) {
|
||||
this.footer = {};
|
||||
this.footer = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -173,28 +173,35 @@ class MessageMentions {
|
||||
* @typedef {Object} MessageMentionsHasOptions
|
||||
* @property {boolean} [ignoreDirect=false] Whether to ignore direct mentions to the item
|
||||
* @property {boolean} [ignoreRoles=false] Whether to ignore role mentions to a guild member
|
||||
* @property {boolean} [ignoreEveryone=false] Whether to ignore everyone/here mentions
|
||||
* @property {boolean} [ignoreRepliedUser=false] Whether to ignore replied user mention to an user
|
||||
* @property {boolean} [ignoreEveryone=false] Whether to ignore `@everyone`/`@here` mentions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if a user, guild member, role, or channel is mentioned.
|
||||
* Takes into account user mentions, role mentions, and `@everyone`/`@here` mentions.
|
||||
* Checks if a user, guild member, thread member, role, or channel is mentioned.
|
||||
* Takes into account user mentions, role mentions, channel mentions,
|
||||
* replied user mention, and `@everyone`/`@here` mentions.
|
||||
* @param {UserResolvable|RoleResolvable|ChannelResolvable} data The User/Role/Channel to check for
|
||||
* @param {MessageMentionsHasOptions} [options] The options for the check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(data, { ignoreDirect = false, ignoreRoles = false, ignoreEveryone = false } = {}) {
|
||||
if (!ignoreEveryone && this.everyone) return true;
|
||||
const { GuildMember } = require('./GuildMember');
|
||||
if (!ignoreRoles && data instanceof GuildMember) {
|
||||
for (const role of this.roles.values()) if (data.roles.cache.has(role.id)) return true;
|
||||
}
|
||||
has(data, { ignoreDirect = false, ignoreRoles = false, ignoreRepliedUser = false, ignoreEveryone = false } = {}) {
|
||||
const user = this.client.users.resolve(data);
|
||||
const role = this.guild?.roles.resolve(data);
|
||||
const channel = this.client.channels.resolve(data);
|
||||
|
||||
if (!ignoreRepliedUser && this.users.has(this.repliedUser?.id) && this.repliedUser?.id === user?.id) return true;
|
||||
if (!ignoreDirect) {
|
||||
const id =
|
||||
this.guild?.roles.resolveId(data) ?? this.client.channels.resolveId(data) ?? this.client.users.resolveId(data);
|
||||
|
||||
return typeof id === 'string' && (this.users.has(id) || this.channels.has(id) || this.roles.has(id));
|
||||
if (this.users.has(user?.id)) return true;
|
||||
if (this.roles.has(role?.id)) return true;
|
||||
if (this.channels.has(channel?.id)) return true;
|
||||
}
|
||||
if (user && !ignoreEveryone && this.everyone) return true;
|
||||
if (!ignoreRoles) {
|
||||
const member = this.guild?.members.resolve(data);
|
||||
if (member) {
|
||||
for (const mentionedRole of this.roles.values()) if (member.roles.cache.has(mentionedRole.id)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -149,11 +149,17 @@ class MessagePayload {
|
||||
}
|
||||
|
||||
let flags;
|
||||
if (this.isMessage || this.isMessageManager) {
|
||||
if (
|
||||
typeof this.options.flags !== 'undefined' ||
|
||||
(this.isMessage && typeof this.options.reply === 'undefined') ||
|
||||
this.isMessageManager
|
||||
) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags?.bitfield;
|
||||
} else if (isInteraction && this.options.ephemeral) {
|
||||
flags = MessageFlags.FLAGS.EPHEMERAL;
|
||||
}
|
||||
|
||||
if (isInteraction && this.options.ephemeral) {
|
||||
flags |= MessageFlags.FLAGS.EPHEMERAL;
|
||||
}
|
||||
|
||||
let allowedMentions =
|
||||
|
||||
@@ -114,7 +114,7 @@ class MessageReaction {
|
||||
if (this.partial) return;
|
||||
this.users.cache.set(user.id, user);
|
||||
if (!this.me || user.id !== this.message.client.user.id || this.count === 0) this.count++;
|
||||
this.me ??= user.id === this.message.client.user.id;
|
||||
this.me ||= user.id === this.message.client.user.id;
|
||||
}
|
||||
|
||||
_remove(user) {
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Modal;
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const { TypeError } = require('../errors');
|
||||
const { MessageComponentTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* A resolver for modal submit interaction text inputs.
|
||||
*/
|
||||
class ModalSubmitFieldsResolver {
|
||||
constructor(components) {
|
||||
/**
|
||||
* The components within the modal
|
||||
* @type {PartialModalActionRow[]} The components in the modal
|
||||
*/
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
/**
|
||||
* The extracted fields from the modal
|
||||
* @type {PartialInputTextData[]} The fields in the modal
|
||||
* @private
|
||||
*/
|
||||
get _fields() {
|
||||
return this.components.reduce((previous, next) => previous.concat(next.components), []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a field given a custom id from a component
|
||||
* @param {string} customId The custom id of the component
|
||||
* @returns {?PartialInputTextData}
|
||||
*/
|
||||
getField(customId) {
|
||||
const field = this._fields.find(f => f.customId === 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 = MessageComponentTypes[MessageComponentTypes.TEXT_INPUT];
|
||||
if (field.type !== expectedType) {
|
||||
throw new TypeError('MODAL_SUBMIT_INTERACTION_FIELD_TYPE', customId, field.type, expectedType);
|
||||
}
|
||||
return field.value;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModalSubmitFieldsResolver;
|
||||
@@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
const Interaction = require('./Interaction');
|
||||
const InteractionWebhook = require('./InteractionWebhook');
|
||||
const ModalSubmitFieldsResolver = require('./ModalSubmitFieldsResolver');
|
||||
const InteractionResponses = require('./interfaces/InteractionResponses');
|
||||
const { MessageComponentTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents a modal submit interaction.
|
||||
* @extends {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;
|
||||
|
||||
/**
|
||||
* @typedef {Object} PartialTextInputData
|
||||
* @property {string} [customId] A unique string to be sent in the interaction when submitted
|
||||
* @property {MessageComponentType} [type] The type of this component
|
||||
* @property {string} [value] Value of this text input component
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} PartialModalActionRow
|
||||
* @property {MessageComponentType} [type] The type of this component
|
||||
* @property {PartialTextInputData[]} [components] Partial text input components
|
||||
*/
|
||||
|
||||
/**
|
||||
* The inputs within the modal
|
||||
* @type {PartialModalActionRow[]}
|
||||
*/
|
||||
this.components =
|
||||
data.data.components?.map(c => ({
|
||||
type: MessageComponentTypes[c.type],
|
||||
components: ModalSubmitInteraction.transformComponent(c),
|
||||
})) ?? [];
|
||||
|
||||
/**
|
||||
* The message associated with this interaction
|
||||
* @type {Message|APIMessage|null}
|
||||
*/
|
||||
this.message = data.message ? this.channel?.messages._add(data.message) ?? data.message : null;
|
||||
|
||||
/**
|
||||
* The fields within the modal
|
||||
* @type {ModalSubmitFieldsResolver}
|
||||
*/
|
||||
this.fields = new ModalSubmitFieldsResolver(this.components);
|
||||
|
||||
/**
|
||||
* Whether the reply to this interaction has been deferred
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.deferred = false;
|
||||
|
||||
/**
|
||||
* Whether the reply to this interaction is ephemeral
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.ephemeral = null;
|
||||
|
||||
/**
|
||||
* Whether this interaction has already been replied to
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.replied = false;
|
||||
|
||||
/**
|
||||
* 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 {PartialTextInputData[]}
|
||||
*/
|
||||
static transformComponent(rawComponent) {
|
||||
return rawComponent.components.map(c => ({
|
||||
value: c.value,
|
||||
type: MessageComponentTypes[c.type],
|
||||
customId: c.custom_id,
|
||||
}));
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by InteractionResponses
|
||||
/* eslint-disable no-empty-function */
|
||||
deferReply() {}
|
||||
reply() {}
|
||||
fetchReply() {}
|
||||
editReply() {}
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
update() {}
|
||||
deferUpdate() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(ModalSubmitInteraction, ['showModal', 'awaitModalSubmit']);
|
||||
|
||||
module.exports = ModalSubmitInteraction;
|
||||
+28
-11
@@ -351,13 +351,21 @@ class RichPresenceAssets {
|
||||
* @returns {?string}
|
||||
*/
|
||||
smallImageURL({ format, size } = {}) {
|
||||
return (
|
||||
this.smallImage &&
|
||||
this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationId, this.smallImage, {
|
||||
format,
|
||||
size,
|
||||
})
|
||||
);
|
||||
if (!this.smallImage) return null;
|
||||
if (this.smallImage.includes(':')) {
|
||||
const [platform, id] = this.smallImage.split(':');
|
||||
switch (platform) {
|
||||
case 'mp':
|
||||
return `https://media.discordapp.net/${id}`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationId, this.smallImage, {
|
||||
format,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,11 +375,20 @@ class RichPresenceAssets {
|
||||
*/
|
||||
largeImageURL({ format, size } = {}) {
|
||||
if (!this.largeImage) return null;
|
||||
if (/^spotify:/.test(this.largeImage)) {
|
||||
return `https://i.scdn.co/image/${this.largeImage.slice(8)}`;
|
||||
} else if (/^twitch:/.test(this.largeImage)) {
|
||||
return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${this.largeImage.slice(7)}.png`;
|
||||
if (this.largeImage.includes(':')) {
|
||||
const [platform, id] = this.largeImage.split(':');
|
||||
switch (platform) {
|
||||
case 'mp':
|
||||
return `https://media.discordapp.net/${id}`;
|
||||
case 'spotify':
|
||||
return `https://i.scdn.co/image/${id}`;
|
||||
case 'twitch':
|
||||
return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${id}.png`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationId, this.largeImage, {
|
||||
format,
|
||||
size,
|
||||
|
||||
+2
-15
@@ -5,7 +5,6 @@ const Base = require('./Base');
|
||||
const { Error } = require('../errors');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
let deprecationEmittedForComparePositions = false;
|
||||
|
||||
@@ -399,20 +398,8 @@ class Role extends Base {
|
||||
* .then(updated => console.log(`Role position: ${updated.position}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async setPosition(position, { relative, reason } = {}) {
|
||||
const updatedRoles = await Util.setPosition(
|
||||
this,
|
||||
position,
|
||||
relative,
|
||||
this.guild._sortedRoles(),
|
||||
this.client.api.guilds(this.guild.id).roles,
|
||||
reason,
|
||||
);
|
||||
this.client.actions.GuildRolesPositionUpdate.handle({
|
||||
guild_id: this.guild.id,
|
||||
roles: updatedRoles,
|
||||
});
|
||||
return this;
|
||||
setPosition(position, options = {}) {
|
||||
return this.guild.roles.setPosition(this, position, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,14 +55,15 @@ class StageChannel extends BaseGuildVoiceChannel {
|
||||
/**
|
||||
* Sets the RTC region of the channel.
|
||||
* @name StageChannel#setRTCRegion
|
||||
* @param {?string} region The new region of the channel. Set to `null` to remove a specific region for the channel
|
||||
* @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel
|
||||
* @param {string} [reason] The reason for modifying this region.
|
||||
* @returns {Promise<StageChannel>}
|
||||
* @example
|
||||
* // Set the RTC region to europe
|
||||
* stageChannel.setRTCRegion('europe');
|
||||
* // Set the RTC region to sydney
|
||||
* stageChannel.setRTCRegion('sydney');
|
||||
* @example
|
||||
* // Remove a fixed region for this channel - let Discord decide automatically
|
||||
* stageChannel.setRTCRegion(null);
|
||||
* stageChannel.setRTCRegion(null, 'We want to let Discord decide.');
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,16 @@ class StageInstance extends Base {
|
||||
} else {
|
||||
this.discoverableDisabled ??= null;
|
||||
}
|
||||
|
||||
if ('guild_scheduled_event_id' in data) {
|
||||
/**
|
||||
* The associated guild scheduled event id of this stage instance
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildScheduledEventId = data.guild_scheduled_event_id;
|
||||
} else {
|
||||
this.guildScheduledEventId ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +93,15 @@ class StageInstance extends Base {
|
||||
return this.client.channels.resolve(this.channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The associated guild scheduled event of this stage instance
|
||||
* @type {?GuildScheduledEvent}
|
||||
* @readonly
|
||||
*/
|
||||
get guildScheduledEvent() {
|
||||
return this.guild?.scheduledEvents.resolve(this.guildScheduledEventId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the stage instance has been deleted
|
||||
* @type {boolean}
|
||||
|
||||
@@ -228,10 +228,7 @@ class Sticker extends Base {
|
||||
async fetchUser() {
|
||||
if (this.partial) await this.fetch();
|
||||
if (!this.guildId) throw new Error('NOT_GUILD_STICKER');
|
||||
|
||||
const data = await this.client.api.guilds(this.guildId).stickers(this.id).get();
|
||||
this._patch(data);
|
||||
return this.user;
|
||||
return this.guild.stickers.fetchUser(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ const GuildChannel = require('./GuildChannel');
|
||||
|
||||
/**
|
||||
* Represents a guild store channel on Discord.
|
||||
* <warn>Store channels are deprecated and will be removed from Discord in March 2022. See
|
||||
* <warn>Store channels have been removed from Discord. See
|
||||
* [Self-serve Game Selling Deprecation](https://support-dev.discord.com/hc/en-us/articles/4414590563479)
|
||||
* for more information.</warn>
|
||||
* @extends {GuildChannel}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { RangeError } = require('../errors');
|
||||
const { TextInputStyles, MessageComponentTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents a text input component in a modal
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
|
||||
class TextInputComponent extends BaseMessageComponent {
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} TextInputComponentOptions
|
||||
* @property {string} [customId] A unique string to be sent in the interaction when submitted
|
||||
* @property {string} [label] The text to be displayed above this text input component
|
||||
* @property {number} [maxLength] Maximum length of text that can be entered
|
||||
* @property {number} [minLength] Minimum length of text required to be entered
|
||||
* @property {string} [placeholder] Custom placeholder text to display when no text is entered
|
||||
* @property {boolean} [required] Whether or not this text input component is required
|
||||
* @property {TextInputStyleResolvable} [style] The style of this text input component
|
||||
* @property {string} [value] Value of this text input component
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {TextInputComponent|TextInputComponentOptions} [data={}] TextInputComponent to clone or raw data
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
super({ type: 'TEXT_INPUT' });
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* A unique string to be sent in the interaction when submitted
|
||||
* @type {?string}
|
||||
*/
|
||||
this.customId = data.custom_id ?? data.customId ?? null;
|
||||
|
||||
/**
|
||||
* The text to be displayed above this text input component
|
||||
* @type {?string}
|
||||
*/
|
||||
this.label = data.label ?? null;
|
||||
|
||||
/**
|
||||
* Maximum length of text that can be entered
|
||||
* @type {?number}
|
||||
*/
|
||||
this.maxLength = data.max_length ?? data.maxLength ?? null;
|
||||
|
||||
/**
|
||||
* Minimum length of text required to be entered
|
||||
* @type {?string}
|
||||
*/
|
||||
this.minLength = data.min_length ?? data.minLength ?? null;
|
||||
|
||||
/**
|
||||
* Custom placeholder text to display when no text is entered
|
||||
* @type {?string}
|
||||
*/
|
||||
this.placeholder = data.placeholder ?? null;
|
||||
|
||||
/**
|
||||
* Whether or not this text input component is required
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.required = data.required ?? false;
|
||||
|
||||
/**
|
||||
* The style of this text input component
|
||||
* @type {?TextInputStyle}
|
||||
*/
|
||||
this.style = data.style ? TextInputComponent.resolveStyle(data.style) : null;
|
||||
|
||||
/**
|
||||
* Value of this text input component
|
||||
* @type {?string}
|
||||
*/
|
||||
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
|
||||
* @param {string} value Value of this text input component
|
||||
* @returns {TextInputComponent}
|
||||
*/
|
||||
setValue(value) {
|
||||
this.value = Util.verifyString(value, RangeError, 'TEXT_INPUT_VALUE');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the text input component into a plain object
|
||||
* @returns {APITextInput} The raw data of this text input component
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
custom_id: this.customId,
|
||||
label: this.label,
|
||||
max_length: this.maxLength,
|
||||
min_length: this.minLength,
|
||||
placeholder: this.placeholder,
|
||||
required: this.required,
|
||||
style: TextInputStyles[this.style],
|
||||
type: MessageComponentTypes[this.type],
|
||||
value: this.value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a TextInputStyle. This can be
|
||||
* * TextInputStyle
|
||||
* * number
|
||||
* @typedef {number|TextInputStyle} TextInputStyleResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves the style of a text input component
|
||||
* @param {TextInputStyleResolvable} style The style to resolve
|
||||
* @returns {TextInputStyle}
|
||||
* @private
|
||||
*/
|
||||
static resolveStyle(style) {
|
||||
return typeof style === 'string' ? style : TextInputStyles[style];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextInputComponent;
|
||||
@@ -6,6 +6,7 @@ const { RangeError } = require('../errors');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const ThreadMemberManager = require('../managers/ThreadMemberManager');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents a thread channel on Discord.
|
||||
@@ -100,6 +101,11 @@ class ThreadChannel extends Channel {
|
||||
* @type {?number}
|
||||
*/
|
||||
this.archiveTimestamp = new Date(data.thread_metadata.archive_timestamp).getTime();
|
||||
|
||||
if ('create_timestamp' in data.thread_metadata) {
|
||||
// Note: this is needed because we can't assign directly to getters
|
||||
this._createdTimestamp = Date.parse(data.thread_metadata.create_timestamp);
|
||||
}
|
||||
} else {
|
||||
this.locked ??= null;
|
||||
this.archived ??= null;
|
||||
@@ -108,6 +114,8 @@ class ThreadChannel extends Channel {
|
||||
this.invitable ??= null;
|
||||
}
|
||||
|
||||
this._createdTimestamp ??= this.type === 'GUILD_PRIVATE_THREAD' ? super.createdTimestamp : null;
|
||||
|
||||
if ('owner_id' in data) {
|
||||
/**
|
||||
* The id of the member who created this thread
|
||||
@@ -176,6 +184,16 @@ class ThreadChannel extends Channel {
|
||||
if (data.messages) for (const message of data.messages) this.messages._add(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp when this thread was created. This isn't available for threads
|
||||
* created before 2022-01-09
|
||||
* @type {?number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return this._createdTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of associated guild member objects of this thread's members
|
||||
* @type {Collection<Snowflake, GuildMember>}
|
||||
@@ -196,6 +214,15 @@ class ThreadChannel extends Channel {
|
||||
return new Date(this.archiveTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the thread was created at
|
||||
* @type {?Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return this.createdTimestamp && new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent channel of this thread
|
||||
* @type {?(NewsChannel|TextChannel)}
|
||||
@@ -288,14 +315,8 @@ class ThreadChannel extends Channel {
|
||||
*/
|
||||
async edit(data, reason) {
|
||||
let autoArchiveDuration = data.autoArchiveDuration;
|
||||
if (data.autoArchiveDuration === 'MAX') {
|
||||
autoArchiveDuration = 1440;
|
||||
if (this.guild.features.includes('SEVEN_DAY_THREAD_ARCHIVE')) {
|
||||
autoArchiveDuration = 10080;
|
||||
} else if (this.guild.features.includes('THREE_DAY_THREAD_ARCHIVE')) {
|
||||
autoArchiveDuration = 4320;
|
||||
}
|
||||
}
|
||||
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.guild);
|
||||
|
||||
const newData = await this.client.api.channels(this.id).patch({
|
||||
data: {
|
||||
name: (data.name ?? this.name).trim(),
|
||||
@@ -487,7 +508,15 @@ class ThreadChannel extends Channel {
|
||||
* @readonly
|
||||
*/
|
||||
get unarchivable() {
|
||||
return this.archived && (this.locked ? this.manageable : this.sendable);
|
||||
return this.archived && this.sendable && (!this.locked || this.manageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this thread is a private thread
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isPrivate() {
|
||||
return this.type === 'GUILD_PRIVATE_THREAD';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -501,7 +530,7 @@ class ThreadChannel extends Channel {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete(reason) {
|
||||
await this.client.api.channels(this.id).delete({ reason });
|
||||
await this.guild.channels.delete(this.id, reason);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const process = require('node:process');
|
||||
const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel');
|
||||
const { VideoQualityModes } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
|
||||
let deprecationEmittedForEditable = false;
|
||||
@@ -11,6 +12,20 @@ let deprecationEmittedForEditable = false;
|
||||
* @extends {BaseGuildVoiceChannel}
|
||||
*/
|
||||
class VoiceChannel extends BaseGuildVoiceChannel {
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
if ('video_quality_mode' in data) {
|
||||
/**
|
||||
* The camera video quality mode of the channel.
|
||||
* @type {?VideoQualityMode}
|
||||
*/
|
||||
this.videoQualityMode = VideoQualityModes[data.videoQualityMode];
|
||||
} else {
|
||||
this.videoQualityMode ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the channel is editable by the client user
|
||||
* @type {boolean}
|
||||
@@ -87,17 +102,28 @@ class VoiceChannel extends BaseGuildVoiceChannel {
|
||||
return this.edit({ userLimit }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the camera video quality mode of the channel.
|
||||
* @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode.
|
||||
* @param {string} [reason] Reason for changing the camera video quality mode.
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
*/
|
||||
setVideoQualityMode(videoQualityMode, reason) {
|
||||
return this.edit({ videoQualityMode }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the RTC region of the channel.
|
||||
* @name VoiceChannel#setRTCRegion
|
||||
* @param {?string} region The new region of the channel. Set to `null` to remove a specific region for the channel
|
||||
* @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel
|
||||
* @param {string} [reason] The reason for modifying this region.
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @example
|
||||
* // Set the RTC region to europe
|
||||
* voiceChannel.setRTCRegion('europe');
|
||||
* // Set the RTC region to sydney
|
||||
* voiceChannel.setRTCRegion('sydney');
|
||||
* @example
|
||||
* // Remove a fixed region for this channel - let Discord decide automatically
|
||||
* voiceChannel.setRTCRegion(null);
|
||||
* voiceChannel.setRTCRegion(null, 'We want to let Discord decide.');
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class VoiceRegion {
|
||||
/**
|
||||
* Whether the region is VIP-only
|
||||
* @type {boolean}
|
||||
* @deprecated This property is no longer being sent by the API.
|
||||
*/
|
||||
this.vip = data.vip;
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class VoiceState extends Base {
|
||||
* The time at which the member requested to speak. This property is specific to stage channels only.
|
||||
* @type {?number}
|
||||
*/
|
||||
this.requestToSpeakTimestamp = new Date(data.request_to_speak_timestamp).getTime();
|
||||
this.requestToSpeakTimestamp = data.request_to_speak_timestamp && Date.parse(data.request_to_speak_timestamp);
|
||||
} else {
|
||||
this.requestToSpeakTimestamp ??= null;
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ class Webhook {
|
||||
* @property {string} [username=this.name] Username override for the message
|
||||
* @property {string} [avatarURL] Avatar URL override for the message
|
||||
* @property {Snowflake} [threadId] The id of the thread in the channel to send to.
|
||||
* @property {MessageFlags} [flags] Which flags to set for the message. Only `SUPPRESS_EMBEDS` can be set.
|
||||
* <info>For interaction webhooks, this property is ignored</info>
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const process = require('node:process');
|
||||
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
const Base = require('../Base');
|
||||
|
||||
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
||||
|
||||
let deprecationEmittedForFetchAssets = false;
|
||||
|
||||
/**
|
||||
* Represents an OAuth2 Application.
|
||||
* @abstract
|
||||
@@ -13,10 +16,7 @@ const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
||||
class Application extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
|
||||
if (data) {
|
||||
this._patch(data);
|
||||
}
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
@@ -106,8 +106,18 @@ class Application extends Base {
|
||||
/**
|
||||
* Gets the application's rich presence assets.
|
||||
* @returns {Promise<Array<ApplicationAsset>>}
|
||||
* @deprecated This will be removed in the next major as it is unsupported functionality.
|
||||
*/
|
||||
async fetchAssets() {
|
||||
if (!deprecationEmittedForFetchAssets) {
|
||||
process.emitWarning(
|
||||
'Application#fetchAssets is deprecated as it is unsupported and will be removed in the next major version.',
|
||||
'DeprecationWarning',
|
||||
);
|
||||
|
||||
deprecationEmittedForFetchAssets = true;
|
||||
}
|
||||
|
||||
const assets = await this.client.api.oauth2.applications(this.id).assets.get();
|
||||
return assets.map(a => ({
|
||||
id: a.id,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const { Error } = require('../../errors');
|
||||
const { InteractionResponseTypes } = require('../../util/Constants');
|
||||
const { InteractionResponseTypes, InteractionTypes } = require('../../util/Constants');
|
||||
const MessageFlags = require('../../util/MessageFlags');
|
||||
const InteractionCollector = require('../InteractionCollector');
|
||||
const MessagePayload = require('../MessagePayload');
|
||||
const Modal = require('../Modal');
|
||||
|
||||
/**
|
||||
* Interface for classes that support shared interaction response types.
|
||||
@@ -28,6 +30,8 @@ class InteractionResponses {
|
||||
* @typedef {BaseMessageOptions} InteractionReplyOptions
|
||||
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
|
||||
* @property {boolean} [fetchReply] Whether to fetch the reply
|
||||
* @property {MessageFlags} [flags] Which flags to set for the message.
|
||||
* Only `SUPPRESS_EMBEDS` and `EPHEMERAL` can be set.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -224,6 +228,56 @@ class InteractionResponses {
|
||||
return options.fetchReply ? this.fetchReply() : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a modal component
|
||||
* @param {Modal|ModalOptions} modal The modal to show
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async showModal(modal) {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
|
||||
const _modal = modal instanceof Modal ? modal : new Modal(modal);
|
||||
await this.client.api.interactions(this.id, this.token).callback.post({
|
||||
data: {
|
||||
type: InteractionResponseTypes.MODAL,
|
||||
data: _modal.toJSON(),
|
||||
},
|
||||
});
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing the same properties as CollectorOptions, but a few more:
|
||||
* @typedef {Object} AwaitModalSubmitOptions
|
||||
* @property {CollectorFilter} [filter] The filter applied to this collector
|
||||
* @property {number} time Time to wait for an interaction before rejecting
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collects a single modal submit interaction that passes the filter.
|
||||
* The Promise will reject if the time expires.
|
||||
* @param {AwaitModalSubmitOptions} options Options to pass to the internal collector
|
||||
* @returns {Promise<ModalSubmitInteraction>}
|
||||
* @example
|
||||
* // Collect a modal submit interaction
|
||||
* const filter = (interaction) => interaction.customId === 'modal';
|
||||
* interaction.awaitModalSubmit({ filter, time: 15_000 })
|
||||
* .then(interaction => console.log(`${interaction.customId} was submitted!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
awaitModalSubmit(options) {
|
||||
if (typeof options.time !== 'number') throw new Error('INVALID_TYPE', 'time', 'number');
|
||||
const _options = { ...options, max: 1, interactionType: InteractionTypes.MODAL_SUBMIT };
|
||||
return new Promise((resolve, reject) => {
|
||||
const collector = new InteractionCollector(this.client, _options);
|
||||
collector.once('end', (interactions, reason) => {
|
||||
const interaction = interactions.first();
|
||||
if (interaction) resolve(interaction);
|
||||
else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static applyToClass(structure, ignore = []) {
|
||||
const props = [
|
||||
'deferReply',
|
||||
@@ -234,6 +288,8 @@ class InteractionResponses {
|
||||
'followUp',
|
||||
'deferUpdate',
|
||||
'update',
|
||||
'showModal',
|
||||
'awaitModalSubmit',
|
||||
];
|
||||
|
||||
for (const prop of props) {
|
||||
|
||||
@@ -74,6 +74,7 @@ class TextBasedChannel {
|
||||
* @typedef {BaseMessageOptions} MessageOptions
|
||||
* @property {ReplyOptions} [reply] The options for replying to a message
|
||||
* @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
|
||||
* @property {MessageFlags} [flags] Which flags to set for the message. Only `SUPPRESS_EMBEDS` can be set.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -129,7 +130,7 @@ class TextBasedChannel {
|
||||
* channel.send({
|
||||
* files: [{
|
||||
* attachment: 'entire/path/to/file.jpg',
|
||||
* name: 'file.jpg'
|
||||
* name: 'file.jpg',
|
||||
* description: 'A description of the file'
|
||||
* }]
|
||||
* })
|
||||
@@ -237,7 +238,7 @@ class TextBasedChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button interaction collector.
|
||||
* Creates a component interaction collector.
|
||||
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
|
||||
* @returns {InteractionCollector}
|
||||
* @example
|
||||
|
||||
Reference in New Issue
Block a user