import { eventHandlers } from "../bot.ts"; 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. */ export async function processQueue(id: string) { const queue = rest.pathQueues.get(id); if (!queue) return; while (queue.length) { rest.eventHandlers.debug?.("loop", "Running while loop in processQueue function."); // IF THE BOT IS GLOBALLY RATELIMITED TRY AGAIN if (rest.globallyRateLimited) { setTimeout(async () => { eventHandlers.debug?.("loop", `Running setTimeout in processQueue function.`); await processQueue(id); }, 1000); break; } // SELECT THE FIRST ITEM FROM THIS QUEUE const [queuedRequest] = queue; // IF THIS DOESNT HAVE ANY ITEMS JUST CANCEL, THE CLEANER WILL REMOVE IT. if (!queuedRequest) return; const basicURL = rest.simplifyUrl(queuedRequest.request.url, queuedRequest.request.method.toUpperCase()); // IF THIS URL IS STILL RATE LIMITED, TRY AGAIN const urlResetIn = rest.checkRateLimits(basicURL); if (urlResetIn) { // PAUSE FOR THIS SPECIFC REQUEST await delay(urlResetIn); continue; } // IF A BUCKET EXISTS, CHECK THE BUCKET'S RATE LIMITS const bucketResetIn = queuedRequest.payload.bucketId ? rest.checkRateLimits(queuedRequest.payload.bucketId) : false; // THIS BUCKET IS STILL RATELIMITED, RE-ADD TO QUEUE if (bucketResetIn) continue; // EXECUTE THE REQUEST // IF THIS IS A GET REQUEST, CHANGE THE BODY TO QUERY PARAMETERS const query = queuedRequest.request.method.toUpperCase() === "GET" && queuedRequest.payload.body ? Object.keys(queuedRequest.payload.body) .map( (key) => `${encodeURIComponent(key)}=${encodeURIComponent( (queuedRequest.payload.body as Record)[key] )}` ) .join("&") : ""; 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); try { const response = await fetch(urlToUse, rest.createRequestBody(queuedRequest)); rest.eventHandlers.fetched(queuedRequest.payload); const bucketIdFromHeaders = rest.processRequestHeaders(basicURL, response.headers); // SET THE BUCKET Id IF IT WAS PRESENT if (bucketIdFromHeaders) { queuedRequest.payload.bucketId = bucketIdFromHeaders; } if (response.status < 200 || response.status >= 400) { 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; } // If Rate limited should not remove from queue if (response.status !== 429) { queuedRequest.request.reject(new Error(`[${response.status}] ${error}`)); queue.shift(); } else { if (queuedRequest.payload.retryCount++ >= rest.maxRetryCount) { rest.eventHandlers.retriesMaxed(queuedRequest.payload); queuedRequest.request.reject( new Error(`[${response.status}] The request was rate limited and it maxed out the retries limit.`) ); // REMOVE ITEM FROM QUEUE TO PREVENT RETRY queue.shift(); continue; } } continue; } // SOMETIMES DISCORD RETURNS AN EMPTY 204 RESPONSE THAT CAN'T BE MADE TO JSON if (response.status === 204) { rest.eventHandlers.fetchSuccess(queuedRequest.payload); // REMOVE FROM QUEUE queue.shift(); queuedRequest.request.respond({ status: 204 }); } else { // CONVERT THE RESPONSE TO JSON const json = await response.json(); // IF THE RESPONSE WAS RATE LIMITED, HANDLE ACCORDINGLY // if (json.retry_after || json.message === "You are being rate limited.") { // // IF IT HAS MAXED RETRIES SOMETHING SERIOUSLY WRONG. CANCEL OUT. // if (queuedRequest.payload.retryCount >= rest.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.", // }), // }); // // REMOVE ITEM FROM QUEUE TO PREVENT RETRY // queue.shift(); // continue; // } // // SINCE IT WAS RATELIMITE, RETRY AGAIN // continue; // } rest.eventHandlers.fetchSuccess(queuedRequest.payload); // REMOVE FROM QUEUE queue.shift(); 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.reject(error); // REMOVE FROM QUEUE queue.shift(); } } // ONCE QUEUE IS DONE, WE CAN TRY CLEANING UP rest.cleanupQueues(); }