diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts index b48cc0ea..5461409d 100644 --- a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts +++ b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts @@ -8,7 +8,11 @@ import { ModActionsPlugin } from "../ModActions/ModActionsPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin"; import { UtilityPlugin } from "../Utility/UtilityPlugin"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ModMenuCmd } from "./commands/ModMenuCmd"; +import { BanCmd } from "./commands/BanUserCtxCmd"; +import { ModMenuCmd } from "./commands/ModMenuUserCtxCmd"; +import { MuteCmd } from "./commands/MuteUserCtxCmd"; +import { NoteCmd } from "./commands/NoteUserCtxCmd"; +import { WarnCmd } from "./commands/WarnUserCtxCmd"; import { ConfigSchema, ContextMenuPluginType } from "./types"; const defaultOptions: PluginOptions = { @@ -45,7 +49,7 @@ export const ContextMenuPlugin = zeppelinGuildPlugin()({ defaultOptions, - contextMenuCommands: [ModMenuCmd], + contextMenuCommands: [ModMenuCmd, NoteCmd, WarnCmd, MuteCmd, BanCmd], beforeLoad(pluginData) { const { state, guild } = pluginData; diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index bfe7bc13..bfc756d2 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -1,6 +1,7 @@ import { ActionRowBuilder, ButtonInteraction, + ContextMenuCommandInteraction, ModalBuilder, ModalSubmitInteraction, TextInputBuilder, @@ -12,7 +13,7 @@ import { canActOn } from "src/pluginUtils"; import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; import { convertDelayStringToMS, renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; -import { MODAL_TIMEOUT } from "../commands/ModMenuCmd"; +import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType } from "../types"; async function banAction( @@ -20,8 +21,10 @@ async function banAction( duration: string | undefined, reason: string | undefined, target: string, - interaction: ButtonInteraction, + interaction: ButtonInteraction | ContextMenuCommandInteraction, + submitInteraction: ModalSubmitInteraction, ) { + const interactionToReply = interaction instanceof ButtonInteraction ? interaction : submitInteraction; const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -30,13 +33,13 @@ async function banAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasBanPermission(executingMember, interaction.channelId))) { - await interaction.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }); + await interactionToReply.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }); return; } const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interaction.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }); + await interactionToReply.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }); return; } @@ -47,7 +50,7 @@ async function banAction( const durationMs = duration ? convertDelayStringToMS(duration)! : undefined; const result = await modactions.banUserId(target, reason, { caseArgs }, durationMs); if (result.status === "failed") { - await interaction.editReply({ content: "Error: Failed to ban user", embeds: [], components: [] }); + await interactionToReply.editReply({ content: "Error: Failed to ban user", embeds: [], components: [] }); return; } @@ -57,12 +60,12 @@ async function banAction( durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" } (Case #${result.case.case_number})${messageResultText}`; - await interaction.editReply({ content: banMessage, embeds: [], components: [] }); + await interactionToReply.editReply({ content: banMessage, embeds: [], components: [] }); } export async function launchBanActionModal( pluginData: GuildPluginData, - interaction: ButtonInteraction, + interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { const modal = new ModalBuilder().setCustomId("ban").setTitle("Ban"); @@ -83,11 +86,15 @@ export async function launchBanActionModal( await interaction.showModal(modal); const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); if (submitted) { - await submitted.deferUpdate(); + if (interaction instanceof ButtonInteraction) { + await submitted.deferUpdate(); + } else { + await submitted.deferReply({ ephemeral: true }); + } const duration = submitted.fields.getTextInputValue("duration"); const reason = submitted.fields.getTextInputValue("reason"); - await banAction(pluginData, duration, reason, target, interaction); + await banAction(pluginData, duration, reason, target, interaction, submitted); } } diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index 3d005bfb..34ae2d14 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -8,7 +8,7 @@ import { } from "discord.js"; import { GuildPluginData } from "knub"; import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; -import { MODAL_TIMEOUT } from "../commands/ModMenuCmd"; +import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType } from "../types"; export async function cleanAction( diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index 90a04575..28a988c8 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -1,6 +1,7 @@ import { ActionRowBuilder, ButtonInteraction, + ContextMenuCommandInteraction, ModalBuilder, ModalSubmitInteraction, TextInputBuilder, @@ -15,7 +16,7 @@ import { convertDelayStringToMS } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { MutesPlugin } from "../../Mutes/MutesPlugin"; -import { MODAL_TIMEOUT } from "../commands/ModMenuCmd"; +import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType } from "../types"; async function muteAction( @@ -23,8 +24,10 @@ async function muteAction( duration: string | undefined, reason: string | undefined, target: string, - interaction: ButtonInteraction, + interaction: ButtonInteraction | ContextMenuCommandInteraction, + submitInteraction: ModalSubmitInteraction, ) { + const interactionToReply = interaction instanceof ButtonInteraction ? interaction : submitInteraction; const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -33,13 +36,21 @@ async function muteAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasMutePermission(executingMember, interaction.channelId))) { - await interaction.editReply({ content: "Cannot mute: insufficient permissions", embeds: [], components: [] }); + await interactionToReply.editReply({ + content: "Cannot mute: insufficient permissions", + embeds: [], + components: [], + }); return; } const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interaction.editReply({ content: "Cannot mute: insufficient permissions", embeds: [], components: [] }); + await interactionToReply.editReply({ + content: "Cannot mute: insufficient permissions", + embeds: [], + components: [], + }); return; } @@ -57,9 +68,13 @@ async function muteAction( durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" } (Case #${result.case.case_number})${messageResultText}`; - await interaction.editReply({ content: muteMessage, embeds: [], components: [] }); + await interactionToReply.editReply({ content: muteMessage, embeds: [], components: [] }); } catch (e) { - await interaction.editReply({ content: "Plugin error, please check your BOT_ALERTs", embeds: [], components: [] }); + await interactionToReply.editReply({ + content: "Plugin error, please check your BOT_ALERTs", + embeds: [], + components: [], + }); if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { pluginData.getPlugin(LogsPlugin).logBotAlert({ @@ -73,7 +88,7 @@ async function muteAction( export async function launchMuteActionModal( pluginData: GuildPluginData, - interaction: ButtonInteraction, + interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { const modal = new ModalBuilder().setCustomId("mute").setTitle("Mute"); @@ -94,11 +109,15 @@ export async function launchMuteActionModal( await interaction.showModal(modal); const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); if (submitted) { - await submitted.deferUpdate(); + if (interaction instanceof ButtonInteraction) { + await submitted.deferUpdate(); + } else { + await submitted.deferReply({ ephemeral: true }); + } const duration = submitted.fields.getTextInputValue("duration"); const reason = submitted.fields.getTextInputValue("reason"); - await muteAction(pluginData, duration, reason, target, interaction); + await muteAction(pluginData, duration, reason, target, interaction, submitted); } } diff --git a/backend/src/plugins/ContextMenus/actions/note.ts b/backend/src/plugins/ContextMenus/actions/note.ts index 84b0e2bb..51a018ca 100644 --- a/backend/src/plugins/ContextMenus/actions/note.ts +++ b/backend/src/plugins/ContextMenus/actions/note.ts @@ -1,6 +1,7 @@ import { ActionRowBuilder, ButtonInteraction, + ContextMenuCommandInteraction, ModalBuilder, ModalSubmitInteraction, TextInputBuilder, @@ -13,15 +14,17 @@ import { CaseTypes } from "../../../data/CaseTypes"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; import { renderUserUsername } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { MODAL_TIMEOUT } from "../commands/ModMenuCmd"; +import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType } from "../types"; async function noteAction( pluginData: GuildPluginData, reason: string, target: string, - interaction: ButtonInteraction, + interaction: ButtonInteraction | ContextMenuCommandInteraction, + submitInteraction: ModalSubmitInteraction, ) { + const interactionToReply = interaction instanceof ButtonInteraction ? interaction : submitInteraction; const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -30,13 +33,21 @@ async function noteAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasNotePermission(executingMember, interaction.channelId))) { - await interaction.editReply({ content: "Cannot note: insufficient permissions", embeds: [], components: [] }); + await interactionToReply.editReply({ + content: "Cannot note: insufficient permissions", + embeds: [], + components: [], + }); return; } const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interaction.editReply({ content: "Cannot note: insufficient permissions", embeds: [], components: [] }); + await interactionToReply.editReply({ + content: "Cannot note: insufficient permissions", + embeds: [], + components: [], + }); return; } @@ -56,7 +67,7 @@ async function noteAction( }); const userName = renderUserUsername(targetMember.user); - await interaction.editReply({ + await interactionToReply.editReply({ content: `Note added on **${userName}** (Case #${createdCase.case_number})`, embeds: [], components: [], @@ -65,7 +76,7 @@ async function noteAction( export async function launchNoteActionModal( pluginData: GuildPluginData, - interaction: ButtonInteraction, + interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { const modal = new ModalBuilder().setCustomId("note").setTitle("Note"); @@ -76,10 +87,14 @@ export async function launchNoteActionModal( await interaction.showModal(modal); const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); if (submitted) { - await submitted.deferUpdate(); + if (interaction instanceof ButtonInteraction) { + await submitted.deferUpdate(); + } else { + await submitted.deferReply({ ephemeral: true }); + } const reason = submitted.fields.getTextInputValue("reason"); - await noteAction(pluginData, reason, target, interaction); + await noteAction(pluginData, reason, target, interaction, submitted); } } diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts index c6a0c8ca..4ec0cf43 100644 --- a/backend/src/plugins/ContextMenus/actions/warn.ts +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -1,6 +1,7 @@ import { ActionRowBuilder, ButtonInteraction, + ContextMenuCommandInteraction, ModalBuilder, ModalSubmitInteraction, TextInputBuilder, @@ -11,15 +12,17 @@ import { canActOn } from "src/pluginUtils"; import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; import { renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; -import { MODAL_TIMEOUT } from "../commands/ModMenuCmd"; +import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType } from "../types"; async function warnAction( pluginData: GuildPluginData, reason: string, target: string, - interaction: ButtonInteraction, + interaction: ButtonInteraction | ContextMenuCommandInteraction, + submitInteraction: ModalSubmitInteraction, ) { + const interactionToReply = interaction instanceof ButtonInteraction ? interaction : submitInteraction; const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -28,13 +31,21 @@ async function warnAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasWarnPermission(executingMember, interaction.channelId))) { - await interaction.editReply({ content: "Cannot warn: insufficient permissions", embeds: [], components: [] }); + await interactionToReply.editReply({ + content: "Cannot warn: insufficient permissions", + embeds: [], + components: [], + }); return; } const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interaction.editReply({ content: "Cannot warn: insufficient permissions", embeds: [], components: [] }); + await interactionToReply.editReply({ + content: "Cannot warn: insufficient permissions", + embeds: [], + components: [], + }); return; } @@ -44,7 +55,7 @@ async function warnAction( const result = await modactions.warnMember(targetMember, reason, { caseArgs }); if (result.status === "failed") { - await interaction.editReply({ content: "Error: Failed to warn user", embeds: [], components: [] }); + await interactionToReply.editReply({ content: "Error: Failed to warn user", embeds: [], components: [] }); return; } @@ -52,12 +63,12 @@ async function warnAction( const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : ""; const muteMessage = `Warned **${userName}** (Case #${result.case.case_number})${messageResultText}`; - await interaction.editReply({ content: muteMessage, embeds: [], components: [] }); + await interactionToReply.editReply({ content: muteMessage, embeds: [], components: [] }); } export async function launchWarnActionModal( pluginData: GuildPluginData, - interaction: ButtonInteraction, + interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { const modal = new ModalBuilder().setCustomId("warn").setTitle("Warn"); @@ -68,10 +79,14 @@ export async function launchWarnActionModal( await interaction.showModal(modal); const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); if (submitted) { - await submitted.deferUpdate(); + if (interaction instanceof ButtonInteraction) { + await submitted.deferUpdate(); + } else { + await submitted.deferReply({ ephemeral: true }); + } const reason = submitted.fields.getTextInputValue("reason"); - await warnAction(pluginData, reason, target, interaction); + await warnAction(pluginData, reason, target, interaction, submitted); } } diff --git a/backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts new file mode 100644 index 00000000..237d81cb --- /dev/null +++ b/backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts @@ -0,0 +1,9 @@ +import { guildPluginUserContextMenuCommand } from "knub"; +import { launchBanActionModal } from "../actions/ban"; + +export const BanCmd = guildPluginUserContextMenuCommand({ + name: "Ban", + async run({ pluginData, interaction }) { + await launchBanActionModal(pluginData, interaction, interaction.targetId); + }, +}); diff --git a/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts similarity index 99% rename from backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts rename to backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts index 1150893c..e8a51556 100644 --- a/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts @@ -9,13 +9,13 @@ import { } from "discord.js"; import { GuildPluginData, guildPluginUserContextMenuCommand } from "knub"; import { Case } from "../../../data/entities/Case"; -import { getUserInfoEmbed } from "../../../plugins/Utility/functions/getUserInfoEmbed"; import { SECONDS, UnknownUser, emptyEmbedValue, renderUserUsername, resolveUser, trimLines } from "../../../utils"; import { asyncMap } from "../../../utils/async"; import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; import { getGuildPrefix } from "../../../utils/getGuildPrefix"; import { CasesPlugin } from "../../Cases/CasesPlugin"; import { UtilityPlugin } from "../../Utility/UtilityPlugin"; +import { getUserInfoEmbed } from "../../Utility/functions/getUserInfoEmbed"; import { launchBanActionModal } from "../actions/ban"; import { launchCleanActionModal } from "../actions/clean"; import { launchMuteActionModal } from "../actions/mute"; diff --git a/backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts new file mode 100644 index 00000000..3c060cb3 --- /dev/null +++ b/backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts @@ -0,0 +1,9 @@ +import { guildPluginUserContextMenuCommand } from "knub"; +import { launchMuteActionModal } from "../actions/mute"; + +export const MuteCmd = guildPluginUserContextMenuCommand({ + name: "Mute", + async run({ pluginData, interaction }) { + await launchMuteActionModal(pluginData, interaction, interaction.targetId); + }, +}); diff --git a/backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts new file mode 100644 index 00000000..c4f0fa9d --- /dev/null +++ b/backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts @@ -0,0 +1,9 @@ +import { guildPluginUserContextMenuCommand } from "knub"; +import { launchNoteActionModal } from "../actions/note"; + +export const NoteCmd = guildPluginUserContextMenuCommand({ + name: "Note", + async run({ pluginData, interaction }) { + await launchNoteActionModal(pluginData, interaction, interaction.targetId); + }, +}); diff --git a/backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts new file mode 100644 index 00000000..3f62196c --- /dev/null +++ b/backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts @@ -0,0 +1,9 @@ +import { guildPluginUserContextMenuCommand } from "knub"; +import { launchWarnActionModal } from "../actions/warn"; + +export const WarnCmd = guildPluginUserContextMenuCommand({ + name: "Warn", + async run({ pluginData, interaction }) { + await launchWarnActionModal(pluginData, interaction, interaction.targetId); + }, +});