mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-02 00:40:07 +00:00
@@ -16,7 +16,7 @@ export async function sendDirectMessage(
|
||||
recipient_id: memberId,
|
||||
}) as DMChannelCreatePayload;
|
||||
const discordenoChannel = await structures.createDiscordenoChannel(
|
||||
dmChannelData as unknown as ChannelCreatePayload,
|
||||
dmChannelData as unknown as DiscordChannel,
|
||||
);
|
||||
// Recreate the channel and add it undert he users id
|
||||
await cacheHandlers.set("channels", memberId, discordenoChannel);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { FileContent } from "../types/misc/file_content.ts";
|
||||
import { USER_AGENT } from "../util/constants.ts";
|
||||
import { rest } from "./rest.ts";
|
||||
import { rest, RestPayload, RestRequest } from "./rest.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) {
|
||||
export function createRequestBody(queuedRequest: {
|
||||
request: RestRequest;
|
||||
payload: RestPayload;
|
||||
}) {
|
||||
const headers: { [key: string]: string } = {
|
||||
Authorization: rest.token,
|
||||
"User-Agent": USER_AGENT,
|
||||
@@ -16,7 +20,7 @@ export function createRequestBody(queuedRequest: QueuedRequest) {
|
||||
// IF A REASON IS PROVIDED ENCODE IT IN HEADERS
|
||||
if (queuedRequest.payload.body?.reason) {
|
||||
headers["X-Audit-Log-Reason"] = encodeURIComponent(
|
||||
queuedRequest.payload.body.reason,
|
||||
queuedRequest.payload.body.reason as string
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,12 +29,12 @@ export function createRequestBody(queuedRequest: QueuedRequest) {
|
||||
const form = new FormData();
|
||||
form.append(
|
||||
"file",
|
||||
queuedRequest.payload.body.file.blob,
|
||||
queuedRequest.payload.body.file.name,
|
||||
(queuedRequest.payload.body.file as FileContent).blob,
|
||||
(queuedRequest.payload.body.file as FileContent).name
|
||||
);
|
||||
form.append(
|
||||
"payload_json",
|
||||
JSON.stringify({ ...queuedRequest.payload.body, file: undefined }),
|
||||
JSON.stringify({ ...queuedRequest.payload.body, file: undefined })
|
||||
);
|
||||
queuedRequest.payload.body.file = form;
|
||||
} else if (
|
||||
@@ -42,8 +46,9 @@ export function createRequestBody(queuedRequest: QueuedRequest) {
|
||||
|
||||
return {
|
||||
headers,
|
||||
body: queuedRequest.payload.body?.file ||
|
||||
JSON.stringify(queuedRequest.payload.body),
|
||||
body:
|
||||
(queuedRequest.payload.body?.file ||
|
||||
JSON.stringify(queuedRequest.payload.body)) as FormData | string,
|
||||
method: queuedRequest.request.method.toUpperCase(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// SERVERLESS REST CLIENT THAT CAN WORK ACROSS SHARDS/WORKERS TO COMMUNICATE GLOBAL RATE LIMITS EASILY
|
||||
import { rest } from "./rest.ts";
|
||||
|
||||
/** Handler function for every request. Converts to json, verified authorization & requirements and begins processing the request */
|
||||
export async function handlePayload(
|
||||
request: Request,
|
||||
) {
|
||||
// INSTANTLY IGNORE ANY REQUESTS THAT DON'T HAVE THE SECRET AUTHORIZATION KEY
|
||||
const authorization = request.headers.get("authorization");
|
||||
if (authorization !== rest.authorization) return;
|
||||
// READ BUFFER AFTER AUTH CHECK
|
||||
const buffer = await Deno.readAll(request.body);
|
||||
try {
|
||||
// CONVERT THE BODY TO JSON
|
||||
const data = JSON.parse(new TextDecoder().decode(buffer));
|
||||
if (
|
||||
!["GET", "POST", "PUT", "PATCH", "HEAD", "DELETE"].includes(
|
||||
request.method,
|
||||
)
|
||||
) {
|
||||
return request.respond(
|
||||
{
|
||||
status: 400,
|
||||
body: JSON.stringify({ error: "Invalid METHOD." }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// PROCESS THE REQUEST
|
||||
await rest.processRequest(request, { body: data, retryCount: 0 });
|
||||
} catch (error) {
|
||||
rest.eventHandlers.error("serverRequest", error);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
export * from "./check_rate_limits.ts";
|
||||
export * from "./cleanup_queues.ts";
|
||||
export * from "./create_request_body.ts";
|
||||
export * from "./handle_payload.ts";
|
||||
export * from "./process_queue.ts";
|
||||
export * from "./process_rate_limited_paths.ts";
|
||||
export * from "./process_request.ts";
|
||||
|
||||
@@ -30,7 +30,6 @@ export async function processQueue(id: string) {
|
||||
// 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()
|
||||
@@ -42,8 +41,9 @@ export async function processQueue(id: string) {
|
||||
// PAUSE FOR THIS SPECIFC REQUEST
|
||||
await delay(urlResetIn);
|
||||
continue;
|
||||
} // IF A BUCKET EXISTS, CHECK THE BUCKET'S RATE LIMITS
|
||||
}
|
||||
|
||||
// IF A BUCKET EXISTS, CHECK THE BUCKET'S RATE LIMITS
|
||||
const bucketResetIn = queuedRequest.payload.bucketId
|
||||
? rest.checkRateLimits(queuedRequest.payload.bucketId)
|
||||
: false;
|
||||
@@ -172,7 +172,7 @@ export async function processQueue(id: string) {
|
||||
} catch (error) {
|
||||
// SOMETHING WENT WRONG, LOG AND RESPOND WITH ERROR
|
||||
rest.eventHandlers.fetchFailed(queuedRequest.payload, error);
|
||||
queuedRequest.request.reject(error);
|
||||
queuedRequest.request.reject?.(error);
|
||||
queuedRequest.request.respond({
|
||||
status: 404,
|
||||
body: JSON.stringify({ error }),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BASE_URL } from "../util/constants.ts";
|
||||
import { rest } from "./rest.ts";
|
||||
import { rest, RestPayload, RestRequest } from "./rest.ts";
|
||||
|
||||
/** Processes a request and assigns it to a queue or creates a queue if none exists for it. */
|
||||
export async function processRequest(
|
||||
request: ServerRequest,
|
||||
payload: RunMethodOptions
|
||||
request: RestRequest,
|
||||
payload: RestPayload
|
||||
) {
|
||||
const route = request.url.substring(request.url.indexOf("api/"));
|
||||
const parts = route.split("/");
|
||||
|
||||
@@ -9,7 +9,8 @@ export function processRequestHeaders(url: string, headers: Headers) {
|
||||
const retryAfter = headers.get("x-ratelimit-reset-after");
|
||||
const reset = Date.now() + Number(retryAfter) * 1000;
|
||||
const global = headers.get("x-ratelimit-global");
|
||||
const bucketId = headers.get("x-ratelimit-bucket");
|
||||
// undefined override null needed for typings
|
||||
const bucketId = headers.get("x-ratelimit-bucket") || undefined;
|
||||
|
||||
// IF THERE IS NO REMAINING RATE LIMIT, MARK IT AS RATE LIMITED
|
||||
if (remaining === "0") {
|
||||
@@ -55,7 +56,7 @@ export function processRequestHeaders(url: string, headers: Headers) {
|
||||
}
|
||||
}
|
||||
|
||||
if (ratelimited && !rest.processingRateLimitedPaths) {
|
||||
if (ratelimited && rest.processingRateLimitedPaths) {
|
||||
rest.processRateLimitedPaths();
|
||||
}
|
||||
return ratelimited ? bucketId : undefined;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { checkRateLimits } from "./check_rate_limits.ts";
|
||||
import { cleanupQueues } from "./cleanup_queues.ts";
|
||||
import { createRequestBody } from "./create_request_body.ts";
|
||||
import { handlePayload } from "./handle_payload.ts";
|
||||
import { processQueue } from "./process_queue.ts";
|
||||
import { processRateLimitedPaths } from "./process_rate_limited_paths.ts";
|
||||
import { processRequest } from "./process_request.ts";
|
||||
@@ -17,25 +16,30 @@ 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(),
|
||||
pathQueues: new Map<
|
||||
string,
|
||||
{
|
||||
request: RestRequest;
|
||||
payload: RestPayload;
|
||||
}[]
|
||||
>(),
|
||||
processingQueue: false,
|
||||
processingRateLimitedPaths: false,
|
||||
globallyRateLimited: false,
|
||||
ratelimitedPaths: new Map(),
|
||||
ratelimitedPaths: new Map<string, RestRateLimitedPath>(),
|
||||
eventHandlers: {
|
||||
// BY DEFAULT WE WILL LOG ALL ERRORS TO CONSOLE. USER CAN CHOOSE TO OVERRIDE
|
||||
error: function (...args: unknown[]) {},
|
||||
// PLACEHOLDERS TO ALLOW USERS TO CUSTOMIZE
|
||||
debug: function (type: string, error: string | Record<string, unknown>) {},
|
||||
fetching(payload: Record<string, unknown>) {},
|
||||
fetched(payload: Record<string, unknown>) {},
|
||||
fetchSuccess(payload: Record<string, unknown>) {},
|
||||
fetchFailed(payload: Record<string, unknown>, error: any) {},
|
||||
fetching(payload: RestPayload) {},
|
||||
fetched(payload: RestPayload) {},
|
||||
fetchSuccess(payload: RestPayload) {},
|
||||
fetchFailed(payload: RestPayload, error: unknown) {},
|
||||
globallyRateLimited(url: string, resetsAt: number) {},
|
||||
retriesMaxed(payload: Record<string, unknown>) {},
|
||||
retriesMaxed(payload: RestPayload) {},
|
||||
},
|
||||
/** Handler function for every request. Converts to json, verified authorization & requirements and begins processing the request */
|
||||
handlePayload,
|
||||
checkRateLimits,
|
||||
cleanupQueues,
|
||||
processQueue,
|
||||
@@ -46,3 +50,22 @@ export const rest = {
|
||||
runMethod,
|
||||
simplifyUrl,
|
||||
};
|
||||
|
||||
export interface RestRequest {
|
||||
url: string;
|
||||
method: string;
|
||||
respond: (payload: { status: number; body?: string }) => unknown;
|
||||
reject?: (error: unknown) => unknown;
|
||||
}
|
||||
|
||||
export interface RestPayload {
|
||||
bucketId?: string;
|
||||
body?: Record<string, unknown>;
|
||||
retryCount: number;
|
||||
}
|
||||
|
||||
export interface RestRateLimitedPath {
|
||||
url: string;
|
||||
resetTimestamp: number;
|
||||
bucketId?: string;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { API_VERSION, BASE_URL, IMAGE_BASE_URL } from "../util/constants.ts";
|
||||
import { rest } from "./rest.ts";
|
||||
|
||||
export function runMethod<T = any>(
|
||||
export async function runMethod(
|
||||
method: "get" | "post" | "put" | "delete" | "patch",
|
||||
url: string,
|
||||
body?: unknown,
|
||||
retryCount = 0,
|
||||
bucketId?: string | null,
|
||||
): Promise<T | undefined> {
|
||||
bucketId?: string
|
||||
) {
|
||||
rest.eventHandlers.debug?.("requestCreate", {
|
||||
method,
|
||||
url,
|
||||
@@ -24,22 +24,18 @@ export function runMethod<T = any>(
|
||||
!url.startsWith(`${BASE_URL}/v${API_VERSION}`) &&
|
||||
!url.startsWith(IMAGE_BASE_URL)
|
||||
) {
|
||||
return fetch(url, {
|
||||
const result = await fetch(url, {
|
||||
body: JSON.stringify(body || {}),
|
||||
headers: {
|
||||
authorization: rest.authorization,
|
||||
},
|
||||
method: method.toUpperCase(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.status === 204) return undefined;
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
throw errorStack;
|
||||
});
|
||||
|
||||
return (res.json() as unknown) as T;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
throw errorStack;
|
||||
});
|
||||
return result.status !== 204 ? await result.json() : undefined;
|
||||
}
|
||||
|
||||
// No proxy so we need to handle all rate limiting and such
|
||||
@@ -54,11 +50,9 @@ export function runMethod<T = any>(
|
||||
},
|
||||
{
|
||||
bucketId,
|
||||
url,
|
||||
method,
|
||||
body,
|
||||
body: body as Record<string, unknown> | undefined,
|
||||
retryCount,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user