From 6eca60777cc077cbc0a0a81e10b355eec67d17b0 Mon Sep 17 00:00:00 2001 From: Skillz Date: Mon, 22 Feb 2021 12:01:32 -0500 Subject: [PATCH] itoh is love itoh is bae this is his stuff --- src/api/handlers/channel.ts | 16 ++++++++++------ src/rest/deps.ts | 2 +- src/rest/queue.ts | 36 ++++++++++++++++++++++++------------ src/rest/request.ts | 18 ++++++++++++++---- src/rest/request_manager.ts | 2 +- src/rest/server.ts | 3 +-- src/rest/types/cache.ts | 9 ++++++++- src/rest/types/server.ts | 2 ++ 8 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/api/handlers/channel.ts b/src/api/handlers/channel.ts index 3cf81ce6f..e22b410ca 100644 --- a/src/api/handlers/channel.ts +++ b/src/api/handlers/channel.ts @@ -127,9 +127,9 @@ export async function getPins(channelID: string) { return Promise.all(result.map((res) => structures.createMessage(res))); } -/** - * Trigger a typing indicator for the specified channel. Generally bots should **NOT** implement this route. - * However, if a bot is responding to a command and expects the computation to take a few seconds, +/** + * Trigger a typing indicator for the specified channel. Generally bots should **NOT** implement this route. + * However, if a bot is responding to a command and expects the computation to take a few seconds, * this endpoint may be called to let the user know that the bot is processing their message. */ export async function startTyping(channelID: string) { @@ -240,9 +240,13 @@ export async function sendMessage( replied_user: content.mentions.repliedUser, } : undefined, - message_reference: { - message_id: content.replyMessageID, - }, + ...(content.replyMessageID + ? { + message_reference: { + message_id: content.replyMessageID, + }, + } + : {}), }, ) as MessageCreateOptions; diff --git a/src/rest/deps.ts b/src/rest/deps.ts index 1bb43a371..53b351893 100644 --- a/src/rest/deps.ts +++ b/src/rest/deps.ts @@ -1 +1 @@ -export * from "https://deno.land/std@0.87.0/http/server.ts"; +export * from "https://deno.land/std@0.88.0/http/server.ts"; diff --git a/src/rest/queue.ts b/src/rest/queue.ts index 5b36dd8c6..fa95bca92 100644 --- a/src/rest/queue.ts +++ b/src/rest/queue.ts @@ -1,6 +1,7 @@ import { restCache } from "./cache.ts"; import { createRequestBody, processRequestHeaders } from "./request.ts"; import { HttpResponseCode } from "./types/mod.ts"; +import { delay } from "../util/utils.ts"; /** If the queue is not already processing, this will start processing the queue. */ export function startQueue() { @@ -12,19 +13,24 @@ export function startQueue() { } /** Processes the queue by looping over each path separately until the queues are empty. */ -export function processQueue() { +export async function processQueue() { while (restCache.processingQueue) { // FOR EVERY PATH WE WILL START ITS OWN LOOP. restCache.pathQueues.forEach(async (queue) => { + // MAKE SURE THIS QUEUE HAS NOT ALREADY STARTED + if (queue.processing) return; // EACH PATH IS UNIQUE LIMITER - while (queue.length) { + while (queue.requests.length) { // IF THE BOT IS GLOBALLY RATELIMITED TRY AGAIN - if (!restCache.globallyRateLimited) continue; + if (restCache.globallyRateLimited) continue; // SELECT THE FIRST ITEM FROM THIS QUEUE - const [queuedRequest] = queue; + const [queuedRequest] = queue.requests; // IF THIS DOESNT HAVE ANY ITEMS JUST CANCEL, THE CLEANER WILL REMOVE IT. if (!queuedRequest) return; + // MARK THIS QUEUE AS NOW BEING PROCESSED + queue.processing = true; + // IF THIS URL IS STILL RATE LIMITED, TRY AGAIN const urlResetIn = checkRateLimits(queuedRequest.payload.url); if (urlResetIn) continue; @@ -90,7 +96,7 @@ export function processQueue() { queuedRequest.request.respond( { status: response.status, body: JSON.stringify({ error }) }, ); - queue.shift(); + queue.requests.shift(); continue; } @@ -102,7 +108,6 @@ export function processQueue() { // CONVERT THE RESPONSE TO JSON const json = await response.json(); - // IF THE RESPONSE WAS RATE LIMITED, HANDLE ACCORDINGLY if ( json.retry_after || @@ -126,7 +131,7 @@ export function processQueue() { }, ); // REMOVE ITEM FROM QUEUE TO PREVENT RETRY - queue.shift(); + queue.requests.shift(); continue; } @@ -140,7 +145,7 @@ export function processQueue() { restCache.eventHandlers.fetchSuccess(queuedRequest.payload); // REMOVE FROM QUEUE - queue.shift(); + queue.requests.shift(); queuedRequest.request.respond( { status: 200, body: JSON.stringify(json) }, ); @@ -151,23 +156,30 @@ export function processQueue() { { status: 404, body: JSON.stringify({ error }) }, ); // REMOVE FROM QUEUE - queue.shift(); + queue.requests.shift(); } } + // MARK THE QUEUE AS NO LONGER PROCESSING + queue.processing = false; // ONCE QUEUE IS DONE, WE CAN TRY CLEANING UP cleanupQueues(); }); + + await delay(1000); } } /** Cleans up the queues by checking if there is nothing left and removing it. */ export function cleanupQueues() { - restCache.pathQueues.forEach((queue, key) => { - if (queue.length) return; + for (const [key, queue] of restCache.pathQueues) { + if (queue.requests.length) continue; // REMOVE IT FROM CACHE restCache.pathQueues.delete(key); - }); + } + + // NO QUEUE LEFT, DISABLE THE QUEUE + if (!restCache.pathQueues.size) restCache.processingQueue = false; } /** Check the rate limits for a url or a bucket. */ diff --git a/src/rest/request.ts b/src/rest/request.ts index 2ecd9854b..bc83a15a7 100644 --- a/src/rest/request.ts +++ b/src/rest/request.ts @@ -1,4 +1,4 @@ -import { USER_AGENT } from "../util/constants.ts"; +import { BASE_URL, USER_AGENT } from "../util/constants.ts"; import { restCache } from "./cache.ts"; import { ServerRequest } from "./deps.ts"; import { startQueue } from "./queue.ts"; @@ -20,6 +20,8 @@ export function processRequest( parts.shift(); // REMOVES THE VERSION NUMBER if (parts[0]?.startsWith("v")) parts.shift(); + // SET THE NEW REQUEST URL + payload.url = `${BASE_URL}/v${options.apiVersion || 8}/${parts.join("/")}`; // REMOVE THE MAJOR PARAM parts.shift(); @@ -28,19 +30,27 @@ export function processRequest( const queue = restCache.pathQueues.get(id); // IF THE QUEUE EXISTS JUST ADD THIS TO THE QUEUE if (queue) { - queue.push({ request, payload, options }); + queue.requests.push({ request, payload, options }); } else { // CREATES A NEW QUEUE - restCache.pathQueues.set(id, [{ request, payload, options }]); + restCache.pathQueues.set(id, { + processing: false, + requests: [{ + request, + payload, + options, + }], + }); } + console.log("starting queue"); startQueue(); } /** 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 } = { - Authorization: queuedRequest.options.token, + Authorization: `Bot ${queuedRequest.options.token}`, "User-Agent": USER_AGENT, }; diff --git a/src/rest/request_manager.ts b/src/rest/request_manager.ts index 071bf91c9..1641bab06 100644 --- a/src/rest/request_manager.ts +++ b/src/rest/request_manager.ts @@ -225,7 +225,7 @@ function runMethod( body: JSON.stringify({ url, method, - ...(body as Record || {}), + body: body || {}, }), headers: { authorization: restAuthorization, diff --git a/src/rest/server.ts b/src/rest/server.ts index 6cf90ff89..4dbbdf7a3 100644 --- a/src/rest/server.ts +++ b/src/rest/server.ts @@ -23,7 +23,6 @@ async function handlePayload( // INSTANTLY IGNORE ANY REQUESTS THAT DON'T HAVE THE SECRET AUTHORIZATION KEY const authorization = request.headers.get("authorization"); if (authorization !== options.authorization) return; - // READ BUFFER AFTER AUTH CHECK const buffer = await Deno.readAll(request.body); @@ -48,7 +47,7 @@ async function handlePayload( } // PROCESS THE REQUEST - await processRequest( + processRequest( request, { method: data.method, url: data.url, body: data.body, retryCount: 0 }, options, diff --git a/src/rest/types/cache.ts b/src/rest/types/cache.ts index 76b8d0cb0..829fd45a1 100644 --- a/src/rest/types/cache.ts +++ b/src/rest/types/cache.ts @@ -3,7 +3,7 @@ import { RestEventHandlers } from "./server.ts"; export interface RestCache { /** The queues that are currently needing to be executed. Key is the url path and the value is all the requests in this same path. Paths are mapped by MAJOR params. */ - pathQueues: Map; + pathQueues: Map; /** Whether or not the queues are currently processing. */ processingQueue: boolean; /** Whether or not this token has been globally rate limited. */ @@ -13,3 +13,10 @@ export interface RestCache { /** The event handlers are functions that run when something is happening internally. Users can customize this for analytics, debugging, logging or anything their heart desires. */ eventHandlers: RestEventHandlers; } + +export interface Queue { + /** Whether or not this queue is being processed */ + processing: boolean; + /** All the requests in this queue. */ + requests: QueuedRequest[]; +} diff --git a/src/rest/types/server.ts b/src/rest/types/server.ts index ab2304b65..bf3881160 100644 --- a/src/rest/types/server.ts +++ b/src/rest/types/server.ts @@ -9,6 +9,8 @@ export interface RestServerOptions { token: string; /** When a request is rate limited, how many times should it keep retrying the request. Recommended: 10 */ maxRetryCount: number; + /** The api version you would like to use */ + apiVersion?: number; } export interface RestEventHandlers {