More BigBot Fixes (#2233)

* Add `.DS_Store` to `.gitignore`

* Format and improve DX.

* Add extra logging and fix gateway workers.

* Deno fmt.

* Be more explicit in `.env.example`.

* Add a `watch-bot` task and format.

* Deno FMT

* Deno FMT (all `template/bigbot` files)

Co-authored-by: ITOH <to@itoh.at>
This commit is contained in:
Reboot-Codes
2022-05-25 13:33:49 -07:00
committed by GitHub
parent 2018919fe2
commit 0986fa9704
16 changed files with 225 additions and 100 deletions

5
.gitignore vendored
View File

@@ -5,4 +5,7 @@ debug.ts
# npm stuff
.npmignore
npm/
npm/
# MacOS is weird
.DS_Store

View File

@@ -1,17 +1,33 @@
# Get this from https://discord.com/developers/applications/${applicationId}/bot
DISCORD_TOKEN=
MAX_SHARDS=0
# Sharding setup. This is largely dependant on your system specs.
# For **development purposes** the defaults are fine.
MAX_SHARDS=1
FIRST_SHARD_ID=0
LAST_SHARD_ID=0
SHARDS_PER_CLUSTER=10
MAX_CLUSTERS=
URL_GATEWAY_PROXY_WILL_FORWARD_TO=
GATEWAY_SECRET_KEY=
REST_AUTHORIZATION_KEY=
REST_PORT=
GATEWAY_PORT=
EVENT_HANDLER_PORT=
EVENT_HANDLER_SECRET_KEY=
EVENT_HANDLER_URL=
MAX_CLUSTERS=10
# For the event handler process, change the key!
# (url is fine unless hosted on a different machine)
EVENT_HANDLER_PORT=1235
EVENT_HANDLER_SECRET_KEY=secreteventhandlerkey
EVENT_HANDLER_URL=localhost
# For the gateway process, change the key!
REST_PORT=1236
REST_AUTHORIZATION_KEY=secretrestkey
# For the gateway process, change the key!
# (url is fine unless hosted on a different machine)
GATEWAY_PORT=1237
GATEWAY_SECRET_KEY=secretgatewaykey
GATEWAY_PROXY_URL=localhost
# Change to false for production use
DEVELOPMENT=true
# Optional, but very helpful for development.
MISSING_TRANSLATION_WEBHOOK=
DEV_GUILD_ID=
DEV_GUILD_ID=

View File

@@ -6,8 +6,7 @@ This template is designed for bots that aim or are already in millions of Discor
## Setup
- Use the template generator button to make your own copy.
- Delete all the template folders except the `bigbot` folder.
- Clone this repository can move this directory into your desired project location.
- Move all files from the `bigbot` folder to the root of the project.
- You may encounter an issue with .vscode but force move the files to the root of the project. We have setup special
import maps in this template that should override the general .vscode folder already in the root folder.
@@ -17,12 +16,16 @@ This template is designed for bots that aim or are already in millions of Discor
## Usage
- Always run the `rest` process first. `deno task rest`
- Start the `bot` process next. `deno task bot`
- Lastly, start the `gateway` process. `deno task gateway`
> Note: Please install at least `deno@^1.22` on your system. (This is due to the requirement of the `Deno` namespace in
> workers for the `gateway` process.)
Note: The `gateway` process and `rest` are designed not to be shut off. So once those are on, the only thing you should
be doing is restarting your `bot` process.
- Always run the `rest` process first with `deno task rest`.
- Start the `bot` process next with `deno task bot`. (If you are developing a bot, use `deno task watch-bot` instead to
auto reload any changes. This won't restart any other processes, just your bot.)
- Lastly, start the `gateway` process with `deno task gateway`.
> Important: The `gateway` process and `rest` are designed not to be shut off. So once those are on, the only thing you
> should be doing is restarting your `bot` process. This saves API requests
## Details

View File

@@ -36,15 +36,15 @@ export const SHARDS_PER_CLUSTER = env.SHARDS_PER_CLUSTER ? parseInt(env.SHARDS_P
export const MAX_CLUSTERS = parseInt(env.MAX_CLUSTERS!, 10);
if (!MAX_CLUSTERS) {
throw new Error(
"Please for the love of god, tell me how many clusters your machine can handle!",
"How many clusters can you run on your machine (MAX_CLUSTERS)? Check your .env file!",
);
}
export const URL_GATEWAY_PROXY_WILL_FORWARD_TO = env
.URL_GATEWAY_PROXY_WILL_FORWARD_TO!;
if (!URL_GATEWAY_PROXY_WILL_FORWARD_TO) {
export const GATEWAY_PROXY_URL = env
.GATEWAY_PROXY_URL!;
if (!GATEWAY_PROXY_URL) {
throw new Error(
"Don't you think you need to give a URL where you want your gateway proxy to send events to?",
"Hmm, it seems like you don't have somewhere to send gateway events to (GATEWAY_PROXY_URL). Please check your .env file!",
);
}
@@ -52,34 +52,36 @@ export const EVENT_HANDLER_URL = env
.EVENT_HANDLER_URL!;
if (!EVENT_HANDLER_URL) {
throw new Error(
"Don't you think you need to give a URL where you want your events sent to?",
"Hmm, it seems like you don't have somewhere to send events to (EVENT_HANDLER_URL). Please check your .env file!",
);
}
export const GATEWAY_SECRET_KEY = env.GATEWAY_SECRET_KEY!;
if (!GATEWAY_SECRET_KEY) {
throw new Error(
"Do you want to be hacked? Add a secret authorization key that can be used to identify requests are from you.",
"You need to add a GATEWAY_SECRET_KEY to your .env file!",
);
}
export const REST_AUTHORIZATION_KEY = env.REST_AUTHORIZATION_KEY!;
if (!REST_AUTHORIZATION_KEY) {
throw new Error(
"Do you want to be hacked? Add a secret authorization key to make sure requests are only made by you.",
"You need to add a REST_AUTHORIZATION_KEY to your .env file!",
);
}
export const EVENT_HANDLER_SECRET_KEY = env.EVENT_HANDLER_SECRET_KEY!;
if (!EVENT_HANDLER_SECRET_KEY) {
throw new Error(
"Do you want to be hacked? Add a secret authorization key to make sure requests are only made by you.",
"You need to add an EVENT_HANDLER_SECRET_KEY to your .env file!",
);
}
export const BOT_ID = BigInt(atob(env.DISCORD_TOKEN.split(".")[0]));
if (!BOT_ID) {
throw new Error("Please enter the BOT ID you want to run this with.");
throw new Error(
"Hmm, it seems like you didn't put in a valid DISCORD_TOKEN. Check your .env file!",
);
}
export const REST_PORT = env.REST_PORT ? parseInt(env.REST_PORT, 10) : 5000;

View File

@@ -2,6 +2,7 @@
"tasks": {
"rest": "deno run -A --unstable --import-map ./importMap.json ./src/rest/mod.ts",
"bot": "deno run -A --unstable --import-map ./importMap.json ./src/bot/mod.ts",
"watch-bot": "deno run --watch -A --unstable --import-map ./importMap.json ./src/bot/mod.ts",
"gateway": "deno run -A --unstable --import-map ./importMap.json ./src/gateway/mod.ts"
}
}

View File

@@ -1,6 +1,8 @@
import log from "../../utils/logger.ts";
import { logger } from "../../utils/logger.ts";
import { decode, encode, Kwik, KwikTable } from "../../../deps.ts";
const log = logger({ name: "DB" });
log.info("Initializing KwikDB Database.");
interface CommandVersionsSchema {

View File

@@ -1,11 +1,17 @@
import { InteractionTypes, MessageComponentTypes } from "../../../../deps.ts";
import { bot } from "../../mod.ts";
import { executeSlashCommand } from "../interactions/executeSlashCommand.ts";
import { logger, LogLevels } from "../../../utils/logger.ts";
const log = logger({ name: "InteractionHandler" });
export function setInteractionCreateEvent() {
bot.events.interactionCreate = async function (_, interaction) {
log.info("Adding `bot.events.interactionCreate` handler.");
bot.events.interactionCreate = async (_, interaction) => {
log.debug("New event fired:\n", interaction);
// SLASH COMMAND
if (interaction.type === InteractionTypes.ApplicationCommand) {
log.debug("Slash Command Fired!");
return await executeSlashCommand(bot, interaction);
}
@@ -17,8 +23,10 @@ export function setInteractionCreateEvent() {
interaction.data.componentType ===
MessageComponentTypes.Button
) {
log.debug("Button Event!");
// processButtonCollectors(bot, interaction)
}
}
};
log.debug("All handlers:\n", bot.events);
}

View File

@@ -11,7 +11,6 @@ import {
sendPrivateInteractionResponse,
white,
} from "../../../../deps.ts";
import logger from "../../../../src/utils/logger.ts";
import { optionParser, translateOptionNames } from "../../../utils/options.ts";
import { privateReplyToInteraction, replyToInteraction } from "../../../utils/replies.ts";
import slashLogWebhook from "../../../utils/slashWebhook.ts";
@@ -19,6 +18,9 @@ import { BotClient } from "../../botClient.ts";
import { loadLanguage, serverLanguages, translate } from "../../languages/translate.ts";
import { Command, ConvertArgumentDefinitionsToArgs } from "../../types/command.ts";
import commands from "./mod.ts";
import { logger, LogLevels } from "../../../utils/logger.ts";
const log = logger({ name: "CommandHandler" });
function logCommand(
info: Interaction,
@@ -40,13 +42,15 @@ function logCommand(
black(`${info.guildId ? `Guild ID: (${info.guildId})` : "DM"}`),
);
logger.info(`${command} by ${user} in ${guild} with MessageID: ${info.id}`);
log.info(`${command} by ${user} in ${guild} with MessageID: ${info.id}`);
}
export async function executeSlashCommand(
bot: BotClient,
interaction: Interaction,
) {
log.debug(`New interaction:\n`, interaction);
const data = interaction.data;
const name = data?.name as keyof typeof commands;
@@ -55,17 +59,22 @@ export async function executeSlashCommand(
// Command could not be found
if (!command?.execute) {
return await sendPrivateInteractionResponse(bot, interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
content: translate(
bot,
interaction.guildId!,
"EXECUTE_COMMAND_NOT_FOUND",
),
return await sendPrivateInteractionResponse(
bot,
interaction.id,
interaction.token,
{
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
content: translate(
bot,
interaction.guildId!,
"EXECUTE_COMMAND_NOT_FOUND",
),
},
},
})
.catch(logger.error);
)
.catch(log.error);
}
// HAVE TO CONVERT OUTSIDE OF TRY SO IT CAN BE USED IN CATCH TOO
@@ -107,11 +116,11 @@ export async function executeSlashCommand(
);
logCommand(interaction, "Success", name);
} catch (error) {
console.error(error);
log.error(error);
logCommand(interaction, "Failure", name);
await slashLogWebhook(bot, interaction, name).catch(logger.error);
await slashLogWebhook(bot, interaction, name).catch(log.error);
return await privateReplyToInteraction(bot, interaction, {
content: translate(bot, interaction.id, "EXECUTE_COMMAND_ERROR"),
}).catch(logger.error);
}).catch(log.error);
}
}

View File

@@ -6,7 +6,10 @@ import { BotClient } from "../../../botClient.ts";
import { usesLatestCommandVersion } from "../../../database/commandVersion.ts";
import { commandVersions } from "../../../database/kwik.ts";
export async function setGuildCommands(bot: BotClient, data: DiscordGatewayPayload) {
export async function setGuildCommands(
bot: BotClient,
data: DiscordGatewayPayload,
) {
if (!data.t) return;
if (data.t === "GUILD_DELETE") {

View File

@@ -1,5 +1,9 @@
import { setInteractionCreateEvent } from "./handlers/interactionCreate.ts";
import { logger } from "../../utils/logger.ts";
const log = logger({ name: "EventHandlers" });
export function setupEventHandlers() {
log.debug("Adding Event Handlers!");
setInteractionCreateEvent();
}

View File

@@ -43,7 +43,7 @@ if (DEVELOPMENT) {
// Start listening on localhost.
const server = Deno.listen({ port: EVENT_HANDLER_PORT });
logger.info(
`HTTP webserver running. Access it at: http://localhost:${EVENT_HANDLER_PORT}/`,
`HTTP webserver running. Access it at: http://localhost:${EVENT_HANDLER_PORT}/`,
);
// Connections to the server will be yielded up as an async iterable.

View File

@@ -1,5 +1,8 @@
import { Collection, createGatewayManager, createRestManager, endpoints } from "../../deps.ts";
import { DISCORD_TOKEN, EVENT_HANDLER_SECRET_KEY, REST_AUTHORIZATION_KEY, REST_PORT } from "../../configs.ts";
import { logger } from "../utils/logger.ts";
const log = logger({ name: "Gateway" });
// CREATE A SIMPLE MANAGER FOR REST
const rest = createRestManager({
@@ -14,23 +17,24 @@ const gateway = createGatewayManager({
token: DISCORD_TOKEN,
intents: ["GuildMessages", "Guilds"],
// THIS WILL BASICALLY BE YOUR HANDLER FOR YOUR EVENTS.
handleDiscordPayload: async function (_, data, shardId) {},
handleDiscordPayload: async (_, data, shardId) => {},
});
const workers = new Collection<number, Worker>();
async function startGateway() {
// CALL THE REST PROCESS TO GET GATEWAY DATA
const result = await rest.runMethod(rest, "get", endpoints.GATEWAY_BOT()).then((res) => ({
url: res.url,
shards: res.shards,
sessionStartLimit: {
total: res.session_start_limit.total,
remaining: res.session_start_limit.remaining,
resetAfter: res.session_start_limit.reset_after,
maxConcurrency: res.session_start_limit.max_concurrency,
},
}));
const result = await rest.runMethod(rest, "get", endpoints.GATEWAY_BOT())
.then((res) => ({
url: res.url,
shards: res.shards,
sessionStartLimit: {
total: res.session_start_limit.total,
remaining: res.session_start_limit.remaining,
resetAfter: res.session_start_limit.reset_after,
maxConcurrency: res.session_start_limit.max_concurrency,
},
}));
// LOAD DATA FROM DISCORDS RECOMMENDATIONS OR YOUR OWN CUSTOM ONES HERE
gateway.shardsRecommended = result.shards;
@@ -44,7 +48,12 @@ async function startGateway() {
// PREPARE BUCKETS FOR IDENTIFYING
gateway.prepareBuckets(gateway, 0, result.shards);
function startWorker(workerId: number, bucketId: number, firstShardId: number, lastShardId: number) {
function startWorker(
workerId: number,
bucketId: number,
firstShardId: number,
lastShardId: number,
) {
const worker = workers.get(workerId);
if (!worker) return;
@@ -82,7 +91,14 @@ async function startGateway() {
const data = JSON.parse(message.data);
if (data.type === "ALL_SHARDS_READY") {
const queue = bucket.workers[i + 1];
if (queue) startWorker(queue[0], bucketId, queue[1], queue[queue.length - 1]);
if (queue) {
startWorker(
queue[0],
bucketId,
queue[1],
queue[queue.length - 1],
);
}
}
if (data.type === "RESHARDED") {
@@ -125,16 +141,17 @@ startGateway();
setInterval(async () => {
console.log("GW DEBUG", "[Resharding] Checking if resharding is needed.");
const results = await rest.runMethod(rest, "get", endpoints.GATEWAY_BOT()).then((res) => ({
url: res.url,
shards: res.shards,
sessionStartLimit: {
total: res.session_start_limit.total,
remaining: res.session_start_limit.remaining,
resetAfter: res.session_start_limit.reset_after,
maxConcurrency: res.session_start_limit.max_concurrency,
},
}));
const results = await rest.runMethod(rest, "get", endpoints.GATEWAY_BOT())
.then((res) => ({
url: res.url,
shards: res.shards,
sessionStartLimit: {
total: res.session_start_limit.total,
remaining: res.session_start_limit.remaining,
resetAfter: res.session_start_limit.reset_after,
maxConcurrency: res.session_start_limit.max_concurrency,
},
}));
const percentage = ((results.shards - gateway.maxShards) / gateway.maxShards) * 100;
// Less than necessary% being used so do nothing
if (percentage < gateway.reshardPercentage) return;

View File

@@ -1,5 +1,12 @@
import { DISCORD_TOKEN, EVENT_HANDLER_PORT, EVENT_HANDLER_SECRET_KEY, EVENT_HANDLER_URL } from "../../configs.ts";
import {
DEVELOPMENT,
DISCORD_TOKEN,
EVENT_HANDLER_PORT,
EVENT_HANDLER_SECRET_KEY,
EVENT_HANDLER_URL,
} from "../../configs.ts";
import { Collection, createGatewayManager, DiscordReady, GatewayManager, GetGatewayBot } from "../../deps.ts";
import { logger } from "../utils/logger.ts";
let gateway: GatewayManager;
// FOR RESHARDED
@@ -7,7 +14,14 @@ let gatewayPendingClosing: GatewayManager;
let workerId: number;
function spawnGateway(shardId: number, options: Partial<GatewayManager>) {
console.log(`[Worker #${workerId}]`, "[Worker] Spawning the worker gateway.", shardId, options);
const log = logger({
name: `GatewayWorker: ${workerId}`,
});
log.info(
`Spawning the worker gateway for shard #${shardId}\n`,
options,
);
gateway = createGatewayManager({
// LOAD DATA FROM DISCORDS RECOMMENDATIONS OR YOUR OWN CUSTOM ONES HERE
shardsRecommended: options.shardsRecommended,
@@ -28,13 +42,16 @@ function spawnGateway(shardId: number, options: Partial<GatewayManager>) {
// TRIGGER RAW EVENT
if (!data.t) return;
const id = (data.t && ["GUILD_CREATE", "GUILD_DELETE", "GUILD_UPDATE"].includes(data.t)
const id = (data.t &&
["GUILD_CREATE", "GUILD_DELETE", "GUILD_UPDATE"].includes(data.t)
? (data.d as any)?.id
: (data.d as any)?.guild_id) ?? "000000000000000000";
// IF FINAL SHARD BECAME READY TRIGGER NEXT WORKER
if (data.t === "READY") {
console.log(`[Worker #${workerId}]`, `[Worker] Shard #${shardId} online`);
log.info(
`Shard online`,
);
if (shardId === gateway.lastShardId) {
// @ts-ignore
@@ -47,11 +64,12 @@ function spawnGateway(shardId: number, options: Partial<GatewayManager>) {
}
// DONT SEND THESE EVENTS USELESS TO BOT
if (["GUILD_LOADED_DD"].includes(data.t)) {
return;
}
if (["GUILD_LOADED_DD"].includes(data.t)) return;
await fetch(`${EVENT_HANDLER_URL}:${EVENT_HANDLER_PORT}`, {
// Debug mode only
log.debug(`New Event:\n`, data);
await fetch(`http://${EVENT_HANDLER_URL}:${EVENT_HANDLER_PORT}`, {
headers: {
Authorization: gateway.secretKey,
"Content-Type": "application/json",
@@ -62,11 +80,11 @@ function spawnGateway(shardId: number, options: Partial<GatewayManager>) {
data,
}),
})
// BELOW IS FOR DENO MEMORY LEAK
.then((res) =>
res.text()
)
.catch(() => null);
.then((res) => {
// BELOW IS FOR DENO MEMORY LEAK
return res.text();
})
.catch((err) => log.error("Error Sending Event:\n", err));
},
});
@@ -107,7 +125,16 @@ interface FullyReshardedPayload {
// @ts-ignore this should not be erroring
self.onmessage = async function (message: MessageEvent<string>) {
const data = JSON.parse(message.data) as IdentifyPayload | ReshardPayload | FullyReshardedPayload;
const log = logger({
name: `GatewayWorker${JSON.parse(message.data).workerId ? `: ${JSON.parse(message.data).workerId}` : undefined}`,
});
log.debug(`New Message:\n`, message.data);
const data = JSON.parse(message.data) as
| IdentifyPayload
| ReshardPayload
| FullyReshardedPayload;
if (data.type === "IDENTIFY") {
workerId = data.workerId;
@@ -125,12 +152,12 @@ self.onmessage = async function (message: MessageEvent<string>) {
}
if (data.type === "RESHARDED-CLOSEOLD") {
console.log(`[Worker #${workerId}]`, "[Resharding] Closing old gateways.");
log.info("[Resharding] Closing old gateways.");
await gateway.resharding.closeOldShards(gatewayPendingClosing);
}
if (data.type === "RESHARD") {
console.log(`[Worker #${workerId}]`, "[Worker] Resharding the worker.");
log.info("[Worker] Resharding the worker.");
gateway.resharding.isPending = async function (gateway: GatewayManager) {
for (let i = gateway.firstShardId; i < gateway.lastShardId; i++) {
const shard = gateway.shards.get(i);
@@ -142,8 +169,14 @@ self.onmessage = async function (message: MessageEvent<string>) {
return false;
};
async function processResharding(oldGateway: GatewayManager, results: GetGatewayBot) {
oldGateway.debug("GW DEBUG", "[Resharding] Starting the reshard process.");
async function processResharding(
oldGateway: GatewayManager,
results: GetGatewayBot,
) {
oldGateway.debug(
"GW DEBUG",
"[Resharding] Starting the reshard process.",
);
const gateway = createGatewayManager({
...oldGateway,
@@ -164,7 +197,9 @@ self.onmessage = async function (message: MessageEvent<string>) {
gateway.handleDiscordPayload = async function (_, data, shardId) {
if (data.t === "READY") {
const payload = data.d as DiscordReady;
console.log(`[Worker - ${workerId}] Shard #${payload.shard?.[0]} online`);
log.info(
`Shard #${payload.shard?.[0]} online`,
);
if (shardId === gateway.lastShardId) {
// @ts-ignore
postMessage(
@@ -185,7 +220,12 @@ self.onmessage = async function (message: MessageEvent<string>) {
}
// DON"T OVERRIDE THESE
if (["cache", "shards", "loadingShards", "buckets", "utf8decoder"].includes(key)) continue;
if (
["cache", "shards", "loadingShards", "buckets", "utf8decoder"]
.includes(key)
) {
continue;
}
// USE ANY CUSTOMIZED OPTIONS FROM OLD GATEWAY
// @ts-ignore silly ts error
@@ -195,8 +235,13 @@ self.onmessage = async function (message: MessageEvent<string>) {
// Begin resharding
// If more than 100K servers, begin switching to 16x sharding
if (gateway.useOptimalLargeBotSharding) {
console.log(`[Worker - ${workerId}]`, "[Resharding] Using optimal large bot sharding solution.");
gateway.maxShards = gateway.calculateMaxShards(results.shards, results.sessionStartLimit.maxConcurrency);
log.info(
"[Resharding] Using optimal large bot sharding solution.",
);
gateway.maxShards = gateway.calculateMaxShards(
results.shards,
results.sessionStartLimit.maxConcurrency,
);
} else {
gateway.maxShards = results.shards;
}
@@ -216,7 +261,10 @@ self.onmessage = async function (message: MessageEvent<string>) {
return new Promise((resolve) => {
// TIMER TO KEEP CHECKING WHEN ALL SHARDS HAVE RESHARDED
const timer = setInterval(async () => {
const pending = await gateway.resharding.isPending(gateway, oldGateway);
const pending = await gateway.resharding.isPending(
gateway,
oldGateway,
);
// STILL PENDING ON SOME SHARDS TO BE CREATED
if (pending) return;
@@ -240,7 +288,7 @@ self.onmessage = async function (message: MessageEvent<string>) {
}
gateway = await processResharding(gateway, data.results);
console.log(`[Worker - ${workerId}] Resharded the worker.`);
log.info(`Resharded the worker.`);
// @ts-ignore this should not be erroring
postMessage(
JSON.stringify({

View File

@@ -1,7 +1,9 @@
// START FILE FOR REST PROCESS
import { DISCORD_TOKEN, REST_AUTHORIZATION_KEY, REST_PORT } from "../../configs.ts";
import { BASE_URL, createRestManager } from "../../deps.ts";
import { log } from "../utils/logger.ts";
import { logger } from "../utils/logger.ts";
const log = logger({ name: "REST" });
// CREATES THE FUNCTIONALITY FOR MANAGING THE REST REQUESTS
const rest = createRestManager({

View File

@@ -98,5 +98,4 @@ export function logger({
};
}
export const log = logger();
export default log;
export default logger();

View File

@@ -119,7 +119,11 @@ export async function updateGuildCommands(bot: BotClient, guildId: bigint) {
// ADVANCED VERSION WILL ALLOW TRANSLATION
const translatedName = translate(bot, guildId, command.name);
const translatedDescription = translate(bot, guildId, command.description);
const translatedDescription = translate(
bot,
guildId,
command.description,
);
return {
name: translatedName.toLowerCase(),
@@ -144,7 +148,11 @@ export async function updateGuildCommands(bot: BotClient, guildId: bigint) {
// ADVANCED VERSION WILL ALLOW TRANSLATION
const translatedName = translate(bot, guildId, command.name);
const translatedDescription = translate(bot, guildId, command.description);
const translatedDescription = translate(
bot,
guildId,
command.description,
);
return {
name: (translatedName || name).toLowerCase(),