From 03caddb4b5e0d01035da53470e288215274f0018 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Thu, 21 Apr 2016 18:24:08 +0100 Subject: [PATCH] Add Rate Limiting, see hammerandchisel/discord-api-docs#20 --- src/client/rest/RESTManager.js | 55 +++++++++++++++++++++++++++++++++- test/random.js | 2 +- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/client/rest/RESTManager.js b/src/client/rest/RESTManager.js index 5cda94ccd..926a4de82 100644 --- a/src/client/rest/RESTManager.js +++ b/src/client/rest/RESTManager.js @@ -12,14 +12,57 @@ class RESTManager{ this.queue = []; this.userAgentManager = new UserAgentManager(this); this.methods = new RESTMethods(this); + this.rateLimitedEndpoints = {}; } - makeRequest(method, url, auth, data, file) { + addRequestToQueue(method, url, auth, data, file, resolve, reject) { + let endpoint = url.replace(/\/[0-9]+/g, '/:id'); + + let rateLimitedEndpoint = this.rateLimitedEndpoints[endpoint]; + + rateLimitedEndpoint.queue = rateLimitedEndpoint.queue || []; + + rateLimitedEndpoint.queue.push({ + method, url, auth, data, file, resolve, reject, + }); + } + + processQueue(endpoint) { + let rateLimitedEndpoint = this.rateLimitedEndpoints[endpoint]; + + // prevent multiple queue processes + if (!rateLimitedEndpoint.timeout) { + return; + } + + // lock the queue + clearTimeout(rateLimitedEndpoint.timeout); + rateLimitedEndpoint.timeout = null; + + for (let item of rateLimitedEndpoint.queue) { + this.makeRequest(item.method, item.url, item.auth, item.data, item.file) + .then(item.resolve) + .catch(item.reject); + } + + rateLimitedEndpoint.queue = []; + } + + makeRequest(method, url, auth, data, file, fromQueue) { /* file is {file, name} */ let apiRequest = request[method](url); + let endpoint = url.replace(/\/[0-9]+/g, '/:id'); + + if (this.rateLimitedEndpoints[endpoint] && this.rateLimitedEndpoints[endpoint].timeout) { + console.log('adding to queue'); + return new Promise((resolve, reject) => { + this.addRequestToQueue(method, url, auth, data, file, resolve, reject); + }); + } + if (auth) { if (this.client.store.token) { apiRequest.set('authorization', this.client.store.token); @@ -41,6 +84,16 @@ class RESTManager{ return new Promise((resolve, reject) => { apiRequest.end((err, res) => { if (err) { + let retry = res.headers['retry-after'] || res.headers['Retry-After']; + if (retry) { + this.rateLimitedEndpoints[endpoint] = {}; + this.addRequestToQueue(method, url, auth, data, file, resolve, reject); + this.rateLimitedEndpoints[endpoint].timeout = setTimeout(() => { + this.processQueue(endpoint); + }, retry); + return; + } + reject(err); } else { resolve(res ? res.body || {} : {}); diff --git a/test/random.js b/test/random.js index 87633f281..8bc302fdd 100644 --- a/test/random.js +++ b/test/random.js @@ -70,7 +70,7 @@ client.on('typingStop.', (channel, user, data) => { client.on('message', message => { if (message.author.username === 'hydrabolt') - message.channel.sendMessage('hydrabolt said: ' + message.content).then(console.log).catch(console.log); + message.channel.sendMessage('hydrabolt said: ' + message.content).then(m => console.log(m.content)).catch(console.log); }); client.on('messageDelete', message => {