From 18e3801bb71b67db6bbfa84b8a11f9e877789dd8 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Mon, 4 Sep 2017 10:49:44 -0500 Subject: [PATCH] lots of important stuff (#1883) * lots of important stuff * Update Constants.js --- src/client/BaseClient.js | 126 ++++++++++++++++++ src/client/Client.js | 96 +------------ src/client/WebhookClient.js | 114 +--------------- src/index.js | 3 +- src/{client => }/rest/APIRequest.js | 16 +-- src/{client => }/rest/APIRouter.js | 1 + src/{client => }/rest/DiscordAPIError.js | 0 src/{client => }/rest/RESTManager.js | 20 ++- src/{client => }/rest/UserAgentManager.js | 2 +- .../rest/handlers/RequestHandler.js | 0 src/{client => }/rest/handlers/burst.js | 0 src/{client => }/rest/handlers/index.js | 0 src/{client => }/rest/handlers/sequential.js | 0 src/structures/TextChannel.js | 3 +- src/util/Constants.js | 84 ++++++------ 15 files changed, 212 insertions(+), 253 deletions(-) create mode 100644 src/client/BaseClient.js rename src/{client => }/rest/APIRequest.js (76%) rename src/{client => }/rest/APIRouter.js (96%) rename src/{client => }/rest/DiscordAPIError.js (100%) rename src/{client => }/rest/RESTManager.js (71%) rename src/{client => }/rest/UserAgentManager.js (91%) rename src/{client => }/rest/handlers/RequestHandler.js (100%) rename src/{client => }/rest/handlers/burst.js (100%) rename src/{client => }/rest/handlers/index.js (100%) rename src/{client => }/rest/handlers/sequential.js (100%) diff --git a/src/client/BaseClient.js b/src/client/BaseClient.js new file mode 100644 index 000000000..76b57b5cb --- /dev/null +++ b/src/client/BaseClient.js @@ -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} + * @private + */ + this._timeouts = new Set(); + + /** + * Intervals set by {@link WebhookClient#setInterval} that are still active + * @type {Set} + * @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; diff --git a/src/client/Client.js b/src/client/Client.js index 7bdc96f6c..1591eb0bf 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -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 diff --git a/src/client/WebhookClient.js b/src/client/WebhookClient.js index 98a1dc938..edb1a0c13 100644 --- a/src/client/WebhookClient.js +++ b/src/client/WebhookClient.js @@ -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} - * @private - */ - this._timeouts = new Set(); - - /** - * Intervals set by {@link WebhookClient#setInterval} that are still active - * @type {Set} - * @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; diff --git a/src/index.js b/src/index.js index 9c192e4f7..ed966e000 100644 --- a/src/index.js +++ b/src/index.js @@ -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'), diff --git a/src/client/rest/APIRequest.js b/src/rest/APIRequest.js similarity index 76% rename from src/client/rest/APIRequest.js rename to src/rest/APIRequest.js index b8914a6d2..f39ab1cbf 100644 --- a/src/client/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -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); diff --git a/src/client/rest/APIRouter.js b/src/rest/APIRouter.js similarity index 96% rename from src/client/rest/APIRouter.js rename to src/rest/APIRouter.js index 88103305d..14660d50e 100644 --- a/src/client/rest/APIRouter.js +++ b/src/rest/APIRouter.js @@ -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; diff --git a/src/client/rest/DiscordAPIError.js b/src/rest/DiscordAPIError.js similarity index 100% rename from src/client/rest/DiscordAPIError.js rename to src/rest/DiscordAPIError.js diff --git a/src/client/rest/RESTManager.js b/src/rest/RESTManager.js similarity index 71% rename from src/client/rest/RESTManager.js rename to src/rest/RESTManager.js index 7ee74a607..7d8f16019 100644 --- a/src/client/rest/RESTManager.js +++ b/src/rest/RESTManager.js @@ -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; diff --git a/src/client/rest/UserAgentManager.js b/src/rest/UserAgentManager.js similarity index 91% rename from src/client/rest/UserAgentManager.js rename to src/rest/UserAgentManager.js index 13e5528e0..a8666c6af 100644 --- a/src/client/rest/UserAgentManager.js +++ b/src/rest/UserAgentManager.js @@ -1,4 +1,4 @@ -const Constants = require('../../util/Constants'); +const Constants = require('../util/Constants'); class UserAgentManager { constructor() { diff --git a/src/client/rest/handlers/RequestHandler.js b/src/rest/handlers/RequestHandler.js similarity index 100% rename from src/client/rest/handlers/RequestHandler.js rename to src/rest/handlers/RequestHandler.js diff --git a/src/client/rest/handlers/burst.js b/src/rest/handlers/burst.js similarity index 100% rename from src/client/rest/handlers/burst.js rename to src/rest/handlers/burst.js diff --git a/src/client/rest/handlers/index.js b/src/rest/handlers/index.js similarity index 100% rename from src/client/rest/handlers/index.js rename to src/rest/handlers/index.js diff --git a/src/client/rest/handlers/sequential.js b/src/rest/handlers/sequential.js similarity index 100% rename from src/client/rest/handlers/sequential.js rename to src/rest/handlers/sequential.js diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 45796be19..071a72c21 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -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); } /** diff --git a/src/util/Constants.js b/src/util/Constants.js index 72cf435f0..447b95875 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -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; +}