diff --git a/template/bigbot/src/analytics.ts b/template/bigbot/src/analytics.ts index b45d5c9e1..af9dbb8d0 100644 --- a/template/bigbot/src/analytics.ts +++ b/template/bigbot/src/analytics.ts @@ -1,5 +1,5 @@ import { InfluxDB } from "@influxdata/influxdb-client"; import { INFLUX_BUCKET, INFLUX_ORG, INFLUX_TOKEN, INFLUX_URL } from "./configs.js"; -export const influxDB = new InfluxDB({ url: INFLUX_URL, token: INFLUX_TOKEN }); -export const Influx = influxDB.getWriteApi(INFLUX_ORG, INFLUX_BUCKET); +export const influxDB = INFLUX_URL && INFLUX_TOKEN ? new InfluxDB({ url: INFLUX_URL, token: INFLUX_TOKEN }) : undefined; +export const Influx = influxDB?.getWriteApi(INFLUX_ORG, INFLUX_BUCKET); diff --git a/template/bigbot/src/bot/bot.ts b/template/bigbot/src/bot/bot.ts index 94b817ff0..f32264e9c 100644 --- a/template/bigbot/src/bot/bot.ts +++ b/template/bigbot/src/bot/bot.ts @@ -2,14 +2,17 @@ import { Bot, Collection, createBot, createRestManager } from "discordeno"; import enableHelpersPlugin from "discordeno/helpers-plugin"; import { createLogger } from "discordeno/logger"; import { DISCORD_TOKEN, INTENTS, REST_AUTHORIZATION, REST_URL } from "../configs.js"; +import { setupEventHandlers } from "./events/mod.js"; import { MessageCollector } from "./utils/collectors.js"; import { customizeInternals } from "./utils/internals/mod.js"; export const bot = enableHelpersPlugin( - customizeBot(createBot({ - token: DISCORD_TOKEN, - intents: INTENTS, - })), + customizeBot( + createBot({ + token: DISCORD_TOKEN, + intents: INTENTS, + }), + ), ); /** Add custom props to your `bot` here */ @@ -42,6 +45,9 @@ export type BotWithCustomProps = B & { // Example of how to customize internal discordeno stuff easily. customizeInternals(bot); +// Setup event handlers. +setupEventHandlers(); + bot.rest = createRestManager({ token: DISCORD_TOKEN, secretKey: REST_AUTHORIZATION, diff --git a/template/bigbot/src/bot/commands/mod.ts b/template/bigbot/src/bot/commands/mod.ts index 356b277ff..557bbfd61 100644 --- a/template/bigbot/src/bot/commands/mod.ts +++ b/template/bigbot/src/bot/commands/mod.ts @@ -1,6 +1,8 @@ +import language from "./language.js"; import ping from "./ping.js"; export const COMMANDS = { + language, ping, }; diff --git a/template/bigbot/src/bot/commands/ping.ts b/template/bigbot/src/bot/commands/ping.ts index ce2e6c856..b1856aeec 100644 --- a/template/bigbot/src/bot/commands/ping.ts +++ b/template/bigbot/src/bot/commands/ping.ts @@ -5,11 +5,9 @@ export default createCommand({ name: "PING_NAME", description: "PING_DESCRIPTION", execute: async function (_, interaction) { - return await interaction.reply(translate( - interaction.guildId!, - "PING_RESPONSE_WITH_TIME", - Date.now() - snowflakeToTimestamp(interaction.id), - )); + return await interaction.reply( + translate(interaction.guildId!, "PING_RESPONSE_WITH_TIME", Date.now() - snowflakeToTimestamp(interaction.id)), + ); }, }); diff --git a/template/bigbot/src/bot/events/interactions/command.ts b/template/bigbot/src/bot/events/interactions/command.ts index 17151995e..253dd19dd 100644 --- a/template/bigbot/src/bot/events/interactions/command.ts +++ b/template/bigbot/src/bot/events/interactions/command.ts @@ -14,7 +14,7 @@ import { } from "discordeno"; import { bot, BotWithCustomProps } from "../../bot.js"; import COMMANDS from "../../commands/mod.js"; -import { getLanguage, loadLanguage, serverLanguages, translate } from "../../languages/translate.js"; +import { getLanguage, loadLanguage, serverLanguages, translate, translationKeys } from "../../languages/translate.js"; import { Command, ConvertArgumentDefinitionsToArgs } from "../../utils/slash/createCommand.js"; function logCommand( @@ -44,9 +44,9 @@ export async function executeSlashCommand(bot: BotWithCustomProps, interaction: // Command could not be found if (!command?.execute) { - return await interaction.reply(translate(interaction.guildId!, "EXECUTE_COMMAND_NOT_FOUND")).catch( - bot.logger.error, - ); + return await interaction + .reply(translate(interaction.guildId!, "EXECUTE_COMMAND_NOT_FOUND")) + .catch(bot.logger.error); } // HAVE TO CONVERT OUTSIDE OF TRY SO IT CAN BE USED IN CATCH TOO @@ -124,13 +124,9 @@ export function translateOptionNames( // TRANSLATE ALL OPTIONS let translated: Record = {}; for (const option of options) { - // TODO: fix this ignore - // @ts-ignore - translated[translate(bot, guildId, option.name).toLowerCase()] = translate( - bot, + translated[translate(guildId, option.name as translationKeys).toLowerCase()] = translate( "english", - // @ts-ignore - option.name, + option.name as translationKeys, ); if (option.options) { translated = { diff --git a/template/bigbot/src/bot/index.ts b/template/bigbot/src/bot/index.ts index 1ec6e0562..c7ad154e0 100644 --- a/template/bigbot/src/bot/index.ts +++ b/template/bigbot/src/bot/index.ts @@ -33,10 +33,8 @@ process .setTimestamp() .setFooter("Unhandled Rejection Error Occurred"); - // SEND ERROR TO THE LOG CHANNEL ON THE GAMER DEV SERVER - return bot.helpers - .sendWebhookMessage(bot.transformers.snowflake(id), token, { embeds }) - .catch(console.error); + // SEND ERROR TO THE LOG CHANNEL ON THE DEV SERVER + return bot.helpers.sendWebhookMessage(bot.transformers.snowflake(id), token, { embeds }).catch(console.error); }) .on("uncaughtException", async (error) => { const { id, token } = webhookURLToIDAndToken(BUGS_ERRORS_REPORT_WEBHOOK); @@ -57,10 +55,8 @@ process .setTimestamp() .setFooter("Unhandled Exception Error Occurred"); - // SEND ERROR TO THE LOG CHANNEL ON THE GAMER DEV SERVER - await bot.helpers - .sendWebhookMessage(bot.transformers.snowflake(id), token, { embeds }) - .catch(console.error); + // SEND ERROR TO THE LOG CHANNEL ON THE DEV SERVER + await bot.helpers.sendWebhookMessage(bot.transformers.snowflake(id), token, { embeds }).catch(console.error); process.exit(1); }); @@ -107,19 +103,20 @@ async function handleRequest(req: express.Request, res: express.Response) { } const json = req.body as { - data: DiscordGatewayPayload; + message: DiscordGatewayPayload; shardId: number; }; - // EMITS RAW EVENT - bot.events.raw(bot, json.data, json.shardId); - if (json.data.t && json.data.t !== "RESUMED") { + // EMITS RAW EVENT + bot.events.raw(bot, json.message, json.shardId); + + if (json.message.t && json.message.t !== "RESUMED") { // When a guild or something isnt in cache this will fetch it before doing anything else - if (json.data.t !== "READY") { - await bot.events.dispatchRequirements(bot, json.data, json.shardId); + if (!["READY", "GUILD_LOADED_DD"].includes(json.message.t)) { + await bot.events.dispatchRequirements(bot, json.message, json.shardId); } - bot.handlers[json.data.t]?.(bot, json.data, json.shardId); + bot.handlers[json.message.t]?.(bot, json.message, json.shardId); } res.status(200).json({ success: true }); diff --git a/template/bigbot/src/configs.ts b/template/bigbot/src/configs.ts index 00a1c37ad..c646b8035 100644 --- a/template/bigbot/src/configs.ts +++ b/template/bigbot/src/configs.ts @@ -27,7 +27,7 @@ export const EVENT_HANDLER_PORT = 8081; /** The url where the bot code(event handler) will run. This is where the gateway will send its messages to. */ // SETUP-DD-TEMP: Set the bot's url here. -export const EVENT_HANDLER_URL = "http://localhost:8080"; +export const EVENT_HANDLER_URL = `http://localhost:${EVENT_HANDLER_PORT}`; /** The full webhook url where the bot can send errors to alert you that the bot is missing translations. */ // SETUP-DD-TEMP: Set a full discord webhook url here. @@ -86,9 +86,9 @@ export const TOTAL_WORKERS: number = 4; // SETUP-DD-TEMP: Add a secret passcode here. export const GATEWAY_AUTHORIZATION = ""; -/** The url where the gateway will run. */ +/** The host where the gateway will run. Must follow https://nodejs.org/api/net.html#serverlistenoptions-callback. */ // SETUP-DD-TEMP: Set the gateways's host here. -export const GATEWAY_HOST = "http://localhost"; +export const GATEWAY_HOST = "localhost"; /** The port where the gateway will run. This is where the bot will send its messages to the gateway. */ // SETUP-DD-TEMP: Set the gateways's port here. diff --git a/template/bigbot/src/gateway/index.ts b/template/bigbot/src/gateway/index.ts index 24f918a14..7cf1932e4 100644 --- a/template/bigbot/src/gateway/index.ts +++ b/template/bigbot/src/gateway/index.ts @@ -76,12 +76,12 @@ async function main() { token: DISCORD_TOKEN, handlerUrls: [EVENT_HANDLER_URL], handlerAuthorization: EVENT_HANDLER_AUTHORIZATION, - path: `${__dirname}/worker.ts`, + path: "./worker.ts", totalShards: gateway.manager.totalShards, workerId, }; - const worker = new Worker(`${__dirname}/worker.js`, { + const worker = new Worker("./worker.js", { workerData, }); diff --git a/template/bigbot/src/gateway/worker.ts b/template/bigbot/src/gateway/worker.ts index b48574064..a65f0cf74 100644 --- a/template/bigbot/src/gateway/worker.ts +++ b/template/bigbot/src/gateway/worker.ts @@ -1,4 +1,13 @@ -import { createShardManager, DiscordUnavailableGuild, Shard, ShardSocketRequest, ShardState } from "discordeno"; +import { + createShardManager, + DiscordGuild, + DiscordReady, + DiscordUnavailableGuild, + GatewayEventNames, + Shard, + ShardSocketRequest, + ShardState, +} from "discordeno"; import { createLogger } from "discordeno/logger"; import { parentPort, workerData } from "worker_threads"; import { ManagerMessage } from "./index.js"; @@ -13,6 +22,10 @@ const log = createLogger({ name: `[WORKER #${script.workerId}]` }); const identifyPromises = new Map void>(); +// Store guild ids, loading guild ids to change GUILD_CREATE event to GUILD_LOADED_DD if needed. +const guildIds: Set = new Set(); +const loadingGuildIds: Set = new Set(); + const manager = createShardManager({ gatewayConfig: { intents: script.intents, @@ -24,14 +37,43 @@ const manager = createShardManager({ const url = script.handlerUrls[shard.id % script.handlerUrls.length]; if (!url) return console.log("ERROR: NO URL FOUND TO SEND MESSAGE"); - // MUST HANDLE GUILD_DELETE EVENTS FOR UNAVAILABLE - if (message.t === "GUILD_DELETE" && (message.d as DiscordUnavailableGuild).unavailable) return; + if (message.t === "READY") { + // Marks which guilds the bot in when initial loading in cache. + (message.d as DiscordReady).guilds.forEach((g) => loadingGuildIds.add(BigInt(g.id))); + } + + // If GUILD_CREATE event came from a shard loaded event, change event to GUILD_LOADED_DD. + if (message.t === "GUILD_CREATE") { + const guild = message.d as DiscordGuild; + const id = BigInt(guild.id); + + const existing = guildIds.has(id); + if (existing) return; + + if (loadingGuildIds.has(id)) { + (message.t as GatewayEventNames | "GUILD_LOADED_DD") = "GUILD_LOADED_DD"; + + loadingGuildIds.delete(id); + } + + guildIds.add(id); + } + + // Delete guild id from cache so GUILD_CREATE from the same guild later works properly. + if (message.t === "GUILD_DELETE") { + const guild = message.d as DiscordUnavailableGuild; + + if (guild.unavailable) return; + + guildIds.delete(BigInt(guild.id)); + } await fetch(url, { method: "POST", body: JSON.stringify({ message, shardId: shard.id }), headers: { "Content-Type": "application/json", Authorization: script.handlerAuthorization }, }).catch((error) => log.error(error)); + log.debug({ shardId: shard.id, message }); }, requestIdentify: async function (shardId: number): Promise { diff --git a/template/bigbot/src/rest/index.ts b/template/bigbot/src/rest/index.ts index 5b5a01752..ef2e8f44f 100644 --- a/template/bigbot/src/rest/index.ts +++ b/template/bigbot/src/rest/index.ts @@ -3,7 +3,7 @@ import { BASE_URL, createRestManager } from "discordeno"; import express, { Request, Response } from "express"; import { Influx } from "../analytics.js"; -import { DISCORD_TOKEN, INFLUX_TOKEN, REST_AUTHORIZATION, REST_PORT, REST_URL } from "../configs.js"; +import { DISCORD_TOKEN, REST_AUTHORIZATION, REST_PORT, REST_URL } from "../configs.js"; const rest = createRestManager({ token: DISCORD_TOKEN, @@ -13,9 +13,9 @@ const rest = createRestManager({ }); // If influxdb data is provided, enable analytics in this proxy. -if (INFLUX_TOKEN) { +if (Influx) { rest.fetching = function (options) { - Influx.writePoint( + Influx?.writePoint( new Point("restEvents") // MARK THE TIME WHEN EVENT ARRIVED .timestamp(new Date()) @@ -28,7 +28,7 @@ if (INFLUX_TOKEN) { }; rest.fetched = function (options, response) { - Influx.writePoint( + Influx?.writePoint( new Point("restEvents") // MARK THE TIME WHEN EVENT ARRIVED .timestamp(new Date()) @@ -44,7 +44,7 @@ if (INFLUX_TOKEN) { setInterval(() => { console.log(`[Influx - REST] Saving events...`); - Influx.flush() + Influx?.flush() .then(() => { console.log(`[Influx - REST] Saved events!`); }) @@ -96,12 +96,7 @@ async function handleRequest(req: Request, res: Response) { } try { - const result = await rest.runMethod( - rest, - req.method as any, - `${BASE_URL}${req.url}`, - req.body, - ); + const result = await rest.runMethod(rest, req.method as any, `${BASE_URL}${req.url}`, req.body); if (result) { res.status(200).json(result);