fix: rest errors preventing startup

This commit is contained in:
Skillz4Killz
2021-04-08 14:14:30 +00:00
committed by GitHub
parent 2e397325f5
commit 3576d98510
10 changed files with 80 additions and 81 deletions
+10 -25
View File
@@ -2,6 +2,7 @@ import { getGatewayBot } from "./helpers/misc/get_gateway_bot.ts";
import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts"; import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts";
import { DiscordGetGatewayBot } from "./types/gateway/get_gateway_bot.ts"; import { DiscordGetGatewayBot } from "./types/gateway/get_gateway_bot.ts";
import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts"; import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts";
import { ws } from "./ws/ws.ts";
export let authorization = ""; export let authorization = "";
export let secretKey = ""; export let secretKey = "";
@@ -39,18 +40,14 @@ export async function startBot(config: BotConfig) {
proxyWSURL = botGatewayData.url; proxyWSURL = botGatewayData.url;
identifyPayload.token = config.token; identifyPayload.token = config.token;
identifyPayload.intents = config.intents.reduce( identifyPayload.intents = config.intents.reduce(
( (bits, next) =>
bits, (bits |= typeof next === "string" ? DiscordGatewayIntents[next] : next),
next, 0
) => (bits |= typeof next === "string"
? DiscordGatewayIntents[next]
: next),
0,
); );
lastShardId = botGatewayData.shards; lastShardId = botGatewayData.shards;
identifyPayload.shard = [0, lastShardId]; identifyPayload.shard = [0, lastShardId];
await spawnShards(botGatewayData, identifyPayload, 0, lastShardId); ws.spawnShards();
} }
/** Allows you to dynamically update the event handlers by passing in new eventHandlers */ /** Allows you to dynamically update the event handlers by passing in new eventHandlers */
@@ -78,7 +75,7 @@ export function setApplicationId(id: string) {
* Please be aware if you are a beginner developer using this, things will not work as per the guides. This is for advanced developers only! * Please be aware if you are a beginner developer using this, things will not work as per the guides. This is for advanced developers only!
* *
* Advanced Devs: This function will allow you to have an insane amount of customization potential as when you get to large bots you need to be able to optimize every tiny detail to make you bot work the way you need. * Advanced Devs: This function will allow you to have an insane amount of customization potential as when you get to large bots you need to be able to optimize every tiny detail to make you bot work the way you need.
*/ */
export async function startBigBrainBot(data: BigBrainBotConfig) { export async function startBigBrainBot(data: BigBrainBotConfig) {
authorization = `Bot ${data.token}`; authorization = `Bot ${data.token}`;
identifyPayload.token = `Bot ${data.token}`; identifyPayload.token = `Bot ${data.token}`;
@@ -92,13 +89,9 @@ export async function startBigBrainBot(data: BigBrainBotConfig) {
} }
identifyPayload.intents = data.intents.reduce( identifyPayload.intents = data.intents.reduce(
( (bits, next) =>
bits, (bits |= typeof next === "string" ? DiscordGatewayIntents[next] : next),
next, 0
) => (bits |= typeof next === "string"
? DiscordGatewayIntents[next]
: next),
0,
); );
// PROXY DOESNT NEED US SPAWNING SHARDS // PROXY DOESNT NEED US SPAWNING SHARDS
@@ -106,15 +99,7 @@ export async function startBigBrainBot(data: BigBrainBotConfig) {
// Initial API connection to get info about bots connection // Initial API connection to get info about bots connection
botGatewayData = await getGatewayBot(); botGatewayData = await getGatewayBot();
proxyWSURL = botGatewayData.url; proxyWSURL = botGatewayData.url;
await spawnShards( ws.spawnShards(data.firstShardId);
botGatewayData,
identifyPayload,
data.firstShardId,
data.lastShardId ||
(botGatewayData.shards >= 25
? (data.firstShardId + 25)
: botGatewayData.shards),
);
} }
} }
+3 -1
View File
@@ -1,3 +1,5 @@
import { USER_AGENT } from "../util/constants.ts";
/** Creates the request body and headers that are necessary to send a request. Will handle different types of methods and everything necessary for discord. */ /** Creates the request body and headers that are necessary to send a request. Will handle different types of methods and everything necessary for discord. */
export function createRequestBody(queuedRequest: QueuedRequest) { export function createRequestBody(queuedRequest: QueuedRequest) {
const headers: { [key: string]: string } = { const headers: { [key: string]: string } = {
@@ -6,7 +8,7 @@ export function createRequestBody(queuedRequest: QueuedRequest) {
}; };
// GET METHODS SHOULD NOT HAVE A BODY // GET METHODS SHOULD NOT HAVE A BODY
if (queuedRequest.request.method === "GET") { if (queuedRequest.request.method.toUpperCase() === "GET") {
queuedRequest.payload.body = undefined; queuedRequest.payload.body = undefined;
} }
+37 -28
View File
@@ -1,4 +1,5 @@
import { DiscordHTTPResponseCodes } from "../types/codes/http_response_codes.ts"; import { DiscordHTTPResponseCodes } from "../types/codes/http_response_codes.ts";
import { delay } from "../util/utils.ts";
import { rest } from "./rest.ts"; import { rest } from "./rest.ts";
/** Processes the queue by looping over each path separately until the queues are empty. */ /** Processes the queue by looping over each path separately until the queues are empty. */
@@ -36,13 +37,19 @@ export async function processQueue(id: string) {
// IF THIS IS A GET REQUEST, CHANGE THE BODY TO QUERY PARAMETERS // IF THIS IS A GET REQUEST, CHANGE THE BODY TO QUERY PARAMETERS
const query = const query =
queuedRequest.request.method === "GET" && queuedRequest.payload.body queuedRequest.request.method.toUpperCase() === "GET" &&
? Object.entries(queuedRequest.payload.body).map(([key, value]) => queuedRequest.payload.body
`${encodeURIComponent(key)}=${encodeURIComponent(value as string)}` ? Object.entries(queuedRequest.payload.body)
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(
value as string
)}`
) )
.join("&") .join("&")
: ""; : "";
const urlToUse = queuedRequest.request.method === "GET" && query const urlToUse =
queuedRequest.request.method.toUpperCase() === "GET" && query
? `${queuedRequest.request.url}?${query}` ? `${queuedRequest.request.url}?${query}`
: queuedRequest.request.url; : queuedRequest.request.url;
@@ -52,45 +59,49 @@ export async function processQueue(id: string) {
try { try {
const response = await fetch( const response = await fetch(
urlToUse, urlToUse,
rest.createRequestBody(queuedRequest), rest.createRequestBody(queuedRequest)
); );
rest.eventHandlers.fetched(queuedRequest.payload); rest.eventHandlers.fetched(queuedRequest.payload);
const bucketIdFromHeaders = rest.processRequestHeaders( const bucketIdFromHeaders = rest.processRequestHeaders(
queuedRequest.request.url, queuedRequest.request.url,
response.headers, response.headers
); );
if (response.status < 200 || response.status >= 400) { if (response.status < 200 || response.status >= 400) {
rest.eventHandlers.error( rest.eventHandlers.error("httpError", queuedRequest.payload, response);
"httpError",
queuedRequest.payload,
response,
);
let error = "REQUEST_UNKNOWN_ERROR"; let error = "REQUEST_UNKNOWN_ERROR";
switch (response.status) { switch (response.status) {
case DiscordHTTPResponseCodes.BadRequest: case DiscordHTTPResponseCodes.BadRequest:
error = error =
"The request was improperly formatted, or the server couldn't understand it."; "The request was improperly formatted, or the server couldn't understand it.";
break;
case DiscordHTTPResponseCodes.Unauthorized: case DiscordHTTPResponseCodes.Unauthorized:
error = "The Authorization header was missing or invalid."; error = "The Authorization header was missing or invalid.";
break;
case DiscordHTTPResponseCodes.Forbidden: case DiscordHTTPResponseCodes.Forbidden:
error = error =
"The Authorization token you passed did not have permission to the resource."; "The Authorization token you passed did not have permission to the resource.";
break;
case DiscordHTTPResponseCodes.NotFound: case DiscordHTTPResponseCodes.NotFound:
error = "The resource at the location specified doesn't exist."; error = "The resource at the location specified doesn't exist.";
break;
case DiscordHTTPResponseCodes.MethodNotAllowed: case DiscordHTTPResponseCodes.MethodNotAllowed:
error = error =
"The HTTP method used is not valid for the location specified."; "The HTTP method used is not valid for the location specified.";
break;
case DiscordHTTPResponseCodes.GatewayUnavailable: case DiscordHTTPResponseCodes.GatewayUnavailable:
error = error =
"There was not a gateway available to process your request. Wait a bit and retry."; "There was not a gateway available to process your request. Wait a bit and retry.";
break;
} }
queuedRequest.request.respond( queuedRequest.request.respond({
{ status: response.status, body: JSON.stringify({ error }) }, status: response.status,
); body: JSON.stringify({ error }),
});
queue.shift(); queue.shift();
continue; continue;
} }
@@ -115,17 +126,13 @@ export async function processQueue(id: string) {
queuedRequest.options.maxRetryCount queuedRequest.options.maxRetryCount
) { ) {
rest.eventHandlers.retriesMaxed(queuedRequest.payload); rest.eventHandlers.retriesMaxed(queuedRequest.payload);
queuedRequest.request.respond( queuedRequest.request.respond({
{
status: 200, status: 200,
body: JSON.stringify( body: JSON.stringify({
{
error: error:
"The request was rate limited and it maxed out the retries limit.", "The request was rate limited and it maxed out the retries limit.",
}, }),
), });
},
);
// REMOVE ITEM FROM QUEUE TO PREVENT RETRY // REMOVE ITEM FROM QUEUE TO PREVENT RETRY
queue.shift(); queue.shift();
continue; continue;
@@ -142,16 +149,18 @@ export async function processQueue(id: string) {
rest.eventHandlers.fetchSuccess(queuedRequest.payload); rest.eventHandlers.fetchSuccess(queuedRequest.payload);
// REMOVE FROM QUEUE // REMOVE FROM QUEUE
queue.shift(); queue.shift();
queuedRequest.request.respond( queuedRequest.request.respond({
{ status: 200, body: JSON.stringify(json) }, status: 200,
); body: JSON.stringify(json),
});
} }
} catch (error) { } catch (error) {
// SOMETHING WENT WRONG, LOG AND RESPOND WITH ERROR // SOMETHING WENT WRONG, LOG AND RESPOND WITH ERROR
rest.eventHandlers.fetchFailed(queuedRequest.payload, error); rest.eventHandlers.fetchFailed(queuedRequest.payload, error);
queuedRequest.request.respond( queuedRequest.request.respond({
{ status: 404, body: JSON.stringify({ error }) }, status: 404,
); body: JSON.stringify({ error }),
});
// REMOVE FROM QUEUE // REMOVE FROM QUEUE
queue.shift(); queue.shift();
} }
+12 -9
View File
@@ -1,8 +1,10 @@
import { BASE_URL } from "../util/constants.ts";
import { rest } from "./rest.ts";
/** Processes a request and assigns it to a queue or creates a queue if none exists for it. */ /** Processes a request and assigns it to a queue or creates a queue if none exists for it. */
export function processRequest( export function processRequest(
request: ServerRequest, request: ServerRequest,
payload: RunMethodOptions, payload: RunMethodOptions
options: RestServerOptions,
) { ) {
const route = request.url.substring(request.url.indexOf("api/")); const route = request.url.substring(request.url.indexOf("api/"));
const parts = route.split("/"); const parts = route.split("/");
@@ -11,23 +13,24 @@ export function processRequest(
// REMOVES THE VERSION NUMBER // REMOVES THE VERSION NUMBER
if (parts[0]?.startsWith("v")) parts.shift(); if (parts[0]?.startsWith("v")) parts.shift();
// SET THE NEW REQUEST URL // SET THE NEW REQUEST URL
request.url = `${BASE_URL}/v${options.apiVersion || 8}/${parts.join("/")}`; request.url = `${BASE_URL}/v${rest.apiVersion}/${parts.join("/")}`;
// REMOVE THE MAJOR PARAM // REMOVE THE MAJOR PARAM
parts.shift(); parts.shift();
const [id] = parts; const [id] = parts;
const queue = restCache.pathQueues.get(id); const queue = rest.pathQueues.get(id);
// IF THE QUEUE EXISTS JUST ADD THIS TO THE QUEUE // IF THE QUEUE EXISTS JUST ADD THIS TO THE QUEUE
if (queue) { if (queue) {
queue.push({ request, payload, options }); queue.push({ request, payload });
} else { } else {
// CREATES A NEW QUEUE // CREATES A NEW QUEUE
restCache.pathQueues.set(id, [{ rest.pathQueues.set(id, [
{
request, request,
payload, payload,
options, },
}]); ]);
processQueue(id); rest.processQueue(id);
} }
} }
+1
View File
@@ -9,6 +9,7 @@ import { processRequestHeaders } from "./process_request_headers.ts";
import { runMethod } from "./run_method.ts"; import { runMethod } from "./run_method.ts";
export const rest = { export const rest = {
apiVersion: "8",
/** The secret authorization key to confirm that this was a request made by you and not a DDOS attack. */ /** The secret authorization key to confirm that this was a request made by you and not a DDOS attack. */
authorization: "discordeno_best_lib_ever", authorization: "discordeno_best_lib_ever",
pathQueues: new Map(), pathQueues: new Map(),
+2 -7
View File
@@ -46,8 +46,7 @@ export function runMethod(
} }
// No proxy so we need to handle all rate limiting and such // No proxy so we need to handle all rate limiting and such
// deno-lint-ignore no-async-promise-executor return new Promise((resolve, reject) => {
return new Promise(async (resolve, reject) => {
const callback = async () => { const callback = async () => {
try { try {
const rateLimitResetIn = rest.checkRateLimits(url); const rateLimitResetIn = rest.checkRateLimits(url);
@@ -143,14 +142,10 @@ export function runMethod(
} }
}; };
rest.addToQueue({ rest.processRequest({ url, method, respond: (data: { status: number, body?: string; }) => resolve(JSON.parse(data.body || "{}")) }, {
callback, callback,
bucketId, bucketId,
url, url,
}); });
if (!rest.queueInProcess) {
rest.queueInProcess = true;
await rest.processQueue();
}
}); });
} }
+1 -1
View File
@@ -11,7 +11,7 @@ export async function createShard(shardId: number) {
ws.log("ERROR", { shardId, error: errorEvent }); ws.log("ERROR", { shardId, error: errorEvent });
}; };
socket.onmessage = ({ data: message }) => handleOnMessage(message, shardId); socket.onmessage = ({ data: message }) => ws.handleOnMessage(message, shardId);
socket.onclose = (event) => { socket.onclose = (event) => {
ws.log("CLOSED", { shardId, payload: event }); ws.log("CLOSED", { shardId, payload: event });
+1
View File
@@ -15,6 +15,7 @@ export async function identify(shardId: number, maxShards: number) {
sessionId: "", sessionId: "",
previousSequenceNumber: 0, previousSequenceNumber: 0,
resuming: false, resuming: false,
unavailableGuildIds: new Set(),
heartbeat: { heartbeat: {
lastSentAt: 0, lastSentAt: 0,
lastReceivedAt: 0, lastReceivedAt: 0,
+1
View File
@@ -28,6 +28,7 @@ export async function resume(shardId: number) {
sessionId, sessionId,
previousSequenceNumber, previousSequenceNumber,
resuming: false, resuming: false,
unavailableGuildIds: new Set(),
heartbeat: { heartbeat: {
lastSentAt: 0, lastSentAt: 0,
lastReceivedAt: 0, lastReceivedAt: 0,
+2
View File
@@ -116,6 +116,8 @@ export interface DiscordenoShard {
previousSequenceNumber: number | null; previousSequenceNumber: number | null;
/** Whether the shard is currently resuming. */ /** Whether the shard is currently resuming. */
resuming: boolean; resuming: boolean;
/** The list of guild ids that are currently unavailable due to an outage. */
unavailableGuildIds: Set<string>;
heartbeat: { heartbeat: {
/** The exact timestamp the last heartbeat was sent */ /** The exact timestamp the last heartbeat was sent */
lastSentAt: number; lastSentAt: number;