From 94abad1112a93c12d86be1295c36b73ef720dba0 Mon Sep 17 00:00:00 2001 From: Guilherme Afonso Date: Thu, 29 Aug 2024 00:02:01 -0300 Subject: [PATCH] fix: improve high order function for error handling --- src/bot/bot.ts | 58 +++++++-------- src/bot/events/interactionCreate.ts | 5 +- src/bot/events/messageCreate.ts | 2 +- src/database/mongo.ts | 15 ++-- src/index.ts | 3 +- src/utils/botError.ts | 5 +- src/utils/errorHandling.ts | 111 ++++++++++++++++------------ 7 files changed, 105 insertions(+), 94 deletions(-) diff --git a/src/bot/bot.ts b/src/bot/bot.ts index a0bc041..a6db21f 100644 --- a/src/bot/bot.ts +++ b/src/bot/bot.ts @@ -17,7 +17,6 @@ import { import { logger } from '@utils/logger'; import { mongoConnection } from '@database/mongo'; import { safeExecute } from '@utils/errorHandling'; -import MinecraftServerStatus from '@utils/minecraftServerStatus'; import * as commands from '@commands'; import * as events from '@events'; @@ -54,24 +53,17 @@ export class Bot { }); } - start() { + async start() { this._loadTextCommands(); this._loadSlashCommands(); - this._sendSlashCommands(); + await this._sendSlashCommands(); this._loadEvents(); - this._startMongo(); - this._startMinecraftServer(); - this._client.login(process.env.MARQUINHOS_TOKEN); + await this._startMongo(); + await this._client.login(process.env.MARQUINHOS_TOKEN); } - private _startMongo() { - mongoConnection(); - } - - private _startMinecraftServer() { - const minecraftServer = MinecraftServerStatus.getInstance(); - minecraftServer.init(this._client); - minecraftServer.start(); + private async _startMongo() { + await mongoConnection(); } private _loadSlashCommands() { @@ -121,11 +113,11 @@ export class Bot { eventsArray.forEach((event: BotEvent) => { if (event.once) { this._client.once(event.name, (...args) => { - safeExecute(event.execute.bind(this, ...args), this._client)(); + safeExecute(event.execute.bind(this, ...args))(); }); } else { this._client.on(event.name, (...args) => { - safeExecute(event.execute.bind(this, ...args), this._client)(); + safeExecute(event.execute.bind(this, ...args))(); }); } @@ -133,25 +125,27 @@ export class Bot { }); } - private _sendSlashCommands() { + private async _sendSlashCommands() { const rest = new REST({ version: '10' }).setToken( - process.env.MARQUINHOS_TOKEN + process.env.MARQUINHOS_TOKEN as string ); - rest - .put(Routes.applicationCommands(process.env.MARQUINHOS_CLIENT_ID), { - body: this._slashCommands.map((command) => command.toJSON()), - }) - .then((data: any) => { - if (!data.length) { - logger.warn('No slash commands loaded'); - return; + try { + const data = (await rest.put( + Routes.applicationCommands(process.env.MARQUINHOS_CLIENT_ID as string), + { + body: this._slashCommands.map((command) => command.toJSON()), } - logger.info(`Successfully loaded ${data.length} slash command(s)`); - }) - .catch((e) => { - logger.error('Error loading slash commands'); - logger.error(e); - }); + )) as SlashCommandBuilder[]; + + if (!data?.length) { + logger.warn('No slash commands loaded'); + return; + } + + logger.info(`Successfully loaded ${data.length} slash command(s)`); + } catch (error: any) { + throw new Error(error); + } } } diff --git a/src/bot/events/interactionCreate.ts b/src/bot/events/interactionCreate.ts index cfaea9f..0641df2 100644 --- a/src/bot/events/interactionCreate.ts +++ b/src/bot/events/interactionCreate.ts @@ -51,10 +51,7 @@ export const interactionCreate: BotEvent = { : '' }` ); - safeExecute( - command.execute.bind(this, interaction), - interaction.client - )(); + safeExecute(command.execute.bind(this, interaction))(); } else if (interaction.isAutocomplete()) { const command = interaction.client.slashCommands.get( interaction.commandName diff --git a/src/bot/events/messageCreate.ts b/src/bot/events/messageCreate.ts index c3e93da..a6fe1b1 100644 --- a/src/bot/events/messageCreate.ts +++ b/src/bot/events/messageCreate.ts @@ -121,7 +121,7 @@ export const messageCreate: BotEvent = { args ? args.join(' ') : '' }` ); - safeExecute(command.execute.bind(this, message, args), message.client)(); + safeExecute(command.execute.bind(this, message, args))(); }, }; diff --git a/src/database/mongo.ts b/src/database/mongo.ts index 5487601..d8a35c3 100644 --- a/src/database/mongo.ts +++ b/src/database/mongo.ts @@ -6,11 +6,12 @@ export const mongoConnection = async () => { const MONGO_DATABASE_NAME = process.env.MARQUINHOS_MONGO_DATABASE_NAME; if (!MONGO_URI) return logger.info(`Mongo URI not found`); if (!MONGO_DATABASE_NAME) return logger.info(`Mongo database name not found`); - return await mongoose - .connect(`${MONGO_URI}/${MONGO_DATABASE_NAME}`) - .then(() => logger.info('MongoDB connection has been established.')) - .catch((error) => { - logger.error('MongoDB connection has been failed'); - logger.error(error); - }); + try { + const mongoConnection = await mongoose.connect( + `${MONGO_URI}/${MONGO_DATABASE_NAME}` + ); + if (!mongoConnection) throw new Error('MongoDB connection failed'); + } catch (error) { + throw new Error(`MongoDB connection failed: ${error}`); + } }; diff --git a/src/index.ts b/src/index.ts index 7a884ce..77e7e55 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ import { config } from 'dotenv'; import { Bot } from '@marquinhos/bot'; +import { safeExecute } from './utils/errorHandling'; process.env.ROOT_DIR = __dirname; config(); const marquinhos = new Bot(); -marquinhos.start(); +safeExecute(marquinhos.start.bind(marquinhos))(); diff --git a/src/utils/botError.ts b/src/utils/botError.ts index d260cfa..22ed9fe 100644 --- a/src/utils/botError.ts +++ b/src/utils/botError.ts @@ -5,16 +5,19 @@ import { BotErrorLogLevel } from '@marquinhos/types'; class BotError extends Error { discordMessage: Message | CommandInteraction; logLevel: BotErrorLogLevel = 'error'; + origin = 'Unknown'; constructor( message: string, discordMessage: Message | CommandInteraction, - logLevel?: BotErrorLogLevel + logLevel?: BotErrorLogLevel, + origin: string = 'Unknown' ) { super(message); this.name = 'MarquinhosError'; this.discordMessage = discordMessage; this.logLevel = logLevel ?? 'error'; + this.origin = origin; } } diff --git a/src/utils/errorHandling.ts b/src/utils/errorHandling.ts index dabe335..d2f7094 100644 --- a/src/utils/errorHandling.ts +++ b/src/utils/errorHandling.ts @@ -1,65 +1,80 @@ import { logger } from '@utils/logger'; import BotError from '@utils/botError'; -import { Client, TextChannel } from 'discord.js'; +import axios from 'axios'; -export const safeExecute = (fn: Function, client: Client) => { +export function safeExecute(fn: Function) { return function () { - fn()?.catch((error: BotError) => commandErrorHandler(error, client)); + try { + const result = fn(); + if (result instanceof Promise) { + result.catch((error: BotError) => { + commandErrorHandler(error); + }); + } + } catch (error) { + commandErrorHandler(error as BotError); + } }; -}; +} -const commandErrorHandler = async (error: BotError, client: Client) => { +const commandErrorHandler = async (error: BotError) => { + await sendErrorMessage(error); switch (error.logLevel) { case 'warn': - logger.warn(`${error} while running ${error.discordMessage}`); + logger.warn(`${error?.stack ?? error.message}`); break; case 'info': - logger.info(`${error} while running ${error.discordMessage}`); + logger.info(`${error?.stack ?? error.message}`); break; default: - try { - const errorStackTraceChunks = error.stack?.match(/.{1,2048}/gs); - if (!errorStackTraceChunks?.length) { - await sendErrorMessage( - client, - error.message, - 'No stack trace available' - ); - } else if (errorStackTraceChunks.length === 1) { - await sendErrorMessage( - client, - error.message, - errorStackTraceChunks[0] - ); - } else { - for (const [index, chunk] of errorStackTraceChunks.entries()) { - await sendErrorMessage( - client, - `${error.message} ${index + 1}/${errorStackTraceChunks.length}`, - chunk - ); - } - } - } catch (error: any) { - logger.error( - `Error while trying to send error message to error channel\n${error.stack}` - ); - } - logger.error( - `${error} while running ${error.discordMessage}\n${error.stack} ` - ); + logger.error(`${error?.stack ?? error.message}`); break; } }; -async function sendErrorMessage( - client: Client, - title: string, - description: string -) { - await ( - client.channels.cache.get( - process.env.MARQUINHOS_ERROR_CHANNEL_ID || '' - ) as TextChannel - )?.send(`### ⚠️⚠️⚠️ ${title} ⚠️⚠️⚠️\n\n\`\`\`ansi\n${description}\`\`\``); +async function sendErrorMessage(error: BotError) { + const title = error.message; + const description = error?.stack || 'No stack trace'; + + try { + await axios.post( + encodeURI(process.env.MARQUINHOS_ERROR_WEBHOOK || ''), + { + username: 'Marquinhos Error Notifier', + avatar_url: 'https://i.imgur.com/M4k2OVe.png', + embeds: [ + { + title: `Search error on StackOverflow`, + url: `https://www.google.com/search?q=${encodeURI( + title + )}%20site:stackoverflow.com`, + description: `\`\`\`${description.slice(0, 1024)}\`\`\``, + color: 0xff0000, + footer: { + text: 'The operation has failed successfully!', + }, + fields: [ + { + name: 'Level', + value: error.logLevel || 'error', + inline: true, + }, + { + name: 'Origin', + value: error.origin || 'Unknown', + inline: true, + }, + ], + }, + ], + }, + { + headers: { + 'Content-Type': 'application/json', + }, + } + ); + } catch (error) { + console.error(error); + } }