diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index 5227c793..0a61c57f 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -34,13 +34,17 @@ async function banAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasBanPermission(executingMember, interaction.channelId))) { - await interactionToReply.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }); + await interactionToReply + .editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }) + .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); return; } const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interactionToReply.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }); + await interactionToReply + .editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }) + .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); return; } @@ -51,7 +55,9 @@ async function banAction( const durationMs = duration ? convertDelayStringToMS(duration)! : undefined; const result = await modactions.banUserId(target, reason, { caseArgs }, durationMs); if (result.status === "failed") { - await interactionToReply.editReply({ content: "Error: Failed to ban user", embeds: [], components: [] }); + await interactionToReply + .editReply({ content: "Error: Failed to ban user", embeds: [], components: [] }) + .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); return; } @@ -61,7 +67,9 @@ async function banAction( durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" } (Case #${result.case.case_number})${messageResultText}`; - await interactionToReply.editReply({ content: banMessage, embeds: [], components: [] }); + await interactionToReply + .editReply({ content: banMessage, embeds: [], components: [] }) + .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); } export async function launchBanActionModal( @@ -90,9 +98,11 @@ export async function launchBanActionModal( .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) .then(async (submitted) => { if (interaction.isButton()) { - await submitted.deferUpdate(); + await submitted.deferUpdate().catch((err) => logger.error(`Ban interaction defer failed: ${err}`)); } else if (interaction.isContextMenuCommand()) { - await submitted.deferReply({ ephemeral: true }); + await submitted + .deferReply({ ephemeral: true }) + .catch((err) => logger.error(`Ban interaction defer failed: ${err}`)); } const duration = submitted.fields.getTextInputValue("duration"); diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index 13795f04..628451c6 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -19,16 +19,20 @@ export async function cleanAction( const utility = pluginData.getPlugin(UtilityPlugin); if (!userCfg.can_use || !(await utility.hasPermission(executingMember, interaction.channelId, "can_clean"))) { - await interaction.editReply({ content: "Cannot clean: insufficient permissions", embeds: [], components: [] }); + await interaction + .editReply({ content: "Cannot clean: insufficient permissions", embeds: [], components: [] }) + .catch((err) => logger.error(`Clean interaction reply failed: ${err}`)); return; } // TODO: Implement message cleaning - await interaction.editReply({ - content: `TODO: Implementation incomplete`, - embeds: [], - components: [], - }); + await interaction + .editReply({ + content: `TODO: Implementation incomplete`, + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Clean interaction reply failed: ${err}`)); } export async function launchCleanActionModal( @@ -46,11 +50,13 @@ export async function launchCleanActionModal( await interaction .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) .then(async (submitted) => { - await submitted.deferUpdate(); + await submitted.deferUpdate().catch((err) => logger.error(`Clean interaction defer failed: ${err}`)); const amount = submitted.fields.getTextInputValue("amount"); if (isNaN(Number(amount))) { - interaction.editReply({ content: `Error: Amount '${amount}' is invalid`, embeds: [], components: [] }); + interaction + .editReply({ content: `Error: Amount '${amount}' is invalid`, embeds: [], components: [] }) + .catch((err) => logger.error(`Clean interaction reply failed: ${err}`)); return; } diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index ad98d544..b0ea8776 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -37,21 +37,25 @@ async function muteAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasMutePermission(executingMember, interaction.channelId))) { - await interactionToReply.editReply({ - content: "Cannot mute: insufficient permissions", - embeds: [], - components: [], - }); + await interactionToReply + .editReply({ + content: "Cannot mute: insufficient permissions", + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); return; } const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interactionToReply.editReply({ - content: "Cannot mute: insufficient permissions", - embeds: [], - components: [], - }); + await interactionToReply + .editReply({ + content: "Cannot mute: insufficient permissions", + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); return; } @@ -69,13 +73,17 @@ async function muteAction( durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" } (Case #${result.case.case_number})${messageResultText}`; - await interactionToReply.editReply({ content: muteMessage, embeds: [], components: [] }); + await interactionToReply + .editReply({ content: muteMessage, embeds: [], components: [] }) + .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); } catch (e) { - await interactionToReply.editReply({ - content: "Plugin error, please check your BOT_ALERTs", - embeds: [], - components: [], - }); + await interactionToReply + .editReply({ + content: "Plugin error, please check your BOT_ALERTs", + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { pluginData.getPlugin(LogsPlugin).logBotAlert({ @@ -113,9 +121,11 @@ export async function launchMuteActionModal( .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) .then(async (submitted) => { if (interaction.isButton()) { - await submitted.deferUpdate(); + await submitted.deferUpdate().catch((err) => logger.error(`Mute interaction defer failed: ${err}`)); } else if (interaction.isContextMenuCommand()) { - await submitted.deferReply({ ephemeral: true }); + await submitted + .deferReply({ ephemeral: true }) + .catch((err) => logger.error(`Mute interaction defer failed: ${err}`)); } const duration = submitted.fields.getTextInputValue("duration"); diff --git a/backend/src/plugins/ContextMenus/actions/note.ts b/backend/src/plugins/ContextMenus/actions/note.ts index ecc48d6e..b6911274 100644 --- a/backend/src/plugins/ContextMenus/actions/note.ts +++ b/backend/src/plugins/ContextMenus/actions/note.ts @@ -34,21 +34,25 @@ async function noteAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasNotePermission(executingMember, interaction.channelId))) { - await interactionToReply.editReply({ - content: "Cannot note: insufficient permissions", - embeds: [], - components: [], - }); + await interactionToReply + .editReply({ + content: "Cannot note: insufficient permissions", + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Note interaction reply failed: ${err}`)); return; } const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interactionToReply.editReply({ - content: "Cannot note: insufficient permissions", - embeds: [], - components: [], - }); + await interactionToReply + .editReply({ + content: "Cannot note: insufficient permissions", + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Note interaction reply failed: ${err}`)); return; } @@ -68,11 +72,13 @@ async function noteAction( }); const userName = renderUserUsername(targetMember.user); - await interactionToReply.editReply({ - content: `Note added on **${userName}** (Case #${createdCase.case_number})`, - embeds: [], - components: [], - }); + await interactionToReply + .editReply({ + content: `Note added on **${userName}** (Case #${createdCase.case_number})`, + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Note interaction reply failed: ${err}`)); } export async function launchNoteActionModal( @@ -91,9 +97,11 @@ export async function launchNoteActionModal( .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) .then(async (submitted) => { if (interaction.isButton()) { - await submitted.deferUpdate(); + await submitted.deferUpdate().catch((err) => logger.error(`Note interaction defer failed: ${err}`)); } else if (interaction.isContextMenuCommand()) { - await submitted.deferReply({ ephemeral: true }); + await submitted + .deferReply({ ephemeral: true }) + .catch((err) => logger.error(`Note interaction defer failed: ${err}`)); } const reason = submitted.fields.getTextInputValue("reason"); diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts index 9afbf44e..d8eba234 100644 --- a/backend/src/plugins/ContextMenus/actions/warn.ts +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -32,21 +32,25 @@ async function warnAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasWarnPermission(executingMember, interaction.channelId))) { - await interactionToReply.editReply({ - content: "Cannot warn: insufficient permissions", - embeds: [], - components: [], - }); + await interactionToReply + .editReply({ + content: "Cannot warn: insufficient permissions", + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); return; } const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interactionToReply.editReply({ - content: "Cannot warn: insufficient permissions", - embeds: [], - components: [], - }); + await interactionToReply + .editReply({ + content: "Cannot warn: insufficient permissions", + embeds: [], + components: [], + }) + .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); return; } @@ -56,7 +60,9 @@ async function warnAction( const result = await modactions.warnMember(targetMember, reason, { caseArgs }); if (result.status === "failed") { - await interactionToReply.editReply({ content: "Error: Failed to warn user", embeds: [], components: [] }); + await interactionToReply + .editReply({ content: "Error: Failed to warn user", embeds: [], components: [] }) + .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); return; } @@ -64,7 +70,9 @@ async function warnAction( const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : ""; const muteMessage = `Warned **${userName}** (Case #${result.case.case_number})${messageResultText}`; - await interactionToReply.editReply({ content: muteMessage, embeds: [], components: [] }); + await interactionToReply + .editReply({ content: muteMessage, embeds: [], components: [] }) + .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); } export async function launchWarnActionModal( @@ -83,14 +91,16 @@ export async function launchWarnActionModal( .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) .then(async (submitted) => { if (interaction.isButton()) { - await submitted.deferUpdate(); + await submitted.deferUpdate().catch((err) => logger.error(`Warn interaction defer failed: ${err}`)); } else if (interaction.isContextMenuCommand()) { - await submitted.deferReply({ ephemeral: true }); + await submitted + .deferReply({ ephemeral: true }) + .catch((err) => logger.error(`Warn interaction defer failed: ${err}`)); } const reason = submitted.fields.getTextInputValue("reason"); await warnAction(pluginData, reason, target, interaction, submitted); }) - .catch((err) => logger.error(`Mute modal interaction failed: ${err}`)); + .catch((err) => logger.error(`Warn modal interaction failed: ${err}`)); } diff --git a/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts index e8a51556..da8a9014 100644 --- a/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts @@ -9,6 +9,7 @@ import { } from "discord.js"; import { GuildPluginData, guildPluginUserContextMenuCommand } from "knub"; import { Case } from "../../../data/entities/Case"; +import { logger } from "../../../logger"; import { SECONDS, UnknownUser, emptyEmbedValue, renderUserUsername, resolveUser, trimLines } from "../../../utils"; import { asyncMap } from "../../../utils/async"; import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; @@ -36,7 +37,9 @@ const CASES_PER_PAGE = 10; export const ModMenuCmd = guildPluginUserContextMenuCommand({ name: "Mod Menu", async run({ pluginData, interaction }) { - await interaction.deferReply({ ephemeral: true }); + await interaction + .deferReply({ ephemeral: true }) + .catch((err) => logger.error(`Mod menu interaction defer failed: ${err}`)); // Run permission checks for executing user. const executingMember = await pluginData.guild.members.fetch(interaction.user.id); @@ -49,13 +52,17 @@ export const ModMenuCmd = guildPluginUserContextMenuCommand({ !userCfg.can_use || (await !utility.hasPermission(executingMember, interaction.channelId, "can_open_mod_menu")) ) { - await interaction.followUp({ content: "Error: Insufficient Permissions" }); + await interaction + .followUp({ content: "Error: Insufficient Permissions" }) + .catch((err) => logger.error(`Mod menu interaction follow up failed: ${err}`)); return; } const user = await resolveUser(pluginData.client, interaction.targetId); if (!user.id) { - await interaction.followUp("Error: User not found"); + await interaction + .followUp("Error: User not found") + .catch((err) => logger.error(`Mod menu interaction follow up failed: ${err}`)); return; } @@ -120,7 +127,7 @@ async function displayModMenu( infoEmbed: APIEmbed | null, ) { if (interaction.deferred == false) { - await interaction.deferReply(); + await interaction.deferReply().catch((err) => logger.error(`Mod menu interaction defer failed: ${err}`)); } const firstButton = new ButtonBuilder() @@ -177,98 +184,109 @@ async function displayModMenu( const moderationRow = new ActionRowBuilder<ButtonBuilder>().addComponents(moderationButtons); let page = 1; - const currentPage = await interaction.editReply({ - embeds: [await loadPage(page)], - components: [navigationRow, moderationRow], - }); - - const collector = await currentPage.createMessageComponentCollector({ - time: MOD_MENU_TIMEOUT, - }); - - collector.on("collect", async (i) => { - const opts = deserializeCustomId(i.customId); - if (opts.action == ModMenuActionType.PAGE) { - await i.deferUpdate(); - } - - // Update displayed embed if any navigation buttons were used - if (opts.action == ModMenuActionType.PAGE && opts.target == ModMenuNavigationType.INFO && infoEmbed != null) { - infoButton - .setLabel("Cases") - .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.CASES })); - firstButton.setDisabled(true); - prevButton.setDisabled(true); - nextButton.setDisabled(true); - lastButton.setDisabled(true); - - await i.editReply({ - embeds: [infoEmbed], - components: [navigationRow, moderationRow], + await interaction + .editReply({ + embeds: [await loadPage(page)], + components: [navigationRow, moderationRow], + }) + .then(async (currentPage) => { + const collector = await currentPage.createMessageComponentCollector({ + time: MOD_MENU_TIMEOUT, }); - } else if (opts.action == ModMenuActionType.PAGE && opts.target == ModMenuNavigationType.CASES) { - infoButton - .setLabel("Info") - .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.INFO })); - updateNavButtonState(firstButton, prevButton, nextButton, lastButton, page, totalPages); - await i.editReply({ - embeds: [await loadPage(page)], - components: [navigationRow, moderationRow], + collector.on("collect", async (i) => { + const opts = deserializeCustomId(i.customId); + if (opts.action == ModMenuActionType.PAGE) { + await i.deferUpdate().catch((err) => logger.error(`Mod menu defer failed: ${err}`)); + } + + // Update displayed embed if any navigation buttons were used + if (opts.action == ModMenuActionType.PAGE && opts.target == ModMenuNavigationType.INFO && infoEmbed != null) { + infoButton + .setLabel("Cases") + .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.CASES })); + firstButton.setDisabled(true); + prevButton.setDisabled(true); + nextButton.setDisabled(true); + lastButton.setDisabled(true); + + await i + .editReply({ + embeds: [infoEmbed], + components: [navigationRow, moderationRow], + }) + .catch((err) => logger.error(`Mod menu info view failed: ${err}`)); + } else if (opts.action == ModMenuActionType.PAGE && opts.target == ModMenuNavigationType.CASES) { + infoButton + .setLabel("Info") + .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.INFO })); + updateNavButtonState(firstButton, prevButton, nextButton, lastButton, page, totalPages); + + await i + .editReply({ + embeds: [await loadPage(page)], + components: [navigationRow, moderationRow], + }) + .catch((err) => logger.error(`Mod menu cases view failed: ${err}`)); + } else if (opts.action == ModMenuActionType.PAGE) { + let pageDelta = 0; + switch (opts.target) { + case ModMenuNavigationType.PREV: + pageDelta = -1; + break; + case ModMenuNavigationType.NEXT: + pageDelta = 1; + break; + } + + let newPage = 1; + if (opts.target == ModMenuNavigationType.PREV || opts.target == ModMenuNavigationType.NEXT) { + newPage = Math.max(Math.min(page + pageDelta, totalPages), 1); + } else if (opts.target == ModMenuNavigationType.FIRST) { + newPage = 1; + } else if (opts.target == ModMenuNavigationType.LAST) { + newPage = totalPages; + } + + if (newPage != page) { + updateNavButtonState(firstButton, prevButton, nextButton, lastButton, newPage, totalPages); + + await i + .editReply({ + embeds: [await loadPage(newPage)], + components: [navigationRow, moderationRow], + }) + .catch((err) => logger.error(`Mod menu navigation failed: ${err}`)); + + page = newPage; + } + } else if (opts.action == ModMenuActionType.NOTE) { + await launchNoteActionModal(pluginData, i as ButtonInteraction, opts.target); + } else if (opts.action == ModMenuActionType.WARN) { + await launchWarnActionModal(pluginData, i as ButtonInteraction, opts.target); + } else if (opts.action == ModMenuActionType.CLEAN) { + await launchCleanActionModal(pluginData, i as ButtonInteraction, opts.target); + } else if (opts.action == ModMenuActionType.MUTE) { + await launchMuteActionModal(pluginData, i as ButtonInteraction, opts.target); + } else if (opts.action == ModMenuActionType.BAN) { + await launchBanActionModal(pluginData, i as ButtonInteraction, opts.target); + } + + collector.resetTimer(); }); - } else if (opts.action == ModMenuActionType.PAGE) { - let pageDelta = 0; - switch (opts.target) { - case ModMenuNavigationType.PREV: - pageDelta = -1; - break; - case ModMenuNavigationType.NEXT: - pageDelta = 1; - break; - } - let newPage = 1; - if (opts.target == ModMenuNavigationType.PREV || opts.target == ModMenuNavigationType.NEXT) { - newPage = Math.max(Math.min(page + pageDelta, totalPages), 1); - } else if (opts.target == ModMenuNavigationType.FIRST) { - newPage = 1; - } else if (opts.target == ModMenuNavigationType.LAST) { - newPage = totalPages; - } - - if (newPage != page) { - updateNavButtonState(firstButton, prevButton, nextButton, lastButton, newPage, totalPages); - - await i.editReply({ - embeds: [await loadPage(newPage)], - components: [navigationRow, moderationRow], - }); - - page = newPage; - } - } else if (opts.action == ModMenuActionType.NOTE) { - await launchNoteActionModal(pluginData, i as ButtonInteraction, opts.target); - } else if (opts.action == ModMenuActionType.WARN) { - await launchWarnActionModal(pluginData, i as ButtonInteraction, opts.target); - } else if (opts.action == ModMenuActionType.CLEAN) { - await launchCleanActionModal(pluginData, i as ButtonInteraction, opts.target); - } else if (opts.action == ModMenuActionType.MUTE) { - await launchMuteActionModal(pluginData, i as ButtonInteraction, opts.target); - } else if (opts.action == ModMenuActionType.BAN) { - await launchBanActionModal(pluginData, i as ButtonInteraction, opts.target); - } - - collector.resetTimer(); - }); - - // Remove components on timeout. - collector.on("end", async (_, reason) => { - if (reason !== "messageDelete") { - interaction.editReply({ - components: [], + // Remove components on timeout. + collector.on("end", async (_, reason) => { + if (reason !== "messageDelete") { + await interaction + .editReply({ + components: [], + }) + .catch((err) => logger.error(`Mod menu timeout failed: ${err}`)); + } }); - } - }); + }) + .catch((err) => logger.error(`Mod menu setup failed: ${err}`)); } function serializeCustomId(opts: ModMenuActionOpts) {