This commit is contained in:
Skillz4Killz
2022-06-17 21:20:12 +00:00
committed by GitHub
18 changed files with 410 additions and 223 deletions

19
bot.ts
View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -11,3 +11,4 @@ export * from "./restManager.ts";
export * from "./runMethod.ts";
export * from "./simplifyUrl.ts";
export * from "./convertRestError.ts";
export * from "./sendRequest.ts";

View File

@@ -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

View File

@@ -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";

View File

@@ -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>;

View File

@@ -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
View 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,
});
}
}

View File

@@ -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";

View 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,
};
}

View 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()),
};
}

View 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,
};
}

View File

@@ -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 {

View 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,
};
}

View 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";

View 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),
})),
};
}

View File

@@ -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[];