mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-03 17:30:07 +00:00
Merge branch 'main' of https://github.com/discordeno/discordeno into main
This commit is contained in:
19
bot.ts
19
bot.ts
@@ -69,6 +69,7 @@ import { transformStageInstance } from "./transformers/stageInstance.ts";
|
||||
import { StickerPack, transformSticker, transformStickerPack } from "./transformers/sticker.ts";
|
||||
import { GetGatewayBot, transformGatewayBot } from "./transformers/gatewayBot.ts";
|
||||
import {
|
||||
DiscordAllowedMentions,
|
||||
DiscordApplicationCommandOptionChoice,
|
||||
DiscordAutoModerationActionExecution,
|
||||
DiscordAutoModerationRule,
|
||||
@@ -138,6 +139,10 @@ import {
|
||||
} from "./transformers/applicationCommandOptionChoice.ts";
|
||||
import { transformEmbedToDiscordEmbed } from "./transformers/reverse/embed.ts";
|
||||
import { transformComponentToDiscordComponent } from "./transformers/reverse/component.ts";
|
||||
import { transformActivityToDiscordActivity } from "./transformers/reverse/activity.ts";
|
||||
import { transformTeamToDiscordTeam } from "./transformers/reverse/team.ts";
|
||||
import { transformMemberToDiscordMember, transformUserToDiscordUser } from "./transformers/reverse/member.ts";
|
||||
import { transformApplicationToDiscordApplication } from "./transformers/reverse/application.ts";
|
||||
import { getBotIdFromToken, removeTokenPrefix } from "./util/token.ts";
|
||||
import { CreateShardManager } from "./gateway/manager/shardManager.ts";
|
||||
import { AutoModerationRule, transformAutoModerationRule } from "./transformers/automodRule.ts";
|
||||
@@ -146,6 +151,8 @@ import {
|
||||
transformAutoModerationActionExecution,
|
||||
} from "./transformers/automodActionExecution.ts";
|
||||
import { routes } from "./util/routes.ts";
|
||||
import { transformAllowedMentionsToDiscordAllowedMentions } from "./transformers/reverse/allowedMentions.ts";
|
||||
import { AllowedMentions } from "./mod.ts";
|
||||
|
||||
export function createBot(options: CreateBotOptions): Bot {
|
||||
const bot = {
|
||||
@@ -394,8 +401,14 @@ export function createBaseHelpers(options: Partial<Helpers>) {
|
||||
|
||||
export interface Transformers {
|
||||
reverse: {
|
||||
allowedMentions: (bot: Bot, payload: AllowedMentions) => DiscordAllowedMentions;
|
||||
embed: (bot: Bot, payload: Embed) => DiscordEmbed;
|
||||
component: (bot: Bot, payload: Component) => DiscordComponent;
|
||||
activity: (bot: Bot, payload: Activity) => DiscordActivity;
|
||||
member: (bot: Bot, payload: Member) => DiscordMember;
|
||||
user: (bot: Bot, payload: User) => DiscordUser;
|
||||
team: (bot: Bot, payload: Team) => DiscordTeam;
|
||||
application: (bot: Bot, payload: Application) => DiscordApplication;
|
||||
};
|
||||
snowflake: (snowflake: string) => bigint;
|
||||
gatewayBot: (payload: DiscordGetGatewayBot) => GetGatewayBot;
|
||||
@@ -447,8 +460,14 @@ export interface Transformers {
|
||||
export function createTransformers(options: Partial<Transformers>) {
|
||||
return {
|
||||
reverse: {
|
||||
allowedMentions: options.reverse?.allowedMentions || transformAllowedMentionsToDiscordAllowedMentions,
|
||||
embed: options.reverse?.embed || transformEmbedToDiscordEmbed,
|
||||
component: options.reverse?.component || transformComponentToDiscordComponent,
|
||||
activity: options.reverse?.activity || transformActivityToDiscordActivity,
|
||||
member: options.reverse?.member || transformMemberToDiscordMember,
|
||||
user: options.reverse?.user || transformUserToDiscordUser,
|
||||
team: options.reverse?.team || transformTeamToDiscordTeam,
|
||||
application: options.reverse?.application || transformApplicationToDiscordApplication,
|
||||
},
|
||||
automodRule: options.automodRule || transformAutoModerationRule,
|
||||
automodActionExecution: options.automodActionExecution || transformAutoModerationActionExecution,
|
||||
|
||||
@@ -23,99 +23,46 @@ export async function sendInteractionResponse(
|
||||
|
||||
// DRY code a little bit
|
||||
const data = {
|
||||
content: options.data.content,
|
||||
tts: options.data.tts,
|
||||
embeds: options.data.embeds?.map((embed) => bot.transformers.reverse.embed(bot, embed)),
|
||||
allowed_mentions: {
|
||||
parse: options.data.allowedMentions!.parse,
|
||||
replied_user: options.data.allowedMentions!.repliedUser,
|
||||
users: options.data.allowedMentions!.users?.map((id) => id.toString()),
|
||||
roles: options.data.allowedMentions!.roles?.map((id) => id.toString()),
|
||||
},
|
||||
custom_id: options.data.customId,
|
||||
title: options.data.title,
|
||||
components: options.data.components?.map((component) => ({
|
||||
type: component.type,
|
||||
components: component.components.map((subComponent) => {
|
||||
if (subComponent.type === MessageComponentTypes.InputText) {
|
||||
return {
|
||||
type: subComponent.type,
|
||||
style: subComponent.style,
|
||||
custom_id: subComponent.customId,
|
||||
label: subComponent.label,
|
||||
placeholder: subComponent.placeholder,
|
||||
value: subComponent.value,
|
||||
min_length: subComponent.minLength,
|
||||
max_length: subComponent.maxLength,
|
||||
required: subComponent.required,
|
||||
};
|
||||
}
|
||||
|
||||
if (subComponent.type === MessageComponentTypes.SelectMenu) {
|
||||
return {
|
||||
type: subComponent.type,
|
||||
custom_id: subComponent.customId,
|
||||
placeholder: subComponent.placeholder,
|
||||
min_values: subComponent.minValues,
|
||||
max_values: subComponent.maxValues,
|
||||
options: subComponent.options.map((option) => ({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
description: option.description,
|
||||
emoji: option.emoji
|
||||
? {
|
||||
id: option.emoji.id?.toString(),
|
||||
name: option.emoji.name,
|
||||
animated: option.emoji.animated,
|
||||
}
|
||||
: undefined,
|
||||
default: option.default,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: subComponent.type,
|
||||
custom_id: subComponent.customId,
|
||||
label: subComponent.label,
|
||||
style: subComponent.style,
|
||||
emoji: "emoji" in subComponent && subComponent.emoji
|
||||
? {
|
||||
id: subComponent.emoji.id?.toString(),
|
||||
name: subComponent.emoji.name,
|
||||
animated: subComponent.emoji.animated,
|
||||
}
|
||||
: undefined,
|
||||
url: "url" in subComponent ? subComponent.url : undefined,
|
||||
disabled: "disabled" in subComponent ? subComponent.disabled : undefined,
|
||||
};
|
||||
}),
|
||||
})),
|
||||
flags: options.data.flags,
|
||||
content: options.data.content,
|
||||
choices: options.data.choices,
|
||||
custom_id: options.data.customId,
|
||||
embeds: options.data.embeds?.map((embed) => bot.transformers.reverse.embed(bot, embed)),
|
||||
allowed_mentions: bot.transformers.reverse.allowedMentions(bot, options.data.allowedMentions!),
|
||||
components: options.data.components?.map((component) => bot.transformers.reverse.component(bot, component)),
|
||||
};
|
||||
|
||||
// A reply has never been send
|
||||
if (bot.cache.unrepliedInteractions.delete(id)) {
|
||||
return await bot.rest.runMethod<undefined>(
|
||||
bot.rest,
|
||||
"POST",
|
||||
bot.constants.routes.INTERACTION_ID_TOKEN(id, token),
|
||||
{
|
||||
type: options.type,
|
||||
data,
|
||||
file: options.data.file,
|
||||
},
|
||||
);
|
||||
return await bot.rest.sendRequest<undefined>(bot.rest, {
|
||||
url: bot.constants.routes.INTERACTION_ID_TOKEN(id, token),
|
||||
method: "POST",
|
||||
payload: bot.rest.createRequestBody(bot.rest, {
|
||||
method: "POST",
|
||||
body: { type: options.type, data, file: options.data.file },
|
||||
headers: {
|
||||
// remove authorization header
|
||||
Authorization: "",
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// If its already been executed, we need to send a followup response
|
||||
const result = await bot.rest.runMethod<DiscordMessage>(
|
||||
bot.rest,
|
||||
"POST",
|
||||
bot.constants.routes.WEBHOOK(bot.applicationId, token),
|
||||
{ ...data, file: options.data.file },
|
||||
);
|
||||
const result = await bot.rest.sendRequest<DiscordMessage>(bot.rest, {
|
||||
url: bot.constants.routes.WEBHOOK(bot.applicationId, token),
|
||||
method: "POST",
|
||||
payload: bot.rest.createRequestBody(bot.rest, {
|
||||
method: "POST",
|
||||
body: { ...data, file: options.data.file },
|
||||
headers: {
|
||||
// remove authorization header
|
||||
Authorization: "",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return bot.transformers.message(bot, result);
|
||||
}
|
||||
|
||||
@@ -1,58 +1,67 @@
|
||||
import { RestManager } from "./restManager.ts";
|
||||
import { FileContent } from "../types/discordeno.ts";
|
||||
import { USER_AGENT } from "../util/constants.ts";
|
||||
import { RestPayload, RestRequest } from "./rest.ts";
|
||||
import { RequestMethod, 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(rest: RestManager, queuedRequest: { request: RestRequest; payload: RestPayload }) {
|
||||
// export function createRequestBody(rest: RestManager, queuedRequest: { request: RestRequest; payload: RestPayload }) {
|
||||
export function createRequestBody(rest: RestManager, options: CreateRequestBodyOptions) {
|
||||
const headers: Record<string, string> = {
|
||||
authorization: `Bot ${rest.token}`,
|
||||
"user-agent": USER_AGENT,
|
||||
};
|
||||
|
||||
if (!options.unauthorized) headers["authorization"] = `Bot ${rest.token}`;
|
||||
|
||||
// SOMETIMES SPECIAL HEADERS (E.G. CUSTOM AUTHORIZATION) NEED TO BE USED
|
||||
if (queuedRequest.payload.headers) {
|
||||
for (const key in queuedRequest.payload.headers) {
|
||||
headers[key] = queuedRequest.payload.headers[key];
|
||||
if (options.headers) {
|
||||
for (const key in options.headers) {
|
||||
headers[key.toLowerCase()] = options.headers[key];
|
||||
}
|
||||
}
|
||||
|
||||
// GET METHODS SHOULD NOT HAVE A BODY
|
||||
if (queuedRequest.request.method === "GET") {
|
||||
queuedRequest.payload.body = undefined;
|
||||
if (options.method === "GET") {
|
||||
options.body = undefined;
|
||||
}
|
||||
|
||||
// IF A REASON IS PROVIDED ENCODE IT IN HEADERS
|
||||
if (queuedRequest.payload.body?.reason) {
|
||||
headers["X-Audit-Log-Reason"] = encodeURIComponent(queuedRequest.payload.body.reason as string);
|
||||
queuedRequest.payload.body.reason = undefined;
|
||||
if (options.body?.reason) {
|
||||
headers["X-Audit-Log-Reason"] = encodeURIComponent(options.body.reason as string);
|
||||
options.body.reason = undefined;
|
||||
}
|
||||
|
||||
// IF A FILE/ATTACHMENT IS PRESENT WE NEED SPECIAL HANDLING
|
||||
if (queuedRequest.payload.body?.file) {
|
||||
if (!Array.isArray(queuedRequest.payload.body.file)) {
|
||||
queuedRequest.payload.body.file = [queuedRequest.payload.body.file];
|
||||
if (options.body?.file) {
|
||||
if (!Array.isArray(options.body.file)) {
|
||||
options.body.file = [options.body.file];
|
||||
}
|
||||
|
||||
const form = new FormData();
|
||||
|
||||
for (let i = 0; i < (queuedRequest.payload.body.file as FileContent[]).length; i++) {
|
||||
for (let i = 0; i < (options.body.file as FileContent[]).length; i++) {
|
||||
form.append(
|
||||
`file${i}`,
|
||||
(queuedRequest.payload.body.file as FileContent[])[i].blob,
|
||||
(queuedRequest.payload.body.file as FileContent[])[i].name,
|
||||
(options.body.file as FileContent[])[i].blob,
|
||||
(options.body.file as FileContent[])[i].name,
|
||||
);
|
||||
}
|
||||
|
||||
form.append("payload_json", JSON.stringify({ ...queuedRequest.payload.body, file: undefined }));
|
||||
queuedRequest.payload.body.file = form;
|
||||
} else if (queuedRequest.payload.body && !["GET", "DELETE"].includes(queuedRequest.request.method)) {
|
||||
form.append("payload_json", JSON.stringify({ ...options.body, file: undefined }));
|
||||
options.body.file = form;
|
||||
} else if (options.body && !["GET", "DELETE"].includes(options.method)) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
body: (queuedRequest.payload.body?.file ?? JSON.stringify(queuedRequest.payload.body)) as FormData | string,
|
||||
method: queuedRequest.request.method,
|
||||
body: (options.body?.file ?? JSON.stringify(options.body)) as FormData | string,
|
||||
method: options.method,
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateRequestBodyOptions {
|
||||
headers?: Record<string, string>;
|
||||
method: RequestMethod;
|
||||
body?: Record<string, unknown>;
|
||||
unauthorized?: boolean;
|
||||
}
|
||||
|
||||
@@ -11,3 +11,4 @@ export * from "./restManager.ts";
|
||||
export * from "./runMethod.ts";
|
||||
export * from "./simplifyUrl.ts";
|
||||
export * from "./convertRestError.ts";
|
||||
export * from "./sendRequest.ts";
|
||||
|
||||
@@ -60,120 +60,20 @@ export async function processGlobalQueue(rest: RestManager) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// CUSTOM HANDLER FOR USER TO LOG OR WHATEVER WHENEVER A FETCH IS MADE
|
||||
rest.debug(`[REST - fetching] URL: ${request.urlToUse} | ${JSON.stringify(request.payload)}`);
|
||||
|
||||
const response = await fetch(request.urlToUse, rest.createRequestBody(rest, request));
|
||||
rest.debug(`[REST - fetched] URL: ${request.urlToUse} | ${JSON.stringify(request.payload)}`);
|
||||
|
||||
const bucketIdFromHeaders = rest.processRequestHeaders(rest, request.basicURL, response.headers);
|
||||
// SET THE BUCKET Id IF IT WAS PRESENT
|
||||
if (bucketIdFromHeaders) {
|
||||
request.payload.bucketId = bucketIdFromHeaders;
|
||||
}
|
||||
|
||||
if (response.status < 200 || response.status >= 400) {
|
||||
rest.debug(
|
||||
`[REST - httpError] Payload: ${JSON.stringify(request.payload)} | Response: ${JSON.stringify(response)}`,
|
||||
);
|
||||
|
||||
let error = "REQUEST_UNKNOWN_ERROR";
|
||||
switch (response.status) {
|
||||
case HTTPResponseCodes.BadRequest:
|
||||
error = "The request was improperly formatted, or the server couldn't understand it.";
|
||||
break;
|
||||
case HTTPResponseCodes.Unauthorized:
|
||||
error = "The Authorization header was missing or invalid.";
|
||||
break;
|
||||
case HTTPResponseCodes.Forbidden:
|
||||
error = "The Authorization token you passed did not have permission to the resource.";
|
||||
break;
|
||||
case HTTPResponseCodes.NotFound:
|
||||
error = "The resource at the location specified doesn't exist.";
|
||||
break;
|
||||
case HTTPResponseCodes.MethodNotAllowed:
|
||||
error = "The HTTP method used is not valid for the location specified.";
|
||||
break;
|
||||
case HTTPResponseCodes.GatewayUnavailable:
|
||||
error = "There was not a gateway available to process your request. Wait a bit and retry.";
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
rest.invalidRequestErrorStatuses.includes(response.status) &&
|
||||
!(response.status === 429 && response.headers.get("X-RateLimit-Scope"))
|
||||
) {
|
||||
// INCREMENT CURRENT INVALID REQUESTS
|
||||
++rest.invalidRequests;
|
||||
|
||||
if (!rest.invalidRequestsTimeoutId) {
|
||||
rest.invalidRequestsTimeoutId = setTimeout(() => {
|
||||
rest.debug(`[REST - processGlobalQueue] Resetting invalid requests counter in setTimeout.`);
|
||||
rest.invalidRequests = 0;
|
||||
rest.invalidRequestsTimeoutId = 0;
|
||||
}, rest.invalidRequestsInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// If NOT rate limited remove from queue
|
||||
if (response.status !== 429) {
|
||||
let json = undefined;
|
||||
if (response.type) {
|
||||
json = JSON.stringify(await response.json());
|
||||
}
|
||||
request.request.reject({
|
||||
ok: false,
|
||||
status: response.status,
|
||||
error,
|
||||
body: json,
|
||||
});
|
||||
} else {
|
||||
if (request.payload.retryCount++ >= rest.maxRetryCount) {
|
||||
rest.debug(`[REST - RetriesMaxed] ${JSON.stringify(request.payload)}`);
|
||||
// REMOVE ITEM FROM QUEUE TO PREVENT RETRY
|
||||
request.request.reject({
|
||||
ok: false,
|
||||
status: response.status,
|
||||
error: "The request was rate limited and it maxed out the retries limit.",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// WAS RATE LIMITED. PUSH TO END OF GLOBAL QUEUE, SO WE DON'T BLOCK OTHER REQUESTS.
|
||||
rest.globalQueue.push(request);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// SOMETIMES DISCORD RETURNS AN EMPTY 204 RESPONSE THAT CAN'T BE MADE TO JSON
|
||||
if (response.status === 204) {
|
||||
rest.debug(`[REST - FetchSuccess] URL: ${request.urlToUse} | ${JSON.stringify(request.payload)}`);
|
||||
request.request.respond({
|
||||
ok: true,
|
||||
status: 204,
|
||||
});
|
||||
} else {
|
||||
// CONVERT THE RESPONSE TO JSON
|
||||
const json = JSON.stringify(await response.json());
|
||||
|
||||
rest.debug(`[REST - fetchSuccess] ${JSON.stringify(request.payload)}`);
|
||||
request.request.respond({
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: json,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// SOMETHING WENT WRONG, LOG AND RESPOND WITH ERROR
|
||||
rest.debug(`[REST - fetchFailed] Payload: ${JSON.stringify(request.payload)} | Error: ${error}`);
|
||||
request.request.reject({
|
||||
ok: false,
|
||||
status: 599,
|
||||
error: "Internal Proxy Error",
|
||||
});
|
||||
}
|
||||
await rest.sendRequest(rest, {
|
||||
url: request.urlToUse,
|
||||
method: request.request.method,
|
||||
bucketId: request.payload.bucketId,
|
||||
reject: request.request.reject,
|
||||
respond: request.request.respond,
|
||||
retryCount: request.payload.retryCount ?? 0,
|
||||
payload: rest.createRequestBody(rest, {
|
||||
method: request.request.method,
|
||||
body: request.payload.body,
|
||||
}),
|
||||
})
|
||||
// Should be handled in sendRequest, this catch just prevents bots from dying
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
// ALLOW OTHER QUEUES TO START WHEN NEW REQUEST IS MADE
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export interface RestRequest {
|
||||
url: string;
|
||||
method: string;
|
||||
method: RequestMethod;
|
||||
respond: (payload: RestRequestResponse) => unknown;
|
||||
reject: (payload: RestRequestRejection) => unknown;
|
||||
}
|
||||
@@ -27,3 +27,5 @@ export interface RestRateLimitedPath {
|
||||
resetTimestamp: number;
|
||||
bucketId?: string;
|
||||
}
|
||||
|
||||
export type RequestMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
||||
|
||||
@@ -13,6 +13,7 @@ import { simplifyUrl } from "./simplifyUrl.ts";
|
||||
import { baseEndpoints } from "../util/constants.ts";
|
||||
import { API_VERSION } from "../util/constants.ts";
|
||||
import { removeTokenPrefix } from "../util/token.ts";
|
||||
import { sendRequest } from "./sendRequest.ts";
|
||||
|
||||
export function createRestManager(options: CreateRestManagerOptions) {
|
||||
const version = options.version || API_VERSION;
|
||||
@@ -73,6 +74,7 @@ export function createRestManager(options: CreateRestManagerOptions) {
|
||||
simplifyUrl: options.simplifyUrl || simplifyUrl,
|
||||
processGlobalQueue: options.processGlobalQueue || processGlobalQueue,
|
||||
convertRestError: options.convertRestError || convertRestError,
|
||||
sendRequest: options.sendRequest || sendRequest,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -94,6 +96,7 @@ export interface CreateRestManagerOptions {
|
||||
simplifyUrl?: typeof simplifyUrl;
|
||||
processGlobalQueue?: typeof processGlobalQueue;
|
||||
convertRestError?: typeof convertRestError;
|
||||
sendRequest?: typeof sendRequest;
|
||||
}
|
||||
|
||||
export type RestManager = ReturnType<typeof createRestManager>;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { RestManager } from "./restManager.ts";
|
||||
import { API_VERSION, BASE_URL, baseEndpoints } from "../util/constants.ts";
|
||||
import { RestRequestRejection, RestRequestResponse } from "./rest.ts";
|
||||
import { RequestMethod, RestRequestRejection, RestRequestResponse } from "./rest.ts";
|
||||
|
||||
export async function runMethod<T = any>(
|
||||
rest: RestManager,
|
||||
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
method: RequestMethod,
|
||||
route: string,
|
||||
body?: unknown,
|
||||
options?: {
|
||||
|
||||
155
rest/sendRequest.ts
Normal file
155
rest/sendRequest.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { HTTPResponseCodes } from "../types/shared.ts";
|
||||
import { BASE_URL } from "../util/constants.ts";
|
||||
import { RequestMethod } from "./rest.ts";
|
||||
import { RestManager } from "./restManager.ts";
|
||||
|
||||
export interface RestSendRequestOptions {
|
||||
url: string;
|
||||
method: RequestMethod;
|
||||
bucketId?: string;
|
||||
reject?: Function;
|
||||
respond?: Function;
|
||||
retryCount?: number;
|
||||
payload?: {
|
||||
headers: Record<string, string>;
|
||||
body: string | FormData;
|
||||
};
|
||||
}
|
||||
|
||||
export async function sendRequest<T>(rest: RestManager, options: RestSendRequestOptions): Promise<T> {
|
||||
try {
|
||||
// CUSTOM HANDLER FOR USER TO LOG OR WHATEVER WHENEVER A FETCH IS MADE
|
||||
rest.debug(`[REST - fetching] URL: ${options.url} | ${JSON.stringify(options)}`);
|
||||
|
||||
const response = await fetch(
|
||||
options.url.startsWith(BASE_URL) ? options.url : `${BASE_URL}/v${rest.version}/${options.url}`,
|
||||
{
|
||||
method: options.method,
|
||||
headers: options.payload?.headers,
|
||||
body: options.payload?.body,
|
||||
},
|
||||
);
|
||||
rest.debug(`[REST - fetched] URL: ${options.url} | ${JSON.stringify(options)}`);
|
||||
|
||||
const bucketIdFromHeaders = rest.processRequestHeaders(
|
||||
rest,
|
||||
rest.simplifyUrl(options.url, options.method),
|
||||
response.headers,
|
||||
);
|
||||
// SET THE BUCKET Id IF IT WAS PRESENT
|
||||
if (bucketIdFromHeaders) {
|
||||
options.bucketId = bucketIdFromHeaders;
|
||||
}
|
||||
|
||||
if (response.status < 200 || response.status >= 400) {
|
||||
rest.debug(
|
||||
`[REST - httpError] Payload: ${JSON.stringify(options)} | Response: ${JSON.stringify(response)}`,
|
||||
);
|
||||
|
||||
let error = "REQUEST_UNKNOWN_ERROR";
|
||||
switch (response.status) {
|
||||
case HTTPResponseCodes.BadRequest:
|
||||
error = "The options was improperly formatted, or the server couldn't understand it.";
|
||||
break;
|
||||
case HTTPResponseCodes.Unauthorized:
|
||||
error = "The Authorization header was missing or invalid.";
|
||||
break;
|
||||
case HTTPResponseCodes.Forbidden:
|
||||
error = "The Authorization token you passed did not have permission to the resource.";
|
||||
break;
|
||||
case HTTPResponseCodes.NotFound:
|
||||
error = "The resource at the location specified doesn't exist.";
|
||||
break;
|
||||
case HTTPResponseCodes.MethodNotAllowed:
|
||||
error = "The HTTP method used is not valid for the location specified.";
|
||||
break;
|
||||
case HTTPResponseCodes.GatewayUnavailable:
|
||||
error = "There was not a gateway available to process your options. Wait a bit and retry.";
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
rest.invalidRequestErrorStatuses.includes(response.status) &&
|
||||
!(response.status === 429 && response.headers.get("X-RateLimit-Scope"))
|
||||
) {
|
||||
// INCREMENT CURRENT INVALID REQUESTS
|
||||
++rest.invalidRequests;
|
||||
|
||||
if (!rest.invalidRequestsTimeoutId) {
|
||||
rest.invalidRequestsTimeoutId = setTimeout(() => {
|
||||
rest.debug(`[REST - processGlobalQueue] Resetting invalid optionss counter in setTimeout.`);
|
||||
rest.invalidRequests = 0;
|
||||
rest.invalidRequestsTimeoutId = 0;
|
||||
}, rest.invalidRequestsInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// If NOT rate limited remove from queue
|
||||
if (response.status !== 429) {
|
||||
options.reject?.({
|
||||
ok: false,
|
||||
status: response.status,
|
||||
error,
|
||||
body: response.type ? JSON.stringify(await response.json()) : undefined,
|
||||
});
|
||||
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
ok: false,
|
||||
status: response.status,
|
||||
error,
|
||||
body: response.type ? JSON.stringify(await response.json()) : undefined,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
if (options.retryCount && options.retryCount++ >= rest.maxRetryCount) {
|
||||
rest.debug(`[REST - RetriesMaxed] ${JSON.stringify(options)}`);
|
||||
// REMOVE ITEM FROM QUEUE TO PREVENT RETRY
|
||||
options.reject?.({
|
||||
ok: false,
|
||||
status: response.status,
|
||||
error: "The options was rate limited and it maxed out the retries limit.",
|
||||
});
|
||||
|
||||
// @ts-ignore Code should never reach here
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SOMETIMES DISCORD RETURNS AN EMPTY 204 RESPONSE THAT CAN'T BE MADE TO JSON
|
||||
if (response.status === 204) {
|
||||
rest.debug(`[REST - FetchSuccess] URL: ${options.url} | ${JSON.stringify(options)}`);
|
||||
options.respond?.({
|
||||
ok: true,
|
||||
status: 204,
|
||||
});
|
||||
// @ts-ignore 204 will be void
|
||||
return;
|
||||
} else {
|
||||
// CONVERT THE RESPONSE TO JSON
|
||||
const json = JSON.stringify(await response.json());
|
||||
|
||||
rest.debug(`[REST - fetchSuccess] ${JSON.stringify(options)}`);
|
||||
options.respond?.({
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: json,
|
||||
});
|
||||
|
||||
return JSON.parse(json);
|
||||
}
|
||||
} catch (error) {
|
||||
// SOMETHING WENT WRONG, LOG AND RESPOND WITH ERROR
|
||||
rest.debug(`[REST - fetchFailed] Payload: ${JSON.stringify(options)} | Error: ${error}`);
|
||||
options.reject?.({
|
||||
ok: false,
|
||||
status: 599,
|
||||
error: "Internal Proxy Error",
|
||||
});
|
||||
|
||||
throw new Error("Something went wrong in sendRequest", {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -32,3 +32,5 @@ export * from "./webhook.ts";
|
||||
export * from "./welcomeScreen.ts";
|
||||
export * from "./widget.ts";
|
||||
export * from "./widgetSettings.ts";
|
||||
|
||||
export * from "./reverse/mod.ts";
|
||||
|
||||
46
transformers/reverse/activity.ts
Normal file
46
transformers/reverse/activity.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Bot } from "../../bot.ts";
|
||||
import { DiscordActivity } from "../../types/discord.ts";
|
||||
import { Activity } from "../activity.ts";
|
||||
|
||||
export function transformActivityToDiscordActivity(bot: Bot, payload: Activity): DiscordActivity {
|
||||
return {
|
||||
name: payload.name,
|
||||
type: payload.type,
|
||||
url: payload.url ?? undefined,
|
||||
created_at: payload.createdAt,
|
||||
timestamps: {
|
||||
start: payload.startedAt,
|
||||
end: payload.endedAt,
|
||||
},
|
||||
application_id: payload.applicationId ? bot.utils.bigintToSnowflake(payload.applicationId) : undefined,
|
||||
details: payload.details ?? undefined,
|
||||
state: payload.state ?? undefined,
|
||||
emoji: payload.emoji
|
||||
? {
|
||||
name: payload.emoji.name,
|
||||
animated: payload.emoji.animated,
|
||||
id: payload.emoji.id ? bot.utils.bigintToSnowflake(payload.emoji.id) : undefined,
|
||||
}
|
||||
: undefined,
|
||||
party: {
|
||||
id: payload.partyId,
|
||||
size: payload.partyCurrentSize && payload.partyMaxSize
|
||||
? [payload.partyCurrentSize, payload.partyMaxSize]
|
||||
: undefined,
|
||||
},
|
||||
assets: {
|
||||
large_image: payload.largeImage,
|
||||
large_text: payload.largeText,
|
||||
small_image: payload.largeImage,
|
||||
small_text: payload.largeText,
|
||||
},
|
||||
secrets: {
|
||||
join: payload.join,
|
||||
spectate: payload.spectate,
|
||||
match: payload.match,
|
||||
},
|
||||
instance: payload.instance,
|
||||
flags: payload.flags,
|
||||
buttons: payload.buttons,
|
||||
};
|
||||
}
|
||||
13
transformers/reverse/allowedMentions.ts
Normal file
13
transformers/reverse/allowedMentions.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { AllowedMentions, Bot, DiscordAllowedMentions } from "../../mod.ts";
|
||||
|
||||
export function transformAllowedMentionsToDiscordAllowedMentions(
|
||||
bot: Bot,
|
||||
mentions: AllowedMentions,
|
||||
): DiscordAllowedMentions {
|
||||
return {
|
||||
parse: mentions.parse,
|
||||
replied_user: mentions.repliedUser,
|
||||
users: mentions.users?.map((id) => id.toString()),
|
||||
roles: mentions.roles?.map((id) => id.toString()),
|
||||
};
|
||||
}
|
||||
26
transformers/reverse/application.ts
Normal file
26
transformers/reverse/application.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Bot } from "../../bot.ts";
|
||||
import { DiscordApplication } from "../../types/discord.ts";
|
||||
import { Application } from "../application.ts";
|
||||
|
||||
export function transformApplicationToDiscordApplication(bot: Bot, payload: Application): DiscordApplication {
|
||||
return {
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
rpc_origins: payload.rpcOrigins,
|
||||
bot_public: payload.botPublic,
|
||||
bot_require_code_grant: payload.botRequireCodeGrant,
|
||||
terms_of_service_url: payload.termsOfServiceUrl,
|
||||
privacy_policy_url: payload.privacyPolicyUrl,
|
||||
verify_key: payload.verifyKey,
|
||||
primary_sku_id: payload.primarySkuId,
|
||||
slug: payload.slug,
|
||||
cover_image: payload.coverImage ? bot.utils.iconBigintToHash(payload.coverImage) : undefined,
|
||||
flags: payload.flags,
|
||||
|
||||
id: bot.utils.bigintToSnowflake(payload.id),
|
||||
icon: payload.icon ? bot.utils.iconBigintToHash(payload.icon) : null,
|
||||
owner: payload.owner ? bot.transformers.reverse.user(bot, payload.owner) : undefined,
|
||||
team: payload.team ? bot.transformers.reverse.team(bot, payload.team) : null,
|
||||
guild_id: payload.guildId ? bot.utils.bigintToSnowflake(payload.guildId) : undefined,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Bot } from "../../bot.ts";
|
||||
import { DiscordEmbed } from "../../types/discord.ts";
|
||||
import { Optionalize } from "../../types/shared.ts";
|
||||
import { Embed } from "../embed.ts";
|
||||
|
||||
export function transformEmbedToDiscordEmbed(bot: Bot, payload: Embed): DiscordEmbed {
|
||||
|
||||
38
transformers/reverse/member.ts
Normal file
38
transformers/reverse/member.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { Bot } from "../../bot.ts";
|
||||
import { DiscordMember, DiscordUser } from "../../types/discord.ts";
|
||||
import { Member, User } from "../member.ts";
|
||||
|
||||
export function transformUserToDiscordUser(bot: Bot, payload: User): DiscordUser {
|
||||
return {
|
||||
id: bot.utils.bigintToSnowflake(payload.id),
|
||||
username: payload.username,
|
||||
discriminator: payload.discriminator,
|
||||
avatar: payload.avatar ? bot.utils.iconBigintToHash(payload.avatar) : null,
|
||||
locale: payload.locale,
|
||||
email: payload.email ?? undefined,
|
||||
flags: payload.flags,
|
||||
premium_type: payload.premiumType,
|
||||
public_flags: payload.publicFlags,
|
||||
bot: payload.toggles.bot,
|
||||
system: payload.toggles.system,
|
||||
mfa_enabled: payload.toggles.mfaEnabled,
|
||||
verified: payload.toggles.verified,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformMemberToDiscordMember(bot: Bot, payload: Member): DiscordMember {
|
||||
return {
|
||||
nick: payload.nick ?? undefined,
|
||||
roles: payload.roles.map((id) => bot.utils.bigintToSnowflake(id)),
|
||||
joined_at: new Date(payload.joinedAt).toISOString(),
|
||||
premium_since: payload.premiumSince ? new Date(payload.premiumSince).toISOString() : undefined,
|
||||
avatar: payload.avatar ? bot.utils.iconBigintToHash(payload.avatar) : undefined,
|
||||
permissions: payload.permissions ? bot.utils.bigintToSnowflake(payload.permissions) : undefined,
|
||||
communication_disabled_until: payload.communicationDisabledUntil
|
||||
? new Date(payload.communicationDisabledUntil).toISOString()
|
||||
: undefined,
|
||||
deaf: payload.toggles.deaf,
|
||||
mute: payload.toggles.mute,
|
||||
pending: payload.toggles.pending,
|
||||
};
|
||||
}
|
||||
6
transformers/reverse/mod.ts
Normal file
6
transformers/reverse/mod.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from "./activity.ts";
|
||||
export * from "./application.ts";
|
||||
export * from "./component.ts";
|
||||
export * from "./embed.ts";
|
||||
export * from "./member.ts";
|
||||
export * from "./team.ts";
|
||||
21
transformers/reverse/team.ts
Normal file
21
transformers/reverse/team.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Bot } from "../../bot.ts";
|
||||
import { DiscordTeam } from "../../types/discord.ts";
|
||||
import { Team } from "../team.ts";
|
||||
|
||||
export function transformTeamToDiscordTeam(bot: Bot, payload: Team): DiscordTeam {
|
||||
const id = bot.utils.bigintToSnowflake(payload.id);
|
||||
|
||||
return {
|
||||
name: payload.name,
|
||||
|
||||
id,
|
||||
icon: payload.icon ? bot.utils.iconBigintToHash(payload.icon) : null,
|
||||
owner_user_id: bot.utils.bigintToSnowflake(payload.ownerUserId),
|
||||
members: payload.members.map((member) => ({
|
||||
membership_state: member.membershipState,
|
||||
permissions: member.permissions,
|
||||
team_id: id,
|
||||
user: bot.transformers.reverse.user(bot, member.user),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -308,7 +308,7 @@ export interface DiscordAllowedMentions {
|
||||
/** An array of allowed mention types to parse from the content. */
|
||||
parse?: AllowedMentionsTypes[];
|
||||
/** For replies, whether to mention the author of the message being replied to (default false) */
|
||||
repliedUser?: boolean;
|
||||
replied_user?: boolean;
|
||||
|
||||
/** Array of role_ids to mention (Max size of 100) */
|
||||
roles?: string[];
|
||||
|
||||
Reference in New Issue
Block a user