feat: mature error handling system

This commit is contained in:
TresAbhi committed 2023-06-06 16:50:33 +00:00
1 parent 1875f30cf5
commit 524edac615
23 files changed
+87 -87

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ const executionStart = new Date().getTime();
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -26,7 +26,7 @@ export const SKILLED_CLANS: Record<SkilledClan, string> = {
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: false,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -11,7 +11,7 @@ import { client } from '../index.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -40,7 +40,7 @@ const COMP_TANKS = [
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -17,7 +17,7 @@ type SortBy = 'name' | 'count';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -13,7 +13,7 @@ import { AccountInfo } from '../types/accountInfo.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -12,7 +12,7 @@ import { ClanList } from '../types/clanList.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -13,7 +13,7 @@ import { AccountList } from '../types/accountList.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -9,7 +9,7 @@ import { CommandRegistry } from '../events/interactionCreate.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -40,7 +40,7 @@ import { PlayerClanData } from '../types/playerClanData.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -40,7 +40,7 @@ import { AccountInfo, AllStats } from '../types/accountInfo.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+1 -1
View File
@@ -14,7 +14,7 @@ import { PlayerClanData } from '../types/playerClanData.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
+8 -22
View File
@@ -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.`,
);
}
}
}
+5 -11
View File
@@ -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.`,
);
}
}
}
+5 -11
View File
@@ -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];
+4 -2
View File
@@ -1,4 +1,5 @@
import { WargamingResponse } from '../../types/wargamingResponse.js';
import errorWithCause from '../process/errorWithCause.js';
export default async function getWargamingResponse<Data extends object>(
url: string,
@@ -11,8 +12,9 @@ export default async function getWargamingResponse<Data extends object>(
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}"`,
);
}
}
+9 -21
View File
@@ -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}".`,
);
}
}
}
+1 -1
View File
@@ -22,5 +22,5 @@ export default async function wargamingError(
});
console.log(`Wargaming returned an error`);
handleError(error, client, command);
handleError(error, command);
}
+1 -1
View File
@@ -24,5 +24,5 @@ export default async function blitzStarsError(
});
console.log(`BlitzStars returned an error`);
handleError(error, client, command);
handleError(error, command);
}
+5 -1
View File
@@ -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.',
);
}
});
+3
View File
@@ -0,0 +1,3 @@
export default function errorWithCause(message: string, cause: string) {
return new Error(message, { cause });
}
+4 -4
View File
@@ -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')),
);
}
+30 -1
View File
@@ -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<ButtonBuilder>().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);
}
}
}