mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 19:43:29 +01:00
rework concurrency
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
"runkitExampleFilename": "./docs/examples/ping.js",
|
"runkitExampleFilename": "./docs/examples/ping.js",
|
||||||
"unpkg": "./webpack/discord.min.js",
|
"unpkg": "./webpack/discord.min.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"async": "^2.6.1",
|
||||||
"form-data": "^2.3.2",
|
"form-data": "^2.3.2",
|
||||||
"node-fetch": "^2.1.2",
|
"node-fetch": "^2.1.2",
|
||||||
"pako": "^1.0.0",
|
"pako": "^1.0.0",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class APIRequest {
|
|||||||
const url = API + this.path;
|
const url = API + this.path;
|
||||||
let headers = {};
|
let headers = {};
|
||||||
|
|
||||||
if (this.options.auth !== false) headers.Authorization = this.rest.getAuth();
|
if (this.options.auth !== false) headers.Authorization = this.rest.auth;
|
||||||
if (this.options.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason);
|
if (this.options.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason);
|
||||||
if (!browser) headers['User-Agent'] = UserAgent;
|
if (!browser) headers['User-Agent'] = UserAgent;
|
||||||
if (this.options.headers) headers = Object.assign(headers, this.options.headers);
|
if (this.options.headers) headers = Object.assign(headers, this.options.headers);
|
||||||
|
|||||||
@@ -1,82 +1,104 @@
|
|||||||
const handlers = require('./handlers');
|
|
||||||
const APIRequest = require('./APIRequest');
|
const APIRequest = require('./APIRequest');
|
||||||
const routeBuilder = require('./APIRouter');
|
const routeBuilder = require('./APIRouter');
|
||||||
|
const RequestBucket = require('./RequestBucket');
|
||||||
const { Error } = require('../errors');
|
const { Error } = require('../errors');
|
||||||
const { Endpoints } = require('../util/Constants');
|
const { Endpoints } = require('../util/Constants');
|
||||||
const Collection = require('../util/Collection');
|
const Collection = require('../util/Collection');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rest manager.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
class RESTManager {
|
class RESTManager {
|
||||||
constructor(client, tokenPrefix = 'Bot') {
|
constructor(client, tokenPrefix = 'Bot') {
|
||||||
|
/**
|
||||||
|
* @type {Client}
|
||||||
|
*/
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.handlers = new Collection();
|
|
||||||
|
/**
|
||||||
|
* Request buckets, mapped by bucket
|
||||||
|
* @type {Collection<string, RequestHandler>}
|
||||||
|
*/
|
||||||
|
this.buckets = new Collection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether we're globally limited
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
this.globallyRateLimited = false;
|
this.globallyRateLimited = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The token prefix to use when generating authorization headers
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
this.tokenPrefix = tokenPrefix;
|
this.tokenPrefix = tokenPrefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use a versioned endpoint. Default to true.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
this.versioned = true;
|
this.versioned = true;
|
||||||
this.timeDifferences = [];
|
|
||||||
if (client.options.restSweepInterval > 0) {
|
if (client.options.restSweepInterval > 0) {
|
||||||
client.setInterval(() => {
|
client.setInterval(() => {
|
||||||
this.handlers.sweep(handler => handler._inactive);
|
this.buckets.sweep(handler => handler.queue.idle());
|
||||||
}, client.options.restSweepInterval * 1000);
|
}, client.options.restSweepInterval * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorization header to use
|
||||||
|
* @readonly
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
get auth() {
|
||||||
|
const token = this.client.token || this.client.accessToken;
|
||||||
|
const prefixed = !!this.client.application || (this.client.user && this.client.user.bot);
|
||||||
|
if (token) {
|
||||||
|
if (prefixed) return `${this.tokenPrefix} ${token}`;
|
||||||
|
return token;
|
||||||
|
} else {
|
||||||
|
throw new Error('TOKEN_MISSING');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a new API router
|
||||||
|
* @readonly
|
||||||
|
* @type {APIRouter}
|
||||||
|
*/
|
||||||
get api() {
|
get api() {
|
||||||
return routeBuilder(this);
|
return routeBuilder(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get timeDifference() {
|
/**
|
||||||
return Math.round(this.timeDifferences.reduce((a, b) => a + b, 0) / this.timeDifferences.length);
|
* The CDN endpoint
|
||||||
}
|
* @readonly
|
||||||
|
* @type {string}
|
||||||
set timeDifference(ms) {
|
*/
|
||||||
this.timeDifferences.unshift(ms);
|
|
||||||
if (this.timeDifferences.length > 5) this.timeDifferences.length = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
get cdn() {
|
||||||
return Endpoints.CDN(this.client.options.http.cdn);
|
return Endpoints.CDN(this.client.options.http.cdn);
|
||||||
}
|
}
|
||||||
|
|
||||||
push(handler, apiRequest) {
|
/**
|
||||||
return new Promise((resolve, reject) => {
|
* Make a request
|
||||||
handler.push({
|
* @param {string} method The HTTP verb to use
|
||||||
request: apiRequest,
|
* @param {string} url The Discord URL
|
||||||
resolve,
|
* @param {*} options Options to send
|
||||||
reject,
|
* @returns {Promise<any>}
|
||||||
});
|
*/
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getRequestHandler() {
|
|
||||||
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 = {}) {
|
request(method, url, options = {}) {
|
||||||
const apiRequest = new APIRequest(this, method, url, options);
|
const req = new APIRequest(this, method, url, options);
|
||||||
let handler = this.handlers.get(apiRequest.route);
|
let bucket = this.buckets.get(req.route);
|
||||||
|
|
||||||
if (!handler) {
|
if (!bucket) {
|
||||||
handler = new handlers.RequestHandler(this, this.getRequestHandler());
|
bucket = new RequestBucket(this, req.route);
|
||||||
this.handlers.set(apiRequest.route, handler);
|
this.buckets.set(req.route, bucket);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.push(handler, apiRequest);
|
return bucket.enqueue(req);
|
||||||
}
|
|
||||||
|
|
||||||
set endpoint(endpoint) {
|
|
||||||
this.client.options.http.api = endpoint;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
210
src/rest/RequestBucket.js
Normal file
210
src/rest/RequestBucket.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
const queue = require('async/queue');
|
||||||
|
const DiscordAPIError = require('./DiscordAPIError');
|
||||||
|
const { Events: { RATE_LIMIT } } = require('../util/Constants');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request bucket
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
class RequestBucket {
|
||||||
|
constructor(manager, route) {
|
||||||
|
/**
|
||||||
|
* @type {RESTManager}
|
||||||
|
*/
|
||||||
|
this.manager = manager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The route that this bucket is controlling
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.route = route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Client}
|
||||||
|
*/
|
||||||
|
this.client = this.manager.client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many requests are allowed in this bucket
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.limit = Infinity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of requests remaining in this bucket
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.remaining = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The async queue
|
||||||
|
* @type {QueueObject}
|
||||||
|
*/
|
||||||
|
this.queue = queue(this.handle.bind(this), this.client.options.restConcurrency || 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long to wait before sending next request.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.timeout = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current ratelimiting resume timer
|
||||||
|
* @type {?Timer}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._timer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time at which this bucket resets
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._resetTime = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current request sequence
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._seq = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The seq of the most recent request, negative if unknown
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._latest = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this bucket is currently ratelimited
|
||||||
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get limited() {
|
||||||
|
return this.manager.globallyRateLimited ||
|
||||||
|
(this.remaining - this.queue.running()) <= 0 ||
|
||||||
|
this.resetTime > Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp at which this bucket's ratelimiting will reset
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
get resetTime() {
|
||||||
|
return this._reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
set resetTime(time) {
|
||||||
|
if (time > this._reset) this._reset = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a request in this bucket
|
||||||
|
* @param {APIRequest} request The request to make
|
||||||
|
* @returns {Promise<*>}
|
||||||
|
*/
|
||||||
|
enqueue(request) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (request.seq === undefined) request.seq = this._seq++;
|
||||||
|
|
||||||
|
this.queue.push(request, (err, res) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
|
||||||
|
if (res.ok) return res.json().then(resolve, reject);
|
||||||
|
return res.json().then(data => {
|
||||||
|
reject(res.status >= 400 && res.status < 500 ?
|
||||||
|
new DiscordAPIError(request.path, data, request.method) : res);
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable callback-return */
|
||||||
|
/**
|
||||||
|
* Handle a request item
|
||||||
|
* @param {APIRequest} request The item to execute
|
||||||
|
* @param {Function} cb A callback to indicate when this item is processed
|
||||||
|
*/
|
||||||
|
handle(request, cb) {
|
||||||
|
if (this.limited && !this.queue.paused) this.queue.pause();
|
||||||
|
|
||||||
|
request.make().then(res => {
|
||||||
|
// Response handling
|
||||||
|
let timeout = 0;
|
||||||
|
if (res.status === 429) {
|
||||||
|
this.queue.unshift(request, cb);
|
||||||
|
this.client.emit('debug', `Exceeded ratelimit for bucket "${this.route}" (${request.method} ${res.url})`);
|
||||||
|
timeout = Number(res.headers.get('retry-after'));
|
||||||
|
} else if (res.status >= 500 && res.status < 600) {
|
||||||
|
if (request.retried) {
|
||||||
|
cb(res);
|
||||||
|
} else {
|
||||||
|
request.retried = true;
|
||||||
|
this.queue.unshift(request, cb);
|
||||||
|
timeout = 1e3;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb(null, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header parsing
|
||||||
|
const date = new Date(res.headers.get('date')).valueOf();
|
||||||
|
this.manager.globallyRateLimited = Boolean(res.headers.get('x-ratelimit-global'));
|
||||||
|
this.limit = Number(res.headers.get('x-ratelimit-limit') || Infinity);
|
||||||
|
this.timeout = (Number(res.headers.get('x-ratelimit-reset')) * 1e3 || date) - date;
|
||||||
|
this.resetTime = Date.now() + this.timeout;
|
||||||
|
|
||||||
|
const remaining = Number(res.headers.get('x-ratelimit-remaining'));
|
||||||
|
if (remaining < this.remaining || this.remaining < 0) this.remaining = remaining;
|
||||||
|
|
||||||
|
// If this is the newest response, control queue flow based on ratelimiting
|
||||||
|
if (request.seq >= this._latest) {
|
||||||
|
if (this.limited && !this.queue.paused) this.queue.pause();
|
||||||
|
else if (!this.limited && this.queue.paused) this.queue.resume();
|
||||||
|
this._latest = request.seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ratelimit handling
|
||||||
|
if (this.limited) {
|
||||||
|
if (!timeout) timeout = this.timeout;
|
||||||
|
timeout += this.client.options.restTimeOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the client hits a rate limit while making a request
|
||||||
|
* @event Client#rateLimit
|
||||||
|
* @param {Object} rateLimitInfo Object containing the rate limit info
|
||||||
|
* @param {number} rateLimitInfo.timeout Timeout in ms
|
||||||
|
* @param {number} rateLimitInfo.limit Number of requests that can be made to this endpoint
|
||||||
|
* @param {string} rateLimitInfo.method HTTP method used for request that triggered this event
|
||||||
|
* @param {string} rateLimitInfo.path Path used for request that triggered this event
|
||||||
|
* @param {string} rateLimitInfo.route Route used for request that triggered this event
|
||||||
|
*/
|
||||||
|
this.client.emit(RATE_LIMIT, {
|
||||||
|
timeout,
|
||||||
|
limit: this.limit,
|
||||||
|
method: request.method,
|
||||||
|
path: request.path,
|
||||||
|
route: request.route,
|
||||||
|
});
|
||||||
|
|
||||||
|
// NOTE: Use Timer#refresh (if the timeout is the same) when targeting Node 10
|
||||||
|
if (this._timer) clearTimeout(this._timer);
|
||||||
|
this._timer = this.client.setTimeout(() => {
|
||||||
|
this._timer = null;
|
||||||
|
this.reset();
|
||||||
|
this.queue.resume();
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
}, cb);
|
||||||
|
}
|
||||||
|
/* eslint-enable callback-return */
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.manager.globallyRateLimited = false;
|
||||||
|
this.remaining = this.limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RequestBucket;
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
const DiscordAPIError = require('../DiscordAPIError');
|
|
||||||
const { Events: { RATE_LIMIT } } = require('../../util/Constants');
|
|
||||||
|
|
||||||
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.queue = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
get limited() {
|
|
||||||
return (this.manager.globallyRateLimited || this.remaining <= 0) && Date.now() < this.resetTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
push(request) {
|
|
||||||
this.queue.push(request);
|
|
||||||
this.handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
get _inactive() {
|
|
||||||
return this.queue.length === 0 && !this.limited && Date.now() > this.resetTime && this.busy !== true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable prefer-promise-reject-errors */
|
|
||||||
execute(item) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const finish = timeout => {
|
|
||||||
if (timeout || this.limited) {
|
|
||||||
if (!timeout) {
|
|
||||||
timeout = this.resetTime - Date.now() + this.client.options.restTimeOffset;
|
|
||||||
}
|
|
||||||
if (!this.manager.globalTimeout && this.manager.globallyRateLimited) {
|
|
||||||
this.manager.globalTimeout = setTimeout(() => {
|
|
||||||
this.manager.globalTimeout = undefined;
|
|
||||||
this.manager.globallyRateLimited = false;
|
|
||||||
this.busy = false;
|
|
||||||
this.handle();
|
|
||||||
}, timeout);
|
|
||||||
reject({ });
|
|
||||||
} else {
|
|
||||||
reject({ timeout });
|
|
||||||
}
|
|
||||||
if (this.client.listenerCount(RATE_LIMIT)) {
|
|
||||||
/**
|
|
||||||
* Emitted when the client hits a rate limit while making a request
|
|
||||||
* @event Client#rateLimit
|
|
||||||
* @param {Object} rateLimitInfo Object containing the rate limit info
|
|
||||||
* @param {number} rateLimitInfo.timeout Timeout in ms
|
|
||||||
* @param {number} rateLimitInfo.limit Number of requests that can be made to this endpoint
|
|
||||||
* @param {string} rateLimitInfo.method HTTP method used for request that triggered this event
|
|
||||||
* @param {string} rateLimitInfo.path Path used for request that triggered this event
|
|
||||||
* @param {string} rateLimitInfo.route Route used for request that triggered this event
|
|
||||||
*/
|
|
||||||
this.client.emit(RATE_LIMIT, {
|
|
||||||
timeout,
|
|
||||||
limit: this.limit,
|
|
||||||
method: item.request.method,
|
|
||||||
path: item.request.path,
|
|
||||||
route: item.request.route,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
item.request.make().then(res => {
|
|
||||||
if (res && res.headers) {
|
|
||||||
if (res.headers['x-ratelimit-global']) this.manager.globallyRateLimited = true;
|
|
||||||
this.limit = Number(res.headers['x-ratelimit-limit']);
|
|
||||||
this.resetTime = Date.now() + Number(res.headers['retry-after']);
|
|
||||||
this.remaining = Number(res.headers['x-ratelimit-remaining']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
res.json().then(item.resolve, item.reject);
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.status === 429) {
|
|
||||||
this.queue.unshift(item);
|
|
||||||
finish(Number(res.headers['retry-after']) + this.client.options.restTimeOffset);
|
|
||||||
} else if (res.status >= 500 && res.status < 600) {
|
|
||||||
if (item.retried) {
|
|
||||||
item.reject(res);
|
|
||||||
finish();
|
|
||||||
} else {
|
|
||||||
item.retried = true;
|
|
||||||
this.queue.unshift(item);
|
|
||||||
finish(1e3 + this.client.options.restTimeOffset);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.json().then(data => {
|
|
||||||
item.reject(res.status >= 400 && res.status < 500 ?
|
|
||||||
new DiscordAPIError(item.path, data, item.method) : res);
|
|
||||||
}, item.reject);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.manager.globallyRateLimited = false;
|
|
||||||
this.remaining = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = RequestHandler;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
module.exports = function burst() {
|
|
||||||
if (this.limited || this.queue.length === 0) return;
|
|
||||||
this.execute(this.queue.shift())
|
|
||||||
.then(this.handle.bind(this))
|
|
||||||
.catch(({ timeout }) => {
|
|
||||||
if (timeout) {
|
|
||||||
this.client.setTimeout(() => {
|
|
||||||
this.reset();
|
|
||||||
this.handle();
|
|
||||||
}, timeout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.remaining--;
|
|
||||||
this.handle();
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
sequential: require('./sequential'),
|
|
||||||
burst: require('./burst'),
|
|
||||||
RequestHandler: require('./RequestHandler'),
|
|
||||||
};
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
module.exports = function sequential() {
|
|
||||||
if (this.busy || this.limited || this.queue.length === 0) return;
|
|
||||||
this.busy = true;
|
|
||||||
this.execute(this.queue.shift())
|
|
||||||
.then(() => {
|
|
||||||
this.busy = false;
|
|
||||||
this.handle();
|
|
||||||
})
|
|
||||||
.catch(({ timeout }) => {
|
|
||||||
if (timeout) {
|
|
||||||
this.client.setTimeout(() => {
|
|
||||||
this.reset();
|
|
||||||
this.busy = false;
|
|
||||||
this.handle();
|
|
||||||
}, timeout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -5,10 +5,6 @@ const browser = exports.browser = typeof window !== 'undefined';
|
|||||||
/**
|
/**
|
||||||
* Options for a client.
|
* Options for a client.
|
||||||
* @typedef {Object} ClientOptions
|
* @typedef {Object} ClientOptions
|
||||||
* @property {string} [apiRequestMethod='sequential'] One of `sequential` or `burst`. The sequential handler executes
|
|
||||||
* all requests in the order they are triggered, whereas the burst handler runs multiple in parallel, and doesn't
|
|
||||||
* provide the guarantee of any particular order. Burst mode is more likely to hit a 429 ratelimit error by its nature,
|
|
||||||
* and is therefore slightly riskier to use.
|
|
||||||
* @property {number} [shardId=0] ID of the shard to run
|
* @property {number} [shardId=0] ID of the shard to run
|
||||||
* @property {number} [shardCount=0] Total number of shards
|
* @property {number} [shardCount=0] Total number of shards
|
||||||
* @property {number} [messageCacheMaxSize=200] Maximum number of messages to cache per channel
|
* @property {number} [messageCacheMaxSize=200] Maximum number of messages to cache per channel
|
||||||
@@ -28,6 +24,9 @@ const browser = exports.browser = typeof window !== 'undefined';
|
|||||||
* requests (higher values will reduce rate-limiting errors on bad connections)
|
* requests (higher values will reduce rate-limiting errors on bad connections)
|
||||||
* @property {number} [restSweepInterval=60] How frequently to delete inactive request buckets, in seconds
|
* @property {number} [restSweepInterval=60] How frequently to delete inactive request buckets, in seconds
|
||||||
* (or 0 for never)
|
* (or 0 for never)
|
||||||
|
* @property {number} [restConcurrency=1] The number REST calls to execute concurrently. Increasing this value above
|
||||||
|
* 1 will not guarantee delivery order but may result in increased performance. There is greater risk of hitting
|
||||||
|
* ratelimits when using concurrency above 1.
|
||||||
* @property {PresenceData} [presence] Presence data to use upon login
|
* @property {PresenceData} [presence] Presence data to use upon login
|
||||||
* @property {WSEventType[]} [disabledEvents] An array of disabled websocket events. Events in this array will not be
|
* @property {WSEventType[]} [disabledEvents] An array of disabled websocket events. Events in this array will not be
|
||||||
* processed, potentially resulting in performance improvements for larger bots. Only disable events you are
|
* processed, potentially resulting in performance improvements for larger bots. Only disable events you are
|
||||||
@@ -37,7 +36,6 @@ const browser = exports.browser = typeof window !== 'undefined';
|
|||||||
* @property {HTTPOptions} [http] HTTP options
|
* @property {HTTPOptions} [http] HTTP options
|
||||||
*/
|
*/
|
||||||
exports.DefaultOptions = {
|
exports.DefaultOptions = {
|
||||||
apiRequestMethod: 'sequential',
|
|
||||||
shardId: 0,
|
shardId: 0,
|
||||||
shardCount: 0,
|
shardCount: 0,
|
||||||
internalSharding: false,
|
internalSharding: false,
|
||||||
@@ -51,6 +49,7 @@ exports.DefaultOptions = {
|
|||||||
disabledEvents: [],
|
disabledEvents: [],
|
||||||
restTimeOffset: 500,
|
restTimeOffset: 500,
|
||||||
restSweepInterval: 60,
|
restSweepInterval: 60,
|
||||||
|
restConcurrency: 1,
|
||||||
presence: {},
|
presence: {},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ const { token, prefix, owner } = require('./auth.js');
|
|||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
const log = (...args) => console.log(process.uptime().toFixed(3), ...args);
|
const log = (...args) => console.log(process.uptime().toFixed(3), ...args);
|
||||||
|
|
||||||
const client = new Discord.Client();
|
const client = new Discord.Client({ apiRequestConcurrency: Infinity, restTimeOffset: 0 });
|
||||||
|
|
||||||
client.on('debug', log);
|
client.on('debug', log);
|
||||||
client.on('ready', () => {
|
client.on('ready', () => {
|
||||||
log('READY', client.user.tag, client.user.id);
|
log('READY', client.user.tag, client.user.id);
|
||||||
});
|
});
|
||||||
client.on('rateLimit', log);
|
client.on('rateLimit', info => log(`ratelimited for ${info.timeout} ms`));
|
||||||
|
client.on('error', log);
|
||||||
|
|
||||||
const commands = {
|
const commands = {
|
||||||
eval: message => {
|
eval: message => {
|
||||||
@@ -24,9 +25,21 @@ const commands = {
|
|||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
res = err.message;
|
res = err.message;
|
||||||
}
|
}
|
||||||
message.channel.send(res, { code: 'js' });
|
if (res.length > 6000) {
|
||||||
|
message.channel.send('response too long; check console');
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(res);
|
||||||
|
} else {
|
||||||
|
message.channel.send(res, { code: 'js', split: true });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ping: message => message.reply('pong'),
|
ping: message => message.reply('pong'),
|
||||||
|
spam: async message => {
|
||||||
|
const start = Date.now();
|
||||||
|
await Promise.all(Array.from({ length: 10 }, (_, i) => message.channel.send(`spam${i}`)));
|
||||||
|
const diff = Date.now() - start;
|
||||||
|
message.channel.send(`total time: \`${diff}ms\``);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
client.on('message', message => {
|
client.on('message', message => {
|
||||||
|
|||||||
Reference in New Issue
Block a user