Skip to main content
Latest
File
import { delay } from "../mod.ts";
/** * A invalid request bucket is used in a similar manner as a leaky bucket but a invalid request bucket can be refilled as needed. * It's purpose is to make sure the bot does not hit the limit to getting a 1 hr ban. * * @param options The options used to configure this bucket. * @returns RefillingBucket */export function createInvalidRequestBucket(options: InvalidRequestBucketOptions): InvalidRequestBucket { const bucket: InvalidRequestBucket = { current: options.current ?? 0, max: options.max ?? 10000, interval: options.interval ?? 600000, timeoutId: options.timeoutId ?? 0, safety: options.safety ?? 1, frozenAt: options.frozenAt ?? 0, errorStatuses: options.errorStatuses ?? [401, 403, 429], requested: options.requested ?? 0, processing: false,
waiting: [],
requestsAllowed: function () { return bucket.max - bucket.current - bucket.requested - bucket.safety; },
isRequestAllowed: function () { return bucket.requestsAllowed() > 0; },
waitUntilRequestAvailable: async function () { return new Promise(async (resolve) => { // If whatever amount of requests is left is more than the safety margin, allow the request if (bucket.isRequestAllowed()) { bucket.requested++; resolve(); } else { bucket.waiting.push(resolve); await bucket.processWaiting(); } }); },
processWaiting: async function () { // If already processing, that loop will handle all waiting requests. if (bucket.processing) { return; }
// Mark as processing so other loops don't start bucket.processing = true;
while (bucket.waiting.length) { if (bucket.isRequestAllowed()) { bucket.requested++; // Resolve the next item in the queue bucket.waiting.shift()?.(); } else { await delay(1000); } }
// Mark as false so next pending request can be triggered by new loop. bucket.processing = false; },
handleCompletedRequest: function (code, sharedScope) { // Since request is complete, we can remove one from requested. bucket.requested--; // Since it is as a valid request, we don't need to do anything if (!bucket.errorStatuses.includes(code)) return; // Shared scope is not considered invalid if (code === 429 && sharedScope) return;
// INVALID REQUEST WAS MADE
// If it was not frozen before, mark it frozen if (!bucket.frozenAt) bucket.frozenAt = Date.now(); // Mark a request has been invalid bucket.current++; // If a timeout was not started, start a timeout to reset this bucket if (!bucket.timeoutId) { bucket.timeoutId = setTimeout(() => { bucket.frozenAt = 0; bucket.current = 0; bucket.timeoutId = 0; }, bucket.frozenAt + bucket.interval); } }, };
return bucket;}
export interface InvalidRequestBucketOptions { /** current invalid amount */ current?: number; /** max invalid requests allowed until ban. Defaults to 10,000 */ max?: number; /** The time that discord allows to make the max number of invalid requests. Defaults to 10 minutes */ interval?: number; /** timer to reset to 0 */ timeoutId?: number; /** how safe to be from max. Defaults to 1 */ safety?: number; /** when first request in this period was made */ frozenAt?: number; /** The request statuses that count as an invalid request. */ errorStatuses?: number[]; /** The amount of requests that were requested from this bucket. */ requested?: number;}
export interface InvalidRequestBucket { /** current invalid amount */ current: number; /** max invalid requests allowed until ban. Defaults to 10,000 */ max: number; /** The time that discord allows to make the max number of invalid requests. Defaults to 10 minutes */ interval: number; /** timer to reset to 0 */ timeoutId: number; /** how safe to be from max. Defaults to 1 */ safety: number; /** when first request in this period was made */ frozenAt: number; /** The request statuses that count as an invalid request. */ errorStatuses: number[]; /** The amount of requests that were requested from this bucket. */ requested: number; /** The requests that are currently pending. */ waiting: ((value: void | PromiseLike<void>) => void)[]; /** Whether or not the waiting queue is already processing. */ processing: boolean;
/** Gives the number of requests that are currently allowed. */ requestsAllowed: () => number; /** Checks if a request is allowed at this time. */ isRequestAllowed: () => boolean; /** Waits until a request is available */ waitUntilRequestAvailable: () => Promise<void>; /** Begins processing the waiting queue of requests. */ processWaiting: () => Promise<void>; /** Handler for whenever a request is validated. This should update the requested values or trigger any other necessary stuff. */ handleCompletedRequest: (code: number, sharedScope: boolean) => void;}