diff --git a/src/bot.ts b/src/bot.ts index 10643dc2c..181001c21 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -2,6 +2,7 @@ import { getGatewayBot } from "./helpers/misc/get_gateway_bot.ts"; import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts"; import { DiscordGetGatewayBot } from "./types/gateway/get_gateway_bot.ts"; import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts"; +import { ws } from "./ws/ws.ts"; export let authorization = ""; export let secretKey = ""; @@ -39,18 +40,14 @@ export async function startBot(config: BotConfig) { proxyWSURL = botGatewayData.url; identifyPayload.token = config.token; identifyPayload.intents = config.intents.reduce( - ( - bits, - next, - ) => (bits |= typeof next === "string" - ? DiscordGatewayIntents[next] - : next), - 0, + (bits, next) => + (bits |= typeof next === "string" ? DiscordGatewayIntents[next] : next), + 0 ); lastShardId = botGatewayData.shards; 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 */ @@ -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! * * 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) { authorization = `Bot ${data.token}`; identifyPayload.token = `Bot ${data.token}`; @@ -92,13 +89,9 @@ export async function startBigBrainBot(data: BigBrainBotConfig) { } identifyPayload.intents = data.intents.reduce( - ( - bits, - next, - ) => (bits |= typeof next === "string" - ? DiscordGatewayIntents[next] - : next), - 0, + (bits, next) => + (bits |= typeof next === "string" ? DiscordGatewayIntents[next] : next), + 0 ); // 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 botGatewayData = await getGatewayBot(); proxyWSURL = botGatewayData.url; - await spawnShards( - botGatewayData, - identifyPayload, - data.firstShardId, - data.lastShardId || - (botGatewayData.shards >= 25 - ? (data.firstShardId + 25) - : botGatewayData.shards), - ); + ws.spawnShards(data.firstShardId); } } diff --git a/src/rest/create_request_body.ts b/src/rest/create_request_body.ts index 07c1effa7..c4d49cc72 100644 --- a/src/rest/create_request_body.ts +++ b/src/rest/create_request_body.ts @@ -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. */ export function createRequestBody(queuedRequest: QueuedRequest) { const headers: { [key: string]: string } = { @@ -6,7 +8,7 @@ export function createRequestBody(queuedRequest: QueuedRequest) { }; // GET METHODS SHOULD NOT HAVE A BODY - if (queuedRequest.request.method === "GET") { + if (queuedRequest.request.method.toUpperCase() === "GET") { queuedRequest.payload.body = undefined; } diff --git a/src/rest/process_queue.ts b/src/rest/process_queue.ts index ba7b67e23..b9539da1c 100644 --- a/src/rest/process_queue.ts +++ b/src/rest/process_queue.ts @@ -1,4 +1,5 @@ import { DiscordHTTPResponseCodes } from "../types/codes/http_response_codes.ts"; +import { delay } from "../util/utils.ts"; import { rest } from "./rest.ts"; /** Processes the queue by looping over each path separately until the queues are empty. */ @@ -36,15 +37,21 @@ export async function processQueue(id: string) { // IF THIS IS A GET REQUEST, CHANGE THE BODY TO QUERY PARAMETERS const query = - queuedRequest.request.method === "GET" && queuedRequest.payload.body - ? Object.entries(queuedRequest.payload.body).map(([key, value]) => - `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}` - ) - .join("&") + queuedRequest.request.method.toUpperCase() === "GET" && + queuedRequest.payload.body + ? Object.entries(queuedRequest.payload.body) + .map( + ([key, value]) => + `${encodeURIComponent(key)}=${encodeURIComponent( + value as string + )}` + ) + .join("&") : ""; - const urlToUse = queuedRequest.request.method === "GET" && query - ? `${queuedRequest.request.url}?${query}` - : queuedRequest.request.url; + const urlToUse = + queuedRequest.request.method.toUpperCase() === "GET" && query + ? `${queuedRequest.request.url}?${query}` + : queuedRequest.request.url; // CUSTOM HANDLER FOR USER TO LOG OR WHATEVER WHENEVER A FETCH IS MADE rest.eventHandlers.fetching(queuedRequest.payload); @@ -52,45 +59,49 @@ export async function processQueue(id: string) { try { const response = await fetch( urlToUse, - rest.createRequestBody(queuedRequest), + rest.createRequestBody(queuedRequest) ); rest.eventHandlers.fetched(queuedRequest.payload); const bucketIdFromHeaders = rest.processRequestHeaders( queuedRequest.request.url, - response.headers, + response.headers ); if (response.status < 200 || response.status >= 400) { - rest.eventHandlers.error( - "httpError", - queuedRequest.payload, - response, - ); + rest.eventHandlers.error("httpError", queuedRequest.payload, response); let error = "REQUEST_UNKNOWN_ERROR"; switch (response.status) { case DiscordHTTPResponseCodes.BadRequest: error = "The request was improperly formatted, or the server couldn't understand it."; + break; case DiscordHTTPResponseCodes.Unauthorized: error = "The Authorization header was missing or invalid."; + break; case DiscordHTTPResponseCodes.Forbidden: error = "The Authorization token you passed did not have permission to the resource."; + break; case DiscordHTTPResponseCodes.NotFound: error = "The resource at the location specified doesn't exist."; + break; case DiscordHTTPResponseCodes.MethodNotAllowed: error = "The HTTP method used is not valid for the location specified."; + break; case DiscordHTTPResponseCodes.GatewayUnavailable: error = "There was not a gateway available to process your request. Wait a bit and retry."; + break; } - queuedRequest.request.respond( - { status: response.status, body: JSON.stringify({ error }) }, - ); + queuedRequest.request.respond({ + status: response.status, + body: JSON.stringify({ error }), + }); + queue.shift(); continue; } @@ -112,20 +123,16 @@ export async function processQueue(id: string) { // IF IT HAS MAXED RETRIES SOMETHING SERIOUSLY WRONG. CANCEL OUT. if ( queuedRequest.payload.retryCount >= - queuedRequest.options.maxRetryCount + queuedRequest.options.maxRetryCount ) { rest.eventHandlers.retriesMaxed(queuedRequest.payload); - queuedRequest.request.respond( - { - status: 200, - body: JSON.stringify( - { - error: - "The request was rate limited and it maxed out the retries limit.", - }, - ), - }, - ); + queuedRequest.request.respond({ + status: 200, + body: JSON.stringify({ + error: + "The request was rate limited and it maxed out the retries limit.", + }), + }); // REMOVE ITEM FROM QUEUE TO PREVENT RETRY queue.shift(); continue; @@ -142,16 +149,18 @@ export async function processQueue(id: string) { rest.eventHandlers.fetchSuccess(queuedRequest.payload); // REMOVE FROM QUEUE queue.shift(); - queuedRequest.request.respond( - { status: 200, body: JSON.stringify(json) }, - ); + queuedRequest.request.respond({ + status: 200, + body: JSON.stringify(json), + }); } } catch (error) { // SOMETHING WENT WRONG, LOG AND RESPOND WITH ERROR rest.eventHandlers.fetchFailed(queuedRequest.payload, error); - queuedRequest.request.respond( - { status: 404, body: JSON.stringify({ error }) }, - ); + queuedRequest.request.respond({ + status: 404, + body: JSON.stringify({ error }), + }); // REMOVE FROM QUEUE queue.shift(); } diff --git a/src/rest/process_request.ts b/src/rest/process_request.ts index 0e8350c34..a2a023ee0 100644 --- a/src/rest/process_request.ts +++ b/src/rest/process_request.ts @@ -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. */ export function processRequest( request: ServerRequest, - payload: RunMethodOptions, - options: RestServerOptions, + payload: RunMethodOptions ) { const route = request.url.substring(request.url.indexOf("api/")); const parts = route.split("/"); @@ -11,23 +13,24 @@ export function processRequest( // REMOVES THE VERSION NUMBER if (parts[0]?.startsWith("v")) parts.shift(); // 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 parts.shift(); 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 (queue) { - queue.push({ request, payload, options }); + queue.push({ request, payload }); } else { // CREATES A NEW QUEUE - restCache.pathQueues.set(id, [{ - request, - payload, - options, - }]); - processQueue(id); + rest.pathQueues.set(id, [ + { + request, + payload, + }, + ]); + rest.processQueue(id); } } diff --git a/src/rest/rest.ts b/src/rest/rest.ts index b38fb3865..8cc3130ca 100644 --- a/src/rest/rest.ts +++ b/src/rest/rest.ts @@ -9,6 +9,7 @@ import { processRequestHeaders } from "./process_request_headers.ts"; import { runMethod } from "./run_method.ts"; export const rest = { + apiVersion: "8", /** The secret authorization key to confirm that this was a request made by you and not a DDOS attack. */ authorization: "discordeno_best_lib_ever", pathQueues: new Map(), diff --git a/src/rest/run_method.ts b/src/rest/run_method.ts index 6c3780b4c..9a32a83f4 100644 --- a/src/rest/run_method.ts +++ b/src/rest/run_method.ts @@ -46,8 +46,7 @@ export function runMethod( } // No proxy so we need to handle all rate limiting and such - // deno-lint-ignore no-async-promise-executor - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { const callback = async () => { try { 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, bucketId, url, }); - if (!rest.queueInProcess) { - rest.queueInProcess = true; - await rest.processQueue(); - } }); } diff --git a/src/ws/create_shard.ts b/src/ws/create_shard.ts index a2c8b7f24..8909efdcd 100644 --- a/src/ws/create_shard.ts +++ b/src/ws/create_shard.ts @@ -11,7 +11,7 @@ export async function createShard(shardId: number) { ws.log("ERROR", { shardId, error: errorEvent }); }; - socket.onmessage = ({ data: message }) => handleOnMessage(message, shardId); + socket.onmessage = ({ data: message }) => ws.handleOnMessage(message, shardId); socket.onclose = (event) => { ws.log("CLOSED", { shardId, payload: event }); diff --git a/src/ws/identify.ts b/src/ws/identify.ts index c75adf540..b3e32f599 100644 --- a/src/ws/identify.ts +++ b/src/ws/identify.ts @@ -15,6 +15,7 @@ export async function identify(shardId: number, maxShards: number) { sessionId: "", previousSequenceNumber: 0, resuming: false, + unavailableGuildIds: new Set(), heartbeat: { lastSentAt: 0, lastReceivedAt: 0, diff --git a/src/ws/resume.ts b/src/ws/resume.ts index 215042c3c..46211b595 100644 --- a/src/ws/resume.ts +++ b/src/ws/resume.ts @@ -28,6 +28,7 @@ export async function resume(shardId: number) { sessionId, previousSequenceNumber, resuming: false, + unavailableGuildIds: new Set(), heartbeat: { lastSentAt: 0, lastReceivedAt: 0, diff --git a/src/ws/ws.ts b/src/ws/ws.ts index d1f6123ef..5e4b62911 100644 --- a/src/ws/ws.ts +++ b/src/ws/ws.ts @@ -116,6 +116,8 @@ export interface DiscordenoShard { previousSequenceNumber: number | null; /** Whether the shard is currently resuming. */ resuming: boolean; + /** The list of guild ids that are currently unavailable due to an outage. */ + unavailableGuildIds: Set; heartbeat: { /** The exact timestamp the last heartbeat was sent */ lastSentAt: number;