"use strict";
const EventEmitter = require('events').EventEmitter;
const Shard = require('../websocket/Shard');
const Store = require('../utils/Store');
const RestHandler = require('../rest/RestHandler');
const User = require('../models/User');
const { ENDPOINTS } = require('../utils/Constants').HTTP;
const DMChannel = require('../models/DMChannel');
const Message = require('../models/Message');
/**
* @extends EventEmitter Represents a Discord Client
* @prop {Store} channels Where channels are being cached
* @prop {Store} guilds Where guilds are being cached
* @prop {String} token The token of the client
* @prop {Store} users Where users are being cached
* @prop {Object} [options] Options for the Discord Client
* @prop {Number|String} [options.shardCount=1] The amount of shards to use
* @prop {Boolean} [options.disableEveryone=true] Whether to disable the @everyone ping
* @prop {Boolean} [options.getAllMembers=false] Whether to fetch all members on each guild regardless of being offline or not
* @prop {Boolean} [options.storeMessages=false] Whether to store messages in a cache, once the bot restarts the messages in the cache will be gone and can't be re-added automatically
*/
class Client extends EventEmitter {
constructor(options = {}) {
super(options);
this.shardCount = options.shardCount || 1;
this.firstShardSent = false;
this.getAllMembers = options.getAllMembers || false;
this.storeMessages = options.storeMessages || false;
this.logger = options.logger || false;
this.largeThreshold = options.largeThreshold || 250;
this.chalk = null;
if (this.logger) {
try {
this.chalk = require('chalk');
} catch(error) {
this.emit('error', new Error(`Module "chalk" is missing! Install it by typing "npm install chalk"`));
}
};
if (this.shards < 1 || (typeof this.shards === 'string' && this.shards !== 'auto')) this.emit('error', new Error('Invalid amount of shards! Must be more than one or use \'auto\''));
this.token = null;
this.channels = new Store();
this.guilds = new Store();
this.shards = new Store();
this.users = new Store();
this.rest = new RestHandler(this);
this.gatewayURL = null;
Object.defineProperty(this, 'connectedShards', { value: [] });
}
get uptime() {
return this.startTime ? Date.now() - this.startTime : null;
}
/**
* Creates a DMChannel between a user
* @param {Snowflake} user The id of the recipient for the DM
* @returns {Promise<DMChannel>}
*/
createDM(user) {
return this.rest.request("POST", ENDPOINTS.USER_CHANNELS('@me'), {
data: {
recipient_id: user
}
}).then(res => {
return new DMChannel(this, res.data);
});
}
/**
* Creates a guild
* @param {String} name The name for the new guild
* @param {Object} [options] Options for the new guild
* @param {String} [options.region] The region for the guild
* @param {String} [options.icon] A base64 128x128 jpeg image for the guild icon
* @returns {Promise<Guild>}
*/
createGuild(name, options = {}) {
return this.rest.request("POST", ENDPOINTS.GUILDS, {
data: {
name,
region: options.region,
icon: options.icon
}
}).then(res => {
return this.guilds.get(res.data.id);
});
}
/**
* Fetches the user from cache, if it doesn't exist use the REST API to fetch it and add to the cache
* @param {Snowflake} user The id of the user to fetch
* @returns {Promise<User>}
*/
getUser(user) {
if (!this.users.has(user)) {
return this.rest.request("GET", ENDPOINTS.USER(user))
.then(res => {
return this.users.set(res.data.id, new User(this, res.data));
});
} else {
return new Promise((resolve, reject) => {
return resolve(this.users.get(user));
});
};
return this.rest.request("GET", ENDPOINTS.USER(user))
.then(res => {
return this.users.set(res.data.id, new User(this, res.data));
});
}
/**
* Makes the bot leave the guild
* @param {Snowflake} guild The id of the guild
* @returns {Promise<Boolean>} Will return true if it's a success
*/
leaveGuild(guild) {
return this.rest.request("DELETE", ENDPOINTS.GUILD(guild))
.then(() => {
return true;
});
}
/**
* Creates an embed to a channel
* @param {Snowflake} channel The id of the channel to send a message to
* @param {Object} embed The embed to send
* @returns {Promise<Message>}
*/
createEmbed(channel, embed) {
return this.rest.request("POST", ENDPOINTS.CHANNEL_MESSAGES(channel), {
data: {
embed: embed.hasOwnProperty('embed') ? embed.embed : embed
}
}).then(res => {
return new Message(this, res.data);
});
}
/**
* Creates a message to a channel
* @param {Snowflake} channel The id of the channel to send a message to
* @param {String} content The content to send
* @returns {Promise<Message>}
*/
createMessage(channel, content) {
return this.rest.request("POST", ENDPOINTS.CHANNEL_MESSAGES(channel), {
data: {
content
}
}).then(res => {
return new Message(this, res.data);
});
}
/**
* This will start connecting to the gateway using the given bot token
* @deprecated
* @param {String} token The token of the user
* @returns {Void}
*/
start(token) {
console.warn(`Client#start() has been deprecated! Please use Client#initiate() instead.`);
return this.initiate(token);
}
/**
* This will start connecting to the gateway using the given bot token
* @param {String} token The token of the user
* @returns {Void}
*/
async initiate(token) {
this.token = token;
let data = await this.rest.request("GET", '/gateway/bot');
this.gatewayURL = data.data.url;
if (this.shardCount === 'auto') {
let res = await this.rest.request("GET", '/gateway/bot');
this.shardCount = res.data.shards;
};
for (let i = 0; i < this.shardCount; i++) {
setTimeout(() => {
new Shard(this, i).connect();
}, i * 6500);
};
}
/**
* Logs something to the console that is colored.
* @param {String} type The type of the log
* @param {String} message The message to log
* @returns {Object}
*/
log(type, message) {
if (!this.logger) return this.emit('error', new Error('You need to install the "chalk" package!'));
let types = ['success', 'warn', 'error', 'loading', 'retrying'];
if (!type || type && !types.includes(type.toLowerCase())) this.emit('Invalid Type of log!');
switch(type) {
case 'error':
console.log(`[${this.chalk.red('ERROR')}]: ${message}`);
break;
case 'loading':
console.log(`[${this.chalk.cyan('LOADING')}]: ${message}`);
break;
case 'success':
console.log(`[${this.chalk.green('SUCCESS')}]: ${message}`);
break;
case 'warn':
console.log(`[${this.chalk.yellow('WARNING')}]: ${message}`);
break;
case 'retrying':
console.log(`[${this.chalk.blue('RETRYING')}]: ${message}`);
break;
}
return { type, message };
}
/**
* Make your own log with your own type and color
* @param {String} type The type of the log, can be anything
* @param {String} color The color of the type
* @param {String} message The message to log
* @returns {Object}
*/
customLog(type, color, message) {
if (!this.logger) return this.emit('error', new Error('You need to install the "chalk" package!'));
if (!type) type = 'info';
if (!color) color = 'white';
console.log(`[${this.chalk[color.toLowerCase()](type)}]: ${message}`);
return { type, color, message };
}
/**
* Make your own log with your own type and color using RGB
* @param {String} type The type of the log, can be anything
* @param {String} message The message to log
* @param {Object} [options] The options for the RGB Color
* @param {Number} [options.red=0] The number for the color red in the rgb data
* @param {Number} [options.green=0] The number for the color green in the rgb data
* @param {Number} [options.blue=0] The number for the color blue in the rgb data
* @returns {Object}
*/
rgbLog(type, message, options = { red: 0, green: 0, blue: 0 }) {
if (!this.logger) return this.emit('error', new Error('You need to install the "chalk" package!'));
console.log(`[${this.chalk.rgb(options.red, options.green, options.blue)(type)}]: ${message}`);
return options;
}
};
module.exports = Client;