From 4adecf4aef004be3074e127326a7f06b07cbca9f Mon Sep 17 00:00:00 2001 From: Crawl Date: Thu, 27 Jul 2017 03:04:03 +0200 Subject: [PATCH] rewrite ratelimiting and api route builder (#1667) * rewrite ratelimiting and api route builder * more stuff * let people pass their own handlers * Update burst.js * Update RequestHandler.js * Update burst.js * Update sequential.js * Update RequestHandler.js --- src/client/Client.js | 8 +- src/client/rest/APIRequest.js | 12 +-- src/client/rest/APIRouter.js | 40 ++++---- src/client/rest/RESTManager.js | 23 ++--- src/client/rest/RequestHandlers/Burst.js | 65 ------------ .../rest/RequestHandlers/RequestHandler.js | 54 ---------- src/client/rest/RequestHandlers/Sequential.js | 98 ------------------- src/client/rest/handlers/RequestHandler.js | 67 +++++++++++++ src/client/rest/handlers/burst.js | 13 +++ src/client/rest/handlers/index.js | 5 + src/client/rest/handlers/sequential.js | 16 +++ src/structures/Channel.js | 2 +- src/structures/ClientUser.js | 6 +- src/structures/Emoji.js | 2 +- src/structures/Guild.js | 40 ++++---- src/structures/GuildChannel.js | 8 +- src/structures/GuildMember.js | 14 +-- src/structures/Message.js | 12 +-- src/structures/User.js | 8 +- src/structures/Webhook.js | 10 +- 20 files changed, 187 insertions(+), 316 deletions(-) delete mode 100644 src/client/rest/RequestHandlers/Burst.js delete mode 100644 src/client/rest/RequestHandlers/RequestHandler.js delete mode 100644 src/client/rest/RequestHandlers/Sequential.js create mode 100644 src/client/rest/handlers/RequestHandler.js create mode 100644 src/client/rest/handlers/burst.js create mode 100644 src/client/rest/handlers/index.js create mode 100644 src/client/rest/handlers/sequential.js diff --git a/src/client/Client.js b/src/client/Client.js index df12aadfb..69986e071 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -332,7 +332,7 @@ class Client extends EventEmitter { */ fetchUser(id, cache = true) { if (this.users.has(id)) return Promise.resolve(this.users.get(id)); - return this.api.users[id].get().then(data => + return this.api.users(id).get().then(data => cache ? this.dataManager.newUser(data) : new User(this, data) ); } @@ -344,7 +344,7 @@ class Client extends EventEmitter { */ fetchInvite(invite) { const code = this.resolver.resolveInviteCode(invite); - return this.api.invites[code].get({ query: { with_counts: true } }) + return this.api.invites(code).get({ query: { with_counts: true } }) .then(data => new Invite(this, data)); } @@ -355,7 +355,7 @@ class Client extends EventEmitter { * @returns {Promise} */ fetchWebhook(id, token) { - return this.api.webhooks.opts(id, token).get().then(data => new Webhook(this, data)); + return this.api.webhooks(id, token).get().then(data => new Webhook(this, data)); } /** @@ -414,7 +414,7 @@ class Client extends EventEmitter { * @returns {Promise} */ fetchApplication(id = '@me') { - return this.api.oauth2.applications[id].get() + return this.api.oauth2.applications(id).get() .then(app => new OAuth2Application(this, app)); } diff --git a/src/client/rest/APIRequest.js b/src/client/rest/APIRequest.js index 9382c9ae2..b8914a6d2 100644 --- a/src/client/rest/APIRequest.js +++ b/src/client/rest/APIRequest.js @@ -8,20 +8,10 @@ class APIRequest { this.client = rest.client; this.method = method; this.path = path.toString(); - this.route = this.getRoute(this.path); + this.route = options.route; this.options = options; } - getRoute(url) { - let route = url.split('?')[0]; - if (route.includes('/channels/') || route.includes('/guilds/')) { - const startInd = route.includes('/channels/') ? route.indexOf('/channels/') : route.indexOf('/guilds/'); - const majorID = route.substring(startInd).split('/')[2]; - route = route.replace(/(\d{8,})/g, ':id').replace(':id', majorID); - } - return route; - } - getAuth() { if (this.client.token && this.client.user && this.client.user.bot) { return `Bot ${this.client.token}`; diff --git a/src/client/rest/APIRouter.js b/src/client/rest/APIRouter.js index e9771e902..88103305d 100644 --- a/src/client/rest/APIRouter.js +++ b/src/client/rest/APIRouter.js @@ -1,32 +1,34 @@ const util = require('util'); +const noop = () => {}; // eslint-disable-line no-empty-function const methods = ['get', 'post', 'delete', 'patch', 'put']; const reflectors = [ 'toString', 'valueOf', 'inspect', 'constructor', Symbol.toPrimitive, util.inspect.custom, ]; -module.exports = restManager => { +function buildRoute(manager) { + const route = ['']; const handler = { - get(list, name) { - if (name === 'opts') { - function toReturn(...args) { // eslint-disable-line no-inner-declarations - list.push(...args.filter(x => x !== null && typeof x !== 'undefined')); - return new Proxy(list, handler); - } - const directJoin = () => `${list.join('/')}/${name}`; - for (const r of reflectors) toReturn[r] = directJoin; - for (const method of methods) { - toReturn[method] = options => restManager.request(method, directJoin(), options); - } - return toReturn; + get(target, name) { + if (reflectors.includes(name)) return () => route.join('/'); + if (methods.includes(name)) { + return options => manager.request(name, route.join('/'), Object.assign({ + route: route.map((r, i) => { + if (/\d{16,19}/g.test(r)) return /channels|guilds/.test(route[i - 1]) ? r : ':id'; + return r; + }).join('/'), + }, options)); } - if (reflectors.includes(name)) return () => list.join('/'); - if (methods.includes(name)) return options => restManager.request(name, list.join('/'), options); - list.push(name); - return new Proxy(list, handler); + route.push(name); + return new Proxy(noop, handler); + }, + apply(target, _, args) { + route.push(...args.filter(x => x != null)); // eslint-disable-line eqeqeq + return new Proxy(noop, handler); }, }; + return new Proxy(noop, handler); +} - return new Proxy([''], handler); -}; +module.exports = buildRoute; diff --git a/src/client/rest/RESTManager.js b/src/client/rest/RESTManager.js index 70c53629f..530623245 100644 --- a/src/client/rest/RESTManager.js +++ b/src/client/rest/RESTManager.js @@ -1,8 +1,7 @@ const UserAgentManager = require('./UserAgentManager'); -const SequentialRequestHandler = require('./RequestHandlers/Sequential'); -const BurstRequestHandler = require('./RequestHandlers/Burst'); +const handlers = require('./handlers'); const APIRequest = require('./APIRequest'); -const mountApi = require('./APIRouter'); +const routeBuilder = require('./APIRouter'); const { Error } = require('../../errors'); class RESTManager { @@ -15,7 +14,7 @@ class RESTManager { } get api() { - return mountApi(this); + return routeBuilder(this); } destroy() { @@ -35,21 +34,17 @@ class RESTManager { } getRequestHandler() { - switch (this.client.options.apiRequestMethod) { - case 'sequential': - return SequentialRequestHandler; - case 'burst': - return BurstRequestHandler; - default: - throw new Error('RATELIMIT_INVALID_METHOD'); - } + const method = this.client.options.apiRequestMethod; + if (typeof method === 'function') return method; + const handler = handlers[method]; + if (!handler) throw new Error('RATELIMIT_INVALID_METHOD'); + return handler; } request(method, url, options = {}) { const apiRequest = new APIRequest(this, method, url, options); if (!this.handlers[apiRequest.route]) { - const RequestHandlerType = this.getRequestHandler(); - this.handlers[apiRequest.route] = new RequestHandlerType(this, apiRequest.route); + this.handlers[apiRequest.route] = new handlers.RequestHandler(this, this.getRequestHandler()); } return this.push(this.handlers[apiRequest.route], apiRequest); diff --git a/src/client/rest/RequestHandlers/Burst.js b/src/client/rest/RequestHandlers/Burst.js deleted file mode 100644 index f5968857f..000000000 --- a/src/client/rest/RequestHandlers/Burst.js +++ /dev/null @@ -1,65 +0,0 @@ -const RequestHandler = require('./RequestHandler'); -const DiscordAPIError = require('../DiscordAPIError'); - -class BurstRequestHandler extends RequestHandler { - constructor(restManager, endpoint) { - super(restManager, endpoint); - - this.client = restManager.client; - - this.limit = Infinity; - this.resetTime = null; - this.remaining = 1; - this.timeDifference = 0; - - this.resetTimeout = null; - } - - push(request) { - super.push(request); - this.handle(); - } - - execute(item) { - if (!item) return; - item.request.gen().end((err, res) => { - if (res && res.headers) { - this.limit = Number(res.headers['x-ratelimit-limit']); - this.resetTime = Number(res.headers['x-ratelimit-reset']) * 1000; - this.remaining = Number(res.headers['x-ratelimit-remaining']); - this.timeDifference = Date.now() - new Date(res.headers.date).getTime(); - } - if (err) { - if (err.status === 429) { - this.queue.unshift(item); - if (res.headers['x-ratelimit-global']) this.globalLimit = true; - if (this.resetTimeout) return; - this.resetTimeout = this.client.setTimeout(() => { - this.remaining = this.limit; - this.globalLimit = false; - this.handle(); - this.resetTimeout = null; - }, Number(res.headers['retry-after']) + this.client.options.restTimeOffset); - } else { - item.reject(err.status === 400 ? new DiscordAPIError(res.request.path, res.body) : err); - this.handle(); - } - } else { - this.globalLimit = false; - const data = res && res.body ? res.body : {}; - item.resolve(data); - this.handle(); - } - }); - } - - handle() { - super.handle(); - if (this.remaining <= 0 || this.queue.length === 0 || this.globalLimit) return; - this.execute(this.queue.shift()); - this.remaining--; - this.handle(); - } -} - -module.exports = BurstRequestHandler; diff --git a/src/client/rest/RequestHandlers/RequestHandler.js b/src/client/rest/RequestHandlers/RequestHandler.js deleted file mode 100644 index c5bad20ab..000000000 --- a/src/client/rest/RequestHandlers/RequestHandler.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * A base class for different types of rate limiting handlers for the REST API. - * @private - */ -class RequestHandler { - /** - * @param {RESTManager} restManager The REST manager to use - */ - constructor(restManager) { - /** - * The RESTManager that instantiated this RequestHandler - * @type {RESTManager} - */ - this.restManager = restManager; - - /** - * A list of requests that have yet to be processed - * @type {APIRequest[]} - */ - this.queue = []; - } - - /** - * Whether or not the client is being rate limited on every endpoint - * @type {boolean} - * @readonly - */ - get globalLimit() { - return this.restManager.globallyRateLimited; - } - - set globalLimit(value) { - this.restManager.globallyRateLimited = value; - } - - /** - * Push a new API request into this bucket. - * @param {APIRequest} request The new request to push into the queue - */ - push(request) { - this.queue.push(request); - } - - /** - * Attempts to get this RequestHandler to process its current queue. - */ - handle() {} // eslint-disable-line no-empty-function - - destroy() { - this.queue = []; - } -} - -module.exports = RequestHandler; diff --git a/src/client/rest/RequestHandlers/Sequential.js b/src/client/rest/RequestHandlers/Sequential.js deleted file mode 100644 index 107d7a670..000000000 --- a/src/client/rest/RequestHandlers/Sequential.js +++ /dev/null @@ -1,98 +0,0 @@ -const RequestHandler = require('./RequestHandler'); -const DiscordAPIError = require('../DiscordAPIError'); - -/** - * Handles API Requests sequentially, i.e. we wait until the current request is finished before moving onto - * the next. This plays a _lot_ nicer in terms of avoiding 429's when there is more than one session of the account, - * but it can be slower. - * @extends {RequestHandler} - * @private - */ -class SequentialRequestHandler extends RequestHandler { - /** - * @param {RESTManager} restManager The REST manager to use - * @param {string} endpoint The endpoint to handle - */ - constructor(restManager, endpoint) { - super(restManager, endpoint); - - /** - * The endpoint that this handler is handling - * @type {string} - */ - this.endpoint = endpoint; - - /** - * The time difference between Discord's Dates and the local computer's Dates. A positive number means the local - * computer's time is ahead of Discord's - * @type {number} - */ - this.timeDifference = 0; - - /** - * Whether the queue is being processed or not - * @type {boolean} - */ - this.busy = false; - } - - push(request) { - super.push(request); - this.handle(); - } - - /** - * Performs a request then resolves a promise to indicate its readiness for a new request. - * @param {APIRequest} item The item to execute - * @returns {Promise} - */ - execute(item) { - this.busy = true; - return new Promise(resolve => { - item.request.gen().end((err, res) => { - if (res && res.headers) { - this.requestLimit = Number(res.headers['x-ratelimit-limit']); - this.requestResetTime = Number(res.headers['x-ratelimit-reset']) * 1000; - this.requestRemaining = Number(res.headers['x-ratelimit-remaining']); - this.timeDifference = Date.now() - new Date(res.headers.date).getTime(); - } - if (err) { - if (err.status === 429) { - this.queue.unshift(item); - this.restManager.client.setTimeout(() => { - this.globalLimit = false; - resolve(); - }, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset); - if (res.headers['x-ratelimit-global']) this.globalLimit = true; - } else { - item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.request.path, res.body) : err); - resolve(err); - } - } else { - this.globalLimit = false; - const data = res && res.body ? res.body : {}; - item.resolve(data); - if (this.requestRemaining === 0) { - this.restManager.client.setTimeout( - () => resolve(data), - this.requestResetTime - Date.now() + this.timeDifference + this.restManager.client.options.restTimeOffset - ); - } else { - resolve(data); - } - } - }); - }); - } - - handle() { - super.handle(); - if (this.busy || this.remaining === 0 || this.queue.length === 0 || this.globalLimit) return; - this.execute(this.queue.shift()).then(() => { - this.busy = false; - this.handle(); - }); - } -} - -module.exports = SequentialRequestHandler; diff --git a/src/client/rest/handlers/RequestHandler.js b/src/client/rest/handlers/RequestHandler.js new file mode 100644 index 000000000..e5dfcebe9 --- /dev/null +++ b/src/client/rest/handlers/RequestHandler.js @@ -0,0 +1,67 @@ +const DiscordAPIError = require('../DiscordAPIError'); + +class RequestHandler { + constructor(manager, handler) { + this.manager = manager; + this.client = this.manager.client; + this.handle = handler.bind(this); + this.limit = Infinity; + this.resetTime = null; + this.remaining = 1; + this.timeDifference = 0; + + this.queue = []; + } + + get limited() { + return this.queue.length === 0 || this.manager.globallyRateLimited || this.remaining <= 0; + } + + set globallyLimited(limited) { + this.manager.globallyRateLimited = limited; + } + + push(request) { + this.queue.push(request); + this.handle(); + } + + execute(item) { + return new Promise((resolve, reject) => { + const finish = timeout => { + // eslint-disable-next-line prefer-promise-reject-errors + if (timeout || this.limited) reject({ timeout, limited: this.limited }); + else resolve(); + }; + item.request.gen().end((err, res) => { + if (res && res.headers) { + if (res.headers['x-ratelimit-global']) this.globallyLimited = true; + this.limit = Number(res.headers['x-ratelimit-limit']); + this.resetTime = Number(res.headers['x-ratelimit-reset']) * 1000; + this.remaining = Number(res.headers['x-ratelimit-remaining']); + this.timeDifference = Date.now() - new Date(res.headers.date).getTime(); + } + if (err) { + if (err.status === 429) { + this.queue.unshift(item); + finish(Number(res.headers['retry-after']) + this.client.options.restTimeOffset); + } else { + item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.request.path, res.body) : err); + finish(); + } + } else { + const data = res && res.body ? res.body : {}; + item.resolve(data); + finish(); + } + }); + }); + } + + reset() { + this.globallyLimited = false; + this.remaining = 1; + } +} + +module.exports = RequestHandler; diff --git a/src/client/rest/handlers/burst.js b/src/client/rest/handlers/burst.js new file mode 100644 index 000000000..b4e4fd2e3 --- /dev/null +++ b/src/client/rest/handlers/burst.js @@ -0,0 +1,13 @@ +module.exports = function burst() { + if (this.limited) return; + this.execute(this.queue.shift()) + .then(this.handle.bind(this)) + .catch(({ timeout }) => { + this.client.setTimeout(() => { + this.reset(); + this.handle(); + }, timeout || (this.resetTime - Date.now() + this.timeDifference + this.client.options.restTimeOffset)); + }); + this.remaining--; + this.handle(); +}; diff --git a/src/client/rest/handlers/index.js b/src/client/rest/handlers/index.js new file mode 100644 index 000000000..47792c46c --- /dev/null +++ b/src/client/rest/handlers/index.js @@ -0,0 +1,5 @@ +module.exports = { + sequential: require('./sequential'), + burst: require('./burst'), + RequestHandler: require('./RequestHandler'), +}; diff --git a/src/client/rest/handlers/sequential.js b/src/client/rest/handlers/sequential.js new file mode 100644 index 000000000..1f644dc17 --- /dev/null +++ b/src/client/rest/handlers/sequential.js @@ -0,0 +1,16 @@ +module.exports = function sequential() { + if (this.busy || this.limited) return; + this.busy = true; + this.execute(this.queue.shift()) + .then(() => { + this.busy = false; + this.handle(); + }) + .catch(({ timeout }) => { + this.client.setTimeout(() => { + this.reset(); + this.busy = false; + this.handle(); + }, timeout || (this.resetTime - Date.now() + this.timeDifference + this.client.options.restTimeOffset)); + }); +}; diff --git a/src/structures/Channel.js b/src/structures/Channel.js index 463179649..d8f877c9a 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -62,7 +62,7 @@ class Channel { * .catch(console.error); // Log error */ delete() { - return this.client.api.channels[this.id].delete().then(() => this); + return this.client.api.channels(this.id).delete().then(() => this); } } diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 4d26487ed..e0a2f3ba2 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -91,7 +91,7 @@ class ClientUser extends User { if (data.new_password) _data.new_password = data.newPassword; } - return this.client.api.users['@me'].patch({ data }) + return this.client.api.users('@me').patch({ data }) .then(newData => this.client.actions.UserUpdate.handle(newData).updated); } @@ -284,7 +284,7 @@ class ClientUser extends User { if (options.guild instanceof Guild) options.guild = options.guild.id; Util.mergeDefault({ limit: 25, roles: true, everyone: true, guild: null }, options); - return this.client.api.users['@me'].mentions.get({ query: options }) + return this.client.api.users('@me').mentions.get({ query: options }) .then(data => data.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client))); } @@ -351,7 +351,7 @@ class ClientUser extends User { return o; }, {}), } : { recipients: recipients.map(u => this.client.resolver.resolveUserID(u)) }; - return this.client.api.users['@me'].channels.post({ data }) + return this.client.api.users('@me').channels.post({ data }) .then(res => new GroupDMChannel(this.client, res)); } } diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index fc3ab07e9..3010d578b 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -121,7 +121,7 @@ class Emoji { * .catch(console.error); */ edit(data, reason) { - return this.client.api.guilds[this.guild.id].emojis[this.id] + return this.client.api.guilds(this.guild.id).emojis(this.id) .patch({ data: { name: data.name, roles: data.roles ? data.roles.map(r => r.id ? r.id : r) : [], diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 0666d72f8..1c82869ba 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -378,7 +378,7 @@ class Guild { * @returns {Promise>} */ fetchBans() { - return this.client.api.guilds[this.id].bans.get().then(bans => + return this.client.api.guilds(this.id).bans.get().then(bans => bans.reduce((collection, ban) => { collection.set(ban.user.id, { reason: ban.reason, @@ -394,7 +394,7 @@ class Guild { * @returns {Promise>} */ fetchInvites() { - return this.client.api.guilds[this.id].invites.get() + return this.client.api.guilds(this.id).invites.get() .then(inviteItems => { const invites = new Collection(); for (const inviteItem of inviteItems) { @@ -410,7 +410,7 @@ class Guild { * @returns {Promise>} */ fetchWebhooks() { - return this.client.api.guilds[this.id].webhooks.get().then(data => { + return this.client.api.guilds(this.id).webhooks.get().then(data => { const hooks = new Collection(); for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook)); return hooks; @@ -422,7 +422,7 @@ class Guild { * @returns {Promise>} */ fetchVoiceRegions() { - return this.client.api.guilds[this.id].regions.get().then(res => { + return this.client.api.guilds(this.id).regions.get().then(res => { const regions = new Collection(); for (const region of res) regions.set(region.id, new VoiceRegion(region)); return regions; @@ -444,7 +444,7 @@ class Guild { if (options.after && options.after instanceof GuildAuditLogs.Entry) options.after = options.after.id; if (typeof options.type === 'string') options.type = GuildAuditLogs.Actions[options.type]; - return this.client.api.guilds[this.id]['audit-logs'].get({ query: { + return this.client.api.guilds(this.id)['audit-logs'].get({ query: { before: options.before, after: options.after, limit: options.limit, @@ -476,7 +476,7 @@ class Guild { options.roles = roles.map(role => role.id); } } - return this.client.api.guilds[this.id].members[user.id].put({ data: options }) + return this.client.api.guilds(this.id).members(user.id).put({ data: options }) .then(data => this.client.actions.GuildMemberGet.handle(this, data).member); } @@ -490,7 +490,7 @@ class Guild { user = this.client.resolver.resolveUser(user); if (!user) return Promise.reject(new Error('USER_NOT_CACHED')); if (this.members.has(user.id)) return Promise.resolve(this.members.get(user.id)); - return this.client.api.guilds[this.id].members[user.id].get() + return this.client.api.guilds(this.id).members(user.id).get() .then(data => { if (cache) return this.client.actions.GuildMemberGet.handle(this, data).member; else return new GuildMember(this, data); @@ -597,7 +597,7 @@ class Guild { if (typeof data.explicitContentFilter !== 'undefined') { _data.explicit_content_filter = Number(data.explicitContentFilter); } - return this.client.api.guilds[this.id].patch({ data: _data, reason }) + return this.client.api.guilds(this.id).patch({ data: _data, reason }) .then(newData => this.client.actions.GuildUpdate.handle(newData).updated); } @@ -742,7 +742,7 @@ class Guild { * @returns {Promise} */ acknowledge() { - return this.client.api.guilds[this.id].ack + return this.client.api.guilds(this.id).ack .post({ data: { token: this.client.rest._ackToken } }) .then(res => { if (res.token) this.client.rest._ackToken = res.token; @@ -781,7 +781,7 @@ class Guild { if (options.days) options['delete-message-days'] = options.days; const id = this.client.resolver.resolveUserID(user); if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID', true)); - return this.client.api.guilds[this.id].bans[id].put({ query: options }) + return this.client.api.guilds(this.id).bans[id].put({ query: options }) .then(() => { if (user instanceof GuildMember) return user; const _user = this.client.resolver.resolveUser(id); @@ -830,7 +830,7 @@ class Guild { */ pruneMembers({ days = 7, dry = false, reason } = {}) { if (typeof days !== 'number') throw new TypeError('PRUNE_DAYS_TYPE'); - return this.client.api.guilds[this.id].prune[dry ? 'get' : 'post']({ query: { days }, reason }) + return this.client.api.guilds(this.id).prune[dry ? 'get' : 'post']({ query: { days }, reason }) .then(data => data.pruned); } @@ -865,7 +865,7 @@ class Guild { id: overwrite.id, })); } - return this.client.api.guilds[this.id].channels.post({ + return this.client.api.guilds(this.id).channels.post({ data: { name, type, permission_overwrites: overwrites, }, @@ -898,7 +898,7 @@ class Guild { }; } - return this.client.api.guilds[this.id].channels.patch({ data: { + return this.client.api.guilds(this.id).channels.patch({ data: { guild_id: this.id, channels: channelPositions, } }).then(() => @@ -936,7 +936,7 @@ class Guild { if (data.color) data.color = Util.resolveColor(data.color); if (data.permissions) data.permissions = Permissions.resolve(data.permissions); - return this.client.api.guilds[this.id].roles.post({ data, reason }).then(role => + return this.client.api.guilds(this.id).roles.post({ data, reason }).then(role => this.client.actions.GuildRoleCreate.handle({ guild_id: this.id, role, @@ -965,7 +965,7 @@ class Guild { if (typeof attachment === 'string' && attachment.startsWith('data:')) { const data = { image: attachment, name }; if (roles) data.roles = roles.map(r => r.id ? r.id : r); - return this.client.api.guilds[this.id].emojis.post({ data }) + return this.client.api.guilds(this.id).emojis.post({ data }) .then(emoji => this.client.actions.GuildEmojiCreate.handle(this, emoji).emoji); } else { return this.client.resolver.resolveBuffer(attachment) @@ -983,7 +983,7 @@ class Guild { */ deleteEmoji(emoji) { if (!(emoji instanceof Emoji)) emoji = this.emojis.get(emoji); - return this.client.api.guilds(this.id).emojis[emoji.id].delete() + return this.client.api.guilds(this.id).emojis(emoji.id).delete() .then(() => this.client.actions.GuildEmojiDelete.handle(emoji).data); } @@ -998,7 +998,7 @@ class Guild { */ leave() { if (this.ownerID === this.client.user.id) return Promise.reject(new Error('GUILD_OWNED')); - return this.client.api.users['@me'].guilds[this.id].delete() + return this.client.api.users('@me').guilds(this.id).delete() .then(() => this.client.actions.GuildDelete.handle({ id: this.id }).guild); } @@ -1012,7 +1012,7 @@ class Guild { * .catch(console.error); */ delete() { - return this.client.api.guilds[this.id].delete() + return this.client.api.guilds(this.id).delete() .then(() => this.client.actions.GuildDelete.handle({ id: this.id }).guild); } @@ -1170,7 +1170,7 @@ class Guild { Util.moveElementInArray(updatedRoles, role, position, relative); updatedRoles = updatedRoles.map((r, i) => ({ id: r.id, position: i })); - return this.client.api.guilds[this.id].roles.patch({ data: updatedRoles }) + return this.client.api.guilds(this.id).roles.patch({ data: updatedRoles }) .then(() => this.client.actions.GuildRolesPositionUpdate.handle({ guild_id: this.id, @@ -1202,7 +1202,7 @@ class Guild { Util.moveElementInArray(updatedChannels, channel, position, relative); updatedChannels = updatedChannels.map((r, i) => ({ id: r.id, position: i })); - return this.client.api.guilds[this.id].channels.patch({ data: updatedChannels }) + return this.client.api.guilds(this.id).channels.patch({ data: updatedChannels }) .then(() => this.client.actions.GuildChannelsPositionUpdate.handle({ guild_id: this.id, diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index ff2e33a58..7d405c564 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -189,7 +189,7 @@ class GuildChannel extends Channel { } } - return this.client.api.channels[this.id].permissions[payload.id] + return this.client.api.channels(this.id).permissions[payload.id] .put({ data: payload, reason }) .then(() => this); } @@ -216,7 +216,7 @@ class GuildChannel extends Channel { * .catch(console.error); */ edit(data, reason) { - return this.client.api.channels[this.id].patch({ + return this.client.api.channels(this.id).patch({ data: { name: (data.name || this.name).trim(), topic: data.topic || this.topic, @@ -289,7 +289,7 @@ class GuildChannel extends Channel { * @returns {Promise} */ createInvite({ temporary = false, maxAge = 86400, maxUses = 0, unique, reason } = {}) { - return this.client.api.channels[this.id].invites.post({ data: { + return this.client.api.channels(this.id).invites.post({ data: { temporary, max_age: maxAge, max_uses: maxUses, unique, }, reason }) .then(invite => new Invite(this.client, invite)); @@ -353,7 +353,7 @@ class GuildChannel extends Channel { * .catch(console.error); // Log error */ delete(reason) { - return this.client.api.channels[this.id].delete({ reason }).then(() => this); + return this.client.api.channels(this.id).delete({ reason }).then(() => this); } /** diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index d8dadd7d2..f6a10cbe9 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -342,13 +342,13 @@ class GuildMember { data.channel = null; } if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role); - let endpoint = this.client.api.guilds[this.guild.id]; + let endpoint = this.client.api.guilds(this.guild.id); if (this.user.id === this.client.user.id) { const keys = Object.keys(data); - if (keys.length === 1 && keys[0] === 'nick') endpoint = endpoint.members['@me'].nick; - else endpoint = endpoint.members[this.id]; + if (keys.length === 1 && keys[0] === 'nick') endpoint = endpoint.members('@me').nick; + else endpoint = endpoint.members(this.id); } else { - endpoint = endpoint.members[this.id]; + endpoint = endpoint.members(this.id); } return endpoint.patch({ data, reason }).then(newData => this.guild._updateMember(this, newData).mem); } @@ -402,7 +402,7 @@ class GuildMember { if (!(role instanceof Role)) role = this.guild.roles.get(role); if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake')); if (this._roles.includes(role.id)) return Promise.resolve(this); - return this.client.api.guilds[this.guild.id].members[this.user.id].roles[role.id] + return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id) .put({ reason }) .then(() => this); } @@ -433,7 +433,7 @@ class GuildMember { removeRole(role, reason) { if (!(role instanceof Role)) role = this.guild.roles.get(role); if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake')); - return this.client.api.guilds[this.guild.id].members[this.user.id].roles[role.id] + return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id) .delete({ reason }) .then(() => this); } @@ -492,7 +492,7 @@ class GuildMember { * @returns {Promise} */ kick(reason) { - return this.client.api.guilds[this.guild.id].members[this.user.id].delete({ reason }) + return this.client.api.guilds(this.guild.id).members(this.user.id).delete({ reason }) .then(() => this.client.actions.GuildMemberRemove.handle({ guild_id: this.guild.id, diff --git a/src/structures/Message.js b/src/structures/Message.js index 2c737e5c2..75ae5d7eb 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -413,7 +413,7 @@ class Message { * @returns {Promise} */ pin() { - return this.client.api.channels[this.channel.id].pins[this.id].put() + return this.client.api.channels(this.channel.id).pins(this.id).put() .then(() => this); } @@ -422,7 +422,7 @@ class Message { * @returns {Promise} */ unpin() { - return this.client.api.channels[this.channel.id].pins[this.id].delete() + return this.client.api.channels(this.channel.id).pins(this.id).delete() .then(() => this); } @@ -435,7 +435,7 @@ class Message { emoji = this.client.resolver.resolveEmojiIdentifier(emoji); if (!emoji) throw new TypeError('EMOJI_TYPE'); - return this.client.api.channels[this.channel.id].messages[this.id].reactions[emoji]['@me'] + return this.client.api.channels(this.channel.id).messages(this.id).reactions(emoji, '@me') .put() .then(() => this._addReaction(Util.parseEmoji(emoji), this.client.user)); } @@ -445,7 +445,7 @@ class Message { * @returns {Promise} */ clearReactions() { - return this.client.api.channels[this.channel.id].messages[this.id].reactions.delete() + return this.client.api.channels(this.channel.id).messages(this.id).reactions.delete() .then(() => this); } @@ -463,7 +463,7 @@ class Message { */ delete({ timeout = 0, reason } = {}) { if (timeout <= 0) { - return this.client.api.channels[this.channel.id].messages[this.id] + return this.client.api.channels(this.channel.id).messages(this.id) .delete({ reason }) .then(() => this.client.actions.MessageDelete.handle({ @@ -506,7 +506,7 @@ class Message { * @returns {Promise} */ acknowledge() { - return this.client.api.channels[this.channel.id].messages[this.id].ack + return this.client.api.channels(this.channel.id).messages(this.id).ack .post({ data: { token: this.client.rest._ackToken } }) .then(res => { if (res.token) this.client.rest._ackToken = res.token; diff --git a/src/structures/User.js b/src/structures/User.js index e3d2e0b4c..022f51f60 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -202,7 +202,7 @@ class User { */ createDM() { if (this.dmChannel) return Promise.resolve(this.dmChannel); - return this.client.api.users[this.client.user.id].channels.post({ data: { + return this.client.api.users(this.client.user.id).channels.post({ data: { recipient_id: this.id, } }) .then(data => this.client.actions.ChannelCreate.handle(data).channel); @@ -214,7 +214,7 @@ class User { */ deleteDM() { if (!this.dmChannel) return Promise.reject(new Error('USER_NO_DMCHANNEL')); - return this.client.api.channels[this.dmChannel.id].delete() + return this.client.api.channels(this.dmChannel.id).delete() .then(data => this.client.actions.ChannelDelete.handle(data).channel); } @@ -224,7 +224,7 @@ class User { * @returns {Promise} */ fetchProfile() { - return this.client.api.users[this.id].profile.get().then(data => new UserProfile(this, data)); + return this.client.api.users(this.id).profile.get().then(data => new UserProfile(this, data)); } /** @@ -234,7 +234,7 @@ class User { * @returns {Promise} */ setNote(note) { - return this.client.api.users['@me'].notes[this.id].put({ data: { note } }) + return this.client.api.users('@me').notes(this.id).put({ data: { note } }) .then(() => this); } diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 20af8554e..f1ee54fad 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -153,7 +153,7 @@ class Webhook { file.file = buffer; return file; }) - )).then(files => this.client.api.webhooks.opts(this.id, this.token).post({ + )).then(files => this.client.api.webhooks(this.id, this.token).post({ data: options, query: { wait: true }, files, @@ -161,7 +161,7 @@ class Webhook { })); } - return this.client.api.webhooks.opts(this.id, this.token).post({ + return this.client.api.webhooks(this.id, this.token).post({ data: options, query: { wait: true }, auth: false, @@ -190,7 +190,7 @@ class Webhook { * }).catch(console.error); */ sendSlackMessage(body) { - return this.client.api.webhooks.opts(this.id, this.token).slack.post({ + return this.client.api.webhooks(this.id, this.token).slack.post({ query: { wait: true }, auth: false, data: body, @@ -216,7 +216,7 @@ class Webhook { return this.edit({ name, avatar: dataURI }, reason); }); } - return this.client.api.webhooks.opts(this.id, this.token).patch({ + return this.client.api.webhooks(this.id, this.token).patch({ data: { name, avatar }, reason, }).then(data => { @@ -232,7 +232,7 @@ class Webhook { * @returns {Promise} */ delete(reason) { - return this.client.api.webhooks.opts(this.id, this.token).delete({ reason }); + return this.client.api.webhooks(this.id, this.token).delete({ reason }); } }