Add files via upload
This commit is contained in:
137
node_modules/eris/lib/rest/Endpoints.js
generated
vendored
Normal file
137
node_modules/eris/lib/rest/Endpoints.js
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
"use strict";
|
||||
|
||||
const {REST_VERSION} = require("../Constants");
|
||||
|
||||
module.exports.BASE_URL = "/api/v" + REST_VERSION;
|
||||
module.exports.CDN_URL = "https://cdn.discordapp.com";
|
||||
module.exports.CLIENT_URL = "https://discord.com";
|
||||
|
||||
module.exports.ORIGINAL_INTERACTION_RESPONSE = (appID, interactToken) => `/webhooks/${appID}/${interactToken}`;
|
||||
module.exports.COMMAND = (applicationID, commandID) => `/applications/${applicationID}/commands/${commandID}`;
|
||||
module.exports.COMMANDS = (applicationID) => `/applications/${applicationID}/commands`;
|
||||
module.exports.COMMAND_PERMISSIONS = (applicationID, guildID, commandID) => `/applications/${applicationID}/guilds/${guildID}/commands/${commandID}/permissions`;
|
||||
module.exports.CHANNEL = (chanID) => `/channels/${chanID}`;
|
||||
module.exports.CHANNEL_BULK_DELETE = (chanID) => `/channels/${chanID}/messages/bulk-delete`;
|
||||
module.exports.CHANNEL_CALL_RING = (chanID) => `/channels/${chanID}/call/ring`;
|
||||
module.exports.CHANNEL_CROSSPOST = (chanID, msgID) => `/channels/${chanID}/messages/${msgID}/crosspost`;
|
||||
module.exports.CHANNEL_FOLLOW = (chanID) => `/channels/${chanID}/followers`;
|
||||
module.exports.CHANNEL_INVITES = (chanID) => `/channels/${chanID}/invites`;
|
||||
module.exports.CHANNEL_MESSAGE_REACTION = (chanID, msgID, reaction) => `/channels/${chanID}/messages/${msgID}/reactions/${reaction}`;
|
||||
module.exports.CHANNEL_MESSAGE_REACTION_USER = (chanID, msgID, reaction, userID) => `/channels/${chanID}/messages/${msgID}/reactions/${reaction}/${userID}`;
|
||||
module.exports.CHANNEL_MESSAGE_REACTIONS = (chanID, msgID) => `/channels/${chanID}/messages/${msgID}/reactions`;
|
||||
module.exports.CHANNEL_MESSAGE = (chanID, msgID) => `/channels/${chanID}/messages/${msgID}`;
|
||||
module.exports.CHANNEL_MESSAGES = (chanID) => `/channels/${chanID}/messages`;
|
||||
module.exports.CHANNEL_MESSAGES_SEARCH = (chanID) => `/channels/${chanID}/messages/search`;
|
||||
module.exports.CHANNEL_PERMISSION = (chanID, overID) => `/channels/${chanID}/permissions/${overID}`;
|
||||
module.exports.CHANNEL_PERMISSIONS = (chanID) => `/channels/${chanID}/permissions`;
|
||||
module.exports.CHANNEL_PIN = (chanID, msgID) => `/channels/${chanID}/pins/${msgID}`;
|
||||
module.exports.CHANNEL_PINS = (chanID) => `/channels/${chanID}/pins`;
|
||||
module.exports.CHANNEL_RECIPIENT = (groupID, userID) => `/channels/${groupID}/recipients/${userID}`;
|
||||
module.exports.CHANNEL_TYPING = (chanID) => `/channels/${chanID}/typing`;
|
||||
module.exports.CHANNEL_WEBHOOKS = (chanID) => `/channels/${chanID}/webhooks`;
|
||||
module.exports.CHANNELS = "/channels";
|
||||
module.exports.CUSTOM_EMOJI_GUILD = (emojiID) => `/emojis/${emojiID}/guild`;
|
||||
module.exports.DISCOVERY_CATEGORIES = "/discovery/categories";
|
||||
module.exports.DISCOVERY_VALIDATION = "/discovery/valid-term";
|
||||
module.exports.GATEWAY = "/gateway";
|
||||
module.exports.GATEWAY_BOT = "/gateway/bot";
|
||||
module.exports.GUILD = (guildID) => `/guilds/${guildID}`;
|
||||
module.exports.GUILD_AUDIT_LOGS = (guildID) => `/guilds/${guildID}/audit-logs`;
|
||||
module.exports.GUILD_BAN = (guildID, memberID) => `/guilds/${guildID}/bans/${memberID}`;
|
||||
module.exports.GUILD_BANS = (guildID) => `/guilds/${guildID}/bans`;
|
||||
module.exports.GUILD_CHANNELS = (guildID) => `/guilds/${guildID}/channels`;
|
||||
module.exports.GUILD_COMMAND = (applicationID, guildID, commandID) => `/applications/${applicationID}/guilds/${guildID}/commands/${commandID}`;
|
||||
module.exports.GUILD_COMMAND_PERMISSIONS = (applicationID, guildID) => `/applications/${applicationID}/guilds/${guildID}/commands/permissions`;
|
||||
module.exports.GUILD_COMMANDS = (applicationID, guildID) => `/applications/${applicationID}/guilds/${guildID}/commands`;
|
||||
module.exports.GUILD_DISCOVERY = (guildID) => `/guilds/${guildID}/discovery-metadata`;
|
||||
module.exports.GUILD_DISCOVERY_CATEGORY = (guildID, categoryID) => `/guilds/${guildID}/discovery-categories/${categoryID}`;
|
||||
module.exports.GUILD_EMOJI = (guildID, emojiID) => `/guilds/${guildID}/emojis/${emojiID}`;
|
||||
module.exports.GUILD_EMOJIS = (guildID) => `/guilds/${guildID}/emojis`;
|
||||
module.exports.GUILD_INTEGRATION = (guildID, inteID) => `/guilds/${guildID}/integrations/${inteID}`;
|
||||
module.exports.GUILD_INTEGRATION_SYNC = (guildID, inteID) => `/guilds/${guildID}/integrations/${inteID}/sync`;
|
||||
module.exports.GUILD_INTEGRATIONS = (guildID) => `/guilds/${guildID}/integrations`;
|
||||
module.exports.GUILD_INVITES = (guildID) => `/guilds/${guildID}/invites`;
|
||||
module.exports.GUILD_VANITY_URL = (guildID) => `/guilds/${guildID}/vanity-url`;
|
||||
module.exports.GUILD_MEMBER = (guildID, memberID) => `/guilds/${guildID}/members/${memberID}`;
|
||||
module.exports.GUILD_MEMBER_NICK = (guildID, memberID) => `/guilds/${guildID}/members/${memberID}/nick`;
|
||||
module.exports.GUILD_MEMBER_ROLE = (guildID, memberID, roleID) => `/guilds/${guildID}/members/${memberID}/roles/${roleID}`;
|
||||
module.exports.GUILD_MEMBERS = (guildID) => `/guilds/${guildID}/members`;
|
||||
module.exports.GUILD_MEMBERS_SEARCH = (guildID) => `/guilds/${guildID}/members/search`;
|
||||
module.exports.GUILD_MESSAGES_SEARCH = (guildID) => `/guilds/${guildID}/messages/search`;
|
||||
module.exports.GUILD_PREVIEW = (guildID) => `/guilds/${guildID}/preview`;
|
||||
module.exports.GUILD_PRUNE = (guildID) => `/guilds/${guildID}/prune`;
|
||||
module.exports.GUILD_ROLE = (guildID, roleID) => `/guilds/${guildID}/roles/${roleID}`;
|
||||
module.exports.GUILD_ROLES = (guildID) => `/guilds/${guildID}/roles`;
|
||||
module.exports.GUILD_SCHEDULED_EVENT = (guildID, scheduledEventID) => `/guilds/${guildID}/scheduled-events/${scheduledEventID}`;
|
||||
module.exports.GUILD_SCHEDULED_EVENT_USERS = (guildID, scheduledEventID) => `/guilds/${guildID}/scheduled-events/${scheduledEventID}/users`;
|
||||
module.exports.GUILD_SCHEDULED_EVENTS = (guildID) => `/guilds/${guildID}/scheduled-events`;
|
||||
module.exports.GUILD_STICKER = (guildID, stickerID) => `/guilds/${guildID}/stickers/${stickerID}`;
|
||||
module.exports.GUILD_STICKERS = (guildID) => `/guilds/${guildID}/stickers`;
|
||||
module.exports.GUILD_TEMPLATE = (code) => `/guilds/templates/${code}`;
|
||||
module.exports.GUILD_TEMPLATES = (guildID) => `/guilds/${guildID}/templates`;
|
||||
module.exports.GUILD_TEMPLATE_GUILD = (guildID, code) => `/guilds/${guildID}/templates/${code}`;
|
||||
module.exports.GUILD_VOICE_REGIONS = (guildID) => `/guilds/${guildID}/regions`;
|
||||
module.exports.GUILD_WEBHOOKS = (guildID) => `/guilds/${guildID}/webhooks`;
|
||||
module.exports.GUILD_WELCOME_SCREEN = (guildID) => `/guilds/${guildID}/welcome-screen`;
|
||||
module.exports.GUILD_WIDGET = (guildID) => `/guilds/${guildID}/widget.json`;
|
||||
module.exports.GUILD_WIDGET_SETTINGS = (guildID) => `/guilds/${guildID}/widget`;
|
||||
module.exports.GUILD_VOICE_STATE = (guildID, user) => `/guilds/${guildID}/voice-states/${user}`;
|
||||
module.exports.GUILDS = "/guilds";
|
||||
module.exports.INTERACTION_RESPOND = (interactID, interactToken) => `/interactions/${interactID}/${interactToken}/callback`;
|
||||
module.exports.INVITE = (inviteID) => `/invites/${inviteID}`;
|
||||
module.exports.OAUTH2_APPLICATION = (appID) => `/oauth2/applications/${appID}`;
|
||||
module.exports.STAGE_INSTANCE = (channelID) => `/stage-instances/${channelID}`;
|
||||
module.exports.STAGE_INSTANCES = "/stage-instances";
|
||||
module.exports.STICKER = (stickerID) => `/stickers/${stickerID}`;
|
||||
module.exports.STICKER_PACKS = "/sticker-packs";
|
||||
module.exports.THREAD_MEMBER = (channelID, userID) => `/channels/${channelID}/thread-members/${userID}`;
|
||||
module.exports.THREAD_MEMBERS = (channelID) => `/channels/${channelID}/thread-members`;
|
||||
module.exports.THREAD_WITH_MESSAGE = (channelID, msgID) => `/channels/${channelID}/messages/${msgID}/threads`;
|
||||
module.exports.THREAD_WITHOUT_MESSAGE = (channelID) => `/channels/${channelID}/threads`;
|
||||
module.exports.THREADS_ACTIVE = (channelID) => `/channels/${channelID}/threads/active`;
|
||||
module.exports.THREADS_ARCHIVED = (channelID, type) => `/channels/${channelID}/threads/archived/${type}`;
|
||||
module.exports.THREADS_ARCHIVED_JOINED = (channelID) => `/channels/${channelID}/users/@me/threads/archived/private`;
|
||||
module.exports.THREADS_GUILD_ACTIVE = (guildID) => `/guilds/${guildID}/threads/active`;
|
||||
module.exports.USER = (userID) => `/users/${userID}`;
|
||||
module.exports.USER_BILLING = (userID) => `/users/${userID}/billing`;
|
||||
module.exports.USER_BILLING_PAYMENTS = (userID) => `/users/${userID}/billing/payments`;
|
||||
module.exports.USER_BILLING_PREMIUM_SUBSCRIPTION = (userID) => `/users/${userID}/billing/premium-subscription`;
|
||||
module.exports.USER_CHANNELS = (userID) => `/users/${userID}/channels`;
|
||||
module.exports.USER_CONNECTIONS = (userID) => `/users/${userID}/connections`;
|
||||
module.exports.USER_CONNECTION_PLATFORM = (userID, platform, id) => `/users/${userID}/connections/${platform}/${id}`;
|
||||
module.exports.USER_GUILD = (userID, guildID) => `/users/${userID}/guilds/${guildID}`;
|
||||
module.exports.USER_GUILDS = (userID) => `/users/${userID}/guilds`;
|
||||
module.exports.USER_MFA_CODES = (userID) => `/users/${userID}/mfa/codes`;
|
||||
module.exports.USER_MFA_TOTP_DISABLE = (userID) => `/users/${userID}/mfa/totp/disable`;
|
||||
module.exports.USER_MFA_TOTP_ENABLE = (userID) => `/users/${userID}/mfa/totp/enable`;
|
||||
module.exports.USER_NOTE = (userID, targetID) => `/users/${userID}/note/${targetID}`;
|
||||
module.exports.USER_PROFILE = (userID) => `/users/${userID}/profile`;
|
||||
module.exports.USER_RELATIONSHIP = (userID, relID) => `/users/${userID}/relationships/${relID}`;
|
||||
module.exports.USER_SETTINGS = (userID) => `/users/${userID}/settings`;
|
||||
module.exports.USERS = "/users";
|
||||
module.exports.VOICE_REGIONS = "/voice/regions";
|
||||
module.exports.WEBHOOK = (hookID) => `/webhooks/${hookID}`;
|
||||
module.exports.WEBHOOK_MESSAGE = (hookID, token, msgID) => `/webhooks/${hookID}/${token}/messages/${msgID}`;
|
||||
module.exports.WEBHOOK_SLACK = (hookID) => `/webhooks/${hookID}/slack`;
|
||||
module.exports.WEBHOOK_TOKEN = (hookID, token) => `/webhooks/${hookID}/${token}`;
|
||||
module.exports.WEBHOOK_TOKEN_SLACK = (hookID, token) => `/webhooks/${hookID}/${token}/slack`;
|
||||
|
||||
// CDN Endpoints
|
||||
module.exports.ACHIEVEMENT_ICON = (applicationID, achievementID, icon) => `/app-assets/${applicationID}/achievements/${achievementID}/icons/${icon}`;
|
||||
module.exports.APPLICATION_ASSET = (applicationID, asset) => `/app-assets/${applicationID}/${asset}`;
|
||||
module.exports.APPLICATION_ICON = (applicationID, icon) => `/app-icons/${applicationID}/${icon}`;
|
||||
module.exports.BANNER = (guildOrUserID, hash) => `/banners/${guildOrUserID}/${hash}`;
|
||||
module.exports.CHANNEL_ICON = (chanID, chanIcon) => `/channel-icons/${chanID}/${chanIcon}`;
|
||||
module.exports.CUSTOM_EMOJI = (emojiID) => `/emojis/${emojiID}`;
|
||||
module.exports.DEFAULT_USER_AVATAR = (userDiscriminator) => `/embed/avatars/${userDiscriminator}`;
|
||||
module.exports.GUILD_AVATAR = (guildID, userID, guildAvatar) => `/guilds/${guildID}/users/${userID}/avatars/${guildAvatar}`;
|
||||
module.exports.GUILD_DISCOVERY_SPLASH = (guildID, guildDiscoverySplash) => `/discovery-splashes/${guildID}/${guildDiscoverySplash}`;
|
||||
module.exports.GUILD_ICON = (guildID, guildIcon) => `/icons/${guildID}/${guildIcon}`;
|
||||
module.exports.GUILD_SCHEDULED_EVENT_COVER = (eventID, eventIcon) => `/guild-events/${eventID}/${eventIcon}`;
|
||||
module.exports.GUILD_SPLASH = (guildID, guildSplash) => `/splashes/${guildID}/${guildSplash}`;
|
||||
module.exports.ROLE_ICON = (roleID, roleIcon) => `/role-icons/${roleID}/${roleIcon}`;
|
||||
module.exports.TEAM_ICON = (teamID, teamIcon) => `/team-icons/${teamID}/${teamIcon}`;
|
||||
module.exports.USER_AVATAR = (userID, userAvatar) => `/avatars/${userID}/${userAvatar}`;
|
||||
|
||||
// Client Endpoints
|
||||
module.exports.MESSAGE_LINK = (guildID, channelID, messageID) => `/channels/${guildID}/${channelID}/${messageID}`;
|
451
node_modules/eris/lib/rest/RequestHandler.js
generated
vendored
Normal file
451
node_modules/eris/lib/rest/RequestHandler.js
generated
vendored
Normal file
@ -0,0 +1,451 @@
|
||||
"use strict";
|
||||
|
||||
const util = require("util");
|
||||
const Base = require("../structures/Base");
|
||||
const DiscordHTTPError = require("../errors/DiscordHTTPError");
|
||||
const DiscordRESTError = require("../errors/DiscordRESTError");
|
||||
const Endpoints = require("./Endpoints");
|
||||
const HTTPS = require("https");
|
||||
const MultipartData = require("../util/MultipartData");
|
||||
const SequentialBucket = require("../util/SequentialBucket");
|
||||
const Zlib = require("zlib");
|
||||
|
||||
/**
|
||||
* Handles API requests
|
||||
*/
|
||||
class RequestHandler {
|
||||
constructor(client, options) {
|
||||
// [DEPRECATED] Previously forceQueueing
|
||||
if(typeof options === "boolean") {
|
||||
options = {
|
||||
forceQueueing: options
|
||||
};
|
||||
}
|
||||
this.options = options = Object.assign({
|
||||
agent: client.options.agent || null,
|
||||
baseURL: Endpoints.BASE_URL,
|
||||
decodeReasons: true,
|
||||
disableLatencyCompensation: false,
|
||||
domain: "discord.com",
|
||||
latencyThreshold: client.options.latencyThreshold || 30000,
|
||||
ratelimiterOffset: client.options.ratelimiterOffset || 0,
|
||||
requestTimeout: client.options.requestTimeout || 15000
|
||||
}, options);
|
||||
|
||||
this._client = client;
|
||||
this.userAgent = `DiscordBot (https://github.com/abalabahaha/eris, ${require("../../package.json").version})`;
|
||||
this.ratelimits = {};
|
||||
this.latencyRef = {
|
||||
latency: this.options.ratelimiterOffset,
|
||||
raw: new Array(10).fill(this.options.ratelimiterOffset),
|
||||
timeOffset: 0,
|
||||
timeOffsets: new Array(10).fill(0),
|
||||
lastTimeOffsetCheck: 0
|
||||
};
|
||||
this.globalBlock = false;
|
||||
this.readyQueue = [];
|
||||
if(this.options.forceQueueing) {
|
||||
this.globalBlock = true;
|
||||
this._client.once("shardPreReady", () => this.globalUnblock());
|
||||
}
|
||||
}
|
||||
|
||||
globalUnblock() {
|
||||
this.globalBlock = false;
|
||||
while(this.readyQueue.length > 0) {
|
||||
this.readyQueue.shift()();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an API request
|
||||
* @arg {String} method Uppercase HTTP method
|
||||
* @arg {String} url URL of the endpoint
|
||||
* @arg {Boolean} [auth] Whether to add the Authorization header and token or not
|
||||
* @arg {Object} [body] Request payload
|
||||
* @arg {Object} [file] File object
|
||||
* @arg {Buffer} file.file A buffer containing file data
|
||||
* @arg {String} file.name What to name the file
|
||||
* @returns {Promise<Object>} Resolves with the returned JSON data
|
||||
*/
|
||||
request(method, url, auth, body, file, _route, short) {
|
||||
const route = _route || this.routefy(url, method);
|
||||
|
||||
const _stackHolder = {}; // Preserve async stack
|
||||
Error.captureStackTrace(_stackHolder);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let attempts = 0;
|
||||
|
||||
const actualCall = (cb) => {
|
||||
const headers = {
|
||||
"User-Agent": this.userAgent,
|
||||
"Accept-Encoding": "gzip,deflate"
|
||||
};
|
||||
let data;
|
||||
let finalURL = url;
|
||||
|
||||
try {
|
||||
if(auth) {
|
||||
headers.Authorization = this._client._token;
|
||||
}
|
||||
if(body && body.reason) { // Audit log reason sniping
|
||||
let unencodedReason = body.reason;
|
||||
if(this.options.decodeReasons) {
|
||||
try {
|
||||
if(unencodedReason.includes("%") && !unencodedReason.includes(" ")) {
|
||||
unencodedReason = decodeURIComponent(unencodedReason);
|
||||
}
|
||||
} catch(err) {
|
||||
this._client.emit("error", err);
|
||||
}
|
||||
}
|
||||
headers["X-Audit-Log-Reason"] = encodeURIComponent(unencodedReason);
|
||||
if((method !== "PUT" || !url.includes("/bans")) && (method !== "POST" || !url.includes("/prune"))) {
|
||||
delete body.reason;
|
||||
} else {
|
||||
body.reason = unencodedReason;
|
||||
}
|
||||
}
|
||||
if(file) {
|
||||
if(Array.isArray(file)) {
|
||||
data = new MultipartData();
|
||||
headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
|
||||
file.forEach(function(f) {
|
||||
if(!f.file) {
|
||||
return;
|
||||
}
|
||||
data.attach(f.name, f.file, f.name);
|
||||
});
|
||||
if(body) {
|
||||
data.attach("payload_json", body);
|
||||
}
|
||||
data = data.finish();
|
||||
} else if(file.file) {
|
||||
data = new MultipartData();
|
||||
headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
|
||||
data.attach("file", file.file, file.name);
|
||||
if(body) {
|
||||
if(method === "POST" && url.endsWith("/stickers")) {
|
||||
for(const key in body) {
|
||||
data.attach(key, body[key]);
|
||||
}
|
||||
} else {
|
||||
data.attach("payload_json", body);
|
||||
}
|
||||
}
|
||||
data = data.finish();
|
||||
} else {
|
||||
throw new Error("Invalid file object");
|
||||
}
|
||||
} else if(body) {
|
||||
if(method === "GET" || method === "DELETE") {
|
||||
let qs = "";
|
||||
Object.keys(body).forEach(function(key) {
|
||||
if(body[key] != undefined) {
|
||||
if(Array.isArray(body[key])) {
|
||||
body[key].forEach(function(val) {
|
||||
qs += `&${encodeURIComponent(key)}=${encodeURIComponent(val)}`;
|
||||
});
|
||||
} else {
|
||||
qs += `&${encodeURIComponent(key)}=${encodeURIComponent(body[key])}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
finalURL += "?" + qs.substring(1);
|
||||
} else {
|
||||
// Replacer function serializes bigints to strings, the format Discord uses
|
||||
data = JSON.stringify(body, (k, v) => typeof v === "bigint" ? v.toString() : v);
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
cb();
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
let req;
|
||||
try {
|
||||
req = HTTPS.request({
|
||||
method: method,
|
||||
host: this.options.domain,
|
||||
path: this.options.baseURL + finalURL,
|
||||
headers: headers,
|
||||
agent: this.options.agent
|
||||
});
|
||||
} catch(err) {
|
||||
cb();
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
let reqError;
|
||||
|
||||
req.once("abort", () => {
|
||||
cb();
|
||||
reqError = reqError || new Error(`Request aborted by client on ${method} ${url}`);
|
||||
reqError.req = req;
|
||||
reject(reqError);
|
||||
}).once("error", (err) => {
|
||||
reqError = err;
|
||||
req.abort();
|
||||
});
|
||||
|
||||
let latency = Date.now();
|
||||
|
||||
req.once("response", (resp) => {
|
||||
latency = Date.now() - latency;
|
||||
if(!this.options.disableLatencyCompensation) {
|
||||
this.latencyRef.raw.push(latency);
|
||||
this.latencyRef.latency = this.latencyRef.latency - ~~(this.latencyRef.raw.shift() / 10) + ~~(latency / 10);
|
||||
}
|
||||
|
||||
if(this._client.listeners("rawREST").length) {
|
||||
/**
|
||||
* Fired when the Client's RequestHandler receives a response
|
||||
* @event Client#rawREST
|
||||
* @prop {Object} [request] The data for the request.
|
||||
* @prop {Boolean} request.auth True if the request required an authorization token
|
||||
* @prop {Object} [request.body] The request payload
|
||||
* @prop {Object} [request.file] The file object sent in the request
|
||||
* @prop {Buffer} request.file.file A buffer containing file data
|
||||
* @prop {String} request.file.name The name of the file
|
||||
* @prop {Number} request.latency The HTTP response latency
|
||||
* @prop {String} request.method Uppercase HTTP method
|
||||
* @prop {IncomingMessage} request.resp The HTTP response to the request
|
||||
* @prop {String} request.route The calculated ratelimiting route for the request
|
||||
* @prop {Boolean} request.short Whether or not the request was prioritized in its ratelimiting queue
|
||||
* @prop {String} request.url URL of the endpoint
|
||||
*/
|
||||
this._client.emit("rawREST", {method, url, auth, body, file, route, short, resp, latency});
|
||||
}
|
||||
|
||||
const headerNow = Date.parse(resp.headers["date"]);
|
||||
if(this.latencyRef.lastTimeOffsetCheck < Date.now() - 5000) {
|
||||
const timeOffset = headerNow + 500 - (this.latencyRef.lastTimeOffsetCheck = Date.now());
|
||||
if(this.latencyRef.timeOffset - this.latencyRef.latency >= this.options.latencyThreshold && timeOffset - this.latencyRef.latency >= this.options.latencyThreshold) {
|
||||
this._client.emit("warn", new Error(`Your clock is ${this.latencyRef.timeOffset}ms behind Discord's server clock. Please check your connection and system time.`));
|
||||
}
|
||||
this.latencyRef.timeOffset = this.latencyRef.timeOffset - ~~(this.latencyRef.timeOffsets.shift() / 10) + ~~(timeOffset / 10);
|
||||
this.latencyRef.timeOffsets.push(timeOffset);
|
||||
}
|
||||
|
||||
resp.once("aborted", () => {
|
||||
cb();
|
||||
reqError = reqError || new Error(`Request aborted by server on ${method} ${url}`);
|
||||
reqError.req = req;
|
||||
reject(reqError);
|
||||
});
|
||||
|
||||
let response = "";
|
||||
|
||||
let _respStream = resp;
|
||||
if(resp.headers["content-encoding"]) {
|
||||
if(resp.headers["content-encoding"].includes("gzip")) {
|
||||
_respStream = resp.pipe(Zlib.createGunzip());
|
||||
} else if(resp.headers["content-encoding"].includes("deflate")) {
|
||||
_respStream = resp.pipe(Zlib.createInflate());
|
||||
}
|
||||
}
|
||||
|
||||
_respStream.on("data", (str) => {
|
||||
response += str;
|
||||
}).on("error", (err) => {
|
||||
reqError = err;
|
||||
req.abort();
|
||||
}).once("end", () => {
|
||||
const now = Date.now();
|
||||
|
||||
if(resp.headers["x-ratelimit-limit"]) {
|
||||
this.ratelimits[route].limit = +resp.headers["x-ratelimit-limit"];
|
||||
}
|
||||
|
||||
if(method !== "GET" && (resp.headers["x-ratelimit-remaining"] == undefined || resp.headers["x-ratelimit-limit"] == undefined) && this.ratelimits[route].limit !== 1) {
|
||||
this._client.emit("debug", `Missing ratelimit headers for SequentialBucket(${this.ratelimits[route].remaining}/${this.ratelimits[route].limit}) with non-default limit\n`
|
||||
+ `${resp.statusCode} ${resp.headers["content-type"]}: ${method} ${route} | ${resp.headers["cf-ray"]}\n`
|
||||
+ "content-type = " + + "\n"
|
||||
+ "x-ratelimit-remaining = " + resp.headers["x-ratelimit-remaining"] + "\n"
|
||||
+ "x-ratelimit-limit = " + resp.headers["x-ratelimit-limit"] + "\n"
|
||||
+ "x-ratelimit-reset = " + resp.headers["x-ratelimit-reset"] + "\n"
|
||||
+ "x-ratelimit-global = " + resp.headers["x-ratelimit-global"]);
|
||||
}
|
||||
|
||||
this.ratelimits[route].remaining = resp.headers["x-ratelimit-remaining"] === undefined ? 1 : +resp.headers["x-ratelimit-remaining"] || 0;
|
||||
|
||||
const retryAfter = Number(resp.headers["x-ratelimit-reset-after"] || resp.headers["retry-after"]) * 1000;
|
||||
if(retryAfter >= 0) {
|
||||
if(resp.headers["x-ratelimit-global"]) {
|
||||
this.globalBlock = true;
|
||||
setTimeout(() => this.globalUnblock(), retryAfter || 1);
|
||||
} else {
|
||||
this.ratelimits[route].reset = (retryAfter || 1) + now;
|
||||
}
|
||||
} else if(resp.headers["x-ratelimit-reset"]) {
|
||||
let resetTime = +resp.headers["x-ratelimit-reset"] * 1000;
|
||||
if(route.endsWith("/reactions/:id") && (+resp.headers["x-ratelimit-reset"] * 1000 - headerNow) === 1000) {
|
||||
resetTime = now + 250;
|
||||
}
|
||||
this.ratelimits[route].reset = Math.max(resetTime - this.latencyRef.latency, now);
|
||||
} else {
|
||||
this.ratelimits[route].reset = now;
|
||||
}
|
||||
|
||||
if(resp.statusCode !== 429) {
|
||||
const content = typeof body === "object" ? `${body.content} ` : "";
|
||||
this._client.emit("debug", `${content}${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - now}ms left)`);
|
||||
}
|
||||
|
||||
if(resp.statusCode >= 300) {
|
||||
if(resp.statusCode === 429) {
|
||||
const content = typeof body === "object" ? `${body.content} ` : "";
|
||||
let delay = retryAfter;
|
||||
if(resp.headers["x-ratelimit-scope"] === "shared") {
|
||||
try {
|
||||
delay = JSON.parse(response).retry_after * 1000;
|
||||
} catch(err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._client.emit("debug", `${resp.headers["x-ratelimit-global"] ? "Global" : "Unexpected"} 429 (╯°□°)╯︵ ┻━┻: ${response}\n${content} ${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${delay} (${this.ratelimits[route].reset - now}ms left) | Scope ${resp.headers["x-ratelimit-scope"]}`);
|
||||
if(delay) {
|
||||
setTimeout(() => {
|
||||
cb();
|
||||
this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
|
||||
}, delay);
|
||||
return;
|
||||
} else {
|
||||
cb();
|
||||
this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
|
||||
return;
|
||||
}
|
||||
} else if(resp.statusCode === 502 && ++attempts < 4) {
|
||||
this._client.emit("debug", "A wild 502 appeared! Thanks CloudFlare!");
|
||||
setTimeout(() => {
|
||||
this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
|
||||
}, Math.floor(Math.random() * 1900 + 100));
|
||||
return cb();
|
||||
}
|
||||
cb();
|
||||
|
||||
if(response.length > 0) {
|
||||
if(resp.headers["content-type"] === "application/json") {
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch(err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let {stack} = _stackHolder;
|
||||
if(stack.startsWith("Error\n")) {
|
||||
stack = stack.substring(6);
|
||||
}
|
||||
let err;
|
||||
if(response.code) {
|
||||
err = new DiscordRESTError(req, resp, response, stack);
|
||||
} else {
|
||||
err = new DiscordHTTPError(req, resp, response, stack);
|
||||
}
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if(response.length > 0) {
|
||||
if(resp.headers["content-type"] === "application/json") {
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch(err) {
|
||||
cb();
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cb();
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
|
||||
req.setTimeout(this.options.requestTimeout, () => {
|
||||
reqError = new Error(`Request timed out (>${this.options.requestTimeout}ms) on ${method} ${url}`);
|
||||
req.abort();
|
||||
});
|
||||
|
||||
if(Array.isArray(data)) {
|
||||
for(const chunk of data) {
|
||||
req.write(chunk);
|
||||
}
|
||||
req.end();
|
||||
} else {
|
||||
req.end(data);
|
||||
}
|
||||
};
|
||||
|
||||
if(this.globalBlock && auth) {
|
||||
this.readyQueue.push(() => {
|
||||
if(!this.ratelimits[route]) {
|
||||
this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
|
||||
}
|
||||
this.ratelimits[route].queue(actualCall, short);
|
||||
});
|
||||
} else {
|
||||
if(!this.ratelimits[route]) {
|
||||
this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
|
||||
}
|
||||
this.ratelimits[route].queue(actualCall, short);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
routefy(url, method) {
|
||||
let route = url.replace(/\/([a-z-]+)\/(?:[0-9]{17,19})/g, function(match, p) {
|
||||
return p === "channels" || p === "guilds" || p === "webhooks" ? match : `/${p}/:id`;
|
||||
}).replace(/\/reactions\/[^/]+/g, "/reactions/:id").replace(/\/reactions\/:id\/[^/]+/g, "/reactions/:id/:userID").replace(/^\/webhooks\/(\d+)\/[A-Za-z0-9-_]{64,}/, "/webhooks/$1/:token");
|
||||
if(method === "DELETE" && route.endsWith("/messages/:id")) {
|
||||
const messageID = url.slice(url.lastIndexOf("/") + 1);
|
||||
const createdAt = Base.getCreatedAt(messageID);
|
||||
if(Date.now() - this.latencyRef.latency - createdAt >= 1000 * 60 * 60 * 24 * 14) {
|
||||
method += "_OLD";
|
||||
} else if(Date.now() - this.latencyRef.latency - createdAt <= 1000 * 10) {
|
||||
method += "_NEW";
|
||||
}
|
||||
route = method + route;
|
||||
} else if(method === "GET" && /\/guilds\/[0-9]+\/channels$/.test(route)) {
|
||||
route = "/guilds/:id/channels";
|
||||
}
|
||||
if(method === "PUT" || method === "DELETE") {
|
||||
const index = route.indexOf("/reactions");
|
||||
if(index !== -1) {
|
||||
route = "MODIFY" + route.slice(0, index + 10);
|
||||
}
|
||||
}
|
||||
return route;
|
||||
}
|
||||
|
||||
[util.inspect.custom]() {
|
||||
return Base.prototype[util.inspect.custom].call(this);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "[RequestHandler]";
|
||||
}
|
||||
|
||||
toJSON(props = []) {
|
||||
return Base.prototype.toJSON.call(this, [
|
||||
"globalBlock",
|
||||
"latencyRef",
|
||||
"options",
|
||||
"ratelimits",
|
||||
"readyQueue",
|
||||
"userAgent",
|
||||
...props
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RequestHandler;
|
Reference in New Issue
Block a user