New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

discord.js

Package Overview
Dependencies
Maintainers
4
Versions
1796
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

discord.js - npm Package Compare versions

Comparing version 13.0.0-dev.086c3f0799d65c64c4e60d6370246a37a27a1eab to 13.0.0-dev.35c2225f5035fce47b0defc12754bda5901a453c

src/index.mjs

19

package.json
{
"name": "discord.js",
"version": "13.0.0-dev.086c3f0799d65c64c4e60d6370246a37a27a1eab",
"version": "13.0.0-dev.35c2225f5035fce47b0defc12754bda5901a453c",
"description": "A powerful library for interacting with the Discord API",
"main": "./src/index.js",
"module": "./src/index.mjs",
"types": "./typings/index.d.ts",
"exports": {
".": [
{
"require": "./src/index.js",
"import": "./esm/discord.mjs"
},
"./src/index.js"
],
"./esm": "./esm/discord.mjs"
"require": "./src/index.js",
"import": "./src/index.mjs"
},

@@ -26,3 +21,3 @@ "scripts": {

"prettier": "prettier --write src/**/*.js typings/**/*.ts",
"prepublishOnly": "npm run test",
"prepublishOnly": "npm run test && gen-esm-wrapper ./src/index.js ./src/index.mjs",
"prepare": "is-ci || husky install",

@@ -58,4 +53,2 @@ "changelog": "conventional-changelog -p angular -i RELEASE_CHANGELOG.md -s"

"node-fetch": "^2.6.1",
"prism-media": "^1.2.9",
"tweetnacl": "^1.0.3",
"ws": "^7.4.6"

@@ -67,2 +60,3 @@ },

"@discordjs/docgen": "^0.10.0",
"@discordjs/voice": "^0.3.0",
"@types/node": "^12.12.6",

@@ -76,2 +70,3 @@ "conventional-changelog-cli": "^2.1.1",

"eslint-plugin-prettier": "^3.4.0",
"gen-esm-wrapper": "^1.1.1",
"husky": "^6.0.0",

@@ -78,0 +73,0 @@ "is-ci": "^3.0.0",

@@ -8,3 +8,3 @@ <div align="center">

<p>
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=7289da&logo=discord&logoColor=white" alt="Discord server" /></a>
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a>

@@ -48,12 +48,2 @@ <a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="NPM downloads" /></a>

Without voice support: `npm install discord.js`
With voice support ([@discordjs/opus](https://www.npmjs.com/package/@discordjs/opus)): `npm install discord.js @discordjs/opus`
With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript`
### Audio engines
The preferred audio engine is @discordjs/opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose @discordjs/opus.
Using opusscript is only recommended for development environments where @discordjs/opus is tough to get working.
For production bots, using @discordjs/opus should be considered a necessity, especially if they're going to be running on multiple servers.
### Optional packages

@@ -68,2 +58,3 @@

- [utf-8-validate](https://www.npmjs.com/package/utf-8-validate) in combination with `bufferutil` for much faster WebSocket processing (`npm install utf-8-validate`)
- [@discordjs/voice](https://github.com/discordjs/voice) for interacting with the Discord Voice API

@@ -70,0 +61,0 @@ ## Example usage

@@ -40,3 +40,3 @@ 'use strict';

for (const channel of guild.channels.cache.values()) this.client.channels.remove(channel.id);
guild.me?.voice.connection?.disconnect();
client.voice.adapters.get(data.id)?.destroy();

@@ -43,0 +43,0 @@ // Delete guild

@@ -258,3 +258,3 @@ 'use strict';

.invites(code)
.get({ query: { with_counts: true } })
.get({ query: { with_counts: true, with_expiration: true } })
.then(data => new Invite(this, data));

@@ -496,2 +496,8 @@ }

}
if (
typeof options.rejectOnRateLimit !== 'undefined' &&
!(typeof options.rejectOnRateLimit === 'function' || Array.isArray(options.rejectOnRateLimit))
) {
throw new TypeError('CLIENT_INVALID_OPTION', 'rejectOnRateLimit', 'an array or a function');
}
}

@@ -498,0 +504,0 @@ }

'use strict';
const VoiceBroadcast = require('./VoiceBroadcast');
const VoiceConnection = require('./VoiceConnection');
const { Error } = require('../../errors');
const Collection = require('../../util/Collection');
const { Events } = require('../../util/Constants');

@@ -22,96 +19,27 @@ /**

/**
* A collection mapping connection IDs to the Connection objects
* @type {Collection<Snowflake, VoiceConnection>}
* Maps guild IDs to voice adapters created for use with @discordjs/voice.
* @type {Map<Snowflake, Object>}
*/
this.connections = new Collection();
this.adapters = new Map();
/**
* Active voice broadcasts that have been created
* @type {VoiceBroadcast[]}
*/
this.broadcasts = [];
client.on(Events.SHARD_DISCONNECT, (_, shardID) => {
for (const [guildID, adapter] of this.adapters.entries()) {
if (client.guilds.cache.get(guildID)?.shardID === shardID) {
adapter.destroy();
}
}
});
}
/**
* Creates a voice broadcast.
* @returns {VoiceBroadcast}
*/
createBroadcast() {
const broadcast = new VoiceBroadcast(this.client);
this.broadcasts.push(broadcast);
return broadcast;
onVoiceServer(payload) {
this.adapters.get(payload.guild_id)?.onVoiceServerUpdate(payload);
}
onVoiceServer({ guild_id, token, endpoint }) {
this.client.emit('debug', `[VOICE] voiceServer guild: ${guild_id} token: ${token} endpoint: ${endpoint}`);
const connection = this.connections.get(guild_id);
if (connection) connection.setTokenAndEndpoint(token, endpoint);
}
onVoiceStateUpdate({ guild_id, session_id, channel_id }) {
const connection = this.connections.get(guild_id);
this.client.emit('debug', `[VOICE] connection? ${!!connection}, ${guild_id} ${session_id} ${channel_id}`);
if (!connection) return;
if (!channel_id) {
connection._disconnect();
this.connections.delete(guild_id);
return;
onVoiceStateUpdate(payload) {
if (payload.guild_id && payload.session_id && payload.user_id === this.client.user?.id) {
this.adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload);
}
const channel = this.client.channels.cache.get(channel_id);
if (channel) {
connection.channel = channel;
connection.setSessionID(session_id);
} else {
this.client.emit('debug', `[VOICE] disconnecting from guild ${guild_id} as channel ${channel_id} is uncached`);
connection.disconnect();
}
}
/**
* Sets up a request to join a voice or stage channel.
* @param {VoiceChannel|StageChannel} channel The channel to join
* @returns {Promise<VoiceConnection>}
* @private
*/
joinChannel(channel) {
return new Promise((resolve, reject) => {
if (!channel.joinable) {
throw new Error('VOICE_JOIN_CHANNEL', channel.full);
}
let connection = this.connections.get(channel.guild.id);
if (connection) {
if (connection.channel.id !== channel.id) {
this.connections.get(channel.guild.id).updateChannel(channel);
}
resolve(connection);
return;
} else {
connection = new VoiceConnection(this, channel);
connection.on('debug', msg =>
this.client.emit('debug', `[VOICE (${channel.guild.id}:${connection.status})]: ${msg}`),
);
connection.authenticate();
this.connections.set(channel.guild.id, connection);
}
connection.once('failed', reason => {
this.connections.delete(channel.guild.id);
reject(reason);
});
connection.on('error', reject);
connection.once('authenticated', () => {
connection.once('ready', () => {
resolve(connection);
connection.removeListener('error', reject);
});
connection.once('disconnect', () => this.connections.delete(channel.guild.id));
});
});
}
}
module.exports = ClientVoiceManager;

@@ -27,2 +27,15 @@ 'use strict';

}
// These are here only for documentation purposes - they are implemented by Webhook
/* eslint-disable no-empty-function */
send() {}
sendSlackMessage() {}
fetchMessage() {}
edit() {}
editMessage() {}
delete() {}
deleteMessage() {}
get createdTimestamp() {}
get createdAt() {}
get url() {}
}

@@ -29,0 +42,0 @@

'use strict';
const { Events, InteractionTypes } = require('../../../util/Constants');
let Structures;
const { Events, InteractionTypes, MessageComponentTypes } = require('../../../util/Constants');
const Structures = require('../../../util/Structures');
module.exports = (client, { d: data }) => {
if (data.type === InteractionTypes.APPLICATION_COMMAND) {
if (!Structures) Structures = require('../../../util/Structures');
const CommandInteraction = Structures.get('CommandInteraction');
const interaction = new CommandInteraction(client, data);
/**
* Emitted when an interaction is created.
* @event Client#interaction
* @param {Interaction} interaction The interaction which was created
*/
client.emit(Events.INTERACTION_CREATE, interaction);
return;
let InteractionType;
switch (data.type) {
case InteractionTypes.APPLICATION_COMMAND:
InteractionType = Structures.get('CommandInteraction');
break;
case InteractionTypes.MESSAGE_COMPONENT:
switch (data.data.component_type) {
case MessageComponentTypes.BUTTON:
InteractionType = Structures.get('ButtonInteraction');
break;
default:
client.emit(
Events.DEBUG,
`[INTERACTION] Received component interaction with unknown type: ${data.data.component_type}`,
);
return;
}
break;
default:
client.emit(Events.DEBUG, `[INTERACTION] Received interaction with unknown type: ${data.type}`);
return;
}
client.emit(Events.DEBUG, `[INTERACTION] Received interaction with unknown type: ${data.type}`);
/**
* Emitted when an interaction is created.
* @event Client#interaction
* @param {Interaction} interaction The interaction which was created
*/
client.emit(Events.INTERACTION_CREATE, new InteractionType(client, data));
};

@@ -6,3 +6,3 @@ 'use strict';

const PacketHandlers = require('./handlers');
const { Error: DJSError } = require('../../errors');
const { Error } = require('../../errors');
const Collection = require('../../util/Collection');

@@ -72,3 +72,3 @@ const { Events, ShardEvents, Status, WSCodes, WSEvents } = require('../../util/Constants');

* An array of queued events before this WebSocketManager became ready
* @type {object[]}
* @type {Object[]}
* @private

@@ -125,3 +125,3 @@ * @name WebSocketManager#packetQueue

async connect() {
const invalidToken = new DJSError(WSCodes[4004]);
const invalidToken = new Error(WSCodes[4004]);
const {

@@ -247,3 +247,3 @@ url: gatewayURL,

if (error && error.code && UNRECOVERABLE_CLOSE_CODES.includes(error.code)) {
throw new DJSError(WSCodes[error.code]);
throw new Error(WSCodes[error.code]);
// Undefined if session is invalid, error event for regular closes

@@ -250,0 +250,0 @@ } else if (!error || error.code) {

@@ -603,3 +603,3 @@ 'use strict';

this.debug(`[IDENTIFY] Shard ${this.id}/${client.options.shardCount}`);
this.debug(`[IDENTIFY] Shard ${this.id}/${client.options.shardCount} with intents: ${d.intents}`);
this.send({ op: OPCodes.IDENTIFY, d }, true);

@@ -606,0 +606,0 @@ }

@@ -17,2 +17,3 @@ 'use strict';

WS_NOT_OPEN: (data = 'data') => `Websocket not open to send ${data}`,
MANAGER_DESTROYED: 'Manager was destroyed.',

@@ -27,2 +28,3 @@ BITFIELD_INVALID: bit => `Invalid bitfield flag or number: ${bit}.`,

SHARDING_IN_PROCESS: 'Shards are still being spawned.',
SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function',
SHARDING_SHARD_NOT_FOUND: id => `Shard ${id} could not be found.`,

@@ -49,2 +51,8 @@ SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`,

BUTTON_LABEL: 'MessageButton label must be a string',
BUTTON_URL: 'MessageButton url must be a string',
BUTTON_CUSTOM_ID: 'MessageButton customID must be a string',
INTERACTION_COLLECTOR_TIMEOUT: 'Collector timed out without receiving any interactions',
FILE_NOT_FOUND: file => `File could not be found: ${file}`,

@@ -54,15 +62,2 @@

VOICE_INVALID_HEARTBEAT: 'Tried to set voice heartbeat but no valid interval was specified.',
VOICE_USER_MISSING: "Couldn't resolve the user to create stream.",
VOICE_JOIN_CHANNEL: (full = false) =>
`You do not have permission to join this voice channel${full ? '; it is full.' : '.'}`,
VOICE_CONNECTION_TIMEOUT: 'Connection not established within 15 seconds.',
VOICE_TOKEN_ABSENT: 'Token not provided from voice server packet.',
VOICE_SESSION_ABSENT: 'Session ID not supplied.',
VOICE_INVALID_ENDPOINT: 'Invalid endpoint received.',
VOICE_CONNECTION_ATTEMPTS_EXCEEDED: attempts => `Too many connection attempts (${attempts}).`,
VOICE_JOIN_SOCKET_CLOSED: 'Tried to send join packet, but the WebSocket is not open.',
VOICE_PLAY_INTERFACE_NO_BROADCAST: 'A broadcast cannot be played in this context.',
VOICE_PLAY_INTERFACE_BAD_TYPE: 'Unknown stream type',
VOICE_PRISM_DEMUXERS_NEED_STREAM: 'To play a webm/ogg stream, you need to pass a ReadableStream.',
VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.',

@@ -75,6 +70,2 @@

UDP_SEND_FAIL: 'Tried to send a UDP packet, but there is no socket available.',
UDP_ADDRESS_MALFORMED: 'Malformed UDP address or port.',
UDP_CONNECTION_EXISTS: 'There is already an existing UDP connection.',
REQ_RESOURCE_TYPE: 'The resource must be a string, Buffer or a valid file stream.',

@@ -81,0 +72,0 @@

@@ -24,2 +24,3 @@ 'use strict';

HTTPError: require('./rest/HTTPError'),
RateLimitError: require('./rest/RateLimitError'),
MessageFlags: require('./util/MessageFlags'),

@@ -72,2 +73,4 @@ Intents: require('./util/Intents'),

BaseGuildVoiceChannel: require('./structures/BaseGuildVoiceChannel'),
BaseMessageComponent: require('./structures/BaseMessageComponent'),
ButtonInteraction: require('./structures/ButtonInteraction'),
CategoryChannel: require('./structures/CategoryChannel'),

@@ -95,6 +98,11 @@ Channel: require('./structures/Channel'),

Interaction: require('./structures/Interaction'),
InteractionWebhook: require('./structures/InteractionWebhook'),
Invite: require('./structures/Invite'),
Message: require('./structures/Message'),
MessageActionRow: require('./structures/MessageActionRow'),
MessageAttachment: require('./structures/MessageAttachment'),
MessageButton: require('./structures/MessageButton'),
MessageCollector: require('./structures/MessageCollector'),
MessageComponentInteraction: require('./structures/MessageComponentInteraction'),
MessageComponentInteractionCollector: require('./structures/MessageComponentInteractionCollector'),
MessageEmbed: require('./structures/MessageEmbed'),

@@ -101,0 +109,0 @@ MessageMentions: require('./structures/MessageMentions'),

'use strict';
const ApplicationCommandManager = require('./ApplicationCommandManager');
const { TypeError } = require('../errors');
const Collection = require('../util/Collection');

@@ -59,3 +60,3 @@ const { ApplicationCommandPermissionTypes } = require('../util/Constants');

* Data used for overwriting the permissions for all application commands in a guild.
* @typedef {object} GuildApplicationCommandPermissionData
* @typedef {Object} GuildApplicationCommandPermissionData
* @prop {Snowflake} command The ID of the command

@@ -62,0 +63,0 @@ * @prop {ApplicationCommandPermissionData[]} permissions The permissions for this command

'use strict';
const BaseManager = require('./BaseManager');
const { TypeError, Error } = require('../errors');
const GuildBan = require('../structures/GuildBan');

@@ -123,7 +124,12 @@ const GuildMember = require('../structures/GuildMember');

/**
* Options for banning a user.
* @typedef {Object} BanOptions
* @property {number} [days] Number of days of messages to delete, must be between 0 and 7, inclusive
* @property {string} [reason] Reason for banning
*/
/**
* Bans a user from the guild.
* @param {UserResolvable} user The user to ban
* @param {Object} [options] Options for the ban
* @param {number} [options.days=0] Number of days of messages to delete, must be between 0 and 7, inclusive
* @param {string} [options.reason] Reason for banning
* @param {BanOptions} [options] Options for the ban
* @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible.

@@ -130,0 +136,0 @@ * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot

@@ -6,2 +6,3 @@ 'use strict';

const PermissionOverwrites = require('../structures/PermissionOverwrites');
const Collection = require('../util/Collection');
const { ChannelTypes } = require('../util/Constants');

@@ -121,4 +122,34 @@

}
/**
* Obtains one or more guild channels from Discord, or the channel cache if they're already available.
* @param {Snowflake} [id] ID of the channel
* @param {boolean} [cache=true] Whether to cache the new channel objects if it weren't already
* @param {boolean} [force=false] Whether to skip the cache check and request the API
* @returns {Promise<?GuildChannel|Collection<Snowflake, GuildChannel>>}
* @example
* // Fetch all channels from the guild
* message.guild.channels.fetch()
* .then(channels => console.log(`There are ${channels.size} channels.`))
* .catch(console.error);
* @example
* // Fetch a single channel
* message.guild.channels.fetch('222197033908436994')
* .then(channel => console.log(`The channel name is: ${channel.name}`))
* .catch(console.error);
*/
async fetch(id, cache = true, force = false) {
if (id && !force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
// We cannot fetch a single guild channel, as of this commit's date, Discord API throws with 404
const data = await this.client.api.guilds(this.guild.id).channels.get();
const channels = new Collection();
for (const channel of data) channels.set(channel.id, this.client.channels.add(channel, this.guild, cache));
return id ? channels.get(id) ?? null : channels;
}
}
module.exports = GuildChannelManager;

@@ -5,2 +5,3 @@ 'use strict';

const { Error, TypeError, RangeError } = require('../errors');
const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel');
const GuildMember = require('../structures/GuildMember');

@@ -142,7 +143,12 @@ const Role = require('../structures/Role');

/**
* Options for searching for guild members.
* @typedef {Object} GuildSearchMembersOptions
* @property {string} query Filter members whose username or nickname start with this query
* @property {number} [limit] Maximum number of members to search
* @property {boolean} [cache] Whether or not to cache the fetched member(s)
*/
/**
* Search for members in the guild based on a query.
* @param {Object} options Search options
* @property {string} options.query Filter members whose username or nickname start with this query
* @property {number} [options.limit=1] Maximum number of members to search
* @property {boolean} [options.cache=true] Whether or not to cache the fetched member(s)
* @param {GuildSearchMembersOptions} options Search options
* @returns {Promise<Collection<Snowflake, GuildMember>>}

@@ -171,3 +177,3 @@ */

_data.channel = this.guild.channels.resolve(_data.channel);
if (!_data.channel || _data.channel.type !== 'voice') {
if (!(_data.channel instanceof BaseGuildVoiceChannel)) {
throw new Error('GUILD_VOICE_CHANNEL_RESOLVE');

@@ -198,10 +204,15 @@ }

/**
* Options for pruning guild members.
* @typedef {Object} GuildPruneMembersOptions
* @property {number} [days] Number of days of inactivity required to kick
* @property {boolean} [dry] Get number of users that will be kicked, without actually kicking them
* @property {boolean} [count] Whether or not to return the number of users that have been kicked.
* @property {RoleResolvable[]} [roles] Array of roles to bypass the "...and no roles" constraint when pruning
* @property {string} [reason] Reason for this prune
*/
/**
* Prunes members from the guild based on how long they have been inactive.
* <info>It's recommended to set options.count to `false` for large guilds.</info>
* @param {Object} [options] Prune options
* @param {number} [options.days=7] Number of days of inactivity required to kick
* @param {boolean} [options.dry=false] Get number of users that will be kicked, without actually kicking them
* @param {boolean} [options.count=true] Whether or not to return the number of users that have been kicked.
* @param {RoleResolvable[]} [options.roles=[]] Array of roles to bypass the "...and no roles" constraint when pruning
* @param {string} [options.reason] Reason for this prune
* @param {GuildPruneMembersOptions} [options] Prune options
* @returns {Promise<number|null>} The number of members that were/will be kicked

@@ -282,5 +293,3 @@ * @example

* @param {UserResolvable} user The user to ban
* @param {Object} [options] Options for the ban
* @param {number} [options.days=0] Number of days of messages to delete, must be between 0 and 7, inclusive
* @param {string} [options.reason] Reason for banning
* @param {BanOptions} [options] Options for the ban
* @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible.

@@ -287,0 +296,0 @@ * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot

@@ -118,3 +118,3 @@ 'use strict';

if (color) color = resolveColor(color);
if (permissions) permissions = Permissions.resolve(permissions).toString();
if (typeof permissions !== 'undefined') permissions = new Permissions(permissions);

@@ -121,0 +121,0 @@ return this.client.api

@@ -8,3 +8,3 @@ 'use strict';

class DiscordAPIError extends Error {
constructor(path, error, method, status) {
constructor(error, status, request) {
super();

@@ -19,3 +19,3 @@ const flattened = this.constructor.flattenErrors(error.errors || error).join('\n');

*/
this.method = method;
this.method = request.method;

@@ -26,3 +26,3 @@ /**

*/
this.path = path;
this.path = request.path;

@@ -40,2 +40,11 @@ /**

this.httpStatus = status;
/**
* The data associated with the request that caused this error
* @type {HTTPErrorData}
*/
this.requestData = {
json: request.options.data,
files: request.options.files ?? [],
};
}

@@ -42,0 +51,0 @@

@@ -8,3 +8,3 @@ 'use strict';

class HTTPError extends Error {
constructor(message, name, code, method, path) {
constructor(message, name, code, request) {
super(message);

@@ -28,3 +28,3 @@

*/
this.method = method;
this.method = request.method;

@@ -35,3 +35,27 @@ /**

*/
this.path = path;
this.path = request.path;
/**
* The HTTP data that was sent to Discord
* @typedef {Object} HTTPErrorData
* @property {*} json The JSON data that was sent
* @property {HTTPAttachmentData[]} files The files that were sent with this request, if any
*/
/**
* The attachment data that is sent to Discord
* @typedef {Object} HTTPAttachmentData
* @property {string|Buffer|Stream} attachment The source of this attachment data
* @property {string} name The file name
* @property {Buffer|Stream} file The file buffer
*/
/**
* The data associated with the request that caused this error
* @type {HTTPErrorData}
*/
this.requestData = {
json: request.options.data,
files: request.options.files ?? [],
};
}

@@ -38,0 +62,0 @@ }

@@ -6,2 +6,3 @@ 'use strict';

const HTTPError = require('./HTTPError');
const RateLimitError = require('./RateLimitError');
const {

@@ -81,2 +82,26 @@ Events: { RATE_LIMIT, INVALID_REQUEST_WARNING },

/*
* Determines whether the request should be queued or whether a RateLimitError should be thrown
*/
async onRateLimit(request, limit, timeout, isGlobal) {
const { options } = this.manager.client;
if (!options.rejectOnRateLimit) return;
const rateLimitData = {
timeout,
limit,
method: request.method,
path: request.path,
route: request.route,
global: isGlobal,
};
const shouldThrow =
typeof options.rejectOnRateLimit === 'function'
? await options.rejectOnRateLimit(rateLimitData)
: options.rejectOnRateLimit.some(route => rateLimitData.route.startsWith(route.toLowerCase()));
if (shouldThrow) {
throw new RateLimitError(rateLimitData);
}
}
async execute(request) {

@@ -95,8 +120,2 @@ /*

timeout = this.manager.globalReset + this.manager.client.options.restTimeOffset - Date.now();
// If this is the first task to reach the global timeout, set the global delay
if (!this.manager.globalDelay) {
// The global delay function should clear the global delay state when it is resolved
this.manager.globalDelay = this.globalDelayFor(timeout);
}
delayPromise = this.manager.globalDelay;
} else {

@@ -106,3 +125,2 @@ // Set the variables based on the route-specific rate limit

timeout = this.reset + this.manager.client.options.restTimeOffset - Date.now();
delayPromise = Util.delayFor(timeout);
}

@@ -132,2 +150,16 @@

if (isGlobal) {
// If this is the first task to reach the global timeout, set the global delay
if (!this.manager.globalDelay) {
// The global delay function should clear the global delay state when it is resolved
this.manager.globalDelay = this.globalDelayFor(timeout);
}
delayPromise = this.manager.globalDelay;
} else {
delayPromise = Util.delayFor(timeout);
}
// Determine whether a RateLimitError should be thrown
await this.onRateLimit(request, limit, timeout, isGlobal); // eslint-disable-line no-await-in-loop
// Wait for the timeout to expire in order to avoid an actual 429

@@ -151,3 +183,3 @@ await delayPromise; // eslint-disable-line no-await-in-loop

if (request.retries === this.manager.client.options.retryLimit) {
throw new HTTPError(error.message, error.constructor.name, error.status, request.method, request.path);
throw new HTTPError(error.message, error.constructor.name, error.status, request);
}

@@ -234,2 +266,16 @@

this.manager.client.emit('debug', `429 hit on route ${request.route}${sublimitTimeout ? ' for sublimit' : ''}`);
const isGlobal = this.globalLimited;
let limit, timeout;
if (isGlobal) {
// Set the variables based on the global rate limit
limit = this.manager.globalLimit;
timeout = this.manager.globalReset + this.manager.client.options.restTimeOffset - Date.now();
} else {
// Set the variables based on the route-specific rate limit
limit = this.limit;
timeout = this.reset + this.manager.client.options.restTimeOffset - Date.now();
}
await this.onRateLimit(request, limit, timeout, isGlobal);
// If caused by a sublimit, wait it out here so other requests on the route can be handled

@@ -247,6 +293,6 @@ if (sublimitTimeout) {

} catch (err) {
throw new HTTPError(err.message, err.constructor.name, err.status, request.method, request.path);
throw new HTTPError(err.message, err.constructor.name, err.status, request);
}
throw new DiscordAPIError(request.path, data, request.method, res.status);
throw new DiscordAPIError(data, res.status, request);
}

@@ -258,3 +304,3 @@

if (request.retries === this.manager.client.options.retryLimit) {
throw new HTTPError(res.statusText, res.constructor.name, res.status, request.method, request.path);
throw new HTTPError(res.statusText, res.constructor.name, res.status, request);
}

@@ -261,0 +307,0 @@

@@ -351,3 +351,3 @@ 'use strict';

const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard };
this.manager.broadcastEval(message._sEval, message._sEvalShard).then(
this.manager._performOnShards('eval', [message._sEval], message._sEvalShard).then(
results => this.send({ ...resp, _result: results }),

@@ -354,0 +354,0 @@ err => this.send({ ...resp, _error: Util.makePlainError(err) }),

'use strict';
const { Error } = require('../errors');
const { Events } = require('../util/Constants');

@@ -130,7 +131,7 @@ const Util = require('../util/Util');

* Evaluates a script or function on all shards, or a given shard, in the context of the {@link Client}s.
* @param {string|Function} script JavaScript to run on each shard
* @param {number} [shard] Shard to run script on, all if undefined
* @param {Function} script JavaScript to run on each shard
* @param {BroadcastEvalOptions} [options={}] The options for the broadcast
* @returns {Promise<*>|Promise<Array<*>>} Results of the script execution
* @example
* client.shard.broadcastEval('this.guilds.cache.size')
* client.shard.broadcastEval(client => client.guilds.cache.size)
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))

@@ -140,9 +141,13 @@ * .catch(console.error);

*/
broadcastEval(script, shard) {
broadcastEval(script, options = {}) {
return new Promise((resolve, reject) => {
const parent = this.parentPort || process;
script = typeof script === 'function' ? `(${script})(this)` : script;
if (typeof script !== 'function') {
reject(new TypeError('SHARDING_INVALID_EVAL_BROADCAST'));
return;
}
script = `(${script})(this, ${JSON.stringify(options.context)})`;
const listener = message => {
if (!message || message._sEval !== script || message._sEvalShard !== shard) return;
if (!message || message._sEval !== script || message._sEvalShard !== options.shard) return;
parent.removeListener('message', listener);

@@ -154,3 +159,3 @@ if (!message._error) resolve(message._result);

this.send({ _sEval: script, _sEvalShard: shard }).catch(err => {
this.send({ _sEval: script, _sEvalShard: options.shard }).catch(err => {
parent.removeListener('message', listener);

@@ -206,3 +211,4 @@ reject(err);

this.send(message).catch(err => {
err.message = `Error when sending ${type} response to master process: ${err.message}`;
const error = new Error(`Error when sending ${type} response to master process: ${err.message}`);
error.stack = err.stack;
/**

@@ -213,3 +219,3 @@ * Emitted when the client encounters an error.

*/
this.client.emit(Events.ERROR, err);
this.client.emit(Events.ERROR, error);
});

@@ -216,0 +222,0 @@ }

@@ -230,9 +230,17 @@ 'use strict';

/**
* Options for {@link ShardingManager#broadcastEval} and {@link ShardClientUtil#broadcastEval}.
* @typedef {Object} BroadcastEvalOptions
* @property {number} [shard] Shard to run script on, all if undefined
* @property {*} [context] The JSON-serializable values to call the script with
*/
/**
* Evaluates a script on all shards, or a given shard, in the context of the {@link Client}s.
* @param {string} script JavaScript to run on each shard
* @param {number} [shard] Shard to run on, all if undefined
* @param {Function} script JavaScript to run on each shard
* @param {BroadcastEvalOptions} [options={}] The options for the broadcast
* @returns {Promise<*>|Promise<Array<*>>} Results of the script execution
*/
broadcastEval(script, shard) {
return this._performOnShards('eval', [script], shard);
broadcastEval(script, options = {}) {
if (typeof script !== 'function') return Promise.reject(new TypeError('SHARDING_INVALID_EVAL_BROADCAST'));
return this._performOnShards('eval', [`(${script})(this, ${JSON.stringify(options.context)})`], options.shard);
}

@@ -239,0 +247,0 @@

'use strict';
const MessageAttachment = require('./MessageAttachment');
const BaseMessageComponent = require('./BaseMessageComponent');
const MessageEmbed = require('./MessageEmbed');
const { RangeError } = require('../errors');
const { MessageComponentTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');

@@ -83,3 +84,4 @@ const MessageFlags = require('../util/MessageFlags');

const Interaction = require('./Interaction');
return this.target instanceof Interaction;
const InteractionWebhook = require('./InteractionWebhook');
return this.target instanceof Interaction || this.target instanceof InteractionWebhook;
}

@@ -129,2 +131,5 @@

if (this.data) return this;
const isInteraction = this.isInteraction;
const isWebhook = this.isWebhook;
const isWebhookLike = isInteraction || isWebhook;

@@ -144,3 +149,3 @@ const content = this.makeContent();

const embedLikes = [];
if (this.isInteraction || this.isWebhook) {
if (isWebhookLike) {
if (this.options.embeds) {

@@ -154,5 +159,11 @@ embedLikes.push(...this.options.embeds);

const components = this.options.components?.map(c =>
BaseMessageComponent.create(
Array.isArray(c) ? { type: MessageComponentTypes.ACTION_ROW, components: c } : c,
).toJSON(),
);
let username;
let avatarURL;
if (this.isWebhook) {
if (isWebhook) {
username = this.options.username || this.target.name;

@@ -166,3 +177,3 @@ if (this.options.avatarURL) avatarURL = this.options.avatarURL;

flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags.bitfield;
} else if (this.isInteraction && this.options.ephemeral) {
} else if (isInteraction && this.options.ephemeral) {
flags = MessageFlags.FLAGS.EPHEMERAL;

@@ -199,4 +210,5 @@ }

nonce,
embed: this.options.embed === null ? null : embeds[0],
embeds,
embed: !isWebhookLike ? (this.options.embed === null ? null : embeds[0]) : undefined,
embeds: isWebhookLike ? embeds : undefined,
components,
username,

@@ -310,72 +322,11 @@ avatar_url: avatarURL,

/**
* Partitions embeds and attachments.
* @param {Array<MessageEmbed|MessageAttachment>} items Items to partition
* @returns {Array<MessageEmbed[], MessageAttachment[]>}
*/
static partitionMessageAdditions(items) {
const embeds = [];
const files = [];
for (const item of items) {
if (item instanceof MessageEmbed) {
embeds.push(item);
} else if (item instanceof MessageAttachment) {
files.push(item);
}
}
return [embeds, files];
}
/**
* Transforms the user-level arguments into a final options object. Passing a transformed options object alone into
* this method will keep it the same, allowing for the reuse of the final options object.
* @param {string} [content] Content to send
* @param {MessageOptions|WebhookMessageOptions|MessageAdditions} [options={}] Options to use
* @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto transformed options
* @param {boolean} [isWebhook=false] Whether or not to use WebhookMessageOptions as the result
* @returns {MessageOptions|WebhookMessageOptions}
*/
static transformOptions(content, options, extra = {}, isWebhook = false) {
if (!options && typeof content === 'object' && !Array.isArray(content)) {
options = content;
content = undefined;
}
if (!options) {
options = {};
} else if (options instanceof MessageEmbed) {
return isWebhook ? { content, embeds: [options], ...extra } : { content, embed: options, ...extra };
} else if (options instanceof MessageAttachment) {
return { content, files: [options], ...extra };
}
if (Array.isArray(options)) {
const [embeds, files] = this.partitionMessageAdditions(options);
return isWebhook ? { content, embeds, files, ...extra } : { content, embed: embeds[0], files, ...extra };
} else if (Array.isArray(content)) {
const [embeds, files] = this.partitionMessageAdditions(content);
if (embeds.length || files.length) {
return isWebhook ? { embeds, files, ...extra } : { embed: embeds[0], files, ...extra };
}
}
return { content, ...options, ...extra };
}
/**
* Creates an `APIMessage` from user-level arguments.
* @param {MessageTarget} target Target to send to
* @param {string} [content] Content to send
* @param {MessageOptions|WebhookMessageOptions|MessageAdditions} [options={}] Options to use
* @param {MessageOptions|WebhookMessageOptions} [extra={}] - Extra options to add onto transformed options
* @param {string|MessageOptions|WebhookMessageOptions} options Options or content to use
* @param {MessageOptions|WebhookMessageOptions} [extra={}] - Extra options to add onto specified options
* @returns {MessageOptions|WebhookMessageOptions}
*/
static create(target, content, options, extra = {}) {
const Interaction = require('./Interaction');
const Webhook = require('./Webhook');
const WebhookClient = require('../client/WebhookClient');
const isWebhook = target instanceof Interaction || target instanceof Webhook || target instanceof WebhookClient;
const transformed = this.transformOptions(content, options, extra, isWebhook);
return new this(target, transformed);
static create(target, options, extra = {}) {
if (typeof options === 'string') return new this(target, { content: options, ...extra });
else return new this(target, { ...options, ...extra });
}

@@ -388,8 +339,3 @@ }

* A target for a message.
* @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction} MessageTarget
* @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook} MessageTarget
*/
/**
* Additional items that can be sent with a message.
* @typedef {MessageEmbed|MessageAttachment|Array<MessageEmbed|MessageAttachment>} MessageAdditions
*/

@@ -135,5 +135,5 @@ 'use strict';

* Data for setting the permissions of an application command.
* @typedef {object} ApplicationCommandPermissionData
* @typedef {Object} ApplicationCommandPermissionData
* @property {Snowflake} id The ID of the role or user
* @property {ApplicationCommandPermissionType|number} type Whether this permission if for a role or a user
* @property {ApplicationCommandPermissionType|number} type Whether this permission is for a role or a user
* @property {boolean} permission Whether the role or user has the permission to use this command

@@ -144,5 +144,5 @@ */

* The object returned when fetching permissions for an application command.
* @typedef {object} ApplicationCommandPermissions
* @typedef {Object} ApplicationCommandPermissions
* @property {Snowflake} id The ID of the role or user
* @property {ApplicationCommandPermissionType} type Whether this permission if for a role or a user
* @property {ApplicationCommandPermissionType} type Whether this permission is for a role or a user
* @property {boolean} permission Whether the role or user has the permission to use this command

@@ -149,0 +149,0 @@ */

@@ -70,26 +70,2 @@ 'use strict';

/**
* Attempts to join this voice-based channel.
* @returns {Promise<VoiceConnection>}
* @example
* // Join a voice-based channel
* channel.join()
* .then(connection => console.log('Connected!'))
* .catch(console.error);
*/
join() {
return this.client.voice.joinChannel(this);
}
/**
* Leaves this voice-based channel.
* @example
* // Leave a voice-based channel
* channel.leave();
*/
leave() {
const connection = this.client.voice.connections.get(this.guild.id);
if (connection?.channel.id === this.id) connection.disconnect();
}
/**
* Sets the RTC region of the channel.

@@ -96,0 +72,0 @@ * @param {?string} region The new region of the channel. Set to `null` to remove a specific region for the channel

'use strict';
const APIMessage = require('./APIMessage');
const Interaction = require('./Interaction');
const WebhookClient = require('../client/WebhookClient');
const { Error } = require('../errors');
const { ApplicationCommandOptionTypes, InteractionResponseTypes } = require('../util/Constants');
const MessageFlags = require('../util/MessageFlags');
const InteractionWebhook = require('./InteractionWebhook');
const InteractionResponses = require('./interfaces/InteractionResponses');
const Collection = require('../util/Collection');
const { ApplicationCommandOptionTypes } = require('../util/Constants');

@@ -13,2 +12,3 @@ /**

* @extends {Interaction}
* @implements {InteractionResponses}
*/

@@ -20,2 +20,15 @@ class CommandInteraction extends Interaction {

/**
* The channel this interaction was sent in
* @type {?TextChannel|NewsChannel|DMChannel}
* @name CommandInteraction#channel
* @readonly
*/
/**
* The ID of the channel this interaction was sent in
* @type {Snowflake}
* @name CommandInteraction#channelID
*/
/**
* The ID of the invoked application command

@@ -40,5 +53,5 @@ * @type {Snowflake}

* The options passed to the command.
* @type {CommandInteractionOption[]}
* @type {Collection<string, CommandInteractionOption>}
*/
this.options = data.data.options?.map(o => this.transformOption(o, data.data.resolved)) ?? [];
this.options = this._createOptionsCollection(data.data.options, data.data.resolved);

@@ -52,6 +65,6 @@ /**

/**
* An associated webhook client, can be used to create deferred replies
* @type {WebhookClient}
* An associated interaction webhook, can be used to further interact with this interaction
* @type {InteractionWebhook}
*/
this.webhook = new WebhookClient(this.applicationID, this.token, this.client.options);
this.webhook = new InteractionWebhook(this.client, this.applicationID, this.token);
}

@@ -69,122 +82,2 @@

/**
* Options for deferring the reply to a {@link CommandInteraction}.
* @typedef {Object} InteractionDeferOptions
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
*/
/**
* Defers the reply to this interaction.
* @param {InteractionDeferOptions} [options] Options for deferring the reply to this interaction
* @returns {Promise<void>}
* @example
* // Defer the reply to this interaction
* interaction.defer()
* .then(console.log)
* .catch(console.error)
* @example
* // Defer to send an ephemeral reply later
* interaction.defer({ ephemeral: true })
* .then(console.log)
* .catch(console.error);
*/
async defer({ ephemeral } = {}) {
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
await this.client.api.interactions(this.id, this.token).callback.post({
data: {
type: InteractionResponseTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: ephemeral ? MessageFlags.FLAGS.EPHEMERAL : undefined,
},
},
});
this.deferred = true;
}
/**
* Options for a reply to an interaction.
* @typedef {BaseMessageOptions} InteractionReplyOptions
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
* @property {MessageEmbed[]|Object[]} [embeds] An array of embeds for the message
*/
/**
* Creates a reply to this interaction.
* @param {string|APIMessage|MessageAdditions} content The content for the reply
* @param {InteractionReplyOptions} [options] Additional options for the reply
* @returns {Promise<void>}
* @example
* // Reply to the interaction with an embed
* const embed = new MessageEmbed().setDescription('Pong!');
*
* interaction.reply(embed)
* .then(console.log)
* .catch(console.error);
* @example
* // Create an ephemeral reply
* interaction.reply('Pong!', { ephemeral: true })
* .then(console.log)
* .catch(console.error);
*/
async reply(content, options) {
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
const apiMessage = content instanceof APIMessage ? content : APIMessage.create(this, content, options);
const { data, files } = await apiMessage.resolveData().resolveFiles();
await this.client.api.interactions(this.id, this.token).callback.post({
data: {
type: InteractionResponseTypes.CHANNEL_MESSAGE_WITH_SOURCE,
data,
},
files,
});
this.replied = true;
}
/**
* Fetches the initial reply to this interaction.
* @see Webhook#fetchMessage
* @returns {Promise<Message|Object>}
* @example
* // Fetch the reply to this interaction
* interaction.fetchReply()
* .then(reply => console.log(`Replied with ${reply.content}`))
* .catch(console.error);
*/
async fetchReply() {
const raw = await this.webhook.fetchMessage('@original');
return this.channel?.messages.add(raw) ?? raw;
}
/**
* Edits the initial reply to this interaction.
* @see Webhook#editMessage
* @param {string|APIMessage|MessageAdditions} content The new content for the message
* @param {WebhookEditMessageOptions} [options] The options to provide
* @returns {Promise<Message|Object>}
* @example
* // Edit the reply to this interaction
* interaction.editReply('New content')
* .then(console.log)
* .catch(console.error);
*/
async editReply(content, options) {
const raw = await this.webhook.editMessage('@original', content, options);
return this.channel?.messages.add(raw) ?? raw;
}
/**
* Deletes the initial reply to this interaction.
* @see Webhook#deleteMessage
* @returns {Promise<void>}
* @example
* // Delete the reply to this interaction
* interaction.deleteReply()
* .then(console.log)
* .catch(console.error);
*/
async deleteReply() {
await this.webhook.deleteMessage('@original');
}
/**
* Represents an option of a received command interaction.

@@ -195,3 +88,4 @@ * @typedef {Object} CommandInteractionOption

* @property {string|number|boolean} [value] The value of the option
* @property {CommandInteractionOption[]} [options] Additional options if this option is a subcommand (group)
* @property {Collection<string, CommandInteractionOption>} [options] Additional options if this option is a
* subcommand (group)
* @property {User} [user] The resolved user

@@ -204,20 +98,2 @@ * @property {GuildMember|Object} [member] The resolved member

/**
* Send a follow-up message to this interaction.
* @param {string|APIMessage|MessageAdditions} content The content for the reply
* @param {InteractionReplyOptions} [options] Additional options for the reply
* @returns {Promise<Message|Object>}
*/
async followUp(content, options) {
const apiMessage = content instanceof APIMessage ? content : APIMessage.create(this, content, options);
const { data, files } = await apiMessage.resolveData().resolveFiles();
const raw = await this.client.api.webhooks(this.applicationID, this.token).post({
data,
files,
});
return this.channel?.messages.add(raw) ?? raw;
}
/**
* Transforms an option received from the API.

@@ -236,3 +112,3 @@ * @param {Object} option The received option

if ('value' in option) result.value = option.value;
if ('options' in option) result.options = option.options.map(o => this.transformOption(o, resolved));
if ('options' in option) result.options = this._createOptionsCollection(option.options, resolved);

@@ -253,4 +129,31 @@ const user = resolved?.users?.[option.value];

}
/**
* Creates a collection of options from the received options array.
* @param {Object[]} options The received options
* @param {Object} resolved The resolved interaction data
* @returns {Collection<string, CommandInteractionOption>}
* @private
*/
_createOptionsCollection(options, resolved) {
const optionsCollection = new Collection();
if (typeof options === 'undefined') return optionsCollection;
for (const option of options) {
optionsCollection.set(option.name, this.transformOption(option, resolved));
}
return optionsCollection;
}
// These are here only for documentation purposes - they are implemented by InteractionResponses
/* eslint-disable no-empty-function */
defer() {}
reply() {}
fetchReply() {}
editReply() {}
deleteReply() {}
followUp() {}
}
InteractionResponses.applyToClass(CommandInteraction, ['deferUpdate', 'update']);
module.exports = CommandInteraction;

@@ -94,2 +94,4 @@ 'use strict';

awaitMessages() {}
createMessageComponentInteractionCollector() {}
awaitMessageComponentInteraction() {}
// Doesn't work on DM channels; bulkDelete() {}

@@ -96,0 +98,0 @@ }

@@ -7,2 +7,10 @@ 'use strict';

/**
* Represents raw emoji data from the API
* @typedef {Object} RawEmoji
* @property {?Snowflake} id ID of this emoji
* @property {?string} name Name of this emoji
* @property {?boolean} animated Whether this emoji is animated
*/
/**
* Represents an emoji, see {@link GuildEmoji} and {@link ReactionEmoji}.

@@ -9,0 +17,0 @@ * @extends {Base}

@@ -28,2 +28,3 @@ 'use strict';

NSFWLevels,
Status,
} = require('../util/Constants');

@@ -180,2 +181,4 @@ const DataResolver = require('../util/DataResolver');

* * MEMBER_VERIFICATION_GATE_ENABLED
* * MONETIZATION_ENABLED
* * MORE_STICKERS
* * NEWS

@@ -185,2 +188,3 @@ * * PARTNERED

* * RELAY_ENABLED
* * TICKETED_EVENTS_ENABLED
* * VANITY_URL

@@ -1332,2 +1336,31 @@ * * VERIFIED

/**
* The voice state adapter for this guild that can be used with @discordjs/voice to play audio in voice
* and stage channels.
* @type {Function}
* @readonly
* @example
* const { joinVoiceChannel } = require('@discordjs/voice');
* const voiceConnection = joinVoiceChannel({
* channelId: channel.id,
* guildId: channel.guild.id,
* adapterCreator: channel.guild.voiceAdapterCreator,
* });
*/
get voiceAdapterCreator() {
return methods => {
this.client.voice.adapters.set(this.id, methods);
return {
sendPayload: data => {
if (this.shard.status !== Status.READY) return false;
this.shard.send(data);
return true;
},
destroy: () => {
this.client.voice.adapters.delete(this.id);
},
};
};
}
/**
* Creates a collection of this guild's roles, sorted by their position and IDs.

@@ -1334,0 +1367,0 @@ * @returns {Collection<Snowflake, Role>}

@@ -115,4 +115,20 @@ 'use strict';

}
/**
* Indicates whether this interaction is a component interaction.
* @returns {boolean}
*/
isMessageComponent() {
return InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT;
}
/**
* Indicates whether this interaction is a button interaction.
* @returns {boolean}
*/
isButton() {
return InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT && this.componentType === 'BUTTON';
}
}
module.exports = Interaction;

@@ -8,3 +8,4 @@ 'use strict';

const Collection = require('../../util/Collection');
const { RangeError, TypeError } = require('../../errors');
const { RangeError, TypeError, Error } = require('../../errors');
const MessageComponentInteractionCollector = require('../MessageComponentInteractionCollector');

@@ -66,2 +67,4 @@ /**

* it exceeds the character limit. If an object is provided, these are the options for splitting the message
* @property {MessageActionRow[]|MessageActionRowOptions[]|MessageActionRowComponentResolvable[][]} [components]
* Action rows containing interactive components for the message (buttons, select menus)
*/

@@ -119,4 +122,3 @@

* Sends a message to this channel.
* @param {string|APIMessage} [content=''] The content to send
* @param {MessageOptions|MessageAdditions} [options={}] The options to provide
* @param {string|APIMessage|MessageOptions} options The options to provide
* @returns {Promise<Message|Message[]>}

@@ -147,12 +149,13 @@ * @example

* // Send an embed with a local image inside
* channel.send('This is an embed', {
* channel.send({
* content: 'This is an embed',
* embed: {
* thumbnail: {
* url: 'attachment://file.jpg'
* }
* },
* files: [{
* attachment: 'entire/path/to/file.jpg',
* name: 'file.jpg'
* }]
* url: 'attachment://file.jpg'
* }
* },
* files: [{
* attachment: 'entire/path/to/file.jpg',
* name: 'file.jpg'
* }]
* })

@@ -162,3 +165,3 @@ * .then(console.log)

*/
async send(content, options) {
async send(options) {
const User = require('../User');

@@ -168,3 +171,3 @@ const GuildMember = require('../GuildMember');

if (this instanceof User || this instanceof GuildMember) {
return this.createDM().then(dm => dm.send(content, options));
return this.createDM().then(dm => dm.send(options));
}

@@ -174,6 +177,6 @@

if (content instanceof APIMessage) {
apiMessage = content.resolveData();
if (options instanceof APIMessage) {
apiMessage = options.resolveData();
} else {
apiMessage = APIMessage.create(this, content, options).resolveData();
apiMessage = APIMessage.create(this, options).resolveData();
if (Array.isArray(apiMessage.data.content)) {

@@ -326,2 +329,42 @@ return Promise.all(apiMessage.split().map(this.send.bind(this)));

/**
* Creates a button interaction collector.
* @param {CollectorFilter} filter The filter to apply
* @param {MessageComponentInteractionCollectorOptions} [options={}] Options to send to the collector
* @returns {MessageComponentInteractionCollector}
* @example
* // Create a button interaction collector
* const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID';
* const collector = channel.createMessageComponentInteractionCollector(filter, { time: 15000 });
* collector.on('collect', i => console.log(`Collected ${i.customID}`));
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
*/
createMessageComponentInteractionCollector(filter, options = {}) {
return new MessageComponentInteractionCollector(this, filter, options);
}
/**
* Collects a single component interaction that passes the filter.
* The Promise will reject if the time expires.
* @param {CollectorFilter} filter The filter function to use
* @param {number} [time] Time to wait for an interaction before rejecting
* @returns {Promise<MessageComponentInteraction>}
* @example
* // Collect a button interaction
* const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID';
* channel.awaitMessageComponentInteraction(filter, 15000)
* .then(interaction => console.log(`${interaction.customID} was clicked!`))
* .catch(console.error);
*/
awaitMessageComponentInteraction(filter, time) {
return new Promise((resolve, reject) => {
const collector = this.createMessageComponentInteractionCollector(filter, { max: 1, time });
collector.once('end', interactions => {
const interaction = interactions.first();
if (!interaction) reject(new Error('INTERACTION_COLLECTOR_TIMEOUT'));
else resolve(interaction);
});
});
}
/**
* Bulk deletes given messages that are newer than two weeks.

@@ -390,2 +433,4 @@ * @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages

'awaitMessages',
'createMessageComponentInteractionCollector',
'awaitMessageComponentInteraction',
);

@@ -392,0 +437,0 @@ }

@@ -5,2 +5,3 @@ 'use strict';

const IntegrationApplication = require('./IntegrationApplication');
const { Error } = require('../errors');
const { Endpoints } = require('../util/Constants');

@@ -113,2 +114,4 @@ const Permissions = require('../util/Permissions');

this.createdTimestamp = 'created_at' in data ? new Date(data.created_at).getTime() : null;
this._expiresTimestamp = 'expires_at' in data ? new Date(data.expires_at).getTime() : null;
}

@@ -146,3 +149,6 @@

get expiresTimestamp() {
return this.createdTimestamp && this.maxAge ? this.createdTimestamp + this.maxAge * 1000 : null;
return (
this._expiresTimestamp ??
(this.createdTimestamp && this.maxAge ? this.createdTimestamp + this.maxAge * 1000 : null)
);
}

@@ -149,0 +155,0 @@

@@ -5,4 +5,6 @@ 'use strict';

const Base = require('./Base');
const BaseMessageComponent = require('./BaseMessageComponent');
const ClientApplication = require('./ClientApplication');
const MessageAttachment = require('./MessageAttachment');
const MessageComponentInteractionCollector = require('./MessageComponentInteractionCollector');
const Embed = require('./MessageEmbed');

@@ -128,2 +130,8 @@ const Mentions = require('./MessageMentions');

/**
* A list of MessageActionRows in the message
* @type {MessageActionRow[]}
*/
this.components = (data.components ?? []).map(c => BaseMessageComponent.create(c, this.client));
/**
* A collection of attachments in the message - e.g. Pictures - mapped by their ID

@@ -189,5 +197,11 @@ * @type {Collection<Snowflake, MessageAttachment>}

*/
this.application = data.application ? new ClientApplication(this.client, data.application) : null;
this.groupActivityApplication = data.application ? new ClientApplication(this.client, data.application) : null;
/**
* ID of the application of the interaction that sent this message, if any
* @type {?Snowflake}
*/
this.applicationID = data.application_id ?? null;
/**
* Group activity

@@ -241,3 +255,3 @@ * @type {?MessageActivity}

* Partial data of the interaction that a message is a reply to
* @typedef {object} MessageInteraction
* @typedef {Object} MessageInteraction
* @property {Snowflake} id The ID of the interaction

@@ -289,2 +303,4 @@ * @property {InteractionType} type The type of the interaction

else this.embeds = this.embeds.slice();
if ('components' in data) this.components = data.components.map(c => BaseMessageComponent.create(c, this.client));
else this.components = this.components.slice();

@@ -416,2 +432,42 @@ if ('attachments' in data) {

/**
* Creates a message component interaction collector.
* @param {CollectorFilter} filter The filter to apply
* @param {MessageComponentInteractionCollectorOptions} [options={}] Options to send to the collector
* @returns {MessageComponentInteractionCollector}
* @example
* // Create a message component interaction collector
* const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID';
* const collector = message.createMessageComponentInteractionCollector(filter, { time: 15000 });
* collector.on('collect', i => console.log(`Collected ${i.customID}`));
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
*/
createMessageComponentInteractionCollector(filter, options = {}) {
return new MessageComponentInteractionCollector(this, filter, options);
}
/**
* Collects a single component interaction that passes the filter.
* The Promise will reject if the time expires.
* @param {CollectorFilter} filter The filter function to use
* @param {number} [time] Time to wait for an interaction before rejecting
* @returns {Promise<MessageComponentInteraction>}
* @example
* // Collect a message component interaction
* const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID';
* message.awaitMessageComponentInteraction(filter, 15000)
* .then(interaction => console.log(`${interaction.customID} was clicked!`))
* .catch(console.error);
*/
awaitMessageComponentInteraction(filter, time) {
return new Promise((resolve, reject) => {
const collector = this.createMessageComponentInteractionCollector(filter, { max: 1, time });
collector.once('end', interactions => {
const interaction = interactions.first();
if (!interaction) reject(new Error('INTERACTION_COLLECTOR_TIMEOUT'));
else resolve(interaction);
});
});
}
/**
* Whether the message is editable by the client user

@@ -483,3 +539,3 @@ * @type {boolean}

* @typedef {Object} MessageEditOptions
* @property {string} [content] Content to be edited
* @property {?string} [content] Content to be edited
* @property {MessageEmbed|Object} [embed] An embed to be added/edited

@@ -492,2 +548,4 @@ * @property {string|boolean} [code] Language for optional codeblock formatting to apply

* @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to add to the message
* @property {MessageActionRow[]|MessageActionRowOptions[]|MessageActionRowComponentResolvable[][]} [components]
* Action rows containing interactive components for the message (buttons, select menus)
*/

@@ -497,4 +555,3 @@

* Edits the content of the message.
* @param {string|APIMessage} [content] The new content for the message
* @param {MessageEditOptions|MessageEmbed} [options] The options to provide
* @param {string|APIMessage|MessageEditOptions} options The options to provide
* @returns {Promise<Message>}

@@ -507,4 +564,3 @@ * @example

*/
edit(content, options) {
options = content instanceof APIMessage ? content : APIMessage.create(this, content, options);
edit(options) {
return this.channel.messages.edit(this.id, options);

@@ -605,4 +661,3 @@ }

* Send an inline reply to this message.
* @param {StringResolvable|APIMessage} [content=''] The content for the message
* @param {ReplyMessageOptions|MessageAdditions} [options] The additional options to provide
* @param {string|APIMessage|ReplyMessageOptions} options The options to provide
* @returns {Promise<Message|Message[]>}

@@ -615,13 +670,16 @@ * @example

*/
reply(content, options) {
return this.channel.send(
content instanceof APIMessage
? content
: APIMessage.transformOptions(content, options, {
reply: {
messageReference: this,
failIfNotExists: options?.failIfNotExists ?? content?.failIfNotExists ?? true,
},
}),
);
reply(options) {
let data;
if (options instanceof APIMessage) {
data = options;
} else {
data = APIMessage.create(this, options, {
reply: {
messageReference: this,
failIfNotExists: options?.failIfNotExists ?? true,
},
});
}
return this.channel.send(data);
}

@@ -719,3 +777,3 @@

author: 'authorID',
application: 'applicationID',
groupActivityApplication: 'groupActivityApplicationID',
guild: 'guildID',

@@ -722,0 +780,0 @@ cleanContent: true,

'use strict';
const { RangeError } = require('../errors');
const Util = require('../util/Util');

@@ -4,0 +5,0 @@

@@ -160,2 +160,4 @@ 'use strict';

awaitMessages() {}
createMessageComponentInteractionCollector() {}
awaitMessageComponentInteraction() {}
bulkDelete() {}

@@ -162,0 +164,0 @@ }

@@ -104,12 +104,2 @@ 'use strict';

/**
* If this is a voice state of the client user, then this will refer to the active VoiceConnection for this guild
* @type {?VoiceConnection}
* @readonly
*/
get connection() {
if (this.id !== this.client.user.id) return null;
return this.client.voice.connections.get(this.guild.id) || null;
}
/**
* Whether this member is either self-deafened or server-deafened

@@ -133,12 +123,2 @@ * @type {?boolean}

/**
* Whether this member is currently speaking. A boolean if the information is available (aka
* the bot is connected to any voice channel or stage channel in the guild), otherwise this is `null`
* @type {?boolean}
* @readonly
*/
get speaking() {
return this.channel && this.channel.connection ? Boolean(this.channel.connection._speaking.get(this.id)) : null;
}
/**
* Mutes/unmutes the member of this voice state.

@@ -186,30 +166,2 @@ * @param {boolean} mute Whether or not the member should be muted

/**
* Self-mutes/unmutes the bot for this voice state.
* @param {boolean} mute Whether or not the bot should be self-muted
* @returns {Promise<boolean>} true if the voice state was successfully updated, otherwise false
*/
async setSelfMute(mute) {
if (this.id !== this.client.user.id) throw new Error('VOICE_STATE_NOT_OWN');
if (typeof mute !== 'boolean') throw new TypeError('VOICE_STATE_INVALID_TYPE', 'mute');
if (!this.connection) return false;
this.selfMute = mute;
await this.connection.sendVoiceStateUpdate();
return true;
}
/**
* Self-deafens/undeafens the bot for this voice state.
* @param {boolean} deaf Whether or not the bot should be self-deafened
* @returns {Promise<boolean>} true if the voice state was successfully updated, otherwise false
*/
async setSelfDeaf(deaf) {
if (this.id !== this.client.user.id) return new Error('VOICE_STATE_NOT_OWN');
if (typeof deaf !== 'boolean') return new TypeError('VOICE_STATE_INVALID_TYPE', 'deaf');
if (!this.connection) return false;
this.selfDeaf = deaf;
await this.connection.sendVoiceStateUpdate();
return true;
}
/**
* Toggles the request to speak in the channel.

@@ -216,0 +168,0 @@ * Only applicable for stage channels and for the client's own voice state.

@@ -101,5 +101,7 @@ 'use strict';

* @property {MessageEmbed[]|Object[]} [embeds] See {@link WebhookMessageOptions#embeds}
* @property {StringResolvable} [content] See {@link BaseMessageOptions#content}
* @property {string} [content] See {@link BaseMessageOptions#content}
* @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] See {@link BaseMessageOptions#files}
* @property {MessageMentionOptions} [allowedMentions] See {@link BaseMessageOptions#allowedMentions}
* @property {MessageActionRow[]|MessageActionRowOptions[]|MessageActionRowComponentResolvable[][]} [components]
* Action rows containing interactive components for the message (buttons, select menus)
*/

@@ -109,4 +111,3 @@

* Sends a message with this webhook.
* @param {string|APIMessage} [content=''] The content to send
* @param {WebhookMessageOptions|MessageAdditions} [options={}] The options to provide
* @param {string|APIMessage|WebhookMessageOptions} options The options to provide
* @returns {Promise<Message|Object>}

@@ -137,3 +138,4 @@ * @example

* // Send an embed with a local image inside
* webhook.send('This is an embed', {
* webhook.send({
* content: 'This is an embed',
* embeds: [{

@@ -152,9 +154,9 @@ * thumbnail: {

*/
async send(content, options) {
async send(options) {
let apiMessage;
if (content instanceof APIMessage) {
apiMessage = content.resolveData();
if (options instanceof APIMessage) {
apiMessage = options.resolveData();
} else {
apiMessage = APIMessage.create(this, content, options).resolveData();
apiMessage = APIMessage.create(this, options).resolveData();
if (Array.isArray(apiMessage.data.content)) {

@@ -249,11 +251,14 @@ return Promise.all(apiMessage.split().map(this.send.bind(this)));

* @param {MessageResolvable|'@original'} message The message to edit
* @param {StringResolvable|APIMessage} [content] The new content for the message
* @param {WebhookEditMessageOptions|MessageAdditions} [options] The options to provide
* @param {string|APIMessage|WebhookEditMessageOptions} options The options to provide
* @returns {Promise<Message|Object>} Returns the raw message data if the webhook was instantiated as a
* {@link WebhookClient} or if the channel is uncached, otherwise a {@link Message} will be returned
*/
async editMessage(message, content, options) {
const { data, files } = await (
content.resolveData?.() ?? APIMessage.create(this, content, options).resolveData()
).resolveFiles();
async editMessage(message, options) {
let apiMessage;
if (options instanceof APIMessage) apiMessage = options;
else apiMessage = APIMessage.create(this, options);
const { data, files } = await apiMessage.resolveData().resolveFiles();
const d = await this.client.api

@@ -333,3 +338,3 @@ .webhooks(this.id, this.token)

static applyToClass(structure) {
static applyToClass(structure, ignore = []) {
for (const prop of [

@@ -347,2 +352,3 @@ 'send',

]) {
if (ignore.includes(prop)) continue;
Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(Webhook.prototype, prop));

@@ -349,0 +355,0 @@ }

@@ -44,3 +44,2 @@ 'use strict';

has(bit) {
if (Array.isArray(bit)) return bit.every(p => this.has(p));
bit = this.constructor.resolve(bit);

@@ -147,3 +146,2 @@ return (this.bitfield & bit) === bit;

const { defaultBit } = this;
if (typeof bit === 'undefined') return defaultBit;
if (typeof defaultBit === typeof bit && bit >= defaultBit) return bit;

@@ -150,0 +148,0 @@ if (bit instanceof BitField) return bit.bitfield;

@@ -7,2 +7,20 @@ 'use strict';

/**
* Rate limit data
* @typedef {Object} RateLimitData
* @property {number} timeout Time until this rate limit ends, in ms
* @property {number} limit The maximum amount of requests of this endpoint
* @property {string} method The http method of this request
* @property {string} path The path of the request relative to the HTTP endpoint
* @property {string} route The route of the request relative to the HTTP endpoint
* @property {boolean} global Whether this is a global rate limit
*/
/**
* Whether this rate limit should throw an Error
* @typedef {Function} RateLimitQueueFilter
* @param {RateLimitData} rateLimitData The data of this rate limit
* @returns {boolean|Promise<boolean>}
*/
/**
* Options for a client.

@@ -38,2 +56,6 @@ * @typedef {Object} ClientOptions

* the standard global limit used by Discord)
* @property {string[]|RateLimitQueueFilter} [rejectOnRateLimit] Decides how rate limits and pre-emptive throttles
* should be handled. If this option is an array containing the prefix of the request route (e.g. /channels to match any
* route starting with /channels, such as /channels/222197033908436994/messages) or a function returning true, a
* {@link RateLimitError} will be thrown. Otherwise the request will be queued for later
* @property {number} [retryLimit=1] How many times to retry on 5XX errors (Infinity for indefinite amount of retries)

@@ -241,3 +263,2 @@ * @property {PresenceData} [presence={}] Presence data to use upon login

GUILD_UNAVAILABLE: 'guildUnavailable',
GUILD_AVAILABLE: 'guildAvailable',
GUILD_MEMBER_ADD: 'guildMemberAdd',

@@ -279,3 +300,2 @@ GUILD_MEMBER_REMOVE: 'guildMemberRemove',

TYPING_START: 'typingStart',
TYPING_STOP: 'typingStop',
WEBHOOKS_UPDATE: 'webhookUpdate',

@@ -806,5 +826,6 @@ INTERACTION_CREATE: 'interaction',

* * APPLICATION_COMMAND
* * MESSAGE_COMPONENT
* @typedef {string} InteractionType
*/
exports.InteractionTypes = createEnum([null, 'PING', 'APPLICATION_COMMAND']);
exports.InteractionTypes = createEnum([null, 'PING', 'APPLICATION_COMMAND', 'MESSAGE_COMPONENT']);

@@ -816,2 +837,4 @@ /**

* * DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
* * DEFERRED_MESSAGE_UPDATE
* * UPDATE_MESSAGE
* @typedef {string} InteractionResponseType

@@ -826,5 +849,26 @@ */

'DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE',
'DEFERRED_MESSAGE_UPDATE',
'UPDATE_MESSAGE',
]);
/**
* The type of a message component
* * ACTION_ROW
* * BUTTON
* @typedef {string} MessageComponentType
*/
exports.MessageComponentTypes = createEnum([null, 'ACTION_ROW', 'BUTTON']);
/**
* The style of a message button
* * PRIMARY
* * SECONDARY
* * SUCCESS
* * DANGER
* * LINK
* @typedef {string} MessageButtonStyle
*/
exports.MessageButtonStyles = createEnum([null, 'PRIMARY', 'SECONDARY', 'SUCCESS', 'DANGER', 'LINK']);
/**
* NSFW level of a Guild

@@ -831,0 +875,0 @@ * * DEFAULT

@@ -23,2 +23,4 @@ 'use strict';

* * **`CommandInteraction`**
* * **`MessageComponentInteraction`**
* * **`ButtonInteraction`**
* @typedef {string} ExtendableStructure

@@ -115,4 +117,6 @@ */

CommandInteraction: require('../structures/CommandInteraction'),
MessageComponentInteraction: require('../structures/MessageComponentInteraction'),
ButtonInteraction: require('../structures/ButtonInteraction'),
};
module.exports = Structures;

@@ -271,2 +271,16 @@ 'use strict';

/**
* Resolves a partial emoji object from an {@link EmojiIdentifierResolvable}, without checking a Client.
* @param {EmojiIdentifierResolvable} emoji Emoji identifier to resolve
* @returns {?RawEmoji}
* @private
*/
static resolvePartialEmoji(emoji) {
if (!emoji) return null;
if (typeof emoji === 'string') return /^\d{17,19}$/.test(emoji) ? { id: emoji } : Util.parseEmoji(emoji);
const { id, name, animated } = emoji;
if (!id && !name) return null;
return { id, name, animated };
}
/**
* Shallow-copies an object with its class/prototype intact.

@@ -273,0 +287,0 @@ * @param {Object} obj Object to clone

@@ -41,5 +41,5 @@ /// <reference path="index.d.ts" />

const embed = new MessageEmbed();
assertIsMessage(channel.send(attachment));
assertIsMessage(channel.send({ files: [attachment] }));
assertIsMessage(channel.send(embed));
assertIsMessage(channel.send([attachment, embed]));
assertIsMessage(channel.send({ embed, files: [attachment] }));

@@ -46,0 +46,0 @@ assertIsMessageArray(channel.send({ split: true }));

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc