From 524edac615796c88e286352315cf948ded67ec34 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Tue, 6 Jun 2023 16:50:33 +0000 Subject: [PATCH] feat: mature error handling system --- src/commands/debug.ts | 2 +- src/commands/eligible.ts | 2 +- src/commands/help.ts | 2 +- src/commands/ownedtanks.tsx | 2 +- src/commands/playerachievements.ts | 2 +- src/commands/playerinfo.ts | 2 +- src/commands/searchclans.ts | 2 +- src/commands/searchplayers.ts | 2 +- src/commands/searchtanks.ts | 2 +- src/commands/stats.tsx | 2 +- src/commands/tankstats.tsx | 2 +- src/commands/verify.ts | 2 +- src/core/blitz/getBlitzAccount.ts | 30 +++++++------------------ src/core/blitz/getBlitzClan.ts | 16 +++++-------- src/core/blitz/getTankStats.ts | 16 +++++-------- src/core/blitz/getWargamingResponse.ts | 6 +++-- src/core/blitz/resolveTankId.ts | 30 ++++++++----------------- src/core/blitz/wargamingError.ts | 2 +- src/core/blitzstars/blitzStarsError.ts | 2 +- src/core/process/args.ts | 6 ++++- src/core/process/errorWithCause.ts | 3 +++ src/events/error.ts | 8 +++---- src/events/interactionCreate.ts | 31 +++++++++++++++++++++++++- 23 files changed, 87 insertions(+), 87 deletions(-) create mode 100644 src/core/process/errorWithCause.ts diff --git a/src/commands/debug.ts b/src/commands/debug.ts index 278060b45..0c3b94b27 100644 --- a/src/commands/debug.ts +++ b/src/commands/debug.ts @@ -13,7 +13,7 @@ const executionStart = new Date().getTime(); export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/eligible.ts b/src/commands/eligible.ts index f4091a0b3..24060f7c8 100644 --- a/src/commands/eligible.ts +++ b/src/commands/eligible.ts @@ -26,7 +26,7 @@ export const SKILLED_CLANS: Record = { export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: false, command: new SlashCommandBuilder() diff --git a/src/commands/help.ts b/src/commands/help.ts index 0b1511cb8..327e465bc 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -11,7 +11,7 @@ import { client } from '../index.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/ownedtanks.tsx b/src/commands/ownedtanks.tsx index bf4d02291..55f7a434d 100644 --- a/src/commands/ownedtanks.tsx +++ b/src/commands/ownedtanks.tsx @@ -40,7 +40,7 @@ const COMP_TANKS = [ export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/playerachievements.ts b/src/commands/playerachievements.ts index 536e1bed9..25847ac8f 100644 --- a/src/commands/playerachievements.ts +++ b/src/commands/playerachievements.ts @@ -17,7 +17,7 @@ type SortBy = 'name' | 'count'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/playerinfo.ts b/src/commands/playerinfo.ts index 82c83a05d..ae4dd54d9 100644 --- a/src/commands/playerinfo.ts +++ b/src/commands/playerinfo.ts @@ -13,7 +13,7 @@ import { AccountInfo } from '../types/accountInfo.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/searchclans.ts b/src/commands/searchclans.ts index db07881eb..d3f3e88a1 100644 --- a/src/commands/searchclans.ts +++ b/src/commands/searchclans.ts @@ -12,7 +12,7 @@ import { ClanList } from '../types/clanList.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/searchplayers.ts b/src/commands/searchplayers.ts index d0f731cd8..b196d8d70 100644 --- a/src/commands/searchplayers.ts +++ b/src/commands/searchplayers.ts @@ -13,7 +13,7 @@ import { AccountList } from '../types/accountList.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/searchtanks.ts b/src/commands/searchtanks.ts index 2d525cdc0..0e4d2d469 100644 --- a/src/commands/searchtanks.ts +++ b/src/commands/searchtanks.ts @@ -9,7 +9,7 @@ import { CommandRegistry } from '../events/interactionCreate.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index e5f13ae4e..31dd32681 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -40,7 +40,7 @@ import { PlayerClanData } from '../types/playerClanData.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/tankstats.tsx b/src/commands/tankstats.tsx index dbcd9d270..aa7bf062f 100644 --- a/src/commands/tankstats.tsx +++ b/src/commands/tankstats.tsx @@ -40,7 +40,7 @@ import { AccountInfo, AllStats } from '../types/accountInfo.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/verify.ts b/src/commands/verify.ts index 7ffa34a8b..cdbe4d674 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -14,7 +14,7 @@ import { PlayerClanData } from '../types/playerClanData.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/core/blitz/getBlitzAccount.ts b/src/core/blitz/getBlitzAccount.ts index 7a16d341b..e25cfda1a 100644 --- a/src/core/blitz/getBlitzAccount.ts +++ b/src/core/blitz/getBlitzAccount.ts @@ -1,6 +1,6 @@ import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import { BlitzServer } from '../../constants/servers.js'; -import negativeEmbed from '../interaction/negativeEmbed.js'; +import errorWithCause from '../process/errorWithCause.js'; import listAccountsPanServer, { usernamePatternWithoutPosition, } from './listAccountsPanServer.js'; @@ -17,17 +17,9 @@ export default async function getBlitzAccount( const username = commandUsername ?? displayName; if (username === undefined) { - await interaction.editReply({ - embeds: [ - negativeEmbed( - 'Username could not be inferred', - "You didn't provide me a username in the command nor was I able to infer your Blitz username from your Discord nickname. Try providing a username manually in the command or using the `/verify` command to set your Discord nickname to your Blitz username.", - ), - ], - }); - - throw new Error( - `No username provided commandUsername: ${commandUsername} displayName: ${displayName} username: ${username}`, + throw errorWithCause( + 'Username could not be inferred', + "You didn't provide me a username in the command nor was I able to infer your Blitz username from your Discord nickname. Try providing a username manually in the command or using the `/verify` command to set your Discord nickname to your Blitz username.", ); } @@ -40,16 +32,10 @@ export default async function getBlitzAccount( if (accounts[0]) { return { server: accounts[0].server, id: accounts[0].account_id }; } else { - await interaction.editReply({ - embeds: [ - negativeEmbed( - 'Could not find user', - `I couldn't find user \`${username}\`. Try providing a username manually or using the \`/verify\` command to set your Discord nickname to your Blitz username.`, - ), - ], - }); - - throw new Error(`Could not find user "${username}"`); + throw errorWithCause( + 'Could not find user', + `I couldn't find user \`${username}\`. Try providing a valid username manually or using the \`/verify\` command to set your Discord nickname to your Blitz username.`, + ); } } } diff --git a/src/core/blitz/getBlitzClan.ts b/src/core/blitz/getBlitzClan.ts index e15c103be..7d5c847c1 100644 --- a/src/core/blitz/getBlitzClan.ts +++ b/src/core/blitz/getBlitzClan.ts @@ -1,6 +1,6 @@ import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import { BlitzServer } from '../../constants/servers.js'; -import negativeEmbed from '../interaction/negativeEmbed.js'; +import errorWithCause from '../process/errorWithCause.js'; import { serverAndIdPattern } from './getBlitzAccount.js'; import listClansPanServer from './listClansPanServer.js'; @@ -17,16 +17,10 @@ export default async function getBlitzClan( if (accounts[0]) { return { server: accounts[0].server, id: accounts[0].clan_id }; } else { - await interaction.editReply({ - embeds: [ - negativeEmbed( - 'Could not find clan', - `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`, - ), - ], - }); - - throw new Error(`Could not find clan "${clan}"`); + throw errorWithCause( + 'Could not find clan', + `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`, + ); } } } diff --git a/src/core/blitz/getTankStats.ts b/src/core/blitz/getTankStats.ts index d41b3a9c6..5dac94cf3 100644 --- a/src/core/blitz/getTankStats.ts +++ b/src/core/blitz/getTankStats.ts @@ -1,8 +1,8 @@ import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers.js'; import { TanksStats } from '../../types/tanksStats.js'; -import negativeEmbed from '../interaction/negativeEmbed.js'; import { args } from '../process/args.js'; +import errorWithCause from '../process/errorWithCause.js'; import getWargamingResponse from './getWargamingResponse.js'; export default async function getTankStats( @@ -15,16 +15,10 @@ export default async function getTankStats( ); if (tankStats[id] === null) { - interaction.editReply({ - embeds: [ - negativeEmbed( - 'No tank stats available', - `Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${BLITZ_SERVERS[server]} server.`, - ), - ], - }); - - throw new Error(`No tank stats available for ${server}/${id}`); + throw errorWithCause( + 'No tank stats available', + `Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${BLITZ_SERVERS[server]} server.`, + ); } return tankStats[id]; diff --git a/src/core/blitz/getWargamingResponse.ts b/src/core/blitz/getWargamingResponse.ts index 7a49afcef..59d30f9d5 100644 --- a/src/core/blitz/getWargamingResponse.ts +++ b/src/core/blitz/getWargamingResponse.ts @@ -1,4 +1,5 @@ import { WargamingResponse } from '../../types/wargamingResponse.js'; +import errorWithCause from '../process/errorWithCause.js'; export default async function getWargamingResponse( url: string, @@ -11,8 +12,9 @@ export default async function getWargamingResponse( if (parsed.status === 'ok') { return parsed.data; } else { - throw new Error( - `Wargaming response error status:"${parsed.status}" message: "${parsed.error.message}" url: "${url}"`, + throw errorWithCause( + `Wargaming response error status:"${parsed.status}"`, + `Message: "${parsed.error.message}"\nURL: "${url}"`, ); } } diff --git a/src/core/blitz/resolveTankId.ts b/src/core/blitz/resolveTankId.ts index c480d0fee..e71e6ff8f 100644 --- a/src/core/blitz/resolveTankId.ts +++ b/src/core/blitz/resolveTankId.ts @@ -1,6 +1,6 @@ import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import { go } from 'fuzzysort'; -import negativeEmbed from '../interaction/negativeEmbed.js'; +import errorWithCause from '../process/errorWithCause.js'; import { TANKS, tankopedia } from './tankopedia.js'; export default async function resolveTankId( @@ -13,16 +13,10 @@ export default async function resolveTankId( const searchResult = go(tank, TANKS, { keys: ['name'], limit: 1 }); if (searchResult.length === 0) { - await interaction.editReply({ - embeds: [ - negativeEmbed( - 'Tank not found', - `Could not find tank by the name "${tank}".`, - ), - ], - }); - - throw new Error(`Tank not found by name "${tank}"`); + throw errorWithCause( + 'Tank not found', + `Could not find tank by the name "${tank}".`, + ); } else { return searchResult[0].obj.tank_id; } @@ -30,16 +24,10 @@ export default async function resolveTankId( if (tankopedia[number]) { return number; } else { - await interaction.editReply({ - embeds: [ - negativeEmbed( - 'Tank not found', - `Could not find tank by the ID "${number}".`, - ), - ], - }); - - throw new Error(`Tank not found by ID "${number}"`); + throw errorWithCause( + 'Tank not found', + `Could not find tank by the ID "${number}".`, + ); } } } diff --git a/src/core/blitz/wargamingError.ts b/src/core/blitz/wargamingError.ts index a6daae8fb..e9e8258aa 100644 --- a/src/core/blitz/wargamingError.ts +++ b/src/core/blitz/wargamingError.ts @@ -22,5 +22,5 @@ export default async function wargamingError( }); console.log(`Wargaming returned an error`); - handleError(error, client, command); + handleError(error, command); } diff --git a/src/core/blitzstars/blitzStarsError.ts b/src/core/blitzstars/blitzStarsError.ts index 1278d3bf1..9f2b8443c 100644 --- a/src/core/blitzstars/blitzStarsError.ts +++ b/src/core/blitzstars/blitzStarsError.ts @@ -24,5 +24,5 @@ export default async function blitzStarsError( }); console.log(`BlitzStars returned an error`); - handleError(error, client, command); + handleError(error, command); } diff --git a/src/core/process/args.ts b/src/core/process/args.ts index 3b44eb03d..1e3f490c1 100644 --- a/src/core/process/args.ts +++ b/src/core/process/args.ts @@ -1,4 +1,5 @@ import { argv } from 'process'; +import errorWithCause from './errorWithCause.js'; type ArgumentName = 'discord-token' | 'wargaming-application-id'; @@ -16,6 +17,9 @@ argv.forEach((arg) => { Object.keys(args).forEach((arg) => { if (args[arg as ArgumentName] === '') { - throw new Error(`Missing argument: ${arg}`); + throw errorWithCause( + `Missing argument: ${arg}`, + 'Missing required auth key.', + ); } }); diff --git a/src/core/process/errorWithCause.ts b/src/core/process/errorWithCause.ts new file mode 100644 index 000000000..0e2ef81c7 --- /dev/null +++ b/src/core/process/errorWithCause.ts @@ -0,0 +1,3 @@ +export default function errorWithCause(message: string, cause: string) { + return new Error(message, { cause }); +} diff --git a/src/events/error.ts b/src/events/error.ts index bc99ced50..1f615cf63 100644 --- a/src/events/error.ts +++ b/src/events/error.ts @@ -1,4 +1,4 @@ -import { Client, EmbedBuilder, TextChannel } from 'discord.js'; +import { EmbedBuilder, TextChannel } from 'discord.js'; import discord from '../../discord.json' assert { type: 'json' }; import { NEGATIVE_COLOR } from '../constants/colors.js'; import isDev from '../core/process/isDev.js'; @@ -7,7 +7,7 @@ import { client } from '../index.js'; const PROCESS_ERROR_EVENTS = ['uncaughtException']; const CLIENT_ERROR_EVENTS = ['error']; -export function handleError(error: Error, client: Client, command: string) { +export function handleError(error: Error, command: string) { console.error(error); ( @@ -31,10 +31,10 @@ export function handleError(error: Error, client: Client, command: string) { export function registerErrorHandlers() { if (!isDev()) { PROCESS_ERROR_EVENTS.forEach((name) => - process.on(name, (error) => handleError(error, client, 'process error')), + process.on(name, (error) => handleError(error, 'process error')), ); CLIENT_ERROR_EVENTS.forEach((name) => - client.on(name, (error) => handleError(error, client, 'client error')), + client.on(name, (error) => handleError(error, 'client error')), ); } diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 5fb7c2b75..236a3ecf5 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,5 +1,8 @@ import { + ActionRowBuilder, AutocompleteInteraction, + ButtonBuilder, + ButtonStyle, CacheType, ChatInputCommandInteraction, Collection, @@ -11,9 +14,11 @@ import { } from 'discord.js'; import { readdirSync } from 'fs'; import discord from '../../discord.json' assert { type: 'json' }; +import negativeEmbed from '../core/interaction/negativeEmbed.js'; import { args } from '../core/process/args.js'; import getClientId from '../core/process/getClientId.js'; import isDev from '../core/process/isDev.js'; +import { handleError } from './error.js'; export interface CommandRegistry { inDevelopment: boolean; @@ -101,6 +106,30 @@ export default async function interactionCreate( console.log(interaction.toString()); await interaction.deferReply(); - command.execute(interaction); + + try { + await command.execute(interaction); + } catch (error) { + const actionRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel('Get Help on Discord Server') + .setURL('https://discord.gg/nDt7AjGJQH') + .setStyle(ButtonStyle.Link), + ); + + await interaction.editReply({ + embeds: [ + negativeEmbed( + (error as Error).message, + `${ + (error as Error).cause ?? 'No further information is available.' + }`, + ), + ], + components: [actionRow], + }); + + handleError(error as Error, interaction.commandName); + } } }