From 5b13e0f9ef3cb2d540e7fcea9d3c4dee9a695653 Mon Sep 17 00:00:00 2001 From: Matthew Meredith Date: Tue, 28 Oct 2025 14:12:38 -0400 Subject: [PATCH 1/4] prevent crashes from missing values --- src/commands/jobs/jobs.ts | 2 +- src/lib/utils/jobUtils/Adzuna_job_search.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/jobs/jobs.ts b/src/commands/jobs/jobs.ts index e19b7f7f..0a4fd646 100644 --- a/src/commands/jobs/jobs.ts +++ b/src/commands/jobs/jobs.ts @@ -279,7 +279,7 @@ export default class extends Command { await db.findOne({ discordId: userID }) )?.jobPreferences; - if (!jobformAnswers) { + if (!jobformAnswers || !jobformAnswers.answers) { await interaction.reply( "You haven't set up your job preferences yet. Please use `/jobform` first." ); diff --git a/src/lib/utils/jobUtils/Adzuna_job_search.ts b/src/lib/utils/jobUtils/Adzuna_job_search.ts index 82fb2b54..012e8509 100644 --- a/src/lib/utils/jobUtils/Adzuna_job_search.ts +++ b/src/lib/utils/jobUtils/Adzuna_job_search.ts @@ -32,6 +32,10 @@ export default async function fetchJobListings(jobData: JobData, interests?: Int for (const interest of keys) { const value = interests[interest]; + if (!value) { + delete interests[interest]; + continue; + } whatInterests += value.replace(/\s+/g, '-'); // Replace spaces with dashes if (value !== lastValue) whatInterests += ' '; } From c9be7efd7ba69942dc531e97e6210b925f10b0a2 Mon Sep 17 00:00:00 2001 From: Matthew Meredith Date: Wed, 5 Nov 2025 13:35:44 -0500 Subject: [PATCH 2/4] rename command, make better help message --- src/commands/jobs/{histogram.ts => jobsalarygraph.ts} | 4 ++-- src/pieces/tasks.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/commands/jobs/{histogram.ts => jobsalarygraph.ts} (94%) diff --git a/src/commands/jobs/histogram.ts b/src/commands/jobs/jobsalarygraph.ts similarity index 94% rename from src/commands/jobs/histogram.ts rename to src/commands/jobs/jobsalarygraph.ts index ce6ffcf6..dd2923bd 100644 --- a/src/commands/jobs/histogram.ts +++ b/src/commands/jobs/jobsalarygraph.ts @@ -7,8 +7,8 @@ import { ChartConfiguration } from 'chart.js'; export default class extends Command { - description = `Get a listing of jobs based on your interests and preferences.`; - extendedHelp = `This command will return a listing of jobs based on your interests and preferences.`; + description = `View a histogram of how many jobs are available at different salary ranges.`; + extendedHelp = `See how many jobs are available at different salaries`; options: ApplicationCommandOptionData[] = [ { name: 'job', diff --git a/src/pieces/tasks.ts b/src/pieces/tasks.ts index 6834a769..61f92d7a 100644 --- a/src/pieces/tasks.ts +++ b/src/pieces/tasks.ts @@ -30,7 +30,7 @@ import nodemailer from 'nodemailer'; import { JobPreferences } from '../lib/types/JobPreferences'; import axios from 'axios'; import { PDFDocument, PDFFont, rgb, StandardFonts } from 'pdf-lib'; -import { generateHistogram } from '../commands/jobs/histogram'; +import { generateHistogram } from '../commands/jobs/jobsalarygraph'; async function register(bot: Client): Promise { schedule('0/30 * * * * *', () => { From afd57e2a877be8dbe2f6301cc9b10d9e9322e334 Mon Sep 17 00:00:00 2001 From: Matthew Meredith Date: Wed, 12 Nov 2025 11:13:00 -0500 Subject: [PATCH 3/4] fix a bug that caused "an unknown error occurred" if a command crashed that used "deferReply" --- src/pieces/commandManager.ts | 40 +++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/pieces/commandManager.ts b/src/pieces/commandManager.ts index 6006d42e..fc9c66b8 100644 --- a/src/pieces/commandManager.ts +++ b/src/pieces/commandManager.ts @@ -1,9 +1,25 @@ -import { Collection, Client, ApplicationCommand, - GuildMember, SelectMenuInteraction, - ModalSubmitInteraction, TextChannel, GuildMemberRoleManager, - ButtonInteraction, ModalBuilder, TextInputBuilder, ActionRowBuilder, - ModalActionRowComponentBuilder, ApplicationCommandType, ApplicationCommandDataResolvable, ChannelType, ApplicationCommandPermissionType, TextInputStyle, - ChatInputCommandInteraction } from 'discord.js'; +import { + Collection, + Client, + ApplicationCommand, + GuildMember, + SelectMenuInteraction, + ModalSubmitInteraction, + TextChannel, + GuildMemberRoleManager, + ButtonInteraction, + ModalBuilder, + TextInputBuilder, + ActionRowBuilder, + ModalActionRowComponentBuilder, + ApplicationCommandType, + ApplicationCommandDataResolvable, + ChannelType, + ApplicationCommandPermissionType, + TextInputStyle, + ChatInputCommandInteraction, + MessageFlags +} from 'discord.js'; import { isCmdEqual, readdirRecursive } from '@root/src/lib/utils/generalUtils'; import { Command } from '@lib/types/Command'; import { SageData } from '@lib/types/SageData'; @@ -326,14 +342,14 @@ async function runCommand(interaction: ChatInputCommandInteraction, bot: Client) if (!success) return interaction.reply(failMessages[Math.floor(Math.random() * failMessages.length)]); try { - bot.commands.get(interaction.commandName).run(interaction) - ?.catch(async (error: Error) => { // Idk if this is needed now, but keeping in case removing it breaks stuff... - bot.emit('error', new CommandError(error, interaction)); - interaction.reply({ content: `An error occurred. ${MAINTAINERS} have been notified.`, ephemeral: true }); - }); + await bot.commands.get(interaction.commandName).run(interaction); } catch (error) { bot.emit('error', new CommandError(error, interaction)); - interaction.reply({ content: `An error occurred. ${MAINTAINERS} have been notified.`, ephemeral: true }); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ content: `An error occurred. ${MAINTAINERS} have been notified.`, flags: MessageFlags.Ephemeral }); + } else { + await interaction.reply({ content: `An error occurred. ${MAINTAINERS} have been notified.`, flags: MessageFlags.Ephemeral }); + } console.log(error.errors); } } From 366aeb2eec3ad3f1400c9a8d7807c23058add7a1 Mon Sep 17 00:00:00 2001 From: Matthew Meredith Date: Wed, 12 Nov 2025 13:49:05 -0500 Subject: [PATCH 4/4] provide an error message in case the API fails --- src/commands/jobs/jobsalarygraph.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/commands/jobs/jobsalarygraph.ts b/src/commands/jobs/jobsalarygraph.ts index dd2923bd..dc0fb99b 100644 --- a/src/commands/jobs/jobsalarygraph.ts +++ b/src/commands/jobs/jobsalarygraph.ts @@ -26,16 +26,22 @@ export default class extends Command { const URL_BASE = `https://api.adzuna.com/v1/api/jobs/us/histogram?app_id=${ADZUNA_APP_ID}&app_key=${ADZUNA_APP_KEY}&what=${encodedJobTitle}`; - const response = await axios.get(URL_BASE); - const data = Object.entries(response.data.histogram).map(([value, frequency]: [string, number]) => ({ - value, - frequency - })); + try { + const response = await axios.get(URL_BASE); - const image = await generateHistogram(data, jobTitle); + const data = Object.entries(response.data.histogram).map(([value, frequency]: [string, number]) => ({ + value, + frequency + })); + const image = await generateHistogram(data, jobTitle); - await interaction.followUp({ files: [{ attachment: image, name: 'histogram.png' }] }); // Send the file as a follow-up + await interaction.followUp({ files: [{ attachment: image, name: 'histogram.png' }] }); // Send the file as a follow-up + } catch (e) { + console.error('Error fetching job salary data', e); + await interaction.followUp({ content: 'There was an issue fetching the job salary data. Please try again later.' }); + return; + } } }