discord.js
Advanced tools
Comparing version 13.0.0-dev.086c3f0799d65c64c4e60d6370246a37a27a1eab to 13.0.0-dev.35c2225f5035fce47b0defc12754bda5901a453c
{ | ||
"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
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
8
13
6
871702
20
209
24320
103
- Removedprism-media@^1.2.9
- Removedtweetnacl@^1.0.3
- Removedprism-media@1.3.5(transitive)
- Removedtweetnacl@1.0.3(transitive)