mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
Errors Standardization (#1246)
* errors and stuff * more errors * all the errors * fix build
This commit is contained in:
@@ -19,6 +19,7 @@ const Invite = require('../structures/Invite');
|
||||
const OAuth2Application = require('../structures/OAuth2Application');
|
||||
const ShardClientUtil = require('../sharding/ShardClientUtil');
|
||||
const VoiceBroadcast = require('./voice/VoiceBroadcast');
|
||||
const { Error, TypeError, RangeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* The main hub for interacting with the Discord API, and the starting point for any bot.
|
||||
@@ -287,7 +288,7 @@ class Client extends EventEmitter {
|
||||
*/
|
||||
login(token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof token !== 'string') throw new Error(Constants.Errors.INVALID_TOKEN);
|
||||
if (typeof token !== 'string') throw new Error('TOKEN_INVALID');
|
||||
token = token.replace(/^Bot\s*/i, '');
|
||||
this.manager.connectToWebSocket(token, resolve, reject);
|
||||
});
|
||||
@@ -375,7 +376,9 @@ class Client extends EventEmitter {
|
||||
* or -1 if the message cache lifetime is unlimited
|
||||
*/
|
||||
sweepMessages(lifetime = this.options.messageCacheLifetime) {
|
||||
if (typeof lifetime !== 'number' || isNaN(lifetime)) throw new TypeError('The lifetime must be a number.');
|
||||
if (typeof lifetime !== 'number' || isNaN(lifetime)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Lifetime', 'a number');
|
||||
}
|
||||
if (lifetime <= 0) {
|
||||
this.emit('debug', 'Didn\'t sweep messages - lifetime is unlimited');
|
||||
return -1;
|
||||
@@ -524,41 +527,40 @@ class Client extends EventEmitter {
|
||||
*/
|
||||
_validateOptions(options = this.options) {
|
||||
if (typeof options.shardCount !== 'number' || isNaN(options.shardCount)) {
|
||||
throw new TypeError('The shardCount option must be a number.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number');
|
||||
}
|
||||
if (typeof options.shardId !== 'number' || isNaN(options.shardId)) {
|
||||
throw new TypeError('The shardId option must be a number.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shardId', 'a number');
|
||||
}
|
||||
if (options.shardCount < 0) throw new RangeError('The shardCount option must be at least 0.');
|
||||
if (options.shardId < 0) throw new RangeError('The shardId option must be at least 0.');
|
||||
if (options.shardCount < 0) throw new RangeError('CLIENT_INVALID_OPTION', 'shardCount', 'at least 0');
|
||||
if (options.shardId < 0) throw new RangeError('CLIENT_INVALID_OPTION', 'shardId', 'at least 0');
|
||||
if (options.shardId !== 0 && options.shardId >= options.shardCount) {
|
||||
throw new RangeError('The shardId option must be less than shardCount.');
|
||||
throw new RangeError('CLIENT_INVALID_OPTION', 'shardId', 'less than shardCount');
|
||||
}
|
||||
if (typeof options.messageCacheMaxSize !== 'number' || isNaN(options.messageCacheMaxSize)) {
|
||||
throw new TypeError('The messageCacheMaxSize option must be a number.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'messageCacheMaxSize', 'a number');
|
||||
}
|
||||
if (typeof options.messageCacheLifetime !== 'number' || isNaN(options.messageCacheLifetime)) {
|
||||
throw new TypeError('The messageCacheLifetime option must be a number.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'The messageCacheLifetime', 'a number');
|
||||
}
|
||||
if (typeof options.messageSweepInterval !== 'number' || isNaN(options.messageSweepInterval)) {
|
||||
throw new TypeError('The messageSweepInterval option must be a number.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'messageSweepInterval', 'a number');
|
||||
}
|
||||
if (typeof options.fetchAllMembers !== 'boolean') {
|
||||
throw new TypeError('The fetchAllMembers option must be a boolean.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'fetchAllMembers', 'a boolean');
|
||||
}
|
||||
if (typeof options.disableEveryone !== 'boolean') {
|
||||
throw new TypeError('The disableEveryone option must be a boolean.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'disableEveryone', 'a boolean');
|
||||
}
|
||||
if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) {
|
||||
throw new TypeError('The restWsBridgeTimeout option must be a number.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number');
|
||||
}
|
||||
if (typeof options.internalSharding !== 'boolean') {
|
||||
throw new TypeError('The internalSharding option must be a boolean.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'internalSharding', 'a boolean');
|
||||
}
|
||||
if (options.internalSharding && ('shardCount' in options || 'shardId' in options)) {
|
||||
throw new TypeError('You cannot specify shardCount/shardId if you are using internal sharding.');
|
||||
if (!(options.disabledEvents instanceof Array)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'disabledEvents', 'an Array');
|
||||
}
|
||||
if (!(options.disabledEvents instanceof Array)) throw new TypeError('The disabledEvents option must be an Array.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const Channel = require('../structures/Channel');
|
||||
const GuildMember = require('../structures/GuildMember');
|
||||
const Emoji = require('../structures/Emoji');
|
||||
const ReactionEmoji = require('../structures/ReactionEmoji');
|
||||
const { Error, TypeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* The DataResolver identifies different objects and tries to resolve a specific piece of information from them, e.g.
|
||||
@@ -194,14 +195,14 @@ class ClientDataResolver {
|
||||
snekfetch.get(resource)
|
||||
.end((err, res) => {
|
||||
if (err) return reject(err);
|
||||
if (!(res.body instanceof Buffer)) return reject(new TypeError('The response body isn\'t a Buffer.'));
|
||||
if (!(res.body instanceof Buffer)) return reject(new TypeError('REQ_BODY_TYPE'));
|
||||
return resolve(res.body);
|
||||
});
|
||||
} else {
|
||||
const file = path.resolve(resource);
|
||||
fs.stat(file, (err, stats) => {
|
||||
if (err) return reject(err);
|
||||
if (!stats || !stats.isFile()) return reject(new Error(`The file could not be found: ${file}`));
|
||||
if (!stats || !stats.isFile()) return reject(new Error('FILE_NOT_FOUND', file));
|
||||
fs.readFile(file, (err2, data) => {
|
||||
if (err2) reject(err2); else resolve(data);
|
||||
});
|
||||
@@ -211,7 +212,7 @@ class ClientDataResolver {
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(new TypeError('The resource must be a string or Buffer.'));
|
||||
return Promise.reject(new TypeError('REQ_RESOURCE_TYPE'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const Constants = require('../util/Constants');
|
||||
const WebSocketConnection = require('./websocket/WebSocketConnection');
|
||||
const { Error } = require('../errors');
|
||||
|
||||
/**
|
||||
* Manages the state and background tasks of the client.
|
||||
@@ -37,16 +38,16 @@ class ClientManager {
|
||||
connectToWebSocket(token, resolve, reject) {
|
||||
this.client.emit(Constants.Events.DEBUG, `Authenticated using token ${token}`);
|
||||
this.client.token = token;
|
||||
const timeout = this.client.setTimeout(() => reject(new Error(Constants.Errors.TOOK_TOO_LONG)), 1000 * 300);
|
||||
const timeout = this.client.setTimeout(() => reject(new Error('INVALID_TOKEN')), 1000 * 300);
|
||||
this.client.api.gateway.get().then(res => {
|
||||
const protocolVersion = Constants.DefaultOptions.ws.version;
|
||||
const gateway = `${res.url}/?v=${protocolVersion}&encoding=${WebSocketConnection.ENCODING}`;
|
||||
this.client.emit(Constants.Events.DEBUG, `Using gateway ${gateway}`);
|
||||
this.client.ws.connect(gateway);
|
||||
this.client.ws.connection.once('close', event => {
|
||||
if (event.code === 4004) reject(new Error(Constants.Errors.BAD_LOGIN));
|
||||
if (event.code === 4010) reject(new Error(Constants.Errors.INVALID_SHARD));
|
||||
if (event.code === 4011) reject(new Error(Constants.Errors.SHARDING_REQUIRED));
|
||||
if (event.code === 4004) reject(new Error('TOKEN_INVALID'));
|
||||
if (event.code === 4010) reject(new Error('SHARDING_INVALID'));
|
||||
if (event.code === 4011) reject(new Error('SHARDING_REQUIRED'));
|
||||
});
|
||||
this.client.once(Constants.Events.READY, () => {
|
||||
resolve(token);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const querystring = require('querystring');
|
||||
const snekfetch = require('snekfetch');
|
||||
const Constants = require('../../util/Constants');
|
||||
const { Error } = require('../../errors');
|
||||
|
||||
class APIRequest {
|
||||
constructor(rest, method, path, options) {
|
||||
@@ -28,7 +28,7 @@ class APIRequest {
|
||||
} else if (this.client.token) {
|
||||
return this.client.token;
|
||||
}
|
||||
throw new Error(Constants.Errors.NO_TOKEN);
|
||||
throw new Error('TOKEN_MISSING');
|
||||
}
|
||||
|
||||
gen() {
|
||||
|
||||
@@ -3,7 +3,7 @@ const SequentialRequestHandler = require('./RequestHandlers/Sequential');
|
||||
const BurstRequestHandler = require('./RequestHandlers/Burst');
|
||||
const APIRequest = require('./APIRequest');
|
||||
const mountApi = require('./APIRouter');
|
||||
const Constants = require('../../util/Constants');
|
||||
const { Error } = require('../../errors');
|
||||
|
||||
class RESTManager {
|
||||
constructor(client) {
|
||||
@@ -39,7 +39,7 @@ class RESTManager {
|
||||
case 'burst':
|
||||
return BurstRequestHandler;
|
||||
default:
|
||||
throw new Error(Constants.Errors.INVALID_RATE_LIMIT_METHOD);
|
||||
throw new Error('RATELIMIT_INVALID_METHOD');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const Collection = require('../../util/Collection');
|
||||
const VoiceConnection = require('./VoiceConnection');
|
||||
const { Error } = require('../../errors');
|
||||
|
||||
/**
|
||||
* Manages all the voice stuff for the client.
|
||||
@@ -44,11 +45,7 @@ class ClientVoiceManager {
|
||||
joinChannel(channel) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!channel.joinable) {
|
||||
if (channel.full) {
|
||||
throw new Error('You do not have permission to join this voice channel; it is full.');
|
||||
} else {
|
||||
throw new Error('You do not have permission to join this voice channel.');
|
||||
}
|
||||
throw new Error('VOICE_JOIN_CHANNEL', channel.full);
|
||||
}
|
||||
|
||||
let connection = this.connections.get(channel.guild.id);
|
||||
|
||||
@@ -6,6 +6,7 @@ const AudioPlayer = require('./player/AudioPlayer');
|
||||
const VoiceReceiver = require('./receiver/VoiceReceiver');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const Prism = require('prism-media');
|
||||
const { Error } = require('../../errors');
|
||||
|
||||
/**
|
||||
* Represents a connection to a guild's voice server.
|
||||
@@ -341,8 +342,8 @@ class VoiceConnection extends EventEmitter {
|
||||
*/
|
||||
connect() {
|
||||
if (this.status !== Constants.VoiceStatus.RECONNECTING) {
|
||||
if (this.sockets.ws) throw new Error('There is already an existing WebSocket connection.');
|
||||
if (this.sockets.udp) throw new Error('There is already an existing UDP connection.');
|
||||
if (this.sockets.ws) throw new Error('WS_CONNECTION_EXISTS');
|
||||
if (this.sockets.udp) throw new Error('UDP_CONNECTION_EXISTS');
|
||||
}
|
||||
|
||||
if (this.sockets.ws) this.sockets.ws.shutdown();
|
||||
|
||||
@@ -2,6 +2,7 @@ const udp = require('dgram');
|
||||
const dns = require('dns');
|
||||
const Constants = require('../../util/Constants');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const { Error } = require('../../errors');
|
||||
|
||||
/**
|
||||
* Represents a UDP client for a Voice Connection.
|
||||
@@ -89,8 +90,8 @@ class VoiceConnectionUDPClient extends EventEmitter {
|
||||
*/
|
||||
send(packet) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.socket) throw new Error('Tried to send a UDP packet, but there is no socket available.');
|
||||
if (!this.discordAddress || !this.discordPort) throw new Error('Malformed UDP address or port.');
|
||||
if (!this.socket) throw new Error('UDP_SEND_FAIL');
|
||||
if (!this.discordAddress || !this.discordPort) throw new Error('UDP_ADDRESS_MALFORMED');
|
||||
this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => {
|
||||
if (error) reject(error); else resolve(packet);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const Constants = require('../../util/Constants');
|
||||
const SecretKey = require('./util/SecretKey');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const { Error } = require('../../errors');
|
||||
|
||||
let WebSocket;
|
||||
try {
|
||||
@@ -88,9 +89,7 @@ class VoiceWebSocket extends EventEmitter {
|
||||
*/
|
||||
send(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||
throw new Error(`Voice websocket not open to send ${data}.`);
|
||||
}
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('WS_NOT_OPEN', data);
|
||||
this.ws.send(data, null, error => {
|
||||
if (error) reject(error); else resolve(data);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const { Error } = require('../../../errors');
|
||||
|
||||
const list = [
|
||||
require('./NodeOpusEngine'),
|
||||
require('./OpusScriptEngine'),
|
||||
@@ -30,5 +32,5 @@ exports.fetch = engineOptions => {
|
||||
|
||||
exports.guaranteeOpusEngine = () => {
|
||||
if (typeof opusEngineFound === 'undefined') opusEngineFound = Boolean(exports.fetch());
|
||||
if (!opusEngineFound) throw new Error('Couldn\'t find an Opus engine.');
|
||||
if (!opusEngineFound) throw new Error('OPUS_ENGINE_MISSING');
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ const EventEmitter = require('events').EventEmitter;
|
||||
const secretbox = require('../util/Secretbox');
|
||||
const Readable = require('./VoiceReadable');
|
||||
const OpusEncoders = require('../opus/OpusEngineList');
|
||||
const { Error } = require('../../../errors');
|
||||
|
||||
const nonce = Buffer.alloc(24);
|
||||
nonce.fill(0);
|
||||
@@ -122,8 +123,8 @@ class VoiceReceiver extends EventEmitter {
|
||||
*/
|
||||
createOpusStream(user) {
|
||||
user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user);
|
||||
if (!user) throw new Error('Couldn\'t resolve the user to create Opus stream.');
|
||||
if (this.opusStreams.get(user.id)) throw new Error('There is already an existing stream for that user.');
|
||||
if (!user) throw new Error('VOICE_USER_MISSING');
|
||||
if (this.opusStreams.get(user.id)) throw new Error('VOICE_STREAM_EXISTS');
|
||||
const stream = new Readable();
|
||||
this.opusStreams.set(user.id, stream);
|
||||
return stream;
|
||||
@@ -137,8 +138,8 @@ class VoiceReceiver extends EventEmitter {
|
||||
*/
|
||||
createPCMStream(user) {
|
||||
user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user);
|
||||
if (!user) throw new Error('Couldn\'t resolve the user to create PCM stream.');
|
||||
if (this.pcmStreams.get(user.id)) throw new Error('There is already an existing stream for that user.');
|
||||
if (!user) throw new Error('VOICE_USER_MISSING');
|
||||
if (this.pcmStreams.get(user.id)) throw new Error('VOICE_STREAM_EXISTS');
|
||||
const stream = new Readable();
|
||||
this.pcmStreams.set(user.id, stream);
|
||||
return stream;
|
||||
|
||||
65
src/errors/DJSError.js
Normal file
65
src/errors/DJSError.js
Normal file
@@ -0,0 +1,65 @@
|
||||
// Heavily inspired by node's `internal/errors` module
|
||||
|
||||
const kCode = Symbol('code');
|
||||
const messages = new Map();
|
||||
const assert = require('assert');
|
||||
const util = require('util');
|
||||
|
||||
/**
|
||||
* Extend an error of some sort into a DiscordjsError
|
||||
* @param {Error} Base Base error to extend
|
||||
* @returns {DiscordjsError}
|
||||
*/
|
||||
function makeDiscordjsError(Base) {
|
||||
return class DiscordjsError extends Base {
|
||||
constructor(key, ...args) {
|
||||
super(message(key, args));
|
||||
this[kCode] = key;
|
||||
if (Error.captureStackTrace) Error.captureStackTrace(this, DiscordjsError);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return `${super.name} [${this[kCode]}]`;
|
||||
}
|
||||
|
||||
get code() {
|
||||
return this[kCode];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the message for an error
|
||||
* @param {string} key Error key
|
||||
* @param {Array<*>} args Arguments to pass for util format or as function args
|
||||
* @returns {string} Formatted string
|
||||
*/
|
||||
function message(key, args) {
|
||||
assert.strictEqual(typeof key, 'string');
|
||||
const msg = messages.get(key);
|
||||
assert(msg, `An invalid error message key was used: ${key}.`);
|
||||
let fmt = util.format;
|
||||
if (typeof msg === 'function') {
|
||||
fmt = msg;
|
||||
} else {
|
||||
if (args === undefined || args.length === 0) return msg;
|
||||
args.unshift(msg);
|
||||
}
|
||||
return String(fmt(...args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an error code and message
|
||||
* @param {string} sym Unique name for the error
|
||||
* @param {*} val Value of the error
|
||||
*/
|
||||
function register(sym, val) {
|
||||
messages.set(sym, typeof val === 'function' ? val : String(val));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
register,
|
||||
Error: makeDiscordjsError(Error),
|
||||
TypeError: makeDiscordjsError(TypeError),
|
||||
RangeError: makeDiscordjsError(RangeError),
|
||||
};
|
||||
84
src/errors/Messages.js
Normal file
84
src/errors/Messages.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const { register } = require('./DJSError');
|
||||
|
||||
const Messages = {
|
||||
CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`,
|
||||
|
||||
TOKEN_INVALID: 'An invalid token was provided.',
|
||||
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.',
|
||||
|
||||
FEATURE_BOT_ONLY: 'Only bot accounts are able to make use of this feature.',
|
||||
FEATURE_USER_ONLY: 'Only user accounts are able to make use of this feature.',
|
||||
|
||||
WS_BAD_MESSAGE: 'A bad message was received from the websocket; either bad compression, or not JSON.',
|
||||
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
|
||||
WS_NOT_OPEN: (data = 'data') => `Websocket not open to send ${data}`,
|
||||
|
||||
PERMISSIONS_INVALID: 'Invalid permission string or number.',
|
||||
PERMISSIONS_INVALID_FLAG: 'Invalid bitfield flag string or number',
|
||||
|
||||
RATELIMIT_INVALID_METHOD: 'Unknown rate limiting method.',
|
||||
|
||||
SHARDING_INVALID: 'Invalid shard settings were provided.',
|
||||
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.',
|
||||
SHARDING_CHILD_CONNECTION: 'Failed to send message to shard\'s process.',
|
||||
SHARDING_PARENT_CONNECTION: 'Failed to send message to master process.',
|
||||
SHARDING_NO_SHARDS: 'No shards have been spawned',
|
||||
SHARDING_IN_PROCESS: 'Shards are still being spawned',
|
||||
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards`,
|
||||
|
||||
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
|
||||
COLOR_CONVERT: 'Unable to convert color to a number.',
|
||||
|
||||
EMBED_FIELD_COUNT: 'MessageEmbeds may not exceed 25 fields.',
|
||||
EMBED_FIELD_NAME: 'MessageEmbed field names may not exceed 256 characters or be empty.',
|
||||
EMBED_FIELD_VALUE: 'MessageEmbed field values may not exceed 1024 characters or be empty.',
|
||||
EMBED_FILE_LIMIT: 'You may not upload more than one file at once.',
|
||||
EMBED_DESCRIPTION: 'MessageEmbed descriptions may not exceed 2048 characters.',
|
||||
EMBED_FOOTER_TEXT: 'MessageEmbed footer text may not exceed 2048 characters.',
|
||||
EMBED_TITLE: 'MessageEmbed titles may not exceed 256 characters.',
|
||||
|
||||
FILE_NOT_FOUND: file => `File could not be found: ${file}`,
|
||||
|
||||
USER_STATUS: 'User status must be a string',
|
||||
SHARD_MESSAGE_FAILED: 'Failed to send message to master process.',
|
||||
|
||||
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_STREAM_EXISTS: 'There is already an existing stream for that user.',
|
||||
VOICE_JOIN_CHANNEL: (full = false) =>
|
||||
`You do not have permission to join this voice channel${full ? '; it is full.' : '.'}`,
|
||||
|
||||
OPUS_ENGINE_MISSING: 'Couldn\'t find an Opus engine.',
|
||||
|
||||
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_BODY_TYPE: 'The response body isn\'t a Buffer.',
|
||||
REQ_RESOURCE_TYPE: 'The resource must be a string or Buffer.',
|
||||
|
||||
IMAGE_FORMAT: format => `Invalid image format: ${format}`,
|
||||
IMAGE_SIZE: size => `Invalid image size: ${size}`,
|
||||
|
||||
MESSAGE_MISSING: 'Message not found',
|
||||
MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.',
|
||||
MESSAGE_NONCE_TYPE: 'Message nonce must fit in an unsigned 64-bit integer.',
|
||||
|
||||
TYPING_COUNT: 'Count must be at least 1',
|
||||
|
||||
SPLIT_MAX_LEN: 'Message exceeds the max length and contains no split characters.',
|
||||
|
||||
BAN_RESOLVE_ID: 'Couldn\'t resolve the user ID to unban.',
|
||||
|
||||
PRUNE_DAYS_TYPE: 'Days must be a number',
|
||||
|
||||
SEARCH_CHANNEL_TYPE: 'Target must be a TextChannel, DMChannel, GroupDMChannel, or Guild.',
|
||||
|
||||
MESSAGE_SPLIT_MISSING: 'Message exceeds the max length and contains no split characters.',
|
||||
|
||||
GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.',
|
||||
|
||||
EMOJI_TYPE: 'Emoji must be a string or Emoji/ReactionEmoji',
|
||||
};
|
||||
|
||||
for (const [name, message] of Object.entries(Messages)) register(name, message);
|
||||
2
src/errors/index.js
Normal file
2
src/errors/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
module.exports = require('./DJSError');
|
||||
module.exports.Messages = require('./Messages');
|
||||
@@ -1,6 +1,7 @@
|
||||
const childProcess = require('child_process');
|
||||
const path = require('path');
|
||||
const Util = require('../util/Util');
|
||||
const { Error } = require('../errors');
|
||||
|
||||
/**
|
||||
* Represents a Shard spawned by the ShardingManager.
|
||||
@@ -60,7 +61,7 @@ class Shard {
|
||||
const sent = this.process.send(message, err => {
|
||||
if (err) reject(err); else resolve(this);
|
||||
});
|
||||
if (!sent) throw new Error('Failed to send message to shard\'s process.');
|
||||
if (!sent) throw new Error('SHARDING_CHILD_CONNECTION');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const Util = require('../util/Util');
|
||||
const { Error } = require('../errors');
|
||||
|
||||
/**
|
||||
* Helper class for sharded clients spawned as a child process, such as from a ShardingManager.
|
||||
@@ -40,7 +41,7 @@ class ShardClientUtil {
|
||||
const sent = process.send(message, err => {
|
||||
if (err) reject(err); else resolve();
|
||||
});
|
||||
if (!sent) throw new Error('Failed to send message to master process.');
|
||||
if (!sent) throw new Error('SHARDING_PARENT_CONNECTION');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const EventEmitter = require('events').EventEmitter;
|
||||
const Shard = require('./Shard');
|
||||
const Collection = require('../util/Collection');
|
||||
const Util = require('../util/Util');
|
||||
const { Error, TypeError, RangeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* This is a utility class that can be used to help you spawn shards of your client. Each shard is completely separate
|
||||
@@ -34,10 +35,10 @@ class ShardingManager extends EventEmitter {
|
||||
* @type {string}
|
||||
*/
|
||||
this.file = file;
|
||||
if (!file) throw new Error('File must be specified.');
|
||||
if (!file) throw new Error('CLIENT_INVALID_OPTION', 'File', 'specified.');
|
||||
if (!path.isAbsolute(file)) this.file = path.resolve(process.cwd(), file);
|
||||
const stats = fs.statSync(this.file);
|
||||
if (!stats.isFile()) throw new Error('File path does not point to a file.');
|
||||
if (!stats.isFile()) throw new Error('CLIENT_INVALID_OPTION', 'File', 'a file');
|
||||
|
||||
/**
|
||||
* Amount of shards that this manager is going to spawn
|
||||
@@ -46,11 +47,11 @@ class ShardingManager extends EventEmitter {
|
||||
this.totalShards = options.totalShards;
|
||||
if (this.totalShards !== 'auto') {
|
||||
if (typeof this.totalShards !== 'number' || isNaN(this.totalShards)) {
|
||||
throw new TypeError('Amount of shards must be a number.');
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.');
|
||||
}
|
||||
if (this.totalShards < 1) throw new RangeError('Amount of shards must be at least 1.');
|
||||
if (this.totalShards < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'at least 1.');
|
||||
if (this.totalShards !== Math.floor(this.totalShards)) {
|
||||
throw new RangeError('Amount of shards must be an integer.');
|
||||
throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,9 +110,13 @@ class ShardingManager extends EventEmitter {
|
||||
return this._spawn(count, delay);
|
||||
});
|
||||
} else {
|
||||
if (typeof amount !== 'number' || isNaN(amount)) throw new TypeError('Amount of shards must be a number.');
|
||||
if (amount < 1) throw new RangeError('Amount of shards must be at least 1.');
|
||||
if (amount !== Math.floor(amount)) throw new TypeError('Amount of shards must be an integer.');
|
||||
if (typeof amount !== 'number' || isNaN(amount)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.');
|
||||
}
|
||||
if (amount < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'at least 1.');
|
||||
if (amount !== Math.floor(amount)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.');
|
||||
}
|
||||
return this._spawn(amount, delay);
|
||||
}
|
||||
}
|
||||
@@ -125,7 +130,7 @@ class ShardingManager extends EventEmitter {
|
||||
*/
|
||||
_spawn(amount, delay) {
|
||||
return new Promise(resolve => {
|
||||
if (this.shards.size >= amount) throw new Error(`Already spawned ${this.shards.size} shards.`);
|
||||
if (this.shards.size >= amount) throw new Error('SHARDING_ALREADY_SPAWNED', this.shards.size);
|
||||
this.totalShards = amount;
|
||||
|
||||
this.createShard();
|
||||
@@ -181,8 +186,8 @@ class ShardingManager extends EventEmitter {
|
||||
* }).catch(console.error);
|
||||
*/
|
||||
fetchClientValues(prop) {
|
||||
if (this.shards.size === 0) return Promise.reject(new Error('No shards have been spawned.'));
|
||||
if (this.shards.size !== this.totalShards) return Promise.reject(new Error('Still spawning shards.'));
|
||||
if (this.shards.size === 0) return Promise.reject(new Error('SHARDING_NO_SHARDS'));
|
||||
if (this.shards.size !== this.totalShards) return Promise.reject(new Error('SHARDING_IN_PROCESS'));
|
||||
const promises = [];
|
||||
for (const shard of this.shards.values()) promises.push(shard.fetchClientValue(prop));
|
||||
return Promise.all(promises);
|
||||
|
||||
@@ -6,6 +6,7 @@ const Util = require('../util/Util');
|
||||
const Guild = require('./Guild');
|
||||
const Message = require('./Message');
|
||||
const GroupDMChannel = require('./GroupDMChannel');
|
||||
const { TypeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* Represents the logged in client's Discord user.
|
||||
@@ -193,7 +194,7 @@ class ClientUser extends User {
|
||||
}
|
||||
|
||||
if (data.status) {
|
||||
if (typeof data.status !== 'string') throw new TypeError('Status must be a string');
|
||||
if (typeof data.status !== 'string') throw new TypeError('STATUS_TYPE');
|
||||
if (this.bot) {
|
||||
status = data.status;
|
||||
} else {
|
||||
|
||||
@@ -14,6 +14,7 @@ const Util = require('../util/Util');
|
||||
const Snowflake = require('../util/Snowflake');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const Shared = require('./shared');
|
||||
const { Error, TypeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* Represents a guild (or a server) on Discord.
|
||||
@@ -804,8 +805,7 @@ class Guild {
|
||||
*/
|
||||
unban(user, reason) {
|
||||
const id = this.client.resolver.resolveUserID(user);
|
||||
if (!id) throw new Error('Couldn\'t resolve the user ID to unban.');
|
||||
|
||||
if (!id) throw new Error('BAN_RESOLVE_ID');
|
||||
return this.client.api.guilds(this.id).bans(id).delete({ reason })
|
||||
.then(() => user);
|
||||
}
|
||||
@@ -828,7 +828,7 @@ class Guild {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
pruneMembers({ days = 7, dry = false, reason } = {}) {
|
||||
if (typeof days !== 'number') throw new TypeError('Days must be a number.');
|
||||
if (typeof days !== 'number') throw new TypeError('PRUNE_DAYS_TYPE');
|
||||
return this.client.api.guilds(this.id).prune[dry ? 'get' : 'post']({ query: { days }, reason })
|
||||
.then(data => data.pruned);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const Role = require('./Role');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const Collection = require('../util/Collection');
|
||||
const Presence = require('./Presence').Presence;
|
||||
const { Error } = require('../errors');
|
||||
|
||||
/**
|
||||
* Represents a member of a guild on Discord.
|
||||
@@ -287,7 +288,7 @@ class GuildMember {
|
||||
*/
|
||||
permissionsIn(channel) {
|
||||
channel = this.client.resolver.resolveChannel(channel);
|
||||
if (!channel || !channel.guild) throw new Error('Could not resolve channel to a guild channel.');
|
||||
if (!channel || !channel.guild) throw new Error('GUILD_CHANNEL_RESOLVE');
|
||||
return channel.permissionsFor(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const Util = require('../util/Util');
|
||||
const Collection = require('../util/Collection');
|
||||
const Constants = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const { TypeError } = require('../errors');
|
||||
let GuildMember;
|
||||
|
||||
/**
|
||||
@@ -428,7 +429,7 @@ class Message {
|
||||
*/
|
||||
react(emoji) {
|
||||
emoji = this.client.resolver.resolveEmojiIdentifier(emoji);
|
||||
if (!emoji) throw new TypeError('Emoji must be a string or Emoji/ReactionEmoji');
|
||||
if (!emoji) throw new TypeError('EMOJI_TYPE');
|
||||
|
||||
return this.client.api.channels(this.channel.id).messages(this.id).reactions(emoji)['@me']
|
||||
.put()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const Util = require('../util/Util');
|
||||
const { RangeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* Represents an embed in a message (image/video preview, rich embed, etc.)
|
||||
@@ -165,13 +166,11 @@ class MessageEmbed {
|
||||
* @returns {MessageEmbed} This embed
|
||||
*/
|
||||
addField(name, value, inline = false) {
|
||||
if (this.fields.length >= 25) throw new RangeError('MessageEmbeds may not exceed 25 fields.');
|
||||
if (this.fields.length >= 25) throw new RangeError('EMBED_FIELD_COUNT');
|
||||
name = Util.resolveString(name);
|
||||
if (name.length > 256) throw new RangeError('MessageEmbed field names may not exceed 256 characters.');
|
||||
if (!/\S/.test(name)) throw new RangeError('MessageEmbed field names may not be empty.');
|
||||
if (!String(name) || name.length > 256) throw new RangeError('EMBED_FIELD_NAME');
|
||||
value = Util.resolveString(value);
|
||||
if (value.length > 1024) throw new RangeError('MessageEmbed field values may not exceed 1024 characters.');
|
||||
if (!/\S/.test(value)) throw new RangeError('MessageEmbed field values may not be empty.');
|
||||
if (!String(name) || value.length > 1024) throw new RangeError('EMBED_FIELD_VALUE');
|
||||
this.fields.push({ name, value, inline });
|
||||
return this;
|
||||
}
|
||||
@@ -183,7 +182,7 @@ class MessageEmbed {
|
||||
* @returns {MessageEmbed} This embed
|
||||
*/
|
||||
attachFile(file) {
|
||||
if (this.file) throw new RangeError('You may not upload more than one file at once.');
|
||||
if (this.file) throw new RangeError('EMBED_FILE_LIMIT');
|
||||
this.file = file;
|
||||
return this;
|
||||
}
|
||||
@@ -217,7 +216,7 @@ class MessageEmbed {
|
||||
*/
|
||||
setDescription(description) {
|
||||
description = Util.resolveString(description);
|
||||
if (description.length > 2048) throw new RangeError('MessageEmbed descriptions may not exceed 2048 characters.');
|
||||
if (description.length > 2048) throw new RangeError('EMBED_DESCRIPTION');
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
@@ -230,7 +229,7 @@ class MessageEmbed {
|
||||
*/
|
||||
setFooter(text, iconURL) {
|
||||
text = Util.resolveString(text);
|
||||
if (text.length > 2048) throw new RangeError('MessageEmbed footer text may not exceed 2048 characters.');
|
||||
if (text.length > 2048) throw new RangeError('EMBED_FOOTER_TEXT');
|
||||
this.footer = { text, iconURL };
|
||||
return this;
|
||||
}
|
||||
@@ -272,7 +271,7 @@ class MessageEmbed {
|
||||
*/
|
||||
setTitle(title) {
|
||||
title = Util.resolveString(title);
|
||||
if (title.length > 256) throw new RangeError('MessageEmbed titles may not exceed 256 characters.');
|
||||
if (title.length > 256) throw new RangeError('EMBED_TITLE');
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const MessageCollector = require('../MessageCollector');
|
||||
const Shared = require('../shared');
|
||||
const Collection = require('../../util/Collection');
|
||||
const Snowflake = require('../../util/Snowflake');
|
||||
const { Error, RangeError, TypeError } = require('../../errors');
|
||||
|
||||
/**
|
||||
* Interface for classes that have text-channel-like features.
|
||||
@@ -136,7 +137,7 @@ class TextBasedChannel {
|
||||
return this.fetchMessages({ limit: 1, around: messageID })
|
||||
.then(messages => {
|
||||
const msg = messages.get(messageID);
|
||||
if (!msg) throw new Error('Message not found.');
|
||||
if (!msg) throw new Error('MESSAGE_MISSING');
|
||||
return msg;
|
||||
});
|
||||
}
|
||||
@@ -227,7 +228,7 @@ class TextBasedChannel {
|
||||
* channel.startTyping();
|
||||
*/
|
||||
startTyping(count) {
|
||||
if (typeof count !== 'undefined' && count < 1) throw new RangeError('Count must be at least 1.');
|
||||
if (typeof count !== 'undefined' && count < 1) throw new RangeError('TYPING_COUNT');
|
||||
if (!this.client.user._typing.has(this.id)) {
|
||||
const endpoint = this.client.api.channels(this.id).typing;
|
||||
this.client.user._typing.set(this.id, {
|
||||
@@ -361,7 +362,7 @@ class TextBasedChannel {
|
||||
}).messages
|
||||
);
|
||||
}
|
||||
throw new TypeError('The messages must be an Array, Collection, or number.');
|
||||
throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const long = require('long');
|
||||
const { TypeError } = require('../../errors');
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageSearchOptions
|
||||
@@ -84,9 +85,7 @@ module.exports = function search(target, options) {
|
||||
const Guild = require('../Guild');
|
||||
const Message = require('../Message');
|
||||
|
||||
if (!(target instanceof Channel || target instanceof Guild)) {
|
||||
throw new TypeError('Target must be a TextChannel, DMChannel, GroupDMChannel, or Guild.');
|
||||
}
|
||||
if (!(target instanceof Channel || target instanceof Guild)) throw new TypeError('SEARCH_CHANNEL_TYPE');
|
||||
|
||||
let endpoint = target.client.api[target instanceof Channel ? 'channels' : 'guilds'](target.id).messages().search;
|
||||
return endpoint.get({ query: options }).then(body => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const Util = require('../../util/Util');
|
||||
const { RangeError } = require('../../errors');
|
||||
|
||||
module.exports = function sendMessage(channel, options) {
|
||||
const User = require('../User');
|
||||
@@ -8,7 +9,7 @@ module.exports = function sendMessage(channel, options) {
|
||||
|
||||
if (typeof nonce !== 'undefined') {
|
||||
nonce = parseInt(nonce);
|
||||
if (isNaN(nonce) || nonce < 0) throw new RangeError('Message nonce must fit in an unsigned 64-bit integer.');
|
||||
if (isNaN(nonce) || nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE');
|
||||
}
|
||||
|
||||
if (content) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
exports.Package = require('../../package.json');
|
||||
const { Error, RangeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* Options for a client.
|
||||
@@ -78,20 +79,6 @@ exports.WSCodes = {
|
||||
4011: 'Shard would be on too many guilds if connected',
|
||||
};
|
||||
|
||||
exports.Errors = {
|
||||
NO_TOKEN: 'Request to use token, but token was unavailable to the client.',
|
||||
NO_BOT_ACCOUNT: 'Only bot accounts are able to make use of this feature.',
|
||||
NO_USER_ACCOUNT: 'Only user accounts are able to make use of this feature.',
|
||||
BAD_WS_MESSAGE: 'A bad message was received from the websocket; either bad compression, or not JSON.',
|
||||
TOOK_TOO_LONG: 'Something took too long to do.',
|
||||
NOT_A_PERMISSION: 'Invalid permission string or number.',
|
||||
INVALID_RATE_LIMIT_METHOD: 'Unknown rate limiting method.',
|
||||
BAD_LOGIN: 'Incorrect login details were provided.',
|
||||
INVALID_SHARD: 'Invalid shard settings were provided.',
|
||||
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.',
|
||||
INVALID_TOKEN: 'An invalid token was provided.',
|
||||
};
|
||||
|
||||
const AllowedImageFormats = [
|
||||
'webp',
|
||||
'png',
|
||||
@@ -108,8 +95,8 @@ const AllowedImageSizes = [
|
||||
];
|
||||
|
||||
function checkImage({ size, format }) {
|
||||
if (format && !AllowedImageFormats.includes(format)) throw new Error(`Invalid image format: ${format}`);
|
||||
if (size && !AllowedImageSizes.includes(size)) throw new RangeError(`Invalid size: ${size}`);
|
||||
if (format && !AllowedImageFormats.includes(format)) throw new Error('IMAGE_FORMAT', format);
|
||||
if (size && !AllowedImageSizes.includes(size)) throw new RangeError('IMAGE_SIZE', size);
|
||||
}
|
||||
|
||||
exports.Endpoints = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const Constants = require('../util/Constants');
|
||||
const { RangeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* Data structure that makes it easy to interact with a permission bitfield. All {@link GuildMember}s have a set of
|
||||
@@ -95,7 +95,7 @@ class Permissions {
|
||||
static resolve(permission) {
|
||||
if (permission instanceof Array) return permission.map(p => this.resolve(p)).reduce((prev, p) => prev | p, 0);
|
||||
if (typeof permission === 'string') permission = this.FLAGS[permission];
|
||||
if (typeof permission !== 'number' || permission < 1) throw new RangeError(Constants.Errors.NOT_A_PERMISSION);
|
||||
if (typeof permission !== 'number' || permission < 1) throw new RangeError('PERMISSION_INVALID');
|
||||
return permission;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const snekfetch = require('snekfetch');
|
||||
const Constants = require('./Constants');
|
||||
const ConstantsHttp = Constants.DefaultOptions.http;
|
||||
const { RangeError, TypeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* Contains various general-purpose utility methods. These functions are also available on the base `Discord` object.
|
||||
@@ -19,7 +20,9 @@ class Util {
|
||||
static splitMessage(text, { maxLength = 1950, char = '\n', prepend = '', append = '' } = {}) {
|
||||
if (text.length <= maxLength) return text;
|
||||
const splitText = text.split(char);
|
||||
if (splitText.length === 1) throw new Error('Message exceeds the max length and contains no split characters.');
|
||||
if (splitText.length === 1) {
|
||||
throw new RangeError('SPLIT_MAX_LEN');
|
||||
}
|
||||
const messages = [''];
|
||||
let msg = 0;
|
||||
for (let i = 0; i < splitText.length; i++) {
|
||||
@@ -54,7 +57,7 @@ class Util {
|
||||
*/
|
||||
static fetchRecommendedShards(token, guildsPerShard = 1000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!token) throw new Error('A token must be provided.');
|
||||
if (!token) throw new Error('TOKEN_MISSING');
|
||||
snekfetch.get(`${ConstantsHttp.host}/api/v${ConstantsHttp.version}${Constants.Endpoints.botGateway}`)
|
||||
.set('Authorization', `Bot ${token.replace(/^Bot\s*/i, '')}`)
|
||||
.end((err, res) => {
|
||||
@@ -279,9 +282,9 @@ class Util {
|
||||
}
|
||||
|
||||
if (color < 0 || color > 0xFFFFFF) {
|
||||
throw new RangeError('Color must be within the range 0 - 16777215 (0xFFFFFF).');
|
||||
throw new RangeError('COLOR_RANGE');
|
||||
} else if (color && isNaN(color)) {
|
||||
throw new TypeError('Unable to convert color to a number.');
|
||||
throw new TypeError('COLOR_CONVERT');
|
||||
}
|
||||
|
||||
return color;
|
||||
|
||||
Reference in New Issue
Block a user