lots of important stuff (#1883)

* lots of important stuff

* Update Constants.js
This commit is contained in:
Gus Caplan
2017-09-04 10:49:44 -05:00
committed by Crawl
parent 3a503ef56e
commit 18e3801bb7
15 changed files with 212 additions and 253 deletions

126
src/client/BaseClient.js Normal file
View File

@@ -0,0 +1,126 @@
const EventEmitter = require('events');
const RESTManager = require('../rest/RESTManager');
const ClientDataResolver = require('./ClientDataResolver');
const Util = require('../util/Util');
const Constants = require('../util/Constants');
/**
* The base class for all clients.
* @extends {EventEmitter}
*/
class BaseClient extends EventEmitter {
constructor(options = {}) {
super();
/**
* The options the client was instantiated with
* @type {ClientOptions}
*/
this.options = Util.mergeDefault(Constants.DefaultOptions, options);
/**
* The REST manager of the client
* @type {RESTManager}
* @private
*/
this.rest = new RESTManager(this, options._tokenType);
/**
* The data resolver of the client
* @type {ClientDataResolver}
* @private
*/
this.resolver = new ClientDataResolver(this);
/**
* Timeouts set by {@link WebhookClient#setTimeout} that are still active
* @type {Set<Timeout>}
* @private
*/
this._timeouts = new Set();
/**
* Intervals set by {@link WebhookClient#setInterval} that are still active
* @type {Set<Timeout>}
* @private
*/
this._intervals = new Set();
}
/**
* Whether the client is in a browser environment
* @type {boolean}
* @readonly
*/
get browser() {
return typeof window !== 'undefined';
}
/**
* API shortcut
* @type {Object}
* @private
*/
get api() {
return this.rest.api;
}
/**
* Destroys all assets used by the base client.
*/
destroy() {
for (const t of this._timeouts) clearTimeout(t);
for (const i of this._intervals) clearInterval(i);
this._timeouts.clear();
this._intervals.clear();
}
/**
* Sets a timeout that will be automatically cancelled if the client is destroyed.
* @param {Function} fn Function to execute
* @param {number} delay Time to wait before executing (in milliseconds)
* @param {...*} args Arguments for the function
* @returns {Timeout}
*/
setTimeout(fn, delay, ...args) {
const timeout = setTimeout(() => {
fn(...args);
this._timeouts.delete(timeout);
}, delay);
this._timeouts.add(timeout);
return timeout;
}
/**
* Clears a timeout.
* @param {Timeout} timeout Timeout to cancel
*/
clearTimeout(timeout) {
clearTimeout(timeout);
this._timeouts.delete(timeout);
}
/**
* Sets an interval that will be automatically cancelled if the client is destroyed.
* @param {Function} fn Function to execute
* @param {number} delay Time to wait before executing (in milliseconds)
* @param {...*} args Arguments for the function
* @returns {Timeout}
*/
setInterval(fn, delay, ...args) {
const interval = setInterval(fn, delay, ...args);
this._intervals.add(interval);
return interval;
}
/**
* Clears an interval.
* @param {Timeout} interval Interval to cancel
*/
clearInterval(interval) {
clearInterval(interval);
this._intervals.delete(interval);
}
}
module.exports = BaseClient;

View File

@@ -1,10 +1,7 @@
const EventEmitter = require('events');
const Constants = require('../util/Constants');
const BaseClient = require('./BaseClient');
const Permissions = require('../util/Permissions');
const Util = require('../util/Util');
const RESTManager = require('./rest/RESTManager');
const RESTManager = require('../rest/RESTManager');
const ClientManager = require('./ClientManager');
const ClientDataResolver = require('./ClientDataResolver');
const ClientVoiceManager = require('./voice/ClientVoiceManager');
const WebSocketManager = require('./websocket/WebSocketManager');
const ActionsManager = require('./actions/ActionsManager');
@@ -19,28 +16,24 @@ const UserStore = require('../stores/UserStore');
const ChannelStore = require('../stores/ChannelStore');
const GuildStore = require('../stores/GuildStore');
const ClientPresenceStore = require('../stores/ClientPresenceStore');
const Constants = require('../util/Constants');
const { Error, TypeError, RangeError } = require('../errors');
/**
* The main hub for interacting with the Discord API, and the starting point for any bot.
* @extends {EventEmitter}
* @extends {BaseClient}
*/
class Client extends EventEmitter {
class Client extends BaseClient {
/**
* @param {ClientOptions} [options] Options for the client
*/
constructor(options = {}) {
super();
super(Object.assign({ _tokenType: 'Bot' }, options));
// Obtain shard details from environment
if (!options.shardId && 'SHARD_ID' in process.env) options.shardId = Number(process.env.SHARD_ID);
if (!options.shardCount && 'SHARD_COUNT' in process.env) options.shardCount = Number(process.env.SHARD_COUNT);
/**
* The options the client was instantiated with
* @type {ClientOptions}
*/
this.options = Util.mergeDefault(Constants.DefaultOptions, options);
this._validateOptions();
/**
@@ -64,13 +57,6 @@ class Client extends EventEmitter {
*/
this.ws = new WebSocketManager(this);
/**
* The data resolver of the client
* @type {ClientDataResolver}
* @private
*/
this.resolver = new ClientDataResolver(this);
/**
* The action manager of the client
* @type {ActionsManager}
@@ -184,15 +170,6 @@ class Client extends EventEmitter {
return this.ws.connection ? this.ws.connection.lastPingTimestamp : 0;
}
/**
* API shortcut
* @type {Object}
* @private
*/
get api() {
return this.rest.api;
}
/**
* Current status of the client's connection to Discord
* @type {?Status}
@@ -252,15 +229,6 @@ class Client extends EventEmitter {
return this.readyAt ? this.readyAt.getTime() : null;
}
/**
* Whether the client is in a browser environment
* @type {boolean}
* @readonly
*/
get browser() {
return typeof window !== 'undefined';
}
/**
* Creates a voice broadcast.
* @returns {VoiceBroadcast}
@@ -298,10 +266,7 @@ class Client extends EventEmitter {
* @returns {Promise}
*/
destroy() {
for (const t of this._timeouts) clearTimeout(t);
for (const i of this._intervals) clearInterval(i);
this._timeouts.clear();
this._intervals.clear();
super.destroy();
return this.manager.destroy();
}
@@ -423,53 +388,6 @@ class Client extends EventEmitter {
);
}
/**
* Sets a timeout that will be automatically cancelled if the client is destroyed.
* @param {Function} fn Function to execute
* @param {number} delay Time to wait before executing (in milliseconds)
* @param {...*} args Arguments for the function
* @returns {Timeout}
*/
setTimeout(fn, delay, ...args) {
const timeout = setTimeout(() => {
fn(...args);
this._timeouts.delete(timeout);
}, delay);
this._timeouts.add(timeout);
return timeout;
}
/**
* Clears a timeout.
* @param {Timeout} timeout Timeout to cancel
*/
clearTimeout(timeout) {
clearTimeout(timeout);
this._timeouts.delete(timeout);
}
/**
* Sets an interval that will be automatically cancelled if the client is destroyed.
* @param {Function} fn Function to execute
* @param {number} delay Time to wait before executing (in milliseconds)
* @param {...*} args Arguments for the function
* @returns {Timeout}
*/
setInterval(fn, delay, ...args) {
const interval = setInterval(fn, delay, ...args);
this._intervals.add(interval);
return interval;
}
/**
* Clears an interval.
* @param {Timeout} interval Interval to cancel
*/
clearInterval(interval) {
clearInterval(interval);
this._intervals.delete(interval);
}
/**
* Adds a ping to {@link Client#pings}.
* @param {number} startTime Starting time of the ping

View File

@@ -1,14 +1,12 @@
const Webhook = require('../structures/Webhook');
const RESTManager = require('./rest/RESTManager');
const ClientDataResolver = require('./ClientDataResolver');
const Constants = require('../util/Constants');
const Util = require('../util/Util');
const BaseClient = require('./BaseClient');
/**
* The webhook client.
* @extends {Webhook}
* @extends {BaseClient}
*/
class WebhookClient extends Webhook {
class WebhookClient extends BaseClient {
/**
* @param {Snowflake} id ID of the webhook
* @param {string} token Token of the webhook
@@ -19,109 +17,11 @@ class WebhookClient extends Webhook {
* hook.sendMessage('This will send a message').catch(console.error);
*/
constructor(id, token, options) {
super(null, id, token);
/**
* The options the client was instantiated with
* @type {ClientOptions}
*/
this.options = Util.mergeDefault(Constants.DefaultOptions, options);
/**
* The REST manager of the client
* @type {RESTManager}
* @private
*/
this.rest = new RESTManager(this);
/**
* The data resolver of the client
* @type {ClientDataResolver}
* @private
*/
this.resolver = new ClientDataResolver(this);
/**
* Timeouts set by {@link WebhookClient#setTimeout} that are still active
* @type {Set<Timeout>}
* @private
*/
this._timeouts = new Set();
/**
* Intervals set by {@link WebhookClient#setInterval} that are still active
* @type {Set<Timeout>}
* @private
*/
this._intervals = new Set();
}
/**
* API shortcut
* @type {Object}
* @private
*/
get api() {
return this.rest.api;
}
/**
* Sets a timeout that will be automatically cancelled if the client is destroyed.
* @param {Function} fn Function to execute
* @param {number} delay Time to wait before executing (in milliseconds)
* @param {...*} args Arguments for the function
* @returns {Timeout}
*/
setTimeout(fn, delay, ...args) {
const timeout = setTimeout(() => {
fn(...args);
this._timeouts.delete(timeout);
}, delay);
this._timeouts.add(timeout);
return timeout;
}
/**
* Clears a timeout.
* @param {Timeout} timeout Timeout to cancel
*/
clearTimeout(timeout) {
clearTimeout(timeout);
this._timeouts.delete(timeout);
}
/**
* Sets an interval that will be automatically cancelled if the client is destroyed.
* @param {Function} fn Function to execute
* @param {number} delay Time to wait before executing (in milliseconds)
* @param {...*} args Arguments for the function
* @returns {Timeout}
*/
setInterval(fn, delay, ...args) {
const interval = setInterval(fn, delay, ...args);
this._intervals.add(interval);
return interval;
}
/**
* Clears an interval.
* @param {Timeout} interval Interval to cancel
*/
clearInterval(interval) {
clearInterval(interval);
this._intervals.delete(interval);
}
/**
* Destroys the client.
*/
destroy() {
for (const t of this._timeouts) clearTimeout(t);
for (const i of this._intervals) clearInterval(i);
this._timeouts.clear();
this._intervals.clear();
super(options);
Webhook.call(this, null, id, token);
}
}
Object.assign(WebhookClient.prototype, Object.create(Webhook.prototype));
module.exports = WebhookClient;

View File

@@ -2,6 +2,7 @@ const Util = require('./util/Util');
module.exports = {
// "Root" classes (starting points)
BaseClient: require('./client/BaseClient'),
Client: require('./client/Client'),
Shard: require('./sharding/Shard'),
ShardClientUtil: require('./sharding/ShardClientUtil'),
@@ -11,7 +12,7 @@ module.exports = {
// Utilities
Collection: require('./util/Collection'),
Constants: require('./util/Constants'),
DiscordAPIError: require('./client/rest/DiscordAPIError'),
DiscordAPIError: require('./rest/DiscordAPIError'),
EvaluatedPermissions: require('./util/Permissions'),
Permissions: require('./util/Permissions'),
Snowflake: require('./util/Snowflake'),

View File

@@ -1,6 +1,5 @@
const querystring = require('querystring');
const snekfetch = require('snekfetch');
const { Error } = require('../../errors');
class APIRequest {
constructor(rest, method, path, options) {
@@ -12,17 +11,9 @@ class APIRequest {
this.options = options;
}
getAuth() {
if (this.client.token && this.client.user && this.client.user.bot) {
return `Bot ${this.client.token}`;
} else if (this.client.token) {
return this.client.token;
}
throw new Error('TOKEN_MISSING');
}
gen() {
const API = `${this.client.options.http.api}/v${this.client.options.http.version}`;
const API = this.options.versioned === false ? this.client.options.http.api :
`${this.client.options.http.api}/v${this.client.options.http.version}`;
if (this.options.query) {
const queryString = (querystring.stringify(this.options.query).match(/[^=&?]+=[^=&?]+/g) || []).join('&');
@@ -31,9 +22,10 @@ class APIRequest {
const request = snekfetch[this.method](`${API}${this.path}`);
if (this.options.auth !== false) request.set('Authorization', this.getAuth());
if (this.options.auth !== false) request.set('Authorization', this.rest.getAuth());
if (this.options.reason) request.set('X-Audit-Log-Reason', encodeURIComponent(this.options.reason));
if (!this.rest.client.browser) request.set('User-Agent', this.rest.userAgentManager.userAgent);
if (this.options.headers) request.set(this.options.headers);
if (this.options.files) {
for (const file of this.options.files) if (file && file.file) request.attach(file.name, file.file, file.name);

View File

@@ -14,6 +14,7 @@ function buildRoute(manager) {
if (reflectors.includes(name)) return () => route.join('/');
if (methods.includes(name)) {
return options => manager.request(name, route.join('/'), Object.assign({
versioned: manager.versioned,
route: route.map((r, i) => {
if (/\d{16,19}/g.test(r)) return /channels|guilds/.test(route[i - 1]) ? r : ':id';
return r;

View File

@@ -2,22 +2,32 @@ const UserAgentManager = require('./UserAgentManager');
const handlers = require('./handlers');
const APIRequest = require('./APIRequest');
const routeBuilder = require('./APIRouter');
const Constants = require('../../util/Constants');
const { Error } = require('../../errors');
const { Error } = require('../errors');
const Constants = require('../util/Constants');
class RESTManager {
constructor(client) {
constructor(client, tokenPrefix = 'Bot') {
this.client = client;
this.handlers = {};
this.userAgentManager = new UserAgentManager(this);
this.rateLimitedEndpoints = {};
this.globallyRateLimited = false;
this.tokenPrefix = tokenPrefix;
this.versioned = true;
}
get api() {
return routeBuilder(this);
}
getAuth() {
const token = this.client.token || this.client.accessToken;
const prefixed = !!this.client.application || (this.client.user && this.client.user.bot);
if (token && prefixed) return `${this.tokenPrefix} ${token}`;
else if (token) return token;
throw new Error('TOKEN_MISSING');
}
get cdn() {
return Constants.Endpoints.CDN(this.client.options.http.cdn);
}
@@ -54,6 +64,10 @@ class RESTManager {
return this.push(this.handlers[apiRequest.route], apiRequest);
}
set endpoint(endpoint) {
this.client.options.http.api = endpoint;
}
}
module.exports = RESTManager;

View File

@@ -1,4 +1,4 @@
const Constants = require('../../util/Constants');
const Constants = require('../util/Constants');
class UserAgentManager {
constructor() {

View File

@@ -12,7 +12,6 @@ const MessageStore = require('../stores/MessageStore');
class TextChannel extends GuildChannel {
constructor(guild, data) {
super(guild, data);
this.type = 'text';
this.messages = new MessageStore(this);
this._typing = new Map();
}
@@ -34,6 +33,8 @@ class TextChannel extends GuildChannel {
this.nsfw = Boolean(data.nsfw);
this.lastMessageID = data.last_message_id;
if (data.messages) for (const message of data.messages) this.messages.create(message);
}
/**

View File

@@ -283,45 +283,45 @@ exports.Events = {
* * RELATIONSHIP_REMOVE
* @typedef {string} WSEventType
*/
exports.WSEvents = {
READY: 'READY',
RESUMED: 'RESUMED',
GUILD_SYNC: 'GUILD_SYNC',
GUILD_CREATE: 'GUILD_CREATE',
GUILD_DELETE: 'GUILD_DELETE',
GUILD_UPDATE: 'GUILD_UPDATE',
GUILD_MEMBER_ADD: 'GUILD_MEMBER_ADD',
GUILD_MEMBER_REMOVE: 'GUILD_MEMBER_REMOVE',
GUILD_MEMBER_UPDATE: 'GUILD_MEMBER_UPDATE',
GUILD_MEMBERS_CHUNK: 'GUILD_MEMBERS_CHUNK',
GUILD_ROLE_CREATE: 'GUILD_ROLE_CREATE',
GUILD_ROLE_DELETE: 'GUILD_ROLE_DELETE',
GUILD_ROLE_UPDATE: 'GUILD_ROLE_UPDATE',
GUILD_BAN_ADD: 'GUILD_BAN_ADD',
GUILD_BAN_REMOVE: 'GUILD_BAN_REMOVE',
GUILD_EMOJIS_UPDATE: 'GUILD_EMOJIS_UPDATE',
CHANNEL_CREATE: 'CHANNEL_CREATE',
CHANNEL_DELETE: 'CHANNEL_DELETE',
CHANNEL_UPDATE: 'CHANNEL_UPDATE',
CHANNEL_PINS_UPDATE: 'CHANNEL_PINS_UPDATE',
MESSAGE_CREATE: 'MESSAGE_CREATE',
MESSAGE_DELETE: 'MESSAGE_DELETE',
MESSAGE_UPDATE: 'MESSAGE_UPDATE',
MESSAGE_DELETE_BULK: 'MESSAGE_DELETE_BULK',
MESSAGE_REACTION_ADD: 'MESSAGE_REACTION_ADD',
MESSAGE_REACTION_REMOVE: 'MESSAGE_REACTION_REMOVE',
MESSAGE_REACTION_REMOVE_ALL: 'MESSAGE_REACTION_REMOVE_ALL',
USER_UPDATE: 'USER_UPDATE',
USER_NOTE_UPDATE: 'USER_NOTE_UPDATE',
USER_SETTINGS_UPDATE: 'USER_SETTINGS_UPDATE',
USER_GUILD_SETTINGS_UPDATE: 'USER_GUILD_SETTINGS_UPDATE',
PRESENCE_UPDATE: 'PRESENCE_UPDATE',
VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE',
TYPING_START: 'TYPING_START',
VOICE_SERVER_UPDATE: 'VOICE_SERVER_UPDATE',
RELATIONSHIP_ADD: 'RELATIONSHIP_ADD',
RELATIONSHIP_REMOVE: 'RELATIONSHIP_REMOVE',
};
exports.WSEvents = keyMirror([
'READY',
'RESUMED',
'GUILD_SYNC',
'GUILD_CREATE',
'GUILD_DELETE',
'GUILD_UPDATE',
'GUILD_MEMBER_ADD',
'GUILD_MEMBER_REMOVE',
'GUILD_MEMBER_UPDATE',
'GUILD_MEMBERS_CHUNK',
'GUILD_ROLE_CREATE',
'GUILD_ROLE_DELETE',
'GUILD_ROLE_UPDATE',
'GUILD_BAN_ADD',
'GUILD_BAN_REMOVE',
'GUILD_EMOJIS_UPDATE',
'CHANNEL_CREATE',
'CHANNEL_DELETE',
'CHANNEL_UPDATE',
'CHANNEL_PINS_UPDATE',
'MESSAGE_CREATE',
'MESSAGE_DELETE',
'MESSAGE_UPDATE',
'MESSAGE_DELETE_BULK',
'MESSAGE_REACTION_ADD',
'MESSAGE_REACTION_REMOVE',
'MESSAGE_REACTION_REMOVE_ALL',
'USER_UPDATE',
'USER_NOTE_UPDATE',
'USER_SETTINGS_UPDATE',
'USER_GUILD_SETTINGS_UPDATE',
'PRESENCE_UPDATE',
'VOICE_STATE_UPDATE',
'TYPING_START',
'VOICE_SERVER_UPDATE',
'RELATIONSHIP_ADD',
'RELATIONSHIP_REMOVE',
]);
/**
* The type of a message, e.g. `DEFAULT`. Here are the available types:
@@ -699,3 +699,9 @@ exports.APIErrors = {
INVITE_ACCEPTED_TO_GUILD_NOT_CONTANING_BOT: 50036,
REACTION_BLOCKED: 90001,
};
function keyMirror(arr) {
let tmp = Object.create(null);
for (const value of arr) tmp[value] = value;
return tmp;
}