fix: improve error handling

This commit is contained in:
Keisuke Hirata 2025-07-16 10:31:24 +09:00
parent 09ccb03626
commit b273fd3a3e
5 changed files with 99 additions and 1294 deletions

1227
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +0,0 @@
{
"scripts": {
"start": "tsx src/index.ts"
},
"type": "module",
"dependencies": {
"discord.js": "^14.16.1",
"dotenv": "^16.4.5"
},
"devDependencies": {
"@types/node": "^22.5.4",
"tsx": "^4.19.0",
"typescript": "^5.5.4"
}
}

View File

@ -16,6 +16,11 @@ export function readConfig(): Entry[] {
if (!fs.existsSync('./config.json')) { if (!fs.existsSync('./config.json')) {
return []; return [];
} }
const data = fs.readFileSync('./config.json', 'utf8'); try {
return JSON.parse(data); const data = fs.readFileSync('./config.json', 'utf8');
return JSON.parse(data);
} catch (error) {
console.error('Failed to read or parse config.json:', error);
return [];
}
} }

View File

@ -1,9 +1,8 @@
import "dotenv/config"; const TOKEN = Deno.env.get("TOKEN");
const TOKEN = process.env.TOKEN; const CLIENT_ID = Deno.env.get("CLIENT_ID");
const CLIENT_ID = process.env.CLIENT_ID;
if (!TOKEN || !CLIENT_ID) { if (!TOKEN || !CLIENT_ID) {
console.error(`No ${TOKEN ? "CLIENT_ID" : "TOKEN"} provided`); console.error(`No ${TOKEN ? "CLIENT_ID" : "TOKEN"} provided`);
process.exit(); Deno.exit(1);
} }
import commands from "./commands.ts"; import commands from "./commands.ts";
@ -42,7 +41,7 @@ const client = new Client({
client.on("ready", () => { client.on("ready", () => {
if (!client.user) { if (!client.user) {
console.error("Failed to login"); console.error("Failed to login");
process.exit(); Deno.exit(1);
} }
console.log(`Logged in as ${client.user.tag}!`); console.log(`Logged in as ${client.user.tag}!`);
@ -79,11 +78,15 @@ client.on("interactionCreate", async (interaction) => {
writeConfig(entries); writeConfig(entries);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
await interaction.followUp({ await interaction.followUp({
content: `Set output to <#${channel.id}>`, content: "Failed to set output channel. Please try again.",
ephemeral: true,
}); });
return;
} }
await interaction.followUp({
content: `Set output to <#${channel.id}>`,
});
} else if (subcommand === "channels") { } else if (subcommand === "channels") {
const channel = interaction.options.getChannel("channel", true); const channel = interaction.options.getChannel("channel", true);
const mode = interaction.options.getString("add_remove", true); const mode = interaction.options.getString("add_remove", true);
@ -101,11 +104,15 @@ client.on("interactionCreate", async (interaction) => {
writeConfig(entries); writeConfig(entries);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
await interaction.followUp({ await interaction.followUp({
content: `Set to watch for reactions in <#${channel.id}>`, content: "Failed to configure channel list. Please try again.",
ephemeral: true,
}); });
return;
} }
await interaction.followUp({
content: `Set to watch for reactions in <#${channel.id}>`,
});
} else if (subcommand === "channels_type") { } else if (subcommand === "channels_type") {
const channel_type = interaction.options.getString( const channel_type = interaction.options.getString(
"channels_type", "channels_type",
@ -121,16 +128,23 @@ client.on("interactionCreate", async (interaction) => {
entry = new Entry(interaction.guildId!); entry = new Entry(interaction.guildId!);
entries.push(entry); entries.push(entry);
} }
if (channel_type !== "include" && channel_type !== "exclude") {
throw new Error(`Invalid channel type: ${channel_type}`);
}
entry.channel_type = channel_type as Type; entry.channel_type = channel_type as Type;
writeConfig(entries); writeConfig(entries);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
await interaction.followUp({ await interaction.followUp({
content: `channel list type set to ${channel_type}`, content: "Failed to set channel list type. Please try again.",
ephemeral: true, ephemeral: true,
}); });
return;
} }
await interaction.followUp({
content: `channel list type set to ${channel_type}`,
ephemeral: true,
});
} else if (subcommand === "emojis") { } else if (subcommand === "emojis") {
const emoji = interaction.options.getString("emoji", true); const emoji = interaction.options.getString("emoji", true);
const mode = interaction.options.getString("add_remove", true); const mode = interaction.options.getString("add_remove", true);
@ -148,12 +162,16 @@ client.on("interactionCreate", async (interaction) => {
await writeConfig(entries); await writeConfig(entries);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
await interaction.followUp({ await interaction.followUp({
content: `Set to watch for ${emoji}`, content: "Failed to configure emoji list. Please try again.",
ephemeral: true, ephemeral: true,
}); });
return;
} }
await interaction.followUp({
content: `Set to watch for ${emoji}`,
ephemeral: true,
});
} else if (subcommand === "emojis_type") { } else if (subcommand === "emojis_type") {
const emoji_type = interaction.options.getString("emojis_type", true); const emoji_type = interaction.options.getString("emojis_type", true);
try { try {
@ -165,16 +183,23 @@ client.on("interactionCreate", async (interaction) => {
entry = new Entry(interaction.guildId!); entry = new Entry(interaction.guildId!);
entries.push(entry); entries.push(entry);
} }
if (emoji_type !== "include" && emoji_type !== "exclude") {
throw new Error(`Invalid emoji type: ${emoji_type}`);
}
entry.emoji_type = emoji_type as Type; entry.emoji_type = emoji_type as Type;
writeConfig(entries); writeConfig(entries);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
await interaction.followUp({ await interaction.followUp({
content: `emoji list type set to ${emoji_type}`, content: "Failed to set emoji list type. Please try again.",
ephemeral: true, ephemeral: true,
}); });
return;
} }
await interaction.followUp({
content: `emoji list type set to ${emoji_type}`,
ephemeral: true,
});
} else if (subcommand === "score") { } else if (subcommand === "score") {
const score = interaction.options.getInteger("score", true); const score = interaction.options.getInteger("score", true);
try { try {
@ -190,27 +215,36 @@ client.on("interactionCreate", async (interaction) => {
await writeConfig(entries); await writeConfig(entries);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
await interaction.followUp({ await interaction.followUp({
content: `The score needed to trigger is now ${score}`, content: "Failed to set score threshold. Please try again.",
ephemeral: true, ephemeral: true,
}); });
return;
} }
await interaction.followUp({
content: `The score needed to trigger is now ${score}`,
ephemeral: true,
});
} }
} }
} }
}); });
import awardEmbed from "./award_message.ts"; import awardEmbed from "./award_message.ts";
client.on(Events.MessageReactionAdd, async (reaction, user) => { async function handleReactionFetch(reaction: any) {
if (reaction.partial) { if (reaction.partial) {
try { try {
await reaction.fetch(); await reaction.fetch();
} catch (error) { } catch (error) {
console.error("Something went wrong when fetching the message:", error); console.error("Something went wrong when fetching the message:", error);
return; return false;
} }
} }
return true;
}
client.on(Events.MessageReactionAdd, async (reaction, user) => {
if (!(await handleReactionFetch(reaction))) return;
if (!reaction.message.guild) return; if (!reaction.message.guild) return;
const entry = entries.find( const entry = entries.find(
@ -235,10 +269,22 @@ client.on(Events.MessageReactionAdd, async (reaction, user) => {
const channel = reaction.message.guild.channels.cache.get( const channel = reaction.message.guild.channels.cache.get(
entry.output_channel entry.output_channel
) as TextChannel; ) as TextChannel;
if (!channel) {
console.error(`Output channel ${entry.output_channel} not found`);
return;
}
if (!channel.isTextBased()) {
console.error(`Channel ${entry.output_channel} is not a text channel`);
return;
}
channel.messages.fetch({ limit: 10 }).then((messages) => { channel.messages.fetch({ limit: 10 }).then((messages) => {
messages.find((message) => { messages.find((message) => {
if (message.embeds[0]?.fields[0].value.includes(reaction.message.id)) { if (message.embeds[0]?.fields[0].value.includes(reaction.message.id)) {
message.delete(); message.delete().catch((error) => {
console.error("Failed to delete old message:", error);
});
console.log("old message deleted."); console.log("old message deleted.");
return true; return true;
} }
@ -246,21 +292,18 @@ client.on(Events.MessageReactionAdd, async (reaction, user) => {
}); });
channel.send({ channel.send({
embeds: [awardEmbed(reaction, totalScore)], embeds: [awardEmbed(reaction, totalScore)],
}).catch((error) => {
console.error("Failed to send award message:", error);
}); });
console.log("message sent."); console.log("message sent.");
}).catch((error) => {
console.error("Failed to fetch messages:", error);
}); });
} }
}); });
client.on(Events.MessageReactionRemove, async (reaction, user) => { client.on(Events.MessageReactionRemove, async (reaction, user) => {
if (reaction.partial) { if (!(await handleReactionFetch(reaction))) return;
try {
await reaction.fetch();
} catch (error) {
console.error("Something went wrong when fetching the message:", error);
return;
}
}
if (!reaction.message.guild) return; if (!reaction.message.guild) return;
const entry = entries.find( const entry = entries.find(
@ -284,14 +327,28 @@ client.on(Events.MessageReactionRemove, async (reaction, user) => {
const channel = reaction.message.guild.channels.cache.get( const channel = reaction.message.guild.channels.cache.get(
entry.output_channel entry.output_channel
) as TextChannel; ) as TextChannel;
if (!channel) {
console.error(`Output channel ${entry.output_channel} not found`);
return;
}
if (!channel.isTextBased()) {
console.error(`Channel ${entry.output_channel} is not a text channel`);
return;
}
channel.messages.fetch({ limit: 10 }).then((messages) => { channel.messages.fetch({ limit: 10 }).then((messages) => {
messages.find((message) => { messages.find((message) => {
if (message.embeds[0]?.fields[0].value.includes(reaction.message.id)) { if (message.embeds[0]?.fields[0].value.includes(reaction.message.id)) {
message.delete(); message.delete().catch((error) => {
console.error("Failed to delete old message:", error);
});
console.log("old message deleted."); console.log("old message deleted.");
if (totalScore >= entry.score) { if (totalScore >= entry.score) {
channel.send({ channel.send({
embeds: [awardEmbed(reaction, totalScore)], embeds: [awardEmbed(reaction, totalScore)],
}).catch((error) => {
console.error("Failed to send award message:", error);
}); });
console.log("message sent."); console.log("message sent.");
} }
@ -299,10 +356,15 @@ client.on(Events.MessageReactionRemove, async (reaction, user) => {
} }
return false; return false;
}); });
}).catch((error) => {
console.error("Failed to fetch messages:", error);
}); });
}); });
client.login(TOKEN); client.login(TOKEN).catch((error) => {
console.error("Failed to login to Discord:", error);
Deno.exit(1);
});
async function getMessageScore( async function getMessageScore(
entry: Entry, entry: Entry,

View File

@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2023"
],
"module": "node16",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"allowImportingTsExtensions": true,
"noEmit": true,
"outDir": "./dist",
},
"include": [
"src/**/*.ts"
]
}