From 771ed76f64e0c330259dbce3a8c6d4c3a7fc14db Mon Sep 17 00:00:00 2001 From: Obliie Date: Sat, 15 Jul 2023 21:34:26 +0100 Subject: [PATCH 01/29] feat: Context menu mod menu command --- backend/src/data/GuildCases.ts | 15 + .../plugins/ContextMenus/ContextMenuPlugin.ts | 40 ++- .../src/plugins/ContextMenus/actions/ban.ts | 97 ++++++ .../src/plugins/ContextMenus/actions/clean.ts | 72 ++-- .../src/plugins/ContextMenus/actions/mute.ts | 78 ++++- .../src/plugins/ContextMenus/actions/note.ts | 88 +++++ .../plugins/ContextMenus/actions/userInfo.ts | 28 -- .../src/plugins/ContextMenus/actions/warn.ts | 80 +++++ .../ContextMenus/commands/ModMenuCmd.ts | 319 ++++++++++++++++++ .../ContextMenus/events/ContextClickedEvt.ts | 12 - backend/src/plugins/ContextMenus/types.ts | 49 ++- .../ContextMenus/utils/contextRouter.ts | 13 - .../utils/hardcodedContextOptions.ts | 23 -- .../ContextMenus/utils/loadAllCommands.ts | 39 --- .../plugins/ModActions/ModActionsPlugin.ts | 29 +- .../ModActions/functions/hasModActionPerm.ts | 35 ++ .../ModActions/functions/hasMutePerm.ts | 11 - 17 files changed, 821 insertions(+), 207 deletions(-) create mode 100644 backend/src/plugins/ContextMenus/actions/ban.ts create mode 100644 backend/src/plugins/ContextMenus/actions/note.ts delete mode 100644 backend/src/plugins/ContextMenus/actions/userInfo.ts create mode 100644 backend/src/plugins/ContextMenus/actions/warn.ts create mode 100644 backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts delete mode 100644 backend/src/plugins/ContextMenus/events/ContextClickedEvt.ts delete mode 100644 backend/src/plugins/ContextMenus/utils/contextRouter.ts delete mode 100644 backend/src/plugins/ContextMenus/utils/hardcodedContextOptions.ts delete mode 100644 backend/src/plugins/ContextMenus/utils/loadAllCommands.ts create mode 100644 backend/src/plugins/ModActions/functions/hasModActionPerm.ts delete mode 100644 backend/src/plugins/ModActions/functions/hasMutePerm.ts diff --git a/backend/src/data/GuildCases.ts b/backend/src/data/GuildCases.ts index d0e7778a1..eb5ed6b72 100644 --- a/backend/src/data/GuildCases.ts +++ b/backend/src/data/GuildCases.ts @@ -83,6 +83,21 @@ export class GuildCases extends BaseGuildRepository { }); } + async getRecentByUserId(userId: string, count: number, skip = 0): Promise { + return this.cases.find({ + relations: this.getRelations(), + where: { + guild_id: this.guildId, + user_id: userId, + }, + skip, + take: count, + order: { + case_number: "DESC", + }, + }); + } + async getTotalCasesByModId(modId: string): Promise { return this.cases.count({ where: { diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts index c41e8c09c..dc08389e8 100644 --- a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts +++ b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts @@ -1,32 +1,30 @@ import { PluginOptions } from "knub"; -import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks"; +import { GuildCases } from "../../data/GuildCases"; import { makeIoTsConfigParser } from "../../pluginUtils"; +import { trimPluginDescription } from "../../utils"; +import { CasesPlugin } from "../Cases/CasesPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin"; import { UtilityPlugin } from "../Utility/UtilityPlugin"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ContextClickedEvt } from "./events/ContextClickedEvt"; +import { ModMenuCmd } from "./commands/ModMenuCmd"; import { ConfigSchema, ContextMenuPluginType } from "./types"; -import { loadAllCommands } from "./utils/loadAllCommands"; const defaultOptions: PluginOptions = { config: { can_use: false, - user_muteindef: false, - user_mute1d: false, - user_mute1h: false, - user_info: false, + can_open_mod_menu: false, - message_clean10: false, - message_clean25: false, - message_clean50: false, + log_channel: null, }, overrides: [ { level: ">=50", config: { can_use: true, + + can_open_mod_menu: true, }, }, ], @@ -34,24 +32,24 @@ const defaultOptions: PluginOptions = { export const ContextMenuPlugin = zeppelinGuildPlugin()({ name: "context_menu", - showInDocs: false, + showInDocs: true, + info: { + prettyName: "Context Menus", + description: trimPluginDescription(` + This plugin provides command shortcuts via context menus + `), + configSchema: ConfigSchema, + }, - dependencies: () => [MutesPlugin, LogsPlugin, UtilityPlugin], + dependencies: () => [CasesPlugin, MutesPlugin, LogsPlugin, UtilityPlugin], configParser: makeIoTsConfigParser(ConfigSchema), defaultOptions, - // prettier-ignore - events: [ - ContextClickedEvt, - ], + contextMenuCommands: [ModMenuCmd], beforeLoad(pluginData) { const { state, guild } = pluginData; - state.contextMenuLinks = new GuildContextMenuLinks(guild.id); - }, - - afterLoad(pluginData) { - loadAllCommands(pluginData); + state.cases = GuildCases.getGuildInstance(guild.id); }, }); diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts new file mode 100644 index 000000000..38ec91a1d --- /dev/null +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -0,0 +1,97 @@ +import { + ActionRowBuilder, + ButtonInteraction, + ModalBuilder, + ModalSubmitInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; +import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +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 { ContextMenuPluginType } from "../types"; + +async function banAction( + pluginData: GuildPluginData, + duration: string | undefined, + reason: string | undefined, + target: string, + interaction: ButtonInteraction, +) { + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const userCfg = await pluginData.config.getMatchingConfig({ + channelId: interaction.channelId, + member: executingMember, + }); + + 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: [] }); + return; + } + + const targetMember = await pluginData.guild.members.fetch(target); + if (!canActOn(pluginData, executingMember, targetMember)) { + await interaction.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }); + return; + } + + const caseArgs: Partial = { + modId: executingMember.id, + }; + + 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: [] }); + return; + } + + const userName = renderUserUsername(targetMember.user); + const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : ""; + const banMessage = `Banned **${userName}** ${ + durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" + } (Case #${result.case.case_number})${messageResultText}`; + + await interaction.editReply({ content: banMessage, embeds: [], components: [] }); +} + +export async function launchBanActionModal( + pluginData: GuildPluginData, + interaction: ButtonInteraction, + target: string, +) { + const modal = new ModalBuilder().setCustomId("ban").setTitle("Ban"); + + const durationIn = new TextInputBuilder() + .setCustomId("duration") + .setLabel("Duration (Optional)") + .setRequired(false) + .setStyle(TextInputStyle.Short); + + const reasonIn = new TextInputBuilder() + .setCustomId("reason") + .setLabel("Reason (Optional)") + .setRequired(false) + .setStyle(TextInputStyle.Paragraph); + + const durationRow = new ActionRowBuilder().addComponents(durationIn); + const reasonRow = new ActionRowBuilder().addComponents(reasonIn); + + modal.addComponents(durationRow, reasonRow); + + await interaction.showModal(modal); + const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); + if (submitted) { + await submitted.deferUpdate(); + + const duration = submitted.fields.getTextInputValue("duration"); + const reason = submitted.fields.getTextInputValue("reason"); + + await banAction(pluginData, duration, reason, target, interaction); + } +} diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index a9d2384b5..6274f230b 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -1,16 +1,22 @@ -import { ContextMenuCommandInteraction, TextChannel } from "discord.js"; +import { + ActionRowBuilder, + ButtonInteraction, + ModalBuilder, + ModalSubmitInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; import { GuildPluginData } from "knub"; -import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { MODAL_TIMEOUT } from "../commands/ModMenuCmd"; import { ContextMenuPluginType } from "../types"; export async function cleanAction( pluginData: GuildPluginData, amount: number, - interaction: ContextMenuCommandInteraction, + target: string, + interaction: ButtonInteraction, ) { - await interaction.deferReply({ ephemeral: true }); const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -19,32 +25,42 @@ export async function cleanAction( const utility = pluginData.getPlugin(UtilityPlugin); if (!userCfg.can_use || !(await utility.hasPermission(executingMember, interaction.channelId, "can_clean"))) { - await interaction.followUp({ content: "Cannot clean: insufficient permissions" }); + await interaction.editReply({ content: "Cannot clean: insufficient permissions", embeds: [], components: [] }); return; } - const targetMessage = interaction.channel - ? await interaction.channel.messages.fetch(interaction.targetId) - : await (pluginData.guild.channels.resolve(interaction.channelId) as TextChannel).messages.fetch( - interaction.targetId, - ); - - const targetUserOnly = false; - const deletePins = false; - const user = undefined; - - try { - await interaction.followUp(`Cleaning... Amount: ${amount}, User Only: ${targetUserOnly}, Pins: ${deletePins}`); - utility.clean({ count: amount, user, channel: targetMessage.channel.id, "delete-pins": deletePins }, targetMessage); - } catch (e) { - await interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" }); - - if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - pluginData.getPlugin(LogsPlugin).logBotAlert({ - body: `Failed to clean in <#${interaction.channelId}> in ContextMenu action \`clean\`:_ ${e}`, - }); - } else { - throw e; + // TODO: Implement message cleaning + await interaction.editReply({ + content: `TODO: Implementation incomplete`, + embeds: [], + components: [], + }); +} + +export async function launchCleanActionModal( + pluginData: GuildPluginData, + interaction: ButtonInteraction, + target: string, +) { + const modal = new ModalBuilder().setCustomId("clean").setTitle("Clean"); + + const amountIn = new TextInputBuilder().setCustomId("amount").setLabel("Amount").setStyle(TextInputStyle.Short); + + const amountRow = new ActionRowBuilder().addComponents(amountIn); + + modal.addComponents(amountRow); + + await interaction.showModal(modal); + const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); + if (submitted) { + await submitted.deferUpdate(); + + const amount = submitted.fields.getTextInputValue("amount"); + if (isNaN(Number(amount))) { + interaction.editReply({ content: `ERROR: Amount ${amount} is invalid`, embeds: [], components: [] }); + return; } + + await cleanAction(pluginData, Number(amount), target, interaction); } } diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index 7fe2f5d59..a86a5ddd4 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -1,4 +1,11 @@ -import { ContextMenuCommandInteraction } from "discord.js"; +import { + ActionRowBuilder, + ButtonInteraction, + ModalBuilder, + ModalSubmitInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import { canActOn } from "src/pluginUtils"; @@ -8,14 +15,16 @@ 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 { ContextMenuPluginType } from "../types"; -export async function muteAction( +async function muteAction( pluginData: GuildPluginData, duration: string | undefined, - interaction: ContextMenuCommandInteraction, + reason: string | undefined, + target: string, + interaction: ButtonInteraction, ) { - await interaction.deferReply({ ephemeral: true }); const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -24,43 +33,76 @@ export async function muteAction( const modactions = pluginData.getPlugin(ModActionsPlugin); if (!userCfg.can_use || !(await modactions.hasMutePermission(executingMember, interaction.channelId))) { - await interaction.followUp({ content: "Cannot mute: insufficient permissions" }); + await interaction.editReply({ content: "Cannot mute: insufficient permissions", embeds: [], components: [] }); return; } - const durationMs = duration ? convertDelayStringToMS(duration)! : undefined; - const mutes = pluginData.getPlugin(MutesPlugin); - const userId = interaction.targetId; - const targetMember = await pluginData.guild.members.fetch(interaction.targetId); - + const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interaction.followUp({ ephemeral: true, content: "Cannot mute: insufficient permissions" }); + await interaction.editReply({ content: "Cannot mute: insufficient permissions", embeds: [], components: [] }); return; } const caseArgs: Partial = { modId: executingMember.id, }; + const mutes = pluginData.getPlugin(MutesPlugin); + const durationMs = duration ? convertDelayStringToMS(duration)! : undefined; try { - const result = await mutes.muteUser(userId, durationMs, "Context Menu Action", { caseArgs }); + const result = await mutes.muteUser(target, durationMs, reason, { caseArgs }); + const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : ""; const muteMessage = `Muted **${result.case.user_name}** ${ durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" - } (Case #${result.case.case_number}) (user notified via ${ - result.notifyResult.method ?? "dm" - })\nPlease update the new case with the \`update\` command`; + } (Case #${result.case.case_number})${messageResultText}`; - await interaction.followUp({ ephemeral: true, content: muteMessage }); + await interaction.editReply({ content: muteMessage, embeds: [], components: [] }); } catch (e) { - await interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" }); + await interaction.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({ - body: `Failed to mute <@!${userId}> in ContextMenu action \`mute\` because a mute role has not been specified in server config`, + body: `Failed to mute <@!${target}> in ContextMenu action \`mute\` because a mute role has not been specified in server config`, }); } else { throw e; } } } + +export async function launchMuteActionModal( + pluginData: GuildPluginData, + interaction: ButtonInteraction, + target: string, +) { + const modal = new ModalBuilder().setCustomId("mute").setTitle("Mute"); + + const durationIn = new TextInputBuilder() + .setCustomId("duration") + .setLabel("Duration (Optional)") + .setRequired(false) + .setStyle(TextInputStyle.Short); + + const reasonIn = new TextInputBuilder() + .setCustomId("reason") + .setLabel("Reason (Optional)") + .setRequired(false) + .setStyle(TextInputStyle.Paragraph); + + const durationRow = new ActionRowBuilder().addComponents(durationIn); + const reasonRow = new ActionRowBuilder().addComponents(reasonIn); + + modal.addComponents(durationRow, reasonRow); + + await interaction.showModal(modal); + const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); + if (submitted) { + await submitted.deferUpdate(); + + const duration = submitted.fields.getTextInputValue("duration"); + const reason = submitted.fields.getTextInputValue("reason"); + + await muteAction(pluginData, duration, reason, target, interaction); + } +} diff --git a/backend/src/plugins/ContextMenus/actions/note.ts b/backend/src/plugins/ContextMenus/actions/note.ts new file mode 100644 index 000000000..13d855d50 --- /dev/null +++ b/backend/src/plugins/ContextMenus/actions/note.ts @@ -0,0 +1,88 @@ +import { + ActionRowBuilder, + ButtonInteraction, + ModalBuilder, + ModalSubmitInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; +import { GuildPluginData } from "knub"; +import { canActOn } from "src/pluginUtils"; +import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; +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 { ContextMenuPluginType } from "../types"; + +async function noteAction( + pluginData: GuildPluginData, + reason: string, + target: string, + interaction: ButtonInteraction, +) { + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const userCfg = await pluginData.config.getMatchingConfig({ + channelId: interaction.channelId, + member: executingMember, + }); + + 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: [] }); + return; + } + + const targetMember = await pluginData.guild.members.fetch(target); + if (!canActOn(pluginData, executingMember, targetMember)) { + await interaction.editReply({ content: "Cannot mute: insufficient permissions", embeds: [], components: [] }); + return; + } + + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const createdCase = await casesPlugin.createCase({ + userId: target, + modId: executingMember.id, + type: CaseTypes.Note, + reason, + }); + + pluginData.getPlugin(LogsPlugin).logMemberNote({ + mod: interaction.user, + user: targetMember.user, + caseNumber: createdCase.case_number, + reason, + }); + + const userName = renderUserUsername(targetMember.user); + await interaction.editReply({ + content: `Note added on **${userName}** (Case #${createdCase.case_number})`, + embeds: [], + components: [], + }); +} + +export async function launchNoteActionModal( + pluginData: GuildPluginData, + interaction: ButtonInteraction, + target: string, +) { + const modal = new ModalBuilder().setCustomId("note").setTitle("Note"); + + const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Note").setStyle(TextInputStyle.Paragraph); + + const reasonRow = new ActionRowBuilder().addComponents(reasonIn); + + modal.addComponents(reasonRow); + + await interaction.showModal(modal); + const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); + if (submitted) { + await submitted.deferUpdate(); + + const reason = submitted.fields.getTextInputValue("reason"); + + await noteAction(pluginData, reason, target, interaction); + } +} diff --git a/backend/src/plugins/ContextMenus/actions/userInfo.ts b/backend/src/plugins/ContextMenus/actions/userInfo.ts deleted file mode 100644 index e445e1c8a..000000000 --- a/backend/src/plugins/ContextMenus/actions/userInfo.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ContextMenuCommandInteraction } from "discord.js"; -import { GuildPluginData } from "knub"; -import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; -import { ContextMenuPluginType } from "../types"; - -export async function userInfoAction( - pluginData: GuildPluginData, - interaction: ContextMenuCommandInteraction, -) { - await interaction.deferReply({ ephemeral: true }); - const executingMember = await pluginData.guild.members.fetch(interaction.user.id); - const userCfg = await pluginData.config.getMatchingConfig({ - channelId: interaction.channelId, - member: executingMember, - }); - const utility = pluginData.getPlugin(UtilityPlugin); - - if (userCfg.can_use && (await utility.hasPermission(executingMember, interaction.channelId, "can_userinfo"))) { - const embed = await utility.userInfo(interaction.targetId, interaction.user.id); - if (!embed) { - await interaction.followUp({ content: "Cannot info: internal error" }); - return; - } - await interaction.followUp({ embeds: [embed] }); - } else { - await interaction.followUp({ content: "Cannot info: insufficient permissions" }); - } -} diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts new file mode 100644 index 000000000..bbfa66c5f --- /dev/null +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -0,0 +1,80 @@ +import { + ActionRowBuilder, + ButtonInteraction, + ModalBuilder, + ModalSubmitInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; +import { GuildPluginData } from "knub"; +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 { ContextMenuPluginType } from "../types"; + +async function warnAction( + pluginData: GuildPluginData, + reason: string, + target: string, + interaction: ButtonInteraction, +) { + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const userCfg = await pluginData.config.getMatchingConfig({ + channelId: interaction.channelId, + member: executingMember, + }); + + 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: [] }); + return; + } + + const targetMember = await pluginData.guild.members.fetch(target); + if (!canActOn(pluginData, executingMember, targetMember)) { + await interaction.editReply({ content: "Cannot mute: insufficient permissions", embeds: [], components: [] }); + return; + } + + const caseArgs: Partial = { + modId: executingMember.id, + }; + + const result = await modactions.warnMember(targetMember, reason, { caseArgs }); + if (result.status === "failed") { + await interaction.editReply({ content: "Failed to warn user", embeds: [], components: [] }); + return; + } + + const userName = renderUserUsername(targetMember.user); + 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: [] }); +} + +export async function launchWarnActionModal( + pluginData: GuildPluginData, + interaction: ButtonInteraction, + target: string, +) { + const modal = new ModalBuilder().setCustomId("warn").setTitle("Warn"); + + const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Reason").setStyle(TextInputStyle.Paragraph); + + const reasonRow = new ActionRowBuilder().addComponents(reasonIn); + + modal.addComponents(reasonRow); + + await interaction.showModal(modal); + const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); + if (submitted) { + await submitted.deferUpdate(); + + const reason = submitted.fields.getTextInputValue("reason"); + + await warnAction(pluginData, reason, target, interaction); + } +} diff --git a/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts b/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts new file mode 100644 index 000000000..faf9bd2f5 --- /dev/null +++ b/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts @@ -0,0 +1,319 @@ +import { + APIEmbed, + ActionRowBuilder, + ButtonBuilder, + ButtonInteraction, + ButtonStyle, + ContextMenuCommandInteraction, + User, +} 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 { launchBanActionModal } from "../actions/ban"; +import { launchCleanActionModal } from "../actions/clean"; +import { launchMuteActionModal } from "../actions/mute"; +import { launchNoteActionModal } from "../actions/note"; +import { launchWarnActionModal } from "../actions/warn"; +import { + ContextMenuPluginType, + LoadModMenuPageFn, + ModMenuActionOpts, + ModMenuActionType, + ModMenuNavigationType, +} from "../types"; + +export const MODAL_TIMEOUT = 60 * SECONDS; +const MOD_MENU_TIMEOUT = 60 * SECONDS; +const CASES_PER_PAGE = 10; + +export const ModMenuCmd = guildPluginUserContextMenuCommand({ + name: "Mod Menu", + async run({ pluginData, interaction }) { + await interaction.deferReply({ ephemeral: true }); + + // Run permission checks for executing user. + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const userCfg = await pluginData.config.getMatchingConfig({ + channelId: interaction.channelId, + member: executingMember, + }); + const utility = pluginData.getPlugin(UtilityPlugin); + if ( + !userCfg.can_use || + (await !utility.hasPermission(executingMember, interaction.channelId, "can_open_mod_menu")) + ) { + await interaction.followUp({ content: "Error: Insufficient Permissions" }); + return; + } + + const user = await resolveUser(pluginData.client, interaction.targetId); + if (!user.id) { + await interaction.followUp("Error: User not found"); + return; + } + + // Load cases and display mod menu + const cases: Case[] = await pluginData.state.cases.with("notes").getByUserId(user.id); + const userName = + user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUserUsername(user); + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const totalCases = cases.length; + const totalPages: number = Math.max(Math.ceil(totalCases / CASES_PER_PAGE), 1); + const prefix = getGuildPrefix(pluginData); + const infoEmbed = await getUserInfoEmbed(pluginData, user.id, false); + displayModMenu( + pluginData, + interaction, + totalPages, + async (page) => { + const pageCases: Case[] = await pluginData.state.cases + .with("notes") + .getRecentByUserId(user.id, CASES_PER_PAGE, (page - 1) * CASES_PER_PAGE); + const lines = await asyncMap(pageCases, (c) => casesPlugin.getCaseSummary(c, true, interaction.targetId)); + + const firstCaseNum = (page - 1) * CASES_PER_PAGE + 1; + const lastCaseNum = Math.min(page * CASES_PER_PAGE, totalCases); + const title = + lines.length == 0 + ? `${userName}` + : `Most recent cases for ${userName} | ${firstCaseNum}-${lastCaseNum} of ${totalCases}`; + const embedFields = + lines.length == 0 + ? [ + { + name: `**No cases found**`, + value: "", + }, + ] + : [ + ...getChunkedEmbedFields( + emptyEmbedValue, + lines.length == 0 ? `No cases found for **${userName}**` : lines.join("\n"), + ), + { + name: emptyEmbedValue, + value: trimLines(` + Use \`${prefix}case \` to see more information about an individual case + `), + }, + ]; + + const embed = { + author: { + name: title, + icon_url: user instanceof User ? user.displayAvatarURL() : undefined, + }, + fields: embedFields, + footer: { text: `Page ${page}/${totalPages}` }, + } satisfies APIEmbed; + + return embed; + }, + infoEmbed, + ); + }, +}); + +async function displayModMenu( + pluginData: GuildPluginData, + interaction: ContextMenuCommandInteraction, + totalPages: number, + loadPage: LoadModMenuPageFn, + infoEmbed: APIEmbed | null, +) { + if (interaction.deferred == false) { + await interaction.deferReply(); + } + + const firstButton = new ButtonBuilder() + .setStyle(ButtonStyle.Primary) + .setLabel("<<") + .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.FIRST })) + .setDisabled(true); + const prevButton = new ButtonBuilder() + .setStyle(ButtonStyle.Primary) + .setLabel("<") + .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.PREV })) + .setDisabled(true); + const infoButton = new ButtonBuilder() + .setStyle(ButtonStyle.Primary) + .setLabel("Info") + .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.INFO })) + .setDisabled(infoEmbed != null ? false : true); + const nextButton = new ButtonBuilder() + .setStyle(ButtonStyle.Primary) + .setLabel(">") + .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.NEXT })) + .setDisabled(totalPages > 1 ? false : true); + const lastButton = new ButtonBuilder() + .setStyle(ButtonStyle.Primary) + .setLabel(">>") + .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.LAST })) + .setDisabled(totalPages > 1 ? false : true); + const navigationButtons = [firstButton, prevButton, infoButton, nextButton, lastButton] satisfies ButtonBuilder[]; + + const moderationButtons = [ + new ButtonBuilder() + .setStyle(ButtonStyle.Secondary) + .setLabel("Note") + .setCustomId(serializeCustomId({ action: ModMenuActionType.NOTE, target: interaction.targetId })), + new ButtonBuilder() + .setStyle(ButtonStyle.Secondary) + .setLabel("Warn") + .setCustomId(serializeCustomId({ action: ModMenuActionType.WARN, target: interaction.targetId })), + new ButtonBuilder() + .setStyle(ButtonStyle.Secondary) + .setLabel("Clean") + .setCustomId(serializeCustomId({ action: ModMenuActionType.CLEAN, target: interaction.targetId })), + new ButtonBuilder() + .setStyle(ButtonStyle.Secondary) + .setLabel("Mute") + .setCustomId(serializeCustomId({ action: ModMenuActionType.MUTE, target: interaction.targetId })), + new ButtonBuilder() + .setStyle(ButtonStyle.Secondary) + .setLabel("Ban") + .setCustomId(serializeCustomId({ action: ModMenuActionType.BAN, target: interaction.targetId })), + ] satisfies ButtonBuilder[]; + + const navigationRow = new ActionRowBuilder().addComponents(navigationButtons); + const moderationRow = new ActionRowBuilder().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], + }); + } 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], + }); + } 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: [], + }); + } + }); +} + +function serializeCustomId(opts: ModMenuActionOpts) { + return `${opts.action}:${opts.target}`; +} + +function deserializeCustomId(customId: string): ModMenuActionOpts { + const opts: ModMenuActionOpts = { + action: customId.split(":")[0] as ModMenuActionType, + target: customId.split(":")[1], + }; + + return opts; +} + +function updateNavButtonState( + firstButton: ButtonBuilder, + prevButton: ButtonBuilder, + nextButton: ButtonBuilder, + lastButton: ButtonBuilder, + currentPage: number, + totalPages: number, +) { + if (currentPage > 1) { + firstButton.setDisabled(false); + prevButton.setDisabled(false); + } else { + firstButton.setDisabled(true); + prevButton.setDisabled(true); + } + + if (currentPage == totalPages) { + nextButton.setDisabled(true); + lastButton.setDisabled(true); + } else { + nextButton.setDisabled(false); + lastButton.setDisabled(false); + } +} diff --git a/backend/src/plugins/ContextMenus/events/ContextClickedEvt.ts b/backend/src/plugins/ContextMenus/events/ContextClickedEvt.ts deleted file mode 100644 index 98e6ab1f5..000000000 --- a/backend/src/plugins/ContextMenus/events/ContextClickedEvt.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { contextMenuEvt } from "../types"; -import { routeContextAction } from "../utils/contextRouter"; - -export const ContextClickedEvt = contextMenuEvt({ - event: "interactionCreate", - - async listener(meta) { - if (!meta.args.interaction.isContextMenuCommand()) return; - const inter = meta.args.interaction; - await routeContextAction(meta.pluginData, inter); - }, -}); diff --git a/backend/src/plugins/ContextMenus/types.ts b/backend/src/plugins/ContextMenus/types.ts index 02c4a29c3..b4340b1b3 100644 --- a/backend/src/plugins/ContextMenus/types.ts +++ b/backend/src/plugins/ContextMenus/types.ts @@ -1,25 +1,52 @@ +import { APIEmbed, Awaitable } from "discord.js"; import * as t from "io-ts"; -import { BasePluginType, guildPluginEventListener } from "knub"; -import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks"; +import { BasePluginType } from "knub"; +import { GuildCases } from "../../data/GuildCases"; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildMutes } from "../../data/GuildMutes"; +import { GuildTempbans } from "../../data/GuildTempbans"; +import { tNullable } from "../../utils"; export const ConfigSchema = t.type({ can_use: t.boolean, - user_muteindef: t.boolean, - user_mute1d: t.boolean, - user_mute1h: t.boolean, - user_info: t.boolean, - message_clean10: t.boolean, - message_clean25: t.boolean, - message_clean50: t.boolean, + can_open_mod_menu: t.boolean, + + log_channel: tNullable(t.string), }); export type TConfigSchema = t.TypeOf; export interface ContextMenuPluginType extends BasePluginType { config: TConfigSchema; state: { - contextMenuLinks: GuildContextMenuLinks; + mutes: GuildMutes; + cases: GuildCases; + tempbans: GuildTempbans; + serverLogs: GuildLogs; }; } -export const contextMenuEvt = guildPluginEventListener(); +export const enum ModMenuActionType { + PAGE = "page", + NOTE = "note", + WARN = "warn", + CLEAN = "clean", + MUTE = "mute", + BAN = "ban", +} + +export const enum ModMenuNavigationType { + FIRST = "first", + PREV = "prev", + NEXT = "next", + LAST = "last", + INFO = "info", + CASES = "cases", +} + +export interface ModMenuActionOpts { + action: ModMenuActionType; + target: string; +} + +export type LoadModMenuPageFn = (page: number) => Awaitable; diff --git a/backend/src/plugins/ContextMenus/utils/contextRouter.ts b/backend/src/plugins/ContextMenus/utils/contextRouter.ts deleted file mode 100644 index 18b7b0640..000000000 --- a/backend/src/plugins/ContextMenus/utils/contextRouter.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ContextMenuCommandInteraction } from "discord.js"; -import { GuildPluginData } from "knub"; -import { ContextMenuPluginType } from "../types"; -import { hardcodedActions } from "./hardcodedContextOptions"; - -export async function routeContextAction( - pluginData: GuildPluginData, - interaction: ContextMenuCommandInteraction, -) { - const contextLink = await pluginData.state.contextMenuLinks.get(interaction.commandId); - if (!contextLink) return; - hardcodedActions[contextLink.action_name](pluginData, interaction); -} diff --git a/backend/src/plugins/ContextMenus/utils/hardcodedContextOptions.ts b/backend/src/plugins/ContextMenus/utils/hardcodedContextOptions.ts deleted file mode 100644 index 84593acef..000000000 --- a/backend/src/plugins/ContextMenus/utils/hardcodedContextOptions.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { cleanAction } from "../actions/clean"; -import { muteAction } from "../actions/mute"; -import { userInfoAction } from "../actions/userInfo"; - -export const hardcodedContext: Record = { - user_muteindef: "Mute Indefinitely", - user_mute1d: "Mute for 1 day", - user_mute1h: "Mute for 1 hour", - user_info: "Get Info", - message_clean10: "Clean 10 messages", - message_clean25: "Clean 25 messages", - message_clean50: "Clean 50 messages", -}; - -export const hardcodedActions = { - user_muteindef: (pluginData, interaction) => muteAction(pluginData, undefined, interaction), - user_mute1d: (pluginData, interaction) => muteAction(pluginData, "1d", interaction), - user_mute1h: (pluginData, interaction) => muteAction(pluginData, "1h", interaction), - user_info: (pluginData, interaction) => userInfoAction(pluginData, interaction), - message_clean10: (pluginData, interaction) => cleanAction(pluginData, 10, interaction), - message_clean25: (pluginData, interaction) => cleanAction(pluginData, 25, interaction), - message_clean50: (pluginData, interaction) => cleanAction(pluginData, 50, interaction), -}; diff --git a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts deleted file mode 100644 index 97d73dfb0..000000000 --- a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ApplicationCommandData, ApplicationCommandType } from "discord.js"; -import { GuildPluginData } from "knub"; -import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; -import { ContextMenuPluginType } from "../types"; -import { hardcodedContext } from "./hardcodedContextOptions"; - -export async function loadAllCommands(pluginData: GuildPluginData) { - const comms = await pluginData.client.application!.commands; - const cfg = pluginData.config.get(); - const newCommands: ApplicationCommandData[] = []; - const addedNames: string[] = []; - - for (const [name, label] of Object.entries(hardcodedContext)) { - if (!cfg[name]) continue; - - const type = name.startsWith("user") ? ApplicationCommandType.User : ApplicationCommandType.Message; - const data: ApplicationCommandData = { - type, - name: label, - }; - - addedNames.push(name); - newCommands.push(data); - } - - const setCommands = await comms.set(newCommands, pluginData.guild.id).catch((e) => { - pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unable to overwrite context menus: ${e}` }); - return undefined; - }); - if (!setCommands) return; - - const setCommandsArray = [...setCommands.values()]; - await pluginData.state.contextMenuLinks.deleteAll(); - - for (let i = 0; i < setCommandsArray.length; i++) { - const command = setCommandsArray[i]; - pluginData.state.contextMenuLinks.create(command.id, addedNames[i]); - } -} diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index bf83b13e3..9b7d3404b 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -41,7 +41,12 @@ import { CreateUnbanCaseOnManualUnbanEvt } from "./events/CreateUnbanCaseOnManua import { PostAlertOnMemberJoinEvt } from "./events/PostAlertOnMemberJoinEvt"; import { banUserId } from "./functions/banUserId"; import { clearTempban } from "./functions/clearTempban"; -import { hasMutePermission } from "./functions/hasMutePerm"; +import { + hasBanPermission, + hasMutePermission, + hasNotePermission, + hasWarnPermission, +} from "./functions/hasModActionPerm"; import { kickMember } from "./functions/kickMember"; import { offModActionsEvent } from "./functions/offModActionsEvent"; import { onModActionsEvent } from "./functions/onModActionsEvent"; @@ -158,7 +163,7 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ public: { warnMember(pluginData) { return (member: GuildMember, reason: string, warnOptions?: WarnOptions) => { - warnMember(pluginData, member, reason, warnOptions); + return warnMember(pluginData, member, reason, warnOptions); }; }, @@ -170,7 +175,7 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ banUserId(pluginData) { return (userId: string, reason?: string, banOptions?: BanOptions, banTime?: number) => { - banUserId(pluginData, userId, reason, banOptions, banTime); + return banUserId(pluginData, userId, reason, banOptions, banTime); }; }, @@ -180,12 +185,30 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ }; }, + hasNotePermission(pluginData) { + return (member: GuildMember, channelId: Snowflake) => { + return hasNotePermission(pluginData, member, channelId); + }; + }, + + hasWarnPermission(pluginData) { + return (member: GuildMember, channelId: Snowflake) => { + return hasWarnPermission(pluginData, member, channelId); + }; + }, + hasMutePermission(pluginData) { return (member: GuildMember, channelId: Snowflake) => { return hasMutePermission(pluginData, member, channelId); }; }, + hasBanPermission(pluginData) { + return (member: GuildMember, channelId: Snowflake) => { + return hasBanPermission(pluginData, member, channelId); + }; + }, + on: mapToPublicFn(onModActionsEvent), off: mapToPublicFn(offModActionsEvent), getEventEmitter(pluginData) { diff --git a/backend/src/plugins/ModActions/functions/hasModActionPerm.ts b/backend/src/plugins/ModActions/functions/hasModActionPerm.ts new file mode 100644 index 000000000..6e28768d4 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/hasModActionPerm.ts @@ -0,0 +1,35 @@ +import { GuildMember, Snowflake } from "discord.js"; +import { GuildPluginData } from "knub"; +import { ModActionsPluginType } from "../types"; + +export async function hasNotePermission( + pluginData: GuildPluginData, + member: GuildMember, + channelId: Snowflake, +) { + return (await pluginData.config.getMatchingConfig({ member, channelId })).can_note; +} + +export async function hasWarnPermission( + pluginData: GuildPluginData, + member: GuildMember, + channelId: Snowflake, +) { + return (await pluginData.config.getMatchingConfig({ member, channelId })).can_warn; +} + +export async function hasMutePermission( + pluginData: GuildPluginData, + member: GuildMember, + channelId: Snowflake, +) { + return (await pluginData.config.getMatchingConfig({ member, channelId })).can_mute; +} + +export async function hasBanPermission( + pluginData: GuildPluginData, + member: GuildMember, + channelId: Snowflake, +) { + return (await pluginData.config.getMatchingConfig({ member, channelId })).can_ban; +} diff --git a/backend/src/plugins/ModActions/functions/hasMutePerm.ts b/backend/src/plugins/ModActions/functions/hasMutePerm.ts deleted file mode 100644 index b26edd4d2..000000000 --- a/backend/src/plugins/ModActions/functions/hasMutePerm.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { GuildMember, Snowflake } from "discord.js"; -import { GuildPluginData } from "knub"; -import { ModActionsPluginType } from "../types"; - -export async function hasMutePermission( - pluginData: GuildPluginData, - member: GuildMember, - channelId: Snowflake, -) { - return (await pluginData.config.getMatchingConfig({ member, channelId })).can_mute; -} From 24b11800f550b9b7584ab705603d6c7ca9fc71cd Mon Sep 17 00:00:00 2001 From: Obliie Date: Sat, 15 Jul 2023 22:05:30 +0100 Subject: [PATCH 02/29] fix: plugin dependencies and cleanup --- .../plugins/ContextMenus/ContextMenuPlugin.ts | 6 ++-- .../src/plugins/ContextMenus/actions/ban.ts | 6 +--- .../src/plugins/ContextMenus/actions/clean.ts | 5 +-- .../src/plugins/ContextMenus/actions/mute.ts | 4 --- .../src/plugins/ContextMenus/actions/note.ts | 5 +-- .../src/plugins/ContextMenus/actions/warn.ts | 7 ++-- .../ContextMenus/commands/ModMenuCmd.ts | 33 +++++++------------ backend/src/plugins/ContextMenus/types.ts | 9 ----- 8 files changed, 20 insertions(+), 55 deletions(-) diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts index dc08389e8..b48cc0eae 100644 --- a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts +++ b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts @@ -4,6 +4,7 @@ import { makeIoTsConfigParser } from "../../pluginUtils"; import { trimPluginDescription } from "../../utils"; import { CasesPlugin } from "../Cases/CasesPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; +import { ModActionsPlugin } from "../ModActions/ModActionsPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin"; import { UtilityPlugin } from "../Utility/UtilityPlugin"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; @@ -15,8 +16,6 @@ const defaultOptions: PluginOptions = { can_use: false, can_open_mod_menu: false, - - log_channel: null, }, overrides: [ { @@ -41,8 +40,9 @@ export const ContextMenuPlugin = zeppelinGuildPlugin()({ configSchema: ConfigSchema, }, - dependencies: () => [CasesPlugin, MutesPlugin, LogsPlugin, UtilityPlugin], + dependencies: () => [CasesPlugin, MutesPlugin, ModActionsPlugin, LogsPlugin, UtilityPlugin], configParser: makeIoTsConfigParser(ConfigSchema), + defaultOptions, contextMenuCommands: [ModMenuCmd], diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index 38ec91a1d..bfe7bc132 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -47,7 +47,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 interaction.editReply({ content: "Error: Failed to ban user", embeds: [], components: [] }); return; } @@ -66,22 +66,18 @@ export async function launchBanActionModal( target: string, ) { const modal = new ModalBuilder().setCustomId("ban").setTitle("Ban"); - const durationIn = new TextInputBuilder() .setCustomId("duration") .setLabel("Duration (Optional)") .setRequired(false) .setStyle(TextInputStyle.Short); - const reasonIn = new TextInputBuilder() .setCustomId("reason") .setLabel("Reason (Optional)") .setRequired(false) .setStyle(TextInputStyle.Paragraph); - const durationRow = new ActionRowBuilder().addComponents(durationIn); const reasonRow = new ActionRowBuilder().addComponents(reasonIn); - modal.addComponents(durationRow, reasonRow); await interaction.showModal(modal); diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index 6274f230b..3d005bfbb 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -43,11 +43,8 @@ export async function launchCleanActionModal( target: string, ) { const modal = new ModalBuilder().setCustomId("clean").setTitle("Clean"); - const amountIn = new TextInputBuilder().setCustomId("amount").setLabel("Amount").setStyle(TextInputStyle.Short); - const amountRow = new ActionRowBuilder().addComponents(amountIn); - modal.addComponents(amountRow); await interaction.showModal(modal); @@ -57,7 +54,7 @@ export async function launchCleanActionModal( 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: [] }); return; } diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index a86a5ddd4..90a045759 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -77,22 +77,18 @@ export async function launchMuteActionModal( target: string, ) { const modal = new ModalBuilder().setCustomId("mute").setTitle("Mute"); - const durationIn = new TextInputBuilder() .setCustomId("duration") .setLabel("Duration (Optional)") .setRequired(false) .setStyle(TextInputStyle.Short); - const reasonIn = new TextInputBuilder() .setCustomId("reason") .setLabel("Reason (Optional)") .setRequired(false) .setStyle(TextInputStyle.Paragraph); - const durationRow = new ActionRowBuilder().addComponents(durationIn); const reasonRow = new ActionRowBuilder().addComponents(reasonIn); - modal.addComponents(durationRow, reasonRow); await interaction.showModal(modal); diff --git a/backend/src/plugins/ContextMenus/actions/note.ts b/backend/src/plugins/ContextMenus/actions/note.ts index 13d855d50..84b0e2bb2 100644 --- a/backend/src/plugins/ContextMenus/actions/note.ts +++ b/backend/src/plugins/ContextMenus/actions/note.ts @@ -36,7 +36,7 @@ async function noteAction( const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interaction.editReply({ content: "Cannot mute: insufficient permissions", embeds: [], components: [] }); + await interaction.editReply({ content: "Cannot note: insufficient permissions", embeds: [], components: [] }); return; } @@ -69,11 +69,8 @@ export async function launchNoteActionModal( target: string, ) { const modal = new ModalBuilder().setCustomId("note").setTitle("Note"); - const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Note").setStyle(TextInputStyle.Paragraph); - const reasonRow = new ActionRowBuilder().addComponents(reasonIn); - modal.addComponents(reasonRow); await interaction.showModal(modal); diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts index bbfa66c5f..c6a0c8ca7 100644 --- a/backend/src/plugins/ContextMenus/actions/warn.ts +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -34,7 +34,7 @@ async function warnAction( const targetMember = await pluginData.guild.members.fetch(target); if (!canActOn(pluginData, executingMember, targetMember)) { - await interaction.editReply({ content: "Cannot mute: insufficient permissions", embeds: [], components: [] }); + await interaction.editReply({ content: "Cannot warn: insufficient permissions", embeds: [], components: [] }); return; } @@ -44,7 +44,7 @@ async function warnAction( const result = await modactions.warnMember(targetMember, reason, { caseArgs }); if (result.status === "failed") { - await interaction.editReply({ content: "Failed to warn user", embeds: [], components: [] }); + await interaction.editReply({ content: "Error: Failed to warn user", embeds: [], components: [] }); return; } @@ -61,11 +61,8 @@ export async function launchWarnActionModal( target: string, ) { const modal = new ModalBuilder().setCustomId("warn").setTitle("Warn"); - const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Reason").setStyle(TextInputStyle.Paragraph); - const reasonRow = new ActionRowBuilder().addComponents(reasonIn); - modal.addComponents(reasonRow); await interaction.showModal(modal); diff --git a/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts b/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts index faf9bd2f5..1150893c6 100644 --- a/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/ModMenuCmd.ts @@ -84,33 +84,24 @@ export const ModMenuCmd = guildPluginUserContextMenuCommand({ lines.length == 0 ? `${userName}` : `Most recent cases for ${userName} | ${firstCaseNum}-${lastCaseNum} of ${totalCases}`; - const embedFields = - lines.length == 0 - ? [ - { - name: `**No cases found**`, - value: "", - }, - ] - : [ - ...getChunkedEmbedFields( - emptyEmbedValue, - lines.length == 0 ? `No cases found for **${userName}**` : lines.join("\n"), - ), - { - name: emptyEmbedValue, - value: trimLines(` - Use \`${prefix}case \` to see more information about an individual case - `), - }, - ]; const embed = { author: { name: title, icon_url: user instanceof User ? user.displayAvatarURL() : undefined, }, - fields: embedFields, + fields: [ + ...getChunkedEmbedFields( + emptyEmbedValue, + lines.length == 0 ? `No cases found for **${userName}**` : lines.join("\n"), + ), + { + name: emptyEmbedValue, + value: trimLines( + lines.length == 0 ? "" : `Use \`${prefix}case \` to see more information about an individual case`, + ), + }, + ], footer: { text: `Page ${page}/${totalPages}` }, } satisfies APIEmbed; diff --git a/backend/src/plugins/ContextMenus/types.ts b/backend/src/plugins/ContextMenus/types.ts index b4340b1b3..d099d843e 100644 --- a/backend/src/plugins/ContextMenus/types.ts +++ b/backend/src/plugins/ContextMenus/types.ts @@ -2,27 +2,18 @@ import { APIEmbed, Awaitable } from "discord.js"; import * as t from "io-ts"; import { BasePluginType } from "knub"; import { GuildCases } from "../../data/GuildCases"; -import { GuildLogs } from "../../data/GuildLogs"; -import { GuildMutes } from "../../data/GuildMutes"; -import { GuildTempbans } from "../../data/GuildTempbans"; -import { tNullable } from "../../utils"; export const ConfigSchema = t.type({ can_use: t.boolean, can_open_mod_menu: t.boolean, - - log_channel: tNullable(t.string), }); export type TConfigSchema = t.TypeOf; export interface ContextMenuPluginType extends BasePluginType { config: TConfigSchema; state: { - mutes: GuildMutes; cases: GuildCases; - tempbans: GuildTempbans; - serverLogs: GuildLogs; }; } From 454bec6c9f0ade8e6512382d6fc44f0f3e1f8bd2 Mon Sep 17 00:00:00 2001 From: Obliie Date: Sat, 15 Jul 2023 22:51:41 +0100 Subject: [PATCH 03/29] feat: Add user context menu commands for notes, warns, mutes and bans --- .../plugins/ContextMenus/ContextMenuPlugin.ts | 8 +++- .../src/plugins/ContextMenus/actions/ban.ts | 25 ++++++++----- .../src/plugins/ContextMenus/actions/clean.ts | 2 +- .../src/plugins/ContextMenus/actions/mute.ts | 37 ++++++++++++++----- .../src/plugins/ContextMenus/actions/note.ts | 31 ++++++++++++---- .../src/plugins/ContextMenus/actions/warn.ts | 33 ++++++++++++----- .../ContextMenus/commands/BanUserCtxCmd.ts | 9 +++++ .../{ModMenuCmd.ts => ModMenuUserCtxCmd.ts} | 2 +- .../ContextMenus/commands/MuteUserCtxCmd.ts | 9 +++++ .../ContextMenus/commands/NoteUserCtxCmd.ts | 9 +++++ .../ContextMenus/commands/WarnUserCtxCmd.ts | 9 +++++ 11 files changed, 135 insertions(+), 39 deletions(-) create mode 100644 backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts rename backend/src/plugins/ContextMenus/commands/{ModMenuCmd.ts => ModMenuUserCtxCmd.ts} (99%) create mode 100644 backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts create mode 100644 backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts create mode 100644 backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts index b48cc0eae..5461409d9 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 bfe7bc132..bfc756d25 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 3d005bfbb..34ae2d14c 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 90a045759..28a988c82 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 84b0e2bb2..51a018ca7 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 c6a0c8ca7..4ec0cf438 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 000000000..237d81cbd --- /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 1150893c6..e8a51556a 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 000000000..3c060cb37 --- /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 000000000..c4f0fa9dd --- /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 000000000..3f62196c9 --- /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); + }, +}); From 7f2f2c8f98ddbd34b142b16e64b7971caf0934e2 Mon Sep 17 00:00:00 2001 From: Obliie Date: Sun, 16 Jul 2023 00:12:13 +0100 Subject: [PATCH 04/29] fix: modal id conflicts causing collectors to respond to unrelated submissions --- .../src/plugins/ContextMenus/actions/ban.ts | 32 +++++++++------- .../src/plugins/ContextMenus/actions/clean.ts | 37 +++++++++---------- .../src/plugins/ContextMenus/actions/mute.ts | 32 +++++++++------- .../src/plugins/ContextMenus/actions/note.ts | 30 ++++++++------- .../src/plugins/ContextMenus/actions/warn.ts | 30 ++++++++------- 5 files changed, 87 insertions(+), 74 deletions(-) diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index bfc756d25..5227c7934 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -11,10 +11,11 @@ import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import { canActOn } from "src/pluginUtils"; import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; +import { logger } from "../../../logger"; import { convertDelayStringToMS, renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; -import { ContextMenuPluginType } from "../types"; +import { ContextMenuPluginType, ModMenuActionType } from "../types"; async function banAction( pluginData: GuildPluginData, @@ -24,7 +25,7 @@ async function banAction( interaction: ButtonInteraction | ContextMenuCommandInteraction, submitInteraction: ModalSubmitInteraction, ) { - const interactionToReply = interaction instanceof ButtonInteraction ? interaction : submitInteraction; + const interactionToReply = interaction.isButton() ? interaction : submitInteraction; const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -68,7 +69,8 @@ export async function launchBanActionModal( interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { - const modal = new ModalBuilder().setCustomId("ban").setTitle("Ban"); + const modalId = `${ModMenuActionType.WARN}:${interaction.id}`; + const modal = new ModalBuilder().setCustomId(modalId).setTitle("Ban"); const durationIn = new TextInputBuilder() .setCustomId("duration") .setLabel("Duration (Optional)") @@ -84,17 +86,19 @@ export async function launchBanActionModal( modal.addComponents(durationRow, reasonRow); await interaction.showModal(modal); - const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); - if (submitted) { - if (interaction instanceof ButtonInteraction) { - await submitted.deferUpdate(); - } else { - await submitted.deferReply({ ephemeral: true }); - } + await interaction + .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) + .then(async (submitted) => { + if (interaction.isButton()) { + await submitted.deferUpdate(); + } else if (interaction.isContextMenuCommand()) { + await submitted.deferReply({ ephemeral: true }); + } - const duration = submitted.fields.getTextInputValue("duration"); - const reason = submitted.fields.getTextInputValue("reason"); + const duration = submitted.fields.getTextInputValue("duration"); + const reason = submitted.fields.getTextInputValue("reason"); - await banAction(pluginData, duration, reason, target, interaction, submitted); - } + await banAction(pluginData, duration, reason, target, interaction, submitted); + }) + .catch((err) => logger.error(`Ban modal interaction failed: ${err}`)); } diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index 34ae2d14c..13795f045 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -1,15 +1,9 @@ -import { - ActionRowBuilder, - ButtonInteraction, - ModalBuilder, - ModalSubmitInteraction, - TextInputBuilder, - TextInputStyle, -} from "discord.js"; +import { ActionRowBuilder, ButtonInteraction, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import { GuildPluginData } from "knub"; +import { logger } from "../../../logger"; import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; -import { ContextMenuPluginType } from "../types"; +import { ContextMenuPluginType, ModMenuActionType } from "../types"; export async function cleanAction( pluginData: GuildPluginData, @@ -42,22 +36,25 @@ export async function launchCleanActionModal( interaction: ButtonInteraction, target: string, ) { - const modal = new ModalBuilder().setCustomId("clean").setTitle("Clean"); + const modalId = `${ModMenuActionType.CLEAN}:${interaction.id}`; + const modal = new ModalBuilder().setCustomId(modalId).setTitle("Clean"); const amountIn = new TextInputBuilder().setCustomId("amount").setLabel("Amount").setStyle(TextInputStyle.Short); const amountRow = new ActionRowBuilder().addComponents(amountIn); modal.addComponents(amountRow); await interaction.showModal(modal); - const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); - if (submitted) { - await submitted.deferUpdate(); + await interaction + .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) + .then(async (submitted) => { + await submitted.deferUpdate(); - const amount = submitted.fields.getTextInputValue("amount"); - if (isNaN(Number(amount))) { - interaction.editReply({ content: `Error: Amount '${amount}' is invalid`, embeds: [], components: [] }); - return; - } + const amount = submitted.fields.getTextInputValue("amount"); + if (isNaN(Number(amount))) { + interaction.editReply({ content: `Error: Amount '${amount}' is invalid`, embeds: [], components: [] }); + return; + } - await cleanAction(pluginData, Number(amount), target, interaction); - } + await cleanAction(pluginData, Number(amount), target, interaction); + }) + .catch((err) => logger.error(`Clean modal interaction failed: ${err}`)); } diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index 28a988c82..ad98d544e 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -12,12 +12,13 @@ import { GuildPluginData } from "knub"; import { canActOn } from "src/pluginUtils"; import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { logger } from "../../../logger"; import { convertDelayStringToMS } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { MutesPlugin } from "../../Mutes/MutesPlugin"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; -import { ContextMenuPluginType } from "../types"; +import { ContextMenuPluginType, ModMenuActionType } from "../types"; async function muteAction( pluginData: GuildPluginData, @@ -27,7 +28,7 @@ async function muteAction( interaction: ButtonInteraction | ContextMenuCommandInteraction, submitInteraction: ModalSubmitInteraction, ) { - const interactionToReply = interaction instanceof ButtonInteraction ? interaction : submitInteraction; + const interactionToReply = interaction.isButton() ? interaction : submitInteraction; const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -91,7 +92,8 @@ export async function launchMuteActionModal( interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { - const modal = new ModalBuilder().setCustomId("mute").setTitle("Mute"); + const modalId = `${ModMenuActionType.MUTE}:${interaction.id}`; + const modal = new ModalBuilder().setCustomId(modalId).setTitle("Mute"); const durationIn = new TextInputBuilder() .setCustomId("duration") .setLabel("Duration (Optional)") @@ -107,17 +109,19 @@ export async function launchMuteActionModal( modal.addComponents(durationRow, reasonRow); await interaction.showModal(modal); - const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); - if (submitted) { - if (interaction instanceof ButtonInteraction) { - await submitted.deferUpdate(); - } else { - await submitted.deferReply({ ephemeral: true }); - } + await interaction + .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) + .then(async (submitted) => { + if (interaction.isButton()) { + await submitted.deferUpdate(); + } else if (interaction.isContextMenuCommand()) { + await submitted.deferReply({ ephemeral: true }); + } - const duration = submitted.fields.getTextInputValue("duration"); - const reason = submitted.fields.getTextInputValue("reason"); + const duration = submitted.fields.getTextInputValue("duration"); + const reason = submitted.fields.getTextInputValue("reason"); - await muteAction(pluginData, duration, reason, target, interaction, submitted); - } + await muteAction(pluginData, duration, reason, target, interaction, submitted); + }) + .catch((err) => logger.error(`Mute modal interaction failed: ${err}`)); } diff --git a/backend/src/plugins/ContextMenus/actions/note.ts b/backend/src/plugins/ContextMenus/actions/note.ts index 51a018ca7..ecc48d6e4 100644 --- a/backend/src/plugins/ContextMenus/actions/note.ts +++ b/backend/src/plugins/ContextMenus/actions/note.ts @@ -11,11 +11,12 @@ import { GuildPluginData } from "knub"; import { canActOn } from "src/pluginUtils"; import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; import { CaseTypes } from "../../../data/CaseTypes"; +import { logger } from "../../../logger"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; import { renderUserUsername } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; -import { ContextMenuPluginType } from "../types"; +import { ContextMenuPluginType, ModMenuActionType } from "../types"; async function noteAction( pluginData: GuildPluginData, @@ -24,7 +25,7 @@ async function noteAction( interaction: ButtonInteraction | ContextMenuCommandInteraction, submitInteraction: ModalSubmitInteraction, ) { - const interactionToReply = interaction instanceof ButtonInteraction ? interaction : submitInteraction; + const interactionToReply = interaction.isButton() ? interaction : submitInteraction; const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -79,22 +80,25 @@ export async function launchNoteActionModal( interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { - const modal = new ModalBuilder().setCustomId("note").setTitle("Note"); + const modalId = `${ModMenuActionType.NOTE}:${interaction.id}`; + const modal = new ModalBuilder().setCustomId(modalId).setTitle("Note"); const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Note").setStyle(TextInputStyle.Paragraph); const reasonRow = new ActionRowBuilder().addComponents(reasonIn); modal.addComponents(reasonRow); await interaction.showModal(modal); - const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); - if (submitted) { - if (interaction instanceof ButtonInteraction) { - await submitted.deferUpdate(); - } else { - await submitted.deferReply({ ephemeral: true }); - } + await interaction + .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) + .then(async (submitted) => { + if (interaction.isButton()) { + await submitted.deferUpdate(); + } else if (interaction.isContextMenuCommand()) { + await submitted.deferReply({ ephemeral: true }); + } - const reason = submitted.fields.getTextInputValue("reason"); + const reason = submitted.fields.getTextInputValue("reason"); - await noteAction(pluginData, reason, target, interaction, submitted); - } + await noteAction(pluginData, reason, target, interaction, submitted); + }) + .catch((err) => logger.error(`Note modal interaction failed: ${err}`)); } diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts index 4ec0cf438..9afbf44e4 100644 --- a/backend/src/plugins/ContextMenus/actions/warn.ts +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -10,10 +10,11 @@ import { import { GuildPluginData } from "knub"; import { canActOn } from "src/pluginUtils"; import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; +import { logger } from "../../../logger"; import { renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; -import { ContextMenuPluginType } from "../types"; +import { ContextMenuPluginType, ModMenuActionType } from "../types"; async function warnAction( pluginData: GuildPluginData, @@ -22,7 +23,7 @@ async function warnAction( interaction: ButtonInteraction | ContextMenuCommandInteraction, submitInteraction: ModalSubmitInteraction, ) { - const interactionToReply = interaction instanceof ButtonInteraction ? interaction : submitInteraction; + const interactionToReply = interaction.isButton() ? interaction : submitInteraction; const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ channelId: interaction.channelId, @@ -71,22 +72,25 @@ export async function launchWarnActionModal( interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { - const modal = new ModalBuilder().setCustomId("warn").setTitle("Warn"); + const modalId = `${ModMenuActionType.WARN}:${interaction.id}`; + const modal = new ModalBuilder().setCustomId(modalId).setTitle("Warn"); const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Reason").setStyle(TextInputStyle.Paragraph); const reasonRow = new ActionRowBuilder().addComponents(reasonIn); modal.addComponents(reasonRow); await interaction.showModal(modal); - const submitted: ModalSubmitInteraction = await interaction.awaitModalSubmit({ time: MODAL_TIMEOUT }); - if (submitted) { - if (interaction instanceof ButtonInteraction) { - await submitted.deferUpdate(); - } else { - await submitted.deferReply({ ephemeral: true }); - } + await interaction + .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) + .then(async (submitted) => { + if (interaction.isButton()) { + await submitted.deferUpdate(); + } else if (interaction.isContextMenuCommand()) { + await submitted.deferReply({ ephemeral: true }); + } - const reason = submitted.fields.getTextInputValue("reason"); + const reason = submitted.fields.getTextInputValue("reason"); - await warnAction(pluginData, reason, target, interaction, submitted); - } + await warnAction(pluginData, reason, target, interaction, submitted); + }) + .catch((err) => logger.error(`Mute modal interaction failed: ${err}`)); } From 8fcbd50ea1517c9fe912cbf8d17d5454372b247a Mon Sep 17 00:00:00 2001 From: Obliie Date: Sun, 16 Jul 2023 00:38:35 +0100 Subject: [PATCH 05/29] fix: interaction error handling --- .../src/plugins/ContextMenus/actions/ban.ts | 22 +- .../src/plugins/ContextMenus/actions/clean.ts | 22 +- .../src/plugins/ContextMenus/actions/mute.ts | 46 +++-- .../src/plugins/ContextMenus/actions/note.ts | 42 ++-- .../src/plugins/ContextMenus/actions/warn.ts | 40 ++-- .../commands/ModMenuUserCtxCmd.ts | 188 ++++++++++-------- 6 files changed, 211 insertions(+), 149 deletions(-) diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index 5227c7934..0a61c57f3 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 13795f045..628451c66 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 ad98d544e..b0ea87763 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 ecc48d6e4..b6911274f 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 9afbf44e4..d8eba234d 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 e8a51556a..da8a9014f 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().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, - }); + await interaction + .editReply({ + embeds: [await loadPage(page)], + components: [navigationRow, moderationRow], + }) + .then(async (currentPage) => { + 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(); - } + 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); + // 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], - }); - } 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: [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], - }); - } else if (opts.action == ModMenuActionType.PAGE) { - let pageDelta = 0; - switch (opts.target) { - case ModMenuNavigationType.PREV: - pageDelta = -1; - break; - case ModMenuNavigationType.NEXT: - pageDelta = 1; - break; - } + 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; - } + 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); + if (newPage != page) { + updateNavButtonState(firstButton, prevButton, nextButton, lastButton, newPage, totalPages); - await i.editReply({ - embeds: [await loadPage(newPage)], - components: [navigationRow, moderationRow], - }); + 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); - } + 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(); - }); + 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) { From cdcca8ccbb2f8f51d7d810fbb26ddc0e0fa39863 Mon Sep 17 00:00:00 2001 From: Obliie Date: Sun, 16 Jul 2023 18:46:00 +0100 Subject: [PATCH 06/29] style(ContextMenus): improve ux --- .../ContextMenus/commands/BanUserCtxCmd.ts | 2 + .../commands/ModMenuUserCtxCmd.ts | 49 ++++++++++++------- .../ContextMenus/commands/MuteUserCtxCmd.ts | 2 + .../ContextMenus/commands/NoteUserCtxCmd.ts | 2 + .../ContextMenus/commands/WarnUserCtxCmd.ts | 2 + 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts index 237d81cbd..741c8f749 100644 --- a/backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts @@ -1,8 +1,10 @@ +import { PermissionFlagsBits } from "discord.js"; import { guildPluginUserContextMenuCommand } from "knub"; import { launchBanActionModal } from "../actions/ban"; export const BanCmd = guildPluginUserContextMenuCommand({ name: "Ban", + defaultMemberPermissions: PermissionFlagsBits.BanMembers.toString(), async run({ pluginData, interaction }) { await launchBanActionModal(pluginData, interaction, interaction.targetId); }, diff --git a/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts index da8a9014f..9cb40f14f 100644 --- a/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts @@ -5,11 +5,14 @@ import { ButtonInteraction, ButtonStyle, ContextMenuCommandInteraction, + GuildMember, + PermissionFlagsBits, User, } from "discord.js"; import { GuildPluginData, guildPluginUserContextMenuCommand } from "knub"; import { Case } from "../../../data/entities/Case"; import { logger } from "../../../logger"; +import { ModActionsPlugin } from "../../../plugins/ModActions/ModActionsPlugin"; import { SECONDS, UnknownUser, emptyEmbedValue, renderUserUsername, resolveUser, trimLines } from "../../../utils"; import { asyncMap } from "../../../utils/async"; import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; @@ -18,7 +21,6 @@ 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"; import { launchNoteActionModal } from "../actions/note"; import { launchWarnActionModal } from "../actions/warn"; @@ -36,6 +38,7 @@ const CASES_PER_PAGE = 10; export const ModMenuCmd = guildPluginUserContextMenuCommand({ name: "Mod Menu", + defaultMemberPermissions: PermissionFlagsBits.ViewAuditLog.toString(), async run({ pluginData, interaction }) { await interaction .deferReply({ ephemeral: true }) @@ -115,6 +118,7 @@ export const ModMenuCmd = guildPluginUserContextMenuCommand({ return embed; }, infoEmbed, + executingMember, ); }, }); @@ -125,58 +129,65 @@ async function displayModMenu( totalPages: number, loadPage: LoadModMenuPageFn, infoEmbed: APIEmbed | null, + executingMember: GuildMember, ) { if (interaction.deferred == false) { await interaction.deferReply().catch((err) => logger.error(`Mod menu interaction defer failed: ${err}`)); } const firstButton = new ButtonBuilder() - .setStyle(ButtonStyle.Primary) - .setLabel("<<") + .setStyle(ButtonStyle.Secondary) + .setEmoji("⏪") .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.FIRST })) .setDisabled(true); const prevButton = new ButtonBuilder() - .setStyle(ButtonStyle.Primary) - .setLabel("<") + .setStyle(ButtonStyle.Secondary) + .setEmoji("⬅") .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.PREV })) .setDisabled(true); const infoButton = new ButtonBuilder() .setStyle(ButtonStyle.Primary) .setLabel("Info") + .setEmoji("ℹ") .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.INFO })) .setDisabled(infoEmbed != null ? false : true); const nextButton = new ButtonBuilder() - .setStyle(ButtonStyle.Primary) - .setLabel(">") + .setStyle(ButtonStyle.Secondary) + .setEmoji("➡") .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.NEXT })) .setDisabled(totalPages > 1 ? false : true); const lastButton = new ButtonBuilder() - .setStyle(ButtonStyle.Primary) - .setLabel(">>") + .setStyle(ButtonStyle.Secondary) + .setEmoji("⏩") .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.LAST })) .setDisabled(totalPages > 1 ? false : true); const navigationButtons = [firstButton, prevButton, infoButton, nextButton, lastButton] satisfies ButtonBuilder[]; + const modactions = pluginData.getPlugin(ModActionsPlugin); const moderationButtons = [ new ButtonBuilder() - .setStyle(ButtonStyle.Secondary) + .setStyle(ButtonStyle.Primary) .setLabel("Note") + .setEmoji("📝") + .setDisabled(!(await modactions.hasNotePermission(executingMember, interaction.channelId))) .setCustomId(serializeCustomId({ action: ModMenuActionType.NOTE, target: interaction.targetId })), new ButtonBuilder() - .setStyle(ButtonStyle.Secondary) + .setStyle(ButtonStyle.Primary) .setLabel("Warn") + .setEmoji("⚠️") + .setDisabled(!(await modactions.hasWarnPermission(executingMember, interaction.channelId))) .setCustomId(serializeCustomId({ action: ModMenuActionType.WARN, target: interaction.targetId })), new ButtonBuilder() - .setStyle(ButtonStyle.Secondary) - .setLabel("Clean") - .setCustomId(serializeCustomId({ action: ModMenuActionType.CLEAN, target: interaction.targetId })), - new ButtonBuilder() - .setStyle(ButtonStyle.Secondary) + .setStyle(ButtonStyle.Primary) .setLabel("Mute") + .setEmoji("🔇") + .setDisabled(!(await modactions.hasMutePermission(executingMember, interaction.channelId))) .setCustomId(serializeCustomId({ action: ModMenuActionType.MUTE, target: interaction.targetId })), new ButtonBuilder() - .setStyle(ButtonStyle.Secondary) + .setStyle(ButtonStyle.Primary) .setLabel("Ban") + .setEmoji("🚫") + .setDisabled(!(await modactions.hasBanPermission(executingMember, interaction.channelId))) .setCustomId(serializeCustomId({ action: ModMenuActionType.BAN, target: interaction.targetId })), ] satisfies ButtonBuilder[]; @@ -204,6 +215,7 @@ async function displayModMenu( if (opts.action == ModMenuActionType.PAGE && opts.target == ModMenuNavigationType.INFO && infoEmbed != null) { infoButton .setLabel("Cases") + .setEmoji("📋") .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.CASES })); firstButton.setDisabled(true); prevButton.setDisabled(true); @@ -219,6 +231,7 @@ async function displayModMenu( } else if (opts.action == ModMenuActionType.PAGE && opts.target == ModMenuNavigationType.CASES) { infoButton .setLabel("Info") + .setEmoji("ℹ") .setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.INFO })); updateNavButtonState(firstButton, prevButton, nextButton, lastButton, page, totalPages); @@ -264,8 +277,6 @@ async function displayModMenu( 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) { diff --git a/backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts index 3c060cb37..55123597e 100644 --- a/backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts @@ -1,8 +1,10 @@ +import { PermissionFlagsBits } from "discord.js"; import { guildPluginUserContextMenuCommand } from "knub"; import { launchMuteActionModal } from "../actions/mute"; export const MuteCmd = guildPluginUserContextMenuCommand({ name: "Mute", + defaultMemberPermissions: PermissionFlagsBits.ModerateMembers.toString(), 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 index c4f0fa9dd..d11633832 100644 --- a/backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts @@ -1,8 +1,10 @@ +import { PermissionFlagsBits } from "discord.js"; import { guildPluginUserContextMenuCommand } from "knub"; import { launchNoteActionModal } from "../actions/note"; export const NoteCmd = guildPluginUserContextMenuCommand({ name: "Note", + defaultMemberPermissions: PermissionFlagsBits.ManageMessages.toString(), 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 index 3f62196c9..b3e6a5459 100644 --- a/backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts @@ -1,8 +1,10 @@ +import { PermissionFlagsBits } from "discord.js"; import { guildPluginUserContextMenuCommand } from "knub"; import { launchWarnActionModal } from "../actions/warn"; export const WarnCmd = guildPluginUserContextMenuCommand({ name: "Warn", + defaultMemberPermissions: PermissionFlagsBits.ManageMessages.toString(), async run({ pluginData, interaction }) { await launchWarnActionModal(pluginData, interaction, interaction.targetId); }, From 26bf9363f91222ef094f75ef2d9f3d4dfe16705b Mon Sep 17 00:00:00 2001 From: Obliie Date: Sun, 16 Jul 2023 19:07:03 +0100 Subject: [PATCH 07/29] fix(ContextMenus): correct ban modal custom id --- backend/src/plugins/ContextMenus/actions/ban.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index 0a61c57f3..259622a6e 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -77,7 +77,7 @@ export async function launchBanActionModal( interaction: ButtonInteraction | ContextMenuCommandInteraction, target: string, ) { - const modalId = `${ModMenuActionType.WARN}:${interaction.id}`; + const modalId = `${ModMenuActionType.BAN}:${interaction.id}`; const modal = new ModalBuilder().setCustomId(modalId).setTitle("Ban"); const durationIn = new TextInputBuilder() .setCustomId("duration") From 6689f91a6a14e0a72889a75fe0af3c170b4a9628 Mon Sep 17 00:00:00 2001 From: Obliie Date: Sun, 16 Jul 2023 19:48:53 +0100 Subject: [PATCH 08/29] feat(ContextMenus): add new field to modals for case evidence --- .../src/plugins/ContextMenus/actions/ban.ts | 17 +++++++++-- .../src/plugins/ContextMenus/actions/mute.ts | 17 +++++++++-- .../plugins/ContextMenus/actions/update.ts | 28 +++++++++++++++++++ .../src/plugins/ContextMenus/actions/warn.ts | 17 +++++++++-- 4 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 backend/src/plugins/ContextMenus/actions/update.ts diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index 259622a6e..f2b391cc7 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -16,11 +16,13 @@ import { convertDelayStringToMS, renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType, ModMenuActionType } from "../types"; +import { updateAction } from "./update"; async function banAction( pluginData: GuildPluginData, duration: string | undefined, reason: string | undefined, + evidence: string | undefined, target: string, interaction: ButtonInteraction | ContextMenuCommandInteraction, submitInteraction: ModalSubmitInteraction, @@ -67,6 +69,10 @@ async function banAction( durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" } (Case #${result.case.case_number})${messageResultText}`; + if (evidence) { + await updateAction(pluginData, executingMember, result.case, evidence); + } + await interactionToReply .editReply({ content: banMessage, embeds: [], components: [] }) .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); @@ -89,9 +95,15 @@ export async function launchBanActionModal( .setLabel("Reason (Optional)") .setRequired(false) .setStyle(TextInputStyle.Paragraph); + const evidenceIn = new TextInputBuilder() + .setCustomId("evidence") + .setLabel("Evidence (Optional)") + .setRequired(false) + .setStyle(TextInputStyle.Paragraph); const durationRow = new ActionRowBuilder().addComponents(durationIn); const reasonRow = new ActionRowBuilder().addComponents(reasonIn); - modal.addComponents(durationRow, reasonRow); + const evidenceRow = new ActionRowBuilder().addComponents(evidenceIn); + modal.addComponents(durationRow, reasonRow, evidenceRow); await interaction.showModal(modal); await interaction @@ -107,8 +119,9 @@ export async function launchBanActionModal( const duration = submitted.fields.getTextInputValue("duration"); const reason = submitted.fields.getTextInputValue("reason"); + const evidence = submitted.fields.getTextInputValue("evidence"); - await banAction(pluginData, duration, reason, target, interaction, submitted); + await banAction(pluginData, duration, reason, evidence, target, interaction, submitted); }) .catch((err) => logger.error(`Ban modal interaction failed: ${err}`)); } diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index b0ea87763..58457cb01 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -19,11 +19,13 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; import { MutesPlugin } from "../../Mutes/MutesPlugin"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType, ModMenuActionType } from "../types"; +import { updateAction } from "./update"; async function muteAction( pluginData: GuildPluginData, duration: string | undefined, reason: string | undefined, + evidence: string | undefined, target: string, interaction: ButtonInteraction | ContextMenuCommandInteraction, submitInteraction: ModalSubmitInteraction, @@ -73,6 +75,10 @@ async function muteAction( durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" } (Case #${result.case.case_number})${messageResultText}`; + if (evidence) { + await updateAction(pluginData, executingMember, result.case, evidence); + } + await interactionToReply .editReply({ content: muteMessage, embeds: [], components: [] }) .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); @@ -112,9 +118,15 @@ export async function launchMuteActionModal( .setLabel("Reason (Optional)") .setRequired(false) .setStyle(TextInputStyle.Paragraph); + const evidenceIn = new TextInputBuilder() + .setCustomId("evidence") + .setLabel("Evidence (Optional)") + .setRequired(false) + .setStyle(TextInputStyle.Paragraph); const durationRow = new ActionRowBuilder().addComponents(durationIn); const reasonRow = new ActionRowBuilder().addComponents(reasonIn); - modal.addComponents(durationRow, reasonRow); + const evidenceRow = new ActionRowBuilder().addComponents(evidenceIn); + modal.addComponents(durationRow, reasonRow, evidenceRow); await interaction.showModal(modal); await interaction @@ -130,8 +142,9 @@ export async function launchMuteActionModal( const duration = submitted.fields.getTextInputValue("duration"); const reason = submitted.fields.getTextInputValue("reason"); + const evidence = submitted.fields.getTextInputValue("evidence"); - await muteAction(pluginData, duration, reason, target, interaction, submitted); + await muteAction(pluginData, duration, reason, evidence, target, interaction, submitted); }) .catch((err) => logger.error(`Mute modal interaction failed: ${err}`)); } diff --git a/backend/src/plugins/ContextMenus/actions/update.ts b/backend/src/plugins/ContextMenus/actions/update.ts new file mode 100644 index 000000000..3365f2935 --- /dev/null +++ b/backend/src/plugins/ContextMenus/actions/update.ts @@ -0,0 +1,28 @@ +import { GuildMember } from "discord.js"; +import { GuildPluginData } from "knub"; +import { CaseTypes } from "../../../data/CaseTypes"; +import { Case } from "../../../data/entities/Case"; +import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; +import { ContextMenuPluginType } from "../types"; + +export async function updateAction( + pluginData: GuildPluginData, + executingMember: GuildMember, + theCase: Case, + value: string, +) { + const casesPlugin = pluginData.getPlugin(CasesPlugin); + await casesPlugin.createCaseNote({ + caseId: theCase.case_number, + modId: executingMember.id, + body: value, + }); + + pluginData.getPlugin(LogsPlugin).logCaseUpdate({ + mod: executingMember.user, + caseNumber: theCase.case_number, + caseType: CaseTypes[theCase.type], + note: value, + }); +} diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts index d8eba234d..6fbd40f37 100644 --- a/backend/src/plugins/ContextMenus/actions/warn.ts +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -15,10 +15,12 @@ import { renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType, ModMenuActionType } from "../types"; +import { updateAction } from "./update"; async function warnAction( pluginData: GuildPluginData, reason: string, + evidence: string | undefined, target: string, interaction: ButtonInteraction | ContextMenuCommandInteraction, submitInteraction: ModalSubmitInteraction, @@ -70,6 +72,10 @@ async function warnAction( const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : ""; const muteMessage = `Warned **${userName}** (Case #${result.case.case_number})${messageResultText}`; + if (evidence) { + await updateAction(pluginData, executingMember, result.case, evidence); + } + await interactionToReply .editReply({ content: muteMessage, embeds: [], components: [] }) .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); @@ -83,8 +89,14 @@ export async function launchWarnActionModal( const modalId = `${ModMenuActionType.WARN}:${interaction.id}`; const modal = new ModalBuilder().setCustomId(modalId).setTitle("Warn"); const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Reason").setStyle(TextInputStyle.Paragraph); + const evidenceIn = new TextInputBuilder() + .setCustomId("evidence") + .setLabel("Evidence (Optional)") + .setRequired(false) + .setStyle(TextInputStyle.Paragraph); const reasonRow = new ActionRowBuilder().addComponents(reasonIn); - modal.addComponents(reasonRow); + const evidenceRow = new ActionRowBuilder().addComponents(evidenceIn); + modal.addComponents(reasonRow, evidenceRow); await interaction.showModal(modal); await interaction @@ -99,8 +111,9 @@ export async function launchWarnActionModal( } const reason = submitted.fields.getTextInputValue("reason"); + const evidence = submitted.fields.getTextInputValue("evidence"); - await warnAction(pluginData, reason, target, interaction, submitted); + await warnAction(pluginData, reason, evidence, target, interaction, submitted); }) .catch((err) => logger.error(`Warn modal interaction failed: ${err}`)); } From 740aa39cd5119581f8744932ba230d1502d0ccee Mon Sep 17 00:00:00 2001 From: Obliie Date: Sat, 5 Aug 2023 21:31:24 +0100 Subject: [PATCH 09/29] feat: add clean message context menu command --- backend/src/pluginUtils.ts | 43 +++++++++++------ .../plugins/ContextMenus/ContextMenuPlugin.ts | 3 +- .../src/plugins/ContextMenus/actions/clean.ts | 36 +++++++++++---- .../commands/CleanMessageCtxCmd.ts | 11 +++++ .../src/plugins/Utility/commands/CleanCmd.ts | 46 +++++++++++++++---- 5 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 backend/src/plugins/ContextMenus/commands/CleanMessageCtxCmd.ts diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 8e6c6da2c..248999165 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -7,6 +7,7 @@ import { Message, MessageCreateOptions, MessageMentionOptions, + ModalSubmitInteraction, PermissionsBitField, TextBasedChannel, } from "discord.js"; @@ -104,6 +105,7 @@ export async function sendSuccessMessage( channel: TextBasedChannel, body: string, allowedMentions?: MessageMentionOptions, + responseInteraction?: ModalSubmitInteraction, ): Promise { const emoji = pluginData.fullConfig.success_emoji || undefined; const formattedBody = successMessage(body, emoji); @@ -111,13 +113,19 @@ export async function sendSuccessMessage( ? { content: formattedBody, allowedMentions } : { content: formattedBody }; - return channel - .send({ ...content }) // Force line break - .catch((err) => { - const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id; - logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); - return undefined; - }); + if (responseInteraction) { + await responseInteraction + .editReply({ content: formattedBody, embeds: [], components: [] }) + .catch((err) => logger.error(`Interaction reply failed: ${err}`)); + } else { + return channel + .send({ ...content }) // Force line break + .catch((err) => { + const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id; + logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); + return undefined; + }); + } } export async function sendErrorMessage( @@ -125,6 +133,7 @@ export async function sendErrorMessage( channel: TextBasedChannel, body: string, allowedMentions?: MessageMentionOptions, + responseInteraction?: ModalSubmitInteraction, ): Promise { const emoji = pluginData.fullConfig.error_emoji || undefined; const formattedBody = errorMessage(body, emoji); @@ -132,13 +141,19 @@ export async function sendErrorMessage( ? { content: formattedBody, allowedMentions } : { content: formattedBody }; - return channel - .send({ ...content }) // Force line break - .catch((err) => { - const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id; - logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); - return undefined; - }); + if (responseInteraction) { + await responseInteraction + .editReply({ content: formattedBody, embeds: [], components: [] }) + .catch((err) => logger.error(`Interaction reply failed: ${err}`)); + } else { + return channel + .send({ ...content }) // Force line break + .catch((err) => { + const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id; + logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); + return undefined; + }); + } } export function getBaseUrl(pluginData: AnyPluginData) { diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts index 5461409d9..1d8750b35 100644 --- a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts +++ b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts @@ -9,6 +9,7 @@ import { MutesPlugin } from "../Mutes/MutesPlugin"; import { UtilityPlugin } from "../Utility/UtilityPlugin"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { BanCmd } from "./commands/BanUserCtxCmd"; +import { CleanCmd } from "./commands/CleanMessageCtxCmd"; import { ModMenuCmd } from "./commands/ModMenuUserCtxCmd"; import { MuteCmd } from "./commands/MuteUserCtxCmd"; import { NoteCmd } from "./commands/NoteUserCtxCmd"; @@ -49,7 +50,7 @@ export const ContextMenuPlugin = zeppelinGuildPlugin()({ defaultOptions, - contextMenuCommands: [ModMenuCmd, NoteCmd, WarnCmd, MuteCmd, BanCmd], + contextMenuCommands: [ModMenuCmd, NoteCmd, WarnCmd, MuteCmd, BanCmd, CleanCmd], beforeLoad(pluginData) { const { state, guild } = pluginData; diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index 628451c66..c5125721a 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -1,4 +1,12 @@ -import { ActionRowBuilder, ButtonInteraction, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; +import { + ActionRowBuilder, + Message, + MessageContextMenuCommandInteraction, + ModalBuilder, + ModalSubmitInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; import { GuildPluginData } from "knub"; import { logger } from "../../../logger"; import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; @@ -9,7 +17,9 @@ export async function cleanAction( pluginData: GuildPluginData, amount: number, target: string, - interaction: ButtonInteraction, + targetMessage: Message, + targetChannel: string, + interaction: ModalSubmitInteraction, ) { const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const userCfg = await pluginData.config.getMatchingConfig({ @@ -18,26 +28,27 @@ export async function cleanAction( }); const utility = pluginData.getPlugin(UtilityPlugin); - if (!userCfg.can_use || !(await utility.hasPermission(executingMember, interaction.channelId, "can_clean"))) { + if (!userCfg.can_use || !(await utility.hasPermission(executingMember, targetChannel, "can_clean"))) { 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`, + content: `Cleaning ${amount} messages from ${target}...`, embeds: [], components: [], }) .catch((err) => logger.error(`Clean interaction reply failed: ${err}`)); + + await utility.clean({ count: amount, channel: targetChannel, "response-interaction": interaction }, targetMessage); } export async function launchCleanActionModal( pluginData: GuildPluginData, - interaction: ButtonInteraction, + interaction: MessageContextMenuCommandInteraction, target: string, ) { const modalId = `${ModMenuActionType.CLEAN}:${interaction.id}`; @@ -50,7 +61,9 @@ export async function launchCleanActionModal( await interaction .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) .then(async (submitted) => { - await submitted.deferUpdate().catch((err) => logger.error(`Clean interaction defer failed: ${err}`)); + await submitted + .deferReply({ ephemeral: true }) + .catch((err) => logger.error(`Clean interaction defer failed: ${err}`)); const amount = submitted.fields.getTextInputValue("amount"); if (isNaN(Number(amount))) { @@ -60,7 +73,14 @@ export async function launchCleanActionModal( return; } - await cleanAction(pluginData, Number(amount), target, interaction); + await cleanAction( + pluginData, + Number(amount), + target, + interaction.targetMessage, + interaction.channelId, + submitted, + ); }) .catch((err) => logger.error(`Clean modal interaction failed: ${err}`)); } diff --git a/backend/src/plugins/ContextMenus/commands/CleanMessageCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/CleanMessageCtxCmd.ts new file mode 100644 index 000000000..0902ab006 --- /dev/null +++ b/backend/src/plugins/ContextMenus/commands/CleanMessageCtxCmd.ts @@ -0,0 +1,11 @@ +import { PermissionFlagsBits } from "discord.js"; +import { guildPluginMessageContextMenuCommand } from "knub"; +import { launchCleanActionModal } from "../actions/clean"; + +export const CleanCmd = guildPluginMessageContextMenuCommand({ + name: "Clean", + defaultMemberPermissions: PermissionFlagsBits.ManageMessages.toString(), + async run({ pluginData, interaction }) { + await launchCleanActionModal(pluginData, interaction, interaction.targetId); + }, +}); diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index 18d4efa2e..e47dc4131 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -1,4 +1,4 @@ -import { Message, Snowflake, TextChannel, User } from "discord.js"; +import { Message, ModalSubmitInteraction, Snowflake, TextChannel, User } from "discord.js"; import { GuildPluginData } from "knub"; import { allowTimeout } from "../../../RegExpRunner"; import { commandTypeHelpers as ct } from "../../../commandTypes"; @@ -77,17 +77,24 @@ export interface CleanArgs { "has-invites"?: boolean; match?: RegExp; "to-id"?: string; + "response-interaction"?: ModalSubmitInteraction; } export async function cleanCmd(pluginData: GuildPluginData, args: CleanArgs | any, msg) { if (args.count > MAX_CLEAN_COUNT || args.count <= 0) { - sendErrorMessage(pluginData, msg.channel, `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`); + sendErrorMessage( + pluginData, + msg.channel, + `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`, + undefined, + args["response-interaction"], + ); return; } const targetChannel = args.channel ? pluginData.guild.channels.cache.get(args.channel as Snowflake) : msg.channel; if (!targetChannel?.isTextBased()) { - sendErrorMessage(pluginData, msg.channel, `Invalid channel specified`); + sendErrorMessage(pluginData, msg.channel, `Invalid channel specified`, undefined, args["response-interaction"]); return; } @@ -99,12 +106,21 @@ export async function cleanCmd(pluginData: GuildPluginData, a categoryId: targetChannel.parentId, }); if (configForTargetChannel.can_clean !== true) { - sendErrorMessage(pluginData, msg.channel, `Missing permissions to use clean on that channel`); + sendErrorMessage( + pluginData, + msg.channel, + `Missing permissions to use clean on that channel`, + undefined, + args["response-interaction"], + ); return; } } - const cleaningMessage = msg.channel.send("Cleaning..."); + let cleaningMessage: Message | undefined = undefined; + if (!args["response-interaction"]) { + cleaningMessage = await msg.channel.send("Cleaning..."); + } const messagesToClean: Message[] = []; let beforeId = msg.id; @@ -202,19 +218,31 @@ export async function cleanCmd(pluginData: GuildPluginData, a } } - responseMsg = await sendSuccessMessage(pluginData, msg.channel, responseText); + responseMsg = await sendSuccessMessage( + pluginData, + msg.channel, + responseText, + undefined, + args["response-interaction"], + ); } else { const responseText = `Found no messages to clean${note ? ` (${note})` : ""}!`; - responseMsg = await sendErrorMessage(pluginData, msg.channel, responseText); + responseMsg = await sendErrorMessage( + pluginData, + msg.channel, + responseText, + undefined, + args["response-interaction"], + ); } - await (await cleaningMessage).delete(); + cleaningMessage?.delete(); if (targetChannel.id === msg.channel.id) { // Delete the !clean command and the bot response if a different channel wasn't specified // (so as not to spam the cleaned channel with the command itself) + msg.delete().catch(noop); setTimeout(() => { - msg.delete().catch(noop); responseMsg?.delete().catch(noop); }, CLEAN_COMMAND_DELETE_DELAY); } From dfb0e2c19d1aa123834294134830a67bdf00858d Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Mon, 22 Jan 2024 17:21:05 +0100 Subject: [PATCH 10/29] WIP: Note Slash Command --- backend/src/pluginUtils.ts | 64 +++++++- .../plugins/ModActions/ModActionsPlugin.ts | 23 ++- .../plugins/ModActions/commands/AddCaseCmd.ts | 4 +- .../src/plugins/ModActions/commands/BanCmd.ts | 146 +++++++++--------- .../plugins/ModActions/commands/CaseCmd.ts | 4 +- .../ModActions/commands/CasesModCmd.ts | 4 +- .../ModActions/commands/CasesUserCmd.ts | 4 +- .../ModActions/commands/DeleteCaseCmd.ts | 4 +- .../ModActions/commands/ForcebanCmd.ts | 4 +- .../ModActions/commands/ForcemuteCmd.ts | 4 +- .../ModActions/commands/ForceunmuteCmd.ts | 4 +- .../ModActions/commands/HideCaseCmd.ts | 4 +- .../plugins/ModActions/commands/KickCmd.ts | 4 +- .../plugins/ModActions/commands/MassBanCmd.ts | 4 +- .../ModActions/commands/MassUnbanCmd.ts | 4 +- .../ModActions/commands/MassmuteCmd.ts | 4 +- .../plugins/ModActions/commands/MuteCmd.ts | 4 +- .../plugins/ModActions/commands/NoteCmd.ts | 54 ------- .../ModActions/commands/SoftbanCommand.ts | 4 +- .../plugins/ModActions/commands/UnbanCmd.ts | 4 +- .../ModActions/commands/UnhideCaseCmd.ts | 4 +- .../plugins/ModActions/commands/UnmuteCmd.ts | 4 +- .../plugins/ModActions/commands/UpdateCmd.ts | 4 +- .../plugins/ModActions/commands/WarnCmd.ts | 4 +- .../ModActions/commands/note/NoteMsgCmd.ts | 31 ++++ .../ModActions/commands/note/NoteSlashCmd.ts | 45 ++++++ .../ModActions/functions/actualNoteCmd.ts | 47 ++++++ backend/src/plugins/ModActions/types.ts | 5 +- backend/src/plugins/availablePlugins.ts | 2 +- 29 files changed, 319 insertions(+), 178 deletions(-) delete mode 100644 backend/src/plugins/ModActions/commands/NoteCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualNoteCmd.ts diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 248999165..7ae532176 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -3,6 +3,7 @@ */ import { + ChatInputCommandInteraction, GuildMember, Message, MessageCreateOptions, @@ -100,12 +101,19 @@ export function makeIoTsConfigParser>(schema: Schema) }; } +function isContextInteraction( + context: TextBasedChannel | ChatInputCommandInteraction, +): context is ChatInputCommandInteraction { + return "commandId" in context && !!context.commandId; +} + export async function sendSuccessMessage( pluginData: AnyPluginData, - channel: TextBasedChannel, + context: TextBasedChannel | ChatInputCommandInteraction, body: string, allowedMentions?: MessageMentionOptions, responseInteraction?: ModalSubmitInteraction, + ephemeral = false, ): Promise { const emoji = pluginData.fullConfig.success_emoji || undefined; const formattedBody = successMessage(body, emoji); @@ -117,23 +125,44 @@ export async function sendSuccessMessage( await responseInteraction .editReply({ content: formattedBody, embeds: [], components: [] }) .catch((err) => logger.error(`Interaction reply failed: ${err}`)); - } else { - return channel + + return; + } + + if (!isContextInteraction(context)) { + // noinspection TypeScriptValidateJSTypes + return context .send({ ...content }) // Force line break .catch((err) => { - const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id; + const channelInfo = "guild" in context ? `${context.id} (${context.guild.id})` : context.id; logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); + return undefined; }); } + + const replyMethod = context.replied ? "followUp" : "reply"; + + return context[replyMethod]({ + content: formattedBody, + embeds: [], + components: [], + fetchReply: true, + ephemeral, + }).catch((err) => { + logger.error(`Context reply failed: ${err}`); + + return undefined; + }); } export async function sendErrorMessage( pluginData: AnyPluginData, - channel: TextBasedChannel, + context: TextBasedChannel | ChatInputCommandInteraction, body: string, allowedMentions?: MessageMentionOptions, responseInteraction?: ModalSubmitInteraction, + ephemeral = false, ): Promise { const emoji = pluginData.fullConfig.error_emoji || undefined; const formattedBody = errorMessage(body, emoji); @@ -145,15 +174,34 @@ export async function sendErrorMessage( await responseInteraction .editReply({ content: formattedBody, embeds: [], components: [] }) .catch((err) => logger.error(`Interaction reply failed: ${err}`)); - } else { - return channel + + return; + } + + if (!isContextInteraction(context)) { + // noinspection TypeScriptValidateJSTypes + return context .send({ ...content }) // Force line break .catch((err) => { - const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id; + const channelInfo = "guild" in context ? `${context.id} (${context.guild.id})` : context.id; logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); return undefined; }); } + + const replyMethod = context.replied ? "followUp" : "reply"; + + return context[replyMethod]({ + content: formattedBody, + embeds: [], + components: [], + fetchReply: true, + ephemeral, + }).catch((err) => { + logger.error(`Context reply failed: ${err}`); + + return undefined; + }); } export function getBaseUrl(pluginData: AnyPluginData) { diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index 9b7d3404b..57b2709e8 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -28,13 +28,14 @@ import { MassbanCmd } from "./commands/MassBanCmd"; import { MassunbanCmd } from "./commands/MassUnbanCmd"; import { MassmuteCmd } from "./commands/MassmuteCmd"; import { MuteCmd } from "./commands/MuteCmd"; -import { NoteCmd } from "./commands/NoteCmd"; import { SoftbanCmd } from "./commands/SoftbanCommand"; import { UnbanCmd } from "./commands/UnbanCmd"; import { UnhideCaseCmd } from "./commands/UnhideCaseCmd"; import { UnmuteCmd } from "./commands/UnmuteCmd"; import { UpdateCmd } from "./commands/UpdateCmd"; import { WarnCmd } from "./commands/WarnCmd"; +import { NoteMsgCmd } from "./commands/note/NoteMsgCmd"; +import { NoteSlashCmd } from "./commands/note/NoteSlashCmd"; import { AuditLogEvents } from "./events/AuditLogEvents"; import { CreateBanCaseOnManualBanEvt } from "./events/CreateBanCaseOnManualBanEvt"; import { CreateUnbanCaseOnManualUnbanEvt } from "./events/CreateUnbanCaseOnManualUnbanEvt"; @@ -52,7 +53,14 @@ import { offModActionsEvent } from "./functions/offModActionsEvent"; import { onModActionsEvent } from "./functions/onModActionsEvent"; import { updateCase } from "./functions/updateCase"; import { warnMember } from "./functions/warnMember"; -import { BanOptions, ConfigSchema, KickOptions, ModActionsPluginType, WarnOptions } from "./types"; +import { + BanOptions, + ConfigSchema, + KickOptions, + ModActionsPluginType, + WarnOptions, + modActionsSlashGroup, +} from "./types"; const defaultOptions = { config: { @@ -135,9 +143,18 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ events: [CreateBanCaseOnManualBanEvt, CreateUnbanCaseOnManualUnbanEvt, PostAlertOnMemberJoinEvt, AuditLogEvents], + slashCommands: [ + modActionsSlashGroup({ + name: "mod", + description: "Moderation actions", + defaultMemberPermissions: "0", + subcommands: [{ type: "slash", ...NoteSlashCmd }], + }), + ], + messageCommands: [ UpdateCmd, - NoteCmd, + NoteMsgCmd, WarnCmd, MuteCmd, ForcemuteCmd, diff --git a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts index 43575463a..dc8df97f6 100644 --- a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts @@ -6,13 +6,13 @@ import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from ". import { renderUserUsername, resolveMember, resolveUser } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), }; -export const AddCaseCmd = modActionsCmd({ +export const AddCaseCmd = modActionsMsgCmd({ trigger: "addcase", permission: "can_addcase", description: "Add an arbitrary case to the specified user without taking any action", diff --git a/backend/src/plugins/ModActions/commands/BanCmd.ts b/backend/src/plugins/ModActions/commands/BanCmd.ts index 9d32cd101..f0a20d5ff 100644 --- a/backend/src/plugins/ModActions/commands/BanCmd.ts +++ b/backend/src/plugins/ModActions/commands/BanCmd.ts @@ -13,7 +13,7 @@ import { banUserId } from "../functions/banUserId"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { isBanned } from "../functions/isBanned"; import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -22,7 +22,7 @@ const opts = { "delete-days": ct.number({ option: true, shortcut: "d" }), }; -export const BanCmd = modActionsCmd({ +export const BanCmd = modActionsMsgCmd({ trigger: "ban", permission: "can_ban", description: "Ban or Tempban the specified member", @@ -45,13 +45,12 @@ export const BanCmd = modActionsCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { sendErrorMessage(pluginData, msg.channel, `User not found`); return; } - const time = args["time"] ? args["time"] : null; - const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id); // The moderator who did the action is the message author or, if used, the specified -mod let mod = msg.member; @@ -64,85 +63,25 @@ export const BanCmd = modActionsCmd({ mod = args.mod; } + const time = args["time"] ? args["time"] : null; + const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); + // acquire a lock because of the needed user-inputs below (if banned/not on server) const lock = await pluginData.locks.acquire(banLock(user)); let forceban = false; const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id); + if (!memberToBan) { const banned = await isBanned(pluginData, user.id); - if (banned) { - // Abort if trying to ban user indefinitely if they are already banned indefinitely - if (!existingTempban && !time) { - sendErrorMessage(pluginData, msg.channel, `User is already banned indefinitely.`); - return; - } - // Ask the mod if we should update the existing ban - const reply = await waitForButtonConfirm( - msg.channel, - { content: "Failed to message the user. Log the warning anyway?" }, - { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, - ); - if (!reply) { - sendErrorMessage(pluginData, msg.channel, "User already banned, update cancelled by moderator"); - lock.unlock(); - return; - } else { - // Update or add new tempban / remove old tempban - if (time && time > 0) { - if (existingTempban) { - await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id); - } else { - await pluginData.state.tempbans.addTempban(user.id, time, mod.id); - } - const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!; - registerExpiringTempban(tempban); - } else if (existingTempban) { - clearExpiringTempban(existingTempban); - pluginData.state.tempbans.clear(user.id); - } - - // Create a new case for the updated ban since we never stored the old case id and log the action - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const createdCase = await casesPlugin.createCase({ - modId: mod.id, - type: CaseTypes.Ban, - userId: user.id, - reason, - noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`], - }); - if (time) { - pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ - mod: mod.user, - user, - caseNumber: createdCase.case_number, - reason, - banTime: humanizeDuration(time), - }); - } else { - pluginData.getPlugin(LogsPlugin).logMemberBan({ - mod: mod.user, - user, - caseNumber: createdCase.case_number, - reason, - }); - } - - sendSuccessMessage( - pluginData, - msg.channel, - `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, - ); - lock.unlock(); - return; - } - } else { + if (!banned) { // Ask the mod if we should upgrade to a forceban as the user is not on the server const reply = await waitForButtonConfirm( msg.channel, { content: "User not on server, forceban instead?" }, { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, ); + if (!reply) { sendErrorMessage(pluginData, msg.channel, "User not on server, ban cancelled by moderator"); lock.unlock(); @@ -151,6 +90,73 @@ export const BanCmd = modActionsCmd({ forceban = true; } } + + // Abort if trying to ban user indefinitely if they are already banned indefinitely + if (!existingTempban && !time) { + sendErrorMessage(pluginData, msg.channel, `User is already banned indefinitely.`); + return; + } + + // Ask the mod if we should update the existing ban + const reply = await waitForButtonConfirm( + msg.channel, + { content: "Failed to message the user. Log the warning anyway?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, + ); + + if (!reply) { + sendErrorMessage(pluginData, msg.channel, "User already banned, update cancelled by moderator"); + lock.unlock(); + return; + } + + // Update or add new tempban / remove old tempban + if (time && time > 0) { + if (existingTempban) { + await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id); + } else { + await pluginData.state.tempbans.addTempban(user.id, time, mod.id); + } + const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!; + registerExpiringTempban(tempban); + } else if (existingTempban) { + clearExpiringTempban(existingTempban); + pluginData.state.tempbans.clear(user.id); + } + + // Create a new case for the updated ban since we never stored the old case id and log the action + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const createdCase = await casesPlugin.createCase({ + modId: mod.id, + type: CaseTypes.Ban, + userId: user.id, + reason, + noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`], + }); + if (time) { + pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ + mod: mod.user, + user, + caseNumber: createdCase.case_number, + reason, + banTime: humanizeDuration(time), + }); + } else { + pluginData.getPlugin(LogsPlugin).logMemberBan({ + mod: mod.user, + user, + caseNumber: createdCase.case_number, + reason, + }); + } + + sendSuccessMessage( + pluginData, + msg.channel, + `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, + ); + lock.unlock(); + return; } // Make sure we're allowed to ban this member if they are on the server diff --git a/backend/src/plugins/ModActions/commands/CaseCmd.ts b/backend/src/plugins/ModActions/commands/CaseCmd.ts index d3d8ce95b..8a92ed3f9 100644 --- a/backend/src/plugins/ModActions/commands/CaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/CaseCmd.ts @@ -1,9 +1,9 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; import { sendErrorMessage } from "../../../pluginUtils"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; -export const CaseCmd = modActionsCmd({ +export const CaseCmd = modActionsMsgCmd({ trigger: "case", permission: "can_view", description: "Show information about a specific case", diff --git a/backend/src/plugins/ModActions/commands/CasesModCmd.ts b/backend/src/plugins/ModActions/commands/CasesModCmd.ts index 5b0e32730..e07176f58 100644 --- a/backend/src/plugins/ModActions/commands/CasesModCmd.ts +++ b/backend/src/plugins/ModActions/commands/CasesModCmd.ts @@ -7,7 +7,7 @@ import { createPaginatedMessage } from "../../../utils/createPaginatedMessage"; import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; import { getGuildPrefix } from "../../../utils/getGuildPrefix"; import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.userId({ option: true }), @@ -15,7 +15,7 @@ const opts = { const casesPerPage = 5; -export const CasesModCmd = modActionsCmd({ +export const CasesModCmd = modActionsMsgCmd({ trigger: ["cases", "modlogs", "infractions"], permission: "can_view", description: "Show the most recent 5 cases by the specified -mod", diff --git a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts index 069ad31f4..a92ea75d9 100644 --- a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts +++ b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts @@ -7,7 +7,7 @@ import { UnknownUser, chunkArray, emptyEmbedValue, renderUserUsername, resolveUs import { asyncMap } from "../../../utils/async"; import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; import { getGuildPrefix } from "../../../utils/getGuildPrefix"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }), @@ -21,7 +21,7 @@ const opts = { unbans: ct.switchOption({ def: false, shortcut: "ub" }), }; -export const CasesUserCmd = modActionsCmd({ +export const CasesUserCmd = modActionsMsgCmd({ trigger: ["cases", "modlogs"], permission: "can_view", description: "Show a list of cases the specified user has", diff --git a/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts b/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts index 823bc7263..cb3c56d82 100644 --- a/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts @@ -6,9 +6,9 @@ import { SECONDS, trimLines } from "../../../utils"; import { CasesPlugin } from "../../Cases/CasesPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; -export const DeleteCaseCmd = modActionsCmd({ +export const DeleteCaseCmd = modActionsMsgCmd({ trigger: ["delete_case", "deletecase"], permission: "can_deletecase", description: trimLines(` diff --git a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts index 4ddef1544..251be232c 100644 --- a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts +++ b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts @@ -9,13 +9,13 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { ignoreEvent } from "../functions/ignoreEvent"; import { isBanned } from "../functions/isBanned"; -import { IgnoredEventType, modActionsCmd } from "../types"; +import { IgnoredEventType, modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), }; -export const ForcebanCmd = modActionsCmd({ +export const ForcebanCmd = modActionsMsgCmd({ trigger: "forceban", permission: "can_ban", description: "Force-ban the specified user, even if they aren't on the server", diff --git a/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts b/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts index 18fe12280..4b6f90dac 100644 --- a/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts @@ -2,7 +2,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn, sendErrorMessage } from "../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../utils"; import { actualMuteUserCmd } from "../functions/actualMuteUserCmd"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -10,7 +10,7 @@ const opts = { "notify-channel": ct.textChannel({ option: true }), }; -export const ForcemuteCmd = modActionsCmd({ +export const ForcemuteCmd = modActionsMsgCmd({ trigger: "forcemute", permission: "can_mute", description: "Force-mute the specified user, even if they're not on the server", diff --git a/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts b/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts index 8ce0ce14f..5ce489a1e 100644 --- a/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts @@ -2,13 +2,13 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn, sendErrorMessage } from "../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../utils"; import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), }; -export const ForceUnmuteCmd = modActionsCmd({ +export const ForceUnmuteCmd = modActionsMsgCmd({ trigger: "forceunmute", permission: "can_mute", description: "Force-unmute the specified user, even if they're not on the server", diff --git a/backend/src/plugins/ModActions/commands/HideCaseCmd.ts b/backend/src/plugins/ModActions/commands/HideCaseCmd.ts index 38337d85f..b08756db7 100644 --- a/backend/src/plugins/ModActions/commands/HideCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/HideCaseCmd.ts @@ -1,8 +1,8 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; -export const HideCaseCmd = modActionsCmd({ +export const HideCaseCmd = modActionsMsgCmd({ trigger: ["hide", "hidecase", "hide_case"], permission: "can_hidecase", description: "Hide the specified case so it doesn't appear in !cases or !info", diff --git a/backend/src/plugins/ModActions/commands/KickCmd.ts b/backend/src/plugins/ModActions/commands/KickCmd.ts index 080294d00..2e32f3e28 100644 --- a/backend/src/plugins/ModActions/commands/KickCmd.ts +++ b/backend/src/plugins/ModActions/commands/KickCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { actualKickMemberCmd } from "../functions/actualKickMemberCmd"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -9,7 +9,7 @@ const opts = { clean: ct.bool({ option: true, isSwitch: true }), }; -export const KickCmd = modActionsCmd({ +export const KickCmd = modActionsMsgCmd({ trigger: "kick", permission: "can_kick", description: "Kick the specified member", diff --git a/backend/src/plugins/ModActions/commands/MassBanCmd.ts b/backend/src/plugins/ModActions/commands/MassBanCmd.ts index d31aadd7a..4769cd51b 100644 --- a/backend/src/plugins/ModActions/commands/MassBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassBanCmd.ts @@ -11,9 +11,9 @@ import { DAYS, MINUTES, SECONDS, noop } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { ignoreEvent } from "../functions/ignoreEvent"; -import { IgnoredEventType, modActionsCmd } from "../types"; +import { IgnoredEventType, modActionsMsgCmd } from "../types"; -export const MassbanCmd = modActionsCmd({ +export const MassbanCmd = modActionsMsgCmd({ trigger: "massban", permission: "can_massban", description: "Mass-ban a list of user IDs", diff --git a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts index c5ce37acb..a873b4c6a 100644 --- a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts @@ -9,9 +9,9 @@ import { LogsPlugin } from "../../Logs/LogsPlugin"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { ignoreEvent } from "../functions/ignoreEvent"; import { isBanned } from "../functions/isBanned"; -import { IgnoredEventType, modActionsCmd } from "../types"; +import { IgnoredEventType, modActionsMsgCmd } from "../types"; -export const MassunbanCmd = modActionsCmd({ +export const MassunbanCmd = modActionsMsgCmd({ trigger: "massunban", permission: "can_massunban", description: "Mass-unban a list of user IDs", diff --git a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts index a62c029e6..65422f0f4 100644 --- a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts @@ -7,9 +7,9 @@ import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginU import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; -export const MassmuteCmd = modActionsCmd({ +export const MassmuteCmd = modActionsMsgCmd({ trigger: "massmute", permission: "can_massmute", description: "Mass-mute a list of user IDs", diff --git a/backend/src/plugins/ModActions/commands/MuteCmd.ts b/backend/src/plugins/ModActions/commands/MuteCmd.ts index f824fca3e..4c5057011 100644 --- a/backend/src/plugins/ModActions/commands/MuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/MuteCmd.ts @@ -4,7 +4,7 @@ import { resolveMember, resolveUser } from "../../../utils"; import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; import { actualMuteUserCmd } from "../functions/actualMuteUserCmd"; import { isBanned } from "../functions/isBanned"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -12,7 +12,7 @@ const opts = { "notify-channel": ct.textChannel({ option: true }), }; -export const MuteCmd = modActionsCmd({ +export const MuteCmd = modActionsMsgCmd({ trigger: "mute", permission: "can_mute", description: "Mute the specified member", diff --git a/backend/src/plugins/ModActions/commands/NoteCmd.ts b/backend/src/plugins/ModActions/commands/NoteCmd.ts deleted file mode 100644 index b13ed4981..000000000 --- a/backend/src/plugins/ModActions/commands/NoteCmd.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { renderUserUsername, resolveUser } from "../../../utils"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { modActionsCmd } from "../types"; - -export const NoteCmd = modActionsCmd({ - trigger: "note", - permission: "can_note", - description: "Add a note to the specified user", - - signature: { - user: ct.string(), - note: ct.string({ required: false, catchAll: true }), - }, - - async run({ pluginData, message: msg, args }) { - const user = await resolveUser(pluginData.client, args.user); - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - if (!args.note && msg.attachments.size === 0) { - sendErrorMessage(pluginData, msg.channel, "Text or attachment required"); - return; - } - - const userName = renderUserUsername(user); - const reason = formatReasonWithAttachments(args.note, [...msg.attachments.values()]); - - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const createdCase = await casesPlugin.createCase({ - userId: user.id, - modId: msg.author.id, - type: CaseTypes.Note, - reason, - }); - - pluginData.getPlugin(LogsPlugin).logMemberNote({ - mod: msg.author, - user, - caseNumber: createdCase.case_number, - reason, - }); - - sendSuccessMessage(pluginData, msg.channel, `Note added on **${userName}** (Case #${createdCase.case_number})`); - - pluginData.state.events.emit("note", user.id, reason); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/SoftbanCommand.ts b/backend/src/plugins/ModActions/commands/SoftbanCommand.ts index 9bbb64150..a5806cdd8 100644 --- a/backend/src/plugins/ModActions/commands/SoftbanCommand.ts +++ b/backend/src/plugins/ModActions/commands/SoftbanCommand.ts @@ -1,7 +1,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { trimPluginDescription } from "../../../utils"; import { actualKickMemberCmd } from "../functions/actualKickMemberCmd"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -9,7 +9,7 @@ const opts = { "notify-channel": ct.textChannel({ option: true }), }; -export const SoftbanCmd = modActionsCmd({ +export const SoftbanCmd = modActionsMsgCmd({ trigger: "softban", permission: "can_kick", description: trimPluginDescription(` diff --git a/backend/src/plugins/ModActions/commands/UnbanCmd.ts b/backend/src/plugins/ModActions/commands/UnbanCmd.ts index 53363ee02..d232f4e4f 100644 --- a/backend/src/plugins/ModActions/commands/UnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnbanCmd.ts @@ -9,13 +9,13 @@ import { resolveUser } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { ignoreEvent } from "../functions/ignoreEvent"; -import { IgnoredEventType, modActionsCmd } from "../types"; +import { IgnoredEventType, modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), }; -export const UnbanCmd = modActionsCmd({ +export const UnbanCmd = modActionsMsgCmd({ trigger: "unban", permission: "can_unban", description: "Unban the specified member", diff --git a/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts b/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts index 9fe9e2088..7c6eb774a 100644 --- a/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts @@ -1,8 +1,8 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; -export const UnhideCaseCmd = modActionsCmd({ +export const UnhideCaseCmd = modActionsMsgCmd({ trigger: ["unhide", "unhidecase", "unhide_case"], permission: "can_hidecase", description: "Un-hide the specified case, making it appear in !cases and !info again", diff --git a/backend/src/plugins/ModActions/commands/UnmuteCmd.ts b/backend/src/plugins/ModActions/commands/UnmuteCmd.ts index 55c84631d..3a4171ac4 100644 --- a/backend/src/plugins/ModActions/commands/UnmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnmuteCmd.ts @@ -5,13 +5,13 @@ import { resolveMember, resolveUser } from "../../../utils"; import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd"; import { isBanned } from "../functions/isBanned"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; const opts = { mod: ct.member({ option: true }), }; -export const UnmuteCmd = modActionsCmd({ +export const UnmuteCmd = modActionsMsgCmd({ trigger: "unmute", permission: "can_mute", description: "Unmute the specified member", diff --git a/backend/src/plugins/ModActions/commands/UpdateCmd.ts b/backend/src/plugins/ModActions/commands/UpdateCmd.ts index 3310522e1..59c68b0d6 100644 --- a/backend/src/plugins/ModActions/commands/UpdateCmd.ts +++ b/backend/src/plugins/ModActions/commands/UpdateCmd.ts @@ -1,8 +1,8 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { updateCase } from "../functions/updateCase"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; -export const UpdateCmd = modActionsCmd({ +export const UpdateCmd = modActionsMsgCmd({ trigger: ["update", "reason"], permission: "can_note", description: diff --git a/backend/src/plugins/ModActions/commands/WarnCmd.ts b/backend/src/plugins/ModActions/commands/WarnCmd.ts index c81920153..2b3121fdb 100644 --- a/backend/src/plugins/ModActions/commands/WarnCmd.ts +++ b/backend/src/plugins/ModActions/commands/WarnCmd.ts @@ -8,9 +8,9 @@ import { formatReasonWithAttachments } from "../functions/formatReasonWithAttach import { isBanned } from "../functions/isBanned"; import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; import { warnMember } from "../functions/warnMember"; -import { modActionsCmd } from "../types"; +import { modActionsMsgCmd } from "../types"; -export const WarnCmd = modActionsCmd({ +export const WarnCmd = modActionsMsgCmd({ trigger: "warn", permission: "can_warn", description: "Send a warning to the specified user", diff --git a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts new file mode 100644 index 000000000..c7122fe99 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts @@ -0,0 +1,31 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { sendErrorMessage } from "../../../../pluginUtils"; +import { resolveUser } from "../../../../utils"; +import { actualNoteCmd } from "../../functions/actualNoteCmd"; +import { modActionsMsgCmd } from "../../types"; + +export const NoteMsgCmd = modActionsMsgCmd({ + trigger: "note", + permission: "can_note", + description: "Add a note to the specified user", + + signature: { + user: ct.string(), + note: ct.string({ required: false, catchAll: true }), + }, + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + if (!args.note && msg.attachments.size === 0) { + sendErrorMessage(pluginData, msg.channel, "Text or attachment required"); + return; + } + + actualNoteCmd(pluginData, msg.channel, msg.author, [...msg.attachments.values()], user, args.note || ""); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts new file mode 100644 index 000000000..82b38be7e --- /dev/null +++ b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts @@ -0,0 +1,45 @@ +import { ApplicationCommandOptionType, ChatInputCommandInteraction } from "discord.js"; +import { slashOptions } from "knub"; +import { sendErrorMessage } from "../../../../pluginUtils"; +import { actualNoteCmd } from "../../functions/actualNoteCmd"; + +export const NoteSlashCmd = { + name: "note", + description: "Add a note to the specified user", + allowDms: false, + configPermission: "can_note", + + signature: [ + slashOptions.user({ name: "user", description: "The user to add a note to", required: true }), + slashOptions.string({ name: "note", description: "The note to add to the user", required: false }), + ...new Array(10).fill(0).map((_, i) => { + return { + name: `attachment${i + 1}`, + description: "An attachment to add to the note", + type: ApplicationCommandOptionType.Attachment, + required: false, + resolveValue: (interaction: ChatInputCommandInteraction) => { + return interaction.options.getAttachment(`attachment${i + 1}`); + }, + getExtraAPIProps: () => ({}), + }; + }), + ], + + async run({ interaction, options, pluginData }) { + const attachments = new Array(10) + .fill(0) + .map((_, i) => { + return options[`attachment${i + 1}`]; + }) + .filter((a) => a); + + if ((!options.note || options.note.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + actualNoteCmd(pluginData, interaction, interaction.user, attachments, options.user, options.note || ""); + }, +}; diff --git a/backend/src/plugins/ModActions/functions/actualNoteCmd.ts b/backend/src/plugins/ModActions/functions/actualNoteCmd.ts new file mode 100644 index 000000000..524a182ca --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualNoteCmd.ts @@ -0,0 +1,47 @@ +import { Attachment, ChatInputCommandInteraction, TextBasedChannel, User } from "discord.js"; +import { GuildPluginData } from "knub"; +import { CaseTypes } from "../../../data/CaseTypes"; +import { sendSuccessMessage } from "../../../pluginUtils"; +import { UnknownUser, renderUserUsername } from "../../../utils"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { ModActionsPluginType } from "../types"; +import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; + +export async function actualNoteCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + author: User, + attachments: Array, + user: User | UnknownUser, + note: string, +) { + const userName = renderUserUsername(user); + const reason = formatReasonWithAttachments(note, attachments); + + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const createdCase = await casesPlugin.createCase({ + userId: user.id, + modId: author.id, + type: CaseTypes.Note, + reason, + }); + + pluginData.getPlugin(LogsPlugin).logMemberNote({ + mod: author, + user, + caseNumber: createdCase.case_number, + reason, + }); + + sendSuccessMessage( + pluginData, + context, + `Note added on **${userName}** (Case #${createdCase.case_number})`, + undefined, + undefined, + true, + ); + + pluginData.state.events.emit("note", user.id, reason); +} diff --git a/backend/src/plugins/ModActions/types.ts b/backend/src/plugins/ModActions/types.ts index 447b96382..c6c1fe703 100644 --- a/backend/src/plugins/ModActions/types.ts +++ b/backend/src/plugins/ModActions/types.ts @@ -1,7 +1,7 @@ import { GuildTextBasedChannel } from "discord.js"; import { EventEmitter } from "events"; import * as t from "io-ts"; -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, guildPluginSlashGroup } from "knub"; import { Queue } from "../../Queue"; import { GuildCases } from "../../data/GuildCases"; import { GuildLogs } from "../../data/GuildLogs"; @@ -147,5 +147,6 @@ export interface BanOptions { export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" | "unban"; -export const modActionsCmd = guildPluginMessageCommand(); +export const modActionsMsgCmd = guildPluginMessageCommand(); +export const modActionsSlashGroup = guildPluginSlashGroup(); export const modActionsEvt = guildPluginEventListener(); diff --git a/backend/src/plugins/availablePlugins.ts b/backend/src/plugins/availablePlugins.ts index 3df9c347a..45119f0f3 100644 --- a/backend/src/plugins/availablePlugins.ts +++ b/backend/src/plugins/availablePlugins.ts @@ -53,8 +53,8 @@ export const guildPlugins: Array> = [ PostPlugin, ReactionRolesPlugin, MessageSaverPlugin, - // GuildMemberCachePlugin, // FIXME: New caching thing, or fix deadlocks with this plugin ModActionsPlugin, + // GuildMemberCachePlugin, // FIXME: New caching thing, or fix deadlocks with this plugin NameHistoryPlugin, RemindersPlugin, RolesPlugin, From a4c4b17a1458ba6f558357fbf4122cd4d5fcc5aa Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Wed, 14 Feb 2024 09:17:11 +0100 Subject: [PATCH 11/29] =?UTF-8?q?feat:=20first=20batch=20of=20emojis=20?= =?UTF-8?q?=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package-lock.json | 30 +- backend/src/data/GuildCases.ts | 37 ++- backend/src/pluginUtils.ts | 28 +- .../Cases/functions/getRecentCasesByMod.ts | 4 +- .../Cases/functions/getTotalCasesByMod.ts | 10 +- .../plugins/ModActions/ModActionsPlugin.ts | 126 ++++++--- .../plugins/ModActions/commands/AddCaseCmd.ts | 93 ------- .../src/plugins/ModActions/commands/BanCmd.ts | 225 --------------- .../plugins/ModActions/commands/CaseCmd.ts | 29 -- .../ModActions/commands/CasesModCmd.ts | 81 ------ .../ModActions/commands/CasesUserCmd.ts | 139 ---------- .../ModActions/commands/DeleteCaseCmd.ts | 98 ------- .../ModActions/commands/ForcebanCmd.ts | 103 ------- .../ModActions/commands/ForcemuteCmd.ts | 51 ---- .../ModActions/commands/HideCaseCmd.ts | 45 --- .../plugins/ModActions/commands/KickCmd.ts | 29 -- .../plugins/ModActions/commands/MassBanCmd.ts | 156 ----------- .../ModActions/commands/MassUnbanCmd.ts | 125 --------- .../ModActions/commands/MassmuteCmd.ts | 105 ------- .../ModActions/commands/SoftbanCommand.ts | 35 --- .../plugins/ModActions/commands/UnbanCmd.ts | 90 ------ .../ModActions/commands/UnhideCaseCmd.ts | 45 --- .../plugins/ModActions/commands/WarnCmd.ts | 112 -------- .../commands/addcase/AddCaseMsgCmd.ts | 63 +++++ .../commands/addcase/AddCaseSlashCmd.ts | 65 +++++ .../ModActions/commands/ban/BanMsgCmd.ts | 75 +++++ .../ModActions/commands/ban/BanSlashCmd.ts | 98 +++++++ .../ModActions/commands/case/CaseMsgCmd.ts | 19 ++ .../ModActions/commands/case/CaseSlashCmd.ts | 17 ++ .../commands/cases/CasesModMsgCmd.ts | 47 ++++ .../commands/cases/CasesSlashCmd.ts | 48 ++++ .../commands/cases/CasesUserMsgCmd.ts | 57 ++++ .../plugins/ModActions/commands/constants.ts | 2 + .../commands/deletecase/DeleteCaseMsgCmd.ts | 23 ++ .../commands/deletecase/DeleteCaseSlashCmd.ts | 27 ++ .../commands/forceban/ForceBanMsgCmd.ts | 60 ++++ .../commands/forceban/ForceBanSlashCmd.ts | 57 ++++ .../commands/forcemute/ForceMuteMsgCmd.ts | 84 ++++++ .../commands/forcemute/ForceMuteSlashCmd.ts | 95 +++++++ .../ForceUnmuteMsgCmd.ts} | 37 ++- .../forceunmute/ForceUnmuteSlashCmd.ts | 60 ++++ .../commands/hidecase/HideCaseMsgCmd.ts | 19 ++ .../commands/hidecase/HideCaseSlashCmd.ts | 17 ++ .../ModActions/commands/kick/KickMsgCmd.ts | 68 +++++ .../ModActions/commands/kick/KickSlashCmd.ts | 91 ++++++ .../commands/massban/MassBanMsgCmd.ts | 19 ++ .../commands/massban/MassBanSlashCmd.ts | 15 + .../commands/massmute/MassMuteMsgCmd.ts | 19 ++ .../commands/massmute/MassMuteSlashCmd.ts | 15 + .../commands/massunban/MassUnbanMsgCmd.ts | 19 ++ .../commands/massunban/MassUnbanSlashCmd.ts | 15 + .../{MuteCmd.ts => mute/MuteMsgCmd.ts} | 51 +++- .../ModActions/commands/mute/MuteSlashCmd.ts | 130 +++++++++ .../ModActions/commands/note/NoteMsgCmd.ts | 2 +- .../ModActions/commands/note/NoteSlashCmd.ts | 39 +-- .../ModActions/commands/unban/UnbanMsgCmd.ts | 45 +++ .../commands/unban/UnbanSlashCmd.ts | 50 ++++ .../commands/unhidecase/UnhideCaseMsgCmd.ts | 19 ++ .../commands/unhidecase/UnhideCaseSlashCmd.ts | 17 ++ .../{UnmuteCmd.ts => unmute/UnmuteMsgCmd.ts} | 43 ++- .../commands/unmute/UnmuteSlashCmd.ts | 108 ++++++++ .../{UpdateCmd.ts => update/UpdateMsgCmd.ts} | 10 +- .../commands/update/UpdateSlashCmd.ts | 33 +++ .../ModActions/commands/warn/WarnMsgCmd.ts | 79 ++++++ .../ModActions/commands/warn/WarnSlashCmd.ts | 105 +++++++ .../actualCommands/actualAddCaseCmd.ts | 55 ++++ .../functions/actualCommands/actualBanCmd.ts | 182 ++++++++++++ .../functions/actualCommands/actualCaseCmd.ts | 24 ++ .../actualCommands/actualCasesCmd.ts | 258 ++++++++++++++++++ .../actualCommands/actualDeleteCaseCmd.ts | 95 +++++++ .../actualCommands/actualForceBanCmd.ts | 60 ++++ .../actualCommands/actualHideCaseCmd.ts | 38 +++ .../functions/actualCommands/actualKickCmd.ts | 89 ++++++ .../actualCommands/actualMassBanCmd.ts | 163 +++++++++++ .../actualCommands/actualMassMuteCmd.ts | 110 ++++++++ .../actualCommands/actualMassUnbanCmd.ts | 127 +++++++++ .../functions/actualCommands/actualMuteCmd.ts | 96 +++++++ .../{ => actualCommands}/actualNoteCmd.ts | 14 +- .../actualCommands/actualUnbanCmd.ts | 63 +++++ .../actualCommands/actualUnhideCaseCmd.ts | 39 +++ .../actualCommands/actualUnmuteCmd.ts | 55 ++++ .../functions/actualCommands/actualWarnCmd.ts | 61 +++++ .../functions/actualKickMemberCmd.ts | 110 -------- .../ModActions/functions/actualMuteUserCmd.ts | 114 -------- .../functions/actualUnmuteUserCmd.ts | 65 ----- .../ModActions/functions/updateCase.ts | 41 +-- .../ModActions/functions/warnMember.ts | 30 +- backend/src/plugins/ModActions/types.ts | 4 +- backend/src/utils/createPaginatedMessage.ts | 6 +- backend/src/utils/multipleSlashOptions.ts | 20 ++ backend/src/utils/waitForInteraction.ts | 14 +- 91 files changed, 3659 insertions(+), 2032 deletions(-) delete mode 100644 backend/src/plugins/ModActions/commands/AddCaseCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/BanCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/CaseCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/CasesModCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/CasesUserCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/ForcebanCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/ForcemuteCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/HideCaseCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/KickCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/MassBanCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/MassUnbanCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/MassmuteCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/SoftbanCommand.ts delete mode 100644 backend/src/plugins/ModActions/commands/UnbanCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts delete mode 100644 backend/src/plugins/ModActions/commands/WarnCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/constants.ts create mode 100644 backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts rename backend/src/plugins/ModActions/commands/{ForceunmuteCmd.ts => forceunmute/ForceUnmuteMsgCmd.ts} (55%) create mode 100644 backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts rename backend/src/plugins/ModActions/commands/{MuteCmd.ts => mute/MuteMsgCmd.ts} (56%) create mode 100644 backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts rename backend/src/plugins/ModActions/commands/{UnmuteCmd.ts => unmute/UnmuteMsgCmd.ts} (65%) create mode 100644 backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts rename backend/src/plugins/ModActions/commands/{UpdateCmd.ts => update/UpdateMsgCmd.ts} (57%) create mode 100644 backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts create mode 100644 backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts rename backend/src/plugins/ModActions/functions/{ => actualCommands}/actualNoteCmd.ts (71%) create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts create mode 100644 backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts delete mode 100644 backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts delete mode 100644 backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts delete mode 100644 backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts create mode 100644 backend/src/utils/multipleSlashOptions.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index 2949ed98b..daf059302 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -5520,9 +5520,9 @@ } }, "node_modules/knub": { - "version": "32.0.0-next.16", - "resolved": "https://registry.npmjs.org/knub/-/knub-32.0.0-next.16.tgz", - "integrity": "sha512-lmjbLusvinWCoyo0T3dtWy6PEuqysIcqQvg85W85th59ubHasnTc+KGR+4o6EgLCzuDUtc4dP1XQk7XAIKnkYQ==", + "version": "32.0.0-next.17", + "resolved": "https://registry.npmjs.org/knub/-/knub-32.0.0-next.17.tgz", + "integrity": "sha512-q8PeIJuOYUQySOmVuXF56ykklKRnnJmAMpfSN1+vWCNhdMsDxIPZAkRpvekQD6oBkYoL7WlzEsMoUsECGZp+QQ==", "dependencies": { "discord.js": "^14.11.0", "knub-command-manager": "^9.1.0", @@ -9228,11 +9228,16 @@ } }, "node_modules/ts-essentials": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.3.2.tgz", - "integrity": "sha512-JxKJzuWqH1MmH4ZFHtJzGEhkfN3QvVR3C3w+4BIoWeoY68UVVoA2Np/Bca9z0IPSErVCWhv439aT0We4Dks8kQ==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.1.tgz", + "integrity": "sha512-oke0rI2EN9pzHsesdmrOrnqv1eQODmJpd/noJjwj2ZPC3Z4N2wbjrOEqnsEgmvlO2+4fBb0a794DCna2elEVIQ==", "peerDependencies": { "typescript": ">=4.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/ts-mixer": { @@ -9645,19 +9650,6 @@ "node": ">=12" } }, - "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/uid2": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", diff --git a/backend/src/data/GuildCases.ts b/backend/src/data/GuildCases.ts index eb5ed6b72..4137fbee3 100644 --- a/backend/src/data/GuildCases.ts +++ b/backend/src/data/GuildCases.ts @@ -1,4 +1,5 @@ import { In, InsertResult, Repository } from "typeorm"; +import { FindOptionsWhere } from "typeorm/find-options/FindOptionsWhere"; import { Queue } from "../Queue"; import { chunkArray } from "../utils"; import { BaseGuildRepository } from "./BaseGuildRepository"; @@ -73,12 +74,16 @@ export class GuildCases extends BaseGuildRepository { }); } - async getByUserId(userId: string): Promise { + async getByUserId( + userId: string, + filters: Omit, "guild_id" | "user_id"> = {}, + ): Promise { return this.cases.find({ relations: this.getRelations(), where: { guild_id: this.guildId, user_id: userId, + ...filters, }, }); } @@ -98,24 +103,40 @@ export class GuildCases extends BaseGuildRepository { }); } - async getTotalCasesByModId(modId: string): Promise { + async getTotalCasesByModId( + modId: string, + filters: Omit, "guild_id" | "mod_id" | "is_hidden"> = {}, + ): Promise { return this.cases.count({ where: { guild_id: this.guildId, mod_id: modId, is_hidden: false, + ...filters, }, }); } - async getRecentByModId(modId: string, count: number, skip = 0): Promise { + async getRecentByModId( + modId: string, + count: number, + skip = 0, + filters: Omit, "guild_id" | "mod_id"> = {}, + ): Promise { + const where: FindOptionsWhere = { + guild_id: this.guildId, + mod_id: modId, + is_hidden: false, + ...filters, + }; + + if (where.is_hidden === true) { + delete where.is_hidden; + } + return this.cases.find({ relations: this.getRelations(), - where: { - guild_id: this.guildId, - mod_id: modId, - is_hidden: false, - }, + where, skip, take: count, order: { diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 7ae532176..9736c9fe8 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -11,6 +11,7 @@ import { ModalSubmitInteraction, PermissionsBitField, TextBasedChannel, + User, } from "discord.js"; import * as t from "io-ts"; import { @@ -101,19 +102,32 @@ export function makeIoTsConfigParser>(schema: Schema) }; } -function isContextInteraction( - context: TextBasedChannel | ChatInputCommandInteraction, +export function isContextInteraction( + context: TextBasedChannel | User | ChatInputCommandInteraction, ): context is ChatInputCommandInteraction { return "commandId" in context && !!context.commandId; } +export function sendContextResponse( + context: TextBasedChannel | User | ChatInputCommandInteraction, + response: string | Omit, +): Promise { + if (isContextInteraction(context)) { + const options = { ...(typeof response === "string" ? { content: response } : response), fetchReply: true }; + + return (context.replied ? context.followUp(options) : context.reply(options)) as Promise; + } else { + return context.send(response); + } +} + export async function sendSuccessMessage( pluginData: AnyPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: TextBasedChannel | User | ChatInputCommandInteraction, body: string, allowedMentions?: MessageMentionOptions, responseInteraction?: ModalSubmitInteraction, - ephemeral = false, + ephemeral = true, ): Promise { const emoji = pluginData.fullConfig.success_emoji || undefined; const formattedBody = successMessage(body, emoji); @@ -153,12 +167,12 @@ export async function sendSuccessMessage( logger.error(`Context reply failed: ${err}`); return undefined; - }); + }) as Promise; } export async function sendErrorMessage( pluginData: AnyPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: TextBasedChannel | User | ChatInputCommandInteraction, body: string, allowedMentions?: MessageMentionOptions, responseInteraction?: ModalSubmitInteraction, @@ -201,7 +215,7 @@ export async function sendErrorMessage( logger.error(`Context reply failed: ${err}`); return undefined; - }); + }) as Promise; } export function getBaseUrl(pluginData: AnyPluginData) { diff --git a/backend/src/plugins/Cases/functions/getRecentCasesByMod.ts b/backend/src/plugins/Cases/functions/getRecentCasesByMod.ts index b9988078f..e482fbd6f 100644 --- a/backend/src/plugins/Cases/functions/getRecentCasesByMod.ts +++ b/backend/src/plugins/Cases/functions/getRecentCasesByMod.ts @@ -1,4 +1,5 @@ import { GuildPluginData } from "knub"; +import { FindOptionsWhere } from "typeorm/find-options/FindOptionsWhere"; import { Case } from "../../../data/entities/Case"; import { CasesPluginType } from "../types"; @@ -7,6 +8,7 @@ export function getRecentCasesByMod( modId: string, count: number, skip = 0, + filters: Omit, "guild_id" | "mod_id" | "is_hidden"> = {}, ): Promise { - return pluginData.state.cases.getRecentByModId(modId, count, skip); + return pluginData.state.cases.getRecentByModId(modId, count, skip, filters); } diff --git a/backend/src/plugins/Cases/functions/getTotalCasesByMod.ts b/backend/src/plugins/Cases/functions/getTotalCasesByMod.ts index 5753f12e7..235f2d8ac 100644 --- a/backend/src/plugins/Cases/functions/getTotalCasesByMod.ts +++ b/backend/src/plugins/Cases/functions/getTotalCasesByMod.ts @@ -1,6 +1,12 @@ import { GuildPluginData } from "knub"; +import { FindOptionsWhere } from "typeorm/find-options/FindOptionsWhere"; +import { Case } from "../../../data/entities/Case"; import { CasesPluginType } from "../types"; -export function getTotalCasesByMod(pluginData: GuildPluginData, modId: string): Promise { - return pluginData.state.cases.getTotalCasesByModId(modId); +export function getTotalCasesByMod( + pluginData: GuildPluginData, + modId: string, + filters: Omit, "guild_id" | "mod_id" | "is_hidden"> = {}, +): Promise { + return pluginData.state.cases.getTotalCasesByModId(modId, filters); } diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index 57b2709e8..d9b083816 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -13,29 +13,47 @@ import { LogsPlugin } from "../Logs/LogsPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { AddCaseCmd } from "./commands/AddCaseCmd"; -import { BanCmd } from "./commands/BanCmd"; -import { CaseCmd } from "./commands/CaseCmd"; -import { CasesModCmd } from "./commands/CasesModCmd"; -import { CasesUserCmd } from "./commands/CasesUserCmd"; -import { DeleteCaseCmd } from "./commands/DeleteCaseCmd"; -import { ForcebanCmd } from "./commands/ForcebanCmd"; -import { ForcemuteCmd } from "./commands/ForcemuteCmd"; -import { ForceUnmuteCmd } from "./commands/ForceunmuteCmd"; -import { HideCaseCmd } from "./commands/HideCaseCmd"; -import { KickCmd } from "./commands/KickCmd"; -import { MassbanCmd } from "./commands/MassBanCmd"; -import { MassunbanCmd } from "./commands/MassUnbanCmd"; -import { MassmuteCmd } from "./commands/MassmuteCmd"; -import { MuteCmd } from "./commands/MuteCmd"; -import { SoftbanCmd } from "./commands/SoftbanCommand"; -import { UnbanCmd } from "./commands/UnbanCmd"; -import { UnhideCaseCmd } from "./commands/UnhideCaseCmd"; -import { UnmuteCmd } from "./commands/UnmuteCmd"; -import { UpdateCmd } from "./commands/UpdateCmd"; -import { WarnCmd } from "./commands/WarnCmd"; +import { AddCaseMsgCmd } from "./commands/addcase/AddCaseMsgCmd"; +import { AddCaseSlashCmd } from "./commands/addcase/AddCaseSlashCmd"; +import { BanMsgCmd } from "./commands/ban/BanMsgCmd"; +import { BanSlashCmd } from "./commands/ban/BanSlashCmd"; +import { CaseMsgCmd } from "./commands/case/CaseMsgCmd"; +import { CaseSlashCmd } from "./commands/case/CaseSlashCmd"; +import { CasesModMsgCmd } from "./commands/cases/CasesModMsgCmd"; +import { CasesSlashCmd } from "./commands/cases/CasesSlashCmd"; +import { CasesUserMsgCmd } from "./commands/cases/CasesUserMsgCmd"; +import { DeleteCaseMsgCmd } from "./commands/deletecase/DeleteCaseMsgCmd"; +import { DeleteCaseSlashCmd } from "./commands/deletecase/DeleteCaseSlashCmd"; +import { ForceBanMsgCmd } from "./commands/forceban/ForceBanMsgCmd"; +import { ForceBanSlashCmd } from "./commands/forceban/ForceBanSlashCmd"; +import { ForceMuteMsgCmd } from "./commands/forcemute/ForceMuteMsgCmd"; +import { ForceMuteSlashCmd } from "./commands/forcemute/ForceMuteSlashCmd"; +import { ForceUnmuteMsgCmd } from "./commands/forceunmute/ForceUnmuteMsgCmd"; +import { ForceUnmuteSlashCmd } from "./commands/forceunmute/ForceUnmuteSlashCmd"; +import { HideCaseMsgCmd } from "./commands/hidecase/HideCaseMsgCmd"; +import { HideCaseSlashCmd } from "./commands/hidecase/HideCaseSlashCmd"; +import { KickMsgCmd } from "./commands/kick/KickMsgCmd"; +import { KickSlashCmd } from "./commands/kick/KickSlashCmd"; +import { MassBanMsgCmd } from "./commands/massban/MassBanMsgCmd"; +import { MassBanSlashCmd } from "./commands/massban/MassBanSlashCmd"; +import { MassMuteMsgCmd } from "./commands/massmute/MassMuteMsgCmd"; +import { MassMuteSlashSlashCmd } from "./commands/massmute/MassMuteSlashCmd"; +import { MassUnbanMsgCmd } from "./commands/massunban/MassUnbanMsgCmd"; +import { MassUnbanSlashCmd } from "./commands/massunban/MassUnbanSlashCmd"; +import { MuteMsgCmd } from "./commands/mute/MuteMsgCmd"; +import { MuteSlashCmd } from "./commands/mute/MuteSlashCmd"; import { NoteMsgCmd } from "./commands/note/NoteMsgCmd"; import { NoteSlashCmd } from "./commands/note/NoteSlashCmd"; +import { UnbanMsgCmd } from "./commands/unban/UnbanMsgCmd"; +import { UnbanSlashCmd } from "./commands/unban/UnbanSlashCmd"; +import { UnhideCaseMsgCmd } from "./commands/unhidecase/UnhideCaseMsgCmd"; +import { UnhideCaseSlashCmd } from "./commands/unhidecase/UnhideCaseSlashCmd"; +import { UnmuteMsgCmd } from "./commands/unmute/UnmuteMsgCmd"; +import { UnmuteSlashCmd } from "./commands/unmute/UnmuteSlashCmd"; +import { UpdateMsgCmd } from "./commands/update/UpdateMsgCmd"; +import { UpdateSlashCmd } from "./commands/update/UpdateSlashCmd"; +import { WarnMsgCmd } from "./commands/warn/WarnMsgCmd"; +import { WarnSlashCmd } from "./commands/warn/WarnSlashCmd"; import { AuditLogEvents } from "./events/AuditLogEvents"; import { CreateBanCaseOnManualBanEvt } from "./events/CreateBanCaseOnManualBanEvt"; import { CreateUnbanCaseOnManualUnbanEvt } from "./events/CreateUnbanCaseOnManualUnbanEvt"; @@ -148,33 +166,53 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ name: "mod", description: "Moderation actions", defaultMemberPermissions: "0", - subcommands: [{ type: "slash", ...NoteSlashCmd }], + subcommands: [ + { type: "slash", ...AddCaseSlashCmd }, + { type: "slash", ...BanSlashCmd }, + { type: "slash", ...CaseSlashCmd }, + { type: "slash", ...CasesSlashCmd }, + { type: "slash", ...DeleteCaseSlashCmd }, + { type: "slash", ...ForceBanSlashCmd }, + { type: "slash", ...ForceMuteSlashCmd }, + { type: "slash", ...ForceUnmuteSlashCmd }, + { type: "slash", ...HideCaseSlashCmd }, + { type: "slash", ...KickSlashCmd }, + { type: "slash", ...MassBanSlashCmd }, + { type: "slash", ...MassMuteSlashSlashCmd }, + { type: "slash", ...MassUnbanSlashCmd }, + { type: "slash", ...MuteSlashCmd }, + { type: "slash", ...NoteSlashCmd }, + { type: "slash", ...UnbanSlashCmd }, + { type: "slash", ...UnhideCaseSlashCmd }, + { type: "slash", ...UnmuteSlashCmd }, + { type: "slash", ...UpdateSlashCmd }, + { type: "slash", ...WarnSlashCmd }, + ], }), ], messageCommands: [ - UpdateCmd, + UpdateMsgCmd, NoteMsgCmd, - WarnCmd, - MuteCmd, - ForcemuteCmd, - UnmuteCmd, - ForceUnmuteCmd, - KickCmd, - SoftbanCmd, - BanCmd, - UnbanCmd, - ForcebanCmd, - MassbanCmd, - MassmuteCmd, - MassunbanCmd, - AddCaseCmd, - CaseCmd, - CasesUserCmd, - CasesModCmd, - HideCaseCmd, - UnhideCaseCmd, - DeleteCaseCmd, + WarnMsgCmd, + MuteMsgCmd, + ForceMuteMsgCmd, + UnmuteMsgCmd, + ForceUnmuteMsgCmd, + KickMsgCmd, + BanMsgCmd, + UnbanMsgCmd, + ForceBanMsgCmd, + MassBanMsgCmd, + MassMuteMsgCmd, + MassUnbanMsgCmd, + AddCaseMsgCmd, + CaseMsgCmd, + CasesUserMsgCmd, + CasesModMsgCmd, + HideCaseMsgCmd, + UnhideCaseMsgCmd, + DeleteCaseMsgCmd, ], public: { @@ -198,7 +236,7 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ updateCase(pluginData) { return (msg: Message, caseNumber: number | null, note: string) => { - updateCase(pluginData, msg, { caseNumber, note }); + updateCase(pluginData, msg.channel, msg.author, caseNumber ?? undefined, note, [...msg.attachments.values()]); }; }, diff --git a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts deleted file mode 100644 index dc8df97f6..000000000 --- a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { Case } from "../../../data/entities/Case"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { renderUserUsername, resolveMember, resolveUser } from "../../../utils"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { modActionsMsgCmd } from "../types"; - -const opts = { - mod: ct.member({ option: true }), -}; - -export const AddCaseCmd = modActionsMsgCmd({ - trigger: "addcase", - permission: "can_addcase", - description: "Add an arbitrary case to the specified user without taking any action", - - signature: [ - { - type: ct.string(), - user: ct.string(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - const user = await resolveUser(pluginData.client, args.user); - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - // If the user exists as a guild member, make sure we can act on them first - const member = await resolveMember(pluginData.client, pluginData.guild, user.id); - if (member && !canActOn(pluginData, msg.member, member)) { - sendErrorMessage(pluginData, msg.channel, "Cannot add case on this user: insufficient permissions"); - return; - } - - // The moderator who did the action is the message author or, if used, the specified -mod - let mod = msg.member; - if (args.mod) { - if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); - return; - } - - mod = args.mod; - } - - // Verify the case type is valid - const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase(); - if (!CaseTypes[type]) { - sendErrorMessage(pluginData, msg.channel, "Cannot add case: invalid case type"); - return; - } - - const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); - - // Create the case - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const theCase: Case = await casesPlugin.createCase({ - userId: user.id, - modId: mod.id, - type: CaseTypes[type], - reason, - ppId: mod.id !== msg.author.id ? msg.author.id : undefined, - }); - - if (user) { - sendSuccessMessage( - pluginData, - msg.channel, - `Case #${theCase.case_number} created for **${renderUserUsername(user)}**`, - ); - } else { - sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} created`); - } - - // Log the action - pluginData.getPlugin(LogsPlugin).logCaseCreate({ - mod: mod.user, - userId: user.id, - caseNum: theCase.case_number, - caseType: type.toUpperCase(), - reason, - }); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/BanCmd.ts b/backend/src/plugins/ModActions/commands/BanCmd.ts deleted file mode 100644 index f0a20d5ff..000000000 --- a/backend/src/plugins/ModActions/commands/BanCmd.ts +++ /dev/null @@ -1,225 +0,0 @@ -import humanizeDuration from "humanize-duration"; -import { getMemberLevel } from "knub/helpers"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { clearExpiringTempban, registerExpiringTempban } from "../../../data/loops/expiringTempbansLoop"; -import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { renderUserUsername, resolveMember, resolveUser } from "../../../utils"; -import { banLock } from "../../../utils/lockNameHelpers"; -import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { banUserId } from "../functions/banUserId"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { isBanned } from "../functions/isBanned"; -import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; -import { modActionsMsgCmd } from "../types"; - -const opts = { - mod: ct.member({ option: true }), - notify: ct.string({ option: true }), - "notify-channel": ct.textChannel({ option: true }), - "delete-days": ct.number({ option: true, shortcut: "d" }), -}; - -export const BanCmd = modActionsMsgCmd({ - trigger: "ban", - permission: "can_ban", - description: "Ban or Tempban the specified member", - - signature: [ - { - user: ct.string(), - time: ct.delay(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - { - user: ct.string(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - const user = await resolveUser(pluginData.client, args.user); - - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id); - // The moderator who did the action is the message author or, if used, the specified -mod - let mod = msg.member; - if (args.mod) { - if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); - return; - } - - mod = args.mod; - } - - const time = args["time"] ? args["time"] : null; - const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); - - // acquire a lock because of the needed user-inputs below (if banned/not on server) - const lock = await pluginData.locks.acquire(banLock(user)); - let forceban = false; - const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id); - - if (!memberToBan) { - const banned = await isBanned(pluginData, user.id); - - if (!banned) { - // Ask the mod if we should upgrade to a forceban as the user is not on the server - const reply = await waitForButtonConfirm( - msg.channel, - { content: "User not on server, forceban instead?" }, - { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, - ); - - if (!reply) { - sendErrorMessage(pluginData, msg.channel, "User not on server, ban cancelled by moderator"); - lock.unlock(); - return; - } else { - forceban = true; - } - } - - // Abort if trying to ban user indefinitely if they are already banned indefinitely - if (!existingTempban && !time) { - sendErrorMessage(pluginData, msg.channel, `User is already banned indefinitely.`); - return; - } - - // Ask the mod if we should update the existing ban - const reply = await waitForButtonConfirm( - msg.channel, - { content: "Failed to message the user. Log the warning anyway?" }, - { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, - ); - - if (!reply) { - sendErrorMessage(pluginData, msg.channel, "User already banned, update cancelled by moderator"); - lock.unlock(); - return; - } - - // Update or add new tempban / remove old tempban - if (time && time > 0) { - if (existingTempban) { - await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id); - } else { - await pluginData.state.tempbans.addTempban(user.id, time, mod.id); - } - const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!; - registerExpiringTempban(tempban); - } else if (existingTempban) { - clearExpiringTempban(existingTempban); - pluginData.state.tempbans.clear(user.id); - } - - // Create a new case for the updated ban since we never stored the old case id and log the action - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const createdCase = await casesPlugin.createCase({ - modId: mod.id, - type: CaseTypes.Ban, - userId: user.id, - reason, - noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`], - }); - if (time) { - pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ - mod: mod.user, - user, - caseNumber: createdCase.case_number, - reason, - banTime: humanizeDuration(time), - }); - } else { - pluginData.getPlugin(LogsPlugin).logMemberBan({ - mod: mod.user, - user, - caseNumber: createdCase.case_number, - reason, - }); - } - - sendSuccessMessage( - pluginData, - msg.channel, - `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, - ); - lock.unlock(); - return; - } - - // Make sure we're allowed to ban this member if they are on the server - if (!forceban && !canActOn(pluginData, msg.member, memberToBan!)) { - const ourLevel = getMemberLevel(pluginData, msg.member); - const targetLevel = getMemberLevel(pluginData, memberToBan!); - sendErrorMessage( - pluginData, - msg.channel, - `Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`, - ); - lock.unlock(); - return; - } - - let contactMethods; - try { - contactMethods = readContactMethodsFromArgs(args); - } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); - lock.unlock(); - return; - } - - const deleteMessageDays = - args["delete-days"] ?? (await pluginData.config.getForMessage(msg)).ban_delete_message_days; - const banResult = await banUserId( - pluginData, - user.id, - reason, - { - contactMethods, - caseArgs: { - modId: mod.id, - ppId: mod.id !== msg.author.id ? msg.author.id : undefined, - }, - deleteMessageDays, - modId: mod.id, - }, - time, - ); - - if (banResult.status === "failed") { - sendErrorMessage(pluginData, msg.channel, `Failed to ban member: ${banResult.error}`); - lock.unlock(); - return; - } - - let forTime = ""; - if (time && time > 0) { - forTime = `for ${humanizeDuration(time)} `; - } - - // Confirm the action to the moderator - let response = ""; - if (!forceban) { - response = `Banned **${renderUserUsername(user)}** ${forTime}(Case #${banResult.case.case_number})`; - if (banResult.notifyResult.text) response += ` (${banResult.notifyResult.text})`; - } else { - response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`; - } - - lock.unlock(); - sendSuccessMessage(pluginData, msg.channel, response); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/CaseCmd.ts b/backend/src/plugins/ModActions/commands/CaseCmd.ts deleted file mode 100644 index 8a92ed3f9..000000000 --- a/backend/src/plugins/ModActions/commands/CaseCmd.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { sendErrorMessage } from "../../../pluginUtils"; -import { modActionsMsgCmd } from "../types"; - -export const CaseCmd = modActionsMsgCmd({ - trigger: "case", - permission: "can_view", - description: "Show information about a specific case", - - signature: [ - { - caseNumber: ct.number(), - }, - ], - - async run({ pluginData, message: msg, args }) { - const theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber); - - if (!theCase) { - sendErrorMessage(pluginData, msg.channel, "Case not found"); - return; - } - - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const embed = await casesPlugin.getCaseEmbed(theCase.id, msg.author.id); - msg.channel.send(embed); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/CasesModCmd.ts b/backend/src/plugins/ModActions/commands/CasesModCmd.ts deleted file mode 100644 index e07176f58..000000000 --- a/backend/src/plugins/ModActions/commands/CasesModCmd.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { APIEmbed, User } from "discord.js"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; -import { emptyEmbedValue, resolveUser, trimLines } from "../../../utils"; -import { asyncMap } from "../../../utils/async"; -import { createPaginatedMessage } from "../../../utils/createPaginatedMessage"; -import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; -import { getGuildPrefix } from "../../../utils/getGuildPrefix"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { modActionsMsgCmd } from "../types"; - -const opts = { - mod: ct.userId({ option: true }), -}; - -const casesPerPage = 5; - -export const CasesModCmd = modActionsMsgCmd({ - trigger: ["cases", "modlogs", "infractions"], - permission: "can_view", - description: "Show the most recent 5 cases by the specified -mod", - - signature: [ - { - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - const modId = args.mod || msg.author.id; - const mod = await resolveUser(pluginData.client, modId); - const modName = mod instanceof User ? mod.tag : modId; - - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const totalCases = await casesPlugin.getTotalCasesByMod(modId); - - if (totalCases === 0) { - sendErrorMessage(pluginData, msg.channel, `No cases by **${modName}**`); - return; - } - - const totalPages = Math.max(Math.ceil(totalCases / casesPerPage), 1); - const prefix = getGuildPrefix(pluginData); - - createPaginatedMessage( - pluginData.client, - msg.channel, - totalPages, - async (page) => { - const cases = await casesPlugin.getRecentCasesByMod(modId, casesPerPage, (page - 1) * casesPerPage); - const lines = await asyncMap(cases, (c) => casesPlugin.getCaseSummary(c, true, msg.author.id)); - - const firstCaseNum = (page - 1) * casesPerPage + 1; - const lastCaseNum = page * casesPerPage; - const title = `Most recent cases ${firstCaseNum}-${lastCaseNum} of ${totalCases} by ${modName}`; - - const embed = { - author: { - name: title, - icon_url: mod instanceof User ? mod.displayAvatarURL() : undefined, - }, - fields: [ - ...getChunkedEmbedFields(emptyEmbedValue, lines.join("\n")), - { - name: emptyEmbedValue, - value: trimLines(` - Use \`${prefix}case \` to see more information about an individual case - Use \`${prefix}cases \` to see a specific user's cases - `), - }, - ], - } satisfies APIEmbed; - - return { embeds: [embed] }; - }, - { - limitToUserId: msg.author.id, - }, - ); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts deleted file mode 100644 index a92ea75d9..000000000 --- a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { APIEmbed, User } from "discord.js"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { UnknownUser, chunkArray, emptyEmbedValue, renderUserUsername, resolveUser, trimLines } from "../../../utils"; -import { asyncMap } from "../../../utils/async"; -import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; -import { getGuildPrefix } from "../../../utils/getGuildPrefix"; -import { modActionsMsgCmd } from "../types"; - -const opts = { - expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }), - hidden: ct.bool({ option: true, isSwitch: true, shortcut: "h" }), - reverseFilters: ct.switchOption({ def: false, shortcut: "r" }), - notes: ct.switchOption({ def: false, shortcut: "n" }), - warns: ct.switchOption({ def: false, shortcut: "w" }), - mutes: ct.switchOption({ def: false, shortcut: "m" }), - unmutes: ct.switchOption({ def: false, shortcut: "um" }), - bans: ct.switchOption({ def: false, shortcut: "b" }), - unbans: ct.switchOption({ def: false, shortcut: "ub" }), -}; - -export const CasesUserCmd = modActionsMsgCmd({ - trigger: ["cases", "modlogs"], - permission: "can_view", - description: "Show a list of cases the specified user has", - - signature: [ - { - user: ct.string(), - - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - const user = await resolveUser(pluginData.client, args.user); - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - let cases = await pluginData.state.cases.with("notes").getByUserId(user.id); - - const typesToShow: CaseTypes[] = []; - if (args.notes) typesToShow.push(CaseTypes.Note); - if (args.warns) typesToShow.push(CaseTypes.Warn); - if (args.mutes) typesToShow.push(CaseTypes.Mute); - if (args.unmutes) typesToShow.push(CaseTypes.Unmute); - if (args.bans) typesToShow.push(CaseTypes.Ban); - if (args.unbans) typesToShow.push(CaseTypes.Unban); - - if (typesToShow.length > 0) { - // Reversed: Hide specified types - if (args.reverseFilters) cases = cases.filter((c) => !typesToShow.includes(c.type)); - // Normal: Show only specified types - else cases = cases.filter((c) => typesToShow.includes(c.type)); - } - - const normalCases = cases.filter((c) => !c.is_hidden); - const hiddenCases = cases.filter((c) => c.is_hidden); - - const userName = - user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUserUsername(user); - - if (cases.length === 0) { - msg.channel.send(`No cases found for **${userName}**`); - } else { - const casesToDisplay = args.hidden ? cases : normalCases; - if (!casesToDisplay.length) { - msg.channel.send( - `No normal cases found for **${userName}**. Use "-hidden" to show ${cases.length} hidden cases.`, - ); - return; - } - - if (args.expand) { - if (casesToDisplay.length > 8) { - msg.channel.send("Too many cases for expanded view. Please use compact view instead."); - return; - } - - // Expanded view (= individual case embeds) - const casesPlugin = pluginData.getPlugin(CasesPlugin); - for (const theCase of casesToDisplay) { - const embed = await casesPlugin.getCaseEmbed(theCase.id); - msg.channel.send(embed); - } - } else { - // Compact view (= regular message with a preview of each case) - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const lines = await asyncMap(casesToDisplay, (c) => casesPlugin.getCaseSummary(c, true, msg.author.id)); - - const prefix = getGuildPrefix(pluginData); - const linesPerChunk = 10; - const lineChunks = chunkArray(lines, linesPerChunk); - - const footerField = { - name: emptyEmbedValue, - value: trimLines(` - Use \`${prefix}case \` to see more information about an individual case - `), - }; - - for (const [i, linesInChunk] of lineChunks.entries()) { - const isLastChunk = i === lineChunks.length - 1; - - if (isLastChunk && !args.hidden && hiddenCases.length) { - if (hiddenCases.length === 1) { - linesInChunk.push(`*+${hiddenCases.length} hidden case, use "-hidden" to show it*`); - } else { - linesInChunk.push(`*+${hiddenCases.length} hidden cases, use "-hidden" to show them*`); - } - } - - const chunkStart = i * linesPerChunk + 1; - const chunkEnd = Math.min((i + 1) * linesPerChunk, lines.length); - - const embed = { - author: { - name: - lineChunks.length === 1 - ? `Cases for ${userName} (${lines.length} total)` - : `Cases ${chunkStart}–${chunkEnd} of ${lines.length} for ${userName}`, - icon_url: user instanceof User ? user.displayAvatarURL() : undefined, - }, - fields: [ - ...getChunkedEmbedFields(emptyEmbedValue, linesInChunk.join("\n")), - ...(isLastChunk ? [footerField] : []), - ], - } satisfies APIEmbed; - - msg.channel.send({ embeds: [embed] }); - } - } - } - }, -}); diff --git a/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts b/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts deleted file mode 100644 index cb3c56d82..000000000 --- a/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { helpers } from "knub"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { Case } from "../../../data/entities/Case"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { SECONDS, trimLines } from "../../../utils"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; -import { modActionsMsgCmd } from "../types"; - -export const DeleteCaseCmd = modActionsMsgCmd({ - trigger: ["delete_case", "deletecase"], - permission: "can_deletecase", - description: trimLines(` - Delete the specified case. This operation can *not* be reversed. - It is generally recommended to use \`!hidecase\` instead when possible. - `), - - signature: { - caseNumber: ct.number({ rest: true }), - - force: ct.switchOption({ def: false, shortcut: "f" }), - }, - - async run({ pluginData, message, args }) { - const failed: number[] = []; - const validCases: Case[] = []; - let cancelled = 0; - - for (const num of args.caseNumber) { - const theCase = await pluginData.state.cases.findByCaseNumber(num); - if (!theCase) { - failed.push(num); - continue; - } - - validCases.push(theCase); - } - - if (failed.length === args.caseNumber.length) { - sendErrorMessage(pluginData, message.channel, "None of the cases were found!"); - return; - } - - for (const theCase of validCases) { - if (!args.force) { - const cases = pluginData.getPlugin(CasesPlugin); - const embedContent = await cases.getCaseEmbed(theCase); - message.channel.send({ - ...embedContent, - content: "Delete the following case? Answer 'Yes' to continue, 'No' to cancel.", - }); - - const reply = await helpers.waitForReply(pluginData.client, message.channel, message.author.id, 15 * SECONDS); - const normalizedReply = (reply?.content || "").toLowerCase().trim(); - if (normalizedReply !== "yes" && normalizedReply !== "y") { - message.channel.send("Cancelled. Case was not deleted."); - cancelled++; - continue; - } - } - - const deletedByName = message.author.tag; - - const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); - const deletedAt = timeAndDate.inGuildTz().format(timeAndDate.getDateFormat("pretty_datetime")); - - await pluginData.state.cases.softDelete( - theCase.id, - message.author.id, - deletedByName, - `Case deleted by **${deletedByName}** (\`${message.author.id}\`) on ${deletedAt}`, - ); - - const logs = pluginData.getPlugin(LogsPlugin); - logs.logCaseDelete({ - mod: message.member, - case: theCase, - }); - } - - const failedAddendum = - failed.length > 0 - ? `\nThe following cases were not found: ${failed.toString().replace(new RegExp(",", "g"), ", ")}` - : ""; - const amt = validCases.length - cancelled; - if (amt === 0) { - sendErrorMessage(pluginData, message.channel, "All deletions were cancelled, no cases were deleted."); - return; - } - - sendSuccessMessage( - pluginData, - message.channel, - `${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}`, - ); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts deleted file mode 100644 index 251be232c..000000000 --- a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Snowflake } from "discord.js"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { LogType } from "../../../data/LogType"; -import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { DAYS, MINUTES, resolveMember, resolveUser } from "../../../utils"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { ignoreEvent } from "../functions/ignoreEvent"; -import { isBanned } from "../functions/isBanned"; -import { IgnoredEventType, modActionsMsgCmd } from "../types"; - -const opts = { - mod: ct.member({ option: true }), -}; - -export const ForcebanCmd = modActionsMsgCmd({ - trigger: "forceban", - permission: "can_ban", - description: "Force-ban the specified user, even if they aren't on the server", - - signature: [ - { - user: ct.string(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - const user = await resolveUser(pluginData.client, args.user); - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - // If the user exists as a guild member, make sure we can act on them first - const member = await resolveMember(pluginData.client, pluginData.guild, user.id); - if (member && !canActOn(pluginData, msg.member, member)) { - sendErrorMessage(pluginData, msg.channel, "Cannot forceban this user: insufficient permissions"); - return; - } - - // Make sure the user isn't already banned - const banned = await isBanned(pluginData, user.id); - if (banned) { - sendErrorMessage(pluginData, msg.channel, `User is already banned`); - return; - } - - // The moderator who did the action is the message author or, if used, the specified -mod - let mod = msg.member; - if (args.mod) { - if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); - return; - } - - mod = args.mod; - } - - const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); - - ignoreEvent(pluginData, IgnoredEventType.Ban, user.id); - pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, user.id); - - try { - // FIXME: Use banUserId()? - await pluginData.guild.bans.create(user.id as Snowflake, { - deleteMessageSeconds: (1 * DAYS) / MINUTES, - reason: reason ?? undefined, - }); - } catch { - sendErrorMessage(pluginData, msg.channel, "Failed to forceban member"); - return; - } - - // Create a case - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const createdCase = await casesPlugin.createCase({ - userId: user.id, - modId: mod.id, - type: CaseTypes.Ban, - reason, - ppId: mod.id !== msg.author.id ? msg.author.id : undefined, - }); - - // Confirm the action - sendSuccessMessage(pluginData, msg.channel, `Member forcebanned (Case #${createdCase.case_number})`); - - // Log the action - pluginData.getPlugin(LogsPlugin).logMemberForceban({ - mod, - userId: user.id, - caseNumber: createdCase.case_number, - reason, - }); - - pluginData.state.events.emit("ban", user.id, reason); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts b/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts deleted file mode 100644 index 4b6f90dac..000000000 --- a/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage } from "../../../pluginUtils"; -import { resolveMember, resolveUser } from "../../../utils"; -import { actualMuteUserCmd } from "../functions/actualMuteUserCmd"; -import { modActionsMsgCmd } from "../types"; - -const opts = { - mod: ct.member({ option: true }), - notify: ct.string({ option: true }), - "notify-channel": ct.textChannel({ option: true }), -}; - -export const ForcemuteCmd = modActionsMsgCmd({ - trigger: "forcemute", - permission: "can_mute", - description: "Force-mute the specified user, even if they're not on the server", - - signature: [ - { - user: ct.string(), - time: ct.delay(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - { - user: ct.string(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - const user = await resolveUser(pluginData.client, args.user); - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - const memberToMute = await resolveMember(pluginData.client, pluginData.guild, user.id); - - // Make sure we're allowed to mute this user - if (memberToMute && !canActOn(pluginData, msg.member, memberToMute)) { - sendErrorMessage(pluginData, msg.channel, "Cannot mute: insufficient permissions"); - return; - } - - actualMuteUserCmd(pluginData, user, msg, { ...args, notify: "none" }); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/HideCaseCmd.ts b/backend/src/plugins/ModActions/commands/HideCaseCmd.ts deleted file mode 100644 index b08756db7..000000000 --- a/backend/src/plugins/ModActions/commands/HideCaseCmd.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { modActionsMsgCmd } from "../types"; - -export const HideCaseCmd = modActionsMsgCmd({ - trigger: ["hide", "hidecase", "hide_case"], - permission: "can_hidecase", - description: "Hide the specified case so it doesn't appear in !cases or !info", - - signature: [ - { - caseNum: ct.number({ rest: true }), - }, - ], - - async run({ pluginData, message: msg, args }) { - const failed: number[] = []; - - for (const num of args.caseNum) { - const theCase = await pluginData.state.cases.findByCaseNumber(num); - if (!theCase) { - failed.push(num); - continue; - } - - await pluginData.state.cases.setHidden(theCase.id, true); - } - - if (failed.length === args.caseNum.length) { - sendErrorMessage(pluginData, msg.channel, "None of the cases were found!"); - return; - } - const failedAddendum = - failed.length > 0 - ? `\nThe following cases were not found: ${failed.toString().replace(new RegExp(",", "g"), ", ")}` - : ""; - - const amt = args.caseNum.length - failed.length; - sendSuccessMessage( - pluginData, - msg.channel, - `${amt} case${amt === 1 ? " is" : "s are"} now hidden! Use \`unhidecase\` to unhide them.${failedAddendum}`, - ); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/KickCmd.ts b/backend/src/plugins/ModActions/commands/KickCmd.ts deleted file mode 100644 index 2e32f3e28..000000000 --- a/backend/src/plugins/ModActions/commands/KickCmd.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { actualKickMemberCmd } from "../functions/actualKickMemberCmd"; -import { modActionsMsgCmd } from "../types"; - -const opts = { - mod: ct.member({ option: true }), - notify: ct.string({ option: true }), - "notify-channel": ct.textChannel({ option: true }), - clean: ct.bool({ option: true, isSwitch: true }), -}; - -export const KickCmd = modActionsMsgCmd({ - trigger: "kick", - permission: "can_kick", - description: "Kick the specified member", - - signature: [ - { - user: ct.string(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - actualKickMemberCmd(pluginData, msg, args); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/MassBanCmd.ts b/backend/src/plugins/ModActions/commands/MassBanCmd.ts deleted file mode 100644 index 4769cd51b..000000000 --- a/backend/src/plugins/ModActions/commands/MassBanCmd.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Snowflake } from "discord.js"; -import { waitForReply } from "knub/helpers"; -import { performance } from "perf_hooks"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { LogType } from "../../../data/LogType"; -import { humanizeDurationShort } from "../../../humanizeDurationShort"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { DAYS, MINUTES, SECONDS, noop } from "../../../utils"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { ignoreEvent } from "../functions/ignoreEvent"; -import { IgnoredEventType, modActionsMsgCmd } from "../types"; - -export const MassbanCmd = modActionsMsgCmd({ - trigger: "massban", - permission: "can_massban", - description: "Mass-ban a list of user IDs", - - signature: [ - { - userIds: ct.string({ rest: true }), - }, - ], - - async run({ pluginData, message: msg, args }) { - // Limit to 100 users at once (arbitrary?) - if (args.userIds.length > 100) { - sendErrorMessage(pluginData, msg.channel, `Can only massban max 100 users at once`); - return; - } - - // Ask for ban reason (cleaner this way instead of trying to cram it into the args) - msg.channel.send("Ban reason? `cancel` to cancel"); - const banReasonReply = await waitForReply(pluginData.client, msg.channel, msg.author.id); - if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") { - sendErrorMessage(pluginData, msg.channel, "Cancelled"); - return; - } - - const banReason = formatReasonWithAttachments(banReasonReply.content, [...msg.attachments.values()]); - - // Verify we can act on each of the users specified - for (const userId of args.userIds) { - const member = pluginData.guild.members.cache.get(userId as Snowflake); // TODO: Get members on demand? - if (member && !canActOn(pluginData, msg.member, member)) { - sendErrorMessage(pluginData, msg.channel, "Cannot massban one or more users: insufficient permissions"); - return; - } - } - - // Show a loading indicator since this can take a while - const maxWaitTime = pluginData.state.massbanQueue.timeout * pluginData.state.massbanQueue.length; - const maxWaitTimeFormatted = humanizeDurationShort(maxWaitTime, { round: true }); - const initialLoadingText = - pluginData.state.massbanQueue.length === 0 - ? "Banning..." - : `Massban queued. Waiting for previous massban to finish (max wait ${maxWaitTimeFormatted}).`; - const loadingMsg = await msg.channel.send(initialLoadingText); - - const waitTimeStart = performance.now(); - const waitingInterval = setInterval(() => { - const waitTime = humanizeDurationShort(performance.now() - waitTimeStart, { round: true }); - loadingMsg - .edit(`Massban queued. Still waiting for previous massban to finish (waited ${waitTime}).`) - .catch(() => clearInterval(waitingInterval)); - }, 1 * MINUTES); - - pluginData.state.massbanQueue.add(async () => { - clearInterval(waitingInterval); - - if (pluginData.state.unloaded) { - void loadingMsg.delete().catch(noop); - return; - } - - void loadingMsg.edit("Banning...").catch(noop); - - // Ban each user and count failed bans (if any) - const startTime = performance.now(); - const failedBans: string[] = []; - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const deleteDays = (await pluginData.config.getForMessage(msg)).ban_delete_message_days; - for (const [i, userId] of args.userIds.entries()) { - if (pluginData.state.unloaded) { - break; - } - - try { - // Ignore automatic ban cases and logs - // We create our own cases below and post a single "mass banned" log instead - ignoreEvent(pluginData, IgnoredEventType.Ban, userId, 30 * MINUTES); - pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, userId, 30 * MINUTES); - - await pluginData.guild.bans.create(userId as Snowflake, { - deleteMessageSeconds: (deleteDays * DAYS) / SECONDS, - reason: banReason, - }); - - await casesPlugin.createCase({ - userId, - modId: msg.author.id, - type: CaseTypes.Ban, - reason: `Mass ban: ${banReason}`, - postInCaseLogOverride: false, - }); - - pluginData.state.events.emit("ban", userId, banReason); - } catch { - failedBans.push(userId); - } - - // Send a status update every 10 bans - if ((i + 1) % 10 === 0) { - loadingMsg.edit(`Banning... ${i + 1}/${args.userIds.length}`).catch(noop); - } - } - - const totalTime = performance.now() - startTime; - const formattedTimeTaken = humanizeDurationShort(totalTime, { round: true }); - - // Clear loading indicator - loadingMsg.delete().catch(noop); - - const successfulBanCount = args.userIds.length - failedBans.length; - if (successfulBanCount === 0) { - // All bans failed - don't create a log entry and notify the user - sendErrorMessage(pluginData, msg.channel, "All bans failed. Make sure the IDs are valid."); - } else { - // Some or all bans were successful. Create a log entry for the mass ban and notify the user. - pluginData.getPlugin(LogsPlugin).logMassBan({ - mod: msg.author, - count: successfulBanCount, - reason: banReason, - }); - - if (failedBans.length) { - sendSuccessMessage( - pluginData, - msg.channel, - `Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${ - failedBans.length - } failed: ${failedBans.join(" ")}`, - ); - } else { - sendSuccessMessage( - pluginData, - msg.channel, - `Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}`, - ); - } - } - }); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts deleted file mode 100644 index a873b4c6a..000000000 --- a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Snowflake } from "discord.js"; -import { waitForReply } from "knub/helpers"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { LogType } from "../../../data/LogType"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { ignoreEvent } from "../functions/ignoreEvent"; -import { isBanned } from "../functions/isBanned"; -import { IgnoredEventType, modActionsMsgCmd } from "../types"; - -export const MassunbanCmd = modActionsMsgCmd({ - trigger: "massunban", - permission: "can_massunban", - description: "Mass-unban a list of user IDs", - - signature: [ - { - userIds: ct.string({ rest: true }), - }, - ], - - async run({ pluginData, message: msg, args }) { - // Limit to 100 users at once (arbitrary?) - if (args.userIds.length > 100) { - sendErrorMessage(pluginData, msg.channel, `Can only mass-unban max 100 users at once`); - return; - } - - // Ask for unban reason (cleaner this way instead of trying to cram it into the args) - msg.channel.send("Unban reason? `cancel` to cancel"); - const unbanReasonReply = await waitForReply(pluginData.client, msg.channel, msg.author.id); - if (!unbanReasonReply || !unbanReasonReply.content || unbanReasonReply.content.toLowerCase().trim() === "cancel") { - sendErrorMessage(pluginData, msg.channel, "Cancelled"); - return; - } - - const unbanReason = formatReasonWithAttachments(unbanReasonReply.content, [...msg.attachments.values()]); - - // Ignore automatic unban cases and logs for these users - // We'll create our own cases below and post a single "mass unbanned" log instead - args.userIds.forEach((userId) => { - // Use longer timeouts since this can take a while - ignoreEvent(pluginData, IgnoredEventType.Unban, userId, 120 * 1000); - pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, userId, 120 * 1000); - }); - - // Show a loading indicator since this can take a while - const loadingMsg = await msg.channel.send("Unbanning..."); - - // Unban each user and count failed unbans (if any) - const failedUnbans: Array<{ userId: string; reason: UnbanFailReasons }> = []; - const casesPlugin = pluginData.getPlugin(CasesPlugin); - for (const userId of args.userIds) { - if (!(await isBanned(pluginData, userId))) { - failedUnbans.push({ userId, reason: UnbanFailReasons.NOT_BANNED }); - continue; - } - - try { - await pluginData.guild.bans.remove(userId as Snowflake, unbanReason ?? undefined); - - await casesPlugin.createCase({ - userId, - modId: msg.author.id, - type: CaseTypes.Unban, - reason: `Mass unban: ${unbanReason}`, - postInCaseLogOverride: false, - }); - } catch { - failedUnbans.push({ userId, reason: UnbanFailReasons.UNBAN_FAILED }); - } - } - - // Clear loading indicator - loadingMsg.delete(); - - const successfulUnbanCount = args.userIds.length - failedUnbans.length; - if (successfulUnbanCount === 0) { - // All unbans failed - don't create a log entry and notify the user - sendErrorMessage(pluginData, msg.channel, "All unbans failed. Make sure the IDs are valid and banned."); - } else { - // Some or all unbans were successful. Create a log entry for the mass unban and notify the user. - pluginData.getPlugin(LogsPlugin).logMassUnban({ - mod: msg.author, - count: successfulUnbanCount, - reason: unbanReason, - }); - - if (failedUnbans.length) { - const notBanned = failedUnbans.filter((x) => x.reason === UnbanFailReasons.NOT_BANNED); - const unbanFailed = failedUnbans.filter((x) => x.reason === UnbanFailReasons.UNBAN_FAILED); - - let failedMsg = ""; - if (notBanned.length > 0) { - failedMsg += `${notBanned.length}x ${UnbanFailReasons.NOT_BANNED}:`; - notBanned.forEach((fail) => { - failedMsg += " " + fail.userId; - }); - } - if (unbanFailed.length > 0) { - failedMsg += `\n${unbanFailed.length}x ${UnbanFailReasons.UNBAN_FAILED}:`; - unbanFailed.forEach((fail) => { - failedMsg += " " + fail.userId; - }); - } - - sendSuccessMessage( - pluginData, - msg.channel, - `Unbanned ${successfulUnbanCount} users, ${failedUnbans.length} failed:\n${failedMsg}`, - ); - } else { - sendSuccessMessage(pluginData, msg.channel, `Unbanned ${successfulUnbanCount} users successfully`); - } - } - }, -}); - -enum UnbanFailReasons { - NOT_BANNED = "Not banned", - UNBAN_FAILED = "Unban failed", -} diff --git a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts deleted file mode 100644 index 65422f0f4..000000000 --- a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Snowflake } from "discord.js"; -import { waitForReply } from "knub/helpers"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { LogType } from "../../../data/LogType"; -import { logger } from "../../../logger"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { modActionsMsgCmd } from "../types"; - -export const MassmuteCmd = modActionsMsgCmd({ - trigger: "massmute", - permission: "can_massmute", - description: "Mass-mute a list of user IDs", - - signature: [ - { - userIds: ct.string({ rest: true }), - }, - ], - - async run({ pluginData, message: msg, args }) { - // Limit to 100 users at once (arbitrary?) - if (args.userIds.length > 100) { - sendErrorMessage(pluginData, msg.channel, `Can only massmute max 100 users at once`); - return; - } - - // Ask for mute reason - msg.channel.send("Mute reason? `cancel` to cancel"); - const muteReasonReceived = await waitForReply(pluginData.client, msg.channel, msg.author.id); - if ( - !muteReasonReceived || - !muteReasonReceived.content || - muteReasonReceived.content.toLowerCase().trim() === "cancel" - ) { - sendErrorMessage(pluginData, msg.channel, "Cancelled"); - return; - } - - const muteReason = formatReasonWithAttachments(muteReasonReceived.content, [...msg.attachments.values()]); - - // Verify we can act upon all users - for (const userId of args.userIds) { - const member = pluginData.guild.members.cache.get(userId as Snowflake); - if (member && !canActOn(pluginData, msg.member, member)) { - sendErrorMessage(pluginData, msg.channel, "Cannot massmute one or more users: insufficient permissions"); - return; - } - } - - // Ignore automatic mute cases and logs for these users - // We'll create our own cases below and post a single "mass muted" log instead - args.userIds.forEach((userId) => { - // Use longer timeouts since this can take a while - pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_MUTE, userId, 120 * 1000); - }); - - // Show loading indicator - const loadingMsg = await msg.channel.send("Muting..."); - - // Mute everyone and count fails - const modId = msg.author.id; - const failedMutes: string[] = []; - const mutesPlugin = pluginData.getPlugin(MutesPlugin); - for (const userId of args.userIds) { - try { - await mutesPlugin.muteUser(userId, 0, `Mass mute: ${muteReason}`, { - caseArgs: { - modId, - }, - }); - } catch (e) { - logger.info(e); - failedMutes.push(userId); - } - } - - // Clear loading indicator - loadingMsg.delete(); - - const successfulMuteCount = args.userIds.length - failedMutes.length; - if (successfulMuteCount === 0) { - // All mutes failed - sendErrorMessage(pluginData, msg.channel, "All mutes failed. Make sure the IDs are valid."); - } else { - // Success on all or some mutes - pluginData.getPlugin(LogsPlugin).logMassMute({ - mod: msg.author, - count: successfulMuteCount, - }); - - if (failedMutes.length) { - sendSuccessMessage( - pluginData, - msg.channel, - `Muted ${successfulMuteCount} users, ${failedMutes.length} failed: ${failedMutes.join(" ")}`, - ); - } else { - sendSuccessMessage(pluginData, msg.channel, `Muted ${successfulMuteCount} users successfully`); - } - } - }, -}); diff --git a/backend/src/plugins/ModActions/commands/SoftbanCommand.ts b/backend/src/plugins/ModActions/commands/SoftbanCommand.ts deleted file mode 100644 index a5806cdd8..000000000 --- a/backend/src/plugins/ModActions/commands/SoftbanCommand.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { trimPluginDescription } from "../../../utils"; -import { actualKickMemberCmd } from "../functions/actualKickMemberCmd"; -import { modActionsMsgCmd } from "../types"; - -const opts = { - mod: ct.member({ option: true }), - notify: ct.string({ option: true }), - "notify-channel": ct.textChannel({ option: true }), -}; - -export const SoftbanCmd = modActionsMsgCmd({ - trigger: "softban", - permission: "can_kick", - description: trimPluginDescription(` - "Softban" the specified user by banning and immediately unbanning them. Effectively a kick with message deletions. - This command will be removed in the future, please use kick with the \`- clean\` argument instead - `), - - signature: [ - { - user: ct.string(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - await actualKickMemberCmd(pluginData, msg, { clean: true, ...args }); - await msg.channel.send( - "Softban will be removed in the future - please use the kick command with the `-clean` argument instead!", - ); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/UnbanCmd.ts b/backend/src/plugins/ModActions/commands/UnbanCmd.ts deleted file mode 100644 index d232f4e4f..000000000 --- a/backend/src/plugins/ModActions/commands/UnbanCmd.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Snowflake } from "discord.js"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { LogType } from "../../../data/LogType"; -import { clearExpiringTempban } from "../../../data/loops/expiringTempbansLoop"; -import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { resolveUser } from "../../../utils"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { ignoreEvent } from "../functions/ignoreEvent"; -import { IgnoredEventType, modActionsMsgCmd } from "../types"; - -const opts = { - mod: ct.member({ option: true }), -}; - -export const UnbanCmd = modActionsMsgCmd({ - trigger: "unban", - permission: "can_unban", - description: "Unban the specified member", - - signature: [ - { - user: ct.string(), - reason: ct.string({ required: false, catchAll: true }), - - ...opts, - }, - ], - - async run({ pluginData, message: msg, args }) { - const user = await resolveUser(pluginData.client, args.user); - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - // The moderator who did the action is the message author or, if used, the specified -mod - let mod = msg.member; - if (args.mod) { - if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); - return; - } - - mod = args.mod; - } - - pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, user.id); - const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); - - try { - ignoreEvent(pluginData, IgnoredEventType.Unban, user.id); - await pluginData.guild.bans.remove(user.id as Snowflake, reason ?? undefined); - } catch { - sendErrorMessage(pluginData, msg.channel, "Failed to unban member; are you sure they're banned?"); - return; - } - - // Create a case - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const createdCase = await casesPlugin.createCase({ - userId: user.id, - modId: mod.id, - type: CaseTypes.Unban, - reason, - ppId: mod.id !== msg.author.id ? msg.author.id : undefined, - }); - // Delete the tempban, if one exists - const tempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id); - if (tempban) { - clearExpiringTempban(tempban); - await pluginData.state.tempbans.clear(user.id); - } - - // Confirm the action - sendSuccessMessage(pluginData, msg.channel, `Member unbanned (Case #${createdCase.case_number})`); - - // Log the action - pluginData.getPlugin(LogsPlugin).logMemberUnban({ - mod: mod.user, - userId: user.id, - caseNumber: createdCase.case_number, - reason: reason ?? "", - }); - - pluginData.state.events.emit("unban", user.id); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts b/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts deleted file mode 100644 index 7c6eb774a..000000000 --- a/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { modActionsMsgCmd } from "../types"; - -export const UnhideCaseCmd = modActionsMsgCmd({ - trigger: ["unhide", "unhidecase", "unhide_case"], - permission: "can_hidecase", - description: "Un-hide the specified case, making it appear in !cases and !info again", - - signature: [ - { - caseNum: ct.number({ rest: true }), - }, - ], - - async run({ pluginData, message: msg, args }) { - const failed: number[] = []; - - for (const num of args.caseNum) { - const theCase = await pluginData.state.cases.findByCaseNumber(num); - if (!theCase) { - failed.push(num); - continue; - } - - await pluginData.state.cases.setHidden(theCase.id, false); - } - - if (failed.length === args.caseNum.length) { - sendErrorMessage(pluginData, msg.channel, "None of the cases were found!"); - return; - } - const failedAddendum = - failed.length > 0 - ? `\nThe following cases were not found: ${failed.toString().replace(new RegExp(",", "g"), ", ")}` - : ""; - - const amt = args.caseNum.length - failed.length; - sendSuccessMessage( - pluginData, - msg.channel, - `${amt} case${amt === 1 ? " is" : "s are"} no longer hidden!${failedAddendum}`, - ); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/WarnCmd.ts b/backend/src/plugins/ModActions/commands/WarnCmd.ts deleted file mode 100644 index 2b3121fdb..000000000 --- a/backend/src/plugins/ModActions/commands/WarnCmd.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { errorMessage, renderUserUsername, resolveMember, resolveUser } from "../../../utils"; -import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { isBanned } from "../functions/isBanned"; -import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; -import { warnMember } from "../functions/warnMember"; -import { modActionsMsgCmd } from "../types"; - -export const WarnCmd = modActionsMsgCmd({ - trigger: "warn", - permission: "can_warn", - description: "Send a warning to the specified user", - - signature: { - user: ct.string(), - reason: ct.string({ catchAll: true }), - - mod: ct.member({ option: true }), - notify: ct.string({ option: true }), - "notify-channel": ct.textChannel({ option: true }), - }, - - async run({ pluginData, message: msg, args }) { - const user = await resolveUser(pluginData.client, args.user); - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - const memberToWarn = await resolveMember(pluginData.client, pluginData.guild, user.id); - - if (!memberToWarn) { - const _isBanned = await isBanned(pluginData, user.id); - if (_isBanned) { - sendErrorMessage(pluginData, msg.channel, `User is banned`); - } else { - sendErrorMessage(pluginData, msg.channel, `User not found on the server`); - } - - return; - } - - // Make sure we're allowed to warn this member - if (!canActOn(pluginData, msg.member, memberToWarn)) { - sendErrorMessage(pluginData, msg.channel, "Cannot warn: insufficient permissions"); - return; - } - - // The moderator who did the action is the message author or, if used, the specified -mod - let mod = msg.member; - if (args.mod) { - if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - msg.channel.send(errorMessage("You don't have permission to use -mod")); - return; - } - - mod = args.mod; - } - - const config = pluginData.config.get(); - const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); - - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const priorWarnAmount = await casesPlugin.getCaseTypeAmountForUserId(memberToWarn.id, CaseTypes.Warn); - if (config.warn_notify_enabled && priorWarnAmount >= config.warn_notify_threshold) { - const reply = await waitForButtonConfirm( - msg.channel, - { content: config.warn_notify_message.replace("{priorWarnings}", `${priorWarnAmount}`) }, - { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, - ); - if (!reply) { - msg.channel.send(errorMessage("Warn cancelled by moderator")); - return; - } - } - - let contactMethods; - try { - contactMethods = readContactMethodsFromArgs(args); - } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); - return; - } - - const warnResult = await warnMember(pluginData, memberToWarn, reason, { - contactMethods, - caseArgs: { - modId: mod.id, - ppId: mod.id !== msg.author.id ? msg.author.id : undefined, - reason, - }, - retryPromptChannel: msg.channel, - }); - - if (warnResult.status === "failed") { - sendErrorMessage(pluginData, msg.channel, "Failed to warn user"); - return; - } - - const messageResultText = warnResult.notifyResult.text ? ` (${warnResult.notifyResult.text})` : ""; - - sendSuccessMessage( - pluginData, - msg.channel, - `Warned **${renderUserUsername(memberToWarn.user)}** (Case #${warnResult.case.case_number})${messageResultText}`, - ); - }, -}); diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts new file mode 100644 index 000000000..41c2a380f --- /dev/null +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts @@ -0,0 +1,63 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { resolveUser } from "../../../../utils"; +import { actualAddCaseCmd } from "../../functions/actualCommands/actualAddCaseCmd"; +import { modActionsMsgCmd } from "../../types"; + +const opts = { + mod: ct.member({ option: true }), +}; + +export const AddCaseMsgCmd = modActionsMsgCmd({ + trigger: "addcase", + permission: "can_addcase", + description: "Add an arbitrary case to the specified user without taking any action", + + signature: [ + { + type: ct.string(), + user: ct.string(), + reason: ct.string({ required: false, catchAll: true }), + + ...opts, + }, + ], + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + } + + // Verify the case type is valid + const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase(); + if (!CaseTypes[type]) { + sendErrorMessage(pluginData, msg.channel, "Cannot add case: invalid case type"); + return; + } + + actualAddCaseCmd( + pluginData, + msg.channel, + msg.member, + mod, + [...msg.attachments.values()], + user, + type as keyof CaseTypes, + args.reason || "", + ); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts new file mode 100644 index 000000000..b5fbfc5f4 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts @@ -0,0 +1,65 @@ +import { slashOptions } from "knub"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualAddCaseCmd } from "../../functions/actualCommands/actualAddCaseCmd"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to add this case as", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the case", + }), +]; + +export const AddCaseSlashCmd = { + name: "addcase", + configPermission: "can_addcase", + description: "Add an arbitrary case to the specified user without taking any action", + allowDms: false, + + signature: [ + slashOptions.string({ + name: "type", + description: "The type of case to add", + required: true, + choices: Object.keys(CaseTypes).map((type) => ({ name: type, value: type })), + }), + slashOptions.user({ name: "user", description: "The user to add a case to", required: true }), + + ...opts, + ], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = interaction.member; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + } + + actualAddCaseCmd( + pluginData, + interaction, + interaction.member, + mod, + attachments, + options.user, + options.type as keyof CaseTypes, + options.reason || "", + ); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts new file mode 100644 index 000000000..efea59d77 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts @@ -0,0 +1,75 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { UserNotificationMethod, resolveUser } from "../../../../utils"; +import { actualBanCmd } from "../../functions/actualCommands/actualBanCmd"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsMsgCmd } from "../../types"; + +const opts = { + mod: ct.member({ option: true }), + notify: ct.string({ option: true }), + "notify-channel": ct.textChannel({ option: true }), + "delete-days": ct.number({ option: true, shortcut: "d" }), +}; + +export const BanMsgCmd = modActionsMsgCmd({ + trigger: "ban", + permission: "can_ban", + description: "Ban or Tempban the specified member", + + signature: [ + { + user: ct.string(), + time: ct.delay(), + reason: ct.string({ required: false, catchAll: true }), + + ...opts, + }, + { + user: ct.string(), + reason: ct.string({ required: false, catchAll: true }), + + ...opts, + }, + ], + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + } + + let contactMethods: UserNotificationMethod[] | undefined; + try { + contactMethods = readContactMethodsFromArgs(args) ?? undefined; + } catch (e) { + sendErrorMessage(pluginData, msg.channel, e.message); + return; + } + + actualBanCmd( + pluginData, + msg.channel, + user, + args["time"] ? args["time"] : null, + args.reason || "", + [...msg.attachments.values()], + msg.member, + mod, + contactMethods, + ); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts new file mode 100644 index 000000000..acd19a43f --- /dev/null +++ b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts @@ -0,0 +1,98 @@ +import { ChannelType } from "discord.js"; +import { slashOptions } from "knub"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { UserNotificationMethod, convertDelayStringToMS } from "../../../../utils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualBanCmd } from "../../functions/actualCommands/actualBanCmd"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "time", description: "The duration of the ban", required: false }), + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to ban as", required: false }), + slashOptions.string({ + name: "notify", + description: "How to notify", + required: false, + choices: [ + { name: "DM", value: "dm" }, + { name: "Channel", value: "channel" }, + ], + }), + slashOptions.channel({ + name: "notify-channel", + description: "The channel to notify in", + channelTypes: [ChannelType.GuildText, ChannelType.PrivateThread, ChannelType.PublicThread], + required: false, + }), + slashOptions.number({ + name: "delete-days", + description: "The number of days of messages to delete", + required: false, + }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the ban", + }), +]; + +export const BanSlashCmd = { + name: "ban", + configPermission: "can_ban", + description: "Ban or Tempban the specified member", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to ban", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + let mod = interaction.member; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + } + + let contactMethods: UserNotificationMethod[] | undefined; + try { + contactMethods = readContactMethodsFromArgs(options) ?? undefined; + } catch (e) { + sendErrorMessage(pluginData, interaction, e.message); + return; + } + + const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + if (options.time && !convertedTime) { + sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + return; + } + + actualBanCmd( + pluginData, + interaction, + options.user, + convertedTime, + options.reason || "", + attachments, + interaction.member, + mod, + contactMethods, + ); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts new file mode 100644 index 000000000..6521e3279 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts @@ -0,0 +1,19 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { actualCaseCmd } from "../../functions/actualCommands/actualCaseCmd"; +import { modActionsMsgCmd } from "../../types"; + +export const CaseMsgCmd = modActionsMsgCmd({ + trigger: "case", + permission: "can_view", + description: "Show information about a specific case", + + signature: [ + { + caseNumber: ct.number(), + }, + ], + + async run({ pluginData, message: msg, args }) { + actualCaseCmd(pluginData, msg.channel, msg.author.id, args.caseNumber); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts new file mode 100644 index 000000000..c537ec6e6 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts @@ -0,0 +1,17 @@ +import { slashOptions } from "knub"; +import { actualCaseCmd } from "../../functions/actualCommands/actualCaseCmd"; + +export const CaseSlashCmd = { + name: "case", + configPermission: "can_view", + description: "Show information about a specific case", + allowDms: false, + + signature: [ + slashOptions.number({ name: "case-number", description: "The number of the case to show", required: true }), + ], + + async run({ interaction, options, pluginData }) { + actualCaseCmd(pluginData, interaction, interaction.user.id, options["case-number"]); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts new file mode 100644 index 000000000..b9e496b79 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts @@ -0,0 +1,47 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { actualCasesCmd } from "../../functions/actualCommands/actualCasesCmd"; +import { modActionsMsgCmd } from "../../types"; + +const opts = { + mod: ct.userId({ option: true }), + expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }), + hidden: ct.bool({ option: true, isSwitch: true, shortcut: "h" }), + reverseFilters: ct.switchOption({ def: false, shortcut: "r" }), + notes: ct.switchOption({ def: false, shortcut: "n" }), + warns: ct.switchOption({ def: false, shortcut: "w" }), + mutes: ct.switchOption({ def: false, shortcut: "m" }), + unmutes: ct.switchOption({ def: false, shortcut: "um" }), + bans: ct.switchOption({ def: false, shortcut: "b" }), + unbans: ct.switchOption({ def: false, shortcut: "ub" }), +}; + +export const CasesModMsgCmd = modActionsMsgCmd({ + trigger: ["cases", "modlogs", "infractions"], + permission: "can_view", + description: "Show the most recent 5 cases by the specified -mod", + + signature: [ + { + ...opts, + }, + ], + + async run({ pluginData, message: msg, args }) { + return actualCasesCmd( + pluginData, + msg.channel, + args.mod, + null, + msg.author, + args.notes, + args.warns, + args.mutes, + args.unmutes, + args.bans, + args.unbans, + args.reverseFilters, + args.hidden, + args.expand, + ); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts new file mode 100644 index 000000000..5207d76f5 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -0,0 +1,48 @@ +import { slashOptions } from "knub"; +import { actualCasesCmd } from "../../functions/actualCommands/actualCasesCmd"; + +const opts = [ + slashOptions.user({ name: "user", description: "The user to show cases for", required: false }), + slashOptions.user({ name: "mod", description: "The mod to filter cases by", required: false }), + slashOptions.boolean({ name: "expand", description: "Show each case individually", required: false }), + slashOptions.boolean({ name: "hidden", description: "Whether or not to show hidden cases", required: false }), + slashOptions.boolean({ + name: "reverse-filters", + description: "To treat case type filters as exclusive instead of inclusive", + required: false, + }), + slashOptions.boolean({ name: "notes", description: "To filter notes", required: false }), + slashOptions.boolean({ name: "warns", description: "To filter warns", required: false }), + slashOptions.boolean({ name: "mutes", description: "To filter mutes", required: false }), + slashOptions.boolean({ name: "unmutes", description: "To filter unmutes", required: false }), + slashOptions.boolean({ name: "bans", description: "To filter bans", required: false }), + slashOptions.boolean({ name: "unbans", description: "To filter unbans", required: false }), +]; + +export const CasesSlashCmd = { + name: "cases", + configPermission: "can_view", + description: "Show a list of cases the specified user has or the specified mod made", + allowDms: false, + + signature: [...opts], + + async run({ interaction, options, pluginData }) { + return actualCasesCmd( + pluginData, + interaction, + options.mod, + options.user, + interaction.user, + options.notes, + options.warns, + options.mutes, + options.unmutes, + options.bans, + options.unbans, + options["reverse-filters"], + options.hidden, + options.expand, + ); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts new file mode 100644 index 000000000..765524dfc --- /dev/null +++ b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts @@ -0,0 +1,57 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { sendErrorMessage } from "../../../../pluginUtils"; +import { resolveUser } from "../../../../utils"; +import { actualCasesCmd } from "../../functions/actualCommands/actualCasesCmd"; +import { modActionsMsgCmd } from "../../types"; + +const opts = { + mod: ct.userId({ option: true }), + expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }), + hidden: ct.bool({ option: true, isSwitch: true, shortcut: "h" }), + reverseFilters: ct.switchOption({ def: false, shortcut: "r" }), + notes: ct.switchOption({ def: false, shortcut: "n" }), + warns: ct.switchOption({ def: false, shortcut: "w" }), + mutes: ct.switchOption({ def: false, shortcut: "m" }), + unmutes: ct.switchOption({ def: false, shortcut: "um" }), + bans: ct.switchOption({ def: false, shortcut: "b" }), + unbans: ct.switchOption({ def: false, shortcut: "ub" }), +}; + +export const CasesUserMsgCmd = modActionsMsgCmd({ + trigger: ["cases", "modlogs", "infractions"], + permission: "can_view", + description: "Show a list of cases the specified user has", + + signature: [ + { + user: ct.string(), + + ...opts, + }, + ], + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + return actualCasesCmd( + pluginData, + msg.channel, + args.mod, + user, + msg.author, + args.notes, + args.warns, + args.mutes, + args.unmutes, + args.bans, + args.unbans, + args.reverseFilters, + args.hidden, + args.expand, + ); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/constants.ts b/backend/src/plugins/ModActions/commands/constants.ts new file mode 100644 index 000000000..624f9de3d --- /dev/null +++ b/backend/src/plugins/ModActions/commands/constants.ts @@ -0,0 +1,2 @@ +export const NUMBER_ATTACHMENTS_CASE_CREATION = 1; +export const NUMBER_ATTACHMENTS_CASE_UPDATE = 3; diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts new file mode 100644 index 000000000..70dbc92f1 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts @@ -0,0 +1,23 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { trimLines } from "../../../../utils"; +import { actualDeleteCaseCmd } from "../../functions/actualCommands/actualDeleteCaseCmd"; +import { modActionsMsgCmd } from "../../types"; + +export const DeleteCaseMsgCmd = modActionsMsgCmd({ + trigger: ["delete_case", "deletecase"], + permission: "can_deletecase", + description: trimLines(` + Delete the specified case. This operation can *not* be reversed. + It is generally recommended to use \`!hidecase\` instead when possible. + `), + + signature: { + caseNumber: ct.number({ rest: true }), + + force: ct.switchOption({ def: false, shortcut: "f" }), + }, + + async run({ pluginData, message, args }) { + actualDeleteCaseCmd(pluginData, message.channel, message.member, args.caseNumber, args.force); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts new file mode 100644 index 000000000..e087e3a4f --- /dev/null +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts @@ -0,0 +1,27 @@ +import { slashOptions } from "knub"; +import { actualDeleteCaseCmd } from "../../functions/actualCommands/actualDeleteCaseCmd"; + +const opts = [slashOptions.boolean({ name: "force", description: "Whether or not to force delete", required: false })]; + +export const DeleteCaseSlashCmd = { + name: "deletecase", + configPermission: "can_deletecase", + description: "Delete the specified case. This operation can *not* be reversed.", + allowDms: false, + + signature: [ + slashOptions.string({ name: "case-number", description: "The number of the case to delete", required: true }), + + ...opts, + ], + + async run({ interaction, options, pluginData }) { + actualDeleteCaseCmd( + pluginData, + interaction, + interaction.member, + options["case-number"].split(/[\s,]+/), + !!options.force, + ); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts new file mode 100644 index 000000000..ee0e4fa7d --- /dev/null +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts @@ -0,0 +1,60 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../../utils"; +import { actualForceBanCmd } from "../../functions/actualCommands/actualForceBanCmd"; +import { isBanned } from "../../functions/isBanned"; +import { modActionsMsgCmd } from "../../types"; + +const opts = { + mod: ct.member({ option: true }), +}; + +export const ForceBanMsgCmd = modActionsMsgCmd({ + trigger: "forceban", + permission: "can_ban", + description: "Force-ban the specified user, even if they aren't on the server", + + signature: [ + { + user: ct.string(), + reason: ct.string({ required: false, catchAll: true }), + + ...opts, + }, + ], + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + // If the user exists as a guild member, make sure we can act on them first + const member = await resolveMember(pluginData.client, pluginData.guild, user.id); + if (member && !canActOn(pluginData, msg.member, member)) { + sendErrorMessage(pluginData, msg.channel, "Cannot forceban this user: insufficient permissions"); + return; + } + + // Make sure the user isn't already banned + const banned = await isBanned(pluginData, user.id); + if (banned) { + sendErrorMessage(pluginData, msg.channel, `User is already banned`); + return; + } + + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + } + + actualForceBanCmd(pluginData, msg.channel, msg.author.id, user, args.reason, [...msg.attachments.values()], mod); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts new file mode 100644 index 000000000..c1722fcce --- /dev/null +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts @@ -0,0 +1,57 @@ +import { slashOptions } from "knub"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { convertDelayStringToMS } from "../../../../utils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualForceBanCmd } from "../../functions/actualCommands/actualForceBanCmd"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to ban as", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the ban", + }), +]; + +export const ForceBanSlashCmd = { + name: "forceban", + configPermission: "can_ban", + description: "Force-ban the specified user, even if they aren't on the server", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to ban", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + let mod = interaction.member; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + } + + const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + if (options.time && !convertedTime) { + sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + return; + } + + actualForceBanCmd(pluginData, interaction, interaction.user.id, options.user, options.reason, attachments, mod); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts new file mode 100644 index 000000000..9ecbae613 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts @@ -0,0 +1,84 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../../utils"; +import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsMsgCmd } from "../../types"; + +const opts = { + mod: ct.member({ option: true }), + notify: ct.string({ option: true }), + "notify-channel": ct.textChannel({ option: true }), +}; + +export const ForceMuteMsgCmd = modActionsMsgCmd({ + trigger: "forcemute", + permission: "can_mute", + description: "Force-mute the specified user, even if they're not on the server", + + signature: [ + { + user: ct.string(), + time: ct.delay(), + reason: ct.string({ required: false, catchAll: true }), + + ...opts, + }, + { + user: ct.string(), + reason: ct.string({ required: false, catchAll: true }), + + ...opts, + }, + ], + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + const memberToMute = await resolveMember(pluginData.client, pluginData.guild, user.id); + + // Make sure we're allowed to mute this user + if (memberToMute && !canActOn(pluginData, msg.member, memberToMute)) { + sendErrorMessage(pluginData, msg.channel, "Cannot mute: insufficient permissions"); + return; + } + + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + let ppId: string | undefined; + + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + ppId = msg.author.id; + } + + let contactMethods; + try { + contactMethods = readContactMethodsFromArgs(args); + } catch (e) { + sendErrorMessage(pluginData, msg.channel, e.message); + return; + } + + actualMuteCmd( + pluginData, + msg.channel, + user, + [...msg.attachments.values()], + mod, + ppId, + "time" in args ? args.time ?? undefined : undefined, + args.reason, + contactMethods, + ); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts new file mode 100644 index 000000000..b32cde4fb --- /dev/null +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts @@ -0,0 +1,95 @@ +import { ChannelType } from "discord.js"; +import { slashOptions } from "knub"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { UserNotificationMethod, convertDelayStringToMS } from "../../../../utils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "time", description: "The duration of the mute", required: false }), + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to mute as", required: false }), + slashOptions.string({ + name: "notify", + description: "How to notify", + required: false, + choices: [ + { name: "DM", value: "dm" }, + { name: "Channel", value: "channel" }, + ], + }), + slashOptions.channel({ + name: "notify-channel", + description: "The channel to notify in", + channelTypes: [ChannelType.GuildText, ChannelType.PrivateThread, ChannelType.PublicThread], + required: false, + }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the mute", + }), +]; + +export const ForceMuteSlashCmd = { + name: "forcemute", + configPermission: "can_mute", + description: "Force-mute the specified user, even if they're not on the server", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to mute", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + let mod = interaction.member; + let ppId: string | undefined; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + ppId = interaction.user.id; + } + + const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + if (options.time && !convertedTime) { + sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + return; + } + + let contactMethods: UserNotificationMethod[] | undefined; + try { + contactMethods = readContactMethodsFromArgs(options) ?? undefined; + } catch (e) { + sendErrorMessage(pluginData, interaction, e.message); + return; + } + + actualMuteCmd( + pluginData, + interaction, + options.user, + attachments, + mod, + ppId, + options.time, + options.reason, + contactMethods, + ); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts similarity index 55% rename from backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts rename to backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts index 5ce489a1e..c1b973512 100644 --- a/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts @@ -1,14 +1,14 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage } from "../../../pluginUtils"; -import { resolveMember, resolveUser } from "../../../utils"; -import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd"; -import { modActionsMsgCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../../utils"; +import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { modActionsMsgCmd } from "../../types"; const opts = { mod: ct.member({ option: true }), }; -export const ForceUnmuteCmd = modActionsMsgCmd({ +export const ForceUnmuteMsgCmd = modActionsMsgCmd({ trigger: "forceunmute", permission: "can_mute", description: "Force-unmute the specified user, even if they're not on the server", @@ -51,6 +51,29 @@ export const ForceUnmuteCmd = modActionsMsgCmd({ return; } - actualUnmuteCmd(pluginData, user, msg, args); + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + let ppId: string | undefined; + + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + ppId = msg.author.id; + } + + actualUnmuteCmd( + pluginData, + msg.channel, + user, + [...msg.attachments.values()], + mod, + ppId, + "time" in args ? args.time ?? undefined : undefined, + args.reason, + ); }, }); diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts new file mode 100644 index 000000000..107151a82 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts @@ -0,0 +1,60 @@ +import { slashOptions } from "knub"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { convertDelayStringToMS } from "../../../../utils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "time", description: "The duration of the unmute", required: false }), + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to unmute as", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the unmute", + }), +]; + +export const ForceUnmuteSlashCmd = { + name: "forceunmute", + configPermission: "can_mute", + description: "Force-unmute the specified user, even if they're not on the server", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to unmute", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + let mod = interaction.member; + let ppId: string | undefined; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + ppId = interaction.user.id; + } + + const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + if (options.time && !convertedTime) { + sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + return; + } + + actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, options.time, options.reason); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts new file mode 100644 index 000000000..e7701d4a4 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts @@ -0,0 +1,19 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { actualHideCaseCmd } from "../../functions/actualCommands/actualHideCaseCmd"; +import { modActionsMsgCmd } from "../../types"; + +export const HideCaseMsgCmd = modActionsMsgCmd({ + trigger: ["hide", "hidecase", "hide_case"], + permission: "can_hidecase", + description: "Hide the specified case so it doesn't appear in !cases or !info", + + signature: [ + { + caseNum: ct.number({ rest: true }), + }, + ], + + async run({ pluginData, message: msg, args }) { + actualHideCaseCmd(pluginData, msg.channel, args.caseNum); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts new file mode 100644 index 000000000..c324b250a --- /dev/null +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts @@ -0,0 +1,17 @@ +import { slashOptions } from "knub"; +import { actualHideCaseCmd } from "../../functions/actualCommands/actualHideCaseCmd"; + +export const HideCaseSlashCmd = { + name: "hidecase", + configPermission: "can_hidecase", + description: "Hide the specified case so it doesn't appear in !cases or !info", + allowDms: false, + + signature: [ + slashOptions.string({ name: "case-number", description: "The number of the case to hide", required: true }), + ], + + async run({ interaction, options, pluginData }) { + actualHideCaseCmd(pluginData, interaction, options["case-number"].split(/[\s,]+/).map(Number)); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts new file mode 100644 index 000000000..bab6064dc --- /dev/null +++ b/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts @@ -0,0 +1,68 @@ +import { hasPermission } from "knub/helpers"; +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { sendErrorMessage } from "../../../../pluginUtils"; +import { resolveUser } from "../../../../utils"; +import { actualKickCmd } from "../../functions/actualCommands/actualKickCmd"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsMsgCmd } from "../../types"; + +const opts = { + mod: ct.member({ option: true }), + notify: ct.string({ option: true }), + "notify-channel": ct.textChannel({ option: true }), + clean: ct.bool({ option: true, isSwitch: true }), +}; + +export const KickMsgCmd = modActionsMsgCmd({ + trigger: "kick", + permission: "can_kick", + description: "Kick the specified member", + + signature: [ + { + user: ct.string(), + reason: ct.string({ required: false, catchAll: true }), + + ...opts, + }, + ], + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + if (args.mod) { + if (!(await hasPermission(await pluginData.config.getForMessage(msg), "can_act_as_other"))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + } + + let contactMethods; + try { + contactMethods = readContactMethodsFromArgs(args); + } catch (e) { + sendErrorMessage(pluginData, msg.channel, e.message); + return; + } + + actualKickCmd( + pluginData, + msg.channel, + msg.member, + user, + args.reason, + [...msg.attachments.values()], + mod, + contactMethods, + args.clean, + ); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts new file mode 100644 index 000000000..460410066 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts @@ -0,0 +1,91 @@ +import { ChannelType } from "discord.js"; +import { slashOptions } from "knub"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { UserNotificationMethod } from "../../../../utils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualKickCmd } from "../../functions/actualCommands/actualKickCmd"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to kick as", required: false }), + slashOptions.string({ + name: "notify", + description: "How to notify", + required: false, + choices: [ + { name: "DM", value: "dm" }, + { name: "Channel", value: "channel" }, + ], + }), + slashOptions.channel({ + name: "notify-channel", + description: "The channel to notify in", + channelTypes: [ChannelType.GuildText, ChannelType.PrivateThread, ChannelType.PublicThread], + required: false, + }), + slashOptions.boolean({ + name: "clean", + description: "Whether or not to delete the member's last messages", + required: false, + }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the kick", + }), +]; + +export const KickSlashCmd = { + name: "kick", + configPermission: "can_kick", + description: "Kick the specified member", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to kick", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + let mod = interaction.member; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + } + + let contactMethods: UserNotificationMethod[] | undefined; + try { + contactMethods = readContactMethodsFromArgs(options) ?? undefined; + } catch (e) { + sendErrorMessage(pluginData, interaction, e.message); + return; + } + + actualKickCmd( + pluginData, + interaction, + interaction.member, + options.user, + options.reason || "", + attachments, + mod, + contactMethods, + options.clean, + ); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts new file mode 100644 index 000000000..222dbaccc --- /dev/null +++ b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts @@ -0,0 +1,19 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; +import { modActionsMsgCmd } from "../../types"; + +export const MassBanMsgCmd = modActionsMsgCmd({ + trigger: "massban", + permission: "can_massban", + description: "Mass-ban a list of user IDs", + + signature: [ + { + userIds: ct.string({ rest: true }), + }, + ], + + async run({ pluginData, message: msg, args }) { + actualMassBanCmd(pluginData, msg.channel, args.userIds, msg.member); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts new file mode 100644 index 000000000..74f9ba918 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts @@ -0,0 +1,15 @@ +import { slashOptions } from "knub"; +import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; + +export const MassBanSlashCmd = { + name: "massban", + configPermission: "can_massban", + description: "Mass-ban a list of user IDs", + allowDms: false, + + signature: [slashOptions.string({ name: "user-ids", description: "The list of user IDs to ban", required: true })], + + async run({ interaction, options, pluginData }) { + actualMassBanCmd(pluginData, interaction, options["user-ids"].split(/[\s,\r\n]+/), interaction.member); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts new file mode 100644 index 000000000..0d7793cea --- /dev/null +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts @@ -0,0 +1,19 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { actualMassMuteCmd } from "../../functions/actualCommands/actualMassMuteCmd"; +import { modActionsMsgCmd } from "../../types"; + +export const MassMuteMsgCmd = modActionsMsgCmd({ + trigger: "massmute", + permission: "can_massmute", + description: "Mass-mute a list of user IDs", + + signature: [ + { + userIds: ct.string({ rest: true }), + }, + ], + + async run({ pluginData, message: msg, args }) { + actualMassMuteCmd(pluginData, msg.channel, args.userIds, msg.member); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts new file mode 100644 index 000000000..1650b1744 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts @@ -0,0 +1,15 @@ +import { slashOptions } from "knub"; +import { actualMassMuteCmd } from "../../functions/actualCommands/actualMassMuteCmd"; + +export const MassMuteSlashSlashCmd = { + name: "massmute", + configPermission: "can_massmute", + description: "Mass-mute a list of user IDs", + allowDms: false, + + signature: [slashOptions.string({ name: "user-ids", description: "The list of user IDs to mute", required: true })], + + async run({ interaction, options, pluginData }) { + actualMassMuteCmd(pluginData, interaction, options["user-ids"].split(/[\s,\r\n]+/), interaction.member); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts new file mode 100644 index 000000000..8508b00ec --- /dev/null +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts @@ -0,0 +1,19 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; +import { modActionsMsgCmd } from "../../types"; + +export const MassUnbanMsgCmd = modActionsMsgCmd({ + trigger: "massunban", + permission: "can_massunban", + description: "Mass-unban a list of user IDs", + + signature: [ + { + userIds: ct.string({ rest: true }), + }, + ], + + async run({ pluginData, message: msg, args }) { + actualMassBanCmd(pluginData, msg.channel, args.userIds, msg.member); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts new file mode 100644 index 000000000..15f6ca6a0 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts @@ -0,0 +1,15 @@ +import { slashOptions } from "knub"; +import { actualMassUnbanCmd } from "../../functions/actualCommands/actualMassUnbanCmd"; + +export const MassUnbanSlashCmd = { + name: "massunban", + configPermission: "can_massunban", + description: "Mass-unban a list of user IDs", + allowDms: false, + + signature: [slashOptions.string({ name: "user-ids", description: "The list of user IDs to unban", required: true })], + + async run({ interaction, options, pluginData }) { + actualMassUnbanCmd(pluginData, interaction, options["user-ids"].split(/[\s,\r\n]+/), interaction.member); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/MuteCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts similarity index 56% rename from backend/src/plugins/ModActions/commands/MuteCmd.ts rename to backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts index 4c5057011..16a5343f0 100644 --- a/backend/src/plugins/ModActions/commands/MuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts @@ -1,10 +1,11 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage } from "../../../pluginUtils"; -import { resolveMember, resolveUser } from "../../../utils"; -import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; -import { actualMuteUserCmd } from "../functions/actualMuteUserCmd"; -import { isBanned } from "../functions/isBanned"; -import { modActionsMsgCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../../utils"; +import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; +import { isBanned } from "../../functions/isBanned"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsMsgCmd } from "../../types"; const opts = { mod: ct.member({ option: true }), @@ -12,7 +13,7 @@ const opts = { "notify-channel": ct.textChannel({ option: true }), }; -export const MuteCmd = modActionsMsgCmd({ +export const MuteMsgCmd = modActionsMsgCmd({ trigger: "mute", permission: "can_mute", description: "Mute the specified member", @@ -73,6 +74,38 @@ export const MuteCmd = modActionsMsgCmd({ return; } - actualMuteUserCmd(pluginData, user, msg, args); + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + let ppId: string | undefined; + + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + ppId = msg.author.id; + } + + let contactMethods; + try { + contactMethods = readContactMethodsFromArgs(args); + } catch (e) { + sendErrorMessage(pluginData, msg.channel, e.message); + return; + } + + actualMuteCmd( + pluginData, + msg.channel, + user, + [...msg.attachments.values()], + mod, + ppId, + "time" in args ? args.time ?? undefined : undefined, + args.reason, + contactMethods, + ); }, }); diff --git a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts new file mode 100644 index 000000000..c17200fcc --- /dev/null +++ b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts @@ -0,0 +1,130 @@ +import { ChannelType } from "discord.js"; +import { slashOptions } from "knub"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; +import { isBanned } from "../../functions/isBanned"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "time", description: "The duration of the mute", required: false }), + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to mute as", required: false }), + slashOptions.string({ + name: "notify", + description: "How to notify", + required: false, + choices: [ + { name: "DM", value: "dm" }, + { name: "Channel", value: "channel" }, + ], + }), + slashOptions.channel({ + name: "notify-channel", + description: "The channel to notify in", + channelTypes: [ChannelType.GuildText, ChannelType.PrivateThread, ChannelType.PublicThread], + required: false, + }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the mute", + }), +]; + +export const MuteSlashCmd = { + name: "mute", + configPermission: "can_mute", + description: "Mute the specified member", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to mute", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + const memberToMute = await resolveMember(pluginData.client, pluginData.guild, options.user.id); + + if (!memberToMute) { + const _isBanned = await isBanned(pluginData, options.user.id); + const prefix = pluginData.fullConfig.prefix; + if (_isBanned) { + sendErrorMessage( + pluginData, + interaction, + `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`, + ); + return; + } else { + // Ask the mod if we should upgrade to a forcemute as the user is not on the server + const reply = await waitForButtonConfirm( + interaction, + { content: "User not found on the server, forcemute instead?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: interaction.member.id }, + ); + + if (!reply) { + sendErrorMessage(pluginData, interaction, "User not on server, mute cancelled by moderator"); + return; + } + } + } + + // Make sure we're allowed to mute this member + if (memberToMute && !canActOn(pluginData, interaction.member, memberToMute)) { + sendErrorMessage(pluginData, interaction, "Cannot mute: insufficient permissions"); + return; + } + + let mod = interaction.member; + let ppId: string | undefined; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + ppId = interaction.user.id; + } + + const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + if (options.time && !convertedTime) { + sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + return; + } + + let contactMethods: UserNotificationMethod[] | undefined; + try { + contactMethods = readContactMethodsFromArgs(options) ?? undefined; + } catch (e) { + sendErrorMessage(pluginData, interaction, e.message); + return; + } + + actualMuteCmd( + pluginData, + interaction, + options.user, + attachments, + mod, + ppId, + options.time, + options.reason, + contactMethods, + ); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts index c7122fe99..14336d892 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts @@ -1,7 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { sendErrorMessage } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; -import { actualNoteCmd } from "../../functions/actualNoteCmd"; +import { actualNoteCmd } from "../../functions/actualCommands/actualNoteCmd"; import { modActionsMsgCmd } from "../../types"; export const NoteMsgCmd = modActionsMsgCmd({ diff --git a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts index 82b38be7e..50fd2735f 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts @@ -1,38 +1,27 @@ -import { ApplicationCommandOptionType, ChatInputCommandInteraction } from "discord.js"; import { slashOptions } from "knub"; import { sendErrorMessage } from "../../../../pluginUtils"; -import { actualNoteCmd } from "../../functions/actualNoteCmd"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualNoteCmd } from "../../functions/actualCommands/actualNoteCmd"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "note", description: "The note to add to the user", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the note", + }), +]; export const NoteSlashCmd = { name: "note", + configPermission: "can_note", description: "Add a note to the specified user", allowDms: false, - configPermission: "can_note", - signature: [ - slashOptions.user({ name: "user", description: "The user to add a note to", required: true }), - slashOptions.string({ name: "note", description: "The note to add to the user", required: false }), - ...new Array(10).fill(0).map((_, i) => { - return { - name: `attachment${i + 1}`, - description: "An attachment to add to the note", - type: ApplicationCommandOptionType.Attachment, - required: false, - resolveValue: (interaction: ChatInputCommandInteraction) => { - return interaction.options.getAttachment(`attachment${i + 1}`); - }, - getExtraAPIProps: () => ({}), - }; - }), - ], + signature: [slashOptions.user({ name: "user", description: "The user to add a note to", required: true }), ...opts], async run({ interaction, options, pluginData }) { - const attachments = new Array(10) - .fill(0) - .map((_, i) => { - return options[`attachment${i + 1}`]; - }) - .filter((a) => a); + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.note || options.note.trim() === "") && attachments.length < 1) { sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts new file mode 100644 index 000000000..1d9bd37d2 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts @@ -0,0 +1,45 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { resolveUser } from "../../../../utils"; +import { actualUnbanCmd } from "../../functions/actualCommands/actualUnbanCmd"; +import { modActionsMsgCmd } from "../../types"; + +const opts = { + mod: ct.member({ option: true }), +}; + +export const UnbanMsgCmd = modActionsMsgCmd({ + trigger: "unban", + permission: "can_unban", + description: "Unban the specified member", + + signature: [ + { + user: ct.string(), + reason: ct.string({ required: false, catchAll: true }), + + ...opts, + }, + ], + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + } + + actualUnbanCmd(pluginData, msg.channel, msg.author.id, user, args.reason, [...msg.attachments.values()], mod); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts new file mode 100644 index 000000000..e85cfdfb7 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts @@ -0,0 +1,50 @@ +import { slashOptions } from "knub"; +import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualUnbanCmd } from "../../functions/actualCommands/actualUnbanCmd"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to unban as", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the unban", + }), +]; + +export const UnbanSlashCmd = { + name: "unban", + configPermission: "can_unban", + description: "Unban the specified member", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to unban", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + let mod = interaction.member; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + } + + actualUnbanCmd(pluginData, interaction, interaction.user.id, options.user, options.reason, attachments, mod); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts new file mode 100644 index 000000000..94c029a21 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts @@ -0,0 +1,19 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { actualHideCaseCmd } from "../../functions/actualCommands/actualHideCaseCmd"; +import { modActionsMsgCmd } from "../../types"; + +export const UnhideCaseMsgCmd = modActionsMsgCmd({ + trigger: ["unhide", "unhidecase", "unhide_case"], + permission: "can_hidecase", + description: "Un-hide the specified case, making it appear in !cases and !info again", + + signature: [ + { + caseNum: ct.number({ rest: true }), + }, + ], + + async run({ pluginData, message: msg, args }) { + actualHideCaseCmd(pluginData, msg.channel, args.caseNum); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts new file mode 100644 index 000000000..d0a66d7c3 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts @@ -0,0 +1,17 @@ +import { slashOptions } from "knub"; +import { actualUnhideCaseCmd } from "../../functions/actualCommands/actualUnhideCaseCmd"; + +export const UnhideCaseSlashCmd = { + name: "unhidecase", + configPermission: "can_hidecase", + description: "Un-hide the specified case", + allowDms: false, + + signature: [ + slashOptions.string({ name: "case-number", description: "The number of the case to unhide", required: true }), + ], + + async run({ interaction, options, pluginData }) { + actualUnhideCaseCmd(pluginData, interaction, options["case-number"].split(/[\s,]+/).map(Number)); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/UnmuteCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts similarity index 65% rename from backend/src/plugins/ModActions/commands/UnmuteCmd.ts rename to backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts index 3a4171ac4..8d7e111a8 100644 --- a/backend/src/plugins/ModActions/commands/UnmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts @@ -1,17 +1,17 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; -import { canActOn, sendErrorMessage } from "../../../pluginUtils"; -import { resolveMember, resolveUser } from "../../../utils"; -import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; -import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd"; -import { isBanned } from "../functions/isBanned"; -import { modActionsMsgCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../../utils"; +import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { MutesPlugin } from "../../../Mutes/MutesPlugin"; +import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { isBanned } from "../../functions/isBanned"; +import { modActionsMsgCmd } from "../../types"; const opts = { mod: ct.member({ option: true }), }; -export const UnmuteCmd = modActionsMsgCmd({ +export const UnmuteMsgCmd = modActionsMsgCmd({ trigger: "unmute", permission: "can_mute", description: "Unmute the specified member", @@ -84,6 +84,29 @@ export const UnmuteCmd = modActionsMsgCmd({ return; } - actualUnmuteCmd(pluginData, user, msg, args); + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + let ppId: string | undefined; + + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + return; + } + + mod = args.mod; + ppId = msg.author.id; + } + + actualUnmuteCmd( + pluginData, + msg.channel, + user, + [...msg.attachments.values()], + mod, + ppId, + "time" in args ? args.time ?? undefined : undefined, + args.reason, + ); }, }); diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts new file mode 100644 index 000000000..04526be61 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts @@ -0,0 +1,108 @@ +import { slashOptions } from "knub"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { convertDelayStringToMS, resolveMember } from "../../../../utils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { MutesPlugin } from "../../../Mutes/MutesPlugin"; +import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { isBanned } from "../../functions/isBanned"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "time", description: "The duration of the unmute", required: false }), + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to unmute as", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the unmute", + }), +]; + +export const UnmuteSlashCmd = { + name: "unmute", + configPermission: "can_mute", + description: "Unmute the specified member", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to unmute", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + const memberToUnmute = await resolveMember(pluginData.client, pluginData.guild, options.user.id); + const mutesPlugin = pluginData.getPlugin(MutesPlugin); + const hasMuteRole = memberToUnmute && mutesPlugin.hasMutedRole(memberToUnmute); + + // Check if they're muted in the first place + if ( + !(await pluginData.state.mutes.isMuted(options.user.id)) && + !hasMuteRole && + !memberToUnmute?.isCommunicationDisabled() + ) { + sendErrorMessage(pluginData, interaction, "Cannot unmute: member is not muted"); + return; + } + + if (!memberToUnmute) { + const banned = await isBanned(pluginData, options.user.id); + const prefix = pluginData.fullConfig.prefix; + if (banned) { + sendErrorMessage( + pluginData, + interaction, + `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`, + ); + return; + } else { + // Ask the mod if we should upgrade to a forceunmute as the user is not on the server + const reply = await waitForButtonConfirm( + interaction, + { content: "User not on server, forceunmute instead?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: interaction.user.id }, + ); + + if (!reply) { + sendErrorMessage(pluginData, interaction, "User not on server, unmute cancelled by moderator"); + return; + } + } + } + + // Make sure we're allowed to unmute this member + if (memberToUnmute && !canActOn(pluginData, interaction.member, memberToUnmute)) { + sendErrorMessage(pluginData, interaction, "Cannot unmute: insufficient permissions"); + return; + } + + let mod = interaction.member; + let ppId: string | undefined; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + ppId = interaction.user.id; + } + + const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + if (options.time && !convertedTime) { + sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + return; + } + + actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, options.time, options.reason); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/UpdateCmd.ts b/backend/src/plugins/ModActions/commands/update/UpdateMsgCmd.ts similarity index 57% rename from backend/src/plugins/ModActions/commands/UpdateCmd.ts rename to backend/src/plugins/ModActions/commands/update/UpdateMsgCmd.ts index 59c68b0d6..73c74f369 100644 --- a/backend/src/plugins/ModActions/commands/UpdateCmd.ts +++ b/backend/src/plugins/ModActions/commands/update/UpdateMsgCmd.ts @@ -1,8 +1,8 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { updateCase } from "../functions/updateCase"; -import { modActionsMsgCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { updateCase } from "../../functions/updateCase"; +import { modActionsMsgCmd } from "../../types"; -export const UpdateCmd = modActionsMsgCmd({ +export const UpdateMsgCmd = modActionsMsgCmd({ trigger: ["update", "reason"], permission: "can_note", description: @@ -19,6 +19,6 @@ export const UpdateCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - await updateCase(pluginData, msg, args); + await updateCase(pluginData, msg.channel, msg.author, args.caseNumber, args.note, [...msg.attachments.values()]); }, }); diff --git a/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts b/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts new file mode 100644 index 000000000..4f6fdae28 --- /dev/null +++ b/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts @@ -0,0 +1,33 @@ +import { slashOptions } from "knub"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { updateCase } from "../../functions/updateCase"; +import { NUMBER_ATTACHMENTS_CASE_UPDATE } from "../constants"; + +const opts = [ + slashOptions.string({ name: "case-number", description: "The number of the case to update", required: false }), + slashOptions.string({ name: "reason", description: "The note to add to the case", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_UPDATE, { + name: "attachment", + description: "An attachment to add to the update", + }), +]; + +export const UpdateSlashCmd = { + name: "update", + configPermission: "can_note", + description: "Update the specified case (or your latest case) by adding more notes to it", + allowDms: false, + + signature: [...opts], + + async run({ interaction, options, pluginData }) { + await updateCase( + pluginData, + interaction, + interaction.user, + options.caseNumber, + options.note, + retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_UPDATE, options, "attachment"), + ); + }, +}; diff --git a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts new file mode 100644 index 000000000..4baeb3c1d --- /dev/null +++ b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts @@ -0,0 +1,79 @@ +import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { errorMessage, resolveMember, resolveUser } from "../../../../utils"; +import { actualWarnCmd } from "../../functions/actualCommands/actualWarnCmd"; +import { isBanned } from "../../functions/isBanned"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsMsgCmd } from "../../types"; + +export const WarnMsgCmd = modActionsMsgCmd({ + trigger: "warn", + permission: "can_warn", + description: "Send a warning to the specified user", + + signature: { + user: ct.string(), + reason: ct.string({ catchAll: true }), + + mod: ct.member({ option: true }), + notify: ct.string({ option: true }), + "notify-channel": ct.textChannel({ option: true }), + }, + + async run({ pluginData, message: msg, args }) { + const user = await resolveUser(pluginData.client, args.user); + if (!user.id) { + sendErrorMessage(pluginData, msg.channel, `User not found`); + return; + } + + const memberToWarn = await resolveMember(pluginData.client, pluginData.guild, user.id); + + if (!memberToWarn) { + const _isBanned = await isBanned(pluginData, user.id); + if (_isBanned) { + sendErrorMessage(pluginData, msg.channel, `User is banned`); + } else { + sendErrorMessage(pluginData, msg.channel, `User not found on the server`); + } + + return; + } + + // Make sure we're allowed to warn this member + if (!canActOn(pluginData, msg.member, memberToWarn)) { + sendErrorMessage(pluginData, msg.channel, "Cannot warn: insufficient permissions"); + return; + } + + // The moderator who did the action is the message author or, if used, the specified -mod + let mod = msg.member; + if (args.mod) { + if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { + msg.channel.send(errorMessage("You don't have permission to use -mod")); + return; + } + + mod = args.mod; + } + + let contactMethods; + try { + contactMethods = readContactMethodsFromArgs(args); + } catch (e) { + sendErrorMessage(pluginData, msg.channel, e.message); + return; + } + + actualWarnCmd( + pluginData, + msg.channel, + msg.author.id, + mod, + memberToWarn, + args.reason, + [...msg.attachments.values()], + contactMethods, + ); + }, +}); diff --git a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts new file mode 100644 index 000000000..025f1981a --- /dev/null +++ b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts @@ -0,0 +1,105 @@ +import { ChannelType } from "discord.js"; +import { slashOptions } from "knub"; +import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { UserNotificationMethod, resolveMember } from "../../../../utils"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { actualWarnCmd } from "../../functions/actualCommands/actualWarnCmd"; +import { isBanned } from "../../functions/isBanned"; +import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "reason", description: "The reason", required: false }), + slashOptions.user({ name: "mod", description: "The moderator to warn as", required: false }), + slashOptions.string({ + name: "notify", + description: "How to notify", + required: false, + choices: [ + { name: "DM", value: "dm" }, + { name: "Channel", value: "channel" }, + ], + }), + slashOptions.channel({ + name: "notify-channel", + description: "The channel to notify in", + channelTypes: [ChannelType.GuildText, ChannelType.PrivateThread, ChannelType.PublicThread], + required: false, + }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason of the warn", + }), +]; + +export const WarnSlashCmd = { + name: "warn", + configPermission: "can_warn", + description: "Send a warning to the specified user", + allowDms: false, + + signature: [slashOptions.user({ name: "user", description: "The user to warn", required: true }), ...opts], + + async run({ interaction, options, pluginData }) { + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + const memberToWarn = await resolveMember(pluginData.client, pluginData.guild, options.user.id); + + if (!memberToWarn) { + const _isBanned = await isBanned(pluginData, options.user.id); + if (_isBanned) { + sendErrorMessage(pluginData, interaction, `User is banned`); + } else { + sendErrorMessage(pluginData, interaction, `User not found on the server`); + } + + return; + } + + // Make sure we're allowed to warn this member + if (!canActOn(pluginData, interaction.member, memberToWarn)) { + sendErrorMessage(pluginData, interaction, "Cannot warn: insufficient permissions"); + return; + } + + let mod = interaction.member; + const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { + channel: interaction.channel, + member: interaction.member, + }); + + if (options.mod) { + if (!canActAsOther) { + sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + return; + } + + mod = options.mod; + } + + let contactMethods: UserNotificationMethod[] | undefined; + try { + contactMethods = readContactMethodsFromArgs(options) ?? undefined; + } catch (e) { + sendErrorMessage(pluginData, interaction, e.message); + return; + } + + actualWarnCmd( + pluginData, + interaction, + interaction.user.id, + mod, + memberToWarn, + options.reason ?? "", + attachments, + contactMethods, + ); + }, +}; diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts new file mode 100644 index 000000000..572d450c2 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts @@ -0,0 +1,55 @@ +import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import { GuildPluginData } from "knub"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { Case } from "../../../../data/entities/Case"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { UnknownUser, renderUserUsername, resolveMember } from "../../../../utils"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; + +export async function actualAddCaseCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + author: GuildMember, + mod: GuildMember, + attachments: Array, + user: User | UnknownUser, + type: keyof CaseTypes, + reason: string, +) { + // If the user exists as a guild member, make sure we can act on them first + const member = await resolveMember(pluginData.client, pluginData.guild, user.id); + if (member && !canActOn(pluginData, author, member)) { + sendErrorMessage(pluginData, context, "Cannot add case on this user: insufficient permissions"); + return; + } + + const formattedReason = formatReasonWithAttachments(reason, attachments); + + // Create the case + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const theCase: Case = await casesPlugin.createCase({ + userId: user.id, + modId: mod.id, + type: CaseTypes[type], + reason: formattedReason, + ppId: mod.id !== author.id ? author.id : undefined, + }); + + if (user) { + sendSuccessMessage(pluginData, context, `Case #${theCase.case_number} created for **${renderUserUsername(user)}**`); + } else { + sendSuccessMessage(pluginData, context, `Case #${theCase.case_number} created`); + } + + // Log the action + pluginData.getPlugin(LogsPlugin).logCaseCreate({ + mod: mod.user, + userId: user.id, + caseNum: theCase.case_number, + caseType: type.toUpperCase(), + reason: formattedReason, + }); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts new file mode 100644 index 000000000..5b665884b --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts @@ -0,0 +1,182 @@ +import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +import { getMemberLevel } from "knub/helpers"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { clearExpiringTempban, registerExpiringTempban } from "../../../../data/loops/expiringTempbansLoop"; +import { canActOn, isContextInteraction, sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { UnknownUser, UserNotificationMethod, renderUserUsername, resolveMember } from "../../../../utils"; +import { banLock } from "../../../../utils/lockNameHelpers"; +import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { ModActionsPluginType } from "../../types"; +import { banUserId } from "../banUserId"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { isBanned } from "../isBanned"; + +export async function actualBanCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + user: User | UnknownUser, + time: number | null, + reason: string, + attachments: Attachment[], + author: GuildMember, + mod: GuildMember, + contactMethods?: UserNotificationMethod[], + deleteDays?: number, +) { + const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id); + const formattedReason = formatReasonWithAttachments(reason, attachments); + + // acquire a lock because of the needed user-inputs below (if banned/not on server) + const lock = await pluginData.locks.acquire(banLock(user)); + let forceban = false; + const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id); + + if (!memberToBan) { + const banned = await isBanned(pluginData, user.id); + + if (!banned) { + // Ask the mod if we should upgrade to a forceban as the user is not on the server + const reply = await waitForButtonConfirm( + context, + { content: "User not on server, forceban instead?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: author.id }, + ); + + if (!reply) { + sendErrorMessage(pluginData, context, "User not on server, ban cancelled by moderator"); + lock.unlock(); + return; + } else { + forceban = true; + } + } + + // Abort if trying to ban user indefinitely if they are already banned indefinitely + if (!existingTempban && !time) { + sendErrorMessage(pluginData, context, `User is already banned indefinitely.`); + return; + } + + // Ask the mod if we should update the existing ban + const reply = await waitForButtonConfirm( + context, + { content: "Failed to message the user. Log the warning anyway?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: author.id }, + ); + + if (!reply) { + sendErrorMessage(pluginData, context, "User already banned, update cancelled by moderator"); + lock.unlock(); + return; + } + + // Update or add new tempban / remove old tempban + if (time && time > 0) { + if (existingTempban) { + await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id); + } else { + await pluginData.state.tempbans.addTempban(user.id, time, mod.id); + } + const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!; + registerExpiringTempban(tempban); + } else if (existingTempban) { + clearExpiringTempban(existingTempban); + pluginData.state.tempbans.clear(user.id); + } + + // Create a new case for the updated ban since we never stored the old case id and log the action + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const createdCase = await casesPlugin.createCase({ + modId: mod.id, + type: CaseTypes.Ban, + userId: user.id, + reason: formattedReason, + noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`], + }); + if (time) { + pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ + mod: mod.user, + user, + caseNumber: createdCase.case_number, + reason: formattedReason, + banTime: humanizeDuration(time), + }); + } else { + pluginData.getPlugin(LogsPlugin).logMemberBan({ + mod: mod.user, + user, + caseNumber: createdCase.case_number, + reason: formattedReason, + }); + } + + sendSuccessMessage( + pluginData, + context, + `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, + ); + lock.unlock(); + return; + } + + // Make sure we're allowed to ban this member if they are on the server + if (!forceban && !canActOn(pluginData, author, memberToBan!)) { + const ourLevel = getMemberLevel(pluginData, author); + const targetLevel = getMemberLevel(pluginData, memberToBan!); + sendErrorMessage( + pluginData, + context, + `Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`, + ); + lock.unlock(); + return; + } + + const matchingConfig = await pluginData.config.getMatchingConfig({ + member: author, + channel: isContextInteraction(context) ? context.channel : context, + }); + const deleteMessageDays = deleteDays ?? matchingConfig.ban_delete_message_days; + const banResult = await banUserId( + pluginData, + user.id, + formattedReason, + { + contactMethods, + caseArgs: { + modId: mod.id, + ppId: mod.id !== author.id ? author.id : undefined, + }, + deleteMessageDays, + modId: mod.id, + }, + time ?? undefined, + ); + + if (banResult.status === "failed") { + sendErrorMessage(pluginData, context, `Failed to ban member: ${banResult.error}`); + lock.unlock(); + return; + } + + let forTime = ""; + if (time && time > 0) { + forTime = `for ${humanizeDuration(time)} `; + } + + // Confirm the action to the moderator + let response = ""; + if (!forceban) { + response = `Banned **${renderUserUsername(user)}** ${forTime}(Case #${banResult.case.case_number})`; + if (banResult.notifyResult.text) response += ` (${banResult.notifyResult.text})`; + } else { + response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`; + } + + lock.unlock(); + sendSuccessMessage(pluginData, context, response); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts new file mode 100644 index 000000000..38ddf2bfd --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts @@ -0,0 +1,24 @@ +import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { sendContextResponse, sendErrorMessage } from "../../../../pluginUtils"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { ModActionsPluginType } from "../../types"; + +export async function actualCaseCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + authorId: string, + caseNumber: number, +) { + const theCase = await pluginData.state.cases.findByCaseNumber(caseNumber); + + if (!theCase) { + sendErrorMessage(pluginData, context, "Case not found"); + return; + } + + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const embed = await casesPlugin.getCaseEmbed(theCase.id, authorId); + + sendContextResponse(context, embed); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts new file mode 100644 index 000000000..22a16e934 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts @@ -0,0 +1,258 @@ +import { APIEmbed, ChatInputCommandInteraction, TextBasedChannel, User } from "discord.js"; +import { GuildPluginData } from "knub"; +import { In } from "typeorm"; +import { FindOptionsWhere } from "typeorm/find-options/FindOptionsWhere"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { Case } from "../../../../data/entities/Case"; +import { sendContextResponse, sendErrorMessage } from "../../../../pluginUtils"; +import { + UnknownUser, + chunkArray, + emptyEmbedValue, + renderUserUsername, + resolveUser, + trimLines, +} from "../../../../utils"; +import { asyncMap } from "../../../../utils/async"; +import { createPaginatedMessage } from "../../../../utils/createPaginatedMessage"; +import { getChunkedEmbedFields } from "../../../../utils/getChunkedEmbedFields"; +import { getGuildPrefix } from "../../../../utils/getGuildPrefix"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { ModActionsPluginType } from "../../types"; + +const casesPerPage = 5; +const maxExpandedCases = 8; + +async function sendExpandedCases( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + casesCount: number, + cases: Case[], +) { + if (casesCount > maxExpandedCases) { + await sendContextResponse(context, "Too many cases for expanded view. Please use compact view instead."); + + return; + } + + const casesPlugin = pluginData.getPlugin(CasesPlugin); + + for (const theCase of cases) { + const embed = await casesPlugin.getCaseEmbed(theCase.id); + await sendContextResponse(context, embed); + } +} + +async function casesUserCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + author: User, + modId: string | null, + user: User | UnknownUser, + modName: string, + typesToShow: CaseTypes[], + hidden: boolean | null, + expand: boolean | null, +) { + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const casesFilters: Omit, "guild_id" | "user_id"> = { type: In(typesToShow) }; + + if (modId) { + casesFilters.mod_id = modId; + } + + const cases = await pluginData.state.cases.with("notes").getByUserId(user.id, casesFilters); + const normalCases = cases.filter((c) => !c.is_hidden); + const hiddenCases = cases.filter((c) => c.is_hidden); + + const userName = + user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUserUsername(user); + + if (cases.length === 0) { + await sendContextResponse(context, `No cases found for **${userName}**${modId ? ` by ${modName}` : ""}.`); + return; + } + + const casesToDisplay = hidden ? cases : normalCases; + + if (!casesToDisplay.length) { + await sendContextResponse( + context, + `No normal cases found for **${userName}**. Use "-hidden" to show ${cases.length} hidden cases.`, + ); + + return; + } + + if (expand) { + sendExpandedCases(pluginData, context, casesToDisplay.length, casesToDisplay); + return; + } + + // Compact view (= regular message with a preview of each case) + const lines = await asyncMap(casesToDisplay, (c) => casesPlugin.getCaseSummary(c, true, author.id)); + + const prefix = getGuildPrefix(pluginData); + const linesPerChunk = 10; + const lineChunks = chunkArray(lines, linesPerChunk); + + const footerField = { + name: emptyEmbedValue, + value: trimLines(` + Use \`${prefix}case \` to see more information about an individual case + `), + }; + + for (const [i, linesInChunk] of lineChunks.entries()) { + const isLastChunk = i === lineChunks.length - 1; + + if (isLastChunk && !hidden && hiddenCases.length) { + if (hiddenCases.length === 1) { + linesInChunk.push(`*+${hiddenCases.length} hidden case, use "-hidden" to show it*`); + } else { + linesInChunk.push(`*+${hiddenCases.length} hidden cases, use "-hidden" to show them*`); + } + } + + const chunkStart = i * linesPerChunk + 1; + const chunkEnd = Math.min((i + 1) * linesPerChunk, lines.length); + + const embed = { + author: { + name: + lineChunks.length === 1 + ? `Cases for ${userName}${modId ? ` by ${modName}` : ""} (${lines.length} total)` + : `Cases ${chunkStart}–${chunkEnd} of ${lines.length} for ${userName}`, + icon_url: user instanceof User ? user.displayAvatarURL() : undefined, + }, + fields: [ + ...getChunkedEmbedFields(emptyEmbedValue, linesInChunk.join("\n")), + ...(isLastChunk ? [footerField] : []), + ], + } satisfies APIEmbed; + + sendContextResponse(context, { embeds: [embed] }); + } +} + +async function casesModCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + author: User, + modId: string | null, + mod: User | UnknownUser, + modName: string, + typesToShow: CaseTypes[], + hidden: boolean | null, + expand: boolean | null, +) { + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const caseFilters = { type: In(typesToShow), is_hidden: !!hidden }; + + const totalCases = await casesPlugin.getTotalCasesByMod(modId ?? author.id, caseFilters); + + if (totalCases === 0) { + sendErrorMessage(pluginData, context, `No cases by **${modName}**`); + return; + } + + const totalPages = Math.max(Math.ceil(totalCases / casesPerPage), 1); + const prefix = getGuildPrefix(pluginData); + + if (expand) { + // Expanded view (= individual case embeds) + const cases = totalCases > 8 ? [] : await casesPlugin.getRecentCasesByMod(modId ?? author.id, 8, 0, caseFilters); + + sendExpandedCases(pluginData, context, totalCases, cases); + return; + } + + createPaginatedMessage( + pluginData.client, + context, + totalPages, + async (page) => { + const cases = await casesPlugin.getRecentCasesByMod( + modId ?? author.id, + casesPerPage, + (page - 1) * casesPerPage, + caseFilters, + ); + + const lines = await asyncMap(cases, (c) => casesPlugin.getCaseSummary(c, true, author.id)); + const firstCaseNum = (page - 1) * casesPerPage + 1; + const lastCaseNum = firstCaseNum - 1 + Math.min(cases.length, casesPerPage); + const title = `Most recent cases ${firstCaseNum}-${lastCaseNum} of ${totalCases} by ${modName}`; + + const embed = { + author: { + name: title, + icon_url: mod instanceof User ? mod.displayAvatarURL() : undefined, + }, + fields: [ + ...getChunkedEmbedFields(emptyEmbedValue, lines.join("\n")), + { + name: emptyEmbedValue, + value: trimLines(` + Use \`${prefix}case \` to see more information about an individual case + Use \`${prefix}cases \` to see a specific user's cases + `), + }, + ], + } satisfies APIEmbed; + + return { embeds: [embed] }; + }, + { + limitToUserId: author.id, + }, + ); +} + +export async function actualCasesCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + modId: string | null, + user: User | UnknownUser | null, + author: User, + notes: boolean | null, + warns: boolean | null, + mutes: boolean | null, + unmutes: boolean | null, + bans: boolean | null, + unbans: boolean | null, + reverseFilters: boolean | null, + hidden: boolean | null, + expand: boolean | null, +) { + const mod = modId ? await resolveUser(pluginData.client, modId) : null; + const modName = modId ? (mod instanceof User ? renderUserUsername(mod) : modId) : renderUserUsername(author); + + let typesToShow: CaseTypes[] = []; + + if (notes) typesToShow.push(CaseTypes.Note); + if (warns) typesToShow.push(CaseTypes.Warn); + if (mutes) typesToShow.push(CaseTypes.Mute); + if (unmutes) typesToShow.push(CaseTypes.Unmute); + if (bans) typesToShow.push(CaseTypes.Ban); + if (unbans) typesToShow.push(CaseTypes.Unban); + + if (typesToShow.length === 0) { + typesToShow = [CaseTypes.Note, CaseTypes.Warn, CaseTypes.Mute, CaseTypes.Unmute, CaseTypes.Ban, CaseTypes.Unban]; + } else { + if (reverseFilters) { + typesToShow = [ + CaseTypes.Note, + CaseTypes.Warn, + CaseTypes.Mute, + CaseTypes.Unmute, + CaseTypes.Ban, + CaseTypes.Unban, + ].filter((t) => !typesToShow.includes(t)); + } + } + + user + ? casesUserCmd(pluginData, context, author, modId!, user, modName, typesToShow, hidden, expand) + : casesModCmd(pluginData, context, author, modId!, mod!, modName, typesToShow, hidden, expand); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts new file mode 100644 index 000000000..3fac80cff --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts @@ -0,0 +1,95 @@ +import { ChatInputCommandInteraction, GuildMember, TextBasedChannel } from "discord.js"; +import { GuildPluginData, helpers } from "knub"; +import { Case } from "../../../../data/entities/Case"; +import { + isContextInteraction, + sendContextResponse, + sendErrorMessage, + sendSuccessMessage, +} from "../../../../pluginUtils"; +import { SECONDS } from "../../../../utils"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { TimeAndDatePlugin } from "../../../TimeAndDate/TimeAndDatePlugin"; +import { ModActionsPluginType } from "../../types"; + +export async function actualDeleteCaseCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + author: GuildMember, + caseNumbers: number[], + force: boolean, +) { + const failed: number[] = []; + const validCases: Case[] = []; + let cancelled = 0; + + for (const num of caseNumbers) { + const theCase = await pluginData.state.cases.findByCaseNumber(num); + if (!theCase) { + failed.push(num); + continue; + } + + validCases.push(theCase); + } + + if (failed.length === caseNumbers.length) { + sendErrorMessage(pluginData, context, "None of the cases were found!"); + return; + } + + for (const theCase of validCases) { + if (!force) { + const cases = pluginData.getPlugin(CasesPlugin); + const embedContent = await cases.getCaseEmbed(theCase); + sendContextResponse(context, { + ...embedContent, + content: "Delete the following case? Answer 'Yes' to continue, 'No' to cancel.", + }); + + const reply = await helpers.waitForReply( + pluginData.client, + isContextInteraction(context) ? context.channel! : context, + author.id, + 15 * SECONDS, + ); + const normalizedReply = (reply?.content || "").toLowerCase().trim(); + if (normalizedReply !== "yes" && normalizedReply !== "y") { + sendContextResponse(context, "Cancelled. Case was not deleted."); + cancelled++; + continue; + } + } + + const deletedByName = author.user.tag; + + const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); + const deletedAt = timeAndDate.inGuildTz().format(timeAndDate.getDateFormat("pretty_datetime")); + + await pluginData.state.cases.softDelete( + theCase.id, + author.id, + deletedByName, + `Case deleted by **${deletedByName}** (\`${author.id}\`) on ${deletedAt}`, + ); + + const logs = pluginData.getPlugin(LogsPlugin); + logs.logCaseDelete({ + mod: author, + case: theCase, + }); + } + + const failedAddendum = + failed.length > 0 + ? `\nThe following cases were not found: ${failed.toString().replace(new RegExp(",", "g"), ", ")}` + : ""; + const amt = validCases.length - cancelled; + if (amt === 0) { + sendErrorMessage(pluginData, context, "All deletions were cancelled, no cases were deleted."); + return; + } + + sendSuccessMessage(pluginData, context, `${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}`); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts new file mode 100644 index 000000000..095e06a75 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts @@ -0,0 +1,60 @@ +import { Attachment, ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel, User } from "discord.js"; +import { GuildPluginData } from "knub"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { LogType } from "../../../../data/LogType"; +import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { DAYS, MINUTES, UnknownUser } from "../../../../utils"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { ignoreEvent } from "../ignoreEvent"; + +export async function actualForceBanCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + authorId: string, + user: User | UnknownUser, + reason: string, + attachments: Array, + mod: GuildMember, +) { + const formattedReason = formatReasonWithAttachments(reason, attachments); + + ignoreEvent(pluginData, IgnoredEventType.Ban, user.id); + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, user.id); + + try { + // FIXME: Use banUserId()? + await pluginData.guild.bans.create(user.id as Snowflake, { + deleteMessageSeconds: (1 * DAYS) / MINUTES, + reason: formattedReason ?? undefined, + }); + } catch { + sendErrorMessage(pluginData, context, "Failed to forceban member"); + return; + } + + // Create a case + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const createdCase = await casesPlugin.createCase({ + userId: user.id, + modId: mod.id, + type: CaseTypes.Ban, + reason: formattedReason, + ppId: mod.id !== authorId ? authorId : undefined, + }); + + // Confirm the action + sendSuccessMessage(pluginData, context, `Member forcebanned (Case #${createdCase.case_number})`); + + // Log the action + pluginData.getPlugin(LogsPlugin).logMemberForceban({ + mod, + userId: user.id, + caseNumber: createdCase.case_number, + reason: formattedReason, + }); + + pluginData.state.events.emit("ban", user.id, formattedReason); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts new file mode 100644 index 000000000..d500ce5f6 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts @@ -0,0 +1,38 @@ +import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { ModActionsPluginType } from "../../types"; + +export async function actualHideCaseCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + caseNumbers: number[], +) { + const failed: number[] = []; + + for (const num of caseNumbers) { + const theCase = await pluginData.state.cases.findByCaseNumber(num); + if (!theCase) { + failed.push(num); + continue; + } + + await pluginData.state.cases.setHidden(theCase.id, true); + } + + if (failed.length === caseNumbers.length) { + sendErrorMessage(pluginData, context, "None of the cases were found!"); + return; + } + const failedAddendum = + failed.length > 0 + ? `\nThe following cases were not found: ${failed.toString().replace(new RegExp(",", "g"), ", ")}` + : ""; + + const amt = caseNumbers.length - failed.length; + sendSuccessMessage( + pluginData, + context, + `${amt} case${amt === 1 ? " is" : "s are"} now hidden! Use \`unhidecase\` to unhide them.${failedAddendum}`, + ); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts new file mode 100644 index 000000000..e66383fec --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts @@ -0,0 +1,89 @@ +import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import { GuildPluginData } from "knub"; +import { LogType } from "../../../../data/LogType"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { + DAYS, + SECONDS, + UnknownUser, + UserNotificationMethod, + renderUserUsername, + resolveMember, +} from "../../../../utils"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { ignoreEvent } from "../ignoreEvent"; +import { isBanned } from "../isBanned"; +import { kickMember } from "../kickMember"; + +export async function actualKickCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + author: GuildMember, + user: User | UnknownUser, + reason: string, + attachments: Attachment[], + mod: GuildMember, + contactMethods?: UserNotificationMethod[], + clean?: boolean, +) { + const memberToKick = await resolveMember(pluginData.client, pluginData.guild, user.id); + + if (!memberToKick) { + const banned = await isBanned(pluginData, user.id); + if (banned) { + sendErrorMessage(pluginData, context, `User is banned`); + } else { + sendErrorMessage(pluginData, context, `User not found on the server`); + } + + return; + } + + // Make sure we're allowed to kick this member + if (!canActOn(pluginData, author, memberToKick)) { + sendErrorMessage(pluginData, context, "Cannot kick: insufficient permissions"); + return; + } + + const formattedReason = formatReasonWithAttachments(reason, attachments); + + const kickResult = await kickMember(pluginData, memberToKick, formattedReason, { + contactMethods, + caseArgs: { + modId: mod.id, + ppId: mod.id !== author.id ? author.id : undefined, + }, + }); + + if (clean) { + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, memberToKick.id); + ignoreEvent(pluginData, IgnoredEventType.Ban, memberToKick.id); + + try { + await memberToKick.ban({ deleteMessageSeconds: (1 * DAYS) / SECONDS, reason: "kick -clean" }); + } catch { + sendErrorMessage(pluginData, context, "Failed to ban the user to clean messages (-clean)"); + } + + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, memberToKick.id); + ignoreEvent(pluginData, IgnoredEventType.Unban, memberToKick.id); + + try { + await pluginData.guild.bans.remove(memberToKick.id, "kick -clean"); + } catch { + sendErrorMessage(pluginData, context, "Failed to unban the user after banning them (-clean)"); + } + } + + if (kickResult.status === "failed") { + sendErrorMessage(pluginData, context, `Failed to kick user`); + return; + } + + // Confirm the action to the moderator + let response = `Kicked **${renderUserUsername(memberToKick.user)}** (Case #${kickResult.case.case_number})`; + + if (kickResult.notifyResult.text) response += ` (${kickResult.notifyResult.text})`; + sendSuccessMessage(pluginData, context, response); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts new file mode 100644 index 000000000..c0abc3a83 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts @@ -0,0 +1,163 @@ +import { ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { waitForReply } from "knub/helpers"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { LogType } from "../../../../data/LogType"; +import { humanizeDurationShort } from "../../../../humanizeDurationShort"; +import { + canActOn, + isContextInteraction, + sendContextResponse, + sendErrorMessage, + sendSuccessMessage, +} from "../../../../pluginUtils"; +import { DAYS, MINUTES, SECONDS, noop } from "../../../../utils"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { ignoreEvent } from "../ignoreEvent"; + +export async function actualMassBanCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + userIds: string[], + author: GuildMember, +) { + // Limit to 100 users at once (arbitrary?) + if (userIds.length > 100) { + sendErrorMessage(pluginData, context, `Can only massban max 100 users at once`); + return; + } + + // Ask for ban reason (cleaner this way instead of trying to cram it into the args) + sendContextResponse(context, "Ban reason? `cancel` to cancel"); + const banReasonReply = await waitForReply( + pluginData.client, + isContextInteraction(context) ? context.channel! : context, + author.id, + ); + + if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") { + sendErrorMessage(pluginData, context, "Cancelled"); + return; + } + + const banReason = formatReasonWithAttachments(banReasonReply.content, [...banReasonReply.attachments.values()]); + + // Verify we can act on each of the users specified + for (const userId of userIds) { + const member = pluginData.guild.members.cache.get(userId as Snowflake); // TODO: Get members on demand? + if (member && !canActOn(pluginData, author, member)) { + sendErrorMessage(pluginData, context, "Cannot massban one or more users: insufficient permissions"); + return; + } + } + + // Show a loading indicator since this can take a while + const maxWaitTime = pluginData.state.massbanQueue.timeout * pluginData.state.massbanQueue.length; + const maxWaitTimeFormatted = humanizeDurationShort(maxWaitTime, { round: true }); + const initialLoadingText = + pluginData.state.massbanQueue.length === 0 + ? "Banning..." + : `Massban queued. Waiting for previous massban to finish (max wait ${maxWaitTimeFormatted}).`; + const loadingMsg = await sendContextResponse(context, initialLoadingText); + + const waitTimeStart = performance.now(); + const waitingInterval = setInterval(() => { + const waitTime = humanizeDurationShort(performance.now() - waitTimeStart, { round: true }); + loadingMsg + .edit(`Massban queued. Still waiting for previous massban to finish (waited ${waitTime}).`) + .catch(() => clearInterval(waitingInterval)); + }, 1 * MINUTES); + + pluginData.state.massbanQueue.add(async () => { + clearInterval(waitingInterval); + + if (pluginData.state.unloaded) { + void loadingMsg.delete().catch(noop); + return; + } + + void loadingMsg.edit("Banning...").catch(noop); + + // Ban each user and count failed bans (if any) + const startTime = performance.now(); + const failedBans: string[] = []; + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const messageConfig = isContextInteraction(context) + ? await pluginData.config.getForInteraction(context) + : await pluginData.config.getForChannel(context); + const deleteDays = messageConfig.ban_delete_message_days; + + for (const [i, userId] of userIds.entries()) { + if (pluginData.state.unloaded) { + break; + } + + try { + // Ignore automatic ban cases and logs + // We create our own cases below and post a single "mass banned" log instead + ignoreEvent(pluginData, IgnoredEventType.Ban, userId, 30 * MINUTES); + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, userId, 30 * MINUTES); + + await pluginData.guild.bans.create(userId as Snowflake, { + deleteMessageSeconds: (deleteDays * DAYS) / SECONDS, + reason: banReason, + }); + + await casesPlugin.createCase({ + userId, + modId: author.id, + type: CaseTypes.Ban, + reason: `Mass ban: ${banReason}`, + postInCaseLogOverride: false, + }); + + pluginData.state.events.emit("ban", userId, banReason); + } catch { + failedBans.push(userId); + } + + // Send a status update every 10 bans + if ((i + 1) % 10 === 0) { + loadingMsg.edit(`Banning... ${i + 1}/${userIds.length}`).catch(noop); + } + } + + const totalTime = performance.now() - startTime; + const formattedTimeTaken = humanizeDurationShort(totalTime, { round: true }); + + // Clear loading indicator + loadingMsg.delete().catch(noop); + + const successfulBanCount = userIds.length - failedBans.length; + if (successfulBanCount === 0) { + // All bans failed - don't create a log entry and notify the user + sendErrorMessage(pluginData, context, "All bans failed. Make sure the IDs are valid."); + } else { + // Some or all bans were successful. Create a log entry for the mass ban and notify the user. + pluginData.getPlugin(LogsPlugin).logMassBan({ + mod: author.user, + count: successfulBanCount, + reason: banReason, + }); + + if (failedBans.length) { + sendSuccessMessage( + pluginData, + context, + `Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${failedBans.length} failed: ${failedBans.join( + " ", + )}`, + ); + } else { + sendSuccessMessage( + pluginData, + context, + `Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}`, + ); + } + } + }); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts new file mode 100644 index 000000000..8dd735aba --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts @@ -0,0 +1,110 @@ +import { ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { waitForReply } from "knub/helpers"; +import { LogType } from "../../../../data/LogType"; +import { logger } from "../../../../logger"; +import { + canActOn, + isContextInteraction, + sendContextResponse, + sendErrorMessage, + sendSuccessMessage, +} from "../../../../pluginUtils"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { MutesPlugin } from "../../../Mutes/MutesPlugin"; +import { ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; + +export async function actualMassMuteCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + userIds: string[], + author: GuildMember, +) { + // Limit to 100 users at once (arbitrary?) + if (userIds.length > 100) { + sendErrorMessage(pluginData, context, `Can only massmute max 100 users at once`); + return; + } + + // Ask for mute reason + sendContextResponse(context, "Mute reason? `cancel` to cancel"); + const muteReasonReceived = await waitForReply( + pluginData.client, + isContextInteraction(context) ? context.channel! : context, + author.id, + ); + if ( + !muteReasonReceived || + !muteReasonReceived.content || + muteReasonReceived.content.toLowerCase().trim() === "cancel" + ) { + sendErrorMessage(pluginData, context, "Cancelled"); + return; + } + + const muteReason = formatReasonWithAttachments(muteReasonReceived.content, [ + ...muteReasonReceived.attachments.values(), + ]); + + // Verify we can act upon all users + for (const userId of userIds) { + const member = pluginData.guild.members.cache.get(userId as Snowflake); + if (member && !canActOn(pluginData, author, member)) { + sendErrorMessage(pluginData, context, "Cannot massmute one or more users: insufficient permissions"); + return; + } + } + + // Ignore automatic mute cases and logs for these users + // We'll create our own cases below and post a single "mass muted" log instead + userIds.forEach((userId) => { + // Use longer timeouts since this can take a while + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_MUTE, userId, 120 * 1000); + }); + + // Show loading indicator + const loadingMsg = await sendContextResponse(context, "Muting..."); + + // Mute everyone and count fails + const modId = author.id; + const failedMutes: string[] = []; + const mutesPlugin = pluginData.getPlugin(MutesPlugin); + for (const userId of userIds) { + try { + await mutesPlugin.muteUser(userId, 0, `Mass mute: ${muteReason}`, { + caseArgs: { + modId, + }, + }); + } catch (e) { + logger.info(e); + failedMutes.push(userId); + } + } + + // Clear loading indicator + loadingMsg.delete(); + + const successfulMuteCount = userIds.length - failedMutes.length; + if (successfulMuteCount === 0) { + // All mutes failed + sendErrorMessage(pluginData, context, "All mutes failed. Make sure the IDs are valid."); + } else { + // Success on all or some mutes + pluginData.getPlugin(LogsPlugin).logMassMute({ + mod: author.user, + count: successfulMuteCount, + }); + + if (failedMutes.length) { + sendSuccessMessage( + pluginData, + context, + `Muted ${successfulMuteCount} users, ${failedMutes.length} failed: ${failedMutes.join(" ")}`, + ); + } else { + sendSuccessMessage(pluginData, context, `Muted ${successfulMuteCount} users successfully`); + } + } +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts new file mode 100644 index 000000000..cf829fd75 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts @@ -0,0 +1,127 @@ +import { ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { waitForReply } from "knub/helpers"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { LogType } from "../../../../data/LogType"; +import { + isContextInteraction, + sendContextResponse, + sendErrorMessage, + sendSuccessMessage, +} from "../../../../pluginUtils"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { ignoreEvent } from "../ignoreEvent"; +import { isBanned } from "../isBanned"; + +export async function actualMassUnbanCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + userIds: string[], + author: GuildMember, +) { + // Limit to 100 users at once (arbitrary?) + if (userIds.length > 100) { + sendErrorMessage(pluginData, context, `Can only mass-unban max 100 users at once`); + return; + } + + // Ask for unban reason (cleaner this way instead of trying to cram it into the args) + sendContextResponse(context, "Unban reason? `cancel` to cancel"); + const unbanReasonReply = await waitForReply( + pluginData.client, + isContextInteraction(context) ? context.channel! : context, + author.id, + ); + if (!unbanReasonReply || !unbanReasonReply.content || unbanReasonReply.content.toLowerCase().trim() === "cancel") { + sendErrorMessage(pluginData, context, "Cancelled"); + return; + } + + const unbanReason = formatReasonWithAttachments(unbanReasonReply.content, [...unbanReasonReply.attachments.values()]); + + // Ignore automatic unban cases and logs for these users + // We'll create our own cases below and post a single "mass unbanned" log instead + userIds.forEach((userId) => { + // Use longer timeouts since this can take a while + ignoreEvent(pluginData, IgnoredEventType.Unban, userId, 120 * 1000); + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, userId, 120 * 1000); + }); + + // Show a loading indicator since this can take a while + const loadingMsg = await sendContextResponse(context, "Unbanning..."); + + // Unban each user and count failed unbans (if any) + const failedUnbans: Array<{ userId: string; reason: UnbanFailReasons }> = []; + const casesPlugin = pluginData.getPlugin(CasesPlugin); + for (const userId of userIds) { + if (!(await isBanned(pluginData, userId))) { + failedUnbans.push({ userId, reason: UnbanFailReasons.NOT_BANNED }); + continue; + } + + try { + await pluginData.guild.bans.remove(userId as Snowflake, unbanReason ?? undefined); + + await casesPlugin.createCase({ + userId, + modId: author.id, + type: CaseTypes.Unban, + reason: `Mass unban: ${unbanReason}`, + postInCaseLogOverride: false, + }); + } catch { + failedUnbans.push({ userId, reason: UnbanFailReasons.UNBAN_FAILED }); + } + } + + // Clear loading indicator + loadingMsg.delete(); + + const successfulUnbanCount = userIds.length - failedUnbans.length; + if (successfulUnbanCount === 0) { + // All unbans failed - don't create a log entry and notify the user + sendErrorMessage(pluginData, context, "All unbans failed. Make sure the IDs are valid and banned."); + } else { + // Some or all unbans were successful. Create a log entry for the mass unban and notify the user. + pluginData.getPlugin(LogsPlugin).logMassUnban({ + mod: author.user, + count: successfulUnbanCount, + reason: unbanReason, + }); + + if (failedUnbans.length) { + const notBanned = failedUnbans.filter((x) => x.reason === UnbanFailReasons.NOT_BANNED); + const unbanFailed = failedUnbans.filter((x) => x.reason === UnbanFailReasons.UNBAN_FAILED); + + let failedMsg = ""; + if (notBanned.length > 0) { + failedMsg += `${notBanned.length}x ${UnbanFailReasons.NOT_BANNED}:`; + notBanned.forEach((fail) => { + failedMsg += " " + fail.userId; + }); + } + if (unbanFailed.length > 0) { + failedMsg += `\n${unbanFailed.length}x ${UnbanFailReasons.UNBAN_FAILED}:`; + unbanFailed.forEach((fail) => { + failedMsg += " " + fail.userId; + }); + } + + sendSuccessMessage( + pluginData, + context, + `Unbanned ${successfulUnbanCount} users, ${failedUnbans.length} failed:\n${failedMsg}`, + ); + } else { + sendSuccessMessage(pluginData, context, `Unbanned ${successfulUnbanCount} users successfully`); + } + } +} + +enum UnbanFailReasons { + NOT_BANNED = "Not banned", + UNBAN_FAILED = "Unban failed", +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts new file mode 100644 index 000000000..487cf453e --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts @@ -0,0 +1,96 @@ +import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +import { ERRORS, RecoverablePluginError } from "../../../../RecoverablePluginError"; +import { logger } from "../../../../logger"; +import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { + UnknownUser, + UserNotificationMethod, + asSingleLine, + isDiscordAPIError, + renderUserUsername, +} from "../../../../utils"; +import { MutesPlugin } from "../../../Mutes/MutesPlugin"; +import { MuteResult } from "../../../Mutes/types"; +import { ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; + +/** + * The actual function run by both !mute and !forcemute. + * The only difference between the two commands is in target member validation. + */ +export async function actualMuteCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + user: User | UnknownUser, + attachments: Array, + mod: GuildMember, + ppId?: string, + time?: number, + reason?: string, + contactMethods?: UserNotificationMethod[], +) { + const timeUntilUnmute = time && humanizeDuration(time); + const formattedReason = reason ? formatReasonWithAttachments(reason, attachments) : undefined; + + let muteResult: MuteResult; + const mutesPlugin = pluginData.getPlugin(MutesPlugin); + + try { + muteResult = await mutesPlugin.muteUser(user.id, time, formattedReason, { + contactMethods, + caseArgs: { + modId: mod.id, + ppId, + }, + }); + } catch (e) { + if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { + sendErrorMessage(pluginData, context, "Could not mute the user: no mute role set in config"); + } else if (isDiscordAPIError(e) && e.code === 10007) { + sendErrorMessage(pluginData, context, "Could not mute the user: unknown member"); + } else { + logger.error(`Failed to mute user ${user.id}: ${e.stack}`); + if (user.id == null) { + // FIXME: Debug + // tslint:disable-next-line:no-console + console.trace("[DEBUG] Null user.id for mute"); + } + sendErrorMessage(pluginData, context, "Could not mute the user"); + } + + return; + } + + // Confirm the action to the moderator + let response: string; + if (time) { + if (muteResult.updatedExistingMute) { + response = asSingleLine(` + Updated **${renderUserUsername(user)}**'s + mute to ${timeUntilUnmute} (Case #${muteResult.case.case_number}) + `); + } else { + response = asSingleLine(` + Muted **${renderUserUsername(user)}** + for ${timeUntilUnmute} (Case #${muteResult.case.case_number}) + `); + } + } else { + if (muteResult.updatedExistingMute) { + response = asSingleLine(` + Updated **${renderUserUsername(user)}**'s + mute to indefinite (Case #${muteResult.case.case_number}) + `); + } else { + response = asSingleLine(` + Muted **${renderUserUsername(user)}** + indefinitely (Case #${muteResult.case.case_number}) + `); + } + } + + if (muteResult.notifyResult.text) response += ` (${muteResult.notifyResult.text})`; + sendSuccessMessage(pluginData, context, response); +} diff --git a/backend/src/plugins/ModActions/functions/actualNoteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts similarity index 71% rename from backend/src/plugins/ModActions/functions/actualNoteCmd.ts rename to backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts index 524a182ca..2a29e2df2 100644 --- a/backend/src/plugins/ModActions/functions/actualNoteCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts @@ -1,12 +1,12 @@ import { Attachment, ChatInputCommandInteraction, TextBasedChannel, User } from "discord.js"; import { GuildPluginData } from "knub"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { sendSuccessMessage } from "../../../pluginUtils"; -import { UnknownUser, renderUserUsername } from "../../../utils"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { ModActionsPluginType } from "../types"; -import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { sendSuccessMessage } from "../../../../pluginUtils"; +import { UnknownUser, renderUserUsername } from "../../../../utils"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; export async function actualNoteCmd( pluginData: GuildPluginData, diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts new file mode 100644 index 000000000..94df7d4f1 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts @@ -0,0 +1,63 @@ +import { Attachment, ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel, User } from "discord.js"; +import { GuildPluginData } from "knub"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { LogType } from "../../../../data/LogType"; +import { clearExpiringTempban } from "../../../../data/loops/expiringTempbansLoop"; +import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { UnknownUser } from "../../../../utils"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../../Logs/LogsPlugin"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { ignoreEvent } from "../ignoreEvent"; + +export async function actualUnbanCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + authorId: string, + user: User | UnknownUser, + reason: string, + attachments: Array, + mod: GuildMember, +) { + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, user.id); + const formattedReason = formatReasonWithAttachments(reason, attachments); + + try { + ignoreEvent(pluginData, IgnoredEventType.Unban, user.id); + await pluginData.guild.bans.remove(user.id as Snowflake, formattedReason ?? undefined); + } catch { + sendErrorMessage(pluginData, context, "Failed to unban member; are you sure they're banned?"); + return; + } + + // Create a case + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const createdCase = await casesPlugin.createCase({ + userId: user.id, + modId: mod.id, + type: CaseTypes.Unban, + reason: formattedReason, + ppId: mod.id !== authorId ? authorId : undefined, + }); + + // Delete the tempban, if one exists + const tempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id); + if (tempban) { + clearExpiringTempban(tempban); + await pluginData.state.tempbans.clear(user.id); + } + + // Confirm the action + sendSuccessMessage(pluginData, context, `Member unbanned (Case #${createdCase.case_number})`); + + // Log the action + pluginData.getPlugin(LogsPlugin).logMemberUnban({ + mod: mod.user, + userId: user.id, + caseNumber: createdCase.case_number, + reason: formattedReason ?? "", + }); + + pluginData.state.events.emit("unban", user.id); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts new file mode 100644 index 000000000..3ec2cd9e8 --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts @@ -0,0 +1,39 @@ +import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { ModActionsPluginType } from "../../types"; + +export async function actualUnhideCaseCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + caseNumbers: number[], +) { + const failed: number[] = []; + + for (const num of caseNumbers) { + const theCase = await pluginData.state.cases.findByCaseNumber(num); + if (!theCase) { + failed.push(num); + continue; + } + + await pluginData.state.cases.setHidden(theCase.id, false); + } + + if (failed.length === caseNumbers.length) { + sendErrorMessage(pluginData, context, "None of the cases were found!"); + return; + } + + const failedAddendum = + failed.length > 0 + ? `\nThe following cases were not found: ${failed.toString().replace(new RegExp(",", "g"), ", ")}` + : ""; + + const amt = caseNumbers.length - failed.length; + sendSuccessMessage( + pluginData, + context, + `${amt} case${amt === 1 ? " is" : "s are"} no longer hidden!${failedAddendum}`, + ); +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts new file mode 100644 index 000000000..96804c38a --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts @@ -0,0 +1,55 @@ +import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { UnknownUser, asSingleLine, renderUserUsername } from "../../../../utils"; +import { MutesPlugin } from "../../../Mutes/MutesPlugin"; +import { ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; + +export async function actualUnmuteCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + user: User | UnknownUser, + attachments: Array, + mod: GuildMember, + ppId?: string, + time?: number, + reason?: string, +) { + const parsedReason = reason ? formatReasonWithAttachments(reason, attachments) : undefined; + + const mutesPlugin = pluginData.getPlugin(MutesPlugin); + const result = await mutesPlugin.unmuteUser(user.id, time, { + modId: mod.id, + ppId: ppId ?? undefined, + reason: parsedReason, + }); + + if (!result) { + sendErrorMessage(pluginData, context, "User is not muted!"); + return; + } + + // Confirm the action to the moderator + if (time) { + const timeUntilUnmute = time && humanizeDuration(time); + sendSuccessMessage( + pluginData, + context, + asSingleLine(` + Unmuting **${renderUserUsername(user)}** + in ${timeUntilUnmute} (Case #${result.case.case_number}) + `), + ); + } else { + sendSuccessMessage( + pluginData, + context, + asSingleLine(` + Unmuted **${renderUserUsername(user)}** + (Case #${result.case.case_number}) + `), + ); + } +} diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts new file mode 100644 index 000000000..5748f72ed --- /dev/null +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts @@ -0,0 +1,61 @@ +import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { CaseTypes } from "../../../../data/CaseTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { UserNotificationMethod, renderUserUsername } from "../../../../utils"; +import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { ModActionsPluginType } from "../../types"; +import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { warnMember } from "../warnMember"; + +export async function actualWarnCmd( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + authorId: string, + mod: GuildMember, + memberToWarn: GuildMember, + reason: string, + attachments: Attachment[], + contactMethods?: UserNotificationMethod[], +) { + const config = pluginData.config.get(); + const formattedReason = formatReasonWithAttachments(reason, attachments); + + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const priorWarnAmount = await casesPlugin.getCaseTypeAmountForUserId(memberToWarn.id, CaseTypes.Warn); + if (config.warn_notify_enabled && priorWarnAmount >= config.warn_notify_threshold) { + const reply = await waitForButtonConfirm( + context, + { content: config.warn_notify_message.replace("{priorWarnings}", `${priorWarnAmount}`) }, + { confirmText: "Yes", cancelText: "No", restrictToId: authorId }, + ); + if (!reply) { + sendErrorMessage(pluginData, context, "Warn cancelled by moderator"); + return; + } + } + + const warnResult = await warnMember(pluginData, memberToWarn, formattedReason, { + contactMethods, + caseArgs: { + modId: mod.id, + ppId: mod.id !== authorId ? authorId : undefined, + reason: formattedReason, + }, + retryPromptContext: context, + }); + + if (warnResult.status === "failed") { + sendErrorMessage(pluginData, context, "Failed to warn user"); + return; + } + + const messageResultText = warnResult.notifyResult.text ? ` (${warnResult.notifyResult.text})` : ""; + + sendSuccessMessage( + pluginData, + context, + `Warned **${renderUserUsername(memberToWarn.user)}** (Case #${warnResult.case.case_number})${messageResultText}`, + ); +} diff --git a/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts b/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts deleted file mode 100644 index 73a1e2d93..000000000 --- a/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { GuildMember, GuildTextBasedChannel } from "discord.js"; -import { GuildPluginData } from "knub"; -import { hasPermission } from "knub/helpers"; -import { LogType } from "../../../data/LogType"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { DAYS, SECONDS, errorMessage, renderUserUsername, resolveMember, resolveUser } from "../../../utils"; -import { IgnoredEventType, ModActionsPluginType } from "../types"; -import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; -import { ignoreEvent } from "./ignoreEvent"; -import { isBanned } from "./isBanned"; -import { kickMember } from "./kickMember"; -import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs"; - -export async function actualKickMemberCmd( - pluginData: GuildPluginData, - msg, - args: { - user: string; - reason: string; - mod: GuildMember; - notify?: string; - "notify-channel"?: GuildTextBasedChannel; - clean?: boolean; - }, -) { - const user = await resolveUser(pluginData.client, args.user); - if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); - return; - } - - const memberToKick = await resolveMember(pluginData.client, pluginData.guild, user.id); - - if (!memberToKick) { - const banned = await isBanned(pluginData, user.id); - if (banned) { - sendErrorMessage(pluginData, msg.channel, `User is banned`); - } else { - sendErrorMessage(pluginData, msg.channel, `User not found on the server`); - } - - return; - } - - // Make sure we're allowed to kick this member - if (!canActOn(pluginData, msg.member, memberToKick)) { - sendErrorMessage(pluginData, msg.channel, "Cannot kick: insufficient permissions"); - return; - } - - // The moderator who did the action is the message author or, if used, the specified -mod - let mod = msg.member; - if (args.mod) { - if (!(await hasPermission(await pluginData.config.getForMessage(msg), "can_act_as_other"))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); - return; - } - - mod = args.mod; - } - - let contactMethods; - try { - contactMethods = readContactMethodsFromArgs(args); - } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); - return; - } - - const reason = formatReasonWithAttachments(args.reason, msg.attachments); - - const kickResult = await kickMember(pluginData, memberToKick, reason, { - contactMethods, - caseArgs: { - modId: mod.id, - ppId: mod.id !== msg.author.id ? msg.author.id : null, - }, - }); - - if (args.clean) { - pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, memberToKick.id); - ignoreEvent(pluginData, IgnoredEventType.Ban, memberToKick.id); - - try { - await memberToKick.ban({ deleteMessageSeconds: (1 * DAYS) / SECONDS, reason: "kick -clean" }); - } catch { - sendErrorMessage(pluginData, msg.channel, "Failed to ban the user to clean messages (-clean)"); - } - - pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, memberToKick.id); - ignoreEvent(pluginData, IgnoredEventType.Unban, memberToKick.id); - - try { - await pluginData.guild.bans.remove(memberToKick.id, "kick -clean"); - } catch { - sendErrorMessage(pluginData, msg.channel, "Failed to unban the user after banning them (-clean)"); - } - } - - if (kickResult.status === "failed") { - msg.channel.send(errorMessage(`Failed to kick user`)); - return; - } - - // Confirm the action to the moderator - let response = `Kicked **${renderUserUsername(memberToKick.user)}** (Case #${kickResult.case.case_number})`; - - if (kickResult.notifyResult.text) response += ` (${kickResult.notifyResult.text})`; - sendSuccessMessage(pluginData, msg.channel, response); -} diff --git a/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts b/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts deleted file mode 100644 index 5c628c4ee..000000000 --- a/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { GuildMember, GuildTextBasedChannel, Message, User } from "discord.js"; -import humanizeDuration from "humanize-duration"; -import { GuildPluginData } from "knub"; -import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; -import { logger } from "../../../logger"; -import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { UnknownUser, asSingleLine, isDiscordAPIError, renderUserUsername } from "../../../utils"; -import { MutesPlugin } from "../../Mutes/MutesPlugin"; -import { MuteResult } from "../../Mutes/types"; -import { ModActionsPluginType } from "../types"; -import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; -import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs"; - -/** - * The actual function run by both !mute and !forcemute. - * The only difference between the two commands is in target member validation. - */ -export async function actualMuteUserCmd( - pluginData: GuildPluginData, - user: User | UnknownUser, - msg: Message, - args: { - time?: number; - reason?: string; - mod: GuildMember; - notify?: string; - "notify-channel"?: GuildTextBasedChannel; - }, -) { - // The moderator who did the action is the message author or, if used, the specified -mod - let mod: GuildMember = msg.member!; - let pp: User | null = null; - - if (args.mod) { - if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); - return; - } - - mod = args.mod; - pp = msg.author; - } - - const timeUntilUnmute = args.time && humanizeDuration(args.time); - const reason = args.reason ? formatReasonWithAttachments(args.reason, [...msg.attachments.values()]) : undefined; - - let muteResult: MuteResult; - const mutesPlugin = pluginData.getPlugin(MutesPlugin); - - let contactMethods; - try { - contactMethods = readContactMethodsFromArgs(args); - } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); - return; - } - - try { - muteResult = await mutesPlugin.muteUser(user.id, args.time, reason, { - contactMethods, - caseArgs: { - modId: mod.id, - ppId: pp ? pp.id : undefined, - }, - }); - } catch (e) { - if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - sendErrorMessage(pluginData, msg.channel, "Could not mute the user: no mute role set in config"); - } else if (isDiscordAPIError(e) && e.code === 10007) { - sendErrorMessage(pluginData, msg.channel, "Could not mute the user: unknown member"); - } else { - logger.error(`Failed to mute user ${user.id}: ${e.stack}`); - if (user.id == null) { - // FIXME: Debug - // tslint:disable-next-line:no-console - console.trace("[DEBUG] Null user.id for mute"); - } - sendErrorMessage(pluginData, msg.channel, "Could not mute the user"); - } - - return; - } - - // Confirm the action to the moderator - let response: string; - if (args.time) { - if (muteResult.updatedExistingMute) { - response = asSingleLine(` - Updated **${renderUserUsername(user)}**'s - mute to ${timeUntilUnmute} (Case #${muteResult.case.case_number}) - `); - } else { - response = asSingleLine(` - Muted **${renderUserUsername(user)}** - for ${timeUntilUnmute} (Case #${muteResult.case.case_number}) - `); - } - } else { - if (muteResult.updatedExistingMute) { - response = asSingleLine(` - Updated **${renderUserUsername(user)}**'s - mute to indefinite (Case #${muteResult.case.case_number}) - `); - } else { - response = asSingleLine(` - Muted **${renderUserUsername(user)}** - indefinitely (Case #${muteResult.case.case_number}) - `); - } - } - - if (muteResult.notifyResult.text) response += ` (${muteResult.notifyResult.text})`; - sendSuccessMessage(pluginData, msg.channel, response); -} diff --git a/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts b/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts deleted file mode 100644 index d70a219c1..000000000 --- a/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { GuildMember, Message, User } from "discord.js"; -import humanizeDuration from "humanize-duration"; -import { GuildPluginData } from "knub"; -import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; -import { UnknownUser, asSingleLine, renderUserUsername } from "../../../utils"; -import { ModActionsPluginType } from "../types"; -import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; - -export async function actualUnmuteCmd( - pluginData: GuildPluginData, - user: User | UnknownUser, - msg: Message, - args: { time?: number; reason?: string; mod?: GuildMember }, -) { - // The moderator who did the action is the message author or, if used, the specified -mod - let mod = msg.author; - let pp: User | null = null; - - if (args.mod) { - if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); - return; - } - - mod = args.mod.user; - pp = msg.author; - } - - const reason = args.reason ? formatReasonWithAttachments(args.reason, [...msg.attachments.values()]) : undefined; - - const mutesPlugin = pluginData.getPlugin(MutesPlugin); - const result = await mutesPlugin.unmuteUser(user.id, args.time, { - modId: mod.id, - ppId: pp ? pp.id : undefined, - reason, - }); - - if (!result) { - sendErrorMessage(pluginData, msg.channel, "User is not muted!"); - return; - } - - // Confirm the action to the moderator - if (args.time) { - const timeUntilUnmute = args.time && humanizeDuration(args.time); - sendSuccessMessage( - pluginData, - msg.channel, - asSingleLine(` - Unmuting **${renderUserUsername(user)}** - in ${timeUntilUnmute} (Case #${result.case.case_number}) - `), - ); - } else { - sendSuccessMessage( - pluginData, - msg.channel, - asSingleLine(` - Unmuted **${renderUserUsername(user)}** - (Case #${result.case.case_number}) - `), - ); - } -} diff --git a/backend/src/plugins/ModActions/functions/updateCase.ts b/backend/src/plugins/ModActions/functions/updateCase.ts index c19c9d065..d715e2637 100644 --- a/backend/src/plugins/ModActions/functions/updateCase.ts +++ b/backend/src/plugins/ModActions/functions/updateCase.ts @@ -1,44 +1,53 @@ -import { Message } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, TextBasedChannel, User } from "discord.js"; +import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; import { Case } from "../../../data/entities/Case"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { ModActionsPluginType } from "../types"; import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; -export async function updateCase(pluginData, msg: Message, args) { - let theCase: Case | undefined; - if (args.caseNumber != null) { - theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber); +export async function updateCase( + pluginData: GuildPluginData, + context: TextBasedChannel | ChatInputCommandInteraction, + author: User, + caseNumber?: number, + note?: string, + attachments: Attachment[] = [], +) { + let theCase: Case | null; + if (caseNumber != null) { + theCase = await pluginData.state.cases.findByCaseNumber(caseNumber); } else { - theCase = await pluginData.state.cases.findLatestByModId(msg.author.id); + theCase = await pluginData.state.cases.findLatestByModId(author.id); } if (!theCase) { - sendErrorMessage(pluginData, msg.channel, "Case not found"); + sendErrorMessage(pluginData, context, "Case not found"); return; } - if (!args.note && msg.attachments.size === 0) { - sendErrorMessage(pluginData, msg.channel, "Text or attachment required"); + if (!note && attachments.length === 0) { + sendErrorMessage(pluginData, context, "Text or attachment required"); return; } - const note = formatReasonWithAttachments(args.note, [...msg.attachments.values()]); + const formattedNote = formatReasonWithAttachments(note ?? "", attachments); const casesPlugin = pluginData.getPlugin(CasesPlugin); await casesPlugin.createCaseNote({ caseId: theCase.id, - modId: msg.author.id, - body: note, + modId: author.id, + body: formattedNote, }); pluginData.getPlugin(LogsPlugin).logCaseUpdate({ - mod: msg.author, + mod: author, caseNumber: theCase.case_number, caseType: CaseTypes[theCase.type], - note, + note: formattedNote, }); - sendSuccessMessage(pluginData, msg.channel, `Case \`#${theCase.case_number}\` updated`); + sendSuccessMessage(pluginData, context, `Case \`#${theCase.case_number}\` updated`); } diff --git a/backend/src/plugins/ModActions/functions/warnMember.ts b/backend/src/plugins/ModActions/functions/warnMember.ts index 9bc0fda9d..8f58f915b 100644 --- a/backend/src/plugins/ModActions/functions/warnMember.ts +++ b/backend/src/plugins/ModActions/functions/warnMember.ts @@ -1,6 +1,7 @@ import { GuildMember, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; +import { isContextInteraction } from "../../../pluginUtils"; import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { UserNotificationResult, createUserNotificationError, notifyUser, resolveUser, ucfirst } from "../../../utils"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; @@ -39,20 +40,23 @@ export async function warnMember( } if (!notifyResult.success) { - if (warnOptions.retryPromptChannel && pluginData.guild.channels.resolve(warnOptions.retryPromptChannel.id)) { - const reply = await waitForButtonConfirm( - warnOptions.retryPromptChannel, - { content: "Failed to message the user. Log the warning anyway?" }, - { confirmText: "Yes", cancelText: "No", restrictToId: warnOptions.caseArgs?.modId }, - ); + const contextIsChannel = warnOptions.retryPromptContext && !isContextInteraction(warnOptions.retryPromptContext); + const isValidChannel = contextIsChannel && pluginData.guild.channels.resolve(warnOptions.retryPromptContext!.id); - if (!reply) { - return { - status: "failed", - error: "Failed to message user", - }; - } - } else { + if (!warnOptions.retryPromptContext || !isValidChannel) { + return { + status: "failed", + error: "Failed to message user", + }; + } + + const reply = await waitForButtonConfirm( + warnOptions.retryPromptContext, + { content: "Failed to message the user. Log the warning anyway?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: warnOptions.caseArgs?.modId }, + ); + + if (!reply) { return { status: "failed", error: "Failed to message user", diff --git a/backend/src/plugins/ModActions/types.ts b/backend/src/plugins/ModActions/types.ts index c6c1fe703..5a61c3423 100644 --- a/backend/src/plugins/ModActions/types.ts +++ b/backend/src/plugins/ModActions/types.ts @@ -1,4 +1,4 @@ -import { GuildTextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js"; import { EventEmitter } from "events"; import * as t from "io-ts"; import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, guildPluginSlashGroup } from "knub"; @@ -127,7 +127,7 @@ export type WarnMemberNotifyRetryCallback = () => boolean | Promise; export interface WarnOptions { caseArgs?: Partial | null; contactMethods?: UserNotificationMethod[] | null; - retryPromptChannel?: GuildTextBasedChannel | null; + retryPromptContext?: TextBasedChannel | ChatInputCommandInteraction | null; isAutomodAction?: boolean; } diff --git a/backend/src/utils/createPaginatedMessage.ts b/backend/src/utils/createPaginatedMessage.ts index 18e8f3d04..d10754993 100644 --- a/backend/src/utils/createPaginatedMessage.ts +++ b/backend/src/utils/createPaginatedMessage.ts @@ -1,4 +1,5 @@ import { + ChatInputCommandInteraction, Client, Message, MessageCreateOptions, @@ -9,6 +10,7 @@ import { TextBasedChannel, User, } from "discord.js"; +import { sendContextResponse } from "../pluginUtils"; import { MINUTES, noop } from "../utils"; import { Awaitable } from "./typeUtils"; import Timeout = NodeJS.Timeout; @@ -27,14 +29,14 @@ const defaultOpts: PaginateMessageOpts = { export async function createPaginatedMessage( client: Client, - channel: TextBasedChannel | User, + context: TextBasedChannel | User | ChatInputCommandInteraction, totalPages: number, loadPageFn: LoadPageFn, opts: Partial = {}, ): Promise { const fullOpts = { ...defaultOpts, ...opts } as PaginateMessageOpts; const firstPageContent = await loadPageFn(1); - const message = await channel.send(firstPageContent); + const message = await sendContextResponse(context, firstPageContent); let page = 1; let pageLoadId = 0; // Used to avoid race conditions when rapidly switching pages diff --git a/backend/src/utils/multipleSlashOptions.ts b/backend/src/utils/multipleSlashOptions.ts new file mode 100644 index 000000000..9cdd2ecaa --- /dev/null +++ b/backend/src/utils/multipleSlashOptions.ts @@ -0,0 +1,20 @@ +import { AttachmentSlashCommandOption, slashOptions } from "knub"; + +type AttachmentSlashOptions = Omit; + +export function generateAttachmentSlashOptions(amount: number, options: AttachmentSlashOptions) { + return new Array(amount).fill(0).map((_, i) => { + return slashOptions.attachment({ + name: amount > 1 ? `${options.name}${i + 1}` : options.name, + description: options.description, + required: options.required ?? false, + }); + }); +} + +export function retrieveMultipleOptions(amount: number, options: any, name: string) { + return new Array(amount) + .fill(0) + .map((_, i) => options[amount > 1 ? `${name}${i + 1}` : name]) + .filter((a) => a); +} diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index 261da4da3..64cab7384 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -2,22 +2,26 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, - GuildTextBasedChannel, + ChatInputCommandInteraction, MessageActionRowComponentBuilder, MessageComponentInteraction, MessageCreateOptions, + TextBasedChannel, + User, } from "discord.js"; import moment from "moment"; import { v4 as uuidv4 } from "uuid"; +import { isContextInteraction } from "../pluginUtils"; import { noop } from "../utils"; export async function waitForButtonConfirm( - channel: GuildTextBasedChannel, + context: TextBasedChannel | User | ChatInputCommandInteraction, toPost: MessageCreateOptions, options?: WaitForOptions, ): Promise { return new Promise(async (resolve) => { - const idMod = `${channel.guild.id}-${moment.utc().valueOf()}`; + const contextIsInteraction = isContextInteraction(context); + const idMod = `${context.id}-${moment.utc().valueOf()}`; const row = new ActionRowBuilder().addComponents([ new ButtonBuilder() .setStyle(ButtonStyle.Success) @@ -29,7 +33,9 @@ export async function waitForButtonConfirm( .setLabel(options?.cancelText || "Cancel") .setCustomId(`cancelButton:${idMod}:${uuidv4()}`), ]); - const message = await channel.send({ ...toPost, components: [row] }); + const sendMethod = contextIsInteraction ? (context.replied ? "followUp" : "reply") : "send"; + const extraParameters = contextIsInteraction ? { fetchReply: true } : {}; + const message = await context[sendMethod]({ ...toPost, components: [row], ...extraParameters }); const collector = message.createMessageComponentCollector({ time: 10000 }); From 592d037148e2f4bdad35996b0505541c47ea63fe Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Fri, 16 Feb 2024 11:51:58 +0100 Subject: [PATCH 12/29] Added Discord attachment link reaction, fixed emoji configuration and moved util functions --- backend/src/index.ts | 20 ++- backend/src/pluginUtils.ts | 136 ++++------------ .../commands/DisableAutoReactionsCmd.ts | 6 +- .../commands/NewAutoReactionsCmd.ts | 23 +-- backend/src/plugins/Automod/actions/ban.ts | 1 + backend/src/plugins/Automod/actions/kick.ts | 2 +- backend/src/plugins/Automod/actions/mute.ts | 1 + backend/src/plugins/Automod/actions/warn.ts | 2 +- .../Automod/commands/AntiraidClearCmd.ts | 4 +- .../Automod/commands/SetAntiraidCmd.ts | 6 +- .../plugins/BotControl/BotControlPlugin.ts | 5 +- .../commands/AddDashboardUserCmd.ts | 16 +- .../commands/AddServerFromInviteCmd.ts | 14 +- .../BotControl/commands/AllowServerCmd.ts | 11 +- .../BotControl/commands/ChannelToServerCmd.ts | 5 +- .../BotControl/commands/DisallowServerCmd.ts | 7 +- .../BotControl/commands/EligibleCmd.ts | 8 +- .../BotControl/commands/LeaveServerCmd.ts | 9 +- .../commands/ListDashboardPermsCmd.ts | 22 +-- .../commands/ListDashboardUsersCmd.ts | 17 +- .../commands/RateLimitPerformanceCmd.ts | 5 +- .../commands/ReloadGlobalPluginsCmd.ts | 5 +- .../BotControl/commands/ReloadServerCmd.ts | 9 +- .../commands/RemoveDashboardUserCmd.ts | 16 +- .../commands/ArchiveChannelCmd.ts | 7 +- backend/src/plugins/Common/CommonPlugin.ts | 146 ++++++++++++++++++ .../src/plugins/Common/functions/getEmoji.ts | 10 ++ backend/src/plugins/Common/types.ts | 13 ++ .../src/plugins/ContextMenus/actions/ban.ts | 2 +- .../src/plugins/ContextMenus/actions/mute.ts | 2 +- .../src/plugins/ContextMenus/actions/warn.ts | 2 +- .../Counters/commands/AddCounterCmd.ts | 22 +-- .../Counters/commands/CountersListCmd.ts | 4 +- .../commands/ResetAllCounterValuesCmd.ts | 16 +- .../Counters/commands/ResetCounterCmd.ts | 18 +-- .../Counters/commands/SetCounterCmd.ts | 24 +-- .../Counters/commands/ViewCounterCmd.ts | 18 +-- .../CustomEvents/functions/runEvent.ts | 4 +- .../plugins/LocateUser/commands/FollowCmd.ts | 34 ++-- .../LocateUser/commands/ListFollowCmd.ts | 8 +- .../plugins/LocateUser/utils/moveMember.ts | 10 +- .../src/plugins/LocateUser/utils/sendWhere.ts | 4 +- .../MessageSaver/commands/SaveMessagesToDB.ts | 15 +- .../MessageSaver/commands/SavePinsToDB.ts | 15 +- .../plugins/ModActions/ModActionsPlugin.ts | 23 ++- .../commands/addcase/AddCaseMsgCmd.ts | 11 +- .../commands/addcase/AddCaseSlashCmd.ts | 7 +- .../ModActions/commands/ban/BanMsgCmd.ts | 11 +- .../ModActions/commands/ban/BanSlashCmd.ts | 15 +- .../ModActions/commands/case/CaseMsgCmd.ts | 2 +- .../commands/cases/CasesModMsgCmd.ts | 2 +- .../commands/cases/CasesUserMsgCmd.ts | 6 +- .../commands/deletecase/DeleteCaseMsgCmd.ts | 2 +- .../commands/forceban/ForceBanMsgCmd.ts | 13 +- .../commands/forceban/ForceBanSlashCmd.ts | 13 +- .../commands/forcemute/ForceMuteMsgCmd.ts | 13 +- .../commands/forcemute/ForceMuteSlashCmd.ts | 15 +- .../commands/forceunmute/ForceUnmuteMsgCmd.ts | 13 +- .../forceunmute/ForceUnmuteSlashCmd.ts | 13 +- .../commands/hidecase/HideCaseMsgCmd.ts | 2 +- .../ModActions/commands/kick/KickMsgCmd.ts | 10 +- .../ModActions/commands/kick/KickSlashCmd.ts | 13 +- .../commands/massban/MassBanMsgCmd.ts | 2 +- .../commands/massmute/MassMuteMsgCmd.ts | 2 +- .../commands/massunban/MassUnbanMsgCmd.ts | 2 +- .../ModActions/commands/mute/MuteMsgCmd.ts | 25 ++- .../ModActions/commands/mute/MuteSlashCmd.ts | 29 ++-- .../ModActions/commands/note/NoteMsgCmd.ts | 8 +- .../ModActions/commands/note/NoteSlashCmd.ts | 6 +- .../ModActions/commands/unban/UnbanMsgCmd.ts | 9 +- .../commands/unban/UnbanSlashCmd.ts | 11 +- .../commands/unhidecase/UnhideCaseMsgCmd.ts | 2 +- .../commands/unmute/UnmuteMsgCmd.ts | 25 ++- .../commands/unmute/UnmuteSlashCmd.ts | 29 ++-- .../commands/update/UpdateMsgCmd.ts | 2 +- .../ModActions/commands/warn/WarnMsgCmd.ts | 15 +- .../ModActions/commands/warn/WarnSlashCmd.ts | 19 ++- .../actualCommands/actualAddCaseCmd.ts | 26 +++- .../functions/actualCommands/actualBanCmd.ts | 54 ++++--- .../functions/actualCommands/actualCaseCmd.ts | 9 +- .../actualCommands/actualCasesCmd.ts | 15 +- .../actualCommands/actualDeleteCaseCmd.ts | 24 +-- .../actualCommands/actualForceBanCmd.ts | 24 ++- .../actualCommands/actualHideCaseCmd.ts | 19 +-- .../functions/actualCommands/actualKickCmd.ts | 35 +++-- .../actualCommands/actualMassBanCmd.ts | 70 +++++---- .../actualCommands/actualMassMuteCmd.ts | 56 +++---- .../actualCommands/actualMassUnbanCmd.ts | 52 ++++--- .../functions/actualCommands/actualMuteCmd.ts | 34 ++-- .../functions/actualCommands/actualNoteCmd.ts | 32 ++-- .../actualCommands/actualUnbanCmd.ts | 21 ++- .../actualCommands/actualUnhideCaseCmd.ts | 16 +- .../actualCommands/actualUnmuteCmd.ts | 28 ++-- .../functions/actualCommands/actualWarnCmd.ts | 33 ++-- .../functions/attachmentLinkReaction.ts | 51 ++++++ .../plugins/ModActions/functions/banUserId.ts | 7 +- .../ModActions/functions/clearTempban.ts | 9 +- .../functions/formatReasonForAttachments.ts | 36 +++++ .../functions/formatReasonWithAttachments.ts | 6 - .../ModActions/functions/kickMember.ts | 5 +- .../ModActions/functions/updateCase.ts | 20 +-- .../ModActions/functions/warnMember.ts | 11 +- backend/src/plugins/ModActions/types.ts | 8 +- .../Mutes/commands/ClearBannedMutesCmd.ts | 4 +- .../plugins/Mutes/commands/ClearMutesCmd.ts | 17 +- .../commands/ClearMutesWithoutRoleCmd.ts | 6 +- .../src/plugins/Mutes/functions/muteUser.ts | 3 +- .../plugins/NameHistory/commands/NamesCmd.ts | 4 +- .../commands/PingableRoleDisableCmd.ts | 14 +- .../commands/PingableRoleEnableCmd.ts | 18 +-- backend/src/plugins/Post/commands/EditCmd.ts | 8 +- .../src/plugins/Post/commands/EditEmbedCmd.ts | 12 +- .../src/plugins/Post/commands/PostEmbedCmd.ts | 10 +- .../Post/commands/ScheduledPostsDeleteCmd.ts | 6 +- .../Post/commands/ScheduledPostsShowCmd.ts | 4 +- .../src/plugins/Post/util/actualPostCmd.ts | 41 +++-- .../commands/ClearReactionRolesCmd.ts | 8 +- .../commands/InitReactionRolesCmd.ts | 34 ++-- .../commands/RefreshReactionRolesCmd.ts | 6 +- .../plugins/Reminders/commands/RemindCmd.ts | 14 +- .../Reminders/commands/RemindersCmd.ts | 4 +- .../Reminders/commands/RemindersDeleteCmd.ts | 6 +- .../RoleButtons/commands/resetButtons.ts | 8 +- .../src/plugins/Roles/commands/AddRoleCmd.ts | 23 +-- .../plugins/Roles/commands/MassAddRoleCmd.ts | 17 +- .../Roles/commands/MassRemoveRoleCmd.ts | 17 +- .../plugins/Roles/commands/RemoveRoleCmd.ts | 23 +-- .../SelfGrantableRoles/commands/RoleAddCmd.ts | 26 ++-- .../commands/RoleRemoveCmd.ts | 50 +++--- .../Slowmode/commands/SlowmodeClearCmd.ts | 30 ++-- .../Slowmode/commands/SlowmodeSetCmd.ts | 51 +++--- .../Slowmode/util/actualDisableSlowmodeCmd.ts | 25 ++- .../Spam/util/logAndDetectMessageSpam.ts | 5 +- .../Spam/util/logAndDetectOtherSpam.ts | 5 +- .../Starboard/commands/MigratePinsCmd.ts | 14 +- .../src/plugins/Tags/commands/TagCreateCmd.ts | 6 +- .../src/plugins/Tags/commands/TagDeleteCmd.ts | 6 +- .../src/plugins/Tags/commands/TagEvalCmd.ts | 6 +- .../src/plugins/Tags/commands/TagSourceCmd.ts | 9 +- .../TimeAndDate/commands/ResetTimezoneCmd.ts | 10 +- .../TimeAndDate/commands/SetTimezoneCmd.ts | 9 +- backend/src/plugins/Utility/UtilityPlugin.ts | 9 +- .../src/plugins/Utility/commands/AvatarCmd.ts | 4 +- .../Utility/commands/ChannelInfoCmd.ts | 4 +- .../src/plugins/Utility/commands/CleanCmd.ts | 57 ++++--- .../plugins/Utility/commands/ContextCmd.ts | 8 +- .../plugins/Utility/commands/EmojiInfoCmd.ts | 6 +- .../src/plugins/Utility/commands/InfoCmd.ts | 32 ++-- .../plugins/Utility/commands/InviteInfoCmd.ts | 4 +- .../src/plugins/Utility/commands/JumboCmd.ts | 6 +- .../Utility/commands/MessageInfoCmd.ts | 13 +- .../plugins/Utility/commands/NicknameCmd.ts | 14 +- .../Utility/commands/NicknameResetCmd.ts | 5 +- .../plugins/Utility/commands/RoleInfoCmd.ts | 2 +- .../src/plugins/Utility/commands/RolesCmd.ts | 4 +- .../plugins/Utility/commands/ServerInfoCmd.ts | 6 +- .../Utility/commands/SnowflakeInfoCmd.ts | 4 +- .../src/plugins/Utility/commands/SourceCmd.ts | 7 +- .../plugins/Utility/commands/UserInfoCmd.ts | 6 +- .../Utility/commands/VcdisconnectCmd.ts | 17 +- .../src/plugins/Utility/commands/VcmoveCmd.ts | 67 ++++---- .../Utility/functions/getChannelInfoEmbed.ts | 1 - .../Utility/functions/getMessageInfoEmbed.ts | 4 - .../Utility/functions/getRoleInfoEmbed.ts | 6 +- .../Utility/functions/getServerInfoEmbed.ts | 1 - .../functions/getSnowflakeInfoEmbed.ts | 9 +- .../Utility/functions/getUserInfoEmbed.ts | 4 - backend/src/plugins/Utility/search.ts | 15 +- backend/src/plugins/availablePlugins.ts | 3 + backend/src/utils.ts | 5 +- backend/src/utils/createPaginatedMessage.ts | 3 +- backend/src/utils/waitForInteraction.ts | 18 ++- dashboard/package-lock.json | 6 +- 173 files changed, 1537 insertions(+), 1167 deletions(-) create mode 100644 backend/src/plugins/Common/CommonPlugin.ts create mode 100644 backend/src/plugins/Common/functions/getEmoji.ts create mode 100644 backend/src/plugins/Common/types.ts create mode 100644 backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts create mode 100644 backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts delete mode 100644 backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts diff --git a/backend/src/index.ts b/backend/src/index.ts index 2fe16c115..146b2297f 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -203,7 +203,7 @@ if (env.DEBUG) { } logger.info("Connecting to database"); -connect().then(async (connection) => { +connect().then(async () => { const client = new Client({ partials: [Partials.User, Partials.Channel, Partials.GuildMember, Partials.Message, Partials.Reaction], @@ -315,9 +315,27 @@ connect().then(async (connection) => { if (row) { try { const loaded = loadYamlSafely(row.config); + + if (loaded.success_emoji || loaded.error_emoji) { + const deprecatedKeys = [] as string[]; + const exampleConfig = `plugins:\n common:\n config:\n success_emoji: "👍"\n error_emoji: "👎"`; + + if (loaded.success_emoji) { + deprecatedKeys.push("success_emoji"); + } + + if (loaded.error_emoji) { + deprecatedKeys.push("error_emoji"); + } + + logger.warn(`Deprecated config properties found in "${key}": ${deprecatedKeys.join(", ")}`); + logger.warn(`You can now configure those emojis in the "common" plugin config\n${exampleConfig}`); + } + // Remove deprecated properties some may still have in their config delete loaded.success_emoji; delete loaded.error_emoji; + return loaded; } catch (err) { logger.error(`Error while loading config "${key}": ${err.message}`); diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 9736c9fe8..7f3940922 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -7,8 +7,6 @@ import { GuildMember, Message, MessageCreateOptions, - MessageMentionOptions, - ModalSubmitInteraction, PermissionsBitField, TextBasedChannel, User, @@ -23,10 +21,9 @@ import { PluginOverrideCriteria, helpers, } from "knub"; -import { logger } from "./logger"; import { isStaff } from "./staff"; import { TZeppelinKnub } from "./types"; -import { errorMessage, successMessage, tNullable } from "./utils"; +import { tNullable } from "./utils"; import { Tail } from "./utils/typeUtils"; import { StrictValidationError, parseIoTsSchema } from "./validatorUtils"; @@ -103,121 +100,50 @@ export function makeIoTsConfigParser>(schema: Schema) } export function isContextInteraction( - context: TextBasedChannel | User | ChatInputCommandInteraction, + context: TextBasedChannel | Message | User | ChatInputCommandInteraction, ): context is ChatInputCommandInteraction { return "commandId" in context && !!context.commandId; } -export function sendContextResponse( - context: TextBasedChannel | User | ChatInputCommandInteraction, +export function isContextMessage( + context: TextBasedChannel | Message | User | ChatInputCommandInteraction, +): context is Message { + return "content" in context || "embeds" in context; +} + +export async function getContextChannel( + context: TextBasedChannel | Message | User | ChatInputCommandInteraction, +): Promise { + if (isContextInteraction(context)) { + // context is ChatInputCommandInteraction + return context.channel!; + } else if ("username" in context) { + // context is User + return await (context as User).createDM(); + } else if ("send" in context) { + // context is TextBaseChannel + return context as TextBasedChannel; + } else { + // context is Message + return context.channel; + } +} + +export async function sendContextResponse( + context: TextBasedChannel | Message | User | ChatInputCommandInteraction, response: string | Omit, ): Promise { if (isContextInteraction(context)) { const options = { ...(typeof response === "string" ? { content: response } : response), fetchReply: true }; return (context.replied ? context.followUp(options) : context.reply(options)) as Promise; - } else { + } else if ("send" in context) { return context.send(response); + } else { + return (await getContextChannel(context)).send(response); } } -export async function sendSuccessMessage( - pluginData: AnyPluginData, - context: TextBasedChannel | User | ChatInputCommandInteraction, - body: string, - allowedMentions?: MessageMentionOptions, - responseInteraction?: ModalSubmitInteraction, - ephemeral = true, -): Promise { - const emoji = pluginData.fullConfig.success_emoji || undefined; - const formattedBody = successMessage(body, emoji); - const content: MessageCreateOptions = allowedMentions - ? { content: formattedBody, allowedMentions } - : { content: formattedBody }; - - if (responseInteraction) { - await responseInteraction - .editReply({ content: formattedBody, embeds: [], components: [] }) - .catch((err) => logger.error(`Interaction reply failed: ${err}`)); - - return; - } - - if (!isContextInteraction(context)) { - // noinspection TypeScriptValidateJSTypes - return context - .send({ ...content }) // Force line break - .catch((err) => { - const channelInfo = "guild" in context ? `${context.id} (${context.guild.id})` : context.id; - logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); - - return undefined; - }); - } - - const replyMethod = context.replied ? "followUp" : "reply"; - - return context[replyMethod]({ - content: formattedBody, - embeds: [], - components: [], - fetchReply: true, - ephemeral, - }).catch((err) => { - logger.error(`Context reply failed: ${err}`); - - return undefined; - }) as Promise; -} - -export async function sendErrorMessage( - pluginData: AnyPluginData, - context: TextBasedChannel | User | ChatInputCommandInteraction, - body: string, - allowedMentions?: MessageMentionOptions, - responseInteraction?: ModalSubmitInteraction, - ephemeral = false, -): Promise { - const emoji = pluginData.fullConfig.error_emoji || undefined; - const formattedBody = errorMessage(body, emoji); - const content: MessageCreateOptions = allowedMentions - ? { content: formattedBody, allowedMentions } - : { content: formattedBody }; - - if (responseInteraction) { - await responseInteraction - .editReply({ content: formattedBody, embeds: [], components: [] }) - .catch((err) => logger.error(`Interaction reply failed: ${err}`)); - - return; - } - - if (!isContextInteraction(context)) { - // noinspection TypeScriptValidateJSTypes - return context - .send({ ...content }) // Force line break - .catch((err) => { - const channelInfo = "guild" in context ? `${context.id} (${context.guild.id})` : context.id; - logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); - return undefined; - }); - } - - const replyMethod = context.replied ? "followUp" : "reply"; - - return context[replyMethod]({ - content: formattedBody, - embeds: [], - components: [], - fetchReply: true, - ephemeral, - }).catch((err) => { - logger.error(`Context reply failed: ${err}`); - - return undefined; - }) as Promise; -} - export function getBaseUrl(pluginData: AnyPluginData) { const knub = pluginData.getKnubInstance() as TZeppelinKnub; // @ts-expect-error diff --git a/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts b/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts index 20f749ea9..ec3417d4f 100644 --- a/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts +++ b/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { autoReactionsCmd } from "../types"; export const DisableAutoReactionsCmd = autoReactionsCmd({ @@ -14,12 +14,12 @@ export const DisableAutoReactionsCmd = autoReactionsCmd({ async run({ message: msg, args, pluginData }) { const autoReaction = await pluginData.state.autoReactions.getForChannel(args.channelId); if (!autoReaction) { - sendErrorMessage(pluginData, msg.channel, `Auto-reactions aren't enabled in <#${args.channelId}>`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Auto-reactions aren't enabled in <#${args.channelId}>`); return; } await pluginData.state.autoReactions.removeFromChannel(args.channelId); pluginData.state.cache.delete(args.channelId); - sendSuccessMessage(pluginData, msg.channel, `Auto-reactions disabled in <#${args.channelId}>`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Auto-reactions disabled in <#${args.channelId}>`); }, }); diff --git a/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts b/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts index a64cb95cd..250df4f0a 100644 --- a/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts +++ b/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts @@ -1,10 +1,10 @@ import { PermissionsBitField } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; import { readChannelPermissions } from "../../../utils/readChannelPermissions"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { autoReactionsCmd } from "../types"; const requiredPermissions = readChannelPermissions | PermissionsBitField.Flags.AddReactions; @@ -25,17 +25,20 @@ export const NewAutoReactionsCmd = autoReactionsCmd({ const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingChannelPermissions(me, args.channel, requiredPermissions); if (missingPermissions) { - sendErrorMessage( - pluginData, - msg.channel, - `Cannot set auto-reactions for that channel. ${missingPermissionError(missingPermissions)}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + msg, + `Cannot set auto-reactions for that channel. ${missingPermissionError(missingPermissions)}`, + ); return; } for (const reaction of args.reactions) { if (!isEmoji(reaction)) { - sendErrorMessage(pluginData, msg.channel, "One or more of the specified reactions were invalid!"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "One or more of the specified reactions were invalid!"); return; } @@ -45,7 +48,9 @@ export const NewAutoReactionsCmd = autoReactionsCmd({ if (customEmojiMatch) { // Custom emoji if (!canUseEmoji(pluginData.client, customEmojiMatch[2])) { - sendErrorMessage(pluginData, msg.channel, "I can only use regular emojis and custom emojis from this server"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "I can only use regular emojis and custom emojis from this server"); return; } @@ -60,6 +65,6 @@ export const NewAutoReactionsCmd = autoReactionsCmd({ await pluginData.state.autoReactions.set(args.channel.id, finalReactions); pluginData.state.cache.delete(args.channel.id); - sendSuccessMessage(pluginData, msg.channel, `Auto-reactions set for <#${args.channel.id}>`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Auto-reactions set for <#${args.channel.id}>`); }, }); diff --git a/backend/src/plugins/Automod/actions/ban.ts b/backend/src/plugins/Automod/actions/ban.ts index 9cd0fd863..1e63af61c 100644 --- a/backend/src/plugins/Automod/actions/ban.ts +++ b/backend/src/plugins/Automod/actions/ban.ts @@ -42,6 +42,7 @@ export const BanAction = automodAction({ await modActions.banUserId( userId, reason, + reason, { contactMethods, caseArgs, diff --git a/backend/src/plugins/Automod/actions/kick.ts b/backend/src/plugins/Automod/actions/kick.ts index 9e6a792d6..af95c8031 100644 --- a/backend/src/plugins/Automod/actions/kick.ts +++ b/backend/src/plugins/Automod/actions/kick.ts @@ -37,7 +37,7 @@ export const KickAction = automodAction({ const modActions = pluginData.getPlugin(ModActionsPlugin); for (const member of membersToKick) { if (!member) continue; - await modActions.kickMember(member, reason, { contactMethods, caseArgs, isAutomodAction: true }); + await modActions.kickMember(member, reason, reason, { contactMethods, caseArgs, isAutomodAction: true }); } }, }); diff --git a/backend/src/plugins/Automod/actions/mute.ts b/backend/src/plugins/Automod/actions/mute.ts index 3219c712e..0afed092e 100644 --- a/backend/src/plugins/Automod/actions/mute.ts +++ b/backend/src/plugins/Automod/actions/mute.ts @@ -48,6 +48,7 @@ export const MuteAction = automodAction({ userId, duration, reason, + reason, { contactMethods, caseArgs, isAutomodAction: true }, rolesToRemove, rolesToRestore, diff --git a/backend/src/plugins/Automod/actions/warn.ts b/backend/src/plugins/Automod/actions/warn.ts index 59135cb2c..ff79f64f2 100644 --- a/backend/src/plugins/Automod/actions/warn.ts +++ b/backend/src/plugins/Automod/actions/warn.ts @@ -37,7 +37,7 @@ export const WarnAction = automodAction({ const modActions = pluginData.getPlugin(ModActionsPlugin); for (const member of membersToWarn) { if (!member) continue; - await modActions.warnMember(member, reason, { contactMethods, caseArgs, isAutomodAction: true }); + await modActions.warnMember(member, reason, reason, { contactMethods, caseArgs, isAutomodAction: true }); } }, }); diff --git a/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts b/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts index fd31d4b14..983a1f994 100644 --- a/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts +++ b/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts @@ -1,5 +1,5 @@ import { guildPluginMessageCommand } from "knub"; -import { sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { setAntiraidLevel } from "../functions/setAntiraidLevel"; import { AutomodPluginType } from "../types"; @@ -9,6 +9,6 @@ export const AntiraidClearCmd = guildPluginMessageCommand()({ async run({ pluginData, message }) { await setAntiraidLevel(pluginData, null, message.author); - sendSuccessMessage(pluginData, message.channel, "Anti-raid turned **off**"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(message, "Anti-raid turned **off**"); }, }); diff --git a/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts b/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts index 159ff576e..91cbacd65 100644 --- a/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts +++ b/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts @@ -1,6 +1,6 @@ import { guildPluginMessageCommand } from "knub"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { setAntiraidLevel } from "../functions/setAntiraidLevel"; import { AutomodPluginType } from "../types"; @@ -15,11 +15,11 @@ export const SetAntiraidCmd = guildPluginMessageCommand()({ async run({ pluginData, message, args }) { const config = pluginData.config.get(); if (!config.antiraid_levels.includes(args.level)) { - sendErrorMessage(pluginData, message.channel, "Unknown anti-raid level"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown anti-raid level"); return; } await setAntiraidLevel(pluginData, args.level, message.author); - sendSuccessMessage(pluginData, message.channel, `Anti-raid level set to **${args.level}**`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(message, `Anti-raid level set to **${args.level}**`); }, }); diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts index 76478ca26..e7d573fcf 100644 --- a/backend/src/plugins/BotControl/BotControlPlugin.ts +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -3,7 +3,8 @@ import { AllowedGuilds } from "../../data/AllowedGuilds"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; import { Configs } from "../../data/Configs"; import { GuildArchives } from "../../data/GuildArchives"; -import { makeIoTsConfigParser, sendSuccessMessage } from "../../pluginUtils"; +import { makeIoTsConfigParser } from "../../pluginUtils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; import { getActiveReload, resetActiveReload } from "./activeReload"; import { AddDashboardUserCmd } from "./commands/AddDashboardUserCmd"; @@ -77,7 +78,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin()({ if (guild) { const channel = guild.channels.cache.get(channelId as Snowflake); if (channel instanceof TextChannel) { - sendSuccessMessage(pluginData, channel, "Global plugins reloaded!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(channel, "Global plugins reloaded!"); } } } diff --git a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts index c12595946..1e88d679a 100644 --- a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts @@ -1,7 +1,8 @@ import { ApiPermissions } from "@shared/apiPermissions"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { isStaffPreFilter } from "../../../pluginUtils"; import { renderUserUsername } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const AddDashboardUserCmd = botControlCmd({ @@ -19,7 +20,7 @@ export const AddDashboardUserCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is not using Zeppelin"); return; } @@ -36,10 +37,11 @@ export const AddDashboardUserCmd = botControlCmd({ } const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUserUsername(user)}**, \`${user.id}\`)`); - sendSuccessMessage( - pluginData, - msg.channel, - `The following users were given dashboard access for **${guild.name}**:\n\n${userNameList}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `The following users were given dashboard access for **${guild.name}**:\n\n${userNameList}`, + ); }, }); diff --git a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts index cbf53bf4f..97944c1de 100644 --- a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts @@ -1,8 +1,8 @@ import { ApiPermissions } from "@shared/apiPermissions"; import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { DBDateFormat, isGuildInvite, resolveInvite } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { isEligible } from "../functions/isEligible"; import { botControlCmd } from "../types"; @@ -18,19 +18,21 @@ export const AddServerFromInviteCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const invite = await resolveInvite(pluginData.client, args.inviteCode, true); if (!invite || !isGuildInvite(invite)) { - sendErrorMessage(pluginData, msg.channel, "Could not resolve invite"); // :D + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Could not resolve invite"); // :D return; } const existing = await pluginData.state.allowedGuilds.find(invite.guild.id); if (existing) { - sendErrorMessage(pluginData, msg.channel, "Server is already allowed!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is already allowed!"); return; } const { result, explanation } = await isEligible(pluginData, args.user, invite); if (!result) { - sendErrorMessage(pluginData, msg.channel, `Could not add server because it's not eligible: ${explanation}`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Could not add server because it's not eligible: ${explanation}`); return; } @@ -51,6 +53,8 @@ export const AddServerFromInviteCmd = botControlCmd({ ); } - sendSuccessMessage(pluginData, msg.channel, "Server was eligible and is now allowed to use Zeppelin!"); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, "Server was eligible and is now allowed to use Zeppelin!"); }, }); diff --git a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts index ce2c3b0c6..36e2d17a5 100644 --- a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts @@ -1,8 +1,9 @@ import { ApiPermissions } from "@shared/apiPermissions"; import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { isStaffPreFilter } from "../../../pluginUtils"; import { DBDateFormat, isSnowflake } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const AllowServerCmd = botControlCmd({ @@ -20,17 +21,17 @@ export const AllowServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const existing = await pluginData.state.allowedGuilds.find(args.guildId); if (existing) { - sendErrorMessage(pluginData, msg.channel, "Server is already allowed!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is already allowed!"); return; } if (!isSnowflake(args.guildId)) { - sendErrorMessage(pluginData, msg.channel, "Invalid server ID!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid server ID!"); return; } if (args.userId && !isSnowflake(args.userId)) { - sendErrorMessage(pluginData, msg.channel, "Invalid user ID!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid user ID!"); return; } @@ -51,6 +52,6 @@ export const AllowServerCmd = botControlCmd({ ); } - sendSuccessMessage(pluginData, msg.channel, "Server is now allowed to use Zeppelin!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Server is now allowed to use Zeppelin!"); }, }); diff --git a/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts b/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts index 11d3a487c..939077e29 100644 --- a/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts @@ -1,5 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isStaffPreFilter, sendErrorMessage } from "../../../pluginUtils"; +import { isStaffPreFilter } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const ChannelToServerCmd = botControlCmd({ @@ -16,7 +17,7 @@ export const ChannelToServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const channel = pluginData.client.channels.cache.get(args.channelId); if (!channel) { - sendErrorMessage(pluginData, msg.channel, "Channel not found in cache!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Channel not found in cache!"); return; } diff --git a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts index 06bc1b143..69ba0b4ca 100644 --- a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts @@ -1,7 +1,8 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { isStaffPreFilter } from "../../../pluginUtils"; import { noop } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const DisallowServerCmd = botControlCmd({ @@ -18,7 +19,7 @@ export const DisallowServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const existing = await pluginData.state.allowedGuilds.find(args.guildId); if (!existing) { - sendErrorMessage(pluginData, msg.channel, "That server is not allowed in the first place!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "That server is not allowed in the first place!"); return; } @@ -27,6 +28,6 @@ export const DisallowServerCmd = botControlCmd({ .get(args.guildId as Snowflake) ?.leave() .catch(noop); - sendSuccessMessage(pluginData, msg.channel, "Server removed!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Server removed!"); }, }); diff --git a/backend/src/plugins/BotControl/commands/EligibleCmd.ts b/backend/src/plugins/BotControl/commands/EligibleCmd.ts index face42a83..bbcd5c5d0 100644 --- a/backend/src/plugins/BotControl/commands/EligibleCmd.ts +++ b/backend/src/plugins/BotControl/commands/EligibleCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { isGuildInvite, resolveInvite } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { isEligible } from "../functions/isEligible"; import { botControlCmd } from "../types"; @@ -16,17 +16,17 @@ export const EligibleCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const invite = await resolveInvite(pluginData.client, args.inviteCode, true); if (!invite || !isGuildInvite(invite)) { - sendErrorMessage(pluginData, msg.channel, "Could not resolve invite"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Could not resolve invite"); return; } const { result, explanation } = await isEligible(pluginData, args.user, invite); if (result) { - sendSuccessMessage(pluginData, msg.channel, `Server is eligible: ${explanation}`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Server is eligible: ${explanation}`); return; } - sendErrorMessage(pluginData, msg.channel, `Server is **NOT** eligible: ${explanation}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Server is **NOT** eligible: ${explanation}`); }, }); diff --git a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts index 14aaf8337..0066a9162 100644 --- a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts @@ -1,6 +1,7 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { isStaffPreFilter } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const LeaveServerCmd = botControlCmd({ @@ -16,7 +17,7 @@ export const LeaveServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { if (!pluginData.client.guilds.cache.has(args.guildId as Snowflake)) { - sendErrorMessage(pluginData, msg.channel, "I am not in that guild"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "I am not in that guild"); return; } @@ -26,10 +27,10 @@ export const LeaveServerCmd = botControlCmd({ try { await pluginData.client.guilds.cache.get(args.guildId as Snowflake)?.leave(); } catch (e) { - sendErrorMessage(pluginData, msg.channel, `Failed to leave guild: ${e.message}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Failed to leave guild: ${e.message}`); return; } - sendSuccessMessage(pluginData, msg.channel, `Left guild **${guildName}**`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Left guild **${guildName}**`); }, }); diff --git a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts index 18c7d0d70..ecab54345 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts @@ -1,8 +1,8 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { AllowedGuild } from "../../../data/entities/AllowedGuild"; import { ApiPermissionAssignment } from "../../../data/entities/ApiPermissionAssignment"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { renderUserUsername, resolveUser } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const ListDashboardPermsCmd = botControlCmd({ @@ -16,7 +16,7 @@ export const ListDashboardPermsCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { if (!args.user && !args.guildId) { - sendErrorMessage(pluginData, msg.channel, "Must specify at least guildId, user, or both."); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Must specify at least guildId, user, or both."); return; } @@ -24,7 +24,7 @@ export const ListDashboardPermsCmd = botControlCmd({ if (args.guildId) { guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is not using Zeppelin"); return; } } @@ -33,7 +33,7 @@ export const ListDashboardPermsCmd = botControlCmd({ if (args.user) { existingUserAssignment = await pluginData.state.apiPermissionAssignments.getByUserId(args.user.id); if (existingUserAssignment.length === 0) { - sendErrorMessage(pluginData, msg.channel, "The user has no assigned permissions."); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "The user has no assigned permissions."); return; } } @@ -54,11 +54,9 @@ export const ListDashboardPermsCmd = botControlCmd({ } if (finalMessage === "") { - sendErrorMessage( - pluginData, - msg.channel, - `The user ${userInfo} has no assigned permissions on the specified server.`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `The user ${userInfo} has no assigned permissions on the specified server.`); return; } // Else display all users that have permissions on the specified guild @@ -67,7 +65,9 @@ export const ListDashboardPermsCmd = botControlCmd({ const existingGuildAssignment = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id); if (existingGuildAssignment.length === 0) { - sendErrorMessage(pluginData, msg.channel, `The server ${guildInfo} has no assigned permissions.`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `The server ${guildInfo} has no assigned permissions.`); return; } @@ -80,6 +80,6 @@ export const ListDashboardPermsCmd = botControlCmd({ } } - await sendSuccessMessage(pluginData, msg.channel, finalMessage.trim(), {}); + await pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, finalMessage.trim(), {}); }, }); diff --git a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts index 36f1432f6..f233e1f7d 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { renderUserUsername, resolveUser } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const ListDashboardUsersCmd = botControlCmd({ @@ -14,7 +14,7 @@ export const ListDashboardUsersCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is not using Zeppelin"); return; } @@ -30,11 +30,12 @@ export const ListDashboardUsersCmd = botControlCmd({ `<@!${user.id}> (**${renderUserUsername(user)}**, \`${user.id}\`): ${permission.permissions.join(", ")}`, ); - sendSuccessMessage( - pluginData, - msg.channel, - `The following users have dashboard access for **${guild.name}**:\n\n${userNameList.join("\n")}`, - {}, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `The following users have dashboard access for **${guild.name}**:\n\n${userNameList.join("\n")}`, + {}, + ); }, }); diff --git a/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts b/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts index 46d2a0e04..9ac5499dc 100644 --- a/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts +++ b/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts @@ -1,7 +1,8 @@ import moment from "moment-timezone"; import { GuildArchives } from "../../../data/GuildArchives"; -import { getBaseUrl, sendSuccessMessage } from "../../../pluginUtils"; +import { getBaseUrl } from "../../../pluginUtils"; import { getRateLimitStats } from "../../../rateLimitStats"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const RateLimitPerformanceCmd = botControlCmd({ @@ -13,7 +14,7 @@ export const RateLimitPerformanceCmd = botControlCmd({ async run({ pluginData, message: msg }) { const logItems = getRateLimitStats(); if (logItems.length === 0) { - sendSuccessMessage(pluginData, msg.channel, `No rate limits hit`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `No rate limits hit`); return; } diff --git a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts index 4f59f8a20..ea51dc002 100644 --- a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts @@ -1,4 +1,5 @@ -import { isStaffPreFilter, sendErrorMessage } from "../../../pluginUtils"; +import { isStaffPreFilter } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getActiveReload, setActiveReload } from "../activeReload"; import { botControlCmd } from "../types"; @@ -14,7 +15,7 @@ export const ReloadGlobalPluginsCmd = botControlCmd({ const guildId = "guild" in message.channel ? message.channel.guild.id : null; if (!guildId) { - sendErrorMessage(pluginData, message.channel, "This command can only be used in a server"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "This command can only be used in a server"); return; } diff --git a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts index 0440129b1..8481682e7 100644 --- a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts @@ -1,6 +1,7 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { isStaffPreFilter } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const ReloadServerCmd = botControlCmd({ @@ -16,18 +17,18 @@ export const ReloadServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { if (!pluginData.client.guilds.cache.has(args.guildId as Snowflake)) { - sendErrorMessage(pluginData, msg.channel, "I am not in that guild"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "I am not in that guild"); return; } try { await pluginData.getKnubInstance().reloadGuild(args.guildId); } catch (e) { - sendErrorMessage(pluginData, msg.channel, `Failed to reload guild: ${e.message}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Failed to reload guild: ${e.message}`); return; } const guild = await pluginData.client.guilds.fetch(args.guildId as Snowflake); - sendSuccessMessage(pluginData, msg.channel, `Reloaded guild **${guild?.name || "???"}**`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Reloaded guild **${guild?.name || "???"}**`); }, }); diff --git a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts index 3a90683c9..350891a97 100644 --- a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { isStaffPreFilter } from "../../../pluginUtils"; import { renderUserUsername } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const RemoveDashboardUserCmd = botControlCmd({ @@ -18,7 +19,7 @@ export const RemoveDashboardUserCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is not using Zeppelin"); return; } @@ -35,10 +36,11 @@ export const RemoveDashboardUserCmd = botControlCmd({ } const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUserUsername(user)}**, \`${user.id}\`)`); - sendSuccessMessage( - pluginData, - msg.channel, - `The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`, + ); }, }); diff --git a/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts b/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts index 3fc1b72bb..ad9a8f859 100644 --- a/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts +++ b/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts @@ -1,8 +1,9 @@ import { Snowflake } from "discord.js"; import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isOwner, sendErrorMessage } from "../../../pluginUtils"; +import { isOwner } from "../../../pluginUtils"; import { SECONDS, confirm, noop, renderUsername } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { rehostAttachment } from "../rehostAttachment"; import { channelArchiverCmd } from "../types"; @@ -32,12 +33,12 @@ export const ArchiveChannelCmd = channelArchiverCmd({ async run({ message: msg, args, pluginData }) { if (!args["attachment-channel"]) { - const confirmed = await confirm(msg.channel, msg.author.id, { + const confirmed = await confirm(msg, msg.author.id, { content: "No `-attachment-channel` specified. Continue? Attachments will not be available in the log if their message is deleted.", }); if (!confirmed) { - sendErrorMessage(pluginData, msg.channel, "Canceled"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Canceled"); return; } } diff --git a/backend/src/plugins/Common/CommonPlugin.ts b/backend/src/plugins/Common/CommonPlugin.ts new file mode 100644 index 000000000..a8935907f --- /dev/null +++ b/backend/src/plugins/Common/CommonPlugin.ts @@ -0,0 +1,146 @@ +import { + ChatInputCommandInteraction, + Message, + MessageCreateOptions, + MessageMentionOptions, + ModalSubmitInteraction, + TextBasedChannel, + User, +} from "discord.js"; +import { PluginOptions } from "knub"; +import { logger } from "../../logger"; +import { isContextInteraction, makeIoTsConfigParser, sendContextResponse } from "../../pluginUtils"; +import { errorMessage, successMessage } from "../../utils"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { getErrorEmoji, getSuccessEmoji } from "./functions/getEmoji"; +import { CommonPluginType, ConfigSchema } from "./types"; + +const defaultOptions: PluginOptions = { + config: { + success_emoji: "✅", + error_emoji: "❌", + }, +}; + +export const CommonPlugin = zeppelinGuildPlugin()({ + name: "common", + showInDocs: false, + info: { + prettyName: "Common", + }, + + dependencies: () => [], + configParser: makeIoTsConfigParser(ConfigSchema), + defaultOptions, + public: { + getSuccessEmoji(pluginData) { + return () => getSuccessEmoji(pluginData); + }, + + getErrorEmoji(pluginData) { + return () => getErrorEmoji(pluginData); + }, + + sendSuccessMessage(pluginData) { + return async ( + context: TextBasedChannel | Message | User | ChatInputCommandInteraction, + body: string, + allowedMentions?: MessageMentionOptions, + responseInteraction?: ModalSubmitInteraction, + ephemeral = true, + ): Promise => { + const emoji = getSuccessEmoji(pluginData); + const formattedBody = successMessage(body, emoji); + const content: MessageCreateOptions = allowedMentions + ? { content: formattedBody, allowedMentions } + : { content: formattedBody }; + + if (responseInteraction) { + await responseInteraction + .editReply({ content: formattedBody, embeds: [], components: [] }) + .catch((err) => logger.error(`Interaction reply failed: ${err}`)); + + return; + } + + if (!isContextInteraction(context)) { + // noinspection TypeScriptValidateJSTypes + return sendContextResponse(context, { ...content }) // Force line break + .catch((err) => { + const channelInfo = + "guild" in context && context.guild ? `${context.id} (${context.guild.id})` : context.id; + + logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); + + return undefined; + }); + } + + const replyMethod = context.replied ? "followUp" : "reply"; + + return context[replyMethod]({ + content: formattedBody, + embeds: [], + components: [], + fetchReply: true, + ephemeral, + }).catch((err) => { + logger.error(`Context reply failed: ${err}`); + + return undefined; + }) as Promise; + }; + }, + + sendErrorMessage(pluginData) { + return async ( + context: TextBasedChannel | Message | User | ChatInputCommandInteraction, + body: string, + allowedMentions?: MessageMentionOptions, + responseInteraction?: ModalSubmitInteraction, + ephemeral = false, + ): Promise => { + const emoji = getErrorEmoji(pluginData); + const formattedBody = errorMessage(body, emoji); + const content: MessageCreateOptions = allowedMentions + ? { content: formattedBody, allowedMentions } + : { content: formattedBody }; + + if (responseInteraction) { + await responseInteraction + .editReply({ content: formattedBody, embeds: [], components: [] }) + .catch((err) => logger.error(`Interaction reply failed: ${err}`)); + + return; + } + + if (!isContextInteraction(context)) { + // noinspection TypeScriptValidateJSTypes + return sendContextResponse(context, { ...content }) // Force line break + .catch((err) => { + const channelInfo = + "guild" in context && context.guild ? `${context.id} (${context.guild.id})` : context.id; + + logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); + + return undefined; + }); + } + + const replyMethod = context.replied ? "followUp" : "reply"; + + return context[replyMethod]({ + content: formattedBody, + embeds: [], + components: [], + fetchReply: true, + ephemeral, + }).catch((err) => { + logger.error(`Context reply failed: ${err}`); + + return undefined; + }) as Promise; + }; + }, + }, +}); diff --git a/backend/src/plugins/Common/functions/getEmoji.ts b/backend/src/plugins/Common/functions/getEmoji.ts new file mode 100644 index 000000000..c19102503 --- /dev/null +++ b/backend/src/plugins/Common/functions/getEmoji.ts @@ -0,0 +1,10 @@ +import { GuildPluginData } from "knub"; +import { CommonPluginType } from "../types"; + +export function getSuccessEmoji(pluginData: GuildPluginData) { + return pluginData.config.get().success_emoji ?? "✅"; +} + +export function getErrorEmoji(pluginData: GuildPluginData) { + return pluginData.config.get().error_emoji ?? "❌"; +} diff --git a/backend/src/plugins/Common/types.ts b/backend/src/plugins/Common/types.ts new file mode 100644 index 000000000..d0c8df857 --- /dev/null +++ b/backend/src/plugins/Common/types.ts @@ -0,0 +1,13 @@ +import * as t from "io-ts"; +import { BasePluginType } from "knub"; + +export const ConfigSchema = t.type({ + success_emoji: t.string, + error_emoji: t.string, +}); + +export type TConfigSchema = t.TypeOf; + +export interface CommonPluginType extends BasePluginType { + config: TConfigSchema; +} diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index f2b391cc7..9848f5d51 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -55,7 +55,7 @@ async function banAction( }; const durationMs = duration ? convertDelayStringToMS(duration)! : undefined; - const result = await modactions.banUserId(target, reason, { caseArgs }, durationMs); + const result = await modactions.banUserId(target, reason, reason, { caseArgs }, durationMs); if (result.status === "failed") { await interactionToReply .editReply({ content: "Error: Failed to ban user", embeds: [], components: [] }) diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index 58457cb01..5959f6d76 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -68,7 +68,7 @@ async function muteAction( const durationMs = duration ? convertDelayStringToMS(duration)! : undefined; try { - const result = await mutes.muteUser(target, durationMs, reason, { caseArgs }); + const result = await mutes.muteUser(target, durationMs, reason, reason, { caseArgs }); const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : ""; const muteMessage = `Muted **${result.case.user_name}** ${ diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts index 6fbd40f37..b467ed61c 100644 --- a/backend/src/plugins/ContextMenus/actions/warn.ts +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -60,7 +60,7 @@ async function warnAction( modId: executingMember.id, }; - const result = await modactions.warnMember(targetMember, reason, { caseArgs }); + const result = await modactions.warnMember(targetMember, reason, reason, { caseArgs }); if (result.status === "failed") { await interactionToReply .editReply({ content: "Error: Failed to warn user", embeds: [], components: [] }) diff --git a/backend/src/plugins/Counters/commands/AddCounterCmd.ts b/backend/src/plugins/Counters/commands/AddCounterCmd.ts index a71418dfc..0eb68f2bc 100644 --- a/backend/src/plugins/Counters/commands/AddCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/AddCounterCmd.ts @@ -2,8 +2,8 @@ import { Snowflake, TextChannel } from "discord.js"; import { guildPluginMessageCommand } from "knub"; import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { UnknownUser, resolveUser } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { changeCounterValue } from "../functions/changeCounterValue"; import { CountersPluginType } from "../types"; @@ -45,22 +45,22 @@ export const AddCounterCmd = guildPluginMessageCommand()({ const counter = config.counters[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName]; if (!counter || !counterId) { - sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Unknown counter: ${args.counterName}`); return; } if (counter.can_edit === false) { - sendErrorMessage(pluginData, message.channel, `Missing permissions to edit this counter's value`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Missing permissions to edit this counter's value`); return; } if (args.channel && !counter.per_channel) { - sendErrorMessage(pluginData, message.channel, `This counter is not per-channel`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-channel`); return; } if (args.user && !counter.per_user) { - sendErrorMessage(pluginData, message.channel, `This counter is not per-user`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-user`); return; } @@ -69,13 +69,13 @@ export const AddCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which channel's counter value would you like to add to?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { - sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Channel is not a text channel, cancelling"); return; } @@ -87,13 +87,13 @@ export const AddCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which user's counter value would you like to add to?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialUser = await resolveUser(pluginData.client, reply.content); if (!potentialUser || potentialUser instanceof UnknownUser) { - sendErrorMessage(pluginData, message.channel, "Unknown user, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown user, cancelling"); return; } @@ -105,13 +105,13 @@ export const AddCounterCmd = guildPluginMessageCommand()({ message.channel.send("How much would you like to add to the counter's value?"); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialAmount = parseInt(reply.content, 10); if (!potentialAmount) { - sendErrorMessage(pluginData, message.channel, "Not a number, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Not a number, cancelling"); return; } diff --git a/backend/src/plugins/Counters/commands/CountersListCmd.ts b/backend/src/plugins/Counters/commands/CountersListCmd.ts index 84a0a2729..db83812e6 100644 --- a/backend/src/plugins/Counters/commands/CountersListCmd.ts +++ b/backend/src/plugins/Counters/commands/CountersListCmd.ts @@ -1,7 +1,7 @@ import { guildPluginMessageCommand } from "knub"; -import { sendErrorMessage } from "../../../pluginUtils"; import { trimMultilineString, ucfirst } from "../../../utils"; import { getGuildPrefix } from "../../../utils/getGuildPrefix"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { CountersPluginType } from "../types"; export const CountersListCmd = guildPluginMessageCommand()({ @@ -15,7 +15,7 @@ export const CountersListCmd = guildPluginMessageCommand()({ const countersToShow = Array.from(Object.values(config.counters)).filter((c) => c.can_view !== false); if (!countersToShow.length) { - sendErrorMessage(pluginData, message.channel, "No counters are configured for this server"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "No counters are configured for this server"); return; } diff --git a/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts b/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts index 3a44ecaa8..e413cf21e 100644 --- a/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts +++ b/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts @@ -1,7 +1,7 @@ import { guildPluginMessageCommand } from "knub"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { confirm, noop, trimMultilineString } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { resetAllCounterValues } from "../functions/resetAllCounterValues"; import { CountersPluginType } from "../types"; @@ -18,17 +18,19 @@ export const ResetAllCounterValuesCmd = guildPluginMessageCommand()({ const counter = config.counters[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName]; if (!counter || !counterId) { - sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Unknown counter: ${args.counterName}`); return; } if (counter.can_edit === false) { - sendErrorMessage(pluginData, message.channel, `Missing permissions to reset this counter's value`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Missing permissions to reset this counter's value`); return; } if (args.channel && !counter.per_channel) { - sendErrorMessage(pluginData, message.channel, `This counter is not per-channel`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-channel`); return; } if (args.user && !counter.per_user) { - sendErrorMessage(pluginData, message.channel, `This counter is not per-user`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-user`); return; } @@ -64,13 +64,13 @@ export const ResetCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which channel's counter value would you like to reset?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { - sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Channel is not a text channel, cancelling"); return; } @@ -82,13 +82,13 @@ export const ResetCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which user's counter value would you like to reset?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialUser = await resolveUser(pluginData.client, reply.content); if (!potentialUser || potentialUser instanceof UnknownUser) { - sendErrorMessage(pluginData, message.channel, "Unknown user, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown user, cancelling"); return; } diff --git a/backend/src/plugins/Counters/commands/SetCounterCmd.ts b/backend/src/plugins/Counters/commands/SetCounterCmd.ts index ba6fbc3ae..3cc62c54d 100644 --- a/backend/src/plugins/Counters/commands/SetCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/SetCounterCmd.ts @@ -2,8 +2,8 @@ import { Snowflake, TextChannel } from "discord.js"; import { guildPluginMessageCommand } from "knub"; import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { UnknownUser, resolveUser } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { setCounterValue } from "../functions/setCounterValue"; import { CountersPluginType } from "../types"; @@ -45,22 +45,22 @@ export const SetCounterCmd = guildPluginMessageCommand()({ const counter = config.counters[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName]; if (!counter || !counterId) { - sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Unknown counter: ${args.counterName}`); return; } if (counter.can_edit === false) { - sendErrorMessage(pluginData, message.channel, `Missing permissions to edit this counter's value`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Missing permissions to edit this counter's value`); return; } if (args.channel && !counter.per_channel) { - sendErrorMessage(pluginData, message.channel, `This counter is not per-channel`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-channel`); return; } if (args.user && !counter.per_user) { - sendErrorMessage(pluginData, message.channel, `This counter is not per-user`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-user`); return; } @@ -69,13 +69,13 @@ export const SetCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which channel's counter value would you like to change?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { - sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Channel is not a text channel, cancelling"); return; } @@ -87,13 +87,13 @@ export const SetCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which user's counter value would you like to change?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialUser = await resolveUser(pluginData.client, reply.content); if (!potentialUser || potentialUser instanceof UnknownUser) { - sendErrorMessage(pluginData, message.channel, "Unknown user, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown user, cancelling"); return; } @@ -105,13 +105,13 @@ export const SetCounterCmd = guildPluginMessageCommand()({ message.channel.send("What would you like to set the counter's value to?"); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialValue = parseInt(reply.content, 10); if (Number.isNaN(potentialValue)) { - sendErrorMessage(pluginData, message.channel, "Not a number, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Not a number, cancelling"); return; } @@ -119,7 +119,7 @@ export const SetCounterCmd = guildPluginMessageCommand()({ } if (value < 0) { - sendErrorMessage(pluginData, message.channel, "Cannot set counter value below 0"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cannot set counter value below 0"); return; } diff --git a/backend/src/plugins/Counters/commands/ViewCounterCmd.ts b/backend/src/plugins/Counters/commands/ViewCounterCmd.ts index d2715c0c3..1c037e69f 100644 --- a/backend/src/plugins/Counters/commands/ViewCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/ViewCounterCmd.ts @@ -2,8 +2,8 @@ import { Snowflake } from "discord.js"; import { guildPluginMessageCommand } from "knub"; import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { resolveUser, UnknownUser } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { CountersPluginType } from "../types"; export const ViewCounterCmd = guildPluginMessageCommand()({ @@ -39,22 +39,22 @@ export const ViewCounterCmd = guildPluginMessageCommand()({ const counter = config.counters[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName]; if (!counter || !counterId) { - sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Unknown counter: ${args.counterName}`); return; } if (counter.can_view === false) { - sendErrorMessage(pluginData, message.channel, `Missing permissions to view this counter's value`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Missing permissions to view this counter's value`); return; } if (args.channel && !counter.per_channel) { - sendErrorMessage(pluginData, message.channel, `This counter is not per-channel`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-channel`); return; } if (args.user && !counter.per_user) { - sendErrorMessage(pluginData, message.channel, `This counter is not per-user`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-user`); return; } @@ -63,13 +63,13 @@ export const ViewCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which channel's counter value would you like to view?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel?.isTextBased()) { - sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Channel is not a text channel, cancelling"); return; } @@ -81,13 +81,13 @@ export const ViewCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which user's counter value would you like to view?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - sendErrorMessage(pluginData, message.channel, "Cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); return; } const potentialUser = await resolveUser(pluginData.client, reply.content); if (!potentialUser || potentialUser instanceof UnknownUser) { - sendErrorMessage(pluginData, message.channel, "Unknown user, cancelling"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown user, cancelling"); return; } diff --git a/backend/src/plugins/CustomEvents/functions/runEvent.ts b/backend/src/plugins/CustomEvents/functions/runEvent.ts index 8e38b9ab7..5ea3ceca8 100644 --- a/backend/src/plugins/CustomEvents/functions/runEvent.ts +++ b/backend/src/plugins/CustomEvents/functions/runEvent.ts @@ -1,7 +1,7 @@ import { Message } from "discord.js"; import { GuildPluginData } from "knub"; -import { sendErrorMessage } from "../../../pluginUtils"; import { TemplateSafeValueContainer } from "../../../templateFormatter"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { ActionError } from "../ActionError"; import { addRoleAction } from "../actions/addRoleAction"; import { createCaseAction } from "../actions/createCaseAction"; @@ -39,7 +39,7 @@ export async function runEvent( } catch (e) { if (e instanceof ActionError) { if (event.trigger.type === "command") { - sendErrorMessage(pluginData, (eventData.msg as Message).channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage((eventData.msg as Message).channel, e.message); } else { // TODO: Where to log action errors from other kinds of triggers? } diff --git a/backend/src/plugins/LocateUser/commands/FollowCmd.ts b/backend/src/plugins/LocateUser/commands/FollowCmd.ts index 1011f7712..a214241bc 100644 --- a/backend/src/plugins/LocateUser/commands/FollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/FollowCmd.ts @@ -2,8 +2,8 @@ import humanizeDuration from "humanize-duration"; import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { registerExpiringVCAlert } from "../../../data/loops/expiringVCAlertsLoop"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { MINUTES, SECONDS } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { locateUserCmd } from "../types"; export const FollowCmd = locateUserCmd({ @@ -27,7 +27,9 @@ export const FollowCmd = locateUserCmd({ const active = args.active || false; if (time < 30 * SECONDS) { - sendErrorMessage(pluginData, msg.channel, "Sorry, but the minimum duration for an alert is 30 seconds!"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "Sorry, but the minimum duration for an alert is 30 seconds!"); return; } @@ -46,19 +48,23 @@ export const FollowCmd = locateUserCmd({ } if (active) { - sendSuccessMessage( - pluginData, - msg.channel, - `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( - time, - )} i will notify and move you.\nPlease make sure to be in a voice channel, otherwise i cannot move you!`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( + time, + )} i will notify and move you.\nPlease make sure to be in a voice channel, otherwise i cannot move you!`, + ); } else { - sendSuccessMessage( - pluginData, - msg.channel, - `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration(time)} i will notify you`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( + time, + )} i will notify you`, + ); } }, }); diff --git a/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts b/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts index d598d8a91..271b401d2 100644 --- a/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts @@ -1,7 +1,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { clearExpiringVCAlert } from "../../../data/loops/expiringVCAlertsLoop"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { createChunkedMessage, sorter } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { locateUserCmd } from "../types"; export const ListFollowCmd = locateUserCmd({ @@ -13,7 +13,7 @@ export const ListFollowCmd = locateUserCmd({ async run({ message: msg, pluginData }) { const alerts = await pluginData.state.alerts.getAlertsByRequestorId(msg.member.id); if (alerts.length === 0) { - sendErrorMessage(pluginData, msg.channel, "You have no active alerts!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You have no active alerts!"); return; } @@ -46,7 +46,7 @@ export const DeleteFollowCmd = locateUserCmd({ alerts.sort(sorter("expires_at")); if (args.num > alerts.length || args.num <= 0) { - sendErrorMessage(pluginData, msg.channel, "Unknown alert!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown alert!"); return; } @@ -54,6 +54,6 @@ export const DeleteFollowCmd = locateUserCmd({ clearExpiringVCAlert(toDelete); await pluginData.state.alerts.delete(toDelete.id); - sendSuccessMessage(pluginData, msg.channel, "Alert deleted"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Alert deleted"); }, }); diff --git a/backend/src/plugins/LocateUser/utils/moveMember.ts b/backend/src/plugins/LocateUser/utils/moveMember.ts index 8d044d597..f6dbd96b3 100644 --- a/backend/src/plugins/LocateUser/utils/moveMember.ts +++ b/backend/src/plugins/LocateUser/utils/moveMember.ts @@ -1,6 +1,6 @@ import { GuildMember, GuildTextBasedChannel, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { sendErrorMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LocateUserPluginType } from "../types"; export async function moveMember( @@ -16,10 +16,14 @@ export async function moveMember( channel: target.voice.channelId, }); } catch { - sendErrorMessage(pluginData, errorChannel, "Failed to move you. Are you in a voice channel?"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?"); return; } } else { - sendErrorMessage(pluginData, errorChannel, "Failed to move you. Are you in a voice channel?"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?"); } } diff --git a/backend/src/plugins/LocateUser/utils/sendWhere.ts b/backend/src/plugins/LocateUser/utils/sendWhere.ts index 7faaad80a..8d40e99c9 100644 --- a/backend/src/plugins/LocateUser/utils/sendWhere.ts +++ b/backend/src/plugins/LocateUser/utils/sendWhere.ts @@ -1,7 +1,7 @@ import { GuildMember, GuildTextBasedChannel, Invite, VoiceChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { getInviteLink } from "knub/helpers"; -import { sendErrorMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LocateUserPluginType } from "../types"; import { createOrReuseInvite } from "./createOrReuseInvite"; @@ -22,7 +22,7 @@ export async function sendWhere( try { invite = await createOrReuseInvite(voice); } catch { - sendErrorMessage(pluginData, channel, "Cannot create an invite to that channel!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(channel, "Cannot create an invite to that channel!"); return; } channel.send({ diff --git a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts index 3d2456e8b..6b9211f1c 100644 --- a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts +++ b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { saveMessagesToDB } from "../saveMessagesToDB"; import { messageSaverCmd } from "../types"; @@ -18,13 +18,14 @@ export const SaveMessagesToDBCmd = messageSaverCmd({ const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, args.ids.trim().split(" ")); if (failed.length) { - sendSuccessMessage( - pluginData, - msg.channel, - `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, + ); } else { - sendSuccessMessage(pluginData, msg.channel, `Saved ${savedCount} messages!`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Saved ${savedCount} messages!`); } }, }); diff --git a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts index e81c73fee..92c5393d9 100644 --- a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts +++ b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { saveMessagesToDB } from "../saveMessagesToDB"; import { messageSaverCmd } from "../types"; @@ -19,13 +19,14 @@ export const SavePinsToDBCmd = messageSaverCmd({ const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, [...pins.keys()]); if (failed.length) { - sendSuccessMessage( - pluginData, - msg.channel, - `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, + ); } else { - sendSuccessMessage(pluginData, msg.channel, `Saved ${savedCount} messages!`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Saved ${savedCount} messages!`); } }, }); diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index d9b083816..12eb729ec 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -72,6 +72,7 @@ import { onModActionsEvent } from "./functions/onModActionsEvent"; import { updateCase } from "./functions/updateCase"; import { warnMember } from "./functions/warnMember"; import { + AttachmentLinkReactionType, BanOptions, ConfigSchema, KickOptions, @@ -100,6 +101,8 @@ const defaultOptions = { warn_notify_message: "The user already has **{priorWarnings}** warnings!\n Please check their prior cases and assess whether or not to warn anyways.\n Proceed with the warning?", ban_delete_message_days: 1, + attachment_link_reaction: "warn" as AttachmentLinkReactionType, + attachment_storing_channel: null, can_note: false, can_warn: false, @@ -217,26 +220,32 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ public: { warnMember(pluginData) { - return (member: GuildMember, reason: string, warnOptions?: WarnOptions) => { - return warnMember(pluginData, member, reason, warnOptions); + return (member: GuildMember, reason: string, reasonWithAttachments: string, warnOptions?: WarnOptions) => { + return warnMember(pluginData, member, reason, reasonWithAttachments, warnOptions); }; }, kickMember(pluginData) { - return (member: GuildMember, reason: string, kickOptions?: KickOptions) => { - kickMember(pluginData, member, reason, kickOptions); + return (member: GuildMember, reason: string, reasonWithAttachments: string, kickOptions?: KickOptions) => { + kickMember(pluginData, member, reason, reasonWithAttachments, kickOptions); }; }, banUserId(pluginData) { - return (userId: string, reason?: string, banOptions?: BanOptions, banTime?: number) => { - return banUserId(pluginData, userId, reason, banOptions, banTime); + return ( + userId: string, + reason?: string, + reasonWithAttachments?: string, + banOptions?: BanOptions, + banTime?: number, + ) => { + return banUserId(pluginData, userId, reason, reasonWithAttachments, banOptions, banTime); }; }, updateCase(pluginData) { return (msg: Message, caseNumber: number | null, note: string) => { - updateCase(pluginData, msg.channel, msg.author, caseNumber ?? undefined, note, [...msg.attachments.values()]); + updateCase(pluginData, msg, msg.author, caseNumber ?? undefined, note, [...msg.attachments.values()]); }; }, diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts index 41c2a380f..beb4cc3d1 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts @@ -1,7 +1,8 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { CaseTypes } from "../../../../data/CaseTypes"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualAddCaseCmd } from "../../functions/actualCommands/actualAddCaseCmd"; import { modActionsMsgCmd } from "../../types"; @@ -27,7 +28,7 @@ export const AddCaseMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -35,7 +36,7 @@ export const AddCaseMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -45,13 +46,13 @@ export const AddCaseMsgCmd = modActionsMsgCmd({ // Verify the case type is valid const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase(); if (!CaseTypes[type]) { - sendErrorMessage(pluginData, msg.channel, "Cannot add case: invalid case type"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot add case: invalid case type"); return; } actualAddCaseCmd( pluginData, - msg.channel, + msg, msg.member, mod, [...msg.attachments.values()], diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts index b5fbfc5f4..c83a6b42d 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts @@ -1,7 +1,8 @@ import { slashOptions } from "knub"; import { CaseTypes } from "../../../../data/CaseTypes"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualAddCaseCmd } from "../../functions/actualCommands/actualAddCaseCmd"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -44,7 +45,9 @@ export const AddCaseSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts index efea59d77..10b4344cb 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualBanCmd } from "../../functions/actualCommands/actualBanCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; @@ -37,7 +38,7 @@ export const BanMsgCmd = modActionsMsgCmd({ const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -45,7 +46,7 @@ export const BanMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -56,13 +57,13 @@ export const BanMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args) ?? undefined; } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } actualBanCmd( pluginData, - msg.channel, + msg, user, args["time"] ? args["time"] : null, args.reason || "", diff --git a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts index acd19a43f..dd86a5464 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts @@ -1,8 +1,9 @@ import { ChannelType } from "discord.js"; import { slashOptions } from "knub"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualBanCmd } from "../../functions/actualCommands/actualBanCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -49,7 +50,9 @@ export const BanSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -62,7 +65,9 @@ export const BanSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } @@ -73,13 +78,13 @@ export const BanSlashCmd = { try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - sendErrorMessage(pluginData, interaction, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); return; } const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; if (options.time && !convertedTime) { - sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } diff --git a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts index 6521e3279..914cec0fb 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts @@ -14,6 +14,6 @@ export const CaseMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualCaseCmd(pluginData, msg.channel, msg.author.id, args.caseNumber); + actualCaseCmd(pluginData, msg, msg.author.id, args.caseNumber); }, }); diff --git a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts index b9e496b79..717ea5314 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts @@ -29,7 +29,7 @@ export const CasesModMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { return actualCasesCmd( pluginData, - msg.channel, + msg, args.mod, null, msg.author, diff --git a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts index 765524dfc..dd50a5de3 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { sendErrorMessage } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualCasesCmd } from "../../functions/actualCommands/actualCasesCmd"; import { modActionsMsgCmd } from "../../types"; @@ -33,13 +33,13 @@ export const CasesUserMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } return actualCasesCmd( pluginData, - msg.channel, + msg, args.mod, user, msg.author, diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts index 70dbc92f1..b4c96a686 100644 --- a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts @@ -18,6 +18,6 @@ export const DeleteCaseMsgCmd = modActionsMsgCmd({ }, async run({ pluginData, message, args }) { - actualDeleteCaseCmd(pluginData, message.channel, message.member, args.caseNumber, args.force); + actualDeleteCaseCmd(pluginData, message, message.member, args.caseNumber, args.force); }, }); diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts index ee0e4fa7d..e15f3c13d 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualForceBanCmd } from "../../functions/actualCommands/actualForceBanCmd"; import { isBanned } from "../../functions/isBanned"; import { modActionsMsgCmd } from "../../types"; @@ -26,21 +27,21 @@ export const ForceBanMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } // If the user exists as a guild member, make sure we can act on them first const member = await resolveMember(pluginData.client, pluginData.guild, user.id); if (member && !canActOn(pluginData, msg.member, member)) { - sendErrorMessage(pluginData, msg.channel, "Cannot forceban this user: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot forceban this user: insufficient permissions"); return; } // Make sure the user isn't already banned const banned = await isBanned(pluginData, user.id); if (banned) { - sendErrorMessage(pluginData, msg.channel, `User is already banned`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User is already banned`); return; } @@ -48,13 +49,13 @@ export const ForceBanMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } mod = args.mod; } - actualForceBanCmd(pluginData, msg.channel, msg.author.id, user, args.reason, [...msg.attachments.values()], mod); + actualForceBanCmd(pluginData, msg, msg.author.id, user, args.reason, [...msg.attachments.values()], mod); }, }); diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts index c1722fcce..c246982ec 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts @@ -1,7 +1,8 @@ import { slashOptions } from "knub"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualForceBanCmd } from "../../functions/actualCommands/actualForceBanCmd"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -26,7 +27,9 @@ export const ForceBanSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -39,7 +42,9 @@ export const ForceBanSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } @@ -48,7 +53,7 @@ export const ForceBanSlashCmd = { const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; if (options.time && !convertedTime) { - sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts index 9ecbae613..9c2a9cbb2 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; @@ -35,7 +36,7 @@ export const ForceMuteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -43,7 +44,7 @@ export const ForceMuteMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to mute this user if (memberToMute && !canActOn(pluginData, msg.member, memberToMute)) { - sendErrorMessage(pluginData, msg.channel, "Cannot mute: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot mute: insufficient permissions"); return; } @@ -53,7 +54,7 @@ export const ForceMuteMsgCmd = modActionsMsgCmd({ if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -65,13 +66,13 @@ export const ForceMuteMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } actualMuteCmd( pluginData, - msg.channel, + msg, user, [...msg.attachments.values()], mod, diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts index b32cde4fb..6d6c2fb90 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts @@ -1,8 +1,9 @@ import { ChannelType } from "discord.js"; import { slashOptions } from "knub"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -44,7 +45,9 @@ export const ForceMuteSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -58,7 +61,9 @@ export const ForceMuteSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } @@ -68,7 +73,7 @@ export const ForceMuteSlashCmd = { const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; if (options.time && !convertedTime) { - sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } @@ -76,7 +81,7 @@ export const ForceMuteSlashCmd = { try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - sendErrorMessage(pluginData, interaction, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts index c1b973512..f9351d8f6 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; import { modActionsMsgCmd } from "../../types"; @@ -32,13 +33,13 @@ export const ForceUnmuteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } // Check if they're muted in the first place if (!(await pluginData.state.mutes.isMuted(user.id))) { - sendErrorMessage(pluginData, msg.channel, "Cannot unmute: member is not muted"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot unmute: member is not muted"); return; } @@ -47,7 +48,7 @@ export const ForceUnmuteMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to unmute this member if (memberToUnmute && !canActOn(pluginData, msg.member, memberToUnmute)) { - sendErrorMessage(pluginData, msg.channel, "Cannot unmute: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot unmute: insufficient permissions"); return; } @@ -57,7 +58,7 @@ export const ForceUnmuteMsgCmd = modActionsMsgCmd({ if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -67,7 +68,7 @@ export const ForceUnmuteMsgCmd = modActionsMsgCmd({ actualUnmuteCmd( pluginData, - msg.channel, + msg, user, [...msg.attachments.values()], mod, diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts index 107151a82..3c68a64e4 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts @@ -1,7 +1,8 @@ import { slashOptions } from "knub"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -27,7 +28,9 @@ export const ForceUnmuteSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -41,7 +44,9 @@ export const ForceUnmuteSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } @@ -51,7 +56,7 @@ export const ForceUnmuteSlashCmd = { const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; if (options.time && !convertedTime) { - sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts index e7701d4a4..3d160d5f0 100644 --- a/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts @@ -14,6 +14,6 @@ export const HideCaseMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualHideCaseCmd(pluginData, msg.channel, args.caseNum); + actualHideCaseCmd(pluginData, msg, args.caseNum); }, }); diff --git a/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts index bab6064dc..fb2754e0b 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts @@ -1,7 +1,7 @@ import { hasPermission } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { sendErrorMessage } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualKickCmd } from "../../functions/actualCommands/actualKickCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; @@ -30,7 +30,7 @@ export const KickMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -38,7 +38,7 @@ export const KickMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(await pluginData.config.getForMessage(msg), "can_act_as_other"))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -49,13 +49,13 @@ export const KickMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } actualKickCmd( pluginData, - msg.channel, + msg, msg.member, user, args.reason, diff --git a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts index 460410066..961445ec7 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts @@ -1,8 +1,9 @@ import { ChannelType } from "discord.js"; import { slashOptions } from "knub"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualKickCmd } from "../../functions/actualCommands/actualKickCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -48,7 +49,9 @@ export const KickSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -61,7 +64,9 @@ export const KickSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } @@ -72,7 +77,7 @@ export const KickSlashCmd = { try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - sendErrorMessage(pluginData, interaction, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts index 222dbaccc..d607224af 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts @@ -14,6 +14,6 @@ export const MassBanMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualMassBanCmd(pluginData, msg.channel, args.userIds, msg.member); + actualMassBanCmd(pluginData, msg, args.userIds, msg.member); }, }); diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts index 0d7793cea..a09a5f26e 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts @@ -14,6 +14,6 @@ export const MassMuteMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualMassMuteCmd(pluginData, msg.channel, args.userIds, msg.member); + actualMassMuteCmd(pluginData, msg, args.userIds, msg.member); }, }); diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts index 8508b00ec..72713ad52 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts @@ -14,6 +14,6 @@ export const MassUnbanMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualMassBanCmd(pluginData, msg.channel, args.userIds, msg.member); + actualMassBanCmd(pluginData, msg, args.userIds, msg.member); }, }); diff --git a/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts index 16a5343f0..469d71621 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts @@ -1,7 +1,8 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; @@ -37,7 +38,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -47,22 +48,20 @@ export const MuteMsgCmd = modActionsMsgCmd({ const _isBanned = await isBanned(pluginData, user.id); const prefix = pluginData.fullConfig.prefix; if (_isBanned) { - sendErrorMessage( - pluginData, - msg.channel, - `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`); return; } else { // Ask the mod if we should upgrade to a forcemute as the user is not on the server const reply = await waitForButtonConfirm( - msg.channel, + msg, { content: "User not found on the server, forcemute instead?" }, { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, ); if (!reply) { - sendErrorMessage(pluginData, msg.channel, "User not on server, mute cancelled by moderator"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "User not on server, mute cancelled by moderator"); return; } } @@ -70,7 +69,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to mute this member if (memberToMute && !canActOn(pluginData, msg.member, memberToMute)) { - sendErrorMessage(pluginData, msg.channel, "Cannot mute: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot mute: insufficient permissions"); return; } @@ -80,7 +79,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -92,13 +91,13 @@ export const MuteMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } actualMuteCmd( pluginData, - msg.channel, + msg, user, [...msg.attachments.values()], mod, diff --git a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts index c17200fcc..c84ce9efb 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts @@ -1,9 +1,10 @@ import { ChannelType } from "discord.js"; import { slashOptions } from "knub"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; @@ -46,7 +47,9 @@ export const MuteSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -57,11 +60,9 @@ export const MuteSlashCmd = { const _isBanned = await isBanned(pluginData, options.user.id); const prefix = pluginData.fullConfig.prefix; if (_isBanned) { - sendErrorMessage( - pluginData, - interaction, - `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`); return; } else { // Ask the mod if we should upgrade to a forcemute as the user is not on the server @@ -72,7 +73,9 @@ export const MuteSlashCmd = { ); if (!reply) { - sendErrorMessage(pluginData, interaction, "User not on server, mute cancelled by moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "User not on server, mute cancelled by moderator"); return; } } @@ -80,7 +83,7 @@ export const MuteSlashCmd = { // Make sure we're allowed to mute this member if (memberToMute && !canActOn(pluginData, interaction.member, memberToMute)) { - sendErrorMessage(pluginData, interaction, "Cannot mute: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot mute: insufficient permissions"); return; } @@ -93,7 +96,9 @@ export const MuteSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } @@ -103,7 +108,7 @@ export const MuteSlashCmd = { const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; if (options.time && !convertedTime) { - sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } @@ -111,7 +116,7 @@ export const MuteSlashCmd = { try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - sendErrorMessage(pluginData, interaction, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts index 14336d892..9861702f9 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { sendErrorMessage } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualNoteCmd } from "../../functions/actualCommands/actualNoteCmd"; import { modActionsMsgCmd } from "../../types"; @@ -17,15 +17,15 @@ export const NoteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } if (!args.note && msg.attachments.size === 0) { - sendErrorMessage(pluginData, msg.channel, "Text or attachment required"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Text or attachment required"); return; } - actualNoteCmd(pluginData, msg.channel, msg.author, [...msg.attachments.values()], user, args.note || ""); + actualNoteCmd(pluginData, msg, msg.author, [...msg.attachments.values()], user, args.note || ""); }, }); diff --git a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts index 50fd2735f..9647d8615 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts @@ -1,6 +1,6 @@ import { slashOptions } from "knub"; -import { sendErrorMessage } from "../../../../pluginUtils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualNoteCmd } from "../../functions/actualCommands/actualNoteCmd"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -24,7 +24,9 @@ export const NoteSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.note || options.note.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts index 1d9bd37d2..531efc4a8 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualUnbanCmd } from "../../functions/actualCommands/actualUnbanCmd"; import { modActionsMsgCmd } from "../../types"; @@ -25,7 +26,7 @@ export const UnbanMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -33,13 +34,13 @@ export const UnbanMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } mod = args.mod; } - actualUnbanCmd(pluginData, msg.channel, msg.author.id, user, args.reason, [...msg.attachments.values()], mod); + actualUnbanCmd(pluginData, msg, msg.author.id, user, args.reason, [...msg.attachments.values()], mod); }, }); diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts index e85cfdfb7..abc969dee 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts @@ -1,6 +1,7 @@ import { slashOptions } from "knub"; -import { hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { hasPermission } from "../../../../pluginUtils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualUnbanCmd } from "../../functions/actualCommands/actualUnbanCmd"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -25,7 +26,9 @@ export const UnbanSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -38,7 +41,9 @@ export const UnbanSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts index 94c029a21..8e71c8e38 100644 --- a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts @@ -14,6 +14,6 @@ export const UnhideCaseMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualHideCaseCmd(pluginData, msg.channel, args.caseNum); + actualHideCaseCmd(pluginData, msg, args.caseNum); }, }); diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts index 8d7e111a8..b917d83b8 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts @@ -1,7 +1,8 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; import { isBanned } from "../../functions/isBanned"; @@ -35,7 +36,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -49,7 +50,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ !hasMuteRole && !memberToUnmute?.isCommunicationDisabled() ) { - sendErrorMessage(pluginData, msg.channel, "Cannot unmute: member is not muted"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot unmute: member is not muted"); return; } @@ -57,22 +58,20 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ const banned = await isBanned(pluginData, user.id); const prefix = pluginData.fullConfig.prefix; if (banned) { - sendErrorMessage( - pluginData, - msg.channel, - `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`); return; } else { // Ask the mod if we should upgrade to a forceunmute as the user is not on the server const reply = await waitForButtonConfirm( - msg.channel, + msg, { content: "User not on server, forceunmute instead?" }, { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, ); if (!reply) { - sendErrorMessage(pluginData, msg.channel, "User not on server, unmute cancelled by moderator"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "User not on server, unmute cancelled by moderator"); return; } } @@ -80,7 +79,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to unmute this member if (memberToUnmute && !canActOn(pluginData, msg.member, memberToUnmute)) { - sendErrorMessage(pluginData, msg.channel, "Cannot unmute: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot unmute: insufficient permissions"); return; } @@ -90,7 +89,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -100,7 +99,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ actualUnmuteCmd( pluginData, - msg.channel, + msg, user, [...msg.attachments.values()], mod, diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts index 04526be61..97163b2bf 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts @@ -1,8 +1,9 @@ import { slashOptions } from "knub"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; import { isBanned } from "../../functions/isBanned"; @@ -30,7 +31,9 @@ export const UnmuteSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -45,7 +48,7 @@ export const UnmuteSlashCmd = { !hasMuteRole && !memberToUnmute?.isCommunicationDisabled() ) { - sendErrorMessage(pluginData, interaction, "Cannot unmute: member is not muted"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot unmute: member is not muted"); return; } @@ -53,11 +56,9 @@ export const UnmuteSlashCmd = { const banned = await isBanned(pluginData, options.user.id); const prefix = pluginData.fullConfig.prefix; if (banned) { - sendErrorMessage( - pluginData, - interaction, - `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`); return; } else { // Ask the mod if we should upgrade to a forceunmute as the user is not on the server @@ -68,7 +69,9 @@ export const UnmuteSlashCmd = { ); if (!reply) { - sendErrorMessage(pluginData, interaction, "User not on server, unmute cancelled by moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "User not on server, unmute cancelled by moderator"); return; } } @@ -76,7 +79,7 @@ export const UnmuteSlashCmd = { // Make sure we're allowed to unmute this member if (memberToUnmute && !canActOn(pluginData, interaction.member, memberToUnmute)) { - sendErrorMessage(pluginData, interaction, "Cannot unmute: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot unmute: insufficient permissions"); return; } @@ -89,7 +92,9 @@ export const UnmuteSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } @@ -99,7 +104,7 @@ export const UnmuteSlashCmd = { const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; if (options.time && !convertedTime) { - sendErrorMessage(pluginData, interaction, `Could not convert ${options.time} to a delay`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } diff --git a/backend/src/plugins/ModActions/commands/update/UpdateMsgCmd.ts b/backend/src/plugins/ModActions/commands/update/UpdateMsgCmd.ts index 73c74f369..c0c186a91 100644 --- a/backend/src/plugins/ModActions/commands/update/UpdateMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/update/UpdateMsgCmd.ts @@ -19,6 +19,6 @@ export const UpdateMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - await updateCase(pluginData, msg.channel, msg.author, args.caseNumber, args.note, [...msg.attachments.values()]); + await updateCase(pluginData, msg, msg.author, args.caseNumber, args.note, [...msg.attachments.values()]); }, }); diff --git a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts index 4baeb3c1d..cd8533b60 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { errorMessage, resolveMember, resolveUser } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualWarnCmd } from "../../functions/actualCommands/actualWarnCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; @@ -23,7 +24,7 @@ export const WarnMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - sendErrorMessage(pluginData, msg.channel, `User not found`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -32,9 +33,9 @@ export const WarnMsgCmd = modActionsMsgCmd({ if (!memberToWarn) { const _isBanned = await isBanned(pluginData, user.id); if (_isBanned) { - sendErrorMessage(pluginData, msg.channel, `User is banned`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User is banned`); } else { - sendErrorMessage(pluginData, msg.channel, `User not found on the server`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found on the server`); } return; @@ -42,7 +43,7 @@ export const WarnMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to warn this member if (!canActOn(pluginData, msg.member, memberToWarn)) { - sendErrorMessage(pluginData, msg.channel, "Cannot warn: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot warn: insufficient permissions"); return; } @@ -61,13 +62,13 @@ export const WarnMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } actualWarnCmd( pluginData, - msg.channel, + msg, msg.author.id, mod, memberToWarn, diff --git a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts index 025f1981a..c34e91e02 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts @@ -1,8 +1,9 @@ import { ChannelType } from "discord.js"; import { slashOptions } from "knub"; -import { canActOn, hasPermission, sendErrorMessage } from "../../../../pluginUtils"; +import { canActOn, hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualWarnCmd } from "../../functions/actualCommands/actualWarnCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; @@ -44,7 +45,9 @@ export const WarnSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -54,9 +57,9 @@ export const WarnSlashCmd = { if (!memberToWarn) { const _isBanned = await isBanned(pluginData, options.user.id); if (_isBanned) { - sendErrorMessage(pluginData, interaction, `User is banned`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `User is banned`); } else { - sendErrorMessage(pluginData, interaction, `User not found on the server`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `User not found on the server`); } return; @@ -64,7 +67,7 @@ export const WarnSlashCmd = { // Make sure we're allowed to warn this member if (!canActOn(pluginData, interaction.member, memberToWarn)) { - sendErrorMessage(pluginData, interaction, "Cannot warn: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot warn: insufficient permissions"); return; } @@ -76,7 +79,9 @@ export const WarnSlashCmd = { if (options.mod) { if (!canActAsOther) { - sendErrorMessage(pluginData, interaction, "You don't have permission to act as another moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } @@ -87,7 +92,7 @@ export const WarnSlashCmd = { try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - sendErrorMessage(pluginData, interaction, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts index 572d450c2..25ce2fbb1 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts @@ -1,17 +1,19 @@ -import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, User } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../../data/CaseTypes"; import { Case } from "../../../../data/entities/Case"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { canActOn } from "../../../../pluginUtils"; import { UnknownUser, renderUserUsername, resolveMember } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; export async function actualAddCaseCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, author: GuildMember, mod: GuildMember, attachments: Array, @@ -19,14 +21,20 @@ export async function actualAddCaseCmd( type: keyof CaseTypes, reason: string, ) { + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { + return; + } + // If the user exists as a guild member, make sure we can act on them first const member = await resolveMember(pluginData.client, pluginData.guild, user.id); if (member && !canActOn(pluginData, author, member)) { - sendErrorMessage(pluginData, context, "Cannot add case on this user: insufficient permissions"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "Cannot add case on this user: insufficient permissions"); return; } - const formattedReason = formatReasonWithAttachments(reason, attachments); + const formattedReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); // Create the case const casesPlugin = pluginData.getPlugin(CasesPlugin); @@ -39,9 +47,11 @@ export async function actualAddCaseCmd( }); if (user) { - sendSuccessMessage(pluginData, context, `Case #${theCase.case_number} created for **${renderUserUsername(user)}**`); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(context, `Case #${theCase.case_number} created for **${renderUserUsername(user)}**`); } else { - sendSuccessMessage(pluginData, context, `Case #${theCase.case_number} created`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, `Case #${theCase.case_number} created`); } // Log the action diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts index 5b665884b..fa9b7c949 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts @@ -1,23 +1,25 @@ -import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, User } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import { getMemberLevel } from "knub/helpers"; import { CaseTypes } from "../../../../data/CaseTypes"; import { clearExpiringTempban, registerExpiringTempban } from "../../../../data/loops/expiringTempbansLoop"; -import { canActOn, isContextInteraction, sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { canActOn, getContextChannel } from "../../../../pluginUtils"; import { UnknownUser, UserNotificationMethod, renderUserUsername, resolveMember } from "../../../../utils"; import { banLock } from "../../../../utils/lockNameHelpers"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../../types"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; import { banUserId } from "../banUserId"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; import { isBanned } from "../isBanned"; export async function actualBanCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, user: User | UnknownUser, time: number | null, reason: string, @@ -27,8 +29,13 @@ export async function actualBanCmd( contactMethods?: UserNotificationMethod[], deleteDays?: number, ) { + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { + return; + } + const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id); - const formattedReason = formatReasonWithAttachments(reason, attachments); + const formattedReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); + const formattedReasonWithAttachments = formatReasonWithAttachments(reason, attachments); // acquire a lock because of the needed user-inputs below (if banned/not on server) const lock = await pluginData.locks.acquire(banLock(user)); @@ -47,7 +54,7 @@ export async function actualBanCmd( ); if (!reply) { - sendErrorMessage(pluginData, context, "User not on server, ban cancelled by moderator"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "User not on server, ban cancelled by moderator"); lock.unlock(); return; } else { @@ -57,7 +64,7 @@ export async function actualBanCmd( // Abort if trying to ban user indefinitely if they are already banned indefinitely if (!existingTempban && !time) { - sendErrorMessage(pluginData, context, `User is already banned indefinitely.`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `User is already banned indefinitely.`); return; } @@ -69,7 +76,9 @@ export async function actualBanCmd( ); if (!reply) { - sendErrorMessage(pluginData, context, "User already banned, update cancelled by moderator"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "User already banned, update cancelled by moderator"); lock.unlock(); return; } @@ -114,11 +123,12 @@ export async function actualBanCmd( }); } - sendSuccessMessage( - pluginData, - context, - `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + context, + `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, + ); lock.unlock(); return; } @@ -127,24 +137,26 @@ export async function actualBanCmd( if (!forceban && !canActOn(pluginData, author, memberToBan!)) { const ourLevel = getMemberLevel(pluginData, author); const targetLevel = getMemberLevel(pluginData, memberToBan!); - sendErrorMessage( - pluginData, - context, - `Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + context, + `Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`, + ); lock.unlock(); return; } const matchingConfig = await pluginData.config.getMatchingConfig({ member: author, - channel: isContextInteraction(context) ? context.channel : context, + channel: await getContextChannel(context), }); const deleteMessageDays = deleteDays ?? matchingConfig.ban_delete_message_days; const banResult = await banUserId( pluginData, user.id, formattedReason, + formattedReasonWithAttachments, { contactMethods, caseArgs: { @@ -158,7 +170,7 @@ export async function actualBanCmd( ); if (banResult.status === "failed") { - sendErrorMessage(pluginData, context, `Failed to ban member: ${banResult.error}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Failed to ban member: ${banResult.error}`); lock.unlock(); return; } @@ -178,5 +190,5 @@ export async function actualBanCmd( } lock.unlock(); - sendSuccessMessage(pluginData, context, response); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, response); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts index 38ddf2bfd..eabd8333c 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts @@ -1,19 +1,20 @@ -import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, Message } from "discord.js"; import { GuildPluginData } from "knub"; -import { sendContextResponse, sendErrorMessage } from "../../../../pluginUtils"; +import { sendContextResponse } from "../../../../pluginUtils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; export async function actualCaseCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, authorId: string, caseNumber: number, ) { const theCase = await pluginData.state.cases.findByCaseNumber(caseNumber); if (!theCase) { - sendErrorMessage(pluginData, context, "Case not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Case not found"); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts index 22a16e934..a3707003b 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts @@ -1,10 +1,10 @@ -import { APIEmbed, ChatInputCommandInteraction, TextBasedChannel, User } from "discord.js"; +import { APIEmbed, ChatInputCommandInteraction, Message, User } from "discord.js"; import { GuildPluginData } from "knub"; import { In } from "typeorm"; import { FindOptionsWhere } from "typeorm/find-options/FindOptionsWhere"; import { CaseTypes } from "../../../../data/CaseTypes"; import { Case } from "../../../../data/entities/Case"; -import { sendContextResponse, sendErrorMessage } from "../../../../pluginUtils"; +import { sendContextResponse } from "../../../../pluginUtils"; import { UnknownUser, chunkArray, @@ -18,6 +18,7 @@ import { createPaginatedMessage } from "../../../../utils/createPaginatedMessage import { getChunkedEmbedFields } from "../../../../utils/getChunkedEmbedFields"; import { getGuildPrefix } from "../../../../utils/getGuildPrefix"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; const casesPerPage = 5; @@ -25,7 +26,7 @@ const maxExpandedCases = 8; async function sendExpandedCases( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, casesCount: number, cases: Case[], ) { @@ -45,7 +46,7 @@ async function sendExpandedCases( async function casesUserCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, author: User, modId: string | null, user: User | UnknownUser, @@ -137,7 +138,7 @@ async function casesUserCmd( async function casesModCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, author: User, modId: string | null, mod: User | UnknownUser, @@ -152,7 +153,7 @@ async function casesModCmd( const totalCases = await casesPlugin.getTotalCasesByMod(modId ?? author.id, caseFilters); if (totalCases === 0) { - sendErrorMessage(pluginData, context, `No cases by **${modName}**`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `No cases by **${modName}**`); return; } @@ -211,7 +212,7 @@ async function casesModCmd( export async function actualCasesCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, modId: string | null, user: User | UnknownUser | null, author: User, diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts index 3fac80cff..662409132 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts @@ -1,21 +1,17 @@ -import { ChatInputCommandInteraction, GuildMember, TextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, GuildMember, Message } from "discord.js"; import { GuildPluginData, helpers } from "knub"; import { Case } from "../../../../data/entities/Case"; -import { - isContextInteraction, - sendContextResponse, - sendErrorMessage, - sendSuccessMessage, -} from "../../../../pluginUtils"; +import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; import { SECONDS } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { TimeAndDatePlugin } from "../../../TimeAndDate/TimeAndDatePlugin"; import { ModActionsPluginType } from "../../types"; export async function actualDeleteCaseCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, author: GuildMember, caseNumbers: number[], force: boolean, @@ -35,7 +31,7 @@ export async function actualDeleteCaseCmd( } if (failed.length === caseNumbers.length) { - sendErrorMessage(pluginData, context, "None of the cases were found!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "None of the cases were found!"); return; } @@ -50,7 +46,7 @@ export async function actualDeleteCaseCmd( const reply = await helpers.waitForReply( pluginData.client, - isContextInteraction(context) ? context.channel! : context, + await getContextChannel(context), author.id, 15 * SECONDS, ); @@ -87,9 +83,13 @@ export async function actualDeleteCaseCmd( : ""; const amt = validCases.length - cancelled; if (amt === 0) { - sendErrorMessage(pluginData, context, "All deletions were cancelled, no cases were deleted."); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "All deletions were cancelled, no cases were deleted."); return; } - sendSuccessMessage(pluginData, context, `${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}`); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(context, `${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}`); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts index 095e06a75..c093306ff 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts @@ -1,25 +1,31 @@ -import { Attachment, ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, Snowflake, User } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../../data/CaseTypes"; import { LogType } from "../../../../data/LogType"; -import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; import { DAYS, MINUTES, UnknownUser } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; import { ignoreEvent } from "../ignoreEvent"; export async function actualForceBanCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, authorId: string, user: User | UnknownUser, reason: string, attachments: Array, mod: GuildMember, ) { - const formattedReason = formatReasonWithAttachments(reason, attachments); + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { + return; + } + + const formattedReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); + const formattedReasonWithAttachments = formatReasonWithAttachments(reason, attachments); ignoreEvent(pluginData, IgnoredEventType.Ban, user.id); pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, user.id); @@ -28,10 +34,10 @@ export async function actualForceBanCmd( // FIXME: Use banUserId()? await pluginData.guild.bans.create(user.id as Snowflake, { deleteMessageSeconds: (1 * DAYS) / MINUTES, - reason: formattedReason ?? undefined, + reason: formattedReasonWithAttachments ?? undefined, }); } catch { - sendErrorMessage(pluginData, context, "Failed to forceban member"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Failed to forceban member"); return; } @@ -46,7 +52,9 @@ export async function actualForceBanCmd( }); // Confirm the action - sendSuccessMessage(pluginData, context, `Member forcebanned (Case #${createdCase.case_number})`); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(context, `Member forcebanned (Case #${createdCase.case_number})`); // Log the action pluginData.getPlugin(LogsPlugin).logMemberForceban({ diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts index d500ce5f6..28527b4db 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts @@ -1,11 +1,11 @@ -import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, Message } from "discord.js"; import { GuildPluginData } from "knub"; -import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; export async function actualHideCaseCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, caseNumbers: number[], ) { const failed: number[] = []; @@ -21,7 +21,7 @@ export async function actualHideCaseCmd( } if (failed.length === caseNumbers.length) { - sendErrorMessage(pluginData, context, "None of the cases were found!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "None of the cases were found!"); return; } const failedAddendum = @@ -30,9 +30,10 @@ export async function actualHideCaseCmd( : ""; const amt = caseNumbers.length - failed.length; - sendSuccessMessage( - pluginData, - context, - `${amt} case${amt === 1 ? " is" : "s are"} now hidden! Use \`unhidecase\` to unhide them.${failedAddendum}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + context, + `${amt} case${amt === 1 ? " is" : "s are"} now hidden! Use \`unhidecase\` to unhide them.${failedAddendum}`, + ); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts index e66383fec..9a85965bd 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts @@ -1,7 +1,7 @@ -import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, User } from "discord.js"; import { GuildPluginData } from "knub"; import { LogType } from "../../../../data/LogType"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { canActOn } from "../../../../pluginUtils"; import { DAYS, SECONDS, @@ -10,15 +10,17 @@ import { renderUserUsername, resolveMember, } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; import { ignoreEvent } from "../ignoreEvent"; import { isBanned } from "../isBanned"; import { kickMember } from "../kickMember"; export async function actualKickCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, author: GuildMember, user: User | UnknownUser, reason: string, @@ -27,14 +29,18 @@ export async function actualKickCmd( contactMethods?: UserNotificationMethod[], clean?: boolean, ) { + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { + return; + } + const memberToKick = await resolveMember(pluginData.client, pluginData.guild, user.id); if (!memberToKick) { const banned = await isBanned(pluginData, user.id); if (banned) { - sendErrorMessage(pluginData, context, `User is banned`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `User is banned`); } else { - sendErrorMessage(pluginData, context, `User not found on the server`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `User not found on the server`); } return; @@ -42,13 +48,14 @@ export async function actualKickCmd( // Make sure we're allowed to kick this member if (!canActOn(pluginData, author, memberToKick)) { - sendErrorMessage(pluginData, context, "Cannot kick: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Cannot kick: insufficient permissions"); return; } - const formattedReason = formatReasonWithAttachments(reason, attachments); + const formattedReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); + const formattedReasonWithAttachments = formatReasonWithAttachments(reason, attachments); - const kickResult = await kickMember(pluginData, memberToKick, formattedReason, { + const kickResult = await kickMember(pluginData, memberToKick, formattedReason, formattedReasonWithAttachments, { contactMethods, caseArgs: { modId: mod.id, @@ -63,7 +70,7 @@ export async function actualKickCmd( try { await memberToKick.ban({ deleteMessageSeconds: (1 * DAYS) / SECONDS, reason: "kick -clean" }); } catch { - sendErrorMessage(pluginData, context, "Failed to ban the user to clean messages (-clean)"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Failed to ban the user to clean messages (-clean)"); } pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, memberToKick.id); @@ -72,12 +79,14 @@ export async function actualKickCmd( try { await pluginData.guild.bans.remove(memberToKick.id, "kick -clean"); } catch { - sendErrorMessage(pluginData, context, "Failed to unban the user after banning them (-clean)"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "Failed to unban the user after banning them (-clean)"); } } if (kickResult.status === "failed") { - sendErrorMessage(pluginData, context, `Failed to kick user`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Failed to kick user`); return; } @@ -85,5 +94,5 @@ export async function actualKickCmd( let response = `Kicked **${renderUserUsername(memberToKick.user)}** (Case #${kickResult.case.case_number})`; if (kickResult.notifyResult.text) response += ` (${kickResult.notifyResult.text})`; - sendSuccessMessage(pluginData, context, response); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, response); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts index c0abc3a83..2b35c51ad 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts @@ -1,55 +1,58 @@ -import { ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; import { waitForReply } from "knub/helpers"; import { CaseTypes } from "../../../../data/CaseTypes"; import { LogType } from "../../../../data/LogType"; import { humanizeDurationShort } from "../../../../humanizeDurationShort"; -import { - canActOn, - isContextInteraction, - sendContextResponse, - sendErrorMessage, - sendSuccessMessage, -} from "../../../../pluginUtils"; +import { canActOn, getContextChannel, isContextInteraction, sendContextResponse } from "../../../../pluginUtils"; import { DAYS, MINUTES, SECONDS, noop } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; import { ignoreEvent } from "../ignoreEvent"; export async function actualMassBanCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, userIds: string[], author: GuildMember, ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { - sendErrorMessage(pluginData, context, `Can only massban max 100 users at once`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Can only massban max 100 users at once`); return; } // Ask for ban reason (cleaner this way instead of trying to cram it into the args) sendContextResponse(context, "Ban reason? `cancel` to cancel"); - const banReasonReply = await waitForReply( - pluginData.client, - isContextInteraction(context) ? context.channel! : context, - author.id, - ); + const banReasonReply = await waitForReply(pluginData.client, await getContextChannel(context), author.id); if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") { - sendErrorMessage(pluginData, context, "Cancelled"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Cancelled"); return; } - const banReason = formatReasonWithAttachments(banReasonReply.content, [...banReasonReply.attachments.values()]); + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, banReasonReply.content)) { + return; + } + + const banReason = await formatReasonWithMessageLinkForAttachments(pluginData, banReasonReply.content, context, [ + ...banReasonReply.attachments.values(), + ]); + const banReasonWithAttachments = formatReasonWithAttachments(banReasonReply.content, [ + ...banReasonReply.attachments.values(), + ]); // Verify we can act on each of the users specified for (const userId of userIds) { const member = pluginData.guild.members.cache.get(userId as Snowflake); // TODO: Get members on demand? if (member && !canActOn(pluginData, author, member)) { - sendErrorMessage(pluginData, context, "Cannot massban one or more users: insufficient permissions"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "Cannot massban one or more users: insufficient permissions"); return; } } @@ -87,7 +90,7 @@ export async function actualMassBanCmd( const casesPlugin = pluginData.getPlugin(CasesPlugin); const messageConfig = isContextInteraction(context) ? await pluginData.config.getForInteraction(context) - : await pluginData.config.getForChannel(context); + : await pluginData.config.getForChannel(await getContextChannel(context)); const deleteDays = messageConfig.ban_delete_message_days; for (const [i, userId] of userIds.entries()) { @@ -103,7 +106,7 @@ export async function actualMassBanCmd( await pluginData.guild.bans.create(userId as Snowflake, { deleteMessageSeconds: (deleteDays * DAYS) / SECONDS, - reason: banReason, + reason: banReasonWithAttachments, }); await casesPlugin.createCase({ @@ -134,7 +137,7 @@ export async function actualMassBanCmd( const successfulBanCount = userIds.length - failedBans.length; if (successfulBanCount === 0) { // All bans failed - don't create a log entry and notify the user - sendErrorMessage(pluginData, context, "All bans failed. Make sure the IDs are valid."); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "All bans failed. Make sure the IDs are valid."); } else { // Some or all bans were successful. Create a log entry for the mass ban and notify the user. pluginData.getPlugin(LogsPlugin).logMassBan({ @@ -144,19 +147,18 @@ export async function actualMassBanCmd( }); if (failedBans.length) { - sendSuccessMessage( - pluginData, - context, - `Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${failedBans.length} failed: ${failedBans.join( - " ", - )}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + context, + `Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${ + failedBans.length + } failed: ${failedBans.join(" ")}`, + ); } else { - sendSuccessMessage( - pluginData, - context, - `Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(context, `Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}`); } } }); diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts index 8dd735aba..2d8086cbb 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts @@ -1,49 +1,48 @@ -import { ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; import { waitForReply } from "knub/helpers"; import { LogType } from "../../../../data/LogType"; import { logger } from "../../../../logger"; -import { - canActOn, - isContextInteraction, - sendContextResponse, - sendErrorMessage, - sendSuccessMessage, -} from "../../../../pluginUtils"; +import { canActOn, getContextChannel, sendContextResponse } from "../../../../pluginUtils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; export async function actualMassMuteCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, userIds: string[], author: GuildMember, ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { - sendErrorMessage(pluginData, context, `Can only massmute max 100 users at once`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Can only massmute max 100 users at once`); return; } // Ask for mute reason sendContextResponse(context, "Mute reason? `cancel` to cancel"); - const muteReasonReceived = await waitForReply( - pluginData.client, - isContextInteraction(context) ? context.channel! : context, - author.id, - ); + const muteReasonReceived = await waitForReply(pluginData.client, await getContextChannel(context), author.id); if ( !muteReasonReceived || !muteReasonReceived.content || muteReasonReceived.content.toLowerCase().trim() === "cancel" ) { - sendErrorMessage(pluginData, context, "Cancelled"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Cancelled"); return; } - const muteReason = formatReasonWithAttachments(muteReasonReceived.content, [ + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, muteReasonReceived.content)) { + return; + } + + const muteReason = await formatReasonWithMessageLinkForAttachments(pluginData, muteReasonReceived.content, context, [ + ...muteReasonReceived.attachments.values(), + ]); + const muteReasonWithAttachments = formatReasonWithAttachments(muteReasonReceived.content, [ ...muteReasonReceived.attachments.values(), ]); @@ -51,7 +50,9 @@ export async function actualMassMuteCmd( for (const userId of userIds) { const member = pluginData.guild.members.cache.get(userId as Snowflake); if (member && !canActOn(pluginData, author, member)) { - sendErrorMessage(pluginData, context, "Cannot massmute one or more users: insufficient permissions"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "Cannot massmute one or more users: insufficient permissions"); return; } } @@ -72,7 +73,7 @@ export async function actualMassMuteCmd( const mutesPlugin = pluginData.getPlugin(MutesPlugin); for (const userId of userIds) { try { - await mutesPlugin.muteUser(userId, 0, `Mass mute: ${muteReason}`, { + await mutesPlugin.muteUser(userId, 0, `Mass mute: ${muteReason}`, `Mass mute: ${muteReasonWithAttachments}`, { caseArgs: { modId, }, @@ -89,7 +90,7 @@ export async function actualMassMuteCmd( const successfulMuteCount = userIds.length - failedMutes.length; if (successfulMuteCount === 0) { // All mutes failed - sendErrorMessage(pluginData, context, "All mutes failed. Make sure the IDs are valid."); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "All mutes failed. Make sure the IDs are valid."); } else { // Success on all or some mutes pluginData.getPlugin(LogsPlugin).logMassMute({ @@ -98,13 +99,14 @@ export async function actualMassMuteCmd( }); if (failedMutes.length) { - sendSuccessMessage( - pluginData, - context, - `Muted ${successfulMuteCount} users, ${failedMutes.length} failed: ${failedMutes.join(" ")}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + context, + `Muted ${successfulMuteCount} users, ${failedMutes.length} failed: ${failedMutes.join(" ")}`, + ); } else { - sendSuccessMessage(pluginData, context, `Muted ${successfulMuteCount} users successfully`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, `Muted ${successfulMuteCount} users successfully`); } } } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts index cf829fd75..bfb57111e 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts @@ -1,46 +1,45 @@ -import { ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; import { waitForReply } from "knub/helpers"; import { CaseTypes } from "../../../../data/CaseTypes"; import { LogType } from "../../../../data/LogType"; -import { - isContextInteraction, - sendContextResponse, - sendErrorMessage, - sendSuccessMessage, -} from "../../../../pluginUtils"; +import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; import { ignoreEvent } from "../ignoreEvent"; import { isBanned } from "../isBanned"; export async function actualMassUnbanCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, userIds: string[], author: GuildMember, ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { - sendErrorMessage(pluginData, context, `Can only mass-unban max 100 users at once`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Can only mass-unban max 100 users at once`); return; } // Ask for unban reason (cleaner this way instead of trying to cram it into the args) sendContextResponse(context, "Unban reason? `cancel` to cancel"); - const unbanReasonReply = await waitForReply( - pluginData.client, - isContextInteraction(context) ? context.channel! : context, - author.id, - ); + const unbanReasonReply = await waitForReply(pluginData.client, await getContextChannel(context), author.id); if (!unbanReasonReply || !unbanReasonReply.content || unbanReasonReply.content.toLowerCase().trim() === "cancel") { - sendErrorMessage(pluginData, context, "Cancelled"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Cancelled"); return; } - const unbanReason = formatReasonWithAttachments(unbanReasonReply.content, [...unbanReasonReply.attachments.values()]); + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, unbanReasonReply.content)) { + return; + } + + const unbanReason = await formatReasonWithMessageLinkForAttachments(pluginData, unbanReasonReply.content, context, [ + ...unbanReasonReply.attachments.values(), + ]); // Ignore automatic unban cases and logs for these users // We'll create our own cases below and post a single "mass unbanned" log instead @@ -83,7 +82,9 @@ export async function actualMassUnbanCmd( const successfulUnbanCount = userIds.length - failedUnbans.length; if (successfulUnbanCount === 0) { // All unbans failed - don't create a log entry and notify the user - sendErrorMessage(pluginData, context, "All unbans failed. Make sure the IDs are valid and banned."); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "All unbans failed. Make sure the IDs are valid and banned."); } else { // Some or all unbans were successful. Create a log entry for the mass unban and notify the user. pluginData.getPlugin(LogsPlugin).logMassUnban({ @@ -110,13 +111,16 @@ export async function actualMassUnbanCmd( }); } - sendSuccessMessage( - pluginData, - context, - `Unbanned ${successfulUnbanCount} users, ${failedUnbans.length} failed:\n${failedMsg}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + context, + `Unbanned ${successfulUnbanCount} users, ${failedUnbans.length} failed:\n${failedMsg}`, + ); } else { - sendSuccessMessage(pluginData, context, `Unbanned ${successfulUnbanCount} users successfully`); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(context, `Unbanned ${successfulUnbanCount} users successfully`); } } } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts index 487cf453e..7a136a324 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts @@ -1,9 +1,8 @@ -import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, User } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import { ERRORS, RecoverablePluginError } from "../../../../RecoverablePluginError"; import { logger } from "../../../../logger"; -import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; import { UnknownUser, UserNotificationMethod, @@ -11,10 +10,12 @@ import { isDiscordAPIError, renderUserUsername, } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { MuteResult } from "../../../Mutes/types"; import { ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; /** * The actual function run by both !mute and !forcemute. @@ -22,23 +23,32 @@ import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; */ export async function actualMuteCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, user: User | UnknownUser, - attachments: Array, + attachments: Attachment[], mod: GuildMember, ppId?: string, time?: number, reason?: string, contactMethods?: UserNotificationMethod[], ) { + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { + return; + } + const timeUntilUnmute = time && humanizeDuration(time); - const formattedReason = reason ? formatReasonWithAttachments(reason, attachments) : undefined; + const formattedReason = + reason || attachments.length > 0 + ? await formatReasonWithMessageLinkForAttachments(pluginData, reason ?? "", context, attachments) + : undefined; + const formattedReasonWithAttachments = + reason || attachments.length > 0 ? formatReasonWithAttachments(reason ?? "", attachments) : undefined; let muteResult: MuteResult; const mutesPlugin = pluginData.getPlugin(MutesPlugin); try { - muteResult = await mutesPlugin.muteUser(user.id, time, formattedReason, { + muteResult = await mutesPlugin.muteUser(user.id, time, formattedReason, formattedReasonWithAttachments, { contactMethods, caseArgs: { modId: mod.id, @@ -47,9 +57,11 @@ export async function actualMuteCmd( }); } catch (e) { if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - sendErrorMessage(pluginData, context, "Could not mute the user: no mute role set in config"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "Could not mute the user: no mute role set in config"); } else if (isDiscordAPIError(e) && e.code === 10007) { - sendErrorMessage(pluginData, context, "Could not mute the user: unknown member"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Could not mute the user: unknown member"); } else { logger.error(`Failed to mute user ${user.id}: ${e.stack}`); if (user.id == null) { @@ -57,7 +69,7 @@ export async function actualMuteCmd( // tslint:disable-next-line:no-console console.trace("[DEBUG] Null user.id for mute"); } - sendErrorMessage(pluginData, context, "Could not mute the user"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Could not mute the user"); } return; @@ -92,5 +104,5 @@ export async function actualMuteCmd( } if (muteResult.notifyResult.text) response += ` (${muteResult.notifyResult.text})`; - sendSuccessMessage(pluginData, context, response); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, response); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts index 2a29e2df2..7b2e48317 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts @@ -1,23 +1,28 @@ -import { Attachment, ChatInputCommandInteraction, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, Message, User } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../../data/CaseTypes"; -import { sendSuccessMessage } from "../../../../pluginUtils"; import { UnknownUser, renderUserUsername } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; export async function actualNoteCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, author: User, attachments: Array, user: User | UnknownUser, note: string, ) { + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, note)) { + return; + } + const userName = renderUserUsername(user); - const reason = formatReasonWithAttachments(note, attachments); + const reason = await formatReasonWithMessageLinkForAttachments(pluginData, note, context, attachments); const casesPlugin = pluginData.getPlugin(CasesPlugin); const createdCase = await casesPlugin.createCase({ @@ -34,14 +39,15 @@ export async function actualNoteCmd( reason, }); - sendSuccessMessage( - pluginData, - context, - `Note added on **${userName}** (Case #${createdCase.case_number})`, - undefined, - undefined, - true, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + context, + `Note added on **${userName}** (Case #${createdCase.case_number})`, + undefined, + undefined, + true, + ); pluginData.state.events.emit("note", user.id, reason); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts index 94df7d4f1..85f9abba5 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts @@ -1,33 +1,40 @@ -import { Attachment, ChatInputCommandInteraction, GuildMember, Snowflake, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, Snowflake, User } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../../data/CaseTypes"; import { LogType } from "../../../../data/LogType"; import { clearExpiringTempban } from "../../../../data/loops/expiringTempbansLoop"; -import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; import { UnknownUser } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; import { ignoreEvent } from "../ignoreEvent"; export async function actualUnbanCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, authorId: string, user: User | UnknownUser, reason: string, attachments: Array, mod: GuildMember, ) { + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { + return; + } + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, user.id); - const formattedReason = formatReasonWithAttachments(reason, attachments); + const formattedReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); try { ignoreEvent(pluginData, IgnoredEventType.Unban, user.id); await pluginData.guild.bans.remove(user.id as Snowflake, formattedReason ?? undefined); } catch { - sendErrorMessage(pluginData, context, "Failed to unban member; are you sure they're banned?"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "Failed to unban member; are you sure they're banned?"); return; } @@ -49,7 +56,7 @@ export async function actualUnbanCmd( } // Confirm the action - sendSuccessMessage(pluginData, context, `Member unbanned (Case #${createdCase.case_number})`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, `Member unbanned (Case #${createdCase.case_number})`); // Log the action pluginData.getPlugin(LogsPlugin).logMemberUnban({ diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts index 3ec2cd9e8..313261eee 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts @@ -1,11 +1,11 @@ -import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, Message } from "discord.js"; import { GuildPluginData } from "knub"; -import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; export async function actualUnhideCaseCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, caseNumbers: number[], ) { const failed: number[] = []; @@ -21,7 +21,7 @@ export async function actualUnhideCaseCmd( } if (failed.length === caseNumbers.length) { - sendErrorMessage(pluginData, context, "None of the cases were found!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "None of the cases were found!"); return; } @@ -31,9 +31,7 @@ export async function actualUnhideCaseCmd( : ""; const amt = caseNumbers.length - failed.length; - sendSuccessMessage( - pluginData, - context, - `${amt} case${amt === 1 ? " is" : "s are"} no longer hidden!${failedAddendum}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(context, `${amt} case${amt === 1 ? " is" : "s are"} no longer hidden!${failedAddendum}`); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts index 96804c38a..0f945a0f8 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts @@ -1,15 +1,16 @@ -import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, User } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; import { UnknownUser, asSingleLine, renderUserUsername } from "../../../../utils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; export async function actualUnmuteCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, user: User | UnknownUser, attachments: Array, mod: GuildMember, @@ -17,25 +18,31 @@ export async function actualUnmuteCmd( time?: number, reason?: string, ) { - const parsedReason = reason ? formatReasonWithAttachments(reason, attachments) : undefined; + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { + return; + } + + const formattedReason = + reason || attachments.length > 0 + ? await formatReasonWithMessageLinkForAttachments(pluginData, reason ?? "", context, attachments) + : undefined; const mutesPlugin = pluginData.getPlugin(MutesPlugin); const result = await mutesPlugin.unmuteUser(user.id, time, { modId: mod.id, ppId: ppId ?? undefined, - reason: parsedReason, + reason: formattedReason, }); if (!result) { - sendErrorMessage(pluginData, context, "User is not muted!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "User is not muted!"); return; } // Confirm the action to the moderator if (time) { const timeUntilUnmute = time && humanizeDuration(time); - sendSuccessMessage( - pluginData, + pluginData.getPlugin(CommonPlugin).sendSuccessMessage( context, asSingleLine(` Unmuting **${renderUserUsername(user)}** @@ -43,8 +50,7 @@ export async function actualUnmuteCmd( `), ); } else { - sendSuccessMessage( - pluginData, + pluginData.getPlugin(CommonPlugin).sendSuccessMessage( context, asSingleLine(` Unmuted **${renderUserUsername(user)}** diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts index 5748f72ed..0349b6da2 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts @@ -1,17 +1,18 @@ -import { Attachment, ChatInputCommandInteraction, GuildMember, TextBasedChannel } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../../data/CaseTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../../pluginUtils"; import { UserNotificationMethod, renderUserUsername } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; -import { formatReasonWithAttachments } from "../formatReasonWithAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; import { warnMember } from "../warnMember"; export async function actualWarnCmd( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, authorId: string, mod: GuildMember, memberToWarn: GuildMember, @@ -19,8 +20,13 @@ export async function actualWarnCmd( attachments: Attachment[], contactMethods?: UserNotificationMethod[], ) { + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { + return; + } + const config = pluginData.config.get(); - const formattedReason = formatReasonWithAttachments(reason, attachments); + const formattedReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); + const formattedReasonWithAttachments = formatReasonWithAttachments(reason, attachments); const casesPlugin = pluginData.getPlugin(CasesPlugin); const priorWarnAmount = await casesPlugin.getCaseTypeAmountForUserId(memberToWarn.id, CaseTypes.Warn); @@ -31,12 +37,12 @@ export async function actualWarnCmd( { confirmText: "Yes", cancelText: "No", restrictToId: authorId }, ); if (!reply) { - sendErrorMessage(pluginData, context, "Warn cancelled by moderator"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Warn cancelled by moderator"); return; } } - const warnResult = await warnMember(pluginData, memberToWarn, formattedReason, { + const warnResult = await warnMember(pluginData, memberToWarn, formattedReason, formattedReasonWithAttachments, { contactMethods, caseArgs: { modId: mod.id, @@ -47,15 +53,16 @@ export async function actualWarnCmd( }); if (warnResult.status === "failed") { - sendErrorMessage(pluginData, context, "Failed to warn user"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Failed to warn user"); return; } const messageResultText = warnResult.notifyResult.text ? ` (${warnResult.notifyResult.text})` : ""; - sendSuccessMessage( - pluginData, - context, - `Warned **${renderUserUsername(memberToWarn.user)}** (Case #${warnResult.case.case_number})${messageResultText}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + context, + `Warned **${renderUserUsername(memberToWarn.user)}** (Case #${warnResult.case.case_number})${messageResultText}`, + ); } diff --git a/backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts b/backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts new file mode 100644 index 000000000..868786eda --- /dev/null +++ b/backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts @@ -0,0 +1,51 @@ +import { ChatInputCommandInteraction, Message, TextBasedChannel } from "discord.js"; +import { AnyPluginData, GuildPluginData } from "knub"; +import { CommonPlugin } from "../../Common/CommonPlugin"; +import { ModActionsPluginType } from "../types"; + +export function shouldReactToAttachmentLink(pluginData: GuildPluginData) { + const config = pluginData.config.get(); + + return !config.attachment_link_reaction || config.attachment_link_reaction !== "none"; +} + +export function attachmentLinkShouldRestrict(pluginData: GuildPluginData) { + return pluginData.config.get().attachment_link_reaction === "restrict"; +} + +export function detectAttachmentLink(reason: string | null | undefined) { + return reason && /https:\/\/(cdn|media)\.discordapp\.(com|net)\/(ephemeral-)?attachments/gu.test(reason); +} + +export function sendAttachmentLinkDetectionErrorMessage( + pluginData: AnyPluginData, + context: TextBasedChannel | Message | ChatInputCommandInteraction, + restricted = false, +) { + const emoji = pluginData.getPlugin(CommonPlugin).getErrorEmoji(); + + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + context, + "You manually added a Discord attachment link to the reason. This link will only work for a limited time.\n" + + "You should instead **re-upload** the attachment with the command, in the same message.\n\n" + + (restricted ? `${emoji} **Command canceled.** ${emoji}` : "").trim(), + ); +} + +export async function handleAttachmentLinkDetectionAndGetRestriction( + pluginData: GuildPluginData, + context: TextBasedChannel | Message | ChatInputCommandInteraction, + reason: string | null | undefined, +) { + if (!shouldReactToAttachmentLink(pluginData) || !detectAttachmentLink(reason)) { + return false; + } + + const restricted = attachmentLinkShouldRestrict(pluginData); + + sendAttachmentLinkDetectionErrorMessage(pluginData, context, restricted); + + return restricted; +} diff --git a/backend/src/plugins/ModActions/functions/banUserId.ts b/backend/src/plugins/ModActions/functions/banUserId.ts index d9d1454b2..55cc00497 100644 --- a/backend/src/plugins/ModActions/functions/banUserId.ts +++ b/backend/src/plugins/ModActions/functions/banUserId.ts @@ -30,6 +30,7 @@ export async function banUserId( pluginData: GuildPluginData, userId: string, reason?: string, + reasonWithAttachments?: string, banOptions: BanOptions = {}, banTime?: number, ): Promise { @@ -45,7 +46,7 @@ export async function banUserId( // Attempt to message the user *before* banning them, as doing it after may not be possible const member = await resolveMember(pluginData.client, pluginData.guild, userId); let notifyResult: UserNotificationResult = { method: null, success: true }; - if (reason && member) { + if (reasonWithAttachments && member) { const contactMethods = banOptions?.contactMethods ? banOptions.contactMethods : getDefaultContactMethods(pluginData, "ban"); @@ -56,7 +57,7 @@ export async function banUserId( config.ban_message, new TemplateSafeValueContainer({ guildName: pluginData.guild.name, - reason, + reason: reasonWithAttachments, moderator: banOptions.caseArgs?.modId ? userToTemplateSafeUser(await resolveUser(pluginData.client, banOptions.caseArgs.modId)) : null, @@ -69,7 +70,7 @@ export async function banUserId( config.tempban_message, new TemplateSafeValueContainer({ guildName: pluginData.guild.name, - reason, + reason: reasonWithAttachments, moderator: banOptions.caseArgs?.modId ? userToTemplateSafeUser(await resolveUser(pluginData.client, banOptions.caseArgs.modId)) : null, diff --git a/backend/src/plugins/ModActions/functions/clearTempban.ts b/backend/src/plugins/ModActions/functions/clearTempban.ts index 25807df85..1b196db09 100644 --- a/backend/src/plugins/ModActions/functions/clearTempban.ts +++ b/backend/src/plugins/ModActions/functions/clearTempban.ts @@ -10,7 +10,6 @@ import { resolveUser } from "../../../utils"; import { CasesPlugin } from "../../Cases/CasesPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../types"; -import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; import { ignoreEvent } from "./ignoreEvent"; import { isBanned } from "./isBanned"; @@ -21,11 +20,9 @@ export async function clearTempban(pluginData: GuildPluginData, + reason: string, + context: Message | ChatInputCommandInteraction, + attachments: Attachment[], +) { + if (isContextMessage(context)) { + return context.attachments.size > 0 ? ((reason || "") + " " + context.url).trim() : reason; + } + + if (attachments.length < 1) { + return reason; + } + + const attachmentChannelId = pluginData.config.get().attachment_storing_channel; + const channel = attachmentChannelId + ? (pluginData.guild.channels.cache.get(attachmentChannelId) as TextBasedChannel) ?? context.channel + : context.channel; + + const message = await channel!.send({ + content: `Storing ${attachments.length} attachment${attachments.length === 1 ? "" : "s"}`, + files: attachments.map((a) => a.url), + }); + + return ((reason || "") + " " + message.url).trim(); +} + +export function formatReasonWithAttachments(reason: string, attachments: Attachment[]) { + const attachmentUrls = attachments.map((a) => a.url); + return ((reason || "") + " " + attachmentUrls.join(" ")).trim(); +} diff --git a/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts b/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts deleted file mode 100644 index 3fd92ee84..000000000 --- a/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Attachment } from "discord.js"; - -export function formatReasonWithAttachments(reason: string, attachments: Attachment[]) { - const attachmentUrls = attachments.map((a) => a.url); - return ((reason || "") + " " + attachmentUrls.join(" ")).trim(); -} diff --git a/backend/src/plugins/ModActions/functions/kickMember.ts b/backend/src/plugins/ModActions/functions/kickMember.ts index d54dfd159..22c6eb63a 100644 --- a/backend/src/plugins/ModActions/functions/kickMember.ts +++ b/backend/src/plugins/ModActions/functions/kickMember.ts @@ -18,13 +18,14 @@ export async function kickMember( pluginData: GuildPluginData, member: GuildMember, reason?: string, + reasonWithAttachments?: string, kickOptions: KickOptions = {}, ): Promise { const config = pluginData.config.get(); // Attempt to message the user *before* kicking them, as doing it after may not be possible let notifyResult: UserNotificationResult = { method: null, success: true }; - if (reason && member) { + if (reasonWithAttachments && member) { const contactMethods = kickOptions?.contactMethods ? kickOptions.contactMethods : getDefaultContactMethods(pluginData, "kick"); @@ -35,7 +36,7 @@ export async function kickMember( config.kick_message, new TemplateSafeValueContainer({ guildName: pluginData.guild.name, - reason, + reason: reasonWithAttachments, moderator: kickOptions.caseArgs?.modId ? userToTemplateSafeUser(await resolveUser(pluginData.client, kickOptions.caseArgs.modId)) : null, diff --git a/backend/src/plugins/ModActions/functions/updateCase.ts b/backend/src/plugins/ModActions/functions/updateCase.ts index d715e2637..78b5d6f44 100644 --- a/backend/src/plugins/ModActions/functions/updateCase.ts +++ b/backend/src/plugins/ModActions/functions/updateCase.ts @@ -1,19 +1,19 @@ -import { Attachment, ChatInputCommandInteraction, TextBasedChannel, User } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, Message, User } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; import { Case } from "../../../data/entities/Case"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../types"; -import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; +import { formatReasonWithMessageLinkForAttachments } from "./formatReasonForAttachments"; export async function updateCase( pluginData: GuildPluginData, - context: TextBasedChannel | ChatInputCommandInteraction, + context: Message | ChatInputCommandInteraction, author: User, caseNumber?: number, - note?: string, + note = "", attachments: Attachment[] = [], ) { let theCase: Case | null; @@ -24,16 +24,16 @@ export async function updateCase( } if (!theCase) { - sendErrorMessage(pluginData, context, "Case not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Case not found"); return; } - if (!note && attachments.length === 0) { - sendErrorMessage(pluginData, context, "Text or attachment required"); + if (note.length === 0 && attachments.length === 0) { + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Text or attachment required"); return; } - const formattedNote = formatReasonWithAttachments(note ?? "", attachments); + const formattedNote = await formatReasonWithMessageLinkForAttachments(pluginData, note, context, attachments); const casesPlugin = pluginData.getPlugin(CasesPlugin); await casesPlugin.createCaseNote({ @@ -49,5 +49,5 @@ export async function updateCase( note: formattedNote, }); - sendSuccessMessage(pluginData, context, `Case \`#${theCase.case_number}\` updated`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, `Case \`#${theCase.case_number}\` updated`); } diff --git a/backend/src/plugins/ModActions/functions/warnMember.ts b/backend/src/plugins/ModActions/functions/warnMember.ts index 8f58f915b..004caf7fe 100644 --- a/backend/src/plugins/ModActions/functions/warnMember.ts +++ b/backend/src/plugins/ModActions/functions/warnMember.ts @@ -1,7 +1,7 @@ import { GuildMember, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; -import { isContextInteraction } from "../../../pluginUtils"; +import { getContextChannel, isContextInteraction } from "../../../pluginUtils"; import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { UserNotificationResult, createUserNotificationError, notifyUser, resolveUser, ucfirst } from "../../../utils"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; @@ -15,6 +15,7 @@ export async function warnMember( pluginData: GuildPluginData, member: GuildMember, reason: string, + reasonWithAttachments: string, warnOptions: WarnOptions = {}, ): Promise { const config = pluginData.config.get(); @@ -25,7 +26,7 @@ export async function warnMember( config.warn_message, new TemplateSafeValueContainer({ guildName: pluginData.guild.name, - reason, + reason: reasonWithAttachments, moderator: warnOptions.caseArgs?.modId ? userToTemplateSafeUser(await resolveUser(pluginData.client, warnOptions.caseArgs.modId)) : null, @@ -40,8 +41,10 @@ export async function warnMember( } if (!notifyResult.success) { - const contextIsChannel = warnOptions.retryPromptContext && !isContextInteraction(warnOptions.retryPromptContext); - const isValidChannel = contextIsChannel && pluginData.guild.channels.resolve(warnOptions.retryPromptContext!.id); + const contextIsNotInteraction = + warnOptions.retryPromptContext && !isContextInteraction(warnOptions.retryPromptContext); + const contextChannel = contextIsNotInteraction ? await getContextChannel(warnOptions.retryPromptContext!) : null; + const isValidChannel = contextIsNotInteraction && pluginData.guild.channels.resolve(contextChannel!.id); if (!warnOptions.retryPromptContext || !isValidChannel) { return { diff --git a/backend/src/plugins/ModActions/types.ts b/backend/src/plugins/ModActions/types.ts index 5a61c3423..68a0acdab 100644 --- a/backend/src/plugins/ModActions/types.ts +++ b/backend/src/plugins/ModActions/types.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js"; +import { ChatInputCommandInteraction, Message } from "discord.js"; import { EventEmitter } from "events"; import * as t from "io-ts"; import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, guildPluginSlashGroup } from "knub"; @@ -11,6 +11,8 @@ import { Case } from "../../data/entities/Case"; import { UserNotificationMethod, UserNotificationResult, tNullable } from "../../utils"; import { CaseArgs } from "../Cases/types"; +export type AttachmentLinkReactionType = "none" | "warn" | "restrict" | null | undefined; + export const ConfigSchema = t.type({ dm_on_warn: t.boolean, dm_on_kick: t.boolean, @@ -29,6 +31,8 @@ export const ConfigSchema = t.type({ warn_notify_threshold: t.number, warn_notify_message: t.string, ban_delete_message_days: t.number, + attachment_link_reaction: tNullable(t.union([t.literal("none"), t.literal("warn"), t.literal("restrict")])), + attachment_storing_channel: tNullable(t.string), can_note: t.boolean, can_warn: t.boolean, can_mute: t.boolean, @@ -127,7 +131,7 @@ export type WarnMemberNotifyRetryCallback = () => boolean | Promise; export interface WarnOptions { caseArgs?: Partial | null; contactMethods?: UserNotificationMethod[] | null; - retryPromptContext?: TextBasedChannel | ChatInputCommandInteraction | null; + retryPromptContext?: Message | ChatInputCommandInteraction | null; isAutomodAction?: boolean; } diff --git a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts index 29f9dc287..3884ae891 100644 --- a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts @@ -1,5 +1,5 @@ import { Snowflake } from "discord.js"; -import { sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { mutesCmd } from "../types"; export const ClearBannedMutesCmd = mutesCmd({ @@ -25,6 +25,6 @@ export const ClearBannedMutesCmd = mutesCmd({ } } - sendSuccessMessage(pluginData, msg.channel, `Cleared ${cleared} mutes from banned users!`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Cleared ${cleared} mutes from banned users!`); }, }); diff --git a/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts index 3021a7a71..a71dc4025 100644 --- a/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { mutesCmd } from "../types"; export const ClearMutesCmd = mutesCmd({ @@ -23,15 +23,18 @@ export const ClearMutesCmd = mutesCmd({ } if (failed.length !== args.userIds.length) { - sendSuccessMessage(pluginData, msg.channel, `**${args.userIds.length - failed.length} active mute(s) cleared**`); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `**${args.userIds.length - failed.length} active mute(s) cleared**`); } if (failed.length) { - sendErrorMessage( - pluginData, - msg.channel, - `**${failed.length}/${args.userIds.length} IDs failed**, they are not muted: ${failed.join(" ")}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + msg, + `**${failed.length}/${args.userIds.length} IDs failed**, they are not muted: ${failed.join(" ")}`, + ); } }, }); diff --git a/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts b/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts index 0dbef291e..1e52f6b3d 100644 --- a/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts @@ -1,6 +1,6 @@ import { Snowflake } from "discord.js"; -import { sendSuccessMessage } from "../../../pluginUtils"; import { resolveMember } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { mutesCmd } from "../types"; export const ClearMutesWithoutRoleCmd = mutesCmd({ @@ -26,6 +26,8 @@ export const ClearMutesWithoutRoleCmd = mutesCmd({ } } - sendSuccessMessage(pluginData, msg.channel, `Cleared ${cleared} mutes from members that don't have the mute role`); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `Cleared ${cleared} mutes from members that don't have the mute role`); }, }); diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index 575f96fb8..2dd5a3d1e 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -35,6 +35,7 @@ export async function muteUser( userId: string, muteTime?: number, reason?: string, + reasonWithAttachments?: string, muteOptions: MuteOptions = {}, removeRolesOnMuteOverride: boolean | string[] | null = null, restoreRolesOnMuteOverride: boolean | string[] | null = null, @@ -193,7 +194,7 @@ export async function muteUser( template, new TemplateSafeValueContainer({ guildName: pluginData.guild.name, - reason: reason || "None", + reason: reasonWithAttachments || "None", time: timeUntilUnmuteStr, moderator: muteOptions.caseArgs?.modId ? userToTemplateSafeUser(await resolveUser(pluginData.client, muteOptions.caseArgs.modId)) diff --git a/backend/src/plugins/NameHistory/commands/NamesCmd.ts b/backend/src/plugins/NameHistory/commands/NamesCmd.ts index 373f1671c..d7ef50b03 100644 --- a/backend/src/plugins/NameHistory/commands/NamesCmd.ts +++ b/backend/src/plugins/NameHistory/commands/NamesCmd.ts @@ -4,8 +4,8 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { MAX_NICKNAME_ENTRIES_PER_USER } from "../../../data/GuildNicknameHistory"; import { MAX_USERNAME_ENTRIES_PER_USER } from "../../../data/UsernameHistory"; import { NICKNAME_RETENTION_PERIOD } from "../../../data/cleanup/nicknames"; -import { sendErrorMessage } from "../../../pluginUtils"; import { DAYS, renderUserUsername } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { nameHistoryCmd } from "../types"; export const NamesCmd = nameHistoryCmd({ @@ -21,7 +21,7 @@ export const NamesCmd = nameHistoryCmd({ const usernames = await pluginData.state.usernameHistory.getByUserId(args.userId); if (nicknames.length === 0 && usernames.length === 0) { - sendErrorMessage(pluginData, msg.channel, "No name history found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No name history found"); return; } diff --git a/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts b/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts index 41709ffcc..00006ceef 100644 --- a/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts +++ b/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { pingableRolesCmd } from "../types"; export const PingableRoleDisableCmd = pingableRolesCmd({ @@ -14,17 +14,17 @@ export const PingableRoleDisableCmd = pingableRolesCmd({ async run({ message: msg, args, pluginData }) { const pingableRole = await pluginData.state.pingableRoles.getByChannelAndRoleId(args.channelId, args.role.id); if (!pingableRole) { - sendErrorMessage(pluginData, msg.channel, `**${args.role.name}** is not set as pingable in <#${args.channelId}>`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `**${args.role.name}** is not set as pingable in <#${args.channelId}>`); return; } await pluginData.state.pingableRoles.delete(args.channelId, args.role.id); pluginData.state.cache.delete(args.channelId); - sendSuccessMessage( - pluginData, - msg.channel, - `**${args.role.name}** is no longer set as pingable in <#${args.channelId}>`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `**${args.role.name}** is no longer set as pingable in <#${args.channelId}>`); }, }); diff --git a/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts b/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts index 30077e091..7a93990fe 100644 --- a/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts +++ b/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { pingableRolesCmd } from "../types"; export const PingableRoleEnableCmd = pingableRolesCmd({ @@ -17,21 +17,17 @@ export const PingableRoleEnableCmd = pingableRolesCmd({ args.role.id, ); if (existingPingableRole) { - sendErrorMessage( - pluginData, - msg.channel, - `**${args.role.name}** is already set as pingable in <#${args.channelId}>`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `**${args.role.name}** is already set as pingable in <#${args.channelId}>`); return; } await pluginData.state.pingableRoles.add(args.channelId, args.role.id); pluginData.state.cache.delete(args.channelId); - sendSuccessMessage( - pluginData, - msg.channel, - `**${args.role.name}** has been set as pingable in <#${args.channelId}>`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `**${args.role.name}** has been set as pingable in <#${args.channelId}>`); }, }); diff --git a/backend/src/plugins/Post/commands/EditCmd.ts b/backend/src/plugins/Post/commands/EditCmd.ts index 47ae07d7a..cd607aa60 100644 --- a/backend/src/plugins/Post/commands/EditCmd.ts +++ b/backend/src/plugins/Post/commands/EditCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; import { formatContent } from "../util/formatContent"; @@ -15,18 +15,18 @@ export const EditCmd = postCmd({ async run({ message: msg, args, pluginData }) { const targetMessage = await args.message.channel.messages.fetch(args.message.messageId); if (!targetMessage) { - sendErrorMessage(pluginData, msg.channel, "Unknown message"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown message"); return; } if (targetMessage.author.id !== pluginData.client.user!.id) { - sendErrorMessage(pluginData, msg.channel, "Message wasn't posted by me"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Message wasn't posted by me"); return; } targetMessage.channel.messages.edit(targetMessage.id, { content: formatContent(args.content), }); - sendSuccessMessage(pluginData, msg.channel, "Message edited"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Message edited"); }, }); diff --git a/backend/src/plugins/Post/commands/EditEmbedCmd.ts b/backend/src/plugins/Post/commands/EditEmbedCmd.ts index 2ac364f15..7d42c0408 100644 --- a/backend/src/plugins/Post/commands/EditEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/EditEmbedCmd.ts @@ -1,9 +1,9 @@ import { APIEmbed } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { isValidEmbed, trimLines } from "../../../utils"; import { parseColor } from "../../../utils/parseColor"; import { rgbToInt } from "../../../utils/rgbToInt"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; import { formatContent } from "../util/formatContent"; @@ -30,14 +30,14 @@ export const EditEmbedCmd = postCmd({ if (colorRgb) { color = rgbToInt(colorRgb); } else { - sendErrorMessage(pluginData, msg.channel, "Invalid color specified"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid color specified"); return; } } const targetMessage = await args.message.channel.messages.fetch(args.message.messageId); if (!targetMessage) { - sendErrorMessage(pluginData, msg.channel, "Unknown message"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown message"); return; } @@ -51,12 +51,12 @@ export const EditEmbedCmd = postCmd({ try { parsed = JSON.parse(content); } catch (e) { - sendErrorMessage(pluginData, msg.channel, `Syntax error in embed JSON: ${e.message}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Syntax error in embed JSON: ${e.message}`); return; } if (!isValidEmbed(parsed)) { - sendErrorMessage(pluginData, msg.channel, "Embed is not valid"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Embed is not valid"); return; } @@ -69,7 +69,7 @@ export const EditEmbedCmd = postCmd({ args.message.channel.messages.edit(targetMessage.id, { embeds: [embed], }); - await sendSuccessMessage(pluginData, msg.channel, "Embed edited"); + await pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Embed edited"); if (args.content) { const prefix = pluginData.fullConfig.prefix || "!"; diff --git a/backend/src/plugins/Post/commands/PostEmbedCmd.ts b/backend/src/plugins/Post/commands/PostEmbedCmd.ts index e605402b0..a78abacb6 100644 --- a/backend/src/plugins/Post/commands/PostEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/PostEmbedCmd.ts @@ -1,9 +1,9 @@ import { APIEmbed } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { isValidEmbed, trimLines } from "../../../utils"; import { parseColor } from "../../../utils/parseColor"; import { rgbToInt } from "../../../utils/rgbToInt"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; import { actualPostCmd } from "../util/actualPostCmd"; import { formatContent } from "../util/formatContent"; @@ -31,7 +31,7 @@ export const PostEmbedCmd = postCmd({ const content = args.content || args.maincontent; if (!args.title && !content) { - sendErrorMessage(pluginData, msg.channel, "Title or content required"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Title or content required"); return; } @@ -41,7 +41,7 @@ export const PostEmbedCmd = postCmd({ if (colorRgb) { color = rgbToInt(colorRgb); } else { - sendErrorMessage(pluginData, msg.channel, "Invalid color specified"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid color specified"); return; } } @@ -56,12 +56,12 @@ export const PostEmbedCmd = postCmd({ try { parsed = JSON.parse(content); } catch (e) { - sendErrorMessage(pluginData, msg.channel, `Syntax error in embed JSON: ${e.message}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Syntax error in embed JSON: ${e.message}`); return; } if (!isValidEmbed(parsed)) { - sendErrorMessage(pluginData, msg.channel, "Embed is not valid"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Embed is not valid"); return; } diff --git a/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts index 78becae89..565135e6e 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts @@ -1,7 +1,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { clearUpcomingScheduledPost } from "../../../data/loops/upcomingScheduledPostsLoop"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { sorter } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; export const ScheduledPostsDeleteCmd = postCmd({ @@ -17,12 +17,12 @@ export const ScheduledPostsDeleteCmd = postCmd({ scheduledPosts.sort(sorter("post_at")); const post = scheduledPosts[args.num - 1]; if (!post) { - sendErrorMessage(pluginData, msg.channel, "Scheduled post not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Scheduled post not found"); return; } clearUpcomingScheduledPost(post); await pluginData.state.scheduledPosts.delete(post.id); - sendSuccessMessage(pluginData, msg.channel, "Scheduled post deleted!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Scheduled post deleted!"); }, }); diff --git a/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts index 990bfbaee..2895cc688 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { sorter } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; import { postMessage } from "../util/postMessage"; @@ -17,7 +17,7 @@ export const ScheduledPostsShowCmd = postCmd({ scheduledPosts.sort(sorter("post_at")); const post = scheduledPosts[args.num - 1]; if (!post) { - sendErrorMessage(pluginData, msg.channel, "Scheduled post not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Scheduled post not found"); return; } diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index c73a672bc..fb564499a 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -3,8 +3,8 @@ import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; import { registerUpcomingScheduledPost } from "../../../data/loops/upcomingScheduledPostsLoop"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { DBDateFormat, MINUTES, StrictMessageContent, errorMessage } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { PostPluginType } from "../types"; @@ -40,11 +40,15 @@ export async function actualPostCmd( if (opts.repeat) { if (opts.repeat < MIN_REPEAT_TIME) { - sendErrorMessage(pluginData, msg.channel, `Minimum time for -repeat is ${humanizeDuration(MIN_REPEAT_TIME)}`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Minimum time for -repeat is ${humanizeDuration(MIN_REPEAT_TIME)}`); return; } if (opts.repeat > MAX_REPEAT_TIME) { - sendErrorMessage(pluginData, msg.channel, `Max time for -repeat is ${humanizeDuration(MAX_REPEAT_TIME)}`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Max time for -repeat is ${humanizeDuration(MAX_REPEAT_TIME)}`); return; } } @@ -55,7 +59,7 @@ export async function actualPostCmd( // Schedule the post to be posted later postAt = await parseScheduleTime(pluginData, msg.author.id, opts.schedule); if (!postAt) { - sendErrorMessage(pluginData, msg.channel, "Invalid schedule time"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid schedule time"); return; } } else if (opts.repeat) { @@ -72,36 +76,41 @@ export async function actualPostCmd( // Invalid time if (!repeatUntil) { - sendErrorMessage(pluginData, msg.channel, "Invalid time specified for -repeat-until"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid time specified for -repeat-until"); return; } if (repeatUntil.isBefore(moment.utc())) { - sendErrorMessage(pluginData, msg.channel, "You can't set -repeat-until in the past"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You can't set -repeat-until in the past"); return; } if (repeatUntil.isAfter(MAX_REPEAT_UNTIL)) { - sendErrorMessage( - pluginData, - msg.channel, - "Unfortunately, -repeat-until can only be at most 100 years into the future. Maybe 99 years would be enough?", - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + msg, + "Unfortunately, -repeat-until can only be at most 100 years into the future. Maybe 99 years would be enough?", + ); return; } } else if (opts["repeat-times"]) { repeatTimes = opts["repeat-times"]; if (repeatTimes <= 0) { - sendErrorMessage(pluginData, msg.channel, "-repeat-times must be 1 or more"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "-repeat-times must be 1 or more"); return; } } if (repeatUntil && repeatTimes) { - sendErrorMessage(pluginData, msg.channel, "You can only use one of -repeat-until or -repeat-times at once"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "You can only use one of -repeat-until or -repeat-times at once"); return; } if (opts.repeat && !repeatUntil && !repeatTimes) { - sendErrorMessage(pluginData, msg.channel, "You must specify -repeat-until or -repeat-times for repeated messages"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "You must specify -repeat-until or -repeat-times for repeated messages"); return; } @@ -116,7 +125,7 @@ export async function actualPostCmd( // Save schedule/repeat information in DB if (postAt) { if (postAt < moment.utc()) { - sendErrorMessage(pluginData, msg.channel, "Post can't be scheduled to be posted in the past"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Post can't be scheduled to be posted in the past"); return; } @@ -192,6 +201,6 @@ export async function actualPostCmd( } if (targetChannel.id !== msg.channel.id || opts.schedule || opts.repeat) { - sendSuccessMessage(pluginData, msg.channel, successMessage); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, successMessage); } } diff --git a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts index ff88b1948..1dc632a7a 100644 --- a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts @@ -1,7 +1,7 @@ import { Message } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { isDiscordAPIError } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { reactionRolesCmd } from "../types"; export const ClearReactionRolesCmd = reactionRolesCmd({ @@ -15,7 +15,7 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ async run({ message: msg, args, pluginData }) { const existingReactionRoles = pluginData.state.reactionRoles.getForMessage(args.message.messageId); if (!existingReactionRoles) { - sendErrorMessage(pluginData, msg.channel, "Message doesn't have reaction roles on it"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Message doesn't have reaction roles on it"); return; } @@ -26,7 +26,7 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ targetMessage = await args.message.channel.messages.fetch(args.message.messageId); } catch (err) { if (isDiscordAPIError(err) && err.code === 50001) { - sendErrorMessage(pluginData, msg.channel, "Missing access to the specified message"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Missing access to the specified message"); return; } @@ -35,6 +35,6 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ await targetMessage.reactions.removeAll(); - sendSuccessMessage(pluginData, msg.channel, "Reaction roles cleared"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Reaction roles cleared"); }, }); diff --git a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts index fefc166e5..26d490393 100644 --- a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts @@ -1,8 +1,8 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { canUseEmoji, isDiscordAPIError, isValidEmoji, noop, trimPluginDescription } from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { TReactionRolePair, reactionRolesCmd } from "../types"; import { applyReactionRoleReactionsToMessage } from "../util/applyReactionRoleReactionsToMessage"; @@ -34,7 +34,9 @@ export const InitReactionRolesCmd = reactionRolesCmd({ async run({ message: msg, args, pluginData }) { if (!canReadChannel(args.message.channel, msg.member)) { - sendErrorMessage(pluginData, msg.channel, "You can't add reaction roles to channels you can't see yourself"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "You can't add reaction roles to channels you can't see yourself"); return; } @@ -43,7 +45,7 @@ export const InitReactionRolesCmd = reactionRolesCmd({ targetMessage = await args.message.channel.messages.fetch(args.message.messageId); } catch (e) { if (isDiscordAPIError(e)) { - sendErrorMessage(pluginData, msg.channel, `Error ${e.code} while getting message: ${e.message}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Error ${e.code} while getting message: ${e.message}`); return; } @@ -71,30 +73,26 @@ export const InitReactionRolesCmd = reactionRolesCmd({ // Verify the specified emojis and roles are valid and usable for (const pair of emojiRolePairs) { if (pair[0] === CLEAR_ROLES_EMOJI) { - sendErrorMessage( - pluginData, - msg.channel, - `The emoji for clearing roles (${CLEAR_ROLES_EMOJI}) is reserved and cannot be used`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `The emoji for clearing roles (${CLEAR_ROLES_EMOJI}) is reserved and cannot be used`); return; } if (!isValidEmoji(pair[0])) { - sendErrorMessage(pluginData, msg.channel, `Invalid emoji: ${pair[0]}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Invalid emoji: ${pair[0]}`); return; } if (!canUseEmoji(pluginData.client, pair[0])) { - sendErrorMessage( - pluginData, - msg.channel, - "I can only use regular emojis and custom emojis from servers I'm on", - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "I can only use regular emojis and custom emojis from servers I'm on"); return; } if (!pluginData.guild.roles.cache.has(pair[1] as Snowflake)) { - sendErrorMessage(pluginData, msg.channel, `Unknown role ${pair[1]}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Unknown role ${pair[1]}`); return; } } @@ -125,9 +123,11 @@ export const InitReactionRolesCmd = reactionRolesCmd({ ); if (errors?.length) { - sendErrorMessage(pluginData, msg.channel, `Errors while adding reaction roles:\n${errors.join("\n")}`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Errors while adding reaction roles:\n${errors.join("\n")}`); } else { - sendSuccessMessage(pluginData, msg.channel, "Reaction roles added"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Reaction roles added"); } (await progressMessage).delete().catch(noop); diff --git a/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts index 3a55f6ba7..6d26ff482 100644 --- a/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { reactionRolesCmd } from "../types"; import { refreshReactionRoles } from "../util/refreshReactionRoles"; @@ -13,12 +13,12 @@ export const RefreshReactionRolesCmd = reactionRolesCmd({ async run({ message: msg, args, pluginData }) { if (pluginData.state.pendingRefreshes.has(`${args.message.channel.id}-${args.message.messageId}`)) { - sendErrorMessage(pluginData, msg.channel, "Another refresh in progress"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Another refresh in progress"); return; } await refreshReactionRoles(pluginData, args.message.channel.id, args.message.messageId); - sendSuccessMessage(pluginData, msg.channel, "Reaction roles refreshed"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Reaction roles refreshed"); }, }); diff --git a/backend/src/plugins/Reminders/commands/RemindCmd.ts b/backend/src/plugins/Reminders/commands/RemindCmd.ts index c46557710..88ceeaa35 100644 --- a/backend/src/plugins/Reminders/commands/RemindCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindCmd.ts @@ -2,8 +2,8 @@ import humanizeDuration from "humanize-duration"; import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { registerUpcomingReminder } from "../../../data/loops/upcomingRemindersLoop"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { convertDelayStringToMS, messageLink } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { remindersCmd } from "../types"; @@ -38,7 +38,7 @@ export const RemindCmd = remindersCmd({ // "Delay string" i.e. e.g. "2h30m" const ms = convertDelayStringToMS(args.time); if (ms === null) { - sendErrorMessage(pluginData, msg.channel, "Invalid reminder time"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid reminder time"); return; } @@ -46,7 +46,7 @@ export const RemindCmd = remindersCmd({ } if (!reminderTime.isValid() || reminderTime.isBefore(now)) { - sendErrorMessage(pluginData, msg.channel, "Invalid reminder time"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid reminder time"); return; } @@ -67,10 +67,8 @@ export const RemindCmd = remindersCmd({ pluginData.getPlugin(TimeAndDatePlugin).getDateFormat("pretty_datetime"), ); - sendSuccessMessage( - pluginData, - msg.channel, - `I will remind you in **${timeUntilReminder}** at **${prettyReminderTime}**`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `I will remind you in **${timeUntilReminder}** at **${prettyReminderTime}**`); }, }); diff --git a/backend/src/plugins/Reminders/commands/RemindersCmd.ts b/backend/src/plugins/Reminders/commands/RemindersCmd.ts index ce914a344..7b2da1507 100644 --- a/backend/src/plugins/Reminders/commands/RemindersCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindersCmd.ts @@ -1,7 +1,7 @@ import humanizeDuration from "humanize-duration"; import moment from "moment-timezone"; -import { sendErrorMessage } from "../../../pluginUtils"; import { createChunkedMessage, DBDateFormat, sorter } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { remindersCmd } from "../types"; @@ -12,7 +12,7 @@ export const RemindersCmd = remindersCmd({ async run({ message: msg, pluginData }) { const reminders = await pluginData.state.reminders.getRemindersByUserId(msg.author.id); if (reminders.length === 0) { - sendErrorMessage(pluginData, msg.channel, "No reminders"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No reminders"); return; } diff --git a/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts b/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts index 25713a4fc..5566a1339 100644 --- a/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts @@ -1,7 +1,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { clearUpcomingReminder } from "../../../data/loops/upcomingRemindersLoop"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { sorter } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { remindersCmd } from "../types"; export const RemindersDeleteCmd = remindersCmd({ @@ -17,7 +17,7 @@ export const RemindersDeleteCmd = remindersCmd({ reminders.sort(sorter("remind_at")); if (args.num > reminders.length || args.num <= 0) { - sendErrorMessage(pluginData, msg.channel, "Unknown reminder"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown reminder"); return; } @@ -25,6 +25,6 @@ export const RemindersDeleteCmd = remindersCmd({ clearUpcomingReminder(toDelete); await pluginData.state.reminders.delete(toDelete.id); - sendSuccessMessage(pluginData, msg.channel, "Reminder deleted"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Reminder deleted"); }, }); diff --git a/backend/src/plugins/RoleButtons/commands/resetButtons.ts b/backend/src/plugins/RoleButtons/commands/resetButtons.ts index cca8e643d..edd99eb05 100644 --- a/backend/src/plugins/RoleButtons/commands/resetButtons.ts +++ b/backend/src/plugins/RoleButtons/commands/resetButtons.ts @@ -1,6 +1,6 @@ import { guildPluginMessageCommand } from "knub"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { applyAllRoleButtons } from "../functions/applyAllRoleButtons"; import { RoleButtonsPluginType } from "../types"; @@ -16,12 +16,14 @@ export const resetButtonsCmd = guildPluginMessageCommand( async run({ pluginData, args, message }) { const config = pluginData.config.get(); if (!config.buttons[args.name]) { - sendErrorMessage(pluginData, message.channel, `Can't find role buttons with the name "${args.name}"`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(message, `Can't find role buttons with the name "${args.name}"`); return; } await pluginData.state.roleButtons.deleteRoleButtonItem(args.name); await applyAllRoleButtons(pluginData); - sendSuccessMessage(pluginData, message.channel, "Done!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(message, "Done!"); }, }); diff --git a/backend/src/plugins/Roles/commands/AddRoleCmd.ts b/backend/src/plugins/Roles/commands/AddRoleCmd.ts index ccd64325e..a66ff3655 100644 --- a/backend/src/plugins/Roles/commands/AddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/AddRoleCmd.ts @@ -1,7 +1,8 @@ import { GuildChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { canActOn } from "../../../pluginUtils"; import { resolveRoleId, verboseUserMention } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin"; import { rolesCmd } from "../types"; @@ -18,19 +19,21 @@ export const AddRoleCmd = rolesCmd({ async run({ message: msg, args, pluginData }) { if (!canActOn(pluginData, msg.member, args.member, true)) { - sendErrorMessage(pluginData, msg.channel, "Cannot add roles to this user: insufficient permissions"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "Cannot add roles to this user: insufficient permissions"); return; } const roleId = await resolveRoleId(pluginData.client, pluginData.guild.id, args.role); if (!roleId) { - sendErrorMessage(pluginData, msg.channel, "Invalid role id"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid role id"); return; } const config = await pluginData.config.getForMessage(msg); if (!config.assignable_roles.includes(roleId)) { - sendErrorMessage(pluginData, msg.channel, "You cannot assign that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot assign that role"); return; } @@ -40,12 +43,12 @@ export const AddRoleCmd = rolesCmd({ pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); - sendErrorMessage(pluginData, msg.channel, "You cannot assign that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot assign that role"); return; } if (args.member.roles.cache.has(roleId)) { - sendErrorMessage(pluginData, msg.channel, "Member already has that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member already has that role"); return; } @@ -57,10 +60,8 @@ export const AddRoleCmd = rolesCmd({ roles: [role], }); - sendSuccessMessage( - pluginData, - msg.channel, - `Added role **${role.name}** to ${verboseUserMention(args.member.user)}!`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `Added role **${role.name}** to ${verboseUserMention(args.member.user)}!`); }, }); diff --git a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts index 9030e45c5..05db86ba0 100644 --- a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts @@ -1,8 +1,9 @@ import { GuildMember } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { logger } from "../../../logger"; -import { canActOn, sendErrorMessage } from "../../../pluginUtils"; +import { canActOn } from "../../../pluginUtils"; import { resolveMember, resolveRoleId, successMessage } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin"; import { rolesCmd } from "../types"; @@ -29,24 +30,22 @@ export const MassAddRoleCmd = rolesCmd({ for (const member of members) { if (!canActOn(pluginData, msg.member, member, true)) { - sendErrorMessage( - pluginData, - msg.channel, - "Cannot add roles to 1 or more specified members: insufficient permissions", - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "Cannot add roles to 1 or more specified members: insufficient permissions"); return; } } const roleId = await resolveRoleId(pluginData.client, pluginData.guild.id, args.role); if (!roleId) { - sendErrorMessage(pluginData, msg.channel, "Invalid role id"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid role id"); return; } const config = await pluginData.config.getForMessage(msg); if (!config.assignable_roles.includes(roleId)) { - sendErrorMessage(pluginData, msg.channel, "You cannot assign that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot assign that role"); return; } @@ -55,7 +54,7 @@ export const MassAddRoleCmd = rolesCmd({ pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); - sendErrorMessage(pluginData, msg.channel, "You cannot assign that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot assign that role"); return; } diff --git a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts index de4591b44..06a5337d1 100644 --- a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts @@ -1,7 +1,8 @@ import { GuildMember } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage } from "../../../pluginUtils"; +import { canActOn } from "../../../pluginUtils"; import { resolveMember, resolveRoleId, successMessage } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin"; import { rolesCmd } from "../types"; @@ -28,24 +29,22 @@ export const MassRemoveRoleCmd = rolesCmd({ for (const member of members) { if (!canActOn(pluginData, msg.member, member, true)) { - sendErrorMessage( - pluginData, - msg.channel, - "Cannot add roles to 1 or more specified members: insufficient permissions", - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "Cannot add roles to 1 or more specified members: insufficient permissions"); return; } } const roleId = await resolveRoleId(pluginData.client, pluginData.guild.id, args.role); if (!roleId) { - sendErrorMessage(pluginData, msg.channel, "Invalid role id"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid role id"); return; } const config = await pluginData.config.getForMessage(msg); if (!config.assignable_roles.includes(roleId)) { - sendErrorMessage(pluginData, msg.channel, "You cannot remove that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot remove that role"); return; } @@ -54,7 +53,7 @@ export const MassRemoveRoleCmd = rolesCmd({ pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); - sendErrorMessage(pluginData, msg.channel, "You cannot remove that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot remove that role"); return; } diff --git a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts index 8cd6f3064..e3dc979d4 100644 --- a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts @@ -1,7 +1,8 @@ import { GuildChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { canActOn } from "../../../pluginUtils"; import { resolveRoleId, verboseUserMention } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin"; import { rolesCmd } from "../types"; @@ -18,19 +19,21 @@ export const RemoveRoleCmd = rolesCmd({ async run({ message: msg, args, pluginData }) { if (!canActOn(pluginData, msg.member, args.member, true)) { - sendErrorMessage(pluginData, msg.channel, "Cannot remove roles from this user: insufficient permissions"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "Cannot remove roles from this user: insufficient permissions"); return; } const roleId = await resolveRoleId(pluginData.client, pluginData.guild.id, args.role); if (!roleId) { - sendErrorMessage(pluginData, msg.channel, "Invalid role id"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid role id"); return; } const config = await pluginData.config.getForMessage(msg); if (!config.assignable_roles.includes(roleId)) { - sendErrorMessage(pluginData, msg.channel, "You cannot remove that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot remove that role"); return; } @@ -40,12 +43,12 @@ export const RemoveRoleCmd = rolesCmd({ pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); - sendErrorMessage(pluginData, msg.channel, "You cannot remove that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot remove that role"); return; } if (!args.member.roles.cache.has(roleId)) { - sendErrorMessage(pluginData, msg.channel, "Member doesn't have that role"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member doesn't have that role"); return; } @@ -56,10 +59,8 @@ export const RemoveRoleCmd = rolesCmd({ roles: [role], }); - sendSuccessMessage( - pluginData, - msg.channel, - `Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`); }, }); diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts index ce8a3a734..006307ac8 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts @@ -1,7 +1,7 @@ import { Role, Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { selfGrantableRolesCmd } from "../types"; import { findMatchingRoles } from "../util/findMatchingRoles"; import { getApplyingEntries } from "../util/getApplyingEntries"; @@ -39,12 +39,11 @@ export const RoleAddCmd = selfGrantableRolesCmd({ }, new Map()); if (!rolesToAdd.size) { - sendErrorMessage( - pluginData, - msg.channel, - `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, - { users: [msg.author.id] }, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, { + users: [msg.author.id], + }); lock.unlock(); return; } @@ -84,12 +83,11 @@ export const RoleAddCmd = selfGrantableRolesCmd({ roles: Array.from(newRoleIds) as Snowflake[], }); } catch { - sendErrorMessage( - pluginData, - msg.channel, - `<@!${msg.author.id}> Got an error while trying to grant you the roles`, - { users: [msg.author.id] }, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `<@!${msg.author.id}> Got an error while trying to grant you the roles`, { + users: [msg.author.id], + }); return; } @@ -120,7 +118,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({ messageParts.push("couldn't recognize some of the roles"); } - sendSuccessMessage(pluginData, msg.channel, `<@!${msg.author.id}> ${messageParts.join("; ")}`, { + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `<@!${msg.author.id}> ${messageParts.join("; ")}`, { users: [msg.author.id], }); diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts index 007aa4596..b688203f7 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts @@ -1,7 +1,7 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { selfGrantableRolesCmd } from "../types"; import { findMatchingRoles } from "../util/findMatchingRoles"; import { getApplyingEntries } from "../util/getApplyingEntries"; @@ -46,36 +46,34 @@ export const RoleRemoveCmd = selfGrantableRolesCmd({ const removedRolesWord = rolesToRemove.length === 1 ? "role" : "roles"; if (rolesToRemove.length !== roleNames.length) { - sendSuccessMessage( - pluginData, - msg.channel, - `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord};` + - ` couldn't recognize the other roles you mentioned`, - { users: [msg.author.id] }, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord};` + + ` couldn't recognize the other roles you mentioned`, + { users: [msg.author.id] }, + ); } else { - sendSuccessMessage( - pluginData, - msg.channel, - `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord}`, - { users: [msg.author.id] }, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord}`, { + users: [msg.author.id], + }); } } catch { - sendSuccessMessage( - pluginData, - msg.channel, - `<@!${msg.author.id}> Got an error while trying to remove the roles`, - { users: [msg.author.id] }, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `<@!${msg.author.id}> Got an error while trying to remove the roles`, { + users: [msg.author.id], + }); } } else { - sendErrorMessage( - pluginData, - msg.channel, - `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, - { users: [msg.author.id] }, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, { + users: [msg.author.id], + }); } lock.unlock(); diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts index 246a048ea..720823502 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts @@ -1,9 +1,9 @@ import { ChannelType, escapeInlineCode } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { asSingleLine, renderUserUsername } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { BOT_SLOWMODE_CLEAR_PERMISSIONS } from "../requiredPermissions"; import { slowmodeCmd } from "../types"; import { clearBotSlowmodeFromUserId } from "../util/clearBotSlowmodeFromUserId"; @@ -22,18 +22,16 @@ export const SlowmodeClearCmd = slowmodeCmd({ async run({ message: msg, args, pluginData }) { const channelSlowmode = await pluginData.state.slowmodes.getChannelSlowmode(args.channel.id); if (!channelSlowmode) { - sendErrorMessage(pluginData, msg.channel, "Channel doesn't have slowmode!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Channel doesn't have slowmode!"); return; } const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_CLEAR_PERMISSIONS); if (missingPermissions) { - sendErrorMessage( - pluginData, - msg.channel, - `Unable to clear slowmode. ${missingPermissionError(missingPermissions)}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Unable to clear slowmode. ${missingPermissionError(missingPermissions)}`); return; } @@ -41,9 +39,8 @@ export const SlowmodeClearCmd = slowmodeCmd({ if (args.channel.type === ChannelType.GuildText) { await clearBotSlowmodeFromUserId(pluginData, args.channel, args.user.id, args.force); } else { - sendErrorMessage( - pluginData, - msg.channel, + pluginData.getPlugin(CommonPlugin).sendErrorMessage( + msg, asSingleLine(` Failed to clear slowmode from **${renderUserUsername(args.user)}** in <#${args.channel.id}>: Threads cannot have Bot Slowmode @@ -52,9 +49,8 @@ export const SlowmodeClearCmd = slowmodeCmd({ return; } } catch (e) { - sendErrorMessage( - pluginData, - msg.channel, + pluginData.getPlugin(CommonPlugin).sendErrorMessage( + msg, asSingleLine(` Failed to clear slowmode from **${renderUserUsername(args.user)}** in <#${args.channel.id}>: \`${escapeInlineCode(e.message)}\` @@ -63,10 +59,8 @@ export const SlowmodeClearCmd = slowmodeCmd({ return; } - sendSuccessMessage( - pluginData, - msg.channel, - `Slowmode cleared from **${renderUserUsername(args.user)}** in <#${args.channel.id}>`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `Slowmode cleared from **${renderUserUsername(args.user)}** in <#${args.channel.id}>`); }, }); diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts index 44d5c1118..0d088b43c 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts @@ -1,10 +1,10 @@ import { escapeInlineCode, PermissionsBitField } from "discord.js"; import humanizeDuration from "humanize-duration"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { asSingleLine, DAYS, HOURS, MINUTES } from "../../../utils"; import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { BOT_SLOWMODE_PERMISSIONS, NATIVE_SLOWMODE_PERMISSIONS } from "../requiredPermissions"; import { slowmodeCmd } from "../types"; import { actualDisableSlowmodeCmd } from "../util/actualDisableSlowmodeCmd"; @@ -40,7 +40,9 @@ export const SlowmodeSetCmd = slowmodeCmd({ const channel = args.channel || msg.channel; if (!channel.isTextBased() || channel.isThread()) { - sendErrorMessage(pluginData, msg.channel, "Slowmode can only be set on non-thread text-based channels"); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, "Slowmode can only be set on non-thread text-based channels"); return; } @@ -56,29 +58,26 @@ export const SlowmodeSetCmd = slowmodeCmd({ const mode = (args.mode as TMode) || defaultMode; if (!validModes.includes(mode)) { - sendErrorMessage(pluginData, msg.channel, "--mode must be 'bot' or 'native'"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "--mode must be 'bot' or 'native'"); return; } // Validate durations if (mode === "native" && args.time > MAX_NATIVE_SLOWMODE) { - sendErrorMessage(pluginData, msg.channel, "Native slowmode can only be set to 6h or less"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Native slowmode can only be set to 6h or less"); return; } if (mode === "bot" && args.time > MAX_BOT_SLOWMODE) { - sendErrorMessage( - pluginData, - msg.channel, - `Sorry, bot managed slowmodes can be at most 100 years long. Maybe 99 would be enough?`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Sorry, bot managed slowmodes can be at most 100 years long. Maybe 99 would be enough?`); return; } if (mode === "bot" && args.time < MIN_BOT_SLOWMODE) { - sendErrorMessage( - pluginData, - msg.channel, + pluginData.getPlugin(CommonPlugin).sendErrorMessage( + msg, asSingleLine(` Bot managed slowmode must be 15min or more. Use \`--mode native\` to use native slowmodes for short slowmodes instead. @@ -96,11 +95,9 @@ export const SlowmodeSetCmd = slowmodeCmd({ NATIVE_SLOWMODE_PERMISSIONS, ); if (missingPermissions) { - sendErrorMessage( - pluginData, - msg.channel, - `Unable to set native slowmode. ${missingPermissionError(missingPermissions)}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Unable to set native slowmode. ${missingPermissionError(missingPermissions)}`); return; } } @@ -111,11 +108,9 @@ export const SlowmodeSetCmd = slowmodeCmd({ BOT_SLOWMODE_PERMISSIONS, ); if (missingPermissions) { - sendErrorMessage( - pluginData, - msg.channel, - `Unable to set bot managed slowmode. ${missingPermissionError(missingPermissions)}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Unable to set bot managed slowmode. ${missingPermissionError(missingPermissions)}`); return; } } @@ -134,7 +129,9 @@ export const SlowmodeSetCmd = slowmodeCmd({ try { await channel.setRateLimitPerUser(rateLimitSeconds); } catch (e) { - sendErrorMessage(pluginData, msg.channel, `Failed to set native slowmode: ${escapeInlineCode(e.message)}`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Failed to set native slowmode: ${escapeInlineCode(e.message)}`); return; } } else { @@ -153,10 +150,8 @@ export const SlowmodeSetCmd = slowmodeCmd({ const humanizedSlowmodeTime = humanizeDuration(args.time); const slowmodeType = mode === "native" ? "native slowmode" : "bot-maintained slowmode"; - sendSuccessMessage( - pluginData, - msg.channel, - `Set ${humanizedSlowmodeTime} slowmode for <#${channel.id}> (${slowmodeType})`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `Set ${humanizedSlowmodeTime} slowmode for <#${channel.id}> (${slowmodeType})`); }, }); diff --git a/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts b/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts index 2bc00bed1..72c86a04c 100644 --- a/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts +++ b/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts @@ -1,8 +1,8 @@ import { Message } from "discord.js"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { noop } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { BOT_SLOWMODE_DISABLE_PERMISSIONS } from "../requiredPermissions"; import { disableBotSlowmodeForChannel } from "./disableBotSlowmodeForChannel"; @@ -11,18 +11,16 @@ export async function actualDisableSlowmodeCmd(msg: Message, args, pluginData) { const hasNativeSlowmode = args.channel.rateLimitPerUser; if (!botSlowmode && hasNativeSlowmode === 0) { - sendErrorMessage(pluginData, msg.channel, "Channel is not on slowmode!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Channel is not on slowmode!"); return; } const me = pluginData.guild.members.cache.get(pluginData.client.user!.id); const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_DISABLE_PERMISSIONS); if (missingPermissions) { - sendErrorMessage( - pluginData, - msg.channel, - `Unable to disable slowmode. ${missingPermissionError(missingPermissions)}`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Unable to disable slowmode. ${missingPermissionError(missingPermissions)}`); return; } @@ -41,13 +39,14 @@ export async function actualDisableSlowmodeCmd(msg: Message, args, pluginData) { } if (failedUsers.length) { - sendSuccessMessage( - pluginData, - msg.channel, - `Slowmode disabled! Failed to clear slowmode from the following users:\n\n<@!${failedUsers.join(">\n<@!")}>`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `Slowmode disabled! Failed to clear slowmode from the following users:\n\n<@!${failedUsers.join(">\n<@!")}>`, + ); } else { - sendSuccessMessage(pluginData, msg.channel, "Slowmode disabled!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Slowmode disabled!"); initMsg.delete().catch(noop); } } diff --git a/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts b/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts index b2a464322..4c8a4e255 100644 --- a/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts +++ b/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts @@ -76,10 +76,13 @@ export async function logAndDetectMessageSpam( (spamConfig.mute_time && convertDelayStringToMS(spamConfig.mute_time.toString())) ?? 120 * 1000; try { + const reason = "Automatic spam detection"; + muteResult = await mutesPlugin.muteUser( member.id, muteTime, - "Automatic spam detection", + reason, + reason, { caseArgs: { modId: pluginData.client.user!.id, diff --git a/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts b/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts index 3a339b5f0..5b83c9a1e 100644 --- a/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts +++ b/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts @@ -40,10 +40,13 @@ export async function logAndDetectOtherSpam( (spamConfig.mute_time && convertDelayStringToMS(spamConfig.mute_time.toString())) ?? 120 * 1000; try { + const reason = "Automatic spam detection"; + await mutesPlugin.muteUser( member.id, muteTime, - "Automatic spam detection", + reason, + reason, { caseArgs: { modId: pluginData.client.user!.id, diff --git a/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts b/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts index 7fba02418..d074c948a 100644 --- a/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts +++ b/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts @@ -1,6 +1,6 @@ import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { starboardCmd } from "../types"; import { saveMessageToStarboard } from "../util/saveMessageToStarboard"; @@ -19,13 +19,13 @@ export const MigratePinsCmd = starboardCmd({ const config = await pluginData.config.get(); const starboard = config.boards[args.starboardName]; if (!starboard) { - sendErrorMessage(pluginData, msg.channel, "Unknown starboard specified"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown starboard specified"); return; } const starboardChannel = pluginData.guild.channels.cache.get(starboard.channel_id as Snowflake); if (!starboardChannel || !(starboardChannel instanceof TextChannel)) { - sendErrorMessage(pluginData, msg.channel, "Starboard has an unknown/invalid channel id"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Starboard has an unknown/invalid channel id"); return; } @@ -43,10 +43,8 @@ export const MigratePinsCmd = starboardCmd({ await saveMessageToStarboard(pluginData, pin, starboard); } - sendSuccessMessage( - pluginData, - msg.channel, - `Pins migrated from <#${args.pinChannel.id}> to <#${starboardChannel.id}>!`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `Pins migrated from <#${args.pinChannel.id}> to <#${starboardChannel.id}>!`); }, }); diff --git a/backend/src/plugins/Tags/commands/TagCreateCmd.ts b/backend/src/plugins/Tags/commands/TagCreateCmd.ts index 10c81e87d..0ee452f53 100644 --- a/backend/src/plugins/Tags/commands/TagCreateCmd.ts +++ b/backend/src/plugins/Tags/commands/TagCreateCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { TemplateParseError, parseTemplate } from "../../../templateFormatter"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { tagsCmd } from "../types"; export const TagCreateCmd = tagsCmd({ @@ -17,7 +17,7 @@ export const TagCreateCmd = tagsCmd({ parseTemplate(args.body); } catch (e) { if (e instanceof TemplateParseError) { - sendErrorMessage(pluginData, msg.channel, `Invalid tag syntax: ${e.message}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Invalid tag syntax: ${e.message}`); return; } else { throw e; @@ -27,6 +27,6 @@ export const TagCreateCmd = tagsCmd({ await pluginData.state.tags.createOrUpdate(args.tag, args.body, msg.author.id); const prefix = pluginData.config.get().prefix; - sendSuccessMessage(pluginData, msg.channel, `Tag set! Use it with: \`${prefix}${args.tag}\``); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Tag set! Use it with: \`${prefix}${args.tag}\``); }, }); diff --git a/backend/src/plugins/Tags/commands/TagDeleteCmd.ts b/backend/src/plugins/Tags/commands/TagDeleteCmd.ts index 47cb623d5..174b7b647 100644 --- a/backend/src/plugins/Tags/commands/TagDeleteCmd.ts +++ b/backend/src/plugins/Tags/commands/TagDeleteCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { tagsCmd } from "../types"; export const TagDeleteCmd = tagsCmd({ @@ -13,11 +13,11 @@ export const TagDeleteCmd = tagsCmd({ async run({ message: msg, args, pluginData }) { const tag = await pluginData.state.tags.find(args.tag); if (!tag) { - sendErrorMessage(pluginData, msg.channel, "No tag with that name"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No tag with that name"); return; } await pluginData.state.tags.delete(args.tag); - sendSuccessMessage(pluginData, msg.channel, "Tag deleted!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Tag deleted!"); }, }); diff --git a/backend/src/plugins/Tags/commands/TagEvalCmd.ts b/backend/src/plugins/Tags/commands/TagEvalCmd.ts index 55659d30c..a5de9a77c 100644 --- a/backend/src/plugins/Tags/commands/TagEvalCmd.ts +++ b/backend/src/plugins/Tags/commands/TagEvalCmd.ts @@ -1,8 +1,8 @@ import { MessageCreateOptions } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { TemplateParseError } from "../../../templateFormatter"; import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { tagsCmd } from "../types"; import { renderTagBody } from "../util/renderTagBody"; @@ -28,14 +28,14 @@ export const TagEvalCmd = tagsCmd({ )) as MessageCreateOptions; if (!rendered.content && !rendered.embeds?.length) { - sendErrorMessage(pluginData, msg.channel, "Evaluation resulted in an empty text"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Evaluation resulted in an empty text"); return; } msg.channel.send(rendered); } catch (e) { if (e instanceof TemplateParseError) { - sendErrorMessage(pluginData, msg.channel, `Failed to render tag: ${e.message}`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Failed to render tag: ${e.message}`); return; } diff --git a/backend/src/plugins/Tags/commands/TagSourceCmd.ts b/backend/src/plugins/Tags/commands/TagSourceCmd.ts index 9e0e5cefe..6690f8f75 100644 --- a/backend/src/plugins/Tags/commands/TagSourceCmd.ts +++ b/backend/src/plugins/Tags/commands/TagSourceCmd.ts @@ -1,6 +1,7 @@ import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { getBaseUrl, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { getBaseUrl } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { tagsCmd } from "../types"; export const TagSourceCmd = tagsCmd({ @@ -17,18 +18,18 @@ export const TagSourceCmd = tagsCmd({ if (args.delete) { const actualTag = await pluginData.state.tags.find(args.tag); if (!actualTag) { - sendErrorMessage(pluginData, msg.channel, "No tag with that name"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No tag with that name"); return; } await pluginData.state.tags.delete(args.tag); - sendSuccessMessage(pluginData, msg.channel, "Tag deleted!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Tag deleted!"); return; } const tag = await pluginData.state.tags.find(args.tag); if (!tag) { - sendErrorMessage(pluginData, msg.channel, "No tag with that name"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No tag with that name"); return; } diff --git a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts index db59965a4..2c39519ed 100644 --- a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts @@ -1,4 +1,4 @@ -import { sendSuccessMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getGuildTz } from "../functions/getGuildTz"; import { timeAndDateCmd } from "../types"; @@ -11,10 +11,8 @@ export const ResetTimezoneCmd = timeAndDateCmd({ async run({ pluginData, message }) { await pluginData.state.memberTimezones.reset(message.author.id); const serverTimezone = getGuildTz(pluginData); - sendSuccessMessage( - pluginData, - message.channel, - `Your timezone has been reset to server default, **${serverTimezone}**`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(message, `Your timezone has been reset to server default, **${serverTimezone}**`); }, }); diff --git a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts index 90d00b3bb..70acf1294 100644 --- a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts @@ -1,8 +1,8 @@ import { escapeInlineCode } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { trimLines } from "../../../utils"; import { parseFuzzyTimezone } from "../../../utils/parseFuzzyTimezone"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { timeAndDateCmd } from "../types"; export const SetTimezoneCmd = timeAndDateCmd({ @@ -16,9 +16,8 @@ export const SetTimezoneCmd = timeAndDateCmd({ async run({ pluginData, message, args }) { const parsedTz = parseFuzzyTimezone(args.timezone); if (!parsedTz) { - sendErrorMessage( - pluginData, - message.channel, + pluginData.getPlugin(CommonPlugin).sendErrorMessage( + message, trimLines(` Invalid timezone: \`${escapeInlineCode(args.timezone)}\` Zeppelin uses timezone locations rather than specific timezone names. @@ -29,6 +28,6 @@ export const SetTimezoneCmd = timeAndDateCmd({ } await pluginData.state.memberTimezones.set(message.author.id, parsedTz); - sendSuccessMessage(pluginData, message.channel, `Your timezone is now set to **${parsedTz}**`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(message, `Your timezone is now set to **${parsedTz}**`); }, }); diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 68f6987a9..9d966d8cd 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -5,8 +5,9 @@ import { GuildCases } from "../../data/GuildCases"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { Supporters } from "../../data/Supporters"; -import { makeIoTsConfigParser, sendSuccessMessage } from "../../pluginUtils"; +import { makeIoTsConfigParser } from "../../pluginUtils"; import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { ModActionsPlugin } from "../ModActions/ModActionsPlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; @@ -169,8 +170,8 @@ export const UtilityPlugin = zeppelinGuildPlugin()({ }, userInfo(pluginData) { - return (userId: Snowflake, requestMemberId?: Snowflake) => { - return getUserInfoEmbed(pluginData, userId, false, requestMemberId); + return (userId: Snowflake) => { + return getUserInfoEmbed(pluginData, userId, false); }; }, @@ -214,7 +215,7 @@ export const UtilityPlugin = zeppelinGuildPlugin()({ const { guild } = pluginData; if (activeReloads.has(guild.id)) { - sendSuccessMessage(pluginData, activeReloads.get(guild.id)!, "Reloaded!"); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(activeReloads.get(guild.id)!, "Reloaded!"); activeReloads.delete(guild.id); } }, diff --git a/backend/src/plugins/Utility/commands/AvatarCmd.ts b/backend/src/plugins/Utility/commands/AvatarCmd.ts index ef44a2cb6..fe81cd150 100644 --- a/backend/src/plugins/Utility/commands/AvatarCmd.ts +++ b/backend/src/plugins/Utility/commands/AvatarCmd.ts @@ -1,7 +1,7 @@ import { APIEmbed, ImageFormat } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { UnknownUser, renderUserUsername } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const AvatarCmd = utilityCmd({ @@ -24,7 +24,7 @@ export const AvatarCmd = utilityCmd({ }; msg.channel.send({ embeds: [embed] }); } else { - sendErrorMessage(pluginData, msg.channel, "Invalid user ID"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid user ID"); } }, }); diff --git a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts index bbef9bcf7..5b4a70bbf 100644 --- a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; import { utilityCmd } from "../types"; @@ -16,7 +16,7 @@ export const ChannelInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { const embed = await getChannelInfoEmbed(pluginData, args.channel); if (!embed) { - sendErrorMessage(pluginData, message.channel, "Unknown channel"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown channel"); return; } diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index e47dc4131..11e7bcfb3 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -5,9 +5,10 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { humanizeDurationShort } from "../../../humanizeDurationShort"; -import { getBaseUrl, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { getBaseUrl } from "../../../pluginUtils"; import { ModActionsPlugin } from "../../../plugins/ModActions/ModActionsPlugin"; import { DAYS, SECONDS, chunkArray, getInviteCodesInString, noop } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { UtilityPluginType, utilityCmd } from "../types"; @@ -82,19 +83,22 @@ export interface CleanArgs { export async function cleanCmd(pluginData: GuildPluginData, args: CleanArgs | any, msg) { if (args.count > MAX_CLEAN_COUNT || args.count <= 0) { - sendErrorMessage( - pluginData, - msg.channel, - `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`, - undefined, - args["response-interaction"], - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + msg, + `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`, + undefined, + args["response-interaction"], + ); return; } const targetChannel = args.channel ? pluginData.guild.channels.cache.get(args.channel as Snowflake) : msg.channel; if (!targetChannel?.isTextBased()) { - sendErrorMessage(pluginData, msg.channel, `Invalid channel specified`, undefined, args["response-interaction"]); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Invalid channel specified`, undefined, args["response-interaction"]); return; } @@ -106,13 +110,14 @@ export async function cleanCmd(pluginData: GuildPluginData, a categoryId: targetChannel.parentId, }); if (configForTargetChannel.can_clean !== true) { - sendErrorMessage( - pluginData, - msg.channel, - `Missing permissions to use clean on that channel`, - undefined, - args["response-interaction"], - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + msg, + `Missing permissions to use clean on that channel`, + undefined, + args["response-interaction"], + ); return; } } @@ -218,22 +223,14 @@ export async function cleanCmd(pluginData: GuildPluginData, a } } - responseMsg = await sendSuccessMessage( - pluginData, - msg.channel, - responseText, - undefined, - args["response-interaction"], - ); + responseMsg = await pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, responseText, undefined, args["response-interaction"]); } else { const responseText = `Found no messages to clean${note ? ` (${note})` : ""}!`; - responseMsg = await sendErrorMessage( - pluginData, - msg.channel, - responseText, - undefined, - args["response-interaction"], - ); + responseMsg = await pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, responseText, undefined, args["response-interaction"]); } cleaningMessage?.delete(); diff --git a/backend/src/plugins/Utility/commands/ContextCmd.ts b/backend/src/plugins/Utility/commands/ContextCmd.ts index ceec44076..eb613f57e 100644 --- a/backend/src/plugins/Utility/commands/ContextCmd.ts +++ b/backend/src/plugins/Utility/commands/ContextCmd.ts @@ -1,8 +1,8 @@ import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { messageLink } from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const ContextCmd = utilityCmd({ @@ -23,7 +23,7 @@ export const ContextCmd = utilityCmd({ async run({ message: msg, args, pluginData }) { if (args.channel && !(args.channel instanceof TextChannel)) { - sendErrorMessage(pluginData, msg.channel, "Channel must be a text channel"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Channel must be a text channel"); return; } @@ -31,7 +31,7 @@ export const ContextCmd = utilityCmd({ const messageId = args.messageId ?? args.message.messageId; if (!canReadChannel(channel, msg.member)) { - sendErrorMessage(pluginData, msg.channel, "Message context not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Message context not found"); return; } @@ -42,7 +42,7 @@ export const ContextCmd = utilityCmd({ }) )[0]; if (!previousMessage) { - sendErrorMessage(pluginData, msg.channel, "Message context not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Message context not found"); return; } diff --git a/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts b/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts index 5c89f3ebe..114652a90 100644 --- a/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getCustomEmojiId } from "../functions/getCustomEmojiId"; import { getEmojiInfoEmbed } from "../functions/getEmojiInfoEmbed"; import { utilityCmd } from "../types"; @@ -17,13 +17,13 @@ export const EmojiInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { const emojiId = getCustomEmojiId(args.emoji); if (!emojiId) { - sendErrorMessage(pluginData, message.channel, "Emoji not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Emoji not found"); return; } const embed = await getEmojiInfoEmbed(pluginData, emojiId); if (!embed) { - sendErrorMessage(pluginData, message.channel, "Emoji not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Emoji not found"); return; } diff --git a/backend/src/plugins/Utility/commands/InfoCmd.ts b/backend/src/plugins/Utility/commands/InfoCmd.ts index 63d0ae81f..23ffa8ac1 100644 --- a/backend/src/plugins/Utility/commands/InfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InfoCmd.ts @@ -1,10 +1,10 @@ import { Snowflake } from "discord.js"; import { getChannelId, getRoleId } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { isValidSnowflake, noop, parseInviteCodeInput, resolveInvite, resolveUser } from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; import { resolveMessageTarget } from "../../../utils/resolveMessageTarget"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; import { getCustomEmojiId } from "../functions/getCustomEmojiId"; import { getEmojiInfoEmbed } from "../functions/getEmojiInfoEmbed"; @@ -42,7 +42,7 @@ export const InfoCmd = utilityCmd({ const channelId = getChannelId(value); const channel = channelId && pluginData.guild.channels.cache.get(channelId as Snowflake); if (channel) { - const embed = await getChannelInfoEmbed(pluginData, channelId!, message.author.id); + const embed = await getChannelInfoEmbed(pluginData, channelId!); if (embed) { message.channel.send({ embeds: [embed] }); return; @@ -54,7 +54,7 @@ export const InfoCmd = utilityCmd({ if (userCfg.can_server) { const guild = await pluginData.client.guilds.fetch(value as Snowflake).catch(noop); if (guild) { - const embed = await getServerInfoEmbed(pluginData, value, message.author.id); + const embed = await getServerInfoEmbed(pluginData, value); if (embed) { message.channel.send({ embeds: [embed] }); return; @@ -66,7 +66,7 @@ export const InfoCmd = utilityCmd({ if (userCfg.can_userinfo) { const user = await resolveUser(pluginData.client, value); if (user && userCfg.can_userinfo) { - const embed = await getUserInfoEmbed(pluginData, user.id, Boolean(args.compact), message.author.id); + const embed = await getUserInfoEmbed(pluginData, user.id, Boolean(args.compact)); if (embed) { message.channel.send({ embeds: [embed] }); return; @@ -79,12 +79,7 @@ export const InfoCmd = utilityCmd({ const messageTarget = await resolveMessageTarget(pluginData, value); if (messageTarget) { if (canReadChannel(messageTarget.channel, message.member)) { - const embed = await getMessageInfoEmbed( - pluginData, - messageTarget.channel.id, - messageTarget.messageId, - message.author.id, - ); + const embed = await getMessageInfoEmbed(pluginData, messageTarget.channel.id, messageTarget.messageId); if (embed) { message.channel.send({ embeds: [embed] }); return; @@ -112,7 +107,7 @@ export const InfoCmd = utilityCmd({ if (userCfg.can_server) { const serverPreview = await getGuildPreview(pluginData.client, value).catch(() => null); if (serverPreview) { - const embed = await getServerInfoEmbed(pluginData, value, message.author.id); + const embed = await getServerInfoEmbed(pluginData, value); if (embed) { message.channel.send({ embeds: [embed] }); return; @@ -125,7 +120,7 @@ export const InfoCmd = utilityCmd({ const roleId = getRoleId(value); const role = roleId && pluginData.guild.roles.cache.get(roleId as Snowflake); if (role) { - const embed = await getRoleInfoEmbed(pluginData, role, message.author.id); + const embed = await getRoleInfoEmbed(pluginData, role); message.channel.send({ embeds: [embed] }); return; } @@ -145,16 +140,17 @@ export const InfoCmd = utilityCmd({ // 9. Arbitrary ID if (isValidSnowflake(value) && userCfg.can_snowflake) { - const embed = await getSnowflakeInfoEmbed(pluginData, value, true, message.author.id); + const embed = await getSnowflakeInfoEmbed(value, true); message.channel.send({ embeds: [embed] }); return; } // 10. No can do - sendErrorMessage( - pluginData, - message.channel, - "Could not find anything with that value or you are lacking permission for the snowflake type", - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + message, + "Could not find anything with that value or you are lacking permission for the snowflake type", + ); }, }); diff --git a/backend/src/plugins/Utility/commands/InviteInfoCmd.ts b/backend/src/plugins/Utility/commands/InviteInfoCmd.ts index df34123ab..262ab94a6 100644 --- a/backend/src/plugins/Utility/commands/InviteInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InviteInfoCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { parseInviteCodeInput } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getInviteInfoEmbed } from "../functions/getInviteInfoEmbed"; import { utilityCmd } from "../types"; @@ -18,7 +18,7 @@ export const InviteInfoCmd = utilityCmd({ const inviteCode = parseInviteCodeInput(args.inviteCode); const embed = await getInviteInfoEmbed(pluginData, inviteCode); if (!embed) { - sendErrorMessage(pluginData, message.channel, "Unknown invite"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown invite"); return; } diff --git a/backend/src/plugins/Utility/commands/JumboCmd.ts b/backend/src/plugins/Utility/commands/JumboCmd.ts index 3dd1d30a5..a8cf86785 100644 --- a/backend/src/plugins/Utility/commands/JumboCmd.ts +++ b/backend/src/plugins/Utility/commands/JumboCmd.ts @@ -3,8 +3,8 @@ import { AttachmentBuilder } from "discord.js"; import fs from "fs"; import twemoji from "twemoji"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { downloadFile, isEmoji, SECONDS } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; const fsp = fs.promises; @@ -51,7 +51,7 @@ export const JumboCmd = utilityCmd({ let file: AttachmentBuilder | undefined; if (!isEmoji(args.emoji)) { - sendErrorMessage(pluginData, msg.channel, "Invalid emoji"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid emoji"); return; } @@ -87,7 +87,7 @@ export const JumboCmd = utilityCmd({ } } if (!image) { - sendErrorMessage(pluginData, msg.channel, "Error occurred while jumboing default emoji"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Error occurred while jumboing default emoji"); return; } diff --git a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts index 82096250e..88df8eff8 100644 --- a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { canReadChannel } from "../../../utils/canReadChannel"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getMessageInfoEmbed } from "../functions/getMessageInfoEmbed"; import { utilityCmd } from "../types"; @@ -16,18 +16,13 @@ export const MessageInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { if (!canReadChannel(args.message.channel, message.member)) { - sendErrorMessage(pluginData, message.channel, "Unknown message"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown message"); return; } - const embed = await getMessageInfoEmbed( - pluginData, - args.message.channel.id, - args.message.messageId, - message.author.id, - ); + const embed = await getMessageInfoEmbed(pluginData, args.message.channel.id, args.message.messageId); if (!embed) { - sendErrorMessage(pluginData, message.channel, "Unknown message"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown message"); return; } diff --git a/backend/src/plugins/Utility/commands/NicknameCmd.ts b/backend/src/plugins/Utility/commands/NicknameCmd.ts index 9821ef328..63d5eac89 100644 --- a/backend/src/plugins/Utility/commands/NicknameCmd.ts +++ b/backend/src/plugins/Utility/commands/NicknameCmd.ts @@ -1,7 +1,8 @@ import { escapeBold } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendSuccessMessage } from "../../../pluginUtils"; +import { canActOn } from "../../../pluginUtils"; import { errorMessage } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const NicknameCmd = utilityCmd({ @@ -45,10 +46,11 @@ export const NicknameCmd = utilityCmd({ return; } - sendSuccessMessage( - pluginData, - msg.channel, - `Changed nickname of <@!${args.member.id}> from **${oldNickname}** to **${args.nickname}**`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `Changed nickname of <@!${args.member.id}> from **${oldNickname}** to **${args.nickname}**`, + ); }, }); diff --git a/backend/src/plugins/Utility/commands/NicknameResetCmd.ts b/backend/src/plugins/Utility/commands/NicknameResetCmd.ts index f86a33c93..614174199 100644 --- a/backend/src/plugins/Utility/commands/NicknameResetCmd.ts +++ b/backend/src/plugins/Utility/commands/NicknameResetCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendSuccessMessage } from "../../../pluginUtils"; +import { canActOn } from "../../../pluginUtils"; import { errorMessage } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const NicknameResetCmd = utilityCmd({ @@ -31,6 +32,6 @@ export const NicknameResetCmd = utilityCmd({ return; } - sendSuccessMessage(pluginData, msg.channel, `The nickname of <@!${args.member.id}> has been reset`); + pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `The nickname of <@!${args.member.id}> has been reset`); }, }); diff --git a/backend/src/plugins/Utility/commands/RoleInfoCmd.ts b/backend/src/plugins/Utility/commands/RoleInfoCmd.ts index b04b49a7d..6f540b4b1 100644 --- a/backend/src/plugins/Utility/commands/RoleInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/RoleInfoCmd.ts @@ -13,7 +13,7 @@ export const RoleInfoCmd = utilityCmd({ }, async run({ message, args, pluginData }) { - const embed = await getRoleInfoEmbed(pluginData, args.role, message.author.id); + const embed = await getRoleInfoEmbed(pluginData, args.role); message.channel.send({ embeds: [embed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/RolesCmd.ts b/backend/src/plugins/Utility/commands/RolesCmd.ts index 86c095544..d04903f9e 100644 --- a/backend/src/plugins/Utility/commands/RolesCmd.ts +++ b/backend/src/plugins/Utility/commands/RolesCmd.ts @@ -1,7 +1,7 @@ import { Role } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { chunkArray, sorter, trimLines } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { refreshMembersIfNeeded } from "../refreshMembers"; import { utilityCmd } from "../types"; @@ -62,7 +62,7 @@ export const RolesCmd = utilityCmd({ } else if (sort === "name") { roles.sort(sorter((r) => r.name.toLowerCase(), sortDir)); } else { - sendErrorMessage(pluginData, msg.channel, "Unknown sorting method"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown sorting method"); return; } diff --git a/backend/src/plugins/Utility/commands/ServerInfoCmd.ts b/backend/src/plugins/Utility/commands/ServerInfoCmd.ts index 30fb7efef..2e5d7ba53 100644 --- a/backend/src/plugins/Utility/commands/ServerInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/ServerInfoCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getServerInfoEmbed } from "../functions/getServerInfoEmbed"; import { utilityCmd } from "../types"; @@ -15,9 +15,9 @@ export const ServerInfoCmd = utilityCmd({ async run({ message, pluginData, args }) { const serverId = args.serverId || pluginData.guild.id; - const serverInfoEmbed = await getServerInfoEmbed(pluginData, serverId, message.author.id); + const serverInfoEmbed = await getServerInfoEmbed(pluginData, serverId); if (!serverInfoEmbed) { - sendErrorMessage(pluginData, message.channel, "Could not find information for that server"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Could not find information for that server"); return; } diff --git a/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts b/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts index 59f438931..bf0e859fc 100644 --- a/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts @@ -12,8 +12,8 @@ export const SnowflakeInfoCmd = utilityCmd({ id: ct.anyId(), }, - async run({ message, args, pluginData }) { - const embed = await getSnowflakeInfoEmbed(pluginData, args.id, false, message.author.id); + async run({ message, args }) { + const embed = await getSnowflakeInfoEmbed(args.id, false); message.channel.send({ embeds: [embed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/SourceCmd.ts b/backend/src/plugins/Utility/commands/SourceCmd.ts index 54c246473..be3a4bab3 100644 --- a/backend/src/plugins/Utility/commands/SourceCmd.ts +++ b/backend/src/plugins/Utility/commands/SourceCmd.ts @@ -1,7 +1,8 @@ import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { getBaseUrl, sendErrorMessage } from "../../../pluginUtils"; +import { getBaseUrl } from "../../../pluginUtils"; import { canReadChannel } from "../../../utils/canReadChannel"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const SourceCmd = utilityCmd({ @@ -16,13 +17,13 @@ export const SourceCmd = utilityCmd({ async run({ message: cmdMessage, args, pluginData }) { if (!canReadChannel(args.message.channel, cmdMessage.member)) { - sendErrorMessage(pluginData, cmdMessage.channel, "Unknown message"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(cmdMessage, "Unknown message"); return; } const message = await args.message.channel.messages.fetch(args.message.messageId); if (!message) { - sendErrorMessage(pluginData, cmdMessage.channel, "Unknown message"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(cmdMessage, "Unknown message"); return; } diff --git a/backend/src/plugins/Utility/commands/UserInfoCmd.ts b/backend/src/plugins/Utility/commands/UserInfoCmd.ts index c7ce8b48c..6ba99548b 100644 --- a/backend/src/plugins/Utility/commands/UserInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/UserInfoCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { getUserInfoEmbed } from "../functions/getUserInfoEmbed"; import { utilityCmd } from "../types"; @@ -17,9 +17,9 @@ export const UserInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { const userId = args.user?.id || message.author.id; - const embed = await getUserInfoEmbed(pluginData, userId, args.compact, message.author.id); + const embed = await getUserInfoEmbed(pluginData, userId, args.compact); if (!embed) { - sendErrorMessage(pluginData, message.channel, "User not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "User not found"); return; } diff --git a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts index 0f82458a5..f5bdf43cf 100644 --- a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts +++ b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts @@ -1,7 +1,8 @@ import { VoiceChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { canActOn } from "../../../pluginUtils"; import { renderUserUsername } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { utilityCmd } from "../types"; @@ -17,12 +18,12 @@ export const VcdisconnectCmd = utilityCmd({ async run({ message: msg, args, pluginData }) { if (!canActOn(pluginData, msg.member, args.member)) { - sendErrorMessage(pluginData, msg.channel, "Cannot move: insufficient permissions"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot move: insufficient permissions"); return; } if (!args.member.voice?.channelId) { - sendErrorMessage(pluginData, msg.channel, "Member is not in a voice channel"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member is not in a voice channel"); return; } const channel = pluginData.guild.channels.cache.get(args.member.voice.channelId) as VoiceChannel; @@ -30,7 +31,7 @@ export const VcdisconnectCmd = utilityCmd({ try { await args.member.voice.disconnect(); } catch { - sendErrorMessage(pluginData, msg.channel, "Failed to disconnect member"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Failed to disconnect member"); return; } @@ -40,10 +41,8 @@ export const VcdisconnectCmd = utilityCmd({ oldChannel: channel, }); - sendSuccessMessage( - pluginData, - msg.channel, - `**${renderUserUsername(args.member.user)}** disconnected from **${channel.name}**`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `**${renderUserUsername(args.member.user)}** disconnected from **${channel.name}**`); }, }); diff --git a/backend/src/plugins/Utility/commands/VcmoveCmd.ts b/backend/src/plugins/Utility/commands/VcmoveCmd.ts index db00161e0..ba9e998da 100644 --- a/backend/src/plugins/Utility/commands/VcmoveCmd.ts +++ b/backend/src/plugins/Utility/commands/VcmoveCmd.ts @@ -1,7 +1,8 @@ import { ChannelType, Snowflake, VoiceChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { canActOn } from "../../../pluginUtils"; import { channelMentionRegex, isSnowflake, renderUserUsername, simpleClosestStringMatch } from "../../../utils"; +import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { utilityCmd } from "../types"; @@ -23,7 +24,7 @@ export const VcmoveCmd = utilityCmd({ // Snowflake -> resolve channel directly const potentialChannel = pluginData.guild.channels.cache.get(args.channel as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { - sendErrorMessage(pluginData, msg.channel, "Unknown or non-voice channel"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown or non-voice channel"); return; } @@ -33,7 +34,7 @@ export const VcmoveCmd = utilityCmd({ const channelId = args.channel.match(channelMentionRegex)![1]; const potentialChannel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { - sendErrorMessage(pluginData, msg.channel, "Unknown or non-voice channel"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown or non-voice channel"); return; } @@ -45,7 +46,7 @@ export const VcmoveCmd = utilityCmd({ ); const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, (ch) => ch.name); if (!closestMatch) { - sendErrorMessage(pluginData, msg.channel, "No matching voice channels"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No matching voice channels"); return; } @@ -53,12 +54,12 @@ export const VcmoveCmd = utilityCmd({ } if (!args.member.voice?.channelId) { - sendErrorMessage(pluginData, msg.channel, "Member is not in a voice channel"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member is not in a voice channel"); return; } if (args.member.voice.channelId === channel.id) { - sendErrorMessage(pluginData, msg.channel, "Member is already on that channel!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member is already on that channel!"); return; } @@ -69,7 +70,7 @@ export const VcmoveCmd = utilityCmd({ channel: channel.id, }); } catch { - sendErrorMessage(pluginData, msg.channel, "Failed to move member"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Failed to move member"); return; } @@ -80,11 +81,9 @@ export const VcmoveCmd = utilityCmd({ newChannel: channel, }); - sendSuccessMessage( - pluginData, - msg.channel, - `**${renderUserUsername(args.member.user)}** moved to **${channel.name}**`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage(msg, `**${renderUserUsername(args.member.user)}** moved to **${channel.name}**`); }, }); @@ -106,7 +105,7 @@ export const VcmoveAllCmd = utilityCmd({ // Snowflake -> resolve channel directly const potentialChannel = pluginData.guild.channels.cache.get(args.channel as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { - sendErrorMessage(pluginData, msg.channel, "Unknown or non-voice channel"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown or non-voice channel"); return; } @@ -116,7 +115,7 @@ export const VcmoveAllCmd = utilityCmd({ const channelId = args.channel.match(channelMentionRegex)![1]; const potentialChannel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { - sendErrorMessage(pluginData, msg.channel, "Unknown or non-voice channel"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown or non-voice channel"); return; } @@ -128,7 +127,7 @@ export const VcmoveAllCmd = utilityCmd({ ); const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, (ch) => ch.name); if (!closestMatch) { - sendErrorMessage(pluginData, msg.channel, "No matching voice channels"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No matching voice channels"); return; } @@ -136,12 +135,12 @@ export const VcmoveAllCmd = utilityCmd({ } if (args.oldChannel.members.size === 0) { - sendErrorMessage(pluginData, msg.channel, "Voice channel is empty"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Voice channel is empty"); return; } if (args.oldChannel.id === channel.id) { - sendErrorMessage(pluginData, msg.channel, "Cant move from and to the same channel!"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cant move from and to the same channel!"); return; } @@ -154,11 +153,12 @@ export const VcmoveAllCmd = utilityCmd({ // Check for permissions but allow self-moves if (currMember.id !== msg.member.id && !canActOn(pluginData, msg.member, currMember)) { - sendErrorMessage( - pluginData, - msg.channel, - `Failed to move ${renderUserUsername(currMember.user)} (${currMember.id}): You cannot act on this member`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage( + msg, + `Failed to move ${renderUserUsername(currMember.user)} (${currMember.id}): You cannot act on this member`, + ); errAmt++; continue; } @@ -169,14 +169,12 @@ export const VcmoveAllCmd = utilityCmd({ }); } catch { if (msg.member.id === currMember.id) { - sendErrorMessage(pluginData, msg.channel, "Unknown error when trying to move members"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown error when trying to move members"); return; } - sendErrorMessage( - pluginData, - msg.channel, - `Failed to move ${renderUserUsername(currMember.user)} (${currMember.id})`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(msg, `Failed to move ${renderUserUsername(currMember.user)} (${currMember.id})`); errAmt++; continue; } @@ -190,13 +188,14 @@ export const VcmoveAllCmd = utilityCmd({ } if (moveAmt !== errAmt) { - sendSuccessMessage( - pluginData, - msg.channel, - `${moveAmt - errAmt} members from **${args.oldChannel.name}** moved to **${channel.name}**`, - ); + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + msg, + `${moveAmt - errAmt} members from **${args.oldChannel.name}** moved to **${channel.name}**`, + ); } else { - sendErrorMessage(pluginData, msg.channel, `Failed to move any members.`); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Failed to move any members.`); } }, }); diff --git a/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts b/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts index ee006f5f2..33a04b8e0 100644 --- a/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts @@ -22,7 +22,6 @@ const FORUM_CHANNEL_ICON = export async function getChannelInfoEmbed( pluginData: GuildPluginData, channelId: string, - requestMemberId?: string, ): Promise { const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!channel) { diff --git a/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts b/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts index 1568a3e30..4b615cf0f 100644 --- a/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts @@ -9,7 +9,6 @@ import { trimEmptyLines, trimLines, } from "../../../utils"; -import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { UtilityPluginType } from "../types"; const MESSAGE_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/740685652152025088/message.png"; @@ -18,7 +17,6 @@ export async function getMessageInfoEmbed( pluginData: GuildPluginData, channelId: string, messageId: string, - requestMemberId?: string, ): Promise { const message = await (pluginData.guild.channels.resolve(channelId as Snowflake) as TextChannel).messages .fetch(messageId as Snowflake) @@ -27,8 +25,6 @@ export async function getMessageInfoEmbed( return null; } - const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); - const embed: EmbedWith<"fields" | "author"> = { fields: [], author: { diff --git a/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts b/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts index fa3188ec5..dd19d9b2a 100644 --- a/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts @@ -6,11 +6,7 @@ import { UtilityPluginType } from "../types"; const MENTION_ICON = "https://cdn.discordapp.com/attachments/705009450855039042/839284872152481792/mention.png"; -export async function getRoleInfoEmbed( - pluginData: GuildPluginData, - role: Role, - requestMemberId?: string, -): Promise { +export async function getRoleInfoEmbed(pluginData: GuildPluginData, role: Role): Promise { const embed: EmbedWith<"fields" | "author" | "color"> = { fields: [], author: { diff --git a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts index d0202db78..bbd1f78f0 100644 --- a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts @@ -25,7 +25,6 @@ const prettifyFeature = (feature: string): string => export async function getServerInfoEmbed( pluginData: GuildPluginData, serverId: string, - requestMemberId?: string, ): Promise { const thisServer = serverId === pluginData.guild.id ? pluginData.guild : null; const [restGuild, guildPreview] = await Promise.all([ diff --git a/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts b/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts index ef676fd17..8449ba74d 100644 --- a/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts @@ -1,17 +1,10 @@ import { APIEmbed } from "discord.js"; -import { GuildPluginData } from "knub"; import { EmbedWith, preEmbedPadding } from "../../../utils"; import { snowflakeToTimestamp } from "../../../utils/snowflakeToTimestamp"; -import { UtilityPluginType } from "../types"; const SNOWFLAKE_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/742020790471491668/snowflake.png"; -export async function getSnowflakeInfoEmbed( - pluginData: GuildPluginData, - snowflake: string, - showUnknownWarning = false, - requestMemberId?: string, -): Promise { +export async function getSnowflakeInfoEmbed(snowflake: string, showUnknownWarning = false): Promise { const embed: EmbedWith<"fields" | "author"> = { fields: [], author: { diff --git a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts index ef8d23205..b568a833a 100644 --- a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts @@ -13,7 +13,6 @@ import { trimLines, UnknownUser, } from "../../../utils"; -import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { UtilityPluginType } from "../types"; const MAX_ROLES_TO_DISPLAY = 15; @@ -27,7 +26,6 @@ export async function getUserInfoEmbed( pluginData: GuildPluginData, userId: string, compact = false, - requestMemberId?: string, ): Promise { const user = await resolveUser(pluginData.client, userId); if (!user || user instanceof UnknownUser) { @@ -40,8 +38,6 @@ export async function getUserInfoEmbed( fields: [], }; - const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); - embed.author = { name: `${user.bot ? "Bot" : "User"}: ${renderUsername(user.username, user.discriminator)}`, }; diff --git a/backend/src/plugins/Utility/search.ts b/backend/src/plugins/Utility/search.ts index 19710b581..07e3477d9 100644 --- a/backend/src/plugins/Utility/search.ts +++ b/backend/src/plugins/Utility/search.ts @@ -13,11 +13,12 @@ import escapeStringRegexp from "escape-string-regexp"; import { ArgsFromSignatureOrArray, GuildPluginData } from "knub"; import moment from "moment-timezone"; import { RegExpRunner, allowTimeout } from "../../RegExpRunner"; -import { getBaseUrl, sendErrorMessage } from "../../pluginUtils"; +import { getBaseUrl } from "../../pluginUtils"; import { MINUTES, multiSorter, renderUserUsername, sorter, trimLines } from "../../utils"; import { asyncFilter } from "../../utils/async"; import { hasDiscordPermissions } from "../../utils/hasDiscordPermissions"; import { InvalidRegexError, inputPatternToRegExp } from "../../validatorUtils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { banSearchSignature } from "./commands/BanSearchCmd"; import { searchCmdSignature } from "./commands/SearchCmd"; import { getUserInfoEmbed } from "./functions/getUserInfoEmbed"; @@ -115,12 +116,12 @@ export async function displaySearch( } } catch (e) { if (e instanceof SearchError) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } if (e instanceof InvalidRegexError) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } @@ -128,7 +129,7 @@ export async function displaySearch( } if (searchResult.totalResults === 0) { - sendErrorMessage(pluginData, msg.channel, "No results found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No results found"); return; } @@ -259,12 +260,12 @@ export async function archiveSearch( } } catch (e) { if (e instanceof SearchError) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } if (e instanceof InvalidRegexError) { - sendErrorMessage(pluginData, msg.channel, e.message); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } @@ -272,7 +273,7 @@ export async function archiveSearch( } if (results.totalResults === 0) { - sendErrorMessage(pluginData, msg.channel, "No results found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No results found"); return; } diff --git a/backend/src/plugins/availablePlugins.ts b/backend/src/plugins/availablePlugins.ts index 45119f0f3..615972152 100644 --- a/backend/src/plugins/availablePlugins.ts +++ b/backend/src/plugins/availablePlugins.ts @@ -5,6 +5,7 @@ import { BotControlPlugin } from "./BotControl/BotControlPlugin"; import { CasesPlugin } from "./Cases/CasesPlugin"; import { CensorPlugin } from "./Censor/CensorPlugin"; import { ChannelArchiverPlugin } from "./ChannelArchiver/ChannelArchiverPlugin"; +import { CommonPlugin } from "./Common/CommonPlugin"; import { CompanionChannelsPlugin } from "./CompanionChannels/CompanionChannelsPlugin"; import { ContextMenuPlugin } from "./ContextMenus/ContextMenuPlugin"; import { CountersPlugin } from "./Counters/CountersPlugin"; @@ -78,6 +79,7 @@ export const guildPlugins: Array> = [ InternalPosterPlugin, RoleManagerPlugin, RoleButtonsPlugin, + CommonPlugin, ]; // prettier-ignore @@ -96,5 +98,6 @@ export const baseGuildPlugins: Array> = [ CasesPlugin, MutesPlugin, TimeAndDatePlugin, + CommonPlugin, // TODO: Replace these with proper dependencies ]; diff --git a/backend/src/utils.ts b/backend/src/utils.ts index c6c705db6..619380190 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -1,6 +1,7 @@ import { APIEmbed, ChannelType, + ChatInputCommandInteraction, Client, DiscordAPIError, EmbedData, @@ -1409,11 +1410,11 @@ export async function resolveStickerId(bot: Client, id: Snowflake): Promise { - return waitForButtonConfirm(channel, content, { restrictToId: userId }); + return waitForButtonConfirm(context, content, { restrictToId: userId }); } export function messageSummary(msg: SavedMessage) { diff --git a/backend/src/utils/createPaginatedMessage.ts b/backend/src/utils/createPaginatedMessage.ts index d10754993..e1be4ea4a 100644 --- a/backend/src/utils/createPaginatedMessage.ts +++ b/backend/src/utils/createPaginatedMessage.ts @@ -7,7 +7,6 @@ import { MessageReaction, PartialMessageReaction, PartialUser, - TextBasedChannel, User, } from "discord.js"; import { sendContextResponse } from "../pluginUtils"; @@ -29,7 +28,7 @@ const defaultOpts: PaginateMessageOpts = { export async function createPaginatedMessage( client: Client, - context: TextBasedChannel | User | ChatInputCommandInteraction, + context: Message | User | ChatInputCommandInteraction, totalPages: number, loadPageFn: LoadPageFn, opts: Partial = {}, diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index 64cab7384..c2cc659b0 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -3,10 +3,10 @@ import { ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, + Message, MessageActionRowComponentBuilder, MessageComponentInteraction, MessageCreateOptions, - TextBasedChannel, User, } from "discord.js"; import moment from "moment"; @@ -15,8 +15,8 @@ import { isContextInteraction } from "../pluginUtils"; import { noop } from "../utils"; export async function waitForButtonConfirm( - context: TextBasedChannel | User | ChatInputCommandInteraction, - toPost: MessageCreateOptions, + context: Message | User | ChatInputCommandInteraction, + toPost: Omit, options?: WaitForOptions, ): Promise { return new Promise(async (resolve) => { @@ -33,9 +33,17 @@ export async function waitForButtonConfirm( .setLabel(options?.cancelText || "Cancel") .setCustomId(`cancelButton:${idMod}:${uuidv4()}`), ]); - const sendMethod = contextIsInteraction ? (context.replied ? "followUp" : "reply") : "send"; + const sendMethod = () => { + return contextIsInteraction + ? context.replied + ? context.followUp + : context.reply + : "send" in context + ? context.send + : context.channel.send; + }; const extraParameters = contextIsInteraction ? { fetchReply: true } : {}; - const message = await context[sendMethod]({ ...toPost, components: [row], ...extraParameters }); + const message = await sendMethod()({ ...toPost, components: [row], ...extraParameters }); const collector = message.createMessageComponentCollector({ time: 10000 }); diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index cf8fc8d33..e68d69304 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -3629,9 +3629,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001507", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001507.tgz", - "integrity": "sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A==", + "version": "1.0.30001587", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", + "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", "dev": true, "funding": [ { From cafcc2839ea572d0681ad16d498eaaa897742b8a Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sat, 17 Feb 2024 18:16:53 +0100 Subject: [PATCH 13/29] Various fixes --- backend/src/configValidator.ts | 14 +++----------- backend/src/plugins/Common/CommonPlugin.ts | 2 +- .../src/plugins/ContextMenus/actions/mute.ts | 1 - .../commands/cases/CasesModMsgCmd.ts | 2 +- .../commands/cases/CasesSlashCmd.ts | 2 +- .../commands/cases/CasesUserMsgCmd.ts | 2 +- .../ModActions/commands/warn/WarnMsgCmd.ts | 10 +++++----- .../ModActions/commands/warn/WarnSlashCmd.ts | 12 ++++++------ .../actualCommands/actualCasesCmd.ts | 14 +++++++------- .../actualCommands/actualMassUnbanCmd.ts | 5 +++-- .../functions/actualCommands/actualWarnCmd.ts | 9 ++++++--- .../ModActions/functions/updateCase.ts | 5 +++++ .../ModActions/functions/warnMember.ts | 8 +------- backend/src/types.ts | 19 +------------------ backend/src/utils/waitForInteraction.ts | 8 ++++---- 15 files changed, 45 insertions(+), 68 deletions(-) diff --git a/backend/src/configValidator.ts b/backend/src/configValidator.ts index eb115d467..b2e2fc555 100644 --- a/backend/src/configValidator.ts +++ b/backend/src/configValidator.ts @@ -1,9 +1,8 @@ -import { ConfigValidationError, PluginConfigManager } from "knub"; -import moment from "moment-timezone"; +import { BaseConfig, ConfigValidationError, PluginConfigManager } from "knub"; import { ZodError } from "zod"; import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; import { guildPlugins } from "./plugins/availablePlugins"; -import { ZeppelinGuildConfig, zZeppelinGuildConfig } from "./types"; +import { zZeppelinGuildConfig } from "./types"; import { formatZodIssue } from "./utils/formatZodIssue"; const pluginNameToPlugin = new Map(); @@ -17,14 +16,7 @@ export async function validateGuildConfig(config: any): Promise { return validationResult.error.issues.map(formatZodIssue).join("\n"); } - const guildConfig = config as ZeppelinGuildConfig; - - if (guildConfig.timezone) { - const validTimezones = moment.tz.names(); - if (!validTimezones.includes(guildConfig.timezone)) { - return `Invalid timezone: ${guildConfig.timezone}`; - } - } + const guildConfig = config as BaseConfig; if (guildConfig.plugins) { for (const [pluginName, pluginOptions] of Object.entries(guildConfig.plugins)) { diff --git a/backend/src/plugins/Common/CommonPlugin.ts b/backend/src/plugins/Common/CommonPlugin.ts index 9f4a6d82c..62ee36285 100644 --- a/backend/src/plugins/Common/CommonPlugin.ts +++ b/backend/src/plugins/Common/CommonPlugin.ts @@ -98,7 +98,7 @@ export const CommonPlugin = zeppelinGuildPlugin()({ body: string, allowedMentions?: MessageMentionOptions, responseInteraction?: ModalSubmitInteraction, - ephemeral = false, + ephemeral = true, ): Promise => { const emoji = getErrorEmoji(pluginData); const formattedBody = errorMessage(body, emoji); diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index 9861a1d25..6e69b16f5 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -69,7 +69,6 @@ async function muteAction( try { const result = await mutes.muteUser(target, durationMs, reason, reason, { caseArgs }); - const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : ""; const muteMessage = `Muted **${result.case!.user_name}** ${ durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" diff --git a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts index 717ea5314..debeff455 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts @@ -32,7 +32,7 @@ export const CasesModMsgCmd = modActionsMsgCmd({ msg, args.mod, null, - msg.author, + msg.member, args.notes, args.warns, args.mutes, diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts index 5207d76f5..7833cfabd 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -33,7 +33,7 @@ export const CasesSlashCmd = { interaction, options.mod, options.user, - interaction.user, + interaction.member, options.notes, options.warns, options.mutes, diff --git a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts index 967ae1609..5640080db 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts @@ -45,7 +45,7 @@ export const CasesUserMsgCmd = modActionsMsgCmd({ msg, args.mod, user, - msg.author, + msg.member, args.notes, args.warns, args.mutes, diff --git a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts index cd8533b60..d61753562 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts @@ -24,7 +24,7 @@ export const WarnMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); return; } @@ -33,9 +33,9 @@ export const WarnMsgCmd = modActionsMsgCmd({ if (!memberToWarn) { const _isBanned = await isBanned(pluginData, user.id); if (_isBanned) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User is banned`); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User is banned`); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found on the server`); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found on the server`); } return; @@ -43,7 +43,7 @@ export const WarnMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to warn this member if (!canActOn(pluginData, msg.member, memberToWarn)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot warn: insufficient permissions"); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot warn: insufficient permissions"); return; } @@ -62,7 +62,7 @@ export const WarnMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts index c34e91e02..43f796f2b 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts @@ -45,7 +45,7 @@ export const WarnSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData + await pluginData .getPlugin(CommonPlugin) .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); @@ -57,9 +57,9 @@ export const WarnSlashCmd = { if (!memberToWarn) { const _isBanned = await isBanned(pluginData, options.user.id); if (_isBanned) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `User is banned`); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `User is banned`); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `User not found on the server`); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `User not found on the server`); } return; @@ -67,7 +67,7 @@ export const WarnSlashCmd = { // Make sure we're allowed to warn this member if (!canActOn(pluginData, interaction.member, memberToWarn)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot warn: insufficient permissions"); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot warn: insufficient permissions"); return; } @@ -79,7 +79,7 @@ export const WarnSlashCmd = { if (options.mod) { if (!canActAsOther) { - pluginData + await pluginData .getPlugin(CommonPlugin) .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; @@ -92,7 +92,7 @@ export const WarnSlashCmd = { try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts index 1ad7b8139..ae92affa3 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts @@ -145,9 +145,9 @@ async function casesModCmd( expand: boolean | null, ) { const casesPlugin = pluginData.getPlugin(CasesPlugin); - const caseFilters = { type: In(typesToShow), is_hidden: !!hidden }; + const casesFilters = { type: In(typesToShow), is_hidden: !!hidden }; - const totalCases = await casesPlugin.getTotalCasesByMod(modId ?? author.id, caseFilters); + const totalCases = await casesPlugin.getTotalCasesByMod(modId ?? author.id, casesFilters); if (totalCases === 0) { pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `No cases by **${modName}**`); @@ -159,7 +159,7 @@ async function casesModCmd( if (expand) { // Expanded view (= individual case embeds) - const cases = totalCases > 8 ? [] : await casesPlugin.getRecentCasesByMod(modId ?? author.id, 8, 0, caseFilters); + const cases = totalCases > 8 ? [] : await casesPlugin.getRecentCasesByMod(modId ?? author.id, 8, 0, casesFilters); sendExpandedCases(pluginData, context, totalCases, cases); return; @@ -174,7 +174,7 @@ async function casesModCmd( modId ?? author.id, casesPerPage, (page - 1) * casesPerPage, - caseFilters, + casesFilters, ); const lines = await asyncMap(cases, (c) => casesPlugin.getCaseSummary(c, true, author.id)); @@ -212,7 +212,7 @@ export async function actualCasesCmd( context: Message | ChatInputCommandInteraction, modId: string | null, user: GuildMember | User | UnknownUser | null, - author: User, + author: GuildMember, notes: boolean | null, warns: boolean | null, mutes: boolean | null, @@ -253,6 +253,6 @@ export async function actualCasesCmd( } user - ? casesUserCmd(pluginData, context, author, modId!, user, modName, typesToShow, hidden, expand) - : casesModCmd(pluginData, context, author, modId!, mod!, modName, typesToShow, hidden, expand); + ? casesUserCmd(pluginData, context, author.user, modId!, user, modName, typesToShow, hidden, expand) + : casesModCmd(pluginData, context, author.user, modId!, mod ?? author, modName, typesToShow, hidden, expand); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts index bfb57111e..a980445cf 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts @@ -4,6 +4,7 @@ import { waitForReply } from "knub/helpers"; import { CaseTypes } from "../../../../data/CaseTypes"; import { LogType } from "../../../../data/LogType"; import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; +import { MINUTES } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; @@ -45,8 +46,8 @@ export async function actualMassUnbanCmd( // We'll create our own cases below and post a single "mass unbanned" log instead userIds.forEach((userId) => { // Use longer timeouts since this can take a while - ignoreEvent(pluginData, IgnoredEventType.Unban, userId, 120 * 1000); - pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, userId, 120 * 1000); + ignoreEvent(pluginData, IgnoredEventType.Unban, userId, 2 * MINUTES); + pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, userId, 2 * MINUTES); }); // Show a loading indicator since this can take a while diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts index 15a54cfed..ac43d8df5 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts @@ -37,7 +37,7 @@ export async function actualWarnCmd( { confirmText: "Yes", cancelText: "No", restrictToId: authorId }, ); if (!reply) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Warn cancelled by moderator"); + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Warn cancelled by moderator"); return; } } @@ -53,13 +53,16 @@ export async function actualWarnCmd( }); if (warnResult.status === "failed") { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Failed to warn user"); + const failReason = warnResult.error ? `: ${warnResult.error}` : ""; + + await pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Failed to warn user${failReason}`); + return; } const messageResultText = warnResult.notifyResult.text ? ` (${warnResult.notifyResult.text})` : ""; - pluginData + await pluginData .getPlugin(CommonPlugin) .sendSuccessMessage( context, diff --git a/backend/src/plugins/ModActions/functions/updateCase.ts b/backend/src/plugins/ModActions/functions/updateCase.ts index 78b5d6f44..08f11230a 100644 --- a/backend/src/plugins/ModActions/functions/updateCase.ts +++ b/backend/src/plugins/ModActions/functions/updateCase.ts @@ -6,6 +6,7 @@ import { CasesPlugin } from "../../Cases/CasesPlugin"; import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../types"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "./attachmentLinkReaction"; import { formatReasonWithMessageLinkForAttachments } from "./formatReasonForAttachments"; export async function updateCase( @@ -33,6 +34,10 @@ export async function updateCase( return; } + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, note)) { + return; + } + const formattedNote = await formatReasonWithMessageLinkForAttachments(pluginData, note, context, attachments); const casesPlugin = pluginData.getPlugin(CasesPlugin); diff --git a/backend/src/plugins/ModActions/functions/warnMember.ts b/backend/src/plugins/ModActions/functions/warnMember.ts index f87ce4085..8ba8ee380 100644 --- a/backend/src/plugins/ModActions/functions/warnMember.ts +++ b/backend/src/plugins/ModActions/functions/warnMember.ts @@ -1,7 +1,6 @@ import { GuildMember, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; -import { getContextChannel, isContextInteraction } from "../../../pluginUtils"; import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { UserNotificationResult, createUserNotificationError, notifyUser, resolveUser, ucfirst } from "../../../utils"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; @@ -52,12 +51,7 @@ export async function warnMember( } if (!notifyResult.success) { - const contextIsNotInteraction = - warnOptions.retryPromptContext && !isContextInteraction(warnOptions.retryPromptContext); - const contextChannel = contextIsNotInteraction ? await getContextChannel(warnOptions.retryPromptContext!) : null; - const isValidChannel = contextIsNotInteraction && pluginData.guild.channels.resolve(contextChannel!.id); - - if (!warnOptions.retryPromptContext || !isValidChannel) { + if (!warnOptions.retryPromptContext) { return { status: "failed", error: "Failed to message user", diff --git a/backend/src/types.ts b/backend/src/types.ts index 409c4dc63..cf0967c32 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -1,29 +1,12 @@ -import { BaseConfig, Knub } from "knub"; +import { Knub } from "knub"; import z from "zod"; import { zSnowflake } from "./utils"; -export interface ZeppelinGuildConfig extends BaseConfig { - success_emoji?: string; - error_emoji?: string; - - // Deprecated - timezone?: string; - date_formats?: any; -} - export const zZeppelinGuildConfig = z.strictObject({ // From BaseConfig prefix: z.string().optional(), levels: z.record(zSnowflake, z.number()).optional(), plugins: z.record(z.string(), z.unknown()).optional(), - - // From ZeppelinGuildConfig - success_emoji: z.string().optional(), - error_emoji: z.string().optional(), - - // Deprecated - timezone: z.string().optional(), - date_formats: z.unknown().optional(), }); export type TZeppelinKnub = Knub; diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index c2cc659b0..0cdd04379 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -36,11 +36,11 @@ export async function waitForButtonConfirm( const sendMethod = () => { return contextIsInteraction ? context.replied - ? context.followUp - : context.reply + ? context.followUp.bind(context) + : context.reply.bind(context) : "send" in context - ? context.send - : context.channel.send; + ? context.send.bind(context) + : context.channel.send.bind(context.channel); }; const extraParameters = contextIsInteraction ? { fetchReply: true } : {}; const message = await sendMethod()({ ...toPost, components: [row], ...extraParameters }); From b428e18fc7c88269b7d0cc84fe3001350888a77d Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sat, 17 Feb 2024 22:06:02 +0100 Subject: [PATCH 14/29] Fixed config parsing error with tags --- backend/src/plugins/Tags/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Tags/types.ts b/backend/src/plugins/Tags/types.ts index 5ff7e9c7c..8b8b472b4 100644 --- a/backend/src/plugins/Tags/types.ts +++ b/backend/src/plugins/Tags/types.ts @@ -4,9 +4,9 @@ import { GuildArchives } from "../../data/GuildArchives"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildTags } from "../../data/GuildTags"; -import { zEmbedInput } from "../../utils"; +import { zBoundedCharacters, zStrictMessageContent } from "../../utils"; -export const zTag = z.union([z.string(), zEmbedInput]); +export const zTag = z.union([zBoundedCharacters(0, 4000), zStrictMessageContent]); export type TTag = z.infer; export const zTagCategory = z From ba65ecb48f5fc80964aa29663f4194ccfb177990 Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sat, 17 Feb 2024 23:57:01 +0100 Subject: [PATCH 15/29] Made cases commands ephemeral by default --- backend/src/pluginUtils.ts | 13 +++++---- .../ModActions/commands/case/CaseMsgCmd.ts | 8 ++++- .../ModActions/commands/case/CaseSlashCmd.ts | 8 ++++- .../commands/cases/CasesModMsgCmd.ts | 2 ++ .../commands/cases/CasesSlashCmd.ts | 2 ++ .../commands/cases/CasesUserMsgCmd.ts | 2 ++ .../functions/actualCommands/actualCaseCmd.ts | 3 +- .../actualCommands/actualCasesCmd.ts | 29 ++++++++++++++----- 8 files changed, 52 insertions(+), 15 deletions(-) diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index f84bf3962..9e7f97834 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -5,6 +5,7 @@ import { ChatInputCommandInteraction, GuildMember, + InteractionReplyOptions, Message, MessageCreateOptions, PermissionsBitField, @@ -80,17 +81,19 @@ export async function getContextChannel( export async function sendContextResponse( context: TextBasedChannel | Message | User | ChatInputCommandInteraction, - response: string | Omit, + response: string | Omit | InteractionReplyOptions, ): Promise { if (isContextInteraction(context)) { const options = { ...(typeof response === "string" ? { content: response } : response), fetchReply: true }; return (context.replied ? context.followUp(options) : context.reply(options)) as Promise; - } else if ("send" in context) { - return context.send(response); - } else { - return (await getContextChannel(context)).send(response); } + + if (typeof response !== "string" && "ephemeral" in response) { + delete response.ephemeral; + } + + return (await getContextChannel(context)).send(response as string | Omit); } export function getBaseUrl(pluginData: AnyPluginData) { diff --git a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts index 914cec0fb..4e278e232 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts @@ -2,6 +2,10 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { actualCaseCmd } from "../../functions/actualCommands/actualCaseCmd"; import { modActionsMsgCmd } from "../../types"; +const opts = { + show: ct.switchOption({ def: false, shortcut: "sh" }), +}; + export const CaseMsgCmd = modActionsMsgCmd({ trigger: "case", permission: "can_view", @@ -10,10 +14,12 @@ export const CaseMsgCmd = modActionsMsgCmd({ signature: [ { caseNumber: ct.number(), + + ...opts, }, ], async run({ pluginData, message: msg, args }) { - actualCaseCmd(pluginData, msg, msg.author.id, args.caseNumber); + actualCaseCmd(pluginData, msg, msg.author.id, args.caseNumber, args.show); }, }); diff --git a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts index c537ec6e6..98219e3f0 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts @@ -1,6 +1,10 @@ import { slashOptions } from "knub"; import { actualCaseCmd } from "../../functions/actualCommands/actualCaseCmd"; +const opts = [ + slashOptions.boolean({ name: "show", description: "To make the result visible to everyone", required: false }), +]; + export const CaseSlashCmd = { name: "case", configPermission: "can_view", @@ -9,9 +13,11 @@ export const CaseSlashCmd = { signature: [ slashOptions.number({ name: "case-number", description: "The number of the case to show", required: true }), + + ...opts, ], async run({ interaction, options, pluginData }) { - actualCaseCmd(pluginData, interaction, interaction.user.id, options["case-number"]); + actualCaseCmd(pluginData, interaction, interaction.user.id, options["case-number"], options.show); }, }; diff --git a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts index debeff455..0777e1cf3 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts @@ -13,6 +13,7 @@ const opts = { unmutes: ct.switchOption({ def: false, shortcut: "um" }), bans: ct.switchOption({ def: false, shortcut: "b" }), unbans: ct.switchOption({ def: false, shortcut: "ub" }), + show: ct.switchOption({ def: false, shortcut: "sh" }), }; export const CasesModMsgCmd = modActionsMsgCmd({ @@ -42,6 +43,7 @@ export const CasesModMsgCmd = modActionsMsgCmd({ args.reverseFilters, args.hidden, args.expand, + args.show, ); }, }); diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts index 7833cfabd..9d22d5d70 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -17,6 +17,7 @@ const opts = [ slashOptions.boolean({ name: "unmutes", description: "To filter unmutes", required: false }), slashOptions.boolean({ name: "bans", description: "To filter bans", required: false }), slashOptions.boolean({ name: "unbans", description: "To filter unbans", required: false }), + slashOptions.boolean({ name: "show", description: "To make the result visible to everyone", required: false }), ]; export const CasesSlashCmd = { @@ -43,6 +44,7 @@ export const CasesSlashCmd = { options["reverse-filters"], options.hidden, options.expand, + options.show, ); }, }; diff --git a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts index 5640080db..a185950ec 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts @@ -15,6 +15,7 @@ const opts = { unmutes: ct.switchOption({ def: false, shortcut: "um" }), bans: ct.switchOption({ def: false, shortcut: "b" }), unbans: ct.switchOption({ def: false, shortcut: "ub" }), + show: ct.switchOption({ def: false, shortcut: "sh" }), }; export const CasesUserMsgCmd = modActionsMsgCmd({ @@ -55,6 +56,7 @@ export const CasesUserMsgCmd = modActionsMsgCmd({ args.reverseFilters, args.hidden, args.expand, + args.show, ); }, }); diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts index eabd8333c..e45dc0e70 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts @@ -10,6 +10,7 @@ export async function actualCaseCmd( context: Message | ChatInputCommandInteraction, authorId: string, caseNumber: number, + show: boolean | null, ) { const theCase = await pluginData.state.cases.findByCaseNumber(caseNumber); @@ -21,5 +22,5 @@ export async function actualCaseCmd( const casesPlugin = pluginData.getPlugin(CasesPlugin); const embed = await casesPlugin.getCaseEmbed(theCase.id, authorId); - sendContextResponse(context, embed); + sendContextResponse(context, { ...embed, ephemeral: show !== true }); } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts index ae92affa3..abe63cfad 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts @@ -29,6 +29,7 @@ async function sendExpandedCases( context: Message | ChatInputCommandInteraction, casesCount: number, cases: Case[], + show: boolean | null, ) { if (casesCount > maxExpandedCases) { await sendContextResponse(context, "Too many cases for expanded view. Please use compact view instead."); @@ -40,7 +41,7 @@ async function sendExpandedCases( for (const theCase of cases) { const embed = await casesPlugin.getCaseEmbed(theCase.id); - await sendContextResponse(context, embed); + await sendContextResponse(context, { ...embed, ephemeral: !show }); } } @@ -54,6 +55,7 @@ async function casesUserCmd( typesToShow: CaseTypes[], hidden: boolean | null, expand: boolean | null, + show: boolean | null, ) { const casesPlugin = pluginData.getPlugin(CasesPlugin); const casesFilters: Omit, "guild_id" | "user_id"> = { type: In(typesToShow) }; @@ -86,7 +88,7 @@ async function casesUserCmd( } if (expand) { - sendExpandedCases(pluginData, context, casesToDisplay.length, casesToDisplay); + sendExpandedCases(pluginData, context, casesToDisplay.length, casesToDisplay, show); return; } @@ -129,7 +131,7 @@ async function casesUserCmd( fields: [...(isLastChunk ? [footerField] : [])], } satisfies APIEmbed; - sendContextResponse(context, { embeds: [embed] }); + sendContextResponse(context, { embeds: [embed], ephemeral: !show }); } } @@ -143,6 +145,7 @@ async function casesModCmd( typesToShow: CaseTypes[], hidden: boolean | null, expand: boolean | null, + show: boolean | null, ) { const casesPlugin = pluginData.getPlugin(CasesPlugin); const casesFilters = { type: In(typesToShow), is_hidden: !!hidden }; @@ -161,7 +164,7 @@ async function casesModCmd( // Expanded view (= individual case embeds) const cases = totalCases > 8 ? [] : await casesPlugin.getRecentCasesByMod(modId ?? author.id, 8, 0, casesFilters); - sendExpandedCases(pluginData, context, totalCases, cases); + sendExpandedCases(pluginData, context, totalCases, cases, show); return; } @@ -199,7 +202,7 @@ async function casesModCmd( ], } satisfies APIEmbed; - return { embeds: [embed] }; + return { embeds: [embed], ephemeral: !show }; }, { limitToUserId: author.id, @@ -222,6 +225,7 @@ export async function actualCasesCmd( reverseFilters: boolean | null, hidden: boolean | null, expand: boolean | null, + show: boolean | null, ) { const mod = modId ? (await resolveMember(pluginData.client, pluginData.guild, modId)) || (await resolveUser(pluginData.client, modId)) @@ -253,6 +257,17 @@ export async function actualCasesCmd( } user - ? casesUserCmd(pluginData, context, author.user, modId!, user, modName, typesToShow, hidden, expand) - : casesModCmd(pluginData, context, author.user, modId!, mod ?? author, modName, typesToShow, hidden, expand); + ? casesUserCmd(pluginData, context, author.user, modId!, user, modName, typesToShow, hidden, expand, show === true) + : casesModCmd( + pluginData, + context, + author.user, + modId!, + mod ?? author, + modName, + typesToShow, + hidden, + expand, + show === true, + ); } From e4e7e1c6958276c323c948bcbce4a309cd9ba2ca Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sun, 18 Feb 2024 01:23:25 +0100 Subject: [PATCH 16/29] Fixed race condition --- .../actualCommands/actualCasesCmd.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts index abe63cfad..3248b20a4 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts @@ -88,7 +88,7 @@ async function casesUserCmd( } if (expand) { - sendExpandedCases(pluginData, context, casesToDisplay.length, casesToDisplay, show); + await sendExpandedCases(pluginData, context, casesToDisplay.length, casesToDisplay, show); return; } @@ -131,7 +131,7 @@ async function casesUserCmd( fields: [...(isLastChunk ? [footerField] : [])], } satisfies APIEmbed; - sendContextResponse(context, { embeds: [embed], ephemeral: !show }); + await sendContextResponse(context, { embeds: [embed], ephemeral: !show }); } } @@ -164,11 +164,11 @@ async function casesModCmd( // Expanded view (= individual case embeds) const cases = totalCases > 8 ? [] : await casesPlugin.getRecentCasesByMod(modId ?? author.id, 8, 0, casesFilters); - sendExpandedCases(pluginData, context, totalCases, cases, show); + await sendExpandedCases(pluginData, context, totalCases, cases, show); return; } - createPaginatedMessage( + await createPaginatedMessage( pluginData.client, context, totalPages, @@ -257,8 +257,19 @@ export async function actualCasesCmd( } user - ? casesUserCmd(pluginData, context, author.user, modId!, user, modName, typesToShow, hidden, expand, show === true) - : casesModCmd( + ? await casesUserCmd( + pluginData, + context, + author.user, + modId!, + user, + modName, + typesToShow, + hidden, + expand, + show === true, + ) + : await casesModCmd( pluginData, context, author.user, From 0fee24e9731a7f3b72c60c8970b8cd1653456bee Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sun, 18 Feb 2024 17:08:31 +0100 Subject: [PATCH 17/29] Fixed supposed-to-be-ephemeral message not being ephemeral --- .../actualCommands/actualCasesCmd.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts index 3248b20a4..24c5a356e 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts @@ -32,7 +32,10 @@ async function sendExpandedCases( show: boolean | null, ) { if (casesCount > maxExpandedCases) { - await sendContextResponse(context, "Too many cases for expanded view. Please use compact view instead."); + await sendContextResponse(context, { + content: "Too many cases for expanded view. Please use compact view instead.", + ephemeral: true, + }); return; } @@ -72,17 +75,21 @@ async function casesUserCmd( user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUsername(user); if (cases.length === 0) { - await sendContextResponse(context, `No cases found for **${userName}**${modId ? ` by ${modName}` : ""}.`); + await sendContextResponse(context, { + content: `No cases found for **${userName}**${modId ? ` by ${modName}` : ""}.`, + ephemeral: true, + }); + return; } const casesToDisplay = hidden ? cases : normalCases; if (!casesToDisplay.length) { - await sendContextResponse( - context, - `No normal cases found for **${userName}**. Use "-hidden" to show ${cases.length} hidden cases.`, - ); + await sendContextResponse(context, { + content: `No normal cases found for **${userName}**. Use "-hidden" to show ${cases.length} hidden cases.`, + ephemeral: true, + }); return; } From 174e5cc23b8b3ac684f12c437ffaf88616774ac1 Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Tue, 20 Feb 2024 14:23:43 +0100 Subject: [PATCH 18/29] Fixed mute command not updating case when no reason --- backend/src/plugins/Mutes/functions/muteUser.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index ba2fe387b..c61766c1f 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -246,10 +246,12 @@ export async function muteUser( if (theCase) { // Update old case const noteDetails = [`Mute updated to ${muteTime ? timeUntilUnmuteStr : "indefinite"}`]; - const reasons = reason ? [reason] : []; + const reasons = reason ? [reason] : [""]; // Empty string so that there is a case update even without reason + if (muteOptions.caseArgs?.extraNotes) { reasons.push(...muteOptions.caseArgs.extraNotes); } + for (const noteReason of reasons) { await casesPlugin.createCaseNote({ caseId: existingMute!.case_id, From 2874a0cf8328ca2fcabf344166f73e012a90e72a Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Thu, 22 Feb 2024 01:07:19 +0100 Subject: [PATCH 19/29] Fixed case type filters --- .../commands/cases/CasesModMsgCmd.ts | 2 ++ .../commands/cases/CasesSlashCmd.ts | 2 ++ .../commands/cases/CasesUserMsgCmd.ts | 2 ++ .../actualCommands/actualCasesCmd.ts | 22 +++++++++++-------- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts index 0777e1cf3..57f45c748 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts @@ -11,6 +11,7 @@ const opts = { warns: ct.switchOption({ def: false, shortcut: "w" }), mutes: ct.switchOption({ def: false, shortcut: "m" }), unmutes: ct.switchOption({ def: false, shortcut: "um" }), + kicks: ct.switchOption({ def: false, shortcut: "k" }), bans: ct.switchOption({ def: false, shortcut: "b" }), unbans: ct.switchOption({ def: false, shortcut: "ub" }), show: ct.switchOption({ def: false, shortcut: "sh" }), @@ -38,6 +39,7 @@ export const CasesModMsgCmd = modActionsMsgCmd({ args.warns, args.mutes, args.unmutes, + args.kicks, args.bans, args.unbans, args.reverseFilters, diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts index 9d22d5d70..3e7209175 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -15,6 +15,7 @@ const opts = [ slashOptions.boolean({ name: "warns", description: "To filter warns", required: false }), slashOptions.boolean({ name: "mutes", description: "To filter mutes", required: false }), slashOptions.boolean({ name: "unmutes", description: "To filter unmutes", required: false }), + slashOptions.boolean({ name: "kicks", description: "To filter kicks", required: false }), slashOptions.boolean({ name: "bans", description: "To filter bans", required: false }), slashOptions.boolean({ name: "unbans", description: "To filter unbans", required: false }), slashOptions.boolean({ name: "show", description: "To make the result visible to everyone", required: false }), @@ -39,6 +40,7 @@ export const CasesSlashCmd = { options.warns, options.mutes, options.unmutes, + options.kicks, options.bans, options.unbans, options["reverse-filters"], diff --git a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts index a185950ec..c65226a6b 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts @@ -13,6 +13,7 @@ const opts = { warns: ct.switchOption({ def: false, shortcut: "w" }), mutes: ct.switchOption({ def: false, shortcut: "m" }), unmutes: ct.switchOption({ def: false, shortcut: "um" }), + kicks: ct.switchOption({ def: false, shortcut: "k" }), bans: ct.switchOption({ def: false, shortcut: "b" }), unbans: ct.switchOption({ def: false, shortcut: "ub" }), show: ct.switchOption({ def: false, shortcut: "sh" }), @@ -51,6 +52,7 @@ export const CasesUserMsgCmd = modActionsMsgCmd({ args.warns, args.mutes, args.unmutes, + args.kicks, args.bans, args.unbans, args.reverseFilters, diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts index 24c5a356e..9bb2d580c 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts @@ -227,6 +227,7 @@ export async function actualCasesCmd( warns: boolean | null, mutes: boolean | null, unmutes: boolean | null, + kicks: boolean | null, bans: boolean | null, unbans: boolean | null, reverseFilters: boolean | null, @@ -239,27 +240,30 @@ export async function actualCasesCmd( : null; const modName = modId ? (mod instanceof UnknownUser ? modId : renderUsername(mod!)) : renderUsername(author); + const allTypes = [ + CaseTypes.Note, + CaseTypes.Warn, + CaseTypes.Mute, + CaseTypes.Unmute, + CaseTypes.Kick, + CaseTypes.Ban, + CaseTypes.Unban, + ]; let typesToShow: CaseTypes[] = []; if (notes) typesToShow.push(CaseTypes.Note); if (warns) typesToShow.push(CaseTypes.Warn); if (mutes) typesToShow.push(CaseTypes.Mute); if (unmutes) typesToShow.push(CaseTypes.Unmute); + if (kicks) typesToShow.push(CaseTypes.Kick); if (bans) typesToShow.push(CaseTypes.Ban); if (unbans) typesToShow.push(CaseTypes.Unban); if (typesToShow.length === 0) { - typesToShow = [CaseTypes.Note, CaseTypes.Warn, CaseTypes.Mute, CaseTypes.Unmute, CaseTypes.Ban, CaseTypes.Unban]; + typesToShow = allTypes; } else { if (reverseFilters) { - typesToShow = [ - CaseTypes.Note, - CaseTypes.Warn, - CaseTypes.Mute, - CaseTypes.Unmute, - CaseTypes.Ban, - CaseTypes.Unban, - ].filter((t) => !typesToShow.includes(t)); + typesToShow = allTypes.filter((t) => !typesToShow.includes(t)); } } From a49bb81ce194cd74b931cba49eaa7231680ff22c Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Thu, 22 Feb 2024 02:44:46 +0100 Subject: [PATCH 20/29] Fixed ban command --- .../functions/actualCommands/actualBanCmd.ts | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts index bc944a599..e4bf5ff94 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts @@ -60,77 +60,77 @@ export async function actualBanCmd( } else { forceban = true; } - } - - // Abort if trying to ban user indefinitely if they are already banned indefinitely - if (!existingTempban && !time) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `User is already banned indefinitely.`); - return; - } + } else { + // Abort if trying to ban user indefinitely if they are already banned indefinitely + if (!existingTempban && !time) { + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `User is already banned indefinitely.`); + return; + } - // Ask the mod if we should update the existing ban - const reply = await waitForButtonConfirm( - context, - { content: "Failed to message the user. Log the warning anyway?" }, - { confirmText: "Yes", cancelText: "No", restrictToId: author.id }, - ); + // Ask the mod if we should update the existing ban + const reply = await waitForButtonConfirm( + context, + { content: "Failed to message the user. Log the warning anyway?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: author.id }, + ); - if (!reply) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "User already banned, update cancelled by moderator"); - lock.unlock(); - return; - } + if (!reply) { + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, "User already banned, update cancelled by moderator"); + lock.unlock(); + return; + } - // Update or add new tempban / remove old tempban - if (time && time > 0) { - if (existingTempban) { - await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id); - } else { - await pluginData.state.tempbans.addTempban(user.id, time, mod.id); + // Update or add new tempban / remove old tempban + if (time && time > 0) { + if (existingTempban) { + await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id); + } else { + await pluginData.state.tempbans.addTempban(user.id, time, mod.id); + } + const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!; + registerExpiringTempban(tempban); + } else if (existingTempban) { + clearExpiringTempban(existingTempban); + pluginData.state.tempbans.clear(user.id); } - const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!; - registerExpiringTempban(tempban); - } else if (existingTempban) { - clearExpiringTempban(existingTempban); - pluginData.state.tempbans.clear(user.id); - } - // Create a new case for the updated ban since we never stored the old case id and log the action - const casesPlugin = pluginData.getPlugin(CasesPlugin); - const createdCase = await casesPlugin.createCase({ - modId: mod.id, - type: CaseTypes.Ban, - userId: user.id, - reason: formattedReason, - noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`], - }); - if (time) { - pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ - mod: mod.user, - user, - caseNumber: createdCase.case_number, - reason: formattedReason, - banTime: humanizeDuration(time), - }); - } else { - pluginData.getPlugin(LogsPlugin).logMemberBan({ - mod: mod.user, - user, - caseNumber: createdCase.case_number, + // Create a new case for the updated ban since we never stored the old case id and log the action + const casesPlugin = pluginData.getPlugin(CasesPlugin); + const createdCase = await casesPlugin.createCase({ + modId: mod.id, + type: CaseTypes.Ban, + userId: user.id, reason: formattedReason, + noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`], }); - } + if (time) { + pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ + mod: mod.user, + user, + caseNumber: createdCase.case_number, + reason: formattedReason, + banTime: humanizeDuration(time), + }); + } else { + pluginData.getPlugin(LogsPlugin).logMemberBan({ + mod: mod.user, + user, + caseNumber: createdCase.case_number, + reason: formattedReason, + }); + } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - context, - `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, - ); - lock.unlock(); - return; + pluginData + .getPlugin(CommonPlugin) + .sendSuccessMessage( + context, + `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, + ); + lock.unlock(); + return; + } } // Make sure we're allowed to ban this member if they are on the server From 4d8b6b5cd73f9d59b3133c375e6b7556b2633463 Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sat, 24 Feb 2024 03:41:43 +0100 Subject: [PATCH 21/29] Made confirms ephemeral and fixed slash command duration options --- backend/src/plugins/Common/CommonPlugin.ts | 4 ++-- .../commands/forcemute/ForceMuteSlashCmd.ts | 4 ++-- .../commands/forceunmute/ForceUnmuteSlashCmd.ts | 4 ++-- .../ModActions/commands/mute/MuteSlashCmd.ts | 13 ++----------- .../ModActions/commands/unmute/UnmuteSlashCmd.ts | 4 ++-- backend/src/utils/waitForInteraction.ts | 12 ++++++------ 6 files changed, 16 insertions(+), 25 deletions(-) diff --git a/backend/src/plugins/Common/CommonPlugin.ts b/backend/src/plugins/Common/CommonPlugin.ts index 62ee36285..7f9c2204e 100644 --- a/backend/src/plugins/Common/CommonPlugin.ts +++ b/backend/src/plugins/Common/CommonPlugin.ts @@ -76,7 +76,7 @@ export const CommonPlugin = zeppelinGuildPlugin()({ }); } - const replyMethod = context.replied ? "followUp" : "reply"; + const replyMethod = context.replied ? "editReply" : "reply"; return context[replyMethod]({ content: formattedBody, @@ -127,7 +127,7 @@ export const CommonPlugin = zeppelinGuildPlugin()({ }); } - const replyMethod = context.replied ? "followUp" : "reply"; + const replyMethod = context.replied ? "editReply" : "reply"; return context[replyMethod]({ content: formattedBody, diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts index 6d6c2fb90..a306fc4a0 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts @@ -71,7 +71,7 @@ export const ForceMuteSlashCmd = { ppId = interaction.user.id; } - const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + const convertedTime = options.time ? convertDelayStringToMS(options.time) ?? undefined : undefined; if (options.time && !convertedTime) { pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; @@ -92,7 +92,7 @@ export const ForceMuteSlashCmd = { attachments, mod, ppId, - options.time, + convertedTime, options.reason, contactMethods, ); diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts index 3c68a64e4..6bf6ca909 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts @@ -54,12 +54,12 @@ export const ForceUnmuteSlashCmd = { ppId = interaction.user.id; } - const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + const convertedTime = options.time ? convertDelayStringToMS(options.time) ?? undefined : undefined; if (options.time && !convertedTime) { pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } - actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, options.time, options.reason); + actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, convertedTime, options.reason); }, }; diff --git a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts index c84ce9efb..b3fb3f01b 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts @@ -45,15 +45,6 @@ export const MuteSlashCmd = { async run({ interaction, options, pluginData }) { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); - - if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); - - return; - } - const memberToMute = await resolveMember(pluginData.client, pluginData.guild, options.user.id); if (!memberToMute) { @@ -106,7 +97,7 @@ export const MuteSlashCmd = { ppId = interaction.user.id; } - const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + const convertedTime = options.time ? convertDelayStringToMS(options.time) ?? undefined : undefined; if (options.time && !convertedTime) { pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; @@ -127,7 +118,7 @@ export const MuteSlashCmd = { attachments, mod, ppId, - options.time, + convertedTime, options.reason, contactMethods, ); diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts index 97163b2bf..5186e86bd 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts @@ -102,12 +102,12 @@ export const UnmuteSlashCmd = { ppId = interaction.user.id; } - const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; + const convertedTime = options.time ? convertDelayStringToMS(options.time) ?? undefined : undefined; if (options.time && !convertedTime) { pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } - actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, options.time, options.reason); + actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, convertedTime, options.reason); }, }; diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index 0cdd04379..59054d727 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -36,14 +36,14 @@ export async function waitForButtonConfirm( const sendMethod = () => { return contextIsInteraction ? context.replied - ? context.followUp.bind(context) + ? context.editReply.bind(context) : context.reply.bind(context) : "send" in context ? context.send.bind(context) : context.channel.send.bind(context.channel); }; - const extraParameters = contextIsInteraction ? { fetchReply: true } : {}; - const message = await sendMethod()({ ...toPost, components: [row], ...extraParameters }); + const extraParameters = contextIsInteraction ? { fetchReply: true, ephemeral: true } : {}; + const message = (await sendMethod()({ ...toPost, components: [row], ...extraParameters })) as Message; const collector = message.createMessageComponentCollector({ time: 10000 }); @@ -55,16 +55,16 @@ export async function waitForButtonConfirm( .catch((err) => console.trace(err.message)); } else { if (interaction.customId.startsWith(`confirmButton:${idMod}:`)) { - message.delete(); + if (!contextIsInteraction) message.delete(); resolve(true); } else if (interaction.customId.startsWith(`cancelButton:${idMod}:`)) { - message.delete(); + if (!contextIsInteraction) message.delete(); resolve(false); } } }); collector.on("end", () => { - if (message.deletable) message.delete().catch(noop); + if (!contextIsInteraction && message.deletable) message.delete().catch(noop); resolve(false); }); }); From 91025d856994557035aec60e50249fdb2f56c803 Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sat, 24 Feb 2024 20:30:11 +0100 Subject: [PATCH 22/29] Made mass action reason part of the slash command options --- .../commands/addcase/AddCaseSlashCmd.ts | 2 +- .../ModActions/commands/ban/BanSlashCmd.ts | 2 +- .../commands/forceban/ForceBanSlashCmd.ts | 2 +- .../commands/forcemute/ForceMuteSlashCmd.ts | 2 +- .../forceunmute/ForceUnmuteSlashCmd.ts | 2 +- .../ModActions/commands/kick/KickSlashCmd.ts | 2 +- .../commands/massban/MassBanMsgCmd.ts | 16 +++++- .../commands/massban/MassBanSlashCmd.ts | 36 ++++++++++++- .../commands/massmute/MassMuteMsgCmd.ts | 19 ++++++- .../commands/massmute/MassMuteSlashCmd.ts | 36 ++++++++++++- .../commands/massunban/MassUnbanMsgCmd.ts | 17 +++++- .../commands/massunban/MassUnbanSlashCmd.ts | 36 ++++++++++++- .../ModActions/commands/mute/MuteSlashCmd.ts | 2 +- .../commands/unban/UnbanSlashCmd.ts | 2 +- .../commands/unmute/UnmuteSlashCmd.ts | 2 +- .../ModActions/commands/warn/WarnSlashCmd.ts | 2 +- .../actualCommands/actualMassBanCmd.ts | 53 ++++++++++--------- .../actualCommands/actualMassMuteCmd.ts | 37 +++++-------- .../actualCommands/actualMassUnbanCmd.ts | 31 +++++------ 19 files changed, 213 insertions(+), 88 deletions(-) diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts index c83a6b42d..2fdaf6c86 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts @@ -11,7 +11,7 @@ const opts = [ slashOptions.user({ name: "mod", description: "The moderator to add this case as", required: false }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the case", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts index dd86a5464..cae32c55d 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts @@ -34,7 +34,7 @@ const opts = [ }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the ban", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts index c246982ec..57de4a0d0 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts @@ -11,7 +11,7 @@ const opts = [ slashOptions.user({ name: "mod", description: "The moderator to ban as", required: false }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the ban", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts index a306fc4a0..7bb65b731 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts @@ -29,7 +29,7 @@ const opts = [ }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the mute", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts index 6bf6ca909..5d93ef00b 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts @@ -12,7 +12,7 @@ const opts = [ slashOptions.user({ name: "mod", description: "The moderator to unmute as", required: false }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the unmute", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts index 961445ec7..98c464525 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts @@ -33,7 +33,7 @@ const opts = [ }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the kick", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts index d607224af..d2f72c702 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts @@ -1,4 +1,7 @@ +import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; import { modActionsMsgCmd } from "../../types"; @@ -14,6 +17,17 @@ export const MassBanMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualMassBanCmd(pluginData, msg, args.userIds, msg.member); + // Ask for ban reason (cleaner this way instead of trying to cram it into the args) + sendContextResponse(msg, "Ban reason? `cancel` to cancel"); + const banReasonReply = await waitForReply(pluginData.client, await getContextChannel(msg), msg.author.id); + + if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") { + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cancelled"); + return; + } + + actualMassBanCmd(pluginData, msg, args.userIds, msg.member, banReasonReply.content, [ + ...banReasonReply.attachments.values(), + ]); }, }); diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts index 74f9ba918..6905e0b14 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts @@ -1,5 +1,16 @@ import { slashOptions } from "knub"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "reason", description: "The reason", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason", + }), +]; export const MassBanSlashCmd = { name: "massban", @@ -7,9 +18,30 @@ export const MassBanSlashCmd = { description: "Mass-ban a list of user IDs", allowDms: false, - signature: [slashOptions.string({ name: "user-ids", description: "The list of user IDs to ban", required: true })], + signature: [ + slashOptions.string({ name: "user-ids", description: "The list of user IDs to ban", required: true }), + + ...opts, + ], async run({ interaction, options, pluginData }) { - actualMassBanCmd(pluginData, interaction, options["user-ids"].split(/[\s,\r\n]+/), interaction.member); + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + actualMassBanCmd( + pluginData, + interaction, + options["user-ids"].split(/[\s,\r\n]+/), + interaction.member, + options.reason || "", + attachments, + ); }, }; diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts index a09a5f26e..713e89dd4 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts @@ -1,4 +1,7 @@ +import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; +import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMassMuteCmd } from "../../functions/actualCommands/actualMassMuteCmd"; import { modActionsMsgCmd } from "../../types"; @@ -14,6 +17,20 @@ export const MassMuteMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualMassMuteCmd(pluginData, msg, args.userIds, msg.member); + // Ask for mute reason + sendContextResponse(msg, "Mute reason? `cancel` to cancel"); + const muteReasonReceived = await waitForReply(pluginData.client, await getContextChannel(msg), msg.author.id); + if ( + !muteReasonReceived || + !muteReasonReceived.content || + muteReasonReceived.content.toLowerCase().trim() === "cancel" + ) { + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cancelled"); + return; + } + + actualMassMuteCmd(pluginData, msg, args.userIds, msg.member, muteReasonReceived.content, [ + ...muteReasonReceived.attachments.values(), + ]); }, }); diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts index 1650b1744..da58647d1 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts @@ -1,5 +1,16 @@ import { slashOptions } from "knub"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMassMuteCmd } from "../../functions/actualCommands/actualMassMuteCmd"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "reason", description: "The reason", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason", + }), +]; export const MassMuteSlashSlashCmd = { name: "massmute", @@ -7,9 +18,30 @@ export const MassMuteSlashSlashCmd = { description: "Mass-mute a list of user IDs", allowDms: false, - signature: [slashOptions.string({ name: "user-ids", description: "The list of user IDs to mute", required: true })], + signature: [ + slashOptions.string({ name: "user-ids", description: "The list of user IDs to mute", required: true }), + + ...opts, + ], async run({ interaction, options, pluginData }) { - actualMassMuteCmd(pluginData, interaction, options["user-ids"].split(/[\s,\r\n]+/), interaction.member); + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + actualMassMuteCmd( + pluginData, + interaction, + options["user-ids"].split(/[\s,\r\n]+/), + interaction.member, + options.reason || "", + attachments, + ); }, }; diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts index 72713ad52..121be1cb8 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts @@ -1,5 +1,8 @@ +import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; +import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; +import { actualMassUnbanCmd } from "../../functions/actualCommands/actualMassUnbanCmd"; import { modActionsMsgCmd } from "../../types"; export const MassUnbanMsgCmd = modActionsMsgCmd({ @@ -14,6 +17,16 @@ export const MassUnbanMsgCmd = modActionsMsgCmd({ ], async run({ pluginData, message: msg, args }) { - actualMassBanCmd(pluginData, msg, args.userIds, msg.member); + // Ask for unban reason (cleaner this way instead of trying to cram it into the args) + sendContextResponse(msg, "Unban reason? `cancel` to cancel"); + const unbanReasonReply = await waitForReply(pluginData.client, await getContextChannel(msg), msg.author.id); + if (!unbanReasonReply || !unbanReasonReply.content || unbanReasonReply.content.toLowerCase().trim() === "cancel") { + pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cancelled"); + return; + } + + actualMassUnbanCmd(pluginData, msg, args.userIds, msg.member, unbanReasonReply.content, [ + ...unbanReasonReply.attachments.values(), + ]); }, }); diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts index 15f6ca6a0..ded26357b 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts @@ -1,5 +1,16 @@ import { slashOptions } from "knub"; +import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; +import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMassUnbanCmd } from "../../functions/actualCommands/actualMassUnbanCmd"; +import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; + +const opts = [ + slashOptions.string({ name: "reason", description: "The reason", required: false }), + ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { + name: "attachment", + description: "An attachment to add to the reason", + }), +]; export const MassUnbanSlashCmd = { name: "massunban", @@ -7,9 +18,30 @@ export const MassUnbanSlashCmd = { description: "Mass-unban a list of user IDs", allowDms: false, - signature: [slashOptions.string({ name: "user-ids", description: "The list of user IDs to unban", required: true })], + signature: [ + slashOptions.string({ name: "user-ids", description: "The list of user IDs to unban", required: true }), + + ...opts, + ], async run({ interaction, options, pluginData }) { - actualMassUnbanCmd(pluginData, interaction, options["user-ids"].split(/[\s,\r\n]+/), interaction.member); + const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); + + if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + + return; + } + + actualMassUnbanCmd( + pluginData, + interaction, + options["user-ids"].split(/[\s,\r\n]+/), + interaction.member, + options.reason || "", + attachments, + ); }, }; diff --git a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts index b3fb3f01b..37b11b524 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts @@ -31,7 +31,7 @@ const opts = [ }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the mute", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts index abc969dee..58be63b23 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts @@ -10,7 +10,7 @@ const opts = [ slashOptions.user({ name: "mod", description: "The moderator to unban as", required: false }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the unban", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts index 5186e86bd..41ec3eac2 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts @@ -15,7 +15,7 @@ const opts = [ slashOptions.user({ name: "mod", description: "The moderator to unmute as", required: false }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the unmute", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts index 43f796f2b..920326a51 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts @@ -29,7 +29,7 @@ const opts = [ }), ...generateAttachmentSlashOptions(NUMBER_ATTACHMENTS_CASE_CREATION, { name: "attachment", - description: "An attachment to add to the reason of the warn", + description: "An attachment to add to the reason", }), ]; diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts index 2b35c51ad..f1c4953cc 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts @@ -1,6 +1,5 @@ -import { ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { waitForReply } from "knub/helpers"; import { CaseTypes } from "../../../../data/CaseTypes"; import { LogType } from "../../../../data/LogType"; import { humanizeDurationShort } from "../../../../humanizeDurationShort"; @@ -19,6 +18,8 @@ export async function actualMassBanCmd( context: Message | ChatInputCommandInteraction, userIds: string[], author: GuildMember, + reason: string, + attachments: Attachment[], ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { @@ -26,25 +27,12 @@ export async function actualMassBanCmd( return; } - // Ask for ban reason (cleaner this way instead of trying to cram it into the args) - sendContextResponse(context, "Ban reason? `cancel` to cancel"); - const banReasonReply = await waitForReply(pluginData.client, await getContextChannel(context), author.id); - - if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Cancelled"); - return; - } - - if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, banReasonReply.content)) { + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { return; } - const banReason = await formatReasonWithMessageLinkForAttachments(pluginData, banReasonReply.content, context, [ - ...banReasonReply.attachments.values(), - ]); - const banReasonWithAttachments = formatReasonWithAttachments(banReasonReply.content, [ - ...banReasonReply.attachments.values(), - ]); + const banReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); + const banReasonWithAttachments = formatReasonWithAttachments(reason, attachments); // Verify we can act on each of the users specified for (const userId of userIds) { @@ -64,7 +52,7 @@ export async function actualMassBanCmd( pluginData.state.massbanQueue.length === 0 ? "Banning..." : `Massban queued. Waiting for previous massban to finish (max wait ${maxWaitTimeFormatted}).`; - const loadingMsg = await sendContextResponse(context, initialLoadingText); + const loadingMsg = await sendContextResponse(context, { content: initialLoadingText, ephemeral: true }); const waitTimeStart = performance.now(); const waitingInterval = setInterval(() => { @@ -78,11 +66,20 @@ export async function actualMassBanCmd( clearInterval(waitingInterval); if (pluginData.state.unloaded) { - void loadingMsg.delete().catch(noop); + if (isContextInteraction(context)) { + void context.deleteReply().catch(noop); + } else { + void loadingMsg.delete().catch(noop); + } + return; } - void loadingMsg.edit("Banning...").catch(noop); + if (isContextInteraction(context)) { + void context.editReply("Banning...").catch(noop); + } else { + void loadingMsg.edit("Banning...").catch(noop); + } // Ban each user and count failed bans (if any) const startTime = performance.now(); @@ -124,15 +121,23 @@ export async function actualMassBanCmd( // Send a status update every 10 bans if ((i + 1) % 10 === 0) { - loadingMsg.edit(`Banning... ${i + 1}/${userIds.length}`).catch(noop); + const newLoadingMessageContent = `Banning... ${i + 1}/${userIds.length}`; + + if (isContextInteraction(context)) { + void context.editReply(newLoadingMessageContent).catch(noop); + } else { + loadingMsg.edit(newLoadingMessageContent).catch(noop); + } } } const totalTime = performance.now() - startTime; const formattedTimeTaken = humanizeDurationShort(totalTime, { round: true }); - // Clear loading indicator - loadingMsg.delete().catch(noop); + if (!isContextInteraction(context)) { + // Clear loading indicator + loadingMsg.delete().catch(noop); + } const successfulBanCount = userIds.length - failedBans.length; if (successfulBanCount === 0) { diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts index 2d8086cbb..53a82421a 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts @@ -1,9 +1,8 @@ -import { ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { waitForReply } from "knub/helpers"; import { LogType } from "../../../../data/LogType"; import { logger } from "../../../../logger"; -import { canActOn, getContextChannel, sendContextResponse } from "../../../../pluginUtils"; +import { canActOn, isContextInteraction, sendContextResponse } from "../../../../pluginUtils"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; @@ -16,6 +15,8 @@ export async function actualMassMuteCmd( context: Message | ChatInputCommandInteraction, userIds: string[], author: GuildMember, + reason: string, + attachments: Attachment[], ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { @@ -23,28 +24,12 @@ export async function actualMassMuteCmd( return; } - // Ask for mute reason - sendContextResponse(context, "Mute reason? `cancel` to cancel"); - const muteReasonReceived = await waitForReply(pluginData.client, await getContextChannel(context), author.id); - if ( - !muteReasonReceived || - !muteReasonReceived.content || - muteReasonReceived.content.toLowerCase().trim() === "cancel" - ) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Cancelled"); + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { return; } - if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, muteReasonReceived.content)) { - return; - } - - const muteReason = await formatReasonWithMessageLinkForAttachments(pluginData, muteReasonReceived.content, context, [ - ...muteReasonReceived.attachments.values(), - ]); - const muteReasonWithAttachments = formatReasonWithAttachments(muteReasonReceived.content, [ - ...muteReasonReceived.attachments.values(), - ]); + const muteReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); + const muteReasonWithAttachments = formatReasonWithAttachments(reason, attachments); // Verify we can act upon all users for (const userId of userIds) { @@ -65,7 +50,7 @@ export async function actualMassMuteCmd( }); // Show loading indicator - const loadingMsg = await sendContextResponse(context, "Muting..."); + const loadingMsg = await sendContextResponse(context, { content: "Muting...", ephemeral: true }); // Mute everyone and count fails const modId = author.id; @@ -84,8 +69,10 @@ export async function actualMassMuteCmd( } } - // Clear loading indicator - loadingMsg.delete(); + if (!isContextInteraction(context)) { + // Clear loading indicator + loadingMsg.delete(); + } const successfulMuteCount = userIds.length - failedMutes.length; if (successfulMuteCount === 0) { diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts index a980445cf..8cf1b4ac1 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts @@ -1,10 +1,9 @@ -import { ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, GuildMember, Message, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { waitForReply } from "knub/helpers"; import { CaseTypes } from "../../../../data/CaseTypes"; import { LogType } from "../../../../data/LogType"; -import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; -import { MINUTES } from "../../../../utils"; +import { isContextInteraction, sendContextResponse } from "../../../../pluginUtils"; +import { MINUTES, noop } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; @@ -19,6 +18,8 @@ export async function actualMassUnbanCmd( context: Message | ChatInputCommandInteraction, userIds: string[], author: GuildMember, + reason: string, + attachments: Attachment[], ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { @@ -26,21 +27,11 @@ export async function actualMassUnbanCmd( return; } - // Ask for unban reason (cleaner this way instead of trying to cram it into the args) - sendContextResponse(context, "Unban reason? `cancel` to cancel"); - const unbanReasonReply = await waitForReply(pluginData.client, await getContextChannel(context), author.id); - if (!unbanReasonReply || !unbanReasonReply.content || unbanReasonReply.content.toLowerCase().trim() === "cancel") { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Cancelled"); + if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { return; } - if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, unbanReasonReply.content)) { - return; - } - - const unbanReason = await formatReasonWithMessageLinkForAttachments(pluginData, unbanReasonReply.content, context, [ - ...unbanReasonReply.attachments.values(), - ]); + const unbanReason = await formatReasonWithMessageLinkForAttachments(pluginData, reason, context, attachments); // Ignore automatic unban cases and logs for these users // We'll create our own cases below and post a single "mass unbanned" log instead @@ -51,7 +42,7 @@ export async function actualMassUnbanCmd( }); // Show a loading indicator since this can take a while - const loadingMsg = await sendContextResponse(context, "Unbanning..."); + const loadingMsg = await sendContextResponse(context, { content: "Unbanning...", ephemeral: true }); // Unban each user and count failed unbans (if any) const failedUnbans: Array<{ userId: string; reason: UnbanFailReasons }> = []; @@ -77,8 +68,10 @@ export async function actualMassUnbanCmd( } } - // Clear loading indicator - loadingMsg.delete(); + if (!isContextInteraction(context)) { + // Clear loading indicator + loadingMsg.delete().catch(noop); + } const successfulUnbanCount = userIds.length - failedUnbans.length; if (successfulUnbanCount === 0) { From e879a15aa48bbb17dbd190f62fa9778ca1d4e372 Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sat, 24 Feb 2024 20:46:48 +0100 Subject: [PATCH 23/29] Fixed incomplete attachment list for mass action message commands --- .../ModActions/functions/formatReasonForAttachments.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts b/backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts index 9bcc825ec..4cb16e822 100644 --- a/backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts +++ b/backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts @@ -10,7 +10,9 @@ export async function formatReasonWithMessageLinkForAttachments( attachments: Attachment[], ) { if (isContextMessage(context)) { - return context.attachments.size > 0 ? ((reason || "") + " " + context.url).trim() : reason; + const allAttachments = [...new Set([...context.attachments.values(), ...attachments])]; + + return allAttachments.length > 0 ? ((reason || "") + " " + context.url).trim() : reason; } if (attachments.length < 1) { From ee861bb5e9fecb6010a4b08b61e7c3d920580b4d Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sat, 24 Feb 2024 20:52:41 +0100 Subject: [PATCH 24/29] Fixed fatal error for massbans --- .../functions/actualCommands/actualMassBanCmd.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts index f1c4953cc..fcb9610d4 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts @@ -57,9 +57,13 @@ export async function actualMassBanCmd( const waitTimeStart = performance.now(); const waitingInterval = setInterval(() => { const waitTime = humanizeDurationShort(performance.now() - waitTimeStart, { round: true }); - loadingMsg - .edit(`Massban queued. Still waiting for previous massban to finish (waited ${waitTime}).`) - .catch(() => clearInterval(waitingInterval)); + const waitMessageContent = `Massban queued. Still waiting for previous massban to finish (waited ${waitTime}).`; + + if (isContextInteraction(context)) { + context.editReply(waitMessageContent).catch(() => clearInterval(waitingInterval)); + } else { + loadingMsg.edit(waitMessageContent).catch(() => clearInterval(waitingInterval)); + } }, 1 * MINUTES); pluginData.state.massbanQueue.add(async () => { From 408f1b9c30019ff4b3e5ab5c2d62ff51e2c8ebc7 Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Mon, 26 Feb 2024 22:05:25 +0100 Subject: [PATCH 25/29] Added slash command deferral to avoid timeouts --- backend/src/pluginUtils.ts | 8 +++++++- backend/src/plugins/Common/CommonPlugin.ts | 4 ++-- .../ModActions/commands/addcase/AddCaseSlashCmd.ts | 1 + .../src/plugins/ModActions/commands/ban/BanSlashCmd.ts | 1 + .../src/plugins/ModActions/commands/case/CaseSlashCmd.ts | 1 + .../plugins/ModActions/commands/cases/CasesSlashCmd.ts | 2 ++ .../ModActions/commands/deletecase/DeleteCaseSlashCmd.ts | 2 ++ .../ModActions/commands/forceban/ForceBanSlashCmd.ts | 1 + .../ModActions/commands/forcemute/ForceMuteSlashCmd.ts | 1 + .../commands/forceunmute/ForceUnmuteSlashCmd.ts | 1 + .../ModActions/commands/hidecase/HideCaseSlashCmd.ts | 1 + .../src/plugins/ModActions/commands/kick/KickSlashCmd.ts | 1 + .../ModActions/commands/massban/MassBanSlashCmd.ts | 1 + .../ModActions/commands/massmute/MassMuteSlashCmd.ts | 1 + .../ModActions/commands/massunban/MassUnbanSlashCmd.ts | 1 + .../src/plugins/ModActions/commands/mute/MuteSlashCmd.ts | 1 + .../src/plugins/ModActions/commands/note/NoteSlashCmd.ts | 1 + .../plugins/ModActions/commands/unban/UnbanSlashCmd.ts | 1 + .../ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts | 1 + .../plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts | 1 + .../plugins/ModActions/commands/update/UpdateSlashCmd.ts | 2 ++ .../src/plugins/ModActions/commands/warn/WarnSlashCmd.ts | 1 + 22 files changed, 32 insertions(+), 3 deletions(-) diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 9e7f97834..b49c4665e 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -86,7 +86,13 @@ export async function sendContextResponse( if (isContextInteraction(context)) { const options = { ...(typeof response === "string" ? { content: response } : response), fetchReply: true }; - return (context.replied ? context.followUp(options) : context.reply(options)) as Promise; + return ( + context.replied + ? context.followUp(options) + : context.deferred + ? context.editReply(options) + : context.reply(options) + ) as Promise; } if (typeof response !== "string" && "ephemeral" in response) { diff --git a/backend/src/plugins/Common/CommonPlugin.ts b/backend/src/plugins/Common/CommonPlugin.ts index 7f9c2204e..92c58b630 100644 --- a/backend/src/plugins/Common/CommonPlugin.ts +++ b/backend/src/plugins/Common/CommonPlugin.ts @@ -76,7 +76,7 @@ export const CommonPlugin = zeppelinGuildPlugin()({ }); } - const replyMethod = context.replied ? "editReply" : "reply"; + const replyMethod = context.replied || context.deferred ? "editReply" : "reply"; return context[replyMethod]({ content: formattedBody, @@ -127,7 +127,7 @@ export const CommonPlugin = zeppelinGuildPlugin()({ }); } - const replyMethod = context.replied ? "editReply" : "reply"; + const replyMethod = context.replied || context.deferred ? "editReply" : "reply"; return context[replyMethod]({ content: formattedBody, diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts index 2fdaf6c86..c8ee903e7 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts @@ -34,6 +34,7 @@ export const AddCaseSlashCmd = { ], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); // The moderator who did the action is the message author or, if used, the specified -mod diff --git a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts index cae32c55d..c1b0ebcd8 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts @@ -47,6 +47,7 @@ export const BanSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to ban", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts index 98219e3f0..3394c0868 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts @@ -18,6 +18,7 @@ export const CaseSlashCmd = { ], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); actualCaseCmd(pluginData, interaction, interaction.user.id, options["case-number"], options.show); }, }; diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts index 3e7209175..07a457fff 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -30,6 +30,8 @@ export const CasesSlashCmd = { signature: [...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); + return actualCasesCmd( pluginData, interaction, diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts index e087e3a4f..890048473 100644 --- a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts @@ -16,6 +16,8 @@ export const DeleteCaseSlashCmd = { ], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); + actualDeleteCaseCmd( pluginData, interaction, diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts index 57de4a0d0..059ff2ded 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts @@ -24,6 +24,7 @@ export const ForceBanSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to ban", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts index 7bb65b731..6f7fa2caf 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts @@ -42,6 +42,7 @@ export const ForceMuteSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to mute", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts index 5d93ef00b..8ea323fee 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts @@ -25,6 +25,7 @@ export const ForceUnmuteSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to unmute", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts index c324b250a..95f485803 100644 --- a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts @@ -12,6 +12,7 @@ export const HideCaseSlashCmd = { ], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); actualHideCaseCmd(pluginData, interaction, options["case-number"].split(/[\s,]+/).map(Number)); }, }; diff --git a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts index 98c464525..ce0a659b6 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts @@ -46,6 +46,7 @@ export const KickSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to kick", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts index 6905e0b14..ab8ea1d1e 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts @@ -25,6 +25,7 @@ export const MassBanSlashCmd = { ], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts index da58647d1..6f2c18ba7 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts @@ -25,6 +25,7 @@ export const MassMuteSlashSlashCmd = { ], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts index ded26357b..da079dfd6 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts @@ -25,6 +25,7 @@ export const MassUnbanSlashCmd = { ], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts index 37b11b524..f20585a92 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts @@ -44,6 +44,7 @@ export const MuteSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to mute", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); const memberToMute = await resolveMember(pluginData.client, pluginData.guild, options.user.id); diff --git a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts index 9647d8615..edcf60e9b 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts @@ -21,6 +21,7 @@ export const NoteSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to add a note to", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.note || options.note.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts index 58be63b23..1a8a21819 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts @@ -23,6 +23,7 @@ export const UnbanSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to unban", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts index d0a66d7c3..92d3c0fb0 100644 --- a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts @@ -12,6 +12,7 @@ export const UnhideCaseSlashCmd = { ], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); actualUnhideCaseCmd(pluginData, interaction, options["case-number"].split(/[\s,]+/).map(Number)); }, }; diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts index 41ec3eac2..df57fa05b 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts @@ -28,6 +28,7 @@ export const UnmuteSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to unmute", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { diff --git a/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts b/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts index 4f6fdae28..a1a6cabf2 100644 --- a/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts @@ -21,6 +21,8 @@ export const UpdateSlashCmd = { signature: [...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); + await updateCase( pluginData, interaction, diff --git a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts index 920326a51..7295551b4 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts @@ -42,6 +42,7 @@ export const WarnSlashCmd = { signature: [slashOptions.user({ name: "user", description: "The user to warn", required: true }), ...opts], async run({ interaction, options, pluginData }) { + await interaction.deferReply({ ephemeral: true }); const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { From 7eff7bcaa6b6f06d59cfc8656cf233cb0fd9dc1a Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Mon, 26 Feb 2024 22:23:30 +0100 Subject: [PATCH 26/29] Fixed show option for case and cases commands --- .../src/plugins/ModActions/commands/case/CaseSlashCmd.ts | 2 +- .../plugins/ModActions/commands/cases/CasesSlashCmd.ts | 2 +- .../ModActions/functions/actualCommands/actualCaseCmd.ts | 2 +- .../functions/actualCommands/actualCasesCmd.ts | 9 ++++++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts index 3394c0868..8db48612c 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts @@ -18,7 +18,7 @@ export const CaseSlashCmd = { ], async run({ interaction, options, pluginData }) { - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: options.show !== true }); actualCaseCmd(pluginData, interaction, interaction.user.id, options["case-number"], options.show); }, }; diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts index 07a457fff..ff29588dd 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -30,7 +30,7 @@ export const CasesSlashCmd = { signature: [...opts], async run({ interaction, options, pluginData }) { - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: options.show !== true }); return actualCasesCmd( pluginData, diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts index e45dc0e70..92b27199c 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts @@ -15,7 +15,7 @@ export async function actualCaseCmd( const theCase = await pluginData.state.cases.findByCaseNumber(caseNumber); if (!theCase) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Case not found"); + pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Case not found", undefined, undefined, show !== true); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts index 9bb2d580c..23c091a14 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts @@ -77,7 +77,7 @@ async function casesUserCmd( if (cases.length === 0) { await sendContextResponse(context, { content: `No cases found for **${userName}**${modId ? ` by ${modName}` : ""}.`, - ephemeral: true, + ephemeral: !show, }); return; @@ -88,7 +88,7 @@ async function casesUserCmd( if (!casesToDisplay.length) { await sendContextResponse(context, { content: `No normal cases found for **${userName}**. Use "-hidden" to show ${cases.length} hidden cases.`, - ephemeral: true, + ephemeral: !show, }); return; @@ -160,7 +160,10 @@ async function casesModCmd( const totalCases = await casesPlugin.getTotalCasesByMod(modId ?? author.id, casesFilters); if (totalCases === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `No cases by **${modName}**`); + pluginData + .getPlugin(CommonPlugin) + .sendErrorMessage(context, `No cases by **${modName}**`, undefined, undefined, !show); + return; } From cbec6101e01430b69ee667f8c5c382b3a81712e0 Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Tue, 5 Mar 2024 05:05:03 +0100 Subject: [PATCH 27/29] Used guildPluginSlashCommand instead of raw blueprints --- .../plugins/ModActions/ModActionsPlugin.ts | 40 +++++++++---------- .../commands/addcase/AddCaseSlashCmd.ts | 13 +++--- .../ModActions/commands/ban/BanSlashCmd.ts | 15 +++---- .../ModActions/commands/case/CaseSlashCmd.ts | 5 ++- .../commands/cases/CasesSlashCmd.ts | 10 +++-- .../commands/deletecase/DeleteCaseSlashCmd.ts | 10 +++-- .../commands/forceban/ForceBanSlashCmd.ts | 22 +++++++--- .../commands/forcemute/ForceMuteSlashCmd.ts | 15 +++---- .../forceunmute/ForceUnmuteSlashCmd.ts | 14 ++++--- .../commands/hidecase/HideCaseSlashCmd.ts | 7 ++-- .../ModActions/commands/kick/KickSlashCmd.ts | 15 +++---- .../commands/massban/MassBanSlashCmd.ts | 10 +++-- .../commands/massmute/MassMuteSlashCmd.ts | 10 +++-- .../commands/massunban/MassUnbanSlashCmd.ts | 8 ++-- .../ModActions/commands/mute/MuteSlashCmd.ts | 15 +++---- .../ModActions/commands/note/NoteSlashCmd.ts | 5 ++- .../commands/unban/UnbanSlashCmd.ts | 13 +++--- .../commands/unhidecase/UnhideCaseSlashCmd.ts | 7 ++-- .../commands/unmute/UnmuteSlashCmd.ts | 12 +++--- .../commands/update/UpdateSlashCmd.ts | 9 +++-- .../ModActions/commands/warn/WarnSlashCmd.ts | 13 +++--- .../functions/actualCommands/actualKickCmd.ts | 2 +- .../functions/actualCommands/actualMuteCmd.ts | 2 +- .../actualCommands/actualUnmuteCmd.ts | 2 +- .../functions/readContactMethodsFromArgs.ts | 4 +- .../ModActions/functions/updateCase.ts | 2 +- backend/src/plugins/ModActions/types.ts | 9 ++++- 27 files changed, 168 insertions(+), 121 deletions(-) diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index 8efc5c4b8..075c6d6fe 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -170,26 +170,26 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ description: "Moderation actions", defaultMemberPermissions: "0", subcommands: [ - { type: "slash", ...AddCaseSlashCmd }, - { type: "slash", ...BanSlashCmd }, - { type: "slash", ...CaseSlashCmd }, - { type: "slash", ...CasesSlashCmd }, - { type: "slash", ...DeleteCaseSlashCmd }, - { type: "slash", ...ForceBanSlashCmd }, - { type: "slash", ...ForceMuteSlashCmd }, - { type: "slash", ...ForceUnmuteSlashCmd }, - { type: "slash", ...HideCaseSlashCmd }, - { type: "slash", ...KickSlashCmd }, - { type: "slash", ...MassBanSlashCmd }, - { type: "slash", ...MassMuteSlashSlashCmd }, - { type: "slash", ...MassUnbanSlashCmd }, - { type: "slash", ...MuteSlashCmd }, - { type: "slash", ...NoteSlashCmd }, - { type: "slash", ...UnbanSlashCmd }, - { type: "slash", ...UnhideCaseSlashCmd }, - { type: "slash", ...UnmuteSlashCmd }, - { type: "slash", ...UpdateSlashCmd }, - { type: "slash", ...WarnSlashCmd }, + AddCaseSlashCmd, + BanSlashCmd, + CaseSlashCmd, + CasesSlashCmd, + DeleteCaseSlashCmd, + ForceBanSlashCmd, + ForceMuteSlashCmd, + ForceUnmuteSlashCmd, + HideCaseSlashCmd, + KickSlashCmd, + MassBanSlashCmd, + MassMuteSlashSlashCmd, + MassUnbanSlashCmd, + MuteSlashCmd, + NoteSlashCmd, + UnbanSlashCmd, + UnhideCaseSlashCmd, + UnmuteSlashCmd, + UpdateSlashCmd, + WarnSlashCmd, ], }), ], diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts index c8ee903e7..5f4276869 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts @@ -1,9 +1,12 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { CaseTypes } from "../../../../data/CaseTypes"; import { hasPermission } from "../../../../pluginUtils"; +import { resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualAddCaseCmd } from "../../functions/actualCommands/actualAddCaseCmd"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -15,7 +18,7 @@ const opts = [ }), ]; -export const AddCaseSlashCmd = { +export const AddCaseSlashCmd = modActionsSlashCmd({ name: "addcase", configPermission: "can_addcase", description: "Add an arbitrary case to the specified user without taking any action", @@ -38,7 +41,7 @@ export const AddCaseSlashCmd = { const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); // The moderator who did the action is the message author or, if used, the specified -mod - let mod = interaction.member; + let mod = interaction.member as GuildMember; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, member: interaction.member, @@ -52,13 +55,13 @@ export const AddCaseSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; } actualAddCaseCmd( pluginData, interaction, - interaction.member, + interaction.member as GuildMember, mod, attachments, options.user, @@ -66,4 +69,4 @@ export const AddCaseSlashCmd = { options.reason || "", ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts index c1b0ebcd8..21b5558a0 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts @@ -1,11 +1,12 @@ -import { ChannelType } from "discord.js"; +import { ChannelType, GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; -import { UserNotificationMethod, convertDelayStringToMS } from "../../../../utils"; +import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualBanCmd } from "../../functions/actualCommands/actualBanCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -38,7 +39,7 @@ const opts = [ }), ]; -export const BanSlashCmd = { +export const BanSlashCmd = modActionsSlashCmd({ name: "ban", configPermission: "can_ban", description: "Ban or Tempban the specified member", @@ -58,7 +59,7 @@ export const BanSlashCmd = { return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, member: interaction.member, @@ -72,7 +73,7 @@ export const BanSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; } let contactMethods: UserNotificationMethod[] | undefined; @@ -96,9 +97,9 @@ export const BanSlashCmd = { convertedTime, options.reason || "", attachments, - interaction.member, + interaction.member as GuildMember, mod, contactMethods, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts index 8db48612c..15f2d03f9 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts @@ -1,11 +1,12 @@ import { slashOptions } from "knub"; import { actualCaseCmd } from "../../functions/actualCommands/actualCaseCmd"; +import { modActionsSlashCmd } from "../../types"; const opts = [ slashOptions.boolean({ name: "show", description: "To make the result visible to everyone", required: false }), ]; -export const CaseSlashCmd = { +export const CaseSlashCmd = modActionsSlashCmd({ name: "case", configPermission: "can_view", description: "Show information about a specific case", @@ -21,4 +22,4 @@ export const CaseSlashCmd = { await interaction.deferReply({ ephemeral: options.show !== true }); actualCaseCmd(pluginData, interaction, interaction.user.id, options["case-number"], options.show); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts index ff29588dd..8e885d1a0 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -1,5 +1,7 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { actualCasesCmd } from "../../functions/actualCommands/actualCasesCmd"; +import { modActionsSlashCmd } from "../../types"; const opts = [ slashOptions.user({ name: "user", description: "The user to show cases for", required: false }), @@ -21,7 +23,7 @@ const opts = [ slashOptions.boolean({ name: "show", description: "To make the result visible to everyone", required: false }), ]; -export const CasesSlashCmd = { +export const CasesSlashCmd = modActionsSlashCmd({ name: "cases", configPermission: "can_view", description: "Show a list of cases the specified user has or the specified mod made", @@ -35,9 +37,9 @@ export const CasesSlashCmd = { return actualCasesCmd( pluginData, interaction, - options.mod, + options.mod?.id ?? null, options.user, - interaction.member, + interaction.member as GuildMember, options.notes, options.warns, options.mutes, @@ -51,4 +53,4 @@ export const CasesSlashCmd = { options.show, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts index 890048473..f852b666f 100644 --- a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts @@ -1,9 +1,11 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { actualDeleteCaseCmd } from "../../functions/actualCommands/actualDeleteCaseCmd"; +import { modActionsSlashCmd } from "../../types"; const opts = [slashOptions.boolean({ name: "force", description: "Whether or not to force delete", required: false })]; -export const DeleteCaseSlashCmd = { +export const DeleteCaseSlashCmd = modActionsSlashCmd({ name: "deletecase", configPermission: "can_deletecase", description: "Delete the specified case. This operation can *not* be reversed.", @@ -21,9 +23,9 @@ export const DeleteCaseSlashCmd = { actualDeleteCaseCmd( pluginData, interaction, - interaction.member, - options["case-number"].split(/[\s,]+/), + interaction.member as GuildMember, + options["case-number"].split(/\D+/).map(Number), !!options.force, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts index 059ff2ded..7f7e824c3 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts @@ -1,9 +1,11 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; -import { convertDelayStringToMS } from "../../../../utils"; +import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualForceBanCmd } from "../../functions/actualCommands/actualForceBanCmd"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -15,7 +17,7 @@ const opts = [ }), ]; -export const ForceBanSlashCmd = { +export const ForceBanSlashCmd = modActionsSlashCmd({ name: "forceban", configPermission: "can_ban", description: "Force-ban the specified user, even if they aren't on the server", @@ -35,7 +37,7 @@ export const ForceBanSlashCmd = { return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, member: interaction.member, @@ -49,7 +51,7 @@ export const ForceBanSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; } const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; @@ -58,6 +60,14 @@ export const ForceBanSlashCmd = { return; } - actualForceBanCmd(pluginData, interaction, interaction.user.id, options.user, options.reason, attachments, mod); + actualForceBanCmd( + pluginData, + interaction, + interaction.user.id, + options.user, + options.reason ?? "", + attachments, + mod, + ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts index 6f7fa2caf..348ff0474 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts @@ -1,11 +1,12 @@ -import { ChannelType } from "discord.js"; +import { ChannelType, GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; -import { UserNotificationMethod, convertDelayStringToMS } from "../../../../utils"; +import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -33,7 +34,7 @@ const opts = [ }), ]; -export const ForceMuteSlashCmd = { +export const ForceMuteSlashCmd = modActionsSlashCmd({ name: "forcemute", configPermission: "can_mute", description: "Force-mute the specified user, even if they're not on the server", @@ -53,7 +54,7 @@ export const ForceMuteSlashCmd = { return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; let ppId: string | undefined; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, @@ -68,7 +69,7 @@ export const ForceMuteSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; ppId = interaction.user.id; } @@ -94,8 +95,8 @@ export const ForceMuteSlashCmd = { mod, ppId, convertedTime, - options.reason, + options.reason ?? "", contactMethods, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts index 8ea323fee..326d5b735 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts @@ -1,9 +1,11 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; -import { convertDelayStringToMS } from "../../../../utils"; +import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -16,7 +18,7 @@ const opts = [ }), ]; -export const ForceUnmuteSlashCmd = { +export const ForceUnmuteSlashCmd = modActionsSlashCmd({ name: "forceunmute", configPermission: "can_mute", description: "Force-unmute the specified user, even if they're not on the server", @@ -36,7 +38,7 @@ export const ForceUnmuteSlashCmd = { return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; let ppId: string | undefined; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, @@ -51,7 +53,7 @@ export const ForceUnmuteSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; ppId = interaction.user.id; } @@ -61,6 +63,6 @@ export const ForceUnmuteSlashCmd = { return; } - actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, convertedTime, options.reason); + actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, convertedTime, options.reason ?? ""); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts index 95f485803..a501a5b13 100644 --- a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts @@ -1,7 +1,8 @@ import { slashOptions } from "knub"; import { actualHideCaseCmd } from "../../functions/actualCommands/actualHideCaseCmd"; +import { modActionsSlashCmd } from "../../types"; -export const HideCaseSlashCmd = { +export const HideCaseSlashCmd = modActionsSlashCmd({ name: "hidecase", configPermission: "can_hidecase", description: "Hide the specified case so it doesn't appear in !cases or !info", @@ -13,6 +14,6 @@ export const HideCaseSlashCmd = { async run({ interaction, options, pluginData }) { await interaction.deferReply({ ephemeral: true }); - actualHideCaseCmd(pluginData, interaction, options["case-number"].split(/[\s,]+/).map(Number)); + actualHideCaseCmd(pluginData, interaction, options["case-number"].split(/\D+/).map(Number)); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts index ce0a659b6..4776d0934 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts @@ -1,11 +1,12 @@ -import { ChannelType } from "discord.js"; +import { ChannelType, GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; -import { UserNotificationMethod } from "../../../../utils"; +import { UserNotificationMethod, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualKickCmd } from "../../functions/actualCommands/actualKickCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -37,7 +38,7 @@ const opts = [ }), ]; -export const KickSlashCmd = { +export const KickSlashCmd = modActionsSlashCmd({ name: "kick", configPermission: "can_kick", description: "Kick the specified member", @@ -57,7 +58,7 @@ export const KickSlashCmd = { return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, member: interaction.member, @@ -71,7 +72,7 @@ export const KickSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; } let contactMethods: UserNotificationMethod[] | undefined; @@ -85,7 +86,7 @@ export const KickSlashCmd = { actualKickCmd( pluginData, interaction, - interaction.member, + interaction.member as GuildMember, options.user, options.reason || "", attachments, @@ -94,4 +95,4 @@ export const KickSlashCmd = { options.clean, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts index ab8ea1d1e..de374434f 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts @@ -1,7 +1,9 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -12,7 +14,7 @@ const opts = [ }), ]; -export const MassBanSlashCmd = { +export const MassBanSlashCmd = modActionsSlashCmd({ name: "massban", configPermission: "can_massban", description: "Mass-ban a list of user IDs", @@ -39,10 +41,10 @@ export const MassBanSlashCmd = { actualMassBanCmd( pluginData, interaction, - options["user-ids"].split(/[\s,\r\n]+/), - interaction.member, + options["user-ids"].split(/\D+/), + interaction.member as GuildMember, options.reason || "", attachments, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts index 6f2c18ba7..ecdadbc77 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts @@ -1,7 +1,9 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMassMuteCmd } from "../../functions/actualCommands/actualMassMuteCmd"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -12,7 +14,7 @@ const opts = [ }), ]; -export const MassMuteSlashSlashCmd = { +export const MassMuteSlashSlashCmd = modActionsSlashCmd({ name: "massmute", configPermission: "can_massmute", description: "Mass-mute a list of user IDs", @@ -39,10 +41,10 @@ export const MassMuteSlashSlashCmd = { actualMassMuteCmd( pluginData, interaction, - options["user-ids"].split(/[\s,\r\n]+/), - interaction.member, + options["user-ids"].split(/\D+/), + interaction.member as GuildMember, options.reason || "", attachments, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts index da079dfd6..1a2cf5582 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts @@ -1,7 +1,9 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMassUnbanCmd } from "../../functions/actualCommands/actualMassUnbanCmd"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -12,7 +14,7 @@ const opts = [ }), ]; -export const MassUnbanSlashCmd = { +export const MassUnbanSlashCmd = modActionsSlashCmd({ name: "massunban", configPermission: "can_massunban", description: "Mass-unban a list of user IDs", @@ -40,9 +42,9 @@ export const MassUnbanSlashCmd = { pluginData, interaction, options["user-ids"].split(/[\s,\r\n]+/), - interaction.member, + interaction.member as GuildMember, options.reason || "", attachments, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts index f20585a92..5913d67e2 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts @@ -1,4 +1,4 @@ -import { ChannelType } from "discord.js"; +import { ChannelType, GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; @@ -8,6 +8,7 @@ import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -35,7 +36,7 @@ const opts = [ }), ]; -export const MuteSlashCmd = { +export const MuteSlashCmd = modActionsSlashCmd({ name: "mute", configPermission: "can_mute", description: "Mute the specified member", @@ -61,7 +62,7 @@ export const MuteSlashCmd = { const reply = await waitForButtonConfirm( interaction, { content: "User not found on the server, forcemute instead?" }, - { confirmText: "Yes", cancelText: "No", restrictToId: interaction.member.id }, + { confirmText: "Yes", cancelText: "No", restrictToId: interaction.user.id }, ); if (!reply) { @@ -74,12 +75,12 @@ export const MuteSlashCmd = { } // Make sure we're allowed to mute this member - if (memberToMute && !canActOn(pluginData, interaction.member, memberToMute)) { + if (memberToMute && !canActOn(pluginData, interaction.member as GuildMember, memberToMute)) { pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot mute: insufficient permissions"); return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; let ppId: string | undefined; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, @@ -94,7 +95,7 @@ export const MuteSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; ppId = interaction.user.id; } @@ -124,4 +125,4 @@ export const MuteSlashCmd = { contactMethods, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts index edcf60e9b..8962be117 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts @@ -2,6 +2,7 @@ import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualNoteCmd } from "../../functions/actualCommands/actualNoteCmd"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -12,7 +13,7 @@ const opts = [ }), ]; -export const NoteSlashCmd = { +export const NoteSlashCmd = modActionsSlashCmd({ name: "note", configPermission: "can_note", description: "Add a note to the specified user", @@ -34,4 +35,4 @@ export const NoteSlashCmd = { actualNoteCmd(pluginData, interaction, interaction.user, attachments, options.user, options.note || ""); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts index 1a8a21819..954a90ae4 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts @@ -1,8 +1,11 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; +import { resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualUnbanCmd } from "../../functions/actualCommands/actualUnbanCmd"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -14,7 +17,7 @@ const opts = [ }), ]; -export const UnbanSlashCmd = { +export const UnbanSlashCmd = modActionsSlashCmd({ name: "unban", configPermission: "can_unban", description: "Unban the specified member", @@ -34,7 +37,7 @@ export const UnbanSlashCmd = { return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, member: interaction.member, @@ -48,9 +51,9 @@ export const UnbanSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; } - actualUnbanCmd(pluginData, interaction, interaction.user.id, options.user, options.reason, attachments, mod); + actualUnbanCmd(pluginData, interaction, interaction.user.id, options.user, options.reason ?? "", attachments, mod); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts index 92d3c0fb0..2dbdc309a 100644 --- a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts @@ -1,7 +1,8 @@ import { slashOptions } from "knub"; import { actualUnhideCaseCmd } from "../../functions/actualCommands/actualUnhideCaseCmd"; +import { modActionsSlashCmd } from "../../types"; -export const UnhideCaseSlashCmd = { +export const UnhideCaseSlashCmd = modActionsSlashCmd({ name: "unhidecase", configPermission: "can_hidecase", description: "Un-hide the specified case", @@ -13,6 +14,6 @@ export const UnhideCaseSlashCmd = { async run({ interaction, options, pluginData }) { await interaction.deferReply({ ephemeral: true }); - actualUnhideCaseCmd(pluginData, interaction, options["case-number"].split(/[\s,]+/).map(Number)); + actualUnhideCaseCmd(pluginData, interaction, options["case-number"].split(/\D+/).map(Number)); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts index df57fa05b..b3ab730cd 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts @@ -1,3 +1,4 @@ +import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS, resolveMember } from "../../../../utils"; @@ -7,6 +8,7 @@ import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; import { isBanned } from "../../functions/isBanned"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -19,7 +21,7 @@ const opts = [ }), ]; -export const UnmuteSlashCmd = { +export const UnmuteSlashCmd = modActionsSlashCmd({ name: "unmute", configPermission: "can_mute", description: "Unmute the specified member", @@ -79,12 +81,12 @@ export const UnmuteSlashCmd = { } // Make sure we're allowed to unmute this member - if (memberToUnmute && !canActOn(pluginData, interaction.member, memberToUnmute)) { + if (memberToUnmute && !canActOn(pluginData, interaction.member as GuildMember, memberToUnmute)) { pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot unmute: insufficient permissions"); return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; let ppId: string | undefined; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, @@ -99,7 +101,7 @@ export const UnmuteSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; ppId = interaction.user.id; } @@ -111,4 +113,4 @@ export const UnmuteSlashCmd = { actualUnmuteCmd(pluginData, interaction, options.user, attachments, mod, ppId, convertedTime, options.reason); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts b/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts index a1a6cabf2..2316f82db 100644 --- a/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/update/UpdateSlashCmd.ts @@ -1,6 +1,7 @@ import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { updateCase } from "../../functions/updateCase"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_UPDATE } from "../constants"; const opts = [ @@ -12,7 +13,7 @@ const opts = [ }), ]; -export const UpdateSlashCmd = { +export const UpdateSlashCmd = modActionsSlashCmd({ name: "update", configPermission: "can_note", description: "Update the specified case (or your latest case) by adding more notes to it", @@ -27,9 +28,9 @@ export const UpdateSlashCmd = { pluginData, interaction, interaction.user, - options.caseNumber, - options.note, + options["case-number"] ? Number(options["case-number"]) : null, + options.reason ?? "", retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_UPDATE, options, "attachment"), ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts index 7295551b4..180d52c85 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts @@ -1,4 +1,4 @@ -import { ChannelType } from "discord.js"; +import { ChannelType, GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveMember } from "../../../../utils"; @@ -7,6 +7,7 @@ import { CommonPlugin } from "../../../Common/CommonPlugin"; import { actualWarnCmd } from "../../functions/actualCommands/actualWarnCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; +import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; const opts = [ @@ -33,7 +34,7 @@ const opts = [ }), ]; -export const WarnSlashCmd = { +export const WarnSlashCmd = modActionsSlashCmd({ name: "warn", configPermission: "can_warn", description: "Send a warning to the specified user", @@ -67,12 +68,12 @@ export const WarnSlashCmd = { } // Make sure we're allowed to warn this member - if (!canActOn(pluginData, interaction.member, memberToWarn)) { + if (!canActOn(pluginData, interaction.member as GuildMember, memberToWarn)) { await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot warn: insufficient permissions"); return; } - let mod = interaction.member; + let mod = interaction.member as GuildMember; const canActAsOther = await hasPermission(pluginData, "can_act_as_other", { channel: interaction.channel, member: interaction.member, @@ -86,7 +87,7 @@ export const WarnSlashCmd = { return; } - mod = options.mod; + mod = (await resolveMember(pluginData.client, pluginData.guild, options.mod.id))!; } let contactMethods: UserNotificationMethod[] | undefined; @@ -108,4 +109,4 @@ export const WarnSlashCmd = { contactMethods, ); }, -}; +}); diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts index a1767be9e..f8ad0158b 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts @@ -20,7 +20,7 @@ export async function actualKickCmd( attachments: Attachment[], mod: GuildMember, contactMethods?: UserNotificationMethod[], - clean?: boolean, + clean?: boolean | null, ) { if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { return; diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts index d93e10d06..2cff62ff7 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts @@ -29,7 +29,7 @@ export async function actualMuteCmd( mod: GuildMember, ppId?: string, time?: number, - reason?: string, + reason?: string | null, contactMethods?: UserNotificationMethod[], ) { if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts b/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts index 22f79eb64..11a668b4d 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts @@ -16,7 +16,7 @@ export async function actualUnmuteCmd( mod: GuildMember, ppId?: string, time?: number, - reason?: string, + reason?: string | null, ) { if (await handleAttachmentLinkDetectionAndGetRestriction(pluginData, context, reason)) { return; diff --git a/backend/src/plugins/ModActions/functions/readContactMethodsFromArgs.ts b/backend/src/plugins/ModActions/functions/readContactMethodsFromArgs.ts index a95033333..5ddc222b5 100644 --- a/backend/src/plugins/ModActions/functions/readContactMethodsFromArgs.ts +++ b/backend/src/plugins/ModActions/functions/readContactMethodsFromArgs.ts @@ -2,8 +2,8 @@ import { GuildTextBasedChannel } from "discord.js"; import { disableUserNotificationStrings, UserNotificationMethod } from "../../../utils"; export function readContactMethodsFromArgs(args: { - notify?: string; - "notify-channel"?: GuildTextBasedChannel; + notify?: string | null; + "notify-channel"?: GuildTextBasedChannel | null; }): null | UserNotificationMethod[] { if (args.notify) { if (args.notify === "dm") { diff --git a/backend/src/plugins/ModActions/functions/updateCase.ts b/backend/src/plugins/ModActions/functions/updateCase.ts index 08f11230a..e4011fb72 100644 --- a/backend/src/plugins/ModActions/functions/updateCase.ts +++ b/backend/src/plugins/ModActions/functions/updateCase.ts @@ -13,7 +13,7 @@ export async function updateCase( pluginData: GuildPluginData, context: Message | ChatInputCommandInteraction, author: User, - caseNumber?: number, + caseNumber?: number | null, note = "", attachments: Attachment[] = [], ) { diff --git a/backend/src/plugins/ModActions/types.ts b/backend/src/plugins/ModActions/types.ts index ec2d29ea3..ee128986c 100644 --- a/backend/src/plugins/ModActions/types.ts +++ b/backend/src/plugins/ModActions/types.ts @@ -1,6 +1,12 @@ import { ChatInputCommandInteraction, Message } from "discord.js"; import { EventEmitter } from "events"; -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, guildPluginSlashGroup } from "knub"; +import { + BasePluginType, + guildPluginEventListener, + guildPluginMessageCommand, + guildPluginSlashCommand, + guildPluginSlashGroup, +} from "knub"; import z from "zod"; import { Queue } from "../../Queue"; import { GuildCases } from "../../data/GuildCases"; @@ -152,4 +158,5 @@ export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" export const modActionsMsgCmd = guildPluginMessageCommand(); export const modActionsSlashGroup = guildPluginSlashGroup(); +export const modActionsSlashCmd = guildPluginSlashCommand(); export const modActionsEvt = guildPluginEventListener(); From 893a77d5620a700ca71707ea9ea979e7c5aa7bea Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Mon, 15 Apr 2024 15:51:45 +0200 Subject: [PATCH 28/29] Fixes, refactoring and PR feedback --- backend/src/api/start.ts | 8 +-- .../AutoReactions/AutoReactionsPlugin.ts | 5 ++ .../commands/DisableAutoReactionsCmd.ts | 4 +- .../commands/NewAutoReactionsCmd.ts | 20 +++---- backend/src/plugins/AutoReactions/types.ts | 4 +- backend/src/plugins/Automod/AutomodPlugin.ts | 5 ++ .../Automod/commands/AntiraidClearCmd.ts | 3 +- .../Automod/commands/SetAntiraidCmd.ts | 5 +- backend/src/plugins/Automod/types.ts | 5 +- .../plugins/BotControl/BotControlPlugin.ts | 3 +- .../commands/AddDashboardUserCmd.ts | 9 +-- .../commands/AddServerFromInviteCmd.ts | 12 ++-- .../BotControl/commands/AllowServerCmd.ts | 8 +-- .../BotControl/commands/ChannelToServerCmd.ts | 2 +- .../BotControl/commands/DisallowServerCmd.ts | 4 +- .../BotControl/commands/EligibleCmd.ts | 6 +- .../BotControl/commands/LeaveServerCmd.ts | 6 +- .../commands/ListDashboardPermsCmd.ts | 19 +++---- .../commands/ListDashboardUsersCmd.ts | 13 ++--- .../commands/RateLimitPerformanceCmd.ts | 2 +- .../commands/ReloadGlobalPluginsCmd.ts | 2 +- .../BotControl/commands/ReloadServerCmd.ts | 6 +- .../commands/RemoveDashboardUserCmd.ts | 11 ++-- backend/src/plugins/BotControl/types.ts | 2 +- .../ChannelArchiver/ChannelArchiverPlugin.ts | 5 ++ .../commands/ArchiveChannelCmd.ts | 2 +- backend/src/plugins/ChannelArchiver/types.ts | 9 ++- backend/src/plugins/Common/CommonPlugin.ts | 55 +++++++++++-------- backend/src/plugins/Common/info.ts | 8 +++ backend/src/plugins/Common/types.ts | 1 + .../src/plugins/ContextMenus/actions/ban.ts | 27 +++------ .../src/plugins/ContextMenus/actions/clean.ts | 11 +--- .../src/plugins/ContextMenus/actions/mute.ts | 47 ++++++---------- .../src/plugins/ContextMenus/actions/note.ts | 47 +++++++--------- .../plugins/ContextMenus/actions/update.ts | 6 +- .../src/plugins/ContextMenus/actions/warn.ts | 43 ++++++--------- .../commands/ModMenuUserCtxCmd.ts | 20 ++----- .../src/plugins/Counters/CountersPlugin.ts | 5 ++ .../Counters/commands/AddCounterCmd.ts | 20 +++---- .../Counters/commands/CountersListCmd.ts | 2 +- .../commands/ResetAllCounterValuesCmd.ts | 12 ++-- .../Counters/commands/ResetCounterCmd.ts | 16 +++--- .../Counters/commands/SetCounterCmd.ts | 22 ++++---- .../Counters/commands/ViewCounterCmd.ts | 16 +++--- backend/src/plugins/Counters/types.ts | 4 +- .../CustomEvents/CustomEventsPlugin.ts | 5 ++ .../CustomEvents/functions/runEvent.ts | 2 +- backend/src/plugins/CustomEvents/types.ts | 4 +- .../plugins/LocateUser/LocateUserPlugin.ts | 5 ++ .../plugins/LocateUser/commands/FollowCmd.ts | 12 +--- .../LocateUser/commands/ListFollowCmd.ts | 6 +- backend/src/plugins/LocateUser/types.ts | 4 +- .../plugins/LocateUser/utils/moveMember.ts | 8 +-- .../src/plugins/LocateUser/utils/sendWhere.ts | 2 +- .../MessageSaver/MessageSaverPlugin.ts | 5 ++ .../MessageSaver/commands/SaveMessagesToDB.ts | 6 +- .../MessageSaver/commands/SavePinsToDB.ts | 6 +- backend/src/plugins/MessageSaver/types.ts | 4 +- .../plugins/ModActions/ModActionsPlugin.ts | 6 +- .../commands/addcase/AddCaseMsgCmd.ts | 9 ++- .../commands/addcase/AddCaseSlashCmd.ts | 10 ++-- .../addcase}/actualAddCaseCmd.ts | 21 +++---- .../ModActions/commands/ban/BanMsgCmd.ts | 9 ++- .../ModActions/commands/ban/BanSlashCmd.ts | 24 ++++---- .../ban}/actualBanCmd.ts | 36 ++++++------ .../ModActions/commands/case/CaseMsgCmd.ts | 2 +- .../ModActions/commands/case/CaseSlashCmd.ts | 2 +- .../case}/actualCaseCmd.ts | 5 +- .../commands/cases/CasesModMsgCmd.ts | 2 +- .../commands/cases/CasesSlashCmd.ts | 2 +- .../commands/cases/CasesUserMsgCmd.ts | 5 +- .../cases}/actualCasesCmd.ts | 11 ++-- .../commands/deletecase/DeleteCaseMsgCmd.ts | 2 +- .../commands/deletecase/DeleteCaseSlashCmd.ts | 2 +- .../deletecase}/actualDeleteCaseCmd.ts | 17 +++--- .../commands/forceban/ForceBanMsgCmd.ts | 11 ++-- .../commands/forceban/ForceBanSlashCmd.ts | 22 +++++--- .../forceban}/actualForceBanCmd.ts | 13 ++--- .../commands/forcemute/ForceMuteMsgCmd.ts | 11 ++-- .../commands/forcemute/ForceMuteSlashCmd.ts | 24 ++++---- .../commands/forceunmute/ForceUnmuteMsgCmd.ts | 11 ++-- .../forceunmute/ForceUnmuteSlashCmd.ts | 22 +++++--- .../commands/hidecase/HideCaseMsgCmd.ts | 2 +- .../commands/hidecase/HideCaseSlashCmd.ts | 2 +- .../hidecase}/actualHideCaseCmd.ts | 7 +-- .../ModActions/commands/kick/KickMsgCmd.ts | 9 ++- .../ModActions/commands/kick/KickSlashCmd.ts | 22 +++++--- .../kick}/actualKickCmd.ts | 29 +++++----- .../commands/massban/MassBanMsgCmd.ts | 5 +- .../commands/massban/MassBanSlashCmd.ts | 13 +++-- .../massban}/actualMassBanCmd.ts | 38 ++++++------- .../commands/massmute/MassMuteMsgCmd.ts | 5 +- .../commands/massmute/MassMuteSlashCmd.ts | 13 +++-- .../massmute}/actualMassMuteCmd.ts | 22 ++++---- .../commands/massunban/MassUnbanMsgCmd.ts | 5 +- .../commands/massunban/MassUnbanSlashCmd.ts | 13 +++-- .../massunban}/actualMassUnbanCmd.ts | 26 ++++----- .../ModActions/commands/mute/MuteMsgCmd.ts | 20 +++---- .../ModActions/commands/mute/MuteSlashCmd.ts | 30 +++++----- .../mute}/actualMuteCmd.ts | 18 +++--- .../ModActions/commands/note/NoteMsgCmd.ts | 7 +-- .../ModActions/commands/note/NoteSlashCmd.ts | 13 +++-- .../note}/actualNoteCmd.ts | 9 +-- .../ModActions/commands/unban/UnbanMsgCmd.ts | 7 +-- .../commands/unban/UnbanSlashCmd.ts | 20 ++++--- .../unban}/actualUnbanCmd.ts | 16 +++--- .../commands/unhidecase/UnhideCaseMsgCmd.ts | 2 +- .../commands/unhidecase/UnhideCaseSlashCmd.ts | 2 +- .../unhidecase}/actualUnhideCaseCmd.ts | 10 ++-- .../commands/unmute/UnmuteMsgCmd.ts | 20 +++---- .../commands/unmute/UnmuteSlashCmd.ts | 40 ++++++++------ .../unmute}/actualUnmuteCmd.ts | 11 ++-- .../ModActions/commands/warn/WarnMsgCmd.ts | 13 ++--- .../ModActions/commands/warn/WarnSlashCmd.ts | 28 ++++++---- .../warn}/actualWarnCmd.ts | 15 ++--- .../functions/attachmentLinkReaction.ts | 19 +++---- .../functions/formatReasonForAttachments.ts | 14 +---- .../ModActions/functions/updateCase.ts | 7 +-- backend/src/plugins/ModActions/types.ts | 8 ++- backend/src/plugins/Mutes/MutesPlugin.ts | 5 ++ .../Mutes/commands/ClearBannedMutesCmd.ts | 2 +- .../plugins/Mutes/commands/ClearMutesCmd.ts | 8 +-- .../commands/ClearMutesWithoutRoleCmd.ts | 4 +- backend/src/plugins/Mutes/types.ts | 5 +- .../plugins/NameHistory/NameHistoryPlugin.ts | 5 ++ .../plugins/NameHistory/commands/NamesCmd.ts | 2 +- backend/src/plugins/NameHistory/types.ts | 4 +- .../PingableRoles/PingableRolesPlugin.ts | 5 ++ .../commands/PingableRoleDisableCmd.ts | 8 +-- .../commands/PingableRoleEnableCmd.ts | 8 +-- backend/src/plugins/PingableRoles/types.ts | 4 +- backend/src/plugins/Post/PostPlugin.ts | 5 ++ backend/src/plugins/Post/commands/EditCmd.ts | 6 +- .../src/plugins/Post/commands/EditEmbedCmd.ts | 10 ++-- .../src/plugins/Post/commands/PostEmbedCmd.ts | 8 +-- .../Post/commands/ScheduledPostsDeleteCmd.ts | 4 +- .../Post/commands/ScheduledPostsShowCmd.ts | 2 +- backend/src/plugins/Post/types.ts | 4 +- .../src/plugins/Post/util/actualPostCmd.ts | 38 +++++-------- .../ReactionRoles/ReactionRolesPlugin.ts | 5 ++ .../commands/ClearReactionRolesCmd.ts | 6 +- .../commands/InitReactionRolesCmd.ts | 24 +++----- .../commands/RefreshReactionRolesCmd.ts | 4 +- backend/src/plugins/ReactionRoles/types.ts | 5 +- .../src/plugins/Reminders/RemindersPlugin.ts | 5 ++ .../plugins/Reminders/commands/RemindCmd.ts | 8 +-- .../Reminders/commands/RemindersCmd.ts | 2 +- .../Reminders/commands/RemindersDeleteCmd.ts | 4 +- backend/src/plugins/Reminders/types.ts | 4 +- .../plugins/RoleButtons/RoleButtonsPlugin.ts | 5 ++ .../RoleButtons/commands/resetButtons.ts | 6 +- backend/src/plugins/RoleButtons/types.ts | 4 +- backend/src/plugins/Roles/RolesPlugin.ts | 5 ++ .../src/plugins/Roles/commands/AddRoleCmd.ts | 16 ++---- .../plugins/Roles/commands/MassAddRoleCmd.ts | 10 ++-- .../Roles/commands/MassRemoveRoleCmd.ts | 10 ++-- .../plugins/Roles/commands/RemoveRoleCmd.ts | 16 ++---- backend/src/plugins/Roles/types.ts | 4 +- .../SelfGrantableRolesPlugin.ts | 5 ++ .../SelfGrantableRoles/commands/RoleAddCmd.ts | 18 +++--- .../commands/RoleRemoveCmd.ts | 38 +++++-------- .../src/plugins/SelfGrantableRoles/types.ts | 4 +- .../src/plugins/Slowmode/SlowmodePlugin.ts | 5 ++ .../Slowmode/commands/SlowmodeClearCmd.ts | 14 ++--- .../Slowmode/commands/SlowmodeSetCmd.ts | 30 +++------- backend/src/plugins/Slowmode/types.ts | 4 +- .../Slowmode/util/actualDisableSlowmodeCmd.ts | 18 +++--- .../src/plugins/Starboard/StarboardPlugin.ts | 5 ++ .../Starboard/commands/MigratePinsCmd.ts | 8 +-- backend/src/plugins/Starboard/types.ts | 4 +- backend/src/plugins/Tags/TagsPlugin.ts | 5 ++ .../src/plugins/Tags/commands/TagCreateCmd.ts | 4 +- .../src/plugins/Tags/commands/TagDeleteCmd.ts | 4 +- .../src/plugins/Tags/commands/TagEvalCmd.ts | 4 +- .../src/plugins/Tags/commands/TagSourceCmd.ts | 6 +- backend/src/plugins/Tags/types.ts | 4 +- .../plugins/TimeAndDate/TimeAndDatePlugin.ts | 5 ++ .../TimeAndDate/commands/ResetTimezoneCmd.ts | 4 +- .../TimeAndDate/commands/SetTimezoneCmd.ts | 4 +- backend/src/plugins/TimeAndDate/types.ts | 4 +- backend/src/plugins/Utility/UtilityPlugin.ts | 6 +- .../src/plugins/Utility/commands/AvatarCmd.ts | 2 +- .../Utility/commands/ChannelInfoCmd.ts | 2 +- .../src/plugins/Utility/commands/CleanCmd.ts | 40 +++++--------- .../plugins/Utility/commands/ContextCmd.ts | 6 +- .../plugins/Utility/commands/EmojiInfoCmd.ts | 4 +- .../src/plugins/Utility/commands/InfoCmd.ts | 10 ++-- .../plugins/Utility/commands/InviteInfoCmd.ts | 2 +- .../src/plugins/Utility/commands/JumboCmd.ts | 4 +- .../Utility/commands/MessageInfoCmd.ts | 4 +- .../plugins/Utility/commands/NicknameCmd.ts | 10 ++-- .../Utility/commands/NicknameResetCmd.ts | 2 +- .../src/plugins/Utility/commands/RolesCmd.ts | 2 +- .../plugins/Utility/commands/ServerInfoCmd.ts | 2 +- .../src/plugins/Utility/commands/SourceCmd.ts | 4 +- .../plugins/Utility/commands/UserInfoCmd.ts | 2 +- .../Utility/commands/VcdisconnectCmd.ts | 13 +++-- .../src/plugins/Utility/commands/VcmoveCmd.ts | 54 ++++++++---------- backend/src/plugins/Utility/search.ts | 13 ++--- backend/src/plugins/Utility/types.ts | 5 +- backend/src/utils/loadYamlSafely.ts | 2 +- backend/src/utils/waitForInteraction.ts | 12 ++-- 202 files changed, 1038 insertions(+), 1070 deletions(-) create mode 100644 backend/src/plugins/Common/info.ts rename backend/src/plugins/ModActions/{functions/actualCommands => commands/addcase}/actualAddCaseCmd.ts (76%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/ban}/actualBanCmd.ts (85%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/case}/actualCaseCmd.ts (74%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/cases}/actualCasesCmd.ts (97%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/deletecase}/actualDeleteCaseCmd.ts (85%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/forceban}/actualForceBanCmd.ts (83%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/hidecase}/actualHideCaseCmd.ts (81%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/kick}/actualKickCmd.ts (72%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/massban}/actualMassBanCmd.ts (85%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/massmute}/actualMassMuteCmd.ts (80%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/massunban}/actualMassUnbanCmd.ts (84%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/mute}/actualMuteCmd.ts (85%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/note}/actualNoteCmd.ts (85%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/unban}/actualUnbanCmd.ts (81%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/unhidecase}/actualUnhideCaseCmd.ts (73%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/unmute}/actualUnmuteCmd.ts (81%) rename backend/src/plugins/ModActions/{functions/actualCommands => commands/warn}/actualWarnCmd.ts (83%) diff --git a/backend/src/api/start.ts b/backend/src/api/start.ts index 088c8ba97..e009e393a 100644 --- a/backend/src/api/start.ts +++ b/backend/src/api/start.ts @@ -28,10 +28,10 @@ app.use(multer().none()); const rootRouter = express.Router(); -initAuth(app); -initGuildsAPI(app); -initArchives(app); -initDocs(app); +initAuth(rootRouter); +initGuildsAPI(rootRouter); +initArchives(rootRouter); +initDocs(rootRouter); // Default route rootRouter.get("/", (req, res) => { diff --git a/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts b/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts index 6c24b2967..df5ebd3a0 100644 --- a/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts +++ b/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts @@ -6,6 +6,7 @@ import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd"; import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd"; import { AddReactionsEvt } from "./events/AddReactionsEvt"; import { AutoReactionsPluginType, zAutoReactionsConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -50,4 +51,8 @@ export const AutoReactionsPlugin = guildPlugin()({ state.autoReactions = GuildAutoReactions.getGuildInstance(guild.id); state.cache = new Map(); }, + + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + } }); diff --git a/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts b/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts index ec3417d4f..f65391741 100644 --- a/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts +++ b/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts @@ -14,12 +14,12 @@ export const DisableAutoReactionsCmd = autoReactionsCmd({ async run({ message: msg, args, pluginData }) { const autoReaction = await pluginData.state.autoReactions.getForChannel(args.channelId); if (!autoReaction) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Auto-reactions aren't enabled in <#${args.channelId}>`); + void pluginData.state.common.sendErrorMessage(msg, `Auto-reactions aren't enabled in <#${args.channelId}>`); return; } await pluginData.state.autoReactions.removeFromChannel(args.channelId); pluginData.state.cache.delete(args.channelId); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Auto-reactions disabled in <#${args.channelId}>`); + void pluginData.state.common.sendSuccessMessage(msg, `Auto-reactions disabled in <#${args.channelId}>`); }, }); diff --git a/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts b/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts index 250df4f0a..7f3355fb3 100644 --- a/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts +++ b/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts @@ -25,20 +25,16 @@ export const NewAutoReactionsCmd = autoReactionsCmd({ const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingChannelPermissions(me, args.channel, requiredPermissions); if (missingPermissions) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( - msg, - `Cannot set auto-reactions for that channel. ${missingPermissionError(missingPermissions)}`, - ); + pluginData.state.common.sendErrorMessage( + msg, + `Cannot set auto-reactions for that channel. ${missingPermissionError(missingPermissions)}`, + ); return; } for (const reaction of args.reactions) { if (!isEmoji(reaction)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "One or more of the specified reactions were invalid!"); + void pluginData.state.common.sendErrorMessage(msg, "One or more of the specified reactions were invalid!"); return; } @@ -48,9 +44,7 @@ export const NewAutoReactionsCmd = autoReactionsCmd({ if (customEmojiMatch) { // Custom emoji if (!canUseEmoji(pluginData.client, customEmojiMatch[2])) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "I can only use regular emojis and custom emojis from this server"); + pluginData.state.common.sendErrorMessage(msg, "I can only use regular emojis and custom emojis from this server"); return; } @@ -65,6 +59,6 @@ export const NewAutoReactionsCmd = autoReactionsCmd({ await pluginData.state.autoReactions.set(args.channel.id, finalReactions); pluginData.state.cache.delete(args.channel.id); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Auto-reactions set for <#${args.channel.id}>`); + void pluginData.state.common.sendSuccessMessage(msg, `Auto-reactions set for <#${args.channel.id}>`); }, }); diff --git a/backend/src/plugins/AutoReactions/types.ts b/backend/src/plugins/AutoReactions/types.ts index 996fba8d2..d060d59a7 100644 --- a/backend/src/plugins/AutoReactions/types.ts +++ b/backend/src/plugins/AutoReactions/types.ts @@ -1,9 +1,10 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildAutoReactions } from "../../data/GuildAutoReactions"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { AutoReaction } from "../../data/entities/AutoReaction"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zAutoReactionsConfig = z.strictObject({ can_manage: z.boolean(), @@ -16,6 +17,7 @@ export interface AutoReactionsPluginType extends BasePluginType { savedMessages: GuildSavedMessages; autoReactions: GuildAutoReactions; cache: Map; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts index 339565902..5da233a1b 100644 --- a/backend/src/plugins/Automod/AutomodPlugin.ts +++ b/backend/src/plugins/Automod/AutomodPlugin.ts @@ -32,6 +32,7 @@ import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChang import { clearOldRecentActions } from "./functions/clearOldRecentActions"; import { clearOldRecentSpam } from "./functions/clearOldRecentSpam"; import { AutomodPluginType, zAutomodConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions = { config: { @@ -117,6 +118,10 @@ export const AutomodPlugin = guildPlugin()({ state.cachedAntiraidLevel = await state.antiraidLevels.get(); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + async afterLoad(pluginData) { const { state } = pluginData; diff --git a/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts b/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts index 983a1f994..85f9bbcb0 100644 --- a/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts +++ b/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts @@ -1,5 +1,4 @@ import { guildPluginMessageCommand } from "knub"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { setAntiraidLevel } from "../functions/setAntiraidLevel"; import { AutomodPluginType } from "../types"; @@ -9,6 +8,6 @@ export const AntiraidClearCmd = guildPluginMessageCommand()({ async run({ pluginData, message }) { await setAntiraidLevel(pluginData, null, message.author); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(message, "Anti-raid turned **off**"); + void pluginData.state.common.sendSuccessMessage(message, "Anti-raid turned **off**"); }, }); diff --git a/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts b/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts index 91cbacd65..46c2106fd 100644 --- a/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts +++ b/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts @@ -1,6 +1,5 @@ import { guildPluginMessageCommand } from "knub"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { setAntiraidLevel } from "../functions/setAntiraidLevel"; import { AutomodPluginType } from "../types"; @@ -15,11 +14,11 @@ export const SetAntiraidCmd = guildPluginMessageCommand()({ async run({ pluginData, message, args }) { const config = pluginData.config.get(); if (!config.antiraid_levels.includes(args.level)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown anti-raid level"); + pluginData.state.common.sendErrorMessage(message, "Unknown anti-raid level"); return; } await setAntiraidLevel(pluginData, args.level, message.author); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(message, `Anti-raid level set to **${args.level}**`); + pluginData.state.common.sendSuccessMessage(message, `Anti-raid level set to **${args.level}**`); }, }); diff --git a/backend/src/plugins/Automod/types.ts b/backend/src/plugins/Automod/types.ts index 6cf88ad65..75cd8daac 100644 --- a/backend/src/plugins/Automod/types.ts +++ b/backend/src/plugins/Automod/types.ts @@ -1,5 +1,5 @@ import { GuildMember, GuildTextBasedChannel, PartialGuildMember, ThreadChannel, User } from "discord.js"; -import { BasePluginType, CooldownManager } from "knub"; +import { BasePluginType, CooldownManager, pluginUtils } from "knub"; import z from "zod"; import { Queue } from "../../Queue"; import { RegExpRunner } from "../../RegExpRunner"; @@ -17,6 +17,7 @@ import { RecentActionType } from "./constants"; import { availableTriggers } from "./triggers/availableTriggers"; import Timeout = NodeJS.Timeout; +import { CommonPlugin } from "../Common/CommonPlugin"; export type ZTriggersMapHelper = { [TriggerName in keyof typeof availableTriggers]: (typeof availableTriggers)[TriggerName]["configSchema"]; @@ -139,6 +140,8 @@ export interface AutomodPluginType extends BasePluginType { modActionsListeners: Map; mutesListeners: Map; + + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts index b3812e029..c1a948885 100644 --- a/backend/src/plugins/BotControl/BotControlPlugin.ts +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -4,7 +4,6 @@ import { AllowedGuilds } from "../../data/AllowedGuilds"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; import { Configs } from "../../data/Configs"; import { GuildArchives } from "../../data/GuildArchives"; -import { CommonPlugin } from "../Common/CommonPlugin"; import { getActiveReload, resetActiveReload } from "./activeReload"; import { AddDashboardUserCmd } from "./commands/AddDashboardUserCmd"; import { AddServerFromInviteCmd } from "./commands/AddServerFromInviteCmd"; @@ -77,7 +76,7 @@ export const BotControlPlugin = globalPlugin()({ if (guild) { const channel = guild.channels.cache.get(channelId as Snowflake); if (channel instanceof TextChannel) { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(channel, "Global plugins reloaded!"); + void channel.send("Global plugins reloaded!"); } } } diff --git a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts index 5a50d1b0f..8e97cf56a 100644 --- a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts @@ -20,7 +20,7 @@ export const AddDashboardUserCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is not using Zeppelin"); + void msg.channel.send("Server is not using Zeppelin"); return; } @@ -38,11 +38,6 @@ export const AddDashboardUserCmd = botControlCmd({ const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`)`); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - msg, - `The following users were given dashboard access for **${guild.name}**:\n\n${userNameList}`, - ); + msg.channel.send(`The following users were given dashboard access for **${guild.name}**:\n\n${userNameList}`); }, }); diff --git a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts index aaeb7147b..e5245bb3c 100644 --- a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts @@ -18,21 +18,19 @@ export const AddServerFromInviteCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const invite = await resolveInvite(pluginData.client, args.inviteCode, true); if (!invite || !isGuildInvite(invite)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Could not resolve invite"); // :D + void msg.channel.send("Could not resolve invite"); // :D return; } const existing = await pluginData.state.allowedGuilds.find(invite.guild.id); if (existing) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is already allowed!"); + void msg.channel.send("Server is already allowed!"); return; } const { result, explanation } = await isEligible(pluginData, args.user, invite); if (!result) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Could not add server because it's not eligible: ${explanation}`); + msg.channel.send(`Could not add server because it's not eligible: ${explanation}`); return; } @@ -53,8 +51,6 @@ export const AddServerFromInviteCmd = botControlCmd({ ); } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, "Server was eligible and is now allowed to use Zeppelin!"); + msg.channel.send("Server was eligible and is now allowed to use Zeppelin!"); }, }); diff --git a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts index ac8438675..9d0a6cea4 100644 --- a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts @@ -21,17 +21,17 @@ export const AllowServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const existing = await pluginData.state.allowedGuilds.find(args.guildId); if (existing) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is already allowed!"); + void msg.channel.send("Server is already allowed!"); return; } if (!isSnowflake(args.guildId)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid server ID!"); + void msg.channel.send("Invalid server ID!"); return; } if (args.userId && !isSnowflake(args.userId)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid user ID!"); + void msg.channel.send("Invalid user ID!"); return; } @@ -52,6 +52,6 @@ export const AllowServerCmd = botControlCmd({ ); } - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Server is now allowed to use Zeppelin!"); + void msg.channel.send("Server is now allowed to use Zeppelin!"); }, }); diff --git a/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts b/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts index 939077e29..f19a41c97 100644 --- a/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts @@ -17,7 +17,7 @@ export const ChannelToServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const channel = pluginData.client.channels.cache.get(args.channelId); if (!channel) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Channel not found in cache!"); + void msg.channel.send("Channel not found in cache!"); return; } diff --git a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts index 69ba0b4ca..8a31ddf53 100644 --- a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts @@ -19,7 +19,7 @@ export const DisallowServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const existing = await pluginData.state.allowedGuilds.find(args.guildId); if (!existing) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "That server is not allowed in the first place!"); + void msg.channel.send("That server is not allowed in the first place!"); return; } @@ -28,6 +28,6 @@ export const DisallowServerCmd = botControlCmd({ .get(args.guildId as Snowflake) ?.leave() .catch(noop); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Server removed!"); + void msg.channel.send("Server removed!"); }, }); diff --git a/backend/src/plugins/BotControl/commands/EligibleCmd.ts b/backend/src/plugins/BotControl/commands/EligibleCmd.ts index bbcd5c5d0..60adcea23 100644 --- a/backend/src/plugins/BotControl/commands/EligibleCmd.ts +++ b/backend/src/plugins/BotControl/commands/EligibleCmd.ts @@ -16,17 +16,17 @@ export const EligibleCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const invite = await resolveInvite(pluginData.client, args.inviteCode, true); if (!invite || !isGuildInvite(invite)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Could not resolve invite"); + void msg.channel.send("Could not resolve invite"); return; } const { result, explanation } = await isEligible(pluginData, args.user, invite); if (result) { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Server is eligible: ${explanation}`); + void msg.channel.send(`Server is eligible: ${explanation}`); return; } - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Server is **NOT** eligible: ${explanation}`); + void msg.channel.send(`Server is **NOT** eligible: ${explanation}`); }, }); diff --git a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts index 0066a9162..c22a885f9 100644 --- a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts @@ -17,7 +17,7 @@ export const LeaveServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { if (!pluginData.client.guilds.cache.has(args.guildId as Snowflake)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "I am not in that guild"); + void msg.channel.send("I am not in that guild"); return; } @@ -27,10 +27,10 @@ export const LeaveServerCmd = botControlCmd({ try { await pluginData.client.guilds.cache.get(args.guildId as Snowflake)?.leave(); } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Failed to leave guild: ${e.message}`); + void msg.channel.send(`Failed to leave guild: ${e.message}`); return; } - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Left guild **${guildName}**`); + void msg.channel.send(`Left guild **${guildName}**`); }, }); diff --git a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts index 0de1ff261..83f0cc375 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts @@ -16,7 +16,7 @@ export const ListDashboardPermsCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { if (!args.user && !args.guildId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Must specify at least guildId, user, or both."); + void msg.channel.send("Must specify at least guildId, user, or both."); return; } @@ -24,7 +24,7 @@ export const ListDashboardPermsCmd = botControlCmd({ if (args.guildId) { guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is not using Zeppelin"); + void msg.channel.send("Server is not using Zeppelin"); return; } } @@ -33,7 +33,7 @@ export const ListDashboardPermsCmd = botControlCmd({ if (args.user) { existingUserAssignment = await pluginData.state.apiPermissionAssignments.getByUserId(args.user.id); if (existingUserAssignment.length === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "The user has no assigned permissions."); + void msg.channel.send("The user has no assigned permissions."); return; } } @@ -54,9 +54,7 @@ export const ListDashboardPermsCmd = botControlCmd({ } if (finalMessage === "") { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `The user ${userInfo} has no assigned permissions on the specified server.`); + msg.channel.send(`The user ${userInfo} has no assigned permissions on the specified server.`); return; } // Else display all users that have permissions on the specified guild @@ -65,9 +63,7 @@ export const ListDashboardPermsCmd = botControlCmd({ const existingGuildAssignment = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id); if (existingGuildAssignment.length === 0) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `The server ${guildInfo} has no assigned permissions.`); + msg.channel.send(`The server ${guildInfo} has no assigned permissions.`); return; } @@ -80,6 +76,9 @@ export const ListDashboardPermsCmd = botControlCmd({ } } - await pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, finalMessage.trim(), {}); + await msg.channel.send({ + content: finalMessage.trim(), + allowedMentions: {}, + }); }, }); diff --git a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts index b835dfeda..2892e6e65 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts @@ -14,7 +14,7 @@ export const ListDashboardUsersCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is not using Zeppelin"); + void msg.channel.send("Server is not using Zeppelin"); return; } @@ -30,12 +30,9 @@ export const ListDashboardUsersCmd = botControlCmd({ `<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`): ${permission.permissions.join(", ")}`, ); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - msg, - `The following users have dashboard access for **${guild.name}**:\n\n${userNameList.join("\n")}`, - {}, - ); + msg.channel.send({ + content: `The following users have dashboard access for **${guild.name}**:\n\n${userNameList.join("\n")}`, + allowedMentions: {}, + }); }, }); diff --git a/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts b/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts index 9ac5499dc..9ba9d23c5 100644 --- a/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts +++ b/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts @@ -14,7 +14,7 @@ export const RateLimitPerformanceCmd = botControlCmd({ async run({ pluginData, message: msg }) { const logItems = getRateLimitStats(); if (logItems.length === 0) { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `No rate limits hit`); + void msg.channel.send(`No rate limits hit`); return; } diff --git a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts index ea51dc002..957fca95d 100644 --- a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts @@ -15,7 +15,7 @@ export const ReloadGlobalPluginsCmd = botControlCmd({ const guildId = "guild" in message.channel ? message.channel.guild.id : null; if (!guildId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "This command can only be used in a server"); + void message.channel.send("This command can only be used in a server"); return; } diff --git a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts index 8481682e7..a171af5f1 100644 --- a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts @@ -17,18 +17,18 @@ export const ReloadServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { if (!pluginData.client.guilds.cache.has(args.guildId as Snowflake)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "I am not in that guild"); + void msg.channel.send("I am not in that guild"); return; } try { await pluginData.getKnubInstance().reloadGuild(args.guildId); } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Failed to reload guild: ${e.message}`); + void msg.channel.send(`Failed to reload guild: ${e.message}`); return; } const guild = await pluginData.client.guilds.fetch(args.guildId as Snowflake); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Reloaded guild **${guild?.name || "???"}**`); + void msg.channel.send(`Reloaded guild **${guild?.name || "???"}**`); }, }); diff --git a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts index 90927a2a8..502570a7b 100644 --- a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts @@ -19,7 +19,7 @@ export const RemoveDashboardUserCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Server is not using Zeppelin"); + void msg.channel.send("Server is not using Zeppelin"); return; } @@ -37,11 +37,8 @@ export const RemoveDashboardUserCmd = botControlCmd({ const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`)`); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - msg, - `The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`, - ); + msg.channel.send( + `The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`, + ); }, }); diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts index 1c1ccea64..592189c16 100644 --- a/backend/src/plugins/BotControl/types.ts +++ b/backend/src/plugins/BotControl/types.ts @@ -1,4 +1,4 @@ -import { BasePluginType, globalPluginEventListener, globalPluginMessageCommand } from "knub"; +import { BasePluginType, globalPluginEventListener, globalPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { AllowedGuilds } from "../../data/AllowedGuilds"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; diff --git a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts index c8373aeb7..b9c310d74 100644 --- a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts +++ b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts @@ -3,6 +3,7 @@ import z from "zod"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd"; import { ChannelArchiverPluginType } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const ChannelArchiverPlugin = guildPlugin()({ name: "channel_archiver", @@ -14,4 +15,8 @@ export const ChannelArchiverPlugin = guildPlugin()({ messageCommands: [ ArchiveChannelCmd, ], + + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + } }); diff --git a/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts b/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts index 327c69560..34bdeef74 100644 --- a/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts +++ b/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts @@ -38,7 +38,7 @@ export const ArchiveChannelCmd = channelArchiverCmd({ "No `-attachment-channel` specified. Continue? Attachments will not be available in the log if their message is deleted.", }); if (!confirmed) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Canceled"); + void pluginData.state.common.sendErrorMessage(msg, "Canceled"); return; } } diff --git a/backend/src/plugins/ChannelArchiver/types.ts b/backend/src/plugins/ChannelArchiver/types.ts index 024edf3d2..a560af398 100644 --- a/backend/src/plugins/ChannelArchiver/types.ts +++ b/backend/src/plugins/ChannelArchiver/types.ts @@ -1,5 +1,10 @@ -import { BasePluginType, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginMessageCommand, pluginUtils } from "knub"; +import { CommonPlugin } from "../Common/CommonPlugin"; -export interface ChannelArchiverPluginType extends BasePluginType {} +export interface ChannelArchiverPluginType extends BasePluginType { + state: { + common: pluginUtils.PluginPublicInterface; + } +} export const channelArchiverCmd = guildPluginMessageCommand(); diff --git a/backend/src/plugins/Common/CommonPlugin.ts b/backend/src/plugins/Common/CommonPlugin.ts index 92c58b630..144c355cd 100644 --- a/backend/src/plugins/Common/CommonPlugin.ts +++ b/backend/src/plugins/Common/CommonPlugin.ts @@ -1,4 +1,5 @@ import { + Attachment, ChatInputCommandInteraction, Message, MessageCreateOptions, @@ -7,11 +8,10 @@ import { TextBasedChannel, User, } from "discord.js"; -import { PluginOptions } from "knub"; +import { PluginOptions, guildPlugin } from "knub"; import { logger } from "../../logger"; import { isContextInteraction, sendContextResponse } from "../../pluginUtils"; import { errorMessage, successMessage } from "../../utils"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { getErrorEmoji, getSuccessEmoji } from "./functions/getEmoji"; import { CommonPluginType, zCommonConfig } from "./types"; @@ -19,30 +19,21 @@ const defaultOptions: PluginOptions = { config: { success_emoji: "✅", error_emoji: "❌", + attachment_storing_channel: null, }, }; -export const CommonPlugin = zeppelinGuildPlugin()({ +export const CommonPlugin = guildPlugin()({ name: "common", - showInDocs: false, - info: { - prettyName: "Common", - }, - dependencies: () => [], configParser: (input) => zCommonConfig.parse(input), defaultOptions, - public: { - getSuccessEmoji(pluginData) { - return () => getSuccessEmoji(pluginData); - }, - - getErrorEmoji(pluginData) { - return () => getErrorEmoji(pluginData); - }, + public(pluginData) { + return { + getSuccessEmoji, + getErrorEmoji, - sendSuccessMessage(pluginData) { - return async ( + sendSuccessMessage: async ( context: TextBasedChannel | Message | User | ChatInputCommandInteraction, body: string, allowedMentions?: MessageMentionOptions, @@ -89,11 +80,9 @@ export const CommonPlugin = zeppelinGuildPlugin()({ return undefined; }) as Promise; - }; - }, + }, - sendErrorMessage(pluginData) { - return async ( + sendErrorMessage: async ( context: TextBasedChannel | Message | User | ChatInputCommandInteraction, body: string, allowedMentions?: MessageMentionOptions, @@ -140,7 +129,25 @@ export const CommonPlugin = zeppelinGuildPlugin()({ return undefined; }) as Promise; - }; - }, + }, + + storeAttachmentsAsMessage: async (attachments: Attachment[], backupChannel?: TextBasedChannel | null) => { + const attachmentChannelId = pluginData.config.get().attachment_storing_channel; + const channel = attachmentChannelId + ? (pluginData.guild.channels.cache.get(attachmentChannelId) as TextBasedChannel) ?? backupChannel + : backupChannel; + + if (!channel) { + throw new Error( + 'Cannot store attachments: no attachment storing channel configured, and no backup channel passed' + ); + } + + return channel!.send({ + content: `Storing ${attachments.length} attachment${attachments.length === 1 ? "" : "s"}`, + files: attachments.map((a) => a.url), + }); + }, + } }, }); diff --git a/backend/src/plugins/Common/info.ts b/backend/src/plugins/Common/info.ts new file mode 100644 index 000000000..666ac502a --- /dev/null +++ b/backend/src/plugins/Common/info.ts @@ -0,0 +1,8 @@ +import { ZeppelinPluginInfo } from "../../types"; +import { zCommonConfig } from "./types"; + +export const contextMenuPluginInfo: ZeppelinPluginInfo = { + showInDocs: false, + prettyName: "Common", + configSchema: zCommonConfig, +}; diff --git a/backend/src/plugins/Common/types.ts b/backend/src/plugins/Common/types.ts index 965046aa1..87a342f5b 100644 --- a/backend/src/plugins/Common/types.ts +++ b/backend/src/plugins/Common/types.ts @@ -4,6 +4,7 @@ import z from "zod"; export const zCommonConfig = z.strictObject({ success_emoji: z.string(), error_emoji: z.string(), + attachment_storing_channel: z.nullable(z.string()), }); export interface CommonPluginType extends BasePluginType { diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index 9848f5d51..9f7114c64 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -9,8 +9,8 @@ import { } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { canActOn } from "src/pluginUtils"; -import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; +import { canActOn } from "../../../pluginUtils"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { logger } from "../../../logger"; import { convertDelayStringToMS, renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; @@ -36,17 +36,13 @@ 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: [] }) - .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); + 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 interactionToReply - .editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }) - .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); + await interactionToReply.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] }); return; } @@ -57,9 +53,7 @@ async function banAction( const durationMs = duration ? convertDelayStringToMS(duration)! : undefined; const result = await modactions.banUserId(target, reason, reason, { caseArgs }, durationMs); if (result.status === "failed") { - await interactionToReply - .editReply({ content: "Error: Failed to ban user", embeds: [], components: [] }) - .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); + await interactionToReply.editReply({ content: "Error: Failed to ban user", embeds: [], components: [] }); return; } @@ -73,9 +67,7 @@ async function banAction( await updateAction(pluginData, executingMember, result.case, evidence); } - await interactionToReply - .editReply({ content: banMessage, embeds: [], components: [] }) - .catch((err) => logger.error(`Ban interaction reply failed: ${err}`)); + await interactionToReply.editReply({ content: banMessage, embeds: [], components: [] }); } export async function launchBanActionModal( @@ -112,9 +104,7 @@ export async function launchBanActionModal( if (interaction.isButton()) { await submitted.deferUpdate().catch((err) => logger.error(`Ban interaction defer failed: ${err}`)); } else if (interaction.isContextMenuCommand()) { - await submitted - .deferReply({ ephemeral: true }) - .catch((err) => logger.error(`Ban interaction defer failed: ${err}`)); + await submitted.deferReply({ ephemeral: true }); } const duration = submitted.fields.getTextInputValue("duration"); @@ -122,6 +112,5 @@ export async function launchBanActionModal( const evidence = submitted.fields.getTextInputValue("evidence"); await banAction(pluginData, duration, reason, evidence, target, interaction, submitted); - }) - .catch((err) => logger.error(`Ban modal interaction failed: ${err}`)); + }); } diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts index c5125721a..8dee27b1b 100644 --- a/backend/src/plugins/ContextMenus/actions/clean.ts +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -61,15 +61,11 @@ export async function launchCleanActionModal( await interaction .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) .then(async (submitted) => { - await submitted - .deferReply({ ephemeral: true }) - .catch((err) => logger.error(`Clean interaction defer failed: ${err}`)); + await submitted.deferReply({ ephemeral: true }); const amount = submitted.fields.getTextInputValue("amount"); if (isNaN(Number(amount))) { - interaction - .editReply({ content: `Error: Amount '${amount}' is invalid`, embeds: [], components: [] }) - .catch((err) => logger.error(`Clean interaction reply failed: ${err}`)); + interaction.editReply({ content: `Error: Amount '${amount}' is invalid`, embeds: [], components: [] }); return; } @@ -81,6 +77,5 @@ export async function launchCleanActionModal( interaction.channelId, submitted, ); - }) - .catch((err) => logger.error(`Clean modal interaction failed: ${err}`)); + }); } diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts index 6e69b16f5..6d7ec1e12 100644 --- a/backend/src/plugins/ContextMenus/actions/mute.ts +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -39,25 +39,21 @@ 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: [], - }) - .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); + 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 interactionToReply - .editReply({ - content: "Cannot mute: insufficient permissions", - embeds: [], - components: [], - }) - .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); + await interactionToReply.editReply({ + content: "Cannot mute: insufficient permissions", + embeds: [], + components: [], + }); return; } @@ -78,17 +74,13 @@ async function muteAction( await updateAction(pluginData, executingMember, result.case!, evidence); } - await interactionToReply - .editReply({ content: muteMessage, embeds: [], components: [] }) - .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); + await interactionToReply.editReply({ content: muteMessage, embeds: [], components: [] }); } catch (e) { - await interactionToReply - .editReply({ - content: "Plugin error, please check your BOT_ALERTs", - embeds: [], - components: [], - }) - .catch((err) => logger.error(`Mute interaction reply failed: ${err}`)); + 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({ @@ -134,9 +126,7 @@ export async function launchMuteActionModal( if (interaction.isButton()) { await submitted.deferUpdate().catch((err) => logger.error(`Mute interaction defer failed: ${err}`)); } else if (interaction.isContextMenuCommand()) { - await submitted - .deferReply({ ephemeral: true }) - .catch((err) => logger.error(`Mute interaction defer failed: ${err}`)); + await submitted.deferReply({ ephemeral: true }); } const duration = submitted.fields.getTextInputValue("duration"); @@ -144,6 +134,5 @@ export async function launchMuteActionModal( const evidence = submitted.fields.getTextInputValue("evidence"); await muteAction(pluginData, duration, reason, evidence, target, interaction, submitted); - }) - .catch((err) => logger.error(`Mute modal interaction failed: ${err}`)); + }); } diff --git a/backend/src/plugins/ContextMenus/actions/note.ts b/backend/src/plugins/ContextMenus/actions/note.ts index b6911274f..bbdc6a8ea 100644 --- a/backend/src/plugins/ContextMenus/actions/note.ts +++ b/backend/src/plugins/ContextMenus/actions/note.ts @@ -8,8 +8,8 @@ import { TextInputStyle, } from "discord.js"; import { GuildPluginData } from "knub"; -import { canActOn } from "src/pluginUtils"; -import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; +import { canActOn } from "../../../pluginUtils"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { CaseTypes } from "../../../data/CaseTypes"; import { logger } from "../../../logger"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; @@ -34,25 +34,21 @@ 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: [], - }) - .catch((err) => logger.error(`Note interaction reply failed: ${err}`)); + 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 interactionToReply - .editReply({ - content: "Cannot note: insufficient permissions", - embeds: [], - components: [], - }) - .catch((err) => logger.error(`Note interaction reply failed: ${err}`)); + await interactionToReply.editReply({ + content: "Cannot note: insufficient permissions", + embeds: [], + components: [], + }); return; } @@ -72,13 +68,11 @@ async function noteAction( }); const userName = renderUserUsername(targetMember.user); - await interactionToReply - .editReply({ - content: `Note added on **${userName}** (Case #${createdCase.case_number})`, - embeds: [], - components: [], - }) - .catch((err) => logger.error(`Note interaction reply failed: ${err}`)); + await interactionToReply.editReply({ + content: `Note added on **${userName}** (Case #${createdCase.case_number})`, + embeds: [], + components: [], + }); } export async function launchNoteActionModal( @@ -99,14 +93,11 @@ export async function launchNoteActionModal( if (interaction.isButton()) { await submitted.deferUpdate().catch((err) => logger.error(`Note interaction defer failed: ${err}`)); } else if (interaction.isContextMenuCommand()) { - await submitted - .deferReply({ ephemeral: true }) - .catch((err) => logger.error(`Note interaction defer failed: ${err}`)); + await submitted.deferReply({ ephemeral: true }); } const reason = submitted.fields.getTextInputValue("reason"); await noteAction(pluginData, reason, target, interaction, submitted); - }) - .catch((err) => logger.error(`Note modal interaction failed: ${err}`)); + }); } diff --git a/backend/src/plugins/ContextMenus/actions/update.ts b/backend/src/plugins/ContextMenus/actions/update.ts index 3365f2935..d534930da 100644 --- a/backend/src/plugins/ContextMenus/actions/update.ts +++ b/backend/src/plugins/ContextMenus/actions/update.ts @@ -2,8 +2,8 @@ import { GuildMember } from "discord.js"; import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; import { Case } from "../../../data/entities/Case"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; import { ContextMenuPluginType } from "../types"; export async function updateAction( @@ -19,7 +19,7 @@ export async function updateAction( body: value, }); - pluginData.getPlugin(LogsPlugin).logCaseUpdate({ + void pluginData.getPlugin(LogsPlugin).logCaseUpdate({ mod: executingMember.user, caseNumber: theCase.case_number, caseType: CaseTypes[theCase.type], diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts index b467ed61c..1e93f42da 100644 --- a/backend/src/plugins/ContextMenus/actions/warn.ts +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -8,8 +8,8 @@ import { TextInputStyle, } from "discord.js"; import { GuildPluginData } from "knub"; -import { canActOn } from "src/pluginUtils"; -import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin"; +import { canActOn } from "../../../pluginUtils"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { logger } from "../../../logger"; import { renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; @@ -34,25 +34,21 @@ 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: [], - }) - .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); + 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 interactionToReply - .editReply({ - content: "Cannot warn: insufficient permissions", - embeds: [], - components: [], - }) - .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); + await interactionToReply.editReply({ + content: "Cannot warn: insufficient permissions", + embeds: [], + components: [], + }); return; } @@ -62,9 +58,7 @@ async function warnAction( const result = await modactions.warnMember(targetMember, reason, reason, { caseArgs }); if (result.status === "failed") { - await interactionToReply - .editReply({ content: "Error: Failed to warn user", embeds: [], components: [] }) - .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); + await interactionToReply.editReply({ content: "Error: Failed to warn user", embeds: [], components: [] }); return; } @@ -76,9 +70,7 @@ async function warnAction( await updateAction(pluginData, executingMember, result.case, evidence); } - await interactionToReply - .editReply({ content: muteMessage, embeds: [], components: [] }) - .catch((err) => logger.error(`Warn interaction reply failed: ${err}`)); + await interactionToReply.editReply({ content: muteMessage, embeds: [], components: [] }); } export async function launchWarnActionModal( @@ -105,15 +97,12 @@ export async function launchWarnActionModal( if (interaction.isButton()) { await submitted.deferUpdate().catch((err) => logger.error(`Warn interaction defer failed: ${err}`)); } else if (interaction.isContextMenuCommand()) { - await submitted - .deferReply({ ephemeral: true }) - .catch((err) => logger.error(`Warn interaction defer failed: ${err}`)); + await submitted.deferReply({ ephemeral: true }); } const reason = submitted.fields.getTextInputValue("reason"); const evidence = submitted.fields.getTextInputValue("evidence"); await warnAction(pluginData, reason, evidence, target, interaction, submitted); - }) - .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 9cb40f14f..fd5389979 100644 --- a/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts @@ -12,7 +12,7 @@ import { import { GuildPluginData, guildPluginUserContextMenuCommand } from "knub"; import { Case } from "../../../data/entities/Case"; import { logger } from "../../../logger"; -import { ModActionsPlugin } from "../../../plugins/ModActions/ModActionsPlugin"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { SECONDS, UnknownUser, emptyEmbedValue, renderUserUsername, resolveUser, trimLines } from "../../../utils"; import { asyncMap } from "../../../utils/async"; import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; @@ -40,9 +40,7 @@ export const ModMenuCmd = guildPluginUserContextMenuCommand({ name: "Mod Menu", defaultMemberPermissions: PermissionFlagsBits.ViewAuditLog.toString(), async run({ pluginData, interaction }) { - await interaction - .deferReply({ ephemeral: true }) - .catch((err) => logger.error(`Mod menu interaction defer failed: ${err}`)); + await interaction.deferReply({ ephemeral: true }); // Run permission checks for executing user. const executingMember = await pluginData.guild.members.fetch(interaction.user.id); @@ -50,22 +48,14 @@ export const ModMenuCmd = guildPluginUserContextMenuCommand({ channelId: interaction.channelId, member: executingMember, }); - const utility = pluginData.getPlugin(UtilityPlugin); - if ( - !userCfg.can_use || - (await !utility.hasPermission(executingMember, interaction.channelId, "can_open_mod_menu")) - ) { - await interaction - .followUp({ content: "Error: Insufficient Permissions" }) - .catch((err) => logger.error(`Mod menu interaction follow up failed: ${err}`)); + if (!userCfg.can_use || !userCfg.can_open_mod_menu) { + await interaction.followUp({ content: "Error: Insufficient Permissions" }); return; } const user = await resolveUser(pluginData.client, interaction.targetId); if (!user.id) { - await interaction - .followUp("Error: User not found") - .catch((err) => logger.error(`Mod menu interaction follow up failed: ${err}`)); + await interaction.followUp("Error: User not found"); return; } diff --git a/backend/src/plugins/Counters/CountersPlugin.ts b/backend/src/plugins/Counters/CountersPlugin.ts index ed1b2d8a6..dd226865b 100644 --- a/backend/src/plugins/Counters/CountersPlugin.ts +++ b/backend/src/plugins/Counters/CountersPlugin.ts @@ -19,6 +19,7 @@ import { offCounterEvent } from "./functions/offCounterEvent"; import { onCounterEvent } from "./functions/onCounterEvent"; import { setCounterValue } from "./functions/setCounterValue"; import { CountersPluginType, zCountersConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const DECAY_APPLY_INTERVAL = 5 * MINUTES; @@ -127,6 +128,10 @@ export const CountersPlugin = guildPlugin()({ await state.counters.markUnusedTriggersToBeDeleted(activeTriggerIds); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + async afterLoad(pluginData) { const { state } = pluginData; diff --git a/backend/src/plugins/Counters/commands/AddCounterCmd.ts b/backend/src/plugins/Counters/commands/AddCounterCmd.ts index 0eb68f2bc..558c23de4 100644 --- a/backend/src/plugins/Counters/commands/AddCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/AddCounterCmd.ts @@ -45,22 +45,22 @@ export const AddCounterCmd = guildPluginMessageCommand()({ const counter = config.counters[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName]; if (!counter || !counterId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Unknown counter: ${args.counterName}`); + void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`); return; } if (counter.can_edit === false) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Missing permissions to edit this counter's value`); + void pluginData.state.common.sendErrorMessage(message, `Missing permissions to edit this counter's value`); return; } if (args.channel && !counter.per_channel) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-channel`); + void pluginData.state.common.sendErrorMessage(message, `This counter is not per-channel`); return; } if (args.user && !counter.per_user) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-user`); + void pluginData.state.common.sendErrorMessage(message, `This counter is not per-user`); return; } @@ -69,13 +69,13 @@ export const AddCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which channel's counter value would you like to add to?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Channel is not a text channel, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Channel is not a text channel, cancelling"); return; } @@ -87,13 +87,13 @@ export const AddCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which user's counter value would you like to add to?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialUser = await resolveUser(pluginData.client, reply.content); if (!potentialUser || potentialUser instanceof UnknownUser) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown user, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Unknown user, cancelling"); return; } @@ -105,13 +105,13 @@ export const AddCounterCmd = guildPluginMessageCommand()({ message.channel.send("How much would you like to add to the counter's value?"); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialAmount = parseInt(reply.content, 10); if (!potentialAmount) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Not a number, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Not a number, cancelling"); return; } diff --git a/backend/src/plugins/Counters/commands/CountersListCmd.ts b/backend/src/plugins/Counters/commands/CountersListCmd.ts index db83812e6..fc79b62c1 100644 --- a/backend/src/plugins/Counters/commands/CountersListCmd.ts +++ b/backend/src/plugins/Counters/commands/CountersListCmd.ts @@ -15,7 +15,7 @@ export const CountersListCmd = guildPluginMessageCommand()({ const countersToShow = Array.from(Object.values(config.counters)).filter((c) => c.can_view !== false); if (!countersToShow.length) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "No counters are configured for this server"); + void pluginData.state.common.sendErrorMessage(message, "No counters are configured for this server"); return; } diff --git a/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts b/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts index e413cf21e..5f7aa5d41 100644 --- a/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts +++ b/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts @@ -18,14 +18,12 @@ export const ResetAllCounterValuesCmd = guildPluginMessageCommand()({ const counter = config.counters[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName]; if (!counter || !counterId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Unknown counter: ${args.counterName}`); + void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`); return; } if (counter.can_edit === false) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Missing permissions to reset this counter's value`); + void pluginData.state.common.sendErrorMessage(message, `Missing permissions to reset this counter's value`); return; } if (args.channel && !counter.per_channel) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-channel`); + void pluginData.state.common.sendErrorMessage(message, `This counter is not per-channel`); return; } if (args.user && !counter.per_user) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-user`); + void pluginData.state.common.sendErrorMessage(message, `This counter is not per-user`); return; } @@ -64,13 +64,13 @@ export const ResetCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which channel's counter value would you like to reset?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Channel is not a text channel, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Channel is not a text channel, cancelling"); return; } @@ -82,13 +82,13 @@ export const ResetCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which user's counter value would you like to reset?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialUser = await resolveUser(pluginData.client, reply.content); if (!potentialUser || potentialUser instanceof UnknownUser) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown user, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Unknown user, cancelling"); return; } diff --git a/backend/src/plugins/Counters/commands/SetCounterCmd.ts b/backend/src/plugins/Counters/commands/SetCounterCmd.ts index 3cc62c54d..da055ee0f 100644 --- a/backend/src/plugins/Counters/commands/SetCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/SetCounterCmd.ts @@ -45,22 +45,22 @@ export const SetCounterCmd = guildPluginMessageCommand()({ const counter = config.counters[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName]; if (!counter || !counterId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Unknown counter: ${args.counterName}`); + void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`); return; } if (counter.can_edit === false) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Missing permissions to edit this counter's value`); + void pluginData.state.common.sendErrorMessage(message, `Missing permissions to edit this counter's value`); return; } if (args.channel && !counter.per_channel) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-channel`); + void pluginData.state.common.sendErrorMessage(message, `This counter is not per-channel`); return; } if (args.user && !counter.per_user) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-user`); + void pluginData.state.common.sendErrorMessage(message, `This counter is not per-user`); return; } @@ -69,13 +69,13 @@ export const SetCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which channel's counter value would you like to change?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Channel is not a text channel, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Channel is not a text channel, cancelling"); return; } @@ -87,13 +87,13 @@ export const SetCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which user's counter value would you like to change?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialUser = await resolveUser(pluginData.client, reply.content); if (!potentialUser || potentialUser instanceof UnknownUser) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown user, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Unknown user, cancelling"); return; } @@ -105,13 +105,13 @@ export const SetCounterCmd = guildPluginMessageCommand()({ message.channel.send("What would you like to set the counter's value to?"); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialValue = parseInt(reply.content, 10); if (Number.isNaN(potentialValue)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Not a number, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Not a number, cancelling"); return; } @@ -119,7 +119,7 @@ export const SetCounterCmd = guildPluginMessageCommand()({ } if (value < 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cannot set counter value below 0"); + void pluginData.state.common.sendErrorMessage(message, "Cannot set counter value below 0"); return; } diff --git a/backend/src/plugins/Counters/commands/ViewCounterCmd.ts b/backend/src/plugins/Counters/commands/ViewCounterCmd.ts index 1c037e69f..d91de3f3e 100644 --- a/backend/src/plugins/Counters/commands/ViewCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/ViewCounterCmd.ts @@ -39,22 +39,22 @@ export const ViewCounterCmd = guildPluginMessageCommand()({ const counter = config.counters[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName]; if (!counter || !counterId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Unknown counter: ${args.counterName}`); + void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`); return; } if (counter.can_view === false) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `Missing permissions to view this counter's value`); + void pluginData.state.common.sendErrorMessage(message, `Missing permissions to view this counter's value`); return; } if (args.channel && !counter.per_channel) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-channel`); + void pluginData.state.common.sendErrorMessage(message, `This counter is not per-channel`); return; } if (args.user && !counter.per_user) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, `This counter is not per-user`); + void pluginData.state.common.sendErrorMessage(message, `This counter is not per-user`); return; } @@ -63,13 +63,13 @@ export const ViewCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which channel's counter value would you like to view?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel?.isTextBased()) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Channel is not a text channel, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Channel is not a text channel, cancelling"); return; } @@ -81,13 +81,13 @@ export const ViewCounterCmd = guildPluginMessageCommand()({ message.channel.send(`Which user's counter value would you like to view?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Cancelling"); return; } const potentialUser = await resolveUser(pluginData.client, reply.content); if (!potentialUser || potentialUser instanceof UnknownUser) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown user, cancelling"); + void pluginData.state.common.sendErrorMessage(message, "Unknown user, cancelling"); return; } diff --git a/backend/src/plugins/Counters/types.ts b/backend/src/plugins/Counters/types.ts index ef322e7b2..c75046eb1 100644 --- a/backend/src/plugins/Counters/types.ts +++ b/backend/src/plugins/Counters/types.ts @@ -1,5 +1,5 @@ import { EventEmitter } from "events"; -import { BasePluginType } from "knub"; +import { BasePluginType, pluginUtils } from "knub"; import z from "zod"; import { GuildCounters, MAX_COUNTER_VALUE, MIN_COUNTER_VALUE } from "../../data/GuildCounters"; import { @@ -10,6 +10,7 @@ import { } from "../../data/entities/CounterTrigger"; import { zBoundedCharacters, zBoundedRecord, zDelayString } from "../../utils"; import Timeout = NodeJS.Timeout; +import { CommonPlugin } from "../Common/CommonPlugin"; const MAX_COUNTERS = 5; const MAX_TRIGGERS_PER_COUNTER = 5; @@ -132,5 +133,6 @@ export interface CountersPluginType extends BasePluginType { decayTimers: Timeout[]; events: CounterEventEmitter; counterTriggersByCounterId: Map; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts index f85036a1a..cdb9aae7d 100644 --- a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts +++ b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts @@ -14,6 +14,7 @@ import { import { LogsPlugin } from "../Logs/LogsPlugin"; import { runEvent } from "./functions/runEvent"; import { CustomEventsPluginType, zCustomEventsConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions = { config: { @@ -28,6 +29,10 @@ export const CustomEventsPlugin = guildPlugin()({ configParser: (input) => zCustomEventsConfig.parse(input), defaultOptions, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const config = pluginData.config.get(); for (const [key, event] of Object.entries(config.events)) { diff --git a/backend/src/plugins/CustomEvents/functions/runEvent.ts b/backend/src/plugins/CustomEvents/functions/runEvent.ts index 5ea3ceca8..3f75b8946 100644 --- a/backend/src/plugins/CustomEvents/functions/runEvent.ts +++ b/backend/src/plugins/CustomEvents/functions/runEvent.ts @@ -39,7 +39,7 @@ export async function runEvent( } catch (e) { if (e instanceof ActionError) { if (event.trigger.type === "command") { - pluginData.getPlugin(CommonPlugin).sendErrorMessage((eventData.msg as Message).channel, e.message); + void pluginData.state.common.sendErrorMessage((eventData.msg as Message).channel, e.message); } else { // TODO: Where to log action errors from other kinds of triggers? } diff --git a/backend/src/plugins/CustomEvents/types.ts b/backend/src/plugins/CustomEvents/types.ts index 6433b6d03..0372fd162 100644 --- a/backend/src/plugins/CustomEvents/types.ts +++ b/backend/src/plugins/CustomEvents/types.ts @@ -1,4 +1,4 @@ -import { BasePluginType } from "knub"; +import { BasePluginType, pluginUtils } from "knub"; import z from "zod"; import { zBoundedCharacters, zBoundedRecord } from "../../utils"; import { zAddRoleAction } from "./actions/addRoleAction"; @@ -8,6 +8,7 @@ import { zMakeRoleUnmentionableAction } from "./actions/makeRoleUnmentionableAct import { zMessageAction } from "./actions/messageAction"; import { zMoveToVoiceChannelAction } from "./actions/moveToVoiceChannelAction"; import { zSetChannelPermissionOverridesAction } from "./actions/setChannelPermissionOverrides"; +import { CommonPlugin } from "../Common/CommonPlugin"; const zCommandTrigger = z.strictObject({ type: z.literal("command"), @@ -43,5 +44,6 @@ export interface CustomEventsPluginType extends BasePluginType { config: z.infer; state: { clearTriggers: () => void; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/LocateUser/LocateUserPlugin.ts b/backend/src/plugins/LocateUser/LocateUserPlugin.ts index 3fb8f8412..39057b00a 100644 --- a/backend/src/plugins/LocateUser/LocateUserPlugin.ts +++ b/backend/src/plugins/LocateUser/LocateUserPlugin.ts @@ -9,6 +9,7 @@ import { VoiceStateUpdateAlertEvt } from "./events/SendAlertsEvts"; import { LocateUserPluginType, zLocateUserConfig } from "./types"; import { clearExpiredAlert } from "./utils/clearExpiredAlert"; import { fillActiveAlertsList } from "./utils/fillAlertsList"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -53,6 +54,10 @@ export const LocateUserPlugin = guildPlugin()({ state.usersWithAlerts = []; }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { state, guild } = pluginData; diff --git a/backend/src/plugins/LocateUser/commands/FollowCmd.ts b/backend/src/plugins/LocateUser/commands/FollowCmd.ts index a214241bc..921ec47fa 100644 --- a/backend/src/plugins/LocateUser/commands/FollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/FollowCmd.ts @@ -27,9 +27,7 @@ export const FollowCmd = locateUserCmd({ const active = args.active || false; if (time < 30 * SECONDS) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "Sorry, but the minimum duration for an alert is 30 seconds!"); + void pluginData.state.common.sendErrorMessage(msg, "Sorry, but the minimum duration for an alert is 30 seconds!"); return; } @@ -48,18 +46,14 @@ export const FollowCmd = locateUserCmd({ } if (active) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + void pluginData.state.common.sendSuccessMessage( msg, `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( time, )} i will notify and move you.\nPlease make sure to be in a voice channel, otherwise i cannot move you!`, ); } else { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + void pluginData.state.common.sendSuccessMessage( msg, `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( time, diff --git a/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts b/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts index 271b401d2..c993c5c77 100644 --- a/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts @@ -13,7 +13,7 @@ export const ListFollowCmd = locateUserCmd({ async run({ message: msg, pluginData }) { const alerts = await pluginData.state.alerts.getAlertsByRequestorId(msg.member.id); if (alerts.length === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You have no active alerts!"); + void pluginData.state.common.sendErrorMessage(msg, "You have no active alerts!"); return; } @@ -46,7 +46,7 @@ export const DeleteFollowCmd = locateUserCmd({ alerts.sort(sorter("expires_at")); if (args.num > alerts.length || args.num <= 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown alert!"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown alert!"); return; } @@ -54,6 +54,6 @@ export const DeleteFollowCmd = locateUserCmd({ clearExpiringVCAlert(toDelete); await pluginData.state.alerts.delete(toDelete.id); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Alert deleted"); + void pluginData.state.common.sendSuccessMessage(msg, "Alert deleted"); }, }); diff --git a/backend/src/plugins/LocateUser/types.ts b/backend/src/plugins/LocateUser/types.ts index 4139f4656..90695a6d7 100644 --- a/backend/src/plugins/LocateUser/types.ts +++ b/backend/src/plugins/LocateUser/types.ts @@ -1,6 +1,7 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildVCAlerts } from "../../data/GuildVCAlerts"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zLocateUserConfig = z.strictObject({ can_where: z.boolean(), @@ -13,6 +14,7 @@ export interface LocateUserPluginType extends BasePluginType { alerts: GuildVCAlerts; usersWithAlerts: string[]; unregisterGuildEventListener: () => void; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/LocateUser/utils/moveMember.ts b/backend/src/plugins/LocateUser/utils/moveMember.ts index f6dbd96b3..f562fe307 100644 --- a/backend/src/plugins/LocateUser/utils/moveMember.ts +++ b/backend/src/plugins/LocateUser/utils/moveMember.ts @@ -16,14 +16,10 @@ export async function moveMember( channel: target.voice.channelId, }); } catch { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?"); + void pluginData.state.common.sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?"); return; } } else { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?"); + void pluginData.state.common.sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?"); } } diff --git a/backend/src/plugins/LocateUser/utils/sendWhere.ts b/backend/src/plugins/LocateUser/utils/sendWhere.ts index 8d40e99c9..78172509c 100644 --- a/backend/src/plugins/LocateUser/utils/sendWhere.ts +++ b/backend/src/plugins/LocateUser/utils/sendWhere.ts @@ -22,7 +22,7 @@ export async function sendWhere( try { invite = await createOrReuseInvite(voice); } catch { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(channel, "Cannot create an invite to that channel!"); + void pluginData.state.common.sendErrorMessage(channel, "Cannot create an invite to that channel!"); return; } channel.send({ diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts index 4c9609043..9d62c2c7b 100644 --- a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -4,6 +4,7 @@ import { SaveMessagesToDBCmd } from "./commands/SaveMessagesToDB"; import { SavePinsToDBCmd } from "./commands/SavePinsToDB"; import { MessageCreateEvt, MessageDeleteBulkEvt, MessageDeleteEvt, MessageUpdateEvt } from "./events/SaveMessagesEvts"; import { MessageSaverPluginType, zMessageSaverConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -43,4 +44,8 @@ export const MessageSaverPlugin = guildPlugin()({ const { state, guild } = pluginData; state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); }, + + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, }); diff --git a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts index 6b9211f1c..a34b103ea 100644 --- a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts +++ b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts @@ -18,14 +18,12 @@ export const SaveMessagesToDBCmd = messageSaverCmd({ const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, args.ids.trim().split(" ")); if (failed.length) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + void pluginData.state.common.sendSuccessMessage( msg, `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, ); } else { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Saved ${savedCount} messages!`); + void pluginData.state.common.sendSuccessMessage(msg, `Saved ${savedCount} messages!`); } }, }); diff --git a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts index 92c5393d9..d2910525a 100644 --- a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts +++ b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts @@ -19,14 +19,12 @@ export const SavePinsToDBCmd = messageSaverCmd({ const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, [...pins.keys()]); if (failed.length) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + void pluginData.state.common.sendSuccessMessage( msg, `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, ); } else { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Saved ${savedCount} messages!`); + void pluginData.state.common.sendSuccessMessage(msg, `Saved ${savedCount} messages!`); } }, }); diff --git a/backend/src/plugins/MessageSaver/types.ts b/backend/src/plugins/MessageSaver/types.ts index f42fa2c3e..671eb9d91 100644 --- a/backend/src/plugins/MessageSaver/types.ts +++ b/backend/src/plugins/MessageSaver/types.ts @@ -1,6 +1,7 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zMessageSaverConfig = z.strictObject({ can_manage: z.boolean(), @@ -10,6 +11,7 @@ export interface MessageSaverPluginType extends BasePluginType { config: z.infer; state: { savedMessages: GuildSavedMessages; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index 953e245d9..bd6aa0c75 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -72,6 +72,7 @@ import { onModActionsEvent } from "./functions/onModActionsEvent"; import { updateCase } from "./functions/updateCase"; import { warnMember } from "./functions/warnMember"; import { AttachmentLinkReactionType, ModActionsPluginType, modActionsSlashGroup, zModActionsConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions = { config: { @@ -94,7 +95,6 @@ const defaultOptions = { "The user already has **{priorWarnings}** warnings!\n Please check their prior cases and assess whether or not to warn anyways.\n Proceed with the warning?", ban_delete_message_days: 1, attachment_link_reaction: "warn" as AttachmentLinkReactionType, - attachment_storing_channel: null, can_note: false, can_warn: false, @@ -236,6 +236,10 @@ export const ModActionsPlugin = guildPlugin()({ state.events = new EventEmitter(); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { state, guild } = pluginData; diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts index beb4cc3d1..9f1b819b0 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts @@ -2,8 +2,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { CaseTypes } from "../../../../data/CaseTypes"; import { hasPermission } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualAddCaseCmd } from "../../functions/actualCommands/actualAddCaseCmd"; +import { actualAddCaseCmd } from "./actualAddCaseCmd"; import { modActionsMsgCmd } from "../../types"; const opts = { @@ -28,7 +27,7 @@ export const AddCaseMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } @@ -36,7 +35,7 @@ export const AddCaseMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -46,7 +45,7 @@ export const AddCaseMsgCmd = modActionsMsgCmd({ // Verify the case type is valid const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase(); if (!CaseTypes[type]) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot add case: invalid case type"); + pluginData.state.common.sendErrorMessage(msg, "Cannot add case: invalid case type"); return; } diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts index 5f4276869..828ec742e 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts @@ -4,8 +4,7 @@ import { CaseTypes } from "../../../../data/CaseTypes"; import { hasPermission } from "../../../../pluginUtils"; import { resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualAddCaseCmd } from "../../functions/actualCommands/actualAddCaseCmd"; +import { actualAddCaseCmd } from "./actualAddCaseCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -49,9 +48,10 @@ export const AddCaseSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts b/backend/src/plugins/ModActions/commands/addcase/actualAddCaseCmd.ts similarity index 76% rename from backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts rename to backend/src/plugins/ModActions/commands/addcase/actualAddCaseCmd.ts index e65fe8d30..2c67dc2f8 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualAddCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/actualAddCaseCmd.ts @@ -5,11 +5,10 @@ import { Case } from "../../../../data/entities/Case"; import { canActOn } from "../../../../pluginUtils"; import { UnknownUser, renderUsername, resolveMember } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; export async function actualAddCaseCmd( pluginData: GuildPluginData, @@ -28,9 +27,10 @@ export async function actualAddCaseCmd( // If the user exists as a guild member, make sure we can act on them first const member = await resolveMember(pluginData.client, pluginData.guild, user.id); if (member && !canActOn(pluginData, author, member)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "Cannot add case on this user: insufficient permissions"); + pluginData.state.common.sendErrorMessage( + context, + "Cannot add case on this user: insufficient permissions" + ); return; } @@ -47,11 +47,12 @@ export async function actualAddCaseCmd( }); if (user) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(context, `Case #${theCase.case_number} created for **${renderUsername(user)}**`); + pluginData.state.common.sendSuccessMessage( + context, + `Case #${theCase.case_number} created for **${renderUsername(user)}**` + ); } else { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, `Case #${theCase.case_number} created`); + pluginData.state.common.sendSuccessMessage(context, `Case #${theCase.case_number} created`); } // Log the action diff --git a/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts index 10b4344cb..35b9f7377 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts @@ -1,8 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualBanCmd } from "../../functions/actualCommands/actualBanCmd"; +import { actualBanCmd } from "./actualBanCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; @@ -38,7 +37,7 @@ export const BanMsgCmd = modActionsMsgCmd({ const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } @@ -46,7 +45,7 @@ export const BanMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -57,7 +56,7 @@ export const BanMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args) ?? undefined; } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + pluginData.state.common.sendErrorMessage(msg, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts index 21b5558a0..16bda55b8 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts @@ -3,8 +3,7 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualBanCmd } from "../../functions/actualCommands/actualBanCmd"; +import { actualBanCmd } from "./actualBanCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -52,9 +51,13 @@ export const BanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } @@ -67,9 +70,10 @@ export const BanSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } @@ -80,13 +84,13 @@ export const BanSlashCmd = modActionsSlashCmd({ try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); + pluginData.state.common.sendErrorMessage(interaction, e.message); return; } const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; if (options.time && !convertedTime) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); + pluginData.state.common.sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts b/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts similarity index 85% rename from backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts rename to backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts index e4bf5ff94..0b442239e 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts @@ -9,13 +9,12 @@ import { UnknownUser, UserNotificationMethod, renderUsername, resolveMember } fr import { banLock } from "../../../../utils/lockNameHelpers"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { banUserId } from "../banUserId"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; -import { isBanned } from "../isBanned"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { banUserId } from "../../functions/banUserId"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { isBanned } from "../../functions/isBanned"; export async function actualBanCmd( pluginData: GuildPluginData, @@ -54,7 +53,7 @@ export async function actualBanCmd( ); if (!reply) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "User not on server, ban cancelled by moderator"); + pluginData.state.common.sendErrorMessage(context, "User not on server, ban cancelled by moderator"); lock.unlock(); return; } else { @@ -63,7 +62,7 @@ export async function actualBanCmd( } else { // Abort if trying to ban user indefinitely if they are already banned indefinitely if (!existingTempban && !time) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `User is already banned indefinitely.`); + pluginData.state.common.sendErrorMessage(context, `User is already banned indefinitely.`); return; } @@ -75,9 +74,10 @@ export async function actualBanCmd( ); if (!reply) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "User already banned, update cancelled by moderator"); + pluginData.state.common.sendErrorMessage( + context, + "User already banned, update cancelled by moderator" + ); lock.unlock(); return; } @@ -122,9 +122,7 @@ export async function actualBanCmd( }); } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + pluginData.state.common.sendSuccessMessage( context, `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, ); @@ -137,11 +135,9 @@ export async function actualBanCmd( if (!forceban && !canActOn(pluginData, author, memberToBan!)) { const ourLevel = getMemberLevel(pluginData, author); const targetLevel = getMemberLevel(pluginData, memberToBan!); - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( - context, - `Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`, + pluginData.state.common.sendErrorMessage( + context, + `Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`, ); lock.unlock(); return; @@ -170,7 +166,7 @@ export async function actualBanCmd( ); if (banResult.status === "failed") { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Failed to ban member: ${banResult.error}`); + pluginData.state.common.sendErrorMessage(context, `Failed to ban member: ${banResult.error}`); lock.unlock(); return; } @@ -190,5 +186,5 @@ export async function actualBanCmd( } lock.unlock(); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, response); + pluginData.state.common.sendSuccessMessage(context, response); } diff --git a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts index 4e278e232..6c727e45f 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualCaseCmd } from "../../functions/actualCommands/actualCaseCmd"; +import { actualCaseCmd } from "./actualCaseCmd"; import { modActionsMsgCmd } from "../../types"; const opts = { diff --git a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts index 15f2d03f9..b86bfb011 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts @@ -1,5 +1,5 @@ import { slashOptions } from "knub"; -import { actualCaseCmd } from "../../functions/actualCommands/actualCaseCmd"; +import { actualCaseCmd } from "./actualCaseCmd"; import { modActionsSlashCmd } from "../../types"; const opts = [ diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts b/backend/src/plugins/ModActions/commands/case/actualCaseCmd.ts similarity index 74% rename from backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts rename to backend/src/plugins/ModActions/commands/case/actualCaseCmd.ts index 92b27199c..5be37726f 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/actualCaseCmd.ts @@ -2,7 +2,6 @@ import { ChatInputCommandInteraction, Message } from "discord.js"; import { GuildPluginData } from "knub"; import { sendContextResponse } from "../../../../pluginUtils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; export async function actualCaseCmd( @@ -15,12 +14,12 @@ export async function actualCaseCmd( const theCase = await pluginData.state.cases.findByCaseNumber(caseNumber); if (!theCase) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Case not found", undefined, undefined, show !== true); + void pluginData.state.common.sendErrorMessage(context, "Case not found", undefined, undefined, show !== true); return; } const casesPlugin = pluginData.getPlugin(CasesPlugin); const embed = await casesPlugin.getCaseEmbed(theCase.id, authorId); - sendContextResponse(context, { ...embed, ephemeral: show !== true }); + void sendContextResponse(context, { ...embed, ephemeral: show !== true }); } diff --git a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts index 57f45c748..d5f5f7fe0 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualCasesCmd } from "../../functions/actualCommands/actualCasesCmd"; +import { actualCasesCmd } from "./actualCasesCmd"; import { modActionsMsgCmd } from "../../types"; const opts = { diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts index 8e885d1a0..fe9323110 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -1,6 +1,6 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; -import { actualCasesCmd } from "../../functions/actualCommands/actualCasesCmd"; +import { actualCasesCmd } from "./actualCasesCmd"; import { modActionsSlashCmd } from "../../types"; const opts = [ diff --git a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts index c65226a6b..b36b39c71 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts @@ -1,7 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { resolveMember, resolveUser, UnknownUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualCasesCmd } from "../../functions/actualCommands/actualCasesCmd"; +import { actualCasesCmd } from "./actualCasesCmd"; import { modActionsMsgCmd } from "../../types"; const opts = { @@ -38,7 +37,7 @@ export const CasesUserMsgCmd = modActionsMsgCmd({ (await resolveUser(pluginData.client, args.user)); if (user instanceof UnknownUser) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts b/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts similarity index 97% rename from backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts rename to backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts index 23c091a14..5973bcdb2 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts @@ -18,7 +18,6 @@ import { asyncMap } from "../../../../utils/async"; import { createPaginatedMessage } from "../../../../utils/createPaginatedMessage"; import { getGuildPrefix } from "../../../../utils/getGuildPrefix"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; const casesPerPage = 5; @@ -160,9 +159,13 @@ async function casesModCmd( const totalCases = await casesPlugin.getTotalCasesByMod(modId ?? author.id, casesFilters); if (totalCases === 0) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, `No cases by **${modName}**`, undefined, undefined, !show); + pluginData.state.common.sendErrorMessage( + context, + `No cases by **${modName}**`, + undefined, + undefined, + !show + ); return; } diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts index b4c96a686..dad54847f 100644 --- a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { trimLines } from "../../../../utils"; -import { actualDeleteCaseCmd } from "../../functions/actualCommands/actualDeleteCaseCmd"; +import { actualDeleteCaseCmd } from "./actualDeleteCaseCmd"; import { modActionsMsgCmd } from "../../types"; export const DeleteCaseMsgCmd = modActionsMsgCmd({ diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts index f852b666f..c9038c74e 100644 --- a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts @@ -1,6 +1,6 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; -import { actualDeleteCaseCmd } from "../../functions/actualCommands/actualDeleteCaseCmd"; +import { actualDeleteCaseCmd } from "./actualDeleteCaseCmd"; import { modActionsSlashCmd } from "../../types"; const opts = [slashOptions.boolean({ name: "force", description: "Whether or not to force delete", required: false })]; diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/actualDeleteCaseCmd.ts similarity index 85% rename from backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts rename to backend/src/plugins/ModActions/commands/deletecase/actualDeleteCaseCmd.ts index cedd03c69..707332e4a 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualDeleteCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/actualDeleteCaseCmd.ts @@ -4,7 +4,6 @@ import { Case } from "../../../../data/entities/Case"; import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; import { SECONDS, renderUsername } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { TimeAndDatePlugin } from "../../../TimeAndDate/TimeAndDatePlugin"; import { ModActionsPluginType } from "../../types"; @@ -31,7 +30,7 @@ export async function actualDeleteCaseCmd( } if (failed.length === caseNumbers.length) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "None of the cases were found!"); + pluginData.state.common.sendErrorMessage(context, "None of the cases were found!"); return; } @@ -83,13 +82,15 @@ export async function actualDeleteCaseCmd( : ""; const amt = validCases.length - cancelled; if (amt === 0) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "All deletions were cancelled, no cases were deleted."); + pluginData.state.common.sendErrorMessage( + context, + "All deletions were cancelled, no cases were deleted." + ); return; } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(context, `${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}`); + pluginData.state.common.sendSuccessMessage( + context, + `${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}` + ); } diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts index e15f3c13d..5224e905a 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts @@ -1,8 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualForceBanCmd } from "../../functions/actualCommands/actualForceBanCmd"; +import { actualForceBanCmd } from "./actualForceBanCmd"; import { isBanned } from "../../functions/isBanned"; import { modActionsMsgCmd } from "../../types"; @@ -27,21 +26,21 @@ export const ForceBanMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } // If the user exists as a guild member, make sure we can act on them first const member = await resolveMember(pluginData.client, pluginData.guild, user.id); if (member && !canActOn(pluginData, msg.member, member)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot forceban this user: insufficient permissions"); + pluginData.state.common.sendErrorMessage(msg, "Cannot forceban this user: insufficient permissions"); return; } // Make sure the user isn't already banned const banned = await isBanned(pluginData, user.id); if (banned) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User is already banned`); + pluginData.state.common.sendErrorMessage(msg, `User is already banned`); return; } @@ -49,7 +48,7 @@ export const ForceBanMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts index 7f7e824c3..8389233b5 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts @@ -3,8 +3,7 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualForceBanCmd } from "../../functions/actualCommands/actualForceBanCmd"; +import { actualForceBanCmd } from "./actualForceBanCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -30,9 +29,13 @@ export const ForceBanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } @@ -45,9 +48,10 @@ export const ForceBanSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } @@ -56,7 +60,7 @@ export const ForceBanSlashCmd = modActionsSlashCmd({ const convertedTime = options.time ? convertDelayStringToMS(options.time) : null; if (options.time && !convertedTime) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); + pluginData.state.common.sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts b/backend/src/plugins/ModActions/commands/forceban/actualForceBanCmd.ts similarity index 83% rename from backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts rename to backend/src/plugins/ModActions/commands/forceban/actualForceBanCmd.ts index c093306ff..448f41341 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualForceBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/actualForceBanCmd.ts @@ -4,12 +4,11 @@ import { CaseTypes } from "../../../../data/CaseTypes"; import { LogType } from "../../../../data/LogType"; import { DAYS, MINUTES, UnknownUser } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; -import { ignoreEvent } from "../ignoreEvent"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { ignoreEvent } from "../../functions/ignoreEvent"; export async function actualForceBanCmd( pluginData: GuildPluginData, @@ -37,7 +36,7 @@ export async function actualForceBanCmd( reason: formattedReasonWithAttachments ?? undefined, }); } catch { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Failed to forceban member"); + pluginData.state.common.sendErrorMessage(context, "Failed to forceban member"); return; } @@ -52,9 +51,7 @@ export async function actualForceBanCmd( }); // Confirm the action - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(context, `Member forcebanned (Case #${createdCase.case_number})`); + pluginData.state.common.sendSuccessMessage(context, `Member forcebanned (Case #${createdCase.case_number})`); // Log the action pluginData.getPlugin(LogsPlugin).logMemberForceban({ diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts index 9c2a9cbb2..833fb1813 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts @@ -1,8 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; +import { actualMuteCmd } from "../mute/actualMuteCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; @@ -36,7 +35,7 @@ export const ForceMuteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } @@ -44,7 +43,7 @@ export const ForceMuteMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to mute this user if (memberToMute && !canActOn(pluginData, msg.member, memberToMute)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot mute: insufficient permissions"); + pluginData.state.common.sendErrorMessage(msg, "Cannot mute: insufficient permissions"); return; } @@ -54,7 +53,7 @@ export const ForceMuteMsgCmd = modActionsMsgCmd({ if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -66,7 +65,7 @@ export const ForceMuteMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + pluginData.state.common.sendErrorMessage(msg, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts index 348ff0474..ba2628352 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts @@ -3,8 +3,7 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; +import { actualMuteCmd } from "../mute/actualMuteCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -47,9 +46,13 @@ export const ForceMuteSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } @@ -63,9 +66,10 @@ export const ForceMuteSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } @@ -75,7 +79,7 @@ export const ForceMuteSlashCmd = modActionsSlashCmd({ const convertedTime = options.time ? convertDelayStringToMS(options.time) ?? undefined : undefined; if (options.time && !convertedTime) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); + pluginData.state.common.sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } @@ -83,7 +87,7 @@ export const ForceMuteSlashCmd = modActionsSlashCmd({ try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); + pluginData.state.common.sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts index f9351d8f6..396052fd9 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts @@ -1,8 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { actualUnmuteCmd } from "../unmute/actualUnmuteCmd"; import { modActionsMsgCmd } from "../../types"; const opts = { @@ -33,13 +32,13 @@ export const ForceUnmuteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } // Check if they're muted in the first place if (!(await pluginData.state.mutes.isMuted(user.id))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot unmute: member is not muted"); + pluginData.state.common.sendErrorMessage(msg, "Cannot unmute: member is not muted"); return; } @@ -48,7 +47,7 @@ export const ForceUnmuteMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to unmute this member if (memberToUnmute && !canActOn(pluginData, msg.member, memberToUnmute)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot unmute: insufficient permissions"); + pluginData.state.common.sendErrorMessage(msg, "Cannot unmute: insufficient permissions"); return; } @@ -58,7 +57,7 @@ export const ForceUnmuteMsgCmd = modActionsMsgCmd({ if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts index 326d5b735..bb03ee897 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts @@ -3,8 +3,7 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { actualUnmuteCmd } from "../unmute/actualUnmuteCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -31,9 +30,13 @@ export const ForceUnmuteSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } @@ -47,9 +50,10 @@ export const ForceUnmuteSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } @@ -59,7 +63,7 @@ export const ForceUnmuteSlashCmd = modActionsSlashCmd({ const convertedTime = options.time ? convertDelayStringToMS(options.time) ?? undefined : undefined; if (options.time && !convertedTime) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); + pluginData.state.common.sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts index 3d160d5f0..c04bf9a1e 100644 --- a/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualHideCaseCmd } from "../../functions/actualCommands/actualHideCaseCmd"; +import { actualHideCaseCmd } from "./actualHideCaseCmd"; import { modActionsMsgCmd } from "../../types"; export const HideCaseMsgCmd = modActionsMsgCmd({ diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts index a501a5b13..b6261d739 100644 --- a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts @@ -1,5 +1,5 @@ import { slashOptions } from "knub"; -import { actualHideCaseCmd } from "../../functions/actualCommands/actualHideCaseCmd"; +import { actualHideCaseCmd } from "./actualHideCaseCmd"; import { modActionsSlashCmd } from "../../types"; export const HideCaseSlashCmd = modActionsSlashCmd({ diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/actualHideCaseCmd.ts similarity index 81% rename from backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts rename to backend/src/plugins/ModActions/commands/hidecase/actualHideCaseCmd.ts index 28527b4db..49bee81bb 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualHideCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/actualHideCaseCmd.ts @@ -1,6 +1,5 @@ import { ChatInputCommandInteraction, Message } from "discord.js"; import { GuildPluginData } from "knub"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; export async function actualHideCaseCmd( @@ -21,7 +20,7 @@ export async function actualHideCaseCmd( } if (failed.length === caseNumbers.length) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "None of the cases were found!"); + pluginData.state.common.sendErrorMessage(context, "None of the cases were found!"); return; } const failedAddendum = @@ -30,9 +29,7 @@ export async function actualHideCaseCmd( : ""; const amt = caseNumbers.length - failed.length; - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + pluginData.state.common.sendSuccessMessage( context, `${amt} case${amt === 1 ? " is" : "s are"} now hidden! Use \`unhidecase\` to unhide them.${failedAddendum}`, ); diff --git a/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts index fb2754e0b..c494a7c76 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts @@ -1,8 +1,7 @@ import { hasPermission } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualKickCmd } from "../../functions/actualCommands/actualKickCmd"; +import { actualKickCmd } from "./actualKickCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; @@ -30,7 +29,7 @@ export const KickMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } @@ -38,7 +37,7 @@ export const KickMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(await pluginData.config.getForMessage(msg), "can_act_as_other"))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -49,7 +48,7 @@ export const KickMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + pluginData.state.common.sendErrorMessage(msg, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts index 4776d0934..5bd94f30a 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts @@ -3,8 +3,7 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualKickCmd } from "../../functions/actualCommands/actualKickCmd"; +import { actualKickCmd } from "./actualKickCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -51,9 +50,13 @@ export const KickSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } @@ -66,9 +69,10 @@ export const KickSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } @@ -79,7 +83,7 @@ export const KickSlashCmd = modActionsSlashCmd({ try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); + pluginData.state.common.sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts b/backend/src/plugins/ModActions/commands/kick/actualKickCmd.ts similarity index 72% rename from backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts rename to backend/src/plugins/ModActions/commands/kick/actualKickCmd.ts index f8ad0158b..4a6b22a40 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualKickCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/actualKickCmd.ts @@ -3,13 +3,12 @@ import { GuildPluginData } from "knub"; import { LogType } from "../../../../data/LogType"; import { canActOn } from "../../../../pluginUtils"; import { DAYS, SECONDS, UnknownUser, UserNotificationMethod, renderUsername, resolveMember } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; -import { ignoreEvent } from "../ignoreEvent"; -import { isBanned } from "../isBanned"; -import { kickMember } from "../kickMember"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { ignoreEvent } from "../../functions/ignoreEvent"; +import { isBanned } from "../../functions/isBanned"; +import { kickMember } from "../../functions/kickMember"; export async function actualKickCmd( pluginData: GuildPluginData, @@ -31,9 +30,9 @@ export async function actualKickCmd( if (!memberToKick) { const banned = await isBanned(pluginData, user.id); if (banned) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `User is banned`); + pluginData.state.common.sendErrorMessage(context, `User is banned`); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `User not found on the server`); + pluginData.state.common.sendErrorMessage(context, `User not found on the server`); } return; @@ -41,7 +40,7 @@ export async function actualKickCmd( // Make sure we're allowed to kick this member if (!canActOn(pluginData, author, memberToKick)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Cannot kick: insufficient permissions"); + pluginData.state.common.sendErrorMessage(context, "Cannot kick: insufficient permissions"); return; } @@ -63,7 +62,7 @@ export async function actualKickCmd( try { await memberToKick.ban({ deleteMessageSeconds: (1 * DAYS) / SECONDS, reason: "kick -clean" }); } catch { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Failed to ban the user to clean messages (-clean)"); + pluginData.state.common.sendErrorMessage(context, "Failed to ban the user to clean messages (-clean)"); } pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, memberToKick.id); @@ -72,14 +71,14 @@ export async function actualKickCmd( try { await pluginData.guild.bans.remove(memberToKick.id, "kick -clean"); } catch { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "Failed to unban the user after banning them (-clean)"); + pluginData.state.common.sendErrorMessage( + context, + "Failed to unban the user after banning them (-clean)"); } } if (kickResult.status === "failed") { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Failed to kick user`); + pluginData.state.common.sendErrorMessage(context, `Failed to kick user`); return; } @@ -87,5 +86,5 @@ export async function actualKickCmd( let response = `Kicked **${renderUsername(memberToKick.user)}** (Case #${kickResult.case.case_number})`; if (kickResult.notifyResult.text) response += ` (${kickResult.notifyResult.text})`; - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, response); + pluginData.state.common.sendSuccessMessage(context, response); } diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts index d2f72c702..d47c61d52 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts @@ -1,8 +1,7 @@ import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; +import { actualMassBanCmd } from "./actualMassBanCmd"; import { modActionsMsgCmd } from "../../types"; export const MassBanMsgCmd = modActionsMsgCmd({ @@ -22,7 +21,7 @@ export const MassBanMsgCmd = modActionsMsgCmd({ const banReasonReply = await waitForReply(pluginData.client, await getContextChannel(msg), msg.author.id); if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cancelled"); + pluginData.state.common.sendErrorMessage(msg, "Cancelled"); return; } diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts index de374434f..a358893e3 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts @@ -1,8 +1,7 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMassBanCmd } from "../../functions/actualCommands/actualMassBanCmd"; +import { actualMassBanCmd } from "./actualMassBanCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -31,9 +30,13 @@ export const MassBanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts b/backend/src/plugins/ModActions/commands/massban/actualMassBanCmd.ts similarity index 85% rename from backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts rename to backend/src/plugins/ModActions/commands/massban/actualMassBanCmd.ts index fcb9610d4..207194dfa 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/actualMassBanCmd.ts @@ -6,12 +6,11 @@ import { humanizeDurationShort } from "../../../../humanizeDurationShort"; import { canActOn, getContextChannel, isContextInteraction, sendContextResponse } from "../../../../pluginUtils"; import { DAYS, MINUTES, SECONDS, noop } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; -import { ignoreEvent } from "../ignoreEvent"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { ignoreEvent } from "../../functions/ignoreEvent"; export async function actualMassBanCmd( pluginData: GuildPluginData, @@ -23,7 +22,7 @@ export async function actualMassBanCmd( ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Can only massban max 100 users at once`); + pluginData.state.common.sendErrorMessage(context, `Can only massban max 100 users at once`); return; } @@ -38,9 +37,9 @@ export async function actualMassBanCmd( for (const userId of userIds) { const member = pluginData.guild.members.cache.get(userId as Snowflake); // TODO: Get members on demand? if (member && !canActOn(pluginData, author, member)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "Cannot massban one or more users: insufficient permissions"); + pluginData.state.common.sendErrorMessage( + context, + "Cannot massban one or more users: insufficient permissions"); return; } } @@ -146,7 +145,7 @@ export async function actualMassBanCmd( const successfulBanCount = userIds.length - failedBans.length; if (successfulBanCount === 0) { // All bans failed - don't create a log entry and notify the user - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "All bans failed. Make sure the IDs are valid."); + pluginData.state.common.sendErrorMessage(context, "All bans failed. Make sure the IDs are valid."); } else { // Some or all bans were successful. Create a log entry for the mass ban and notify the user. pluginData.getPlugin(LogsPlugin).logMassBan({ @@ -156,18 +155,17 @@ export async function actualMassBanCmd( }); if (failedBans.length) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - context, - `Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${ - failedBans.length - } failed: ${failedBans.join(" ")}`, - ); + pluginData.state.common.sendSuccessMessage( + context, + `Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${ + failedBans.length + } failed: ${failedBans.join(" ")}`, + ); } else { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(context, `Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}`); + pluginData.state.common.sendSuccessMessage( + context, + `Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}` + ); } } }); diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts index 713e89dd4..bbcfd7156 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts @@ -1,8 +1,7 @@ import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMassMuteCmd } from "../../functions/actualCommands/actualMassMuteCmd"; +import { actualMassMuteCmd } from "./actualMassMuteCmd"; import { modActionsMsgCmd } from "../../types"; export const MassMuteMsgCmd = modActionsMsgCmd({ @@ -25,7 +24,7 @@ export const MassMuteMsgCmd = modActionsMsgCmd({ !muteReasonReceived.content || muteReasonReceived.content.toLowerCase().trim() === "cancel" ) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cancelled"); + pluginData.state.common.sendErrorMessage(msg, "Cancelled"); return; } diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts index ecdadbc77..d7ad927a3 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts @@ -1,8 +1,7 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMassMuteCmd } from "../../functions/actualCommands/actualMassMuteCmd"; +import { actualMassMuteCmd } from "./actualMassMuteCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -31,9 +30,13 @@ export const MassMuteSlashSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts b/backend/src/plugins/ModActions/commands/massmute/actualMassMuteCmd.ts similarity index 80% rename from backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts rename to backend/src/plugins/ModActions/commands/massmute/actualMassMuteCmd.ts index 53a82421a..e8ed71114 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassMuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/actualMassMuteCmd.ts @@ -3,12 +3,11 @@ import { GuildPluginData } from "knub"; import { LogType } from "../../../../data/LogType"; import { logger } from "../../../../logger"; import { canActOn, isContextInteraction, sendContextResponse } from "../../../../pluginUtils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; export async function actualMassMuteCmd( pluginData: GuildPluginData, @@ -20,7 +19,7 @@ export async function actualMassMuteCmd( ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Can only massmute max 100 users at once`); + pluginData.state.common.sendErrorMessage(context, `Can only massmute max 100 users at once`); return; } @@ -35,9 +34,10 @@ export async function actualMassMuteCmd( for (const userId of userIds) { const member = pluginData.guild.members.cache.get(userId as Snowflake); if (member && !canActOn(pluginData, author, member)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "Cannot massmute one or more users: insufficient permissions"); + pluginData.state.common.sendErrorMessage( + context, + "Cannot massmute one or more users: insufficient permissions" + ); return; } } @@ -77,7 +77,7 @@ export async function actualMassMuteCmd( const successfulMuteCount = userIds.length - failedMutes.length; if (successfulMuteCount === 0) { // All mutes failed - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "All mutes failed. Make sure the IDs are valid."); + pluginData.state.common.sendErrorMessage(context, "All mutes failed. Make sure the IDs are valid."); } else { // Success on all or some mutes pluginData.getPlugin(LogsPlugin).logMassMute({ @@ -86,14 +86,12 @@ export async function actualMassMuteCmd( }); if (failedMutes.length) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + pluginData.state.common.sendSuccessMessage( context, `Muted ${successfulMuteCount} users, ${failedMutes.length} failed: ${failedMutes.join(" ")}`, ); } else { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, `Muted ${successfulMuteCount} users successfully`); + pluginData.state.common.sendSuccessMessage(context, `Muted ${successfulMuteCount} users successfully`); } } } diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts index 121be1cb8..c10df41f0 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts @@ -1,8 +1,7 @@ import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMassUnbanCmd } from "../../functions/actualCommands/actualMassUnbanCmd"; +import { actualMassUnbanCmd } from "./actualMassUnbanCmd"; import { modActionsMsgCmd } from "../../types"; export const MassUnbanMsgCmd = modActionsMsgCmd({ @@ -21,7 +20,7 @@ export const MassUnbanMsgCmd = modActionsMsgCmd({ sendContextResponse(msg, "Unban reason? `cancel` to cancel"); const unbanReasonReply = await waitForReply(pluginData.client, await getContextChannel(msg), msg.author.id); if (!unbanReasonReply || !unbanReasonReply.content || unbanReasonReply.content.toLowerCase().trim() === "cancel") { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cancelled"); + pluginData.state.common.sendErrorMessage(msg, "Cancelled"); return; } diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts index 1a2cf5582..4a7335520 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts @@ -1,8 +1,7 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMassUnbanCmd } from "../../functions/actualCommands/actualMassUnbanCmd"; +import { actualMassUnbanCmd } from "./actualMassUnbanCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -31,9 +30,13 @@ export const MassUnbanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts b/backend/src/plugins/ModActions/commands/massunban/actualMassUnbanCmd.ts similarity index 84% rename from backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts rename to backend/src/plugins/ModActions/commands/massunban/actualMassUnbanCmd.ts index 8cf1b4ac1..6f4a0ebfd 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/actualMassUnbanCmd.ts @@ -5,13 +5,12 @@ import { LogType } from "../../../../data/LogType"; import { isContextInteraction, sendContextResponse } from "../../../../pluginUtils"; import { MINUTES, noop } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; -import { ignoreEvent } from "../ignoreEvent"; -import { isBanned } from "../isBanned"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { ignoreEvent } from "../../functions/ignoreEvent"; +import { isBanned } from "../../functions/isBanned"; export async function actualMassUnbanCmd( pluginData: GuildPluginData, @@ -23,7 +22,7 @@ export async function actualMassUnbanCmd( ) { // Limit to 100 users at once (arbitrary?) if (userIds.length > 100) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Can only mass-unban max 100 users at once`); + pluginData.state.common.sendErrorMessage(context, `Can only mass-unban max 100 users at once`); return; } @@ -76,9 +75,10 @@ export async function actualMassUnbanCmd( const successfulUnbanCount = userIds.length - failedUnbans.length; if (successfulUnbanCount === 0) { // All unbans failed - don't create a log entry and notify the user - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "All unbans failed. Make sure the IDs are valid and banned."); + pluginData.state.common.sendErrorMessage( + context, + "All unbans failed. Make sure the IDs are valid and banned." + ); } else { // Some or all unbans were successful. Create a log entry for the mass unban and notify the user. pluginData.getPlugin(LogsPlugin).logMassUnban({ @@ -105,16 +105,12 @@ export async function actualMassUnbanCmd( }); } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + pluginData.state.common.sendSuccessMessage( context, `Unbanned ${successfulUnbanCount} users, ${failedUnbans.length} failed:\n${failedMsg}`, ); } else { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(context, `Unbanned ${successfulUnbanCount} users successfully`); + pluginData.state.common.sendSuccessMessage(context, `Unbanned ${successfulUnbanCount} users successfully`); } } } diff --git a/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts index 469d71621..d6aeb2845 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts @@ -2,8 +2,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; +import { actualMuteCmd } from "./actualMuteCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; @@ -38,7 +37,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } @@ -48,9 +47,10 @@ export const MuteMsgCmd = modActionsMsgCmd({ const _isBanned = await isBanned(pluginData, user.id); const prefix = pluginData.fullConfig.prefix; if (_isBanned) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`); + pluginData.state.common.sendErrorMessage( + msg, + `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.` + ); return; } else { // Ask the mod if we should upgrade to a forcemute as the user is not on the server @@ -61,7 +61,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ ); if (!reply) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "User not on server, mute cancelled by moderator"); + pluginData.state.common.sendErrorMessage(msg, "User not on server, mute cancelled by moderator"); return; } } @@ -69,7 +69,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to mute this member if (memberToMute && !canActOn(pluginData, msg.member, memberToMute)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot mute: insufficient permissions"); + pluginData.state.common.sendErrorMessage(msg, "Cannot mute: insufficient permissions"); return; } @@ -79,7 +79,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } @@ -91,7 +91,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + pluginData.state.common.sendErrorMessage(msg, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts index 5913d67e2..b662b33d0 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts @@ -4,8 +4,7 @@ import { canActOn, hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualMuteCmd } from "../../functions/actualCommands/actualMuteCmd"; +import { actualMuteCmd } from "./actualMuteCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; @@ -53,9 +52,10 @@ export const MuteSlashCmd = modActionsSlashCmd({ const _isBanned = await isBanned(pluginData, options.user.id); const prefix = pluginData.fullConfig.prefix; if (_isBanned) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`); + pluginData.state.common.sendErrorMessage( + interaction, + `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.` + ); return; } else { // Ask the mod if we should upgrade to a forcemute as the user is not on the server @@ -66,9 +66,10 @@ export const MuteSlashCmd = modActionsSlashCmd({ ); if (!reply) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "User not on server, mute cancelled by moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "User not on server, mute cancelled by moderator" + ); return; } } @@ -76,7 +77,7 @@ export const MuteSlashCmd = modActionsSlashCmd({ // Make sure we're allowed to mute this member if (memberToMute && !canActOn(pluginData, interaction.member as GuildMember, memberToMute)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot mute: insufficient permissions"); + pluginData.state.common.sendErrorMessage(interaction, "Cannot mute: insufficient permissions"); return; } @@ -89,9 +90,10 @@ export const MuteSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } @@ -101,7 +103,7 @@ export const MuteSlashCmd = modActionsSlashCmd({ const convertedTime = options.time ? convertDelayStringToMS(options.time) ?? undefined : undefined; if (options.time && !convertedTime) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); + pluginData.state.common.sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } @@ -109,7 +111,7 @@ export const MuteSlashCmd = modActionsSlashCmd({ try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); + pluginData.state.common.sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts b/backend/src/plugins/ModActions/commands/mute/actualMuteCmd.ts similarity index 85% rename from backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts rename to backend/src/plugins/ModActions/commands/mute/actualMuteCmd.ts index 2cff62ff7..6500d0722 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualMuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/actualMuteCmd.ts @@ -10,12 +10,11 @@ import { isDiscordAPIError, renderUsername, } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { MuteResult } from "../../../Mutes/types"; import { ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; /** * The actual function run by both !mute and !forcemute. @@ -57,11 +56,12 @@ export async function actualMuteCmd( }); } catch (e) { if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "Could not mute the user: no mute role set in config"); + pluginData.state.common.sendErrorMessage( + context, + "Could not mute the user: no mute role set in config" + ); } else if (isDiscordAPIError(e) && e.code === 10007) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Could not mute the user: unknown member"); + pluginData.state.common.sendErrorMessage(context, "Could not mute the user: unknown member"); } else { logger.error(`Failed to mute user ${user.id}: ${e.stack}`); if (user.id == null) { @@ -69,7 +69,7 @@ export async function actualMuteCmd( // tslint:disable-next-line:no-console console.trace("[DEBUG] Null user.id for mute"); } - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Could not mute the user"); + pluginData.state.common.sendErrorMessage(context, "Could not mute the user"); } return; @@ -104,5 +104,5 @@ export async function actualMuteCmd( } if (muteResult.notifyResult.text) response += ` (${muteResult.notifyResult.text})`; - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, response); + pluginData.state.common.sendSuccessMessage(context, response); } diff --git a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts index 9861702f9..2cccbd6dc 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts @@ -1,7 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualNoteCmd } from "../../functions/actualCommands/actualNoteCmd"; +import { actualNoteCmd } from "./actualNoteCmd"; import { modActionsMsgCmd } from "../../types"; export const NoteMsgCmd = modActionsMsgCmd({ @@ -17,12 +16,12 @@ export const NoteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } if (!args.note && msg.attachments.size === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Text or attachment required"); + pluginData.state.common.sendErrorMessage(msg, "Text or attachment required"); return; } diff --git a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts index 8962be117..c13bbb607 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts @@ -1,7 +1,6 @@ import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualNoteCmd } from "../../functions/actualCommands/actualNoteCmd"; +import { actualNoteCmd } from "./actualNoteCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -26,9 +25,13 @@ export const NoteSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.note || options.note.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts b/backend/src/plugins/ModActions/commands/note/actualNoteCmd.ts similarity index 85% rename from backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts rename to backend/src/plugins/ModActions/commands/note/actualNoteCmd.ts index 06772113b..5c70cb046 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualNoteCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/actualNoteCmd.ts @@ -3,11 +3,10 @@ import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../../data/CaseTypes"; import { UnknownUser, renderUsername } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; export async function actualNoteCmd( pluginData: GuildPluginData, @@ -39,9 +38,7 @@ export async function actualNoteCmd( reason, }); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + pluginData.state.common.sendSuccessMessage( context, `Note added on **${userName}** (Case #${createdCase.case_number})`, undefined, diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts index 531efc4a8..ec346acc4 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts @@ -1,8 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { hasPermission } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualUnbanCmd } from "../../functions/actualCommands/actualUnbanCmd"; +import { actualUnbanCmd } from "./actualUnbanCmd"; import { modActionsMsgCmd } from "../../types"; const opts = { @@ -26,7 +25,7 @@ export const UnbanMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } @@ -34,7 +33,7 @@ export const UnbanMsgCmd = modActionsMsgCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts index 954a90ae4..a5b0124b3 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts @@ -3,8 +3,7 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualUnbanCmd } from "../../functions/actualCommands/actualUnbanCmd"; +import { actualUnbanCmd } from "./actualUnbanCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -30,9 +29,13 @@ export const UnbanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } @@ -45,9 +48,10 @@ export const UnbanSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts b/backend/src/plugins/ModActions/commands/unban/actualUnbanCmd.ts similarity index 81% rename from backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts rename to backend/src/plugins/ModActions/commands/unban/actualUnbanCmd.ts index 85f9abba5..e49b5b21f 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualUnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/actualUnbanCmd.ts @@ -5,12 +5,11 @@ import { LogType } from "../../../../data/LogType"; import { clearExpiringTempban } from "../../../../data/loops/expiringTempbansLoop"; import { UnknownUser } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { IgnoredEventType, ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; -import { ignoreEvent } from "../ignoreEvent"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { ignoreEvent } from "../../functions/ignoreEvent"; export async function actualUnbanCmd( pluginData: GuildPluginData, @@ -32,9 +31,10 @@ export async function actualUnbanCmd( ignoreEvent(pluginData, IgnoredEventType.Unban, user.id); await pluginData.guild.bans.remove(user.id as Snowflake, formattedReason ?? undefined); } catch { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(context, "Failed to unban member; are you sure they're banned?"); + pluginData.state.common.sendErrorMessage( + context, + "Failed to unban member; are you sure they're banned?" + ); return; } @@ -56,7 +56,7 @@ export async function actualUnbanCmd( } // Confirm the action - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, `Member unbanned (Case #${createdCase.case_number})`); + pluginData.state.common.sendSuccessMessage(context, `Member unbanned (Case #${createdCase.case_number})`); // Log the action pluginData.getPlugin(LogsPlugin).logMemberUnban({ diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts index 8e71c8e38..308c34f28 100644 --- a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts @@ -1,5 +1,5 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualHideCaseCmd } from "../../functions/actualCommands/actualHideCaseCmd"; +import { actualHideCaseCmd } from "../hidecase/actualHideCaseCmd"; import { modActionsMsgCmd } from "../../types"; export const UnhideCaseMsgCmd = modActionsMsgCmd({ diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts index 2dbdc309a..f4b4b80f1 100644 --- a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts @@ -1,5 +1,5 @@ import { slashOptions } from "knub"; -import { actualUnhideCaseCmd } from "../../functions/actualCommands/actualUnhideCaseCmd"; +import { actualUnhideCaseCmd } from "./actualUnhideCaseCmd"; import { modActionsSlashCmd } from "../../types"; export const UnhideCaseSlashCmd = modActionsSlashCmd({ diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/actualUnhideCaseCmd.ts similarity index 73% rename from backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts rename to backend/src/plugins/ModActions/commands/unhidecase/actualUnhideCaseCmd.ts index 313261eee..f1e8405e8 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualUnhideCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/actualUnhideCaseCmd.ts @@ -1,6 +1,5 @@ import { ChatInputCommandInteraction, Message } from "discord.js"; import { GuildPluginData } from "knub"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; export async function actualUnhideCaseCmd( @@ -21,7 +20,7 @@ export async function actualUnhideCaseCmd( } if (failed.length === caseNumbers.length) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "None of the cases were found!"); + pluginData.state.common.sendErrorMessage(context, "None of the cases were found!"); return; } @@ -31,7 +30,8 @@ export async function actualUnhideCaseCmd( : ""; const amt = caseNumbers.length - failed.length; - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(context, `${amt} case${amt === 1 ? " is" : "s are"} no longer hidden!${failedAddendum}`); + pluginData.state.common.sendSuccessMessage( + context, + `${amt} case${amt === 1 ? " is" : "s are"} no longer hidden!${failedAddendum}` + ); } diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts index b917d83b8..a0654d138 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts @@ -2,9 +2,8 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; -import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { actualUnmuteCmd } from "./actualUnmuteCmd"; import { isBanned } from "../../functions/isBanned"; import { modActionsMsgCmd } from "../../types"; @@ -36,7 +35,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } @@ -50,7 +49,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ !hasMuteRole && !memberToUnmute?.isCommunicationDisabled() ) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot unmute: member is not muted"); + pluginData.state.common.sendErrorMessage(msg, "Cannot unmute: member is not muted"); return; } @@ -58,9 +57,10 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ const banned = await isBanned(pluginData, user.id); const prefix = pluginData.fullConfig.prefix; if (banned) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`); + pluginData.state.common.sendErrorMessage( + msg, + `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.` + ); return; } else { // Ask the mod if we should upgrade to a forceunmute as the user is not on the server @@ -71,7 +71,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ ); if (!reply) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "User not on server, unmute cancelled by moderator"); + pluginData.state.common.sendErrorMessage(msg, "User not on server, unmute cancelled by moderator"); return; } } @@ -79,7 +79,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to unmute this member if (memberToUnmute && !canActOn(pluginData, msg.member, memberToUnmute)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot unmute: insufficient permissions"); + pluginData.state.common.sendErrorMessage(msg, "Cannot unmute: insufficient permissions"); return; } @@ -89,7 +89,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You don't have permission to use -mod"); + pluginData.state.common.sendErrorMessage(msg, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts index b3ab730cd..609c74514 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts @@ -4,9 +4,8 @@ import { canActOn, hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; -import { actualUnmuteCmd } from "../../functions/actualCommands/actualUnmuteCmd"; +import { actualUnmuteCmd } from "./actualUnmuteCmd"; import { isBanned } from "../../functions/isBanned"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; @@ -34,9 +33,13 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } @@ -51,7 +54,7 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ !hasMuteRole && !memberToUnmute?.isCommunicationDisabled() ) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot unmute: member is not muted"); + pluginData.state.common.sendErrorMessage(interaction, "Cannot unmute: member is not muted"); return; } @@ -59,9 +62,10 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ const banned = await isBanned(pluginData, options.user.id); const prefix = pluginData.fullConfig.prefix; if (banned) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`); + pluginData.state.common.sendErrorMessage( + interaction, + `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.` + ); return; } else { // Ask the mod if we should upgrade to a forceunmute as the user is not on the server @@ -72,9 +76,10 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ ); if (!reply) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "User not on server, unmute cancelled by moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "User not on server, unmute cancelled by moderator" + ); return; } } @@ -82,7 +87,7 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ // Make sure we're allowed to unmute this member if (memberToUnmute && !canActOn(pluginData, interaction.member as GuildMember, memberToUnmute)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot unmute: insufficient permissions"); + pluginData.state.common.sendErrorMessage(interaction, "Cannot unmute: insufficient permissions"); return; } @@ -95,9 +100,10 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } @@ -107,7 +113,7 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ const convertedTime = options.time ? convertDelayStringToMS(options.time) ?? undefined : undefined; if (options.time && !convertedTime) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); + pluginData.state.common.sendErrorMessage(interaction, `Could not convert ${options.time} to a delay`); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts b/backend/src/plugins/ModActions/commands/unmute/actualUnmuteCmd.ts similarity index 81% rename from backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts rename to backend/src/plugins/ModActions/commands/unmute/actualUnmuteCmd.ts index 11a668b4d..3e3be3125 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualUnmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/actualUnmuteCmd.ts @@ -2,11 +2,10 @@ import { Attachment, ChatInputCommandInteraction, GuildMember, Message, User } f import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import { UnknownUser, asSingleLine, renderUsername } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; export async function actualUnmuteCmd( pluginData: GuildPluginData, @@ -35,14 +34,14 @@ export async function actualUnmuteCmd( }); if (!result) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "User is not muted!"); + pluginData.state.common.sendErrorMessage(context, "User is not muted!"); return; } // Confirm the action to the moderator if (time) { const timeUntilUnmute = time && humanizeDuration(time); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage( + pluginData.state.common.sendSuccessMessage( context, asSingleLine(` Unmuting **${renderUsername(user)}** @@ -50,7 +49,7 @@ export async function actualUnmuteCmd( `), ); } else { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage( + pluginData.state.common.sendSuccessMessage( context, asSingleLine(` Unmuted **${renderUsername(user)}** diff --git a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts index d61753562..1d9e331f7 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts @@ -1,8 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { errorMessage, resolveMember, resolveUser } from "../../../../utils"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualWarnCmd } from "../../functions/actualCommands/actualWarnCmd"; +import { actualWarnCmd } from "./actualWarnCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; @@ -24,7 +23,7 @@ export const WarnMsgCmd = modActionsMsgCmd({ async run({ pluginData, message: msg, args }) { const user = await resolveUser(pluginData.client, args.user); if (!user.id) { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found`); + await pluginData.state.common.sendErrorMessage(msg, `User not found`); return; } @@ -33,9 +32,9 @@ export const WarnMsgCmd = modActionsMsgCmd({ if (!memberToWarn) { const _isBanned = await isBanned(pluginData, user.id); if (_isBanned) { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User is banned`); + await pluginData.state.common.sendErrorMessage(msg, `User is banned`); } else { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `User not found on the server`); + await pluginData.state.common.sendErrorMessage(msg, `User not found on the server`); } return; @@ -43,7 +42,7 @@ export const WarnMsgCmd = modActionsMsgCmd({ // Make sure we're allowed to warn this member if (!canActOn(pluginData, msg.member, memberToWarn)) { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot warn: insufficient permissions"); + await pluginData.state.common.sendErrorMessage(msg, "Cannot warn: insufficient permissions"); return; } @@ -62,7 +61,7 @@ export const WarnMsgCmd = modActionsMsgCmd({ try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + await pluginData.state.common.sendErrorMessage(msg, e.message); return; } diff --git a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts index 180d52c85..82f80ba0e 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts @@ -3,8 +3,7 @@ import { slashOptions } from "knub"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; -import { actualWarnCmd } from "../../functions/actualCommands/actualWarnCmd"; +import { actualWarnCmd } from "./actualWarnCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; @@ -47,9 +46,13 @@ export const WarnSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - await pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); + await pluginData.state.common.sendErrorMessage( + interaction, + "Text or attachment required", + undefined, + undefined, + true + ); return; } @@ -59,9 +62,9 @@ export const WarnSlashCmd = modActionsSlashCmd({ if (!memberToWarn) { const _isBanned = await isBanned(pluginData, options.user.id); if (_isBanned) { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `User is banned`); + await pluginData.state.common.sendErrorMessage(interaction, `User is banned`); } else { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, `User not found on the server`); + await pluginData.state.common.sendErrorMessage(interaction, `User not found on the server`); } return; @@ -69,7 +72,7 @@ export const WarnSlashCmd = modActionsSlashCmd({ // Make sure we're allowed to warn this member if (!canActOn(pluginData, interaction.member as GuildMember, memberToWarn)) { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, "Cannot warn: insufficient permissions"); + await pluginData.state.common.sendErrorMessage(interaction, "Cannot warn: insufficient permissions"); return; } @@ -81,9 +84,10 @@ export const WarnSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - await pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(interaction, "You don't have permission to act as another moderator"); + await pluginData.state.common.sendErrorMessage( + interaction, + "You don't have permission to act as another moderator" + ); return; } @@ -94,7 +98,7 @@ export const WarnSlashCmd = modActionsSlashCmd({ try { contactMethods = readContactMethodsFromArgs(options) ?? undefined; } catch (e) { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(interaction, e.message); + await pluginData.state.common.sendErrorMessage(interaction, e.message); return; } diff --git a/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts b/backend/src/plugins/ModActions/commands/warn/actualWarnCmd.ts similarity index 83% rename from backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts rename to backend/src/plugins/ModActions/commands/warn/actualWarnCmd.ts index ac43d8df5..c764d29db 100644 --- a/backend/src/plugins/ModActions/functions/actualCommands/actualWarnCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/actualWarnCmd.ts @@ -4,11 +4,10 @@ import { CaseTypes } from "../../../../data/CaseTypes"; import { UserNotificationMethod, renderUsername } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../../Common/CommonPlugin"; import { ModActionsPluginType } from "../../types"; -import { handleAttachmentLinkDetectionAndGetRestriction } from "../attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../formatReasonForAttachments"; -import { warnMember } from "../warnMember"; +import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; +import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { warnMember } from "../../functions/warnMember"; export async function actualWarnCmd( pluginData: GuildPluginData, @@ -37,7 +36,7 @@ export async function actualWarnCmd( { confirmText: "Yes", cancelText: "No", restrictToId: authorId }, ); if (!reply) { - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Warn cancelled by moderator"); + await pluginData.state.common.sendErrorMessage(context, "Warn cancelled by moderator"); return; } } @@ -55,16 +54,14 @@ export async function actualWarnCmd( if (warnResult.status === "failed") { const failReason = warnResult.error ? `: ${warnResult.error}` : ""; - await pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, `Failed to warn user${failReason}`); + await pluginData.state.common.sendErrorMessage(context, `Failed to warn user${failReason}`); return; } const messageResultText = warnResult.notifyResult.text ? ` (${warnResult.notifyResult.text})` : ""; - await pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( + await pluginData.state.common.sendSuccessMessage( context, `Warned **${renderUsername(memberToWarn.user)}** (Case #${warnResult.case.case_number})${messageResultText}`, ); diff --git a/backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts b/backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts index 868786eda..b10e063e0 100644 --- a/backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts +++ b/backend/src/plugins/ModActions/functions/attachmentLinkReaction.ts @@ -1,6 +1,5 @@ import { ChatInputCommandInteraction, Message, TextBasedChannel } from "discord.js"; import { AnyPluginData, GuildPluginData } from "knub"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { ModActionsPluginType } from "../types"; export function shouldReactToAttachmentLink(pluginData: GuildPluginData) { @@ -22,16 +21,14 @@ export function sendAttachmentLinkDetectionErrorMessage( context: TextBasedChannel | Message | ChatInputCommandInteraction, restricted = false, ) { - const emoji = pluginData.getPlugin(CommonPlugin).getErrorEmoji(); - - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( - context, - "You manually added a Discord attachment link to the reason. This link will only work for a limited time.\n" + - "You should instead **re-upload** the attachment with the command, in the same message.\n\n" + - (restricted ? `${emoji} **Command canceled.** ${emoji}` : "").trim(), - ); + const emoji = pluginData.state.common.getErrorEmoji(); + + pluginData.state.common.sendErrorMessage( + context, + "You manually added a Discord attachment link to the reason. This link will only work for a limited time.\n" + + "You should instead **re-upload** the attachment with the command, in the same message.\n\n" + + (restricted ? `${emoji} **Command canceled.** ${emoji}` : "").trim(), + ); } export async function handleAttachmentLinkDetectionAndGetRestriction( diff --git a/backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts b/backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts index 4cb16e822..8932aa268 100644 --- a/backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts +++ b/backend/src/plugins/ModActions/functions/formatReasonForAttachments.ts @@ -1,4 +1,4 @@ -import { Attachment, ChatInputCommandInteraction, Message, TextBasedChannel } from "discord.js"; +import { Attachment, ChatInputCommandInteraction, Message } from "discord.js"; import { GuildPluginData } from "knub"; import { isContextMessage } from "../../../pluginUtils"; import { ModActionsPluginType } from "../types"; @@ -19,17 +19,9 @@ export async function formatReasonWithMessageLinkForAttachments( return reason; } - const attachmentChannelId = pluginData.config.get().attachment_storing_channel; - const channel = attachmentChannelId - ? (pluginData.guild.channels.cache.get(attachmentChannelId) as TextBasedChannel) ?? context.channel - : context.channel; + const attachmentsMessage = await pluginData.state.common.storeAttachmentsAsMessage(attachments, context.channel); - const message = await channel!.send({ - content: `Storing ${attachments.length} attachment${attachments.length === 1 ? "" : "s"}`, - files: attachments.map((a) => a.url), - }); - - return ((reason || "") + " " + message.url).trim(); + return ((reason || "") + " " + attachmentsMessage.url).trim(); } export function formatReasonWithAttachments(reason: string, attachments: Attachment[]) { diff --git a/backend/src/plugins/ModActions/functions/updateCase.ts b/backend/src/plugins/ModActions/functions/updateCase.ts index e4011fb72..8cfffa98a 100644 --- a/backend/src/plugins/ModActions/functions/updateCase.ts +++ b/backend/src/plugins/ModActions/functions/updateCase.ts @@ -3,7 +3,6 @@ import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; import { Case } from "../../../data/entities/Case"; import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { ModActionsPluginType } from "../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "./attachmentLinkReaction"; @@ -25,12 +24,12 @@ export async function updateCase( } if (!theCase) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Case not found"); + pluginData.state.common.sendErrorMessage(context, "Case not found"); return; } if (note.length === 0 && attachments.length === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(context, "Text or attachment required"); + pluginData.state.common.sendErrorMessage(context, "Text or attachment required"); return; } @@ -54,5 +53,5 @@ export async function updateCase( note: formattedNote, }); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(context, `Case \`#${theCase.case_number}\` updated`); + pluginData.state.common.sendSuccessMessage(context, `Case \`#${theCase.case_number}\` updated`); } diff --git a/backend/src/plugins/ModActions/types.ts b/backend/src/plugins/ModActions/types.ts index ee128986c..a3f36c116 100644 --- a/backend/src/plugins/ModActions/types.ts +++ b/backend/src/plugins/ModActions/types.ts @@ -1,11 +1,11 @@ import { ChatInputCommandInteraction, Message } from "discord.js"; import { EventEmitter } from "events"; import { - BasePluginType, + BasePluginType, pluginUtils, guildPluginEventListener, guildPluginMessageCommand, guildPluginSlashCommand, - guildPluginSlashGroup, + guildPluginSlashGroup } from "knub"; import z from "zod"; import { Queue } from "../../Queue"; @@ -16,6 +16,7 @@ import { GuildTempbans } from "../../data/GuildTempbans"; import { Case } from "../../data/entities/Case"; import { UserNotificationMethod, UserNotificationResult } from "../../utils"; import { CaseArgs } from "../Cases/types"; +import { CommonPlugin } from "../Common/CommonPlugin"; export type AttachmentLinkReactionType = "none" | "warn" | "restrict" | null; @@ -38,7 +39,6 @@ export const zModActionsConfig = z.strictObject({ warn_notify_message: z.string(), ban_delete_message_days: z.number(), attachment_link_reaction: z.nullable(z.union([z.literal("none"), z.literal("warn"), z.literal("restrict")])), - attachment_storing_channel: z.nullable(z.string()), can_note: z.boolean(), can_warn: z.boolean(), can_mute: z.boolean(), @@ -84,6 +84,8 @@ export interface ModActionsPluginType extends BasePluginType { massbanQueue: Queue; events: ModActionsEventEmitter; + + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/Mutes/MutesPlugin.ts b/backend/src/plugins/Mutes/MutesPlugin.ts index db8548bea..c1a8c84ba 100644 --- a/backend/src/plugins/Mutes/MutesPlugin.ts +++ b/backend/src/plugins/Mutes/MutesPlugin.ts @@ -24,6 +24,7 @@ import { onMutesEvent } from "./functions/onMutesEvent"; import { renewTimeoutMute } from "./functions/renewTimeoutMute"; import { unmuteUser } from "./functions/unmuteUser"; import { MutesPluginType, zMutesConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions = { config: { @@ -109,6 +110,10 @@ export const MutesPlugin = guildPlugin()({ state.events = new EventEmitter(); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { state, guild } = pluginData; diff --git a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts index 3884ae891..64b77233b 100644 --- a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts @@ -25,6 +25,6 @@ export const ClearBannedMutesCmd = mutesCmd({ } } - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Cleared ${cleared} mutes from banned users!`); + void pluginData.state.common.sendSuccessMessage(msg, `Cleared ${cleared} mutes from banned users!`); }, }); diff --git a/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts index a71dc4025..b8f750228 100644 --- a/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts @@ -23,15 +23,11 @@ export const ClearMutesCmd = mutesCmd({ } if (failed.length !== args.userIds.length) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `**${args.userIds.length - failed.length} active mute(s) cleared**`); + void pluginData.state.common.sendSuccessMessage(msg, `**${args.userIds.length - failed.length} active mute(s) cleared**`); } if (failed.length) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( + void pluginData.state.common.sendErrorMessage( msg, `**${failed.length}/${args.userIds.length} IDs failed**, they are not muted: ${failed.join(" ")}`, ); diff --git a/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts b/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts index 1e52f6b3d..c5f7be130 100644 --- a/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts @@ -26,8 +26,6 @@ export const ClearMutesWithoutRoleCmd = mutesCmd({ } } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `Cleared ${cleared} mutes from members that don't have the mute role`); + void pluginData.state.common.sendSuccessMessage(msg, `Cleared ${cleared} mutes from members that don't have the mute role`); }, }); diff --git a/backend/src/plugins/Mutes/types.ts b/backend/src/plugins/Mutes/types.ts index e1f266a94..59790782e 100644 --- a/backend/src/plugins/Mutes/types.ts +++ b/backend/src/plugins/Mutes/types.ts @@ -1,6 +1,6 @@ import { GuildMember } from "discord.js"; import { EventEmitter } from "events"; -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildArchives } from "../../data/GuildArchives"; import { GuildCases } from "../../data/GuildCases"; @@ -10,6 +10,7 @@ import { Case } from "../../data/entities/Case"; import { Mute } from "../../data/entities/Mute"; import { UserNotificationMethod, UserNotificationResult, zSnowflake } from "../../utils"; import { CaseArgs } from "../Cases/types"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zMutesConfig = z.strictObject({ mute_role: zSnowflake.nullable(), @@ -53,6 +54,8 @@ export interface MutesPluginType extends BasePluginType { unregisterTimeoutMuteToRenewListener: () => void; events: MutesEventEmitter; + + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/NameHistory/NameHistoryPlugin.ts b/backend/src/plugins/NameHistory/NameHistoryPlugin.ts index ee226f3ed..f8d5f9900 100644 --- a/backend/src/plugins/NameHistory/NameHistoryPlugin.ts +++ b/backend/src/plugins/NameHistory/NameHistoryPlugin.ts @@ -4,6 +4,7 @@ import { GuildNicknameHistory } from "../../data/GuildNicknameHistory"; import { UsernameHistory } from "../../data/UsernameHistory"; import { NamesCmd } from "./commands/NamesCmd"; import { NameHistoryPluginType, zNameHistoryConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -44,4 +45,8 @@ export const NameHistoryPlugin = guildPlugin()({ state.usernameHistory = new UsernameHistory(); state.updateQueue = new Queue(); }, + + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, }); diff --git a/backend/src/plugins/NameHistory/commands/NamesCmd.ts b/backend/src/plugins/NameHistory/commands/NamesCmd.ts index f584857f6..9f3bb4188 100644 --- a/backend/src/plugins/NameHistory/commands/NamesCmd.ts +++ b/backend/src/plugins/NameHistory/commands/NamesCmd.ts @@ -21,7 +21,7 @@ export const NamesCmd = nameHistoryCmd({ const usernames = await pluginData.state.usernameHistory.getByUserId(args.userId); if (nicknames.length === 0 && usernames.length === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No name history found"); + void pluginData.state.common.sendErrorMessage(msg, "No name history found"); return; } diff --git a/backend/src/plugins/NameHistory/types.ts b/backend/src/plugins/NameHistory/types.ts index 70101b539..df96a56be 100644 --- a/backend/src/plugins/NameHistory/types.ts +++ b/backend/src/plugins/NameHistory/types.ts @@ -1,8 +1,9 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { Queue } from "../../Queue"; import { GuildNicknameHistory } from "../../data/GuildNicknameHistory"; import { UsernameHistory } from "../../data/UsernameHistory"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zNameHistoryConfig = z.strictObject({ can_view: z.boolean(), @@ -14,6 +15,7 @@ export interface NameHistoryPluginType extends BasePluginType { nicknameHistory: GuildNicknameHistory; usernameHistory: UsernameHistory; updateQueue: Queue; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts b/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts index 68dbcd2e3..118cf0443 100644 --- a/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts +++ b/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts @@ -3,6 +3,7 @@ import { GuildPingableRoles } from "../../data/GuildPingableRoles"; import { PingableRoleDisableCmd } from "./commands/PingableRoleDisableCmd"; import { PingableRoleEnableCmd } from "./commands/PingableRoleEnableCmd"; import { PingableRolesPluginType, zPingableRolesConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -44,4 +45,8 @@ export const PingableRolesPlugin = guildPlugin()({ state.cache = new Map(); state.timeouts = new Map(); }, + + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, }); diff --git a/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts b/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts index 00006ceef..3bc1ad581 100644 --- a/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts +++ b/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts @@ -14,17 +14,13 @@ export const PingableRoleDisableCmd = pingableRolesCmd({ async run({ message: msg, args, pluginData }) { const pingableRole = await pluginData.state.pingableRoles.getByChannelAndRoleId(args.channelId, args.role.id); if (!pingableRole) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `**${args.role.name}** is not set as pingable in <#${args.channelId}>`); + void pluginData.state.common.sendErrorMessage(msg, `**${args.role.name}** is not set as pingable in <#${args.channelId}>`); return; } await pluginData.state.pingableRoles.delete(args.channelId, args.role.id); pluginData.state.cache.delete(args.channelId); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `**${args.role.name}** is no longer set as pingable in <#${args.channelId}>`); + void pluginData.state.common.sendSuccessMessage(msg, `**${args.role.name}** is no longer set as pingable in <#${args.channelId}>`); }, }); diff --git a/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts b/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts index 7a93990fe..004c07414 100644 --- a/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts +++ b/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts @@ -17,17 +17,13 @@ export const PingableRoleEnableCmd = pingableRolesCmd({ args.role.id, ); if (existingPingableRole) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `**${args.role.name}** is already set as pingable in <#${args.channelId}>`); + void pluginData.state.common.sendErrorMessage(msg, `**${args.role.name}** is already set as pingable in <#${args.channelId}>`); return; } await pluginData.state.pingableRoles.add(args.channelId, args.role.id); pluginData.state.cache.delete(args.channelId); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `**${args.role.name}** has been set as pingable in <#${args.channelId}>`); + void pluginData.state.common.sendSuccessMessage(msg, `**${args.role.name}** has been set as pingable in <#${args.channelId}>`); }, }); diff --git a/backend/src/plugins/PingableRoles/types.ts b/backend/src/plugins/PingableRoles/types.ts index 3bd6faa80..55edd97ca 100644 --- a/backend/src/plugins/PingableRoles/types.ts +++ b/backend/src/plugins/PingableRoles/types.ts @@ -1,7 +1,8 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildPingableRoles } from "../../data/GuildPingableRoles"; import { PingableRole } from "../../data/entities/PingableRole"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zPingableRolesConfig = z.strictObject({ can_manage: z.boolean(), @@ -14,6 +15,7 @@ export interface PingableRolesPluginType extends BasePluginType { pingableRoles: GuildPingableRoles; cache: Map; timeouts: Map; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/Post/PostPlugin.ts b/backend/src/plugins/Post/PostPlugin.ts index 0deb881b7..93376dcfd 100644 --- a/backend/src/plugins/Post/PostPlugin.ts +++ b/backend/src/plugins/Post/PostPlugin.ts @@ -14,6 +14,7 @@ import { ScheduledPostsListCmd } from "./commands/ScheduledPostsListCmd"; import { ScheduledPostsShowCmd } from "./commands/ScheduledPostsShowCmd"; import { PostPluginType, zPostConfig } from "./types"; import { postScheduledPost } from "./util/postScheduledPost"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -55,6 +56,10 @@ export const PostPlugin = guildPlugin()({ state.logs = new GuildLogs(guild.id); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { state, guild } = pluginData; diff --git a/backend/src/plugins/Post/commands/EditCmd.ts b/backend/src/plugins/Post/commands/EditCmd.ts index cd607aa60..fb7859291 100644 --- a/backend/src/plugins/Post/commands/EditCmd.ts +++ b/backend/src/plugins/Post/commands/EditCmd.ts @@ -15,18 +15,18 @@ export const EditCmd = postCmd({ async run({ message: msg, args, pluginData }) { const targetMessage = await args.message.channel.messages.fetch(args.message.messageId); if (!targetMessage) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown message"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown message"); return; } if (targetMessage.author.id !== pluginData.client.user!.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Message wasn't posted by me"); + void pluginData.state.common.sendErrorMessage(msg, "Message wasn't posted by me"); return; } targetMessage.channel.messages.edit(targetMessage.id, { content: formatContent(args.content), }); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Message edited"); + void pluginData.state.common.sendSuccessMessage(msg, "Message edited"); }, }); diff --git a/backend/src/plugins/Post/commands/EditEmbedCmd.ts b/backend/src/plugins/Post/commands/EditEmbedCmd.ts index 7d42c0408..4cdfdd8b4 100644 --- a/backend/src/plugins/Post/commands/EditEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/EditEmbedCmd.ts @@ -30,14 +30,14 @@ export const EditEmbedCmd = postCmd({ if (colorRgb) { color = rgbToInt(colorRgb); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid color specified"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid color specified"); return; } } const targetMessage = await args.message.channel.messages.fetch(args.message.messageId); if (!targetMessage) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown message"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown message"); return; } @@ -51,12 +51,12 @@ export const EditEmbedCmd = postCmd({ try { parsed = JSON.parse(content); } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Syntax error in embed JSON: ${e.message}`); + void pluginData.state.common.sendErrorMessage(msg, `Syntax error in embed JSON: ${e.message}`); return; } if (!isValidEmbed(parsed)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Embed is not valid"); + void pluginData.state.common.sendErrorMessage(msg, "Embed is not valid"); return; } @@ -69,7 +69,7 @@ export const EditEmbedCmd = postCmd({ args.message.channel.messages.edit(targetMessage.id, { embeds: [embed], }); - await pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Embed edited"); + await pluginData.state.common.sendSuccessMessage(msg, "Embed edited"); if (args.content) { const prefix = pluginData.fullConfig.prefix || "!"; diff --git a/backend/src/plugins/Post/commands/PostEmbedCmd.ts b/backend/src/plugins/Post/commands/PostEmbedCmd.ts index a78abacb6..e54649e66 100644 --- a/backend/src/plugins/Post/commands/PostEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/PostEmbedCmd.ts @@ -31,7 +31,7 @@ export const PostEmbedCmd = postCmd({ const content = args.content || args.maincontent; if (!args.title && !content) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Title or content required"); + void pluginData.state.common.sendErrorMessage(msg, "Title or content required"); return; } @@ -41,7 +41,7 @@ export const PostEmbedCmd = postCmd({ if (colorRgb) { color = rgbToInt(colorRgb); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid color specified"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid color specified"); return; } } @@ -56,12 +56,12 @@ export const PostEmbedCmd = postCmd({ try { parsed = JSON.parse(content); } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Syntax error in embed JSON: ${e.message}`); + void pluginData.state.common.sendErrorMessage(msg, `Syntax error in embed JSON: ${e.message}`); return; } if (!isValidEmbed(parsed)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Embed is not valid"); + void pluginData.state.common.sendErrorMessage(msg, "Embed is not valid"); return; } diff --git a/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts index 565135e6e..d3c6063a0 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts @@ -17,12 +17,12 @@ export const ScheduledPostsDeleteCmd = postCmd({ scheduledPosts.sort(sorter("post_at")); const post = scheduledPosts[args.num - 1]; if (!post) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Scheduled post not found"); + void pluginData.state.common.sendErrorMessage(msg, "Scheduled post not found"); return; } clearUpcomingScheduledPost(post); await pluginData.state.scheduledPosts.delete(post.id); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Scheduled post deleted!"); + void pluginData.state.common.sendSuccessMessage(msg, "Scheduled post deleted!"); }, }); diff --git a/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts index 2895cc688..a990e6d91 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts @@ -17,7 +17,7 @@ export const ScheduledPostsShowCmd = postCmd({ scheduledPosts.sort(sorter("post_at")); const post = scheduledPosts[args.num - 1]; if (!post) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Scheduled post not found"); + void pluginData.state.common.sendErrorMessage(msg, "Scheduled post not found"); return; } diff --git a/backend/src/plugins/Post/types.ts b/backend/src/plugins/Post/types.ts index e0ec7d2c9..7a0a1db79 100644 --- a/backend/src/plugins/Post/types.ts +++ b/backend/src/plugins/Post/types.ts @@ -1,8 +1,9 @@ -import { BasePluginType, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildScheduledPosts } from "../../data/GuildScheduledPosts"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zPostConfig = z.strictObject({ can_post: z.boolean(), @@ -14,6 +15,7 @@ export interface PostPluginType extends BasePluginType { savedMessages: GuildSavedMessages; scheduledPosts: GuildScheduledPosts; logs: GuildLogs; + common: pluginUtils.PluginPublicInterface; unregisterGuildEventListener: () => void; }; diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index 537d469f0..b75bcc537 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -40,15 +40,11 @@ export async function actualPostCmd( if (opts.repeat) { if (opts.repeat < MIN_REPEAT_TIME) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Minimum time for -repeat is ${humanizeDuration(MIN_REPEAT_TIME)}`); + void pluginData.state.common.sendErrorMessage(msg, `Minimum time for -repeat is ${humanizeDuration(MIN_REPEAT_TIME)}`); return; } if (opts.repeat > MAX_REPEAT_TIME) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Max time for -repeat is ${humanizeDuration(MAX_REPEAT_TIME)}`); + void pluginData.state.common.sendErrorMessage(msg, `Max time for -repeat is ${humanizeDuration(MAX_REPEAT_TIME)}`); return; } } @@ -59,7 +55,7 @@ export async function actualPostCmd( // Schedule the post to be posted later postAt = await parseScheduleTime(pluginData, msg.author.id, opts.schedule); if (!postAt) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid schedule time"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid schedule time"); return; } } else if (opts.repeat) { @@ -76,41 +72,35 @@ export async function actualPostCmd( // Invalid time if (!repeatUntil) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid time specified for -repeat-until"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid time specified for -repeat-until"); return; } if (repeatUntil.isBefore(moment.utc())) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You can't set -repeat-until in the past"); + void pluginData.state.common.sendErrorMessage(msg, "You can't set -repeat-until in the past"); return; } if (repeatUntil.isAfter(MAX_REPEAT_UNTIL)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( - msg, - "Unfortunately, -repeat-until can only be at most 100 years into the future. Maybe 99 years would be enough?", - ); + void pluginData.state.common.sendErrorMessage( + msg, + "Unfortunately, -repeat-until can only be at most 100 years into the future. Maybe 99 years would be enough?", + ); return; } } else if (opts["repeat-times"]) { repeatTimes = opts["repeat-times"]; if (repeatTimes <= 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "-repeat-times must be 1 or more"); + void pluginData.state.common.sendErrorMessage(msg, "-repeat-times must be 1 or more"); return; } } if (repeatUntil && repeatTimes) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "You can only use one of -repeat-until or -repeat-times at once"); + void pluginData.state.common.sendErrorMessage(msg, "You can only use one of -repeat-until or -repeat-times at once"); return; } if (opts.repeat && !repeatUntil && !repeatTimes) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "You must specify -repeat-until or -repeat-times for repeated messages"); + void pluginData.state.common.sendErrorMessage(msg, "You must specify -repeat-until or -repeat-times for repeated messages"); return; } @@ -125,7 +115,7 @@ export async function actualPostCmd( // Save schedule/repeat information in DB if (postAt) { if (postAt < moment.utc()) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Post can't be scheduled to be posted in the past"); + void pluginData.state.common.sendErrorMessage(msg, "Post can't be scheduled to be posted in the past"); return; } @@ -201,6 +191,6 @@ export async function actualPostCmd( } if (targetChannel.id !== msg.channel.id || opts.schedule || opts.repeat) { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, successMessage); + void pluginData.state.common.sendSuccessMessage(msg, successMessage); } } diff --git a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts index 8622b3b91..1848e6639 100644 --- a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts +++ b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts @@ -9,6 +9,7 @@ import { RefreshReactionRolesCmd } from "./commands/RefreshReactionRolesCmd"; import { AddReactionRoleEvt } from "./events/AddReactionRoleEvt"; import { MessageDeletedEvt } from "./events/MessageDeletedEvt"; import { ReactionRolesPluginType, zReactionRolesConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const MIN_AUTO_REFRESH = 1000 * 60 * 15; // 15min minimum, let's not abuse the API @@ -63,6 +64,10 @@ export const ReactionRolesPlugin = guildPlugin()({ state.pendingRefreshes = new Set(); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const config = pluginData.config.get(); if (config.button_groups) { diff --git a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts index 1dc632a7a..18baef48c 100644 --- a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts @@ -15,7 +15,7 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ async run({ message: msg, args, pluginData }) { const existingReactionRoles = pluginData.state.reactionRoles.getForMessage(args.message.messageId); if (!existingReactionRoles) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Message doesn't have reaction roles on it"); + void pluginData.state.common.sendErrorMessage(msg, "Message doesn't have reaction roles on it"); return; } @@ -26,7 +26,7 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ targetMessage = await args.message.channel.messages.fetch(args.message.messageId); } catch (err) { if (isDiscordAPIError(err) && err.code === 50001) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Missing access to the specified message"); + void pluginData.state.common.sendErrorMessage(msg, "Missing access to the specified message"); return; } @@ -35,6 +35,6 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ await targetMessage.reactions.removeAll(); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Reaction roles cleared"); + void pluginData.state.common.sendSuccessMessage(msg, "Reaction roles cleared"); }, }); diff --git a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts index 26d490393..1c166039f 100644 --- a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts @@ -34,9 +34,7 @@ export const InitReactionRolesCmd = reactionRolesCmd({ async run({ message: msg, args, pluginData }) { if (!canReadChannel(args.message.channel, msg.member)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "You can't add reaction roles to channels you can't see yourself"); + void pluginData.state.common.sendErrorMessage(msg, "You can't add reaction roles to channels you can't see yourself"); return; } @@ -45,7 +43,7 @@ export const InitReactionRolesCmd = reactionRolesCmd({ targetMessage = await args.message.channel.messages.fetch(args.message.messageId); } catch (e) { if (isDiscordAPIError(e)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Error ${e.code} while getting message: ${e.message}`); + void pluginData.state.common.sendErrorMessage(msg, `Error ${e.code} while getting message: ${e.message}`); return; } @@ -73,26 +71,22 @@ export const InitReactionRolesCmd = reactionRolesCmd({ // Verify the specified emojis and roles are valid and usable for (const pair of emojiRolePairs) { if (pair[0] === CLEAR_ROLES_EMOJI) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `The emoji for clearing roles (${CLEAR_ROLES_EMOJI}) is reserved and cannot be used`); + void pluginData.state.common.sendErrorMessage(msg, `The emoji for clearing roles (${CLEAR_ROLES_EMOJI}) is reserved and cannot be used`); return; } if (!isValidEmoji(pair[0])) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Invalid emoji: ${pair[0]}`); + void pluginData.state.common.sendErrorMessage(msg, `Invalid emoji: ${pair[0]}`); return; } if (!canUseEmoji(pluginData.client, pair[0])) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "I can only use regular emojis and custom emojis from servers I'm on"); + void pluginData.state.common.sendErrorMessage(msg, "I can only use regular emojis and custom emojis from servers I'm on"); return; } if (!pluginData.guild.roles.cache.has(pair[1] as Snowflake)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Unknown role ${pair[1]}`); + void pluginData.state.common.sendErrorMessage(msg, `Unknown role ${pair[1]}`); return; } } @@ -123,11 +117,9 @@ export const InitReactionRolesCmd = reactionRolesCmd({ ); if (errors?.length) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Errors while adding reaction roles:\n${errors.join("\n")}`); + void pluginData.state.common.sendErrorMessage(msg, `Errors while adding reaction roles:\n${errors.join("\n")}`); } else { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Reaction roles added"); + void pluginData.state.common.sendSuccessMessage(msg, "Reaction roles added"); } (await progressMessage).delete().catch(noop); diff --git a/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts index 6d26ff482..fa0e8ac16 100644 --- a/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts @@ -13,12 +13,12 @@ export const RefreshReactionRolesCmd = reactionRolesCmd({ async run({ message: msg, args, pluginData }) { if (pluginData.state.pendingRefreshes.has(`${args.message.channel.id}-${args.message.messageId}`)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Another refresh in progress"); + void pluginData.state.common.sendErrorMessage(msg, "Another refresh in progress"); return; } await refreshReactionRoles(pluginData, args.message.channel.id, args.message.messageId); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Reaction roles refreshed"); + void pluginData.state.common.sendSuccessMessage(msg, "Reaction roles refreshed"); }, }); diff --git a/backend/src/plugins/ReactionRoles/types.ts b/backend/src/plugins/ReactionRoles/types.ts index b955c6fa2..c985ece40 100644 --- a/backend/src/plugins/ReactionRoles/types.ts +++ b/backend/src/plugins/ReactionRoles/types.ts @@ -1,8 +1,9 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { Queue } from "../../Queue"; import { GuildReactionRoles } from "../../data/GuildReactionRoles"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zReactionRolesConfig = z.strictObject({ auto_refresh_interval: z.number(), @@ -37,6 +38,8 @@ export interface ReactionRolesPluginType extends BasePluginType { pendingRefreshes: Set; autoRefreshTimeout: NodeJS.Timeout; + + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/Reminders/RemindersPlugin.ts b/backend/src/plugins/Reminders/RemindersPlugin.ts index d43961c33..a60701126 100644 --- a/backend/src/plugins/Reminders/RemindersPlugin.ts +++ b/backend/src/plugins/Reminders/RemindersPlugin.ts @@ -7,6 +7,7 @@ import { RemindersCmd } from "./commands/RemindersCmd"; import { RemindersDeleteCmd } from "./commands/RemindersDeleteCmd"; import { postReminder } from "./functions/postReminder"; import { RemindersPluginType, zRemindersConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -44,6 +45,10 @@ export const RemindersPlugin = guildPlugin()({ state.unloaded = false; }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { state, guild } = pluginData; diff --git a/backend/src/plugins/Reminders/commands/RemindCmd.ts b/backend/src/plugins/Reminders/commands/RemindCmd.ts index 88ceeaa35..d0fb4eb04 100644 --- a/backend/src/plugins/Reminders/commands/RemindCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindCmd.ts @@ -38,7 +38,7 @@ export const RemindCmd = remindersCmd({ // "Delay string" i.e. e.g. "2h30m" const ms = convertDelayStringToMS(args.time); if (ms === null) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid reminder time"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid reminder time"); return; } @@ -46,7 +46,7 @@ export const RemindCmd = remindersCmd({ } if (!reminderTime.isValid() || reminderTime.isBefore(now)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid reminder time"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid reminder time"); return; } @@ -67,8 +67,6 @@ export const RemindCmd = remindersCmd({ pluginData.getPlugin(TimeAndDatePlugin).getDateFormat("pretty_datetime"), ); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `I will remind you in **${timeUntilReminder}** at **${prettyReminderTime}**`); + void pluginData.state.common.sendSuccessMessage(msg, `I will remind you in **${timeUntilReminder}** at **${prettyReminderTime}**`); }, }); diff --git a/backend/src/plugins/Reminders/commands/RemindersCmd.ts b/backend/src/plugins/Reminders/commands/RemindersCmd.ts index 7b2da1507..9f6dacc79 100644 --- a/backend/src/plugins/Reminders/commands/RemindersCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindersCmd.ts @@ -12,7 +12,7 @@ export const RemindersCmd = remindersCmd({ async run({ message: msg, pluginData }) { const reminders = await pluginData.state.reminders.getRemindersByUserId(msg.author.id); if (reminders.length === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No reminders"); + void pluginData.state.common.sendErrorMessage(msg, "No reminders"); return; } diff --git a/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts b/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts index 5566a1339..7a57f79b3 100644 --- a/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts @@ -17,7 +17,7 @@ export const RemindersDeleteCmd = remindersCmd({ reminders.sort(sorter("remind_at")); if (args.num > reminders.length || args.num <= 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown reminder"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown reminder"); return; } @@ -25,6 +25,6 @@ export const RemindersDeleteCmd = remindersCmd({ clearUpcomingReminder(toDelete); await pluginData.state.reminders.delete(toDelete.id); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Reminder deleted"); + void pluginData.state.common.sendSuccessMessage(msg, "Reminder deleted"); }, }); diff --git a/backend/src/plugins/Reminders/types.ts b/backend/src/plugins/Reminders/types.ts index 4356fac0d..ee2ad9bbf 100644 --- a/backend/src/plugins/Reminders/types.ts +++ b/backend/src/plugins/Reminders/types.ts @@ -1,6 +1,7 @@ -import { BasePluginType, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildReminders } from "../../data/GuildReminders"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zRemindersConfig = z.strictObject({ can_use: z.boolean(), @@ -12,6 +13,7 @@ export interface RemindersPluginType extends BasePluginType { state: { reminders: GuildReminders; tries: Map; + common: pluginUtils.PluginPublicInterface; unregisterGuildEventListener: () => void; diff --git a/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts b/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts index d7f9111ac..ecd25e8df 100644 --- a/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts +++ b/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts @@ -6,6 +6,7 @@ import { resetButtonsCmd } from "./commands/resetButtons"; import { onButtonInteraction } from "./events/buttonInteraction"; import { applyAllRoleButtons } from "./functions/applyAllRoleButtons"; import { RoleButtonsPluginType, zRoleButtonsConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const RoleButtonsPlugin = guildPlugin()({ name: "role_buttons", @@ -37,6 +38,10 @@ export const RoleButtonsPlugin = guildPlugin()({ pluginData.state.roleButtons = GuildRoleButtons.getGuildInstance(pluginData.guild.id); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + async afterLoad(pluginData) { await applyAllRoleButtons(pluginData); }, diff --git a/backend/src/plugins/RoleButtons/commands/resetButtons.ts b/backend/src/plugins/RoleButtons/commands/resetButtons.ts index edd99eb05..78b3e6a2a 100644 --- a/backend/src/plugins/RoleButtons/commands/resetButtons.ts +++ b/backend/src/plugins/RoleButtons/commands/resetButtons.ts @@ -16,14 +16,12 @@ export const resetButtonsCmd = guildPluginMessageCommand( async run({ pluginData, args, message }) { const config = pluginData.config.get(); if (!config.buttons[args.name]) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(message, `Can't find role buttons with the name "${args.name}"`); + void pluginData.state.common.sendErrorMessage(message, `Can't find role buttons with the name "${args.name}"`); return; } await pluginData.state.roleButtons.deleteRoleButtonItem(args.name); await applyAllRoleButtons(pluginData); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(message, "Done!"); + void pluginData.state.common.sendSuccessMessage(message, "Done!"); }, }); diff --git a/backend/src/plugins/RoleButtons/types.ts b/backend/src/plugins/RoleButtons/types.ts index 95e46f79c..42fa6bf9a 100644 --- a/backend/src/plugins/RoleButtons/types.ts +++ b/backend/src/plugins/RoleButtons/types.ts @@ -1,10 +1,11 @@ import { ButtonStyle } from "discord.js"; -import { BasePluginType } from "knub"; +import { BasePluginType, pluginUtils } from "knub"; import z from "zod"; import { GuildRoleButtons } from "../../data/GuildRoleButtons"; import { zBoundedCharacters, zBoundedRecord, zMessageContent, zSnowflake } from "../../utils"; import { TooManyComponentsError } from "./functions/TooManyComponentsError"; import { createButtonComponents } from "./functions/createButtonComponents"; +import { CommonPlugin } from "../Common/CommonPlugin"; const zRoleButtonOption = z.strictObject({ role_id: zSnowflake, @@ -109,5 +110,6 @@ export interface RoleButtonsPluginType extends BasePluginType { config: z.infer; state: { roleButtons: GuildRoleButtons; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/Roles/RolesPlugin.ts b/backend/src/plugins/Roles/RolesPlugin.ts index 89b3b6c90..4d4bdbb97 100644 --- a/backend/src/plugins/Roles/RolesPlugin.ts +++ b/backend/src/plugins/Roles/RolesPlugin.ts @@ -7,6 +7,7 @@ import { MassAddRoleCmd } from "./commands/MassAddRoleCmd"; import { MassRemoveRoleCmd } from "./commands/MassRemoveRoleCmd"; import { RemoveRoleCmd } from "./commands/RemoveRoleCmd"; import { RolesPluginType, zRolesConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -50,4 +51,8 @@ export const RolesPlugin = guildPlugin()({ state.logs = new GuildLogs(guild.id); }, + + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, }); diff --git a/backend/src/plugins/Roles/commands/AddRoleCmd.ts b/backend/src/plugins/Roles/commands/AddRoleCmd.ts index a66ff3655..66506c9e6 100644 --- a/backend/src/plugins/Roles/commands/AddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/AddRoleCmd.ts @@ -19,21 +19,19 @@ export const AddRoleCmd = rolesCmd({ async run({ message: msg, args, pluginData }) { if (!canActOn(pluginData, msg.member, args.member, true)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "Cannot add roles to this user: insufficient permissions"); + void pluginData.state.common.sendErrorMessage(msg, "Cannot add roles to this user: insufficient permissions"); return; } const roleId = await resolveRoleId(pluginData.client, pluginData.guild.id, args.role); if (!roleId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid role id"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid role id"); return; } const config = await pluginData.config.getForMessage(msg); if (!config.assignable_roles.includes(roleId)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot assign that role"); + void pluginData.state.common.sendErrorMessage(msg, "You cannot assign that role"); return; } @@ -43,12 +41,12 @@ export const AddRoleCmd = rolesCmd({ pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot assign that role"); + void pluginData.state.common.sendErrorMessage(msg, "You cannot assign that role"); return; } if (args.member.roles.cache.has(roleId)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member already has that role"); + void pluginData.state.common.sendErrorMessage(msg, "Member already has that role"); return; } @@ -60,8 +58,6 @@ export const AddRoleCmd = rolesCmd({ roles: [role], }); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `Added role **${role.name}** to ${verboseUserMention(args.member.user)}!`); + void pluginData.state.common.sendSuccessMessage(msg, `Added role **${role.name}** to ${verboseUserMention(args.member.user)}!`); }, }); diff --git a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts index 05db86ba0..991cdcdb6 100644 --- a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts @@ -30,22 +30,20 @@ export const MassAddRoleCmd = rolesCmd({ for (const member of members) { if (!canActOn(pluginData, msg.member, member, true)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "Cannot add roles to 1 or more specified members: insufficient permissions"); + void pluginData.state.common.sendErrorMessage(msg, "Cannot add roles to 1 or more specified members: insufficient permissions"); return; } } const roleId = await resolveRoleId(pluginData.client, pluginData.guild.id, args.role); if (!roleId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid role id"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid role id"); return; } const config = await pluginData.config.getForMessage(msg); if (!config.assignable_roles.includes(roleId)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot assign that role"); + void pluginData.state.common.sendErrorMessage(msg, "You cannot assign that role"); return; } @@ -54,7 +52,7 @@ export const MassAddRoleCmd = rolesCmd({ pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot assign that role"); + void pluginData.state.common.sendErrorMessage(msg, "You cannot assign that role"); return; } diff --git a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts index 06a5337d1..6ba75d530 100644 --- a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts @@ -29,22 +29,20 @@ export const MassRemoveRoleCmd = rolesCmd({ for (const member of members) { if (!canActOn(pluginData, msg.member, member, true)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "Cannot add roles to 1 or more specified members: insufficient permissions"); + void pluginData.state.common.sendErrorMessage(msg, "Cannot add roles to 1 or more specified members: insufficient permissions"); return; } } const roleId = await resolveRoleId(pluginData.client, pluginData.guild.id, args.role); if (!roleId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid role id"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid role id"); return; } const config = await pluginData.config.getForMessage(msg); if (!config.assignable_roles.includes(roleId)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot remove that role"); + void pluginData.state.common.sendErrorMessage(msg, "You cannot remove that role"); return; } @@ -53,7 +51,7 @@ export const MassRemoveRoleCmd = rolesCmd({ pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot remove that role"); + void pluginData.state.common.sendErrorMessage(msg, "You cannot remove that role"); return; } diff --git a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts index e3dc979d4..d1b8a7fa0 100644 --- a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts @@ -19,21 +19,19 @@ export const RemoveRoleCmd = rolesCmd({ async run({ message: msg, args, pluginData }) { if (!canActOn(pluginData, msg.member, args.member, true)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "Cannot remove roles from this user: insufficient permissions"); + void pluginData.state.common.sendErrorMessage(msg, "Cannot remove roles from this user: insufficient permissions"); return; } const roleId = await resolveRoleId(pluginData.client, pluginData.guild.id, args.role); if (!roleId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid role id"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid role id"); return; } const config = await pluginData.config.getForMessage(msg); if (!config.assignable_roles.includes(roleId)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot remove that role"); + void pluginData.state.common.sendErrorMessage(msg, "You cannot remove that role"); return; } @@ -43,12 +41,12 @@ export const RemoveRoleCmd = rolesCmd({ pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unknown role configured for 'roles' plugin: ${roleId}`, }); - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "You cannot remove that role"); + void pluginData.state.common.sendErrorMessage(msg, "You cannot remove that role"); return; } if (!args.member.roles.cache.has(roleId)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member doesn't have that role"); + void pluginData.state.common.sendErrorMessage(msg, "Member doesn't have that role"); return; } @@ -59,8 +57,6 @@ export const RemoveRoleCmd = rolesCmd({ roles: [role], }); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`); + void pluginData.state.common.sendSuccessMessage(msg, `Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`); }, }); diff --git a/backend/src/plugins/Roles/types.ts b/backend/src/plugins/Roles/types.ts index caf55b769..c3f48670b 100644 --- a/backend/src/plugins/Roles/types.ts +++ b/backend/src/plugins/Roles/types.ts @@ -1,6 +1,7 @@ -import { BasePluginType, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildLogs } from "../../data/GuildLogs"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zRolesConfig = z.strictObject({ can_assign: z.boolean(), @@ -12,6 +13,7 @@ export interface RolesPluginType extends BasePluginType { config: z.infer; state: { logs: GuildLogs; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts b/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts index 9a9e770c9..a6a823d87 100644 --- a/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts +++ b/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts @@ -3,6 +3,7 @@ import { RoleAddCmd } from "./commands/RoleAddCmd"; import { RoleHelpCmd } from "./commands/RoleHelpCmd"; import { RoleRemoveCmd } from "./commands/RoleRemoveCmd"; import { SelfGrantableRolesPluginType, zSelfGrantableRolesConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -27,4 +28,8 @@ export const SelfGrantableRolesPlugin = guildPlugin Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, { - users: [msg.author.id], - }); + void pluginData.state.common.sendErrorMessage(msg, `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, { + users: [msg.author.id], + }); lock.unlock(); return; } @@ -83,11 +81,9 @@ export const RoleAddCmd = selfGrantableRolesCmd({ roles: Array.from(newRoleIds) as Snowflake[], }); } catch { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `<@!${msg.author.id}> Got an error while trying to grant you the roles`, { - users: [msg.author.id], - }); + void pluginData.state.common.sendErrorMessage(msg, `<@!${msg.author.id}> Got an error while trying to grant you the roles`, { + users: [msg.author.id], + }); return; } @@ -118,7 +114,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({ messageParts.push("couldn't recognize some of the roles"); } - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `<@!${msg.author.id}> ${messageParts.join("; ")}`, { + void pluginData.state.common.sendSuccessMessage(msg, `<@!${msg.author.id}> ${messageParts.join("; ")}`, { users: [msg.author.id], }); diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts index b688203f7..11e4a4cda 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts @@ -46,34 +46,26 @@ export const RoleRemoveCmd = selfGrantableRolesCmd({ const removedRolesWord = rolesToRemove.length === 1 ? "role" : "roles"; if (rolesToRemove.length !== roleNames.length) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - msg, - `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord};` + - ` couldn't recognize the other roles you mentioned`, - { users: [msg.author.id] }, - ); + void pluginData.state.common.sendSuccessMessage( + msg, + `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord};` + + ` couldn't recognize the other roles you mentioned`, + { users: [msg.author.id] }, + ); } else { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord}`, { - users: [msg.author.id], - }); - } - } catch { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `<@!${msg.author.id}> Got an error while trying to remove the roles`, { + void pluginData.state.common.sendSuccessMessage(msg, `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord}`, { users: [msg.author.id], }); - } - } else { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, { + } + } catch { + void pluginData.state.common.sendSuccessMessage(msg, `<@!${msg.author.id}> Got an error while trying to remove the roles`, { users: [msg.author.id], }); + } + } else { + void pluginData.state.common.sendErrorMessage(msg, `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, { + users: [msg.author.id], + }); } lock.unlock(); diff --git a/backend/src/plugins/SelfGrantableRoles/types.ts b/backend/src/plugins/SelfGrantableRoles/types.ts index e3c7a7867..7d8c4a0f4 100644 --- a/backend/src/plugins/SelfGrantableRoles/types.ts +++ b/backend/src/plugins/SelfGrantableRoles/types.ts @@ -1,6 +1,7 @@ -import { BasePluginType, CooldownManager, guildPluginMessageCommand } from "knub"; +import { BasePluginType, CooldownManager, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { zBoundedCharacters, zBoundedRecord } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; const zRoleMap = z.record( zBoundedCharacters(1, 100), @@ -27,6 +28,7 @@ export interface SelfGrantableRolesPluginType extends BasePluginType { config: z.infer; state: { cooldowns: CooldownManager; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/Slowmode/SlowmodePlugin.ts b/backend/src/plugins/Slowmode/SlowmodePlugin.ts index 8e97adb14..8a0b7de98 100644 --- a/backend/src/plugins/Slowmode/SlowmodePlugin.ts +++ b/backend/src/plugins/Slowmode/SlowmodePlugin.ts @@ -12,6 +12,7 @@ import { SlowmodeSetCmd } from "./commands/SlowmodeSetCmd"; import { SlowmodePluginType, zSlowmodeConfig } from "./types"; import { clearExpiredSlowmodes } from "./util/clearExpiredSlowmodes"; import { onMessageCreate } from "./util/onMessageCreate"; +import { CommonPlugin } from "../Common/CommonPlugin"; const BOT_SLOWMODE_CLEAR_INTERVAL = 60 * SECONDS; @@ -63,6 +64,10 @@ export const SlowmodePlugin = guildPlugin()({ state.channelSlowmodeCache = new Map(); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { state } = pluginData; diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts index d3c8cedb6..29465e79c 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts @@ -22,16 +22,14 @@ export const SlowmodeClearCmd = slowmodeCmd({ async run({ message: msg, args, pluginData }) { const channelSlowmode = await pluginData.state.slowmodes.getChannelSlowmode(args.channel.id); if (!channelSlowmode) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Channel doesn't have slowmode!"); + void pluginData.state.common.sendErrorMessage(msg, "Channel doesn't have slowmode!"); return; } const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_CLEAR_PERMISSIONS); if (missingPermissions) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Unable to clear slowmode. ${missingPermissionError(missingPermissions)}`); + void pluginData.state.common.sendErrorMessage(msg, `Unable to clear slowmode. ${missingPermissionError(missingPermissions)}`); return; } @@ -39,7 +37,7 @@ export const SlowmodeClearCmd = slowmodeCmd({ if (args.channel.type === ChannelType.GuildText) { await clearBotSlowmodeFromUserId(pluginData, args.channel, args.user.id, args.force); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage( + void pluginData.state.common.sendErrorMessage( msg, asSingleLine(` Failed to clear slowmode from **${renderUsername(args.user)}** in <#${args.channel.id}>: @@ -49,7 +47,7 @@ export const SlowmodeClearCmd = slowmodeCmd({ return; } } catch (e) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage( + void pluginData.state.common.sendErrorMessage( msg, asSingleLine(` Failed to clear slowmode from **${renderUsername(args.user)}** in <#${args.channel.id}>: @@ -59,8 +57,6 @@ export const SlowmodeClearCmd = slowmodeCmd({ return; } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `Slowmode cleared from **${renderUsername(args.user)}** in <#${args.channel.id}>`); + void pluginData.state.common.sendSuccessMessage(msg, `Slowmode cleared from **${renderUsername(args.user)}** in <#${args.channel.id}>`); }, }); diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts index 0d088b43c..6f9510847 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts @@ -40,9 +40,7 @@ export const SlowmodeSetCmd = slowmodeCmd({ const channel = args.channel || msg.channel; if (!channel.isTextBased() || channel.isThread()) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, "Slowmode can only be set on non-thread text-based channels"); + void pluginData.state.common.sendErrorMessage(msg, "Slowmode can only be set on non-thread text-based channels"); return; } @@ -58,25 +56,23 @@ export const SlowmodeSetCmd = slowmodeCmd({ const mode = (args.mode as TMode) || defaultMode; if (!validModes.includes(mode)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "--mode must be 'bot' or 'native'"); + void pluginData.state.common.sendErrorMessage(msg, "--mode must be 'bot' or 'native'"); return; } // Validate durations if (mode === "native" && args.time > MAX_NATIVE_SLOWMODE) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Native slowmode can only be set to 6h or less"); + void pluginData.state.common.sendErrorMessage(msg, "Native slowmode can only be set to 6h or less"); return; } if (mode === "bot" && args.time > MAX_BOT_SLOWMODE) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Sorry, bot managed slowmodes can be at most 100 years long. Maybe 99 would be enough?`); + void pluginData.state.common.sendErrorMessage(msg, `Sorry, bot managed slowmodes can be at most 100 years long. Maybe 99 would be enough?`); return; } if (mode === "bot" && args.time < MIN_BOT_SLOWMODE) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage( + void pluginData.state.common.sendErrorMessage( msg, asSingleLine(` Bot managed slowmode must be 15min or more. @@ -95,9 +91,7 @@ export const SlowmodeSetCmd = slowmodeCmd({ NATIVE_SLOWMODE_PERMISSIONS, ); if (missingPermissions) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Unable to set native slowmode. ${missingPermissionError(missingPermissions)}`); + void pluginData.state.common.sendErrorMessage(msg, `Unable to set native slowmode. ${missingPermissionError(missingPermissions)}`); return; } } @@ -108,9 +102,7 @@ export const SlowmodeSetCmd = slowmodeCmd({ BOT_SLOWMODE_PERMISSIONS, ); if (missingPermissions) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Unable to set bot managed slowmode. ${missingPermissionError(missingPermissions)}`); + void pluginData.state.common.sendErrorMessage(msg, `Unable to set bot managed slowmode. ${missingPermissionError(missingPermissions)}`); return; } } @@ -129,9 +121,7 @@ export const SlowmodeSetCmd = slowmodeCmd({ try { await channel.setRateLimitPerUser(rateLimitSeconds); } catch (e) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Failed to set native slowmode: ${escapeInlineCode(e.message)}`); + void pluginData.state.common.sendErrorMessage(msg, `Failed to set native slowmode: ${escapeInlineCode(e.message)}`); return; } } else { @@ -150,8 +140,6 @@ export const SlowmodeSetCmd = slowmodeCmd({ const humanizedSlowmodeTime = humanizeDuration(args.time); const slowmodeType = mode === "native" ? "native slowmode" : "bot-maintained slowmode"; - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `Set ${humanizedSlowmodeTime} slowmode for <#${channel.id}> (${slowmodeType})`); + void pluginData.state.common.sendSuccessMessage(msg, `Set ${humanizedSlowmodeTime} slowmode for <#${channel.id}> (${slowmodeType})`); }, }); diff --git a/backend/src/plugins/Slowmode/types.ts b/backend/src/plugins/Slowmode/types.ts index 7c8d5b566..73bd3cb81 100644 --- a/backend/src/plugins/Slowmode/types.ts +++ b/backend/src/plugins/Slowmode/types.ts @@ -1,9 +1,10 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSlowmodes } from "../../data/GuildSlowmodes"; import { SlowmodeChannel } from "../../data/entities/SlowmodeChannel"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zSlowmodeConfig = z.strictObject({ use_native_slowmode: z.boolean(), @@ -21,6 +22,7 @@ export interface SlowmodePluginType extends BasePluginType { clearInterval: NodeJS.Timeout; serverLogs: GuildLogs; channelSlowmodeCache: Map; + common: pluginUtils.PluginPublicInterface; onMessageCreateFn; }; diff --git a/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts b/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts index 72c86a04c..da245eb48 100644 --- a/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts +++ b/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts @@ -11,16 +11,14 @@ export async function actualDisableSlowmodeCmd(msg: Message, args, pluginData) { const hasNativeSlowmode = args.channel.rateLimitPerUser; if (!botSlowmode && hasNativeSlowmode === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Channel is not on slowmode!"); + void pluginData.state.common.sendErrorMessage(msg, "Channel is not on slowmode!"); return; } const me = pluginData.guild.members.cache.get(pluginData.client.user!.id); const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_DISABLE_PERMISSIONS); if (missingPermissions) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Unable to disable slowmode. ${missingPermissionError(missingPermissions)}`); + void pluginData.state.common.sendErrorMessage(msg, `Unable to disable slowmode. ${missingPermissionError(missingPermissions)}`); return; } @@ -39,14 +37,12 @@ export async function actualDisableSlowmodeCmd(msg: Message, args, pluginData) { } if (failedUsers.length) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - msg, - `Slowmode disabled! Failed to clear slowmode from the following users:\n\n<@!${failedUsers.join(">\n<@!")}>`, - ); + void pluginData.state.common.sendSuccessMessage( + msg, + `Slowmode disabled! Failed to clear slowmode from the following users:\n\n<@!${failedUsers.join(">\n<@!")}>`, + ); } else { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Slowmode disabled!"); + void pluginData.state.common.sendSuccessMessage(msg, "Slowmode disabled!"); initMsg.delete().catch(noop); } } diff --git a/backend/src/plugins/Starboard/StarboardPlugin.ts b/backend/src/plugins/Starboard/StarboardPlugin.ts index 7e12102c0..9888b0139 100644 --- a/backend/src/plugins/Starboard/StarboardPlugin.ts +++ b/backend/src/plugins/Starboard/StarboardPlugin.ts @@ -7,6 +7,7 @@ import { StarboardReactionAddEvt } from "./events/StarboardReactionAddEvt"; import { StarboardReactionRemoveAllEvt, StarboardReactionRemoveEvt } from "./events/StarboardReactionRemoveEvts"; import { StarboardPluginType, zStarboardConfig } from "./types"; import { onMessageDelete } from "./util/onMessageDelete"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -50,6 +51,10 @@ export const StarboardPlugin = guildPlugin()({ state.starboardReactions = GuildStarboardReactions.getGuildInstance(guild.id); }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { state } = pluginData; diff --git a/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts b/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts index d074c948a..114b293f0 100644 --- a/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts +++ b/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts @@ -19,13 +19,13 @@ export const MigratePinsCmd = starboardCmd({ const config = await pluginData.config.get(); const starboard = config.boards[args.starboardName]; if (!starboard) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown starboard specified"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown starboard specified"); return; } const starboardChannel = pluginData.guild.channels.cache.get(starboard.channel_id as Snowflake); if (!starboardChannel || !(starboardChannel instanceof TextChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Starboard has an unknown/invalid channel id"); + void pluginData.state.common.sendErrorMessage(msg, "Starboard has an unknown/invalid channel id"); return; } @@ -43,8 +43,6 @@ export const MigratePinsCmd = starboardCmd({ await saveMessageToStarboard(pluginData, pin, starboard); } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `Pins migrated from <#${args.pinChannel.id}> to <#${starboardChannel.id}>!`); + void pluginData.state.common.sendSuccessMessage(msg, `Pins migrated from <#${args.pinChannel.id}> to <#${starboardChannel.id}>!`); }, }); diff --git a/backend/src/plugins/Starboard/types.ts b/backend/src/plugins/Starboard/types.ts index bb8845fe2..2e625d5a7 100644 --- a/backend/src/plugins/Starboard/types.ts +++ b/backend/src/plugins/Starboard/types.ts @@ -1,9 +1,10 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildStarboardMessages } from "../../data/GuildStarboardMessages"; import { GuildStarboardReactions } from "../../data/GuildStarboardReactions"; import { zBoundedRecord, zSnowflake } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; const zStarboardOpts = z.strictObject({ channel_id: zSnowflake, @@ -29,6 +30,7 @@ export interface StarboardPluginType extends BasePluginType { savedMessages: GuildSavedMessages; starboardMessages: GuildStarboardMessages; starboardReactions: GuildStarboardReactions; + common: pluginUtils.PluginPublicInterface; onMessageDeleteFn; }; diff --git a/backend/src/plugins/Tags/TagsPlugin.ts b/backend/src/plugins/Tags/TagsPlugin.ts index 09379d6d8..97d667c46 100644 --- a/backend/src/plugins/Tags/TagsPlugin.ts +++ b/backend/src/plugins/Tags/TagsPlugin.ts @@ -20,6 +20,7 @@ import { findTagByName } from "./util/findTagByName"; import { onMessageCreate } from "./util/onMessageCreate"; import { onMessageDelete } from "./util/onMessageDelete"; import { renderTagBody } from "./util/renderTagBody"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -91,6 +92,10 @@ export const TagsPlugin = guildPlugin()({ state.tagFunctions = {}; }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { state } = pluginData; diff --git a/backend/src/plugins/Tags/commands/TagCreateCmd.ts b/backend/src/plugins/Tags/commands/TagCreateCmd.ts index 0ee452f53..8aab96475 100644 --- a/backend/src/plugins/Tags/commands/TagCreateCmd.ts +++ b/backend/src/plugins/Tags/commands/TagCreateCmd.ts @@ -17,7 +17,7 @@ export const TagCreateCmd = tagsCmd({ parseTemplate(args.body); } catch (e) { if (e instanceof TemplateParseError) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Invalid tag syntax: ${e.message}`); + void pluginData.state.common.sendErrorMessage(msg, `Invalid tag syntax: ${e.message}`); return; } else { throw e; @@ -27,6 +27,6 @@ export const TagCreateCmd = tagsCmd({ await pluginData.state.tags.createOrUpdate(args.tag, args.body, msg.author.id); const prefix = pluginData.config.get().prefix; - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `Tag set! Use it with: \`${prefix}${args.tag}\``); + void pluginData.state.common.sendSuccessMessage(msg, `Tag set! Use it with: \`${prefix}${args.tag}\``); }, }); diff --git a/backend/src/plugins/Tags/commands/TagDeleteCmd.ts b/backend/src/plugins/Tags/commands/TagDeleteCmd.ts index 174b7b647..618b67960 100644 --- a/backend/src/plugins/Tags/commands/TagDeleteCmd.ts +++ b/backend/src/plugins/Tags/commands/TagDeleteCmd.ts @@ -13,11 +13,11 @@ export const TagDeleteCmd = tagsCmd({ async run({ message: msg, args, pluginData }) { const tag = await pluginData.state.tags.find(args.tag); if (!tag) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No tag with that name"); + void pluginData.state.common.sendErrorMessage(msg, "No tag with that name"); return; } await pluginData.state.tags.delete(args.tag); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Tag deleted!"); + void pluginData.state.common.sendSuccessMessage(msg, "Tag deleted!"); }, }); diff --git a/backend/src/plugins/Tags/commands/TagEvalCmd.ts b/backend/src/plugins/Tags/commands/TagEvalCmd.ts index 580c938bc..9cdab7749 100644 --- a/backend/src/plugins/Tags/commands/TagEvalCmd.ts +++ b/backend/src/plugins/Tags/commands/TagEvalCmd.ts @@ -29,7 +29,7 @@ export const TagEvalCmd = tagsCmd({ )) as MessageCreateOptions; if (!rendered.content && !rendered.embeds?.length) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Evaluation resulted in an empty text"); + void pluginData.state.common.sendErrorMessage(msg, "Evaluation resulted in an empty text"); return; } @@ -37,7 +37,7 @@ export const TagEvalCmd = tagsCmd({ } catch (e) { const errorMessage = e instanceof TemplateParseError ? e.message : "Internal error"; - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Failed to render tag: ${errorMessage}`); + void pluginData.state.common.sendErrorMessage(msg, `Failed to render tag: ${errorMessage}`); if (!(e instanceof TemplateParseError)) { logger.warn(`Internal error evaluating tag in ${pluginData.guild.id}: ${e}`); diff --git a/backend/src/plugins/Tags/commands/TagSourceCmd.ts b/backend/src/plugins/Tags/commands/TagSourceCmd.ts index 6690f8f75..0a16c41b4 100644 --- a/backend/src/plugins/Tags/commands/TagSourceCmd.ts +++ b/backend/src/plugins/Tags/commands/TagSourceCmd.ts @@ -18,18 +18,18 @@ export const TagSourceCmd = tagsCmd({ if (args.delete) { const actualTag = await pluginData.state.tags.find(args.tag); if (!actualTag) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No tag with that name"); + void pluginData.state.common.sendErrorMessage(msg, "No tag with that name"); return; } await pluginData.state.tags.delete(args.tag); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, "Tag deleted!"); + void pluginData.state.common.sendSuccessMessage(msg, "Tag deleted!"); return; } const tag = await pluginData.state.tags.find(args.tag); if (!tag) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No tag with that name"); + void pluginData.state.common.sendErrorMessage(msg, "No tag with that name"); return; } diff --git a/backend/src/plugins/Tags/types.ts b/backend/src/plugins/Tags/types.ts index 8b8b472b4..c750edc87 100644 --- a/backend/src/plugins/Tags/types.ts +++ b/backend/src/plugins/Tags/types.ts @@ -1,10 +1,11 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { GuildArchives } from "../../data/GuildArchives"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildTags } from "../../data/GuildTags"; import { zBoundedCharacters, zStrictMessageContent } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zTag = z.union([zBoundedCharacters(0, 4000), zStrictMessageContent]); export type TTag = z.infer; @@ -59,6 +60,7 @@ export interface TagsPluginType extends BasePluginType { tags: GuildTags; savedMessages: GuildSavedMessages; logs: GuildLogs; + common: pluginUtils.PluginPublicInterface; onMessageCreateFn; diff --git a/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts b/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts index 78dd8fa08..4c84c4ffc 100644 --- a/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts +++ b/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts @@ -11,6 +11,7 @@ import { getMemberTz } from "./functions/getMemberTz"; import { inGuildTz } from "./functions/inGuildTz"; import { inMemberTz } from "./functions/inMemberTz"; import { TimeAndDatePluginType, zTimeAndDateConfig } from "./types"; +import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -57,4 +58,8 @@ export const TimeAndDatePlugin = guildPlugin()({ state.memberTimezones = GuildMemberTimezones.getGuildInstance(guild.id); }, + + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, }); diff --git a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts index 2c39519ed..88eddd18b 100644 --- a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts @@ -11,8 +11,6 @@ export const ResetTimezoneCmd = timeAndDateCmd({ async run({ pluginData, message }) { await pluginData.state.memberTimezones.reset(message.author.id); const serverTimezone = getGuildTz(pluginData); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(message, `Your timezone has been reset to server default, **${serverTimezone}**`); + void pluginData.state.common.sendSuccessMessage(message, `Your timezone has been reset to server default, **${serverTimezone}**`); }, }); diff --git a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts index 70acf1294..684f60f76 100644 --- a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts @@ -16,7 +16,7 @@ export const SetTimezoneCmd = timeAndDateCmd({ async run({ pluginData, message, args }) { const parsedTz = parseFuzzyTimezone(args.timezone); if (!parsedTz) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage( + void pluginData.state.common.sendErrorMessage( message, trimLines(` Invalid timezone: \`${escapeInlineCode(args.timezone)}\` @@ -28,6 +28,6 @@ export const SetTimezoneCmd = timeAndDateCmd({ } await pluginData.state.memberTimezones.set(message.author.id, parsedTz); - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(message, `Your timezone is now set to **${parsedTz}**`); + void pluginData.state.common.sendSuccessMessage(message, `Your timezone is now set to **${parsedTz}**`); }, }); diff --git a/backend/src/plugins/TimeAndDate/types.ts b/backend/src/plugins/TimeAndDate/types.ts index 7a942518c..5bb5253d3 100644 --- a/backend/src/plugins/TimeAndDate/types.ts +++ b/backend/src/plugins/TimeAndDate/types.ts @@ -1,10 +1,11 @@ -import { BasePluginType, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginMessageCommand, pluginUtils } from "knub"; import { U } from "ts-toolbelt"; import z from "zod"; import { GuildMemberTimezones } from "../../data/GuildMemberTimezones"; import { keys } from "../../utils"; import { zValidTimezone } from "../../utils/zValidTimezone"; import { defaultDateFormats } from "./defaultDateFormats"; +import { CommonPlugin } from "../Common/CommonPlugin"; const zDateFormatKeys = z.enum(keys(defaultDateFormats) as U.ListOf); @@ -18,6 +19,7 @@ export interface TimeAndDatePluginType extends BasePluginType { config: z.infer; state: { memberTimezones: GuildMemberTimezones; + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 59da3e97a..bd35837eb 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -193,11 +193,15 @@ export const UtilityPlugin = guildPlugin()({ } }, + beforeStart(pluginData) { + pluginData.state.common = pluginData.getPlugin(CommonPlugin); + }, + afterLoad(pluginData) { const { guild } = pluginData; if (activeReloads.has(guild.id)) { - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(activeReloads.get(guild.id)!, "Reloaded!"); + pluginData.state.common.sendSuccessMessage(activeReloads.get(guild.id)!, "Reloaded!"); activeReloads.delete(guild.id); } }, diff --git a/backend/src/plugins/Utility/commands/AvatarCmd.ts b/backend/src/plugins/Utility/commands/AvatarCmd.ts index e06c7d895..b0b7ce7ed 100644 --- a/backend/src/plugins/Utility/commands/AvatarCmd.ts +++ b/backend/src/plugins/Utility/commands/AvatarCmd.ts @@ -24,7 +24,7 @@ export const AvatarCmd = utilityCmd({ }; msg.channel.send({ embeds: [embed] }); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid user ID"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid user ID"); } }, }); diff --git a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts index 5b4a70bbf..dc0901bc3 100644 --- a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts @@ -16,7 +16,7 @@ export const ChannelInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { const embed = await getChannelInfoEmbed(pluginData, args.channel); if (!embed) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown channel"); + void pluginData.state.common.sendErrorMessage(message, "Unknown channel"); return; } diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index 11e7bcfb3..6eac645eb 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -83,22 +83,18 @@ export interface CleanArgs { export async function cleanCmd(pluginData: GuildPluginData, args: CleanArgs | any, msg) { if (args.count > MAX_CLEAN_COUNT || args.count <= 0) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( - msg, - `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`, - undefined, - args["response-interaction"], - ); + void pluginData.state.common.sendErrorMessage( + msg, + `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`, + undefined, + args["response-interaction"], + ); return; } const targetChannel = args.channel ? pluginData.guild.channels.cache.get(args.channel as Snowflake) : msg.channel; if (!targetChannel?.isTextBased()) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Invalid channel specified`, undefined, args["response-interaction"]); + void pluginData.state.common.sendErrorMessage(msg, `Invalid channel specified`, undefined, args["response-interaction"]); return; } @@ -110,14 +106,12 @@ export async function cleanCmd(pluginData: GuildPluginData, a categoryId: targetChannel.parentId, }); if (configForTargetChannel.can_clean !== true) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( - msg, - `Missing permissions to use clean on that channel`, - undefined, - args["response-interaction"], - ); + void pluginData.state.common.sendErrorMessage( + msg, + `Missing permissions to use clean on that channel`, + undefined, + args["response-interaction"], + ); return; } } @@ -223,14 +217,10 @@ export async function cleanCmd(pluginData: GuildPluginData, a } } - responseMsg = await pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, responseText, undefined, args["response-interaction"]); + responseMsg = await pluginData.state.common.sendSuccessMessage(msg, responseText, undefined, args["response-interaction"]); } else { const responseText = `Found no messages to clean${note ? ` (${note})` : ""}!`; - responseMsg = await pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, responseText, undefined, args["response-interaction"]); + responseMsg = await pluginData.state.common.sendErrorMessage(msg, responseText, undefined, args["response-interaction"]); } cleaningMessage?.delete(); diff --git a/backend/src/plugins/Utility/commands/ContextCmd.ts b/backend/src/plugins/Utility/commands/ContextCmd.ts index eb613f57e..9db76606d 100644 --- a/backend/src/plugins/Utility/commands/ContextCmd.ts +++ b/backend/src/plugins/Utility/commands/ContextCmd.ts @@ -23,7 +23,7 @@ export const ContextCmd = utilityCmd({ async run({ message: msg, args, pluginData }) { if (args.channel && !(args.channel instanceof TextChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Channel must be a text channel"); + void pluginData.state.common.sendErrorMessage(msg, "Channel must be a text channel"); return; } @@ -31,7 +31,7 @@ export const ContextCmd = utilityCmd({ const messageId = args.messageId ?? args.message.messageId; if (!canReadChannel(channel, msg.member)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Message context not found"); + void pluginData.state.common.sendErrorMessage(msg, "Message context not found"); return; } @@ -42,7 +42,7 @@ export const ContextCmd = utilityCmd({ }) )[0]; if (!previousMessage) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Message context not found"); + void pluginData.state.common.sendErrorMessage(msg, "Message context not found"); return; } diff --git a/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts b/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts index 114652a90..e5806aec5 100644 --- a/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts @@ -17,13 +17,13 @@ export const EmojiInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { const emojiId = getCustomEmojiId(args.emoji); if (!emojiId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Emoji not found"); + void pluginData.state.common.sendErrorMessage(message, "Emoji not found"); return; } const embed = await getEmojiInfoEmbed(pluginData, emojiId); if (!embed) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Emoji not found"); + void pluginData.state.common.sendErrorMessage(message, "Emoji not found"); return; } diff --git a/backend/src/plugins/Utility/commands/InfoCmd.ts b/backend/src/plugins/Utility/commands/InfoCmd.ts index 23ffa8ac1..9314dbce1 100644 --- a/backend/src/plugins/Utility/commands/InfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InfoCmd.ts @@ -146,11 +146,9 @@ export const InfoCmd = utilityCmd({ } // 10. No can do - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( - message, - "Could not find anything with that value or you are lacking permission for the snowflake type", - ); + void pluginData.state.common.sendErrorMessage( + message, + "Could not find anything with that value or you are lacking permission for the snowflake type", + ); }, }); diff --git a/backend/src/plugins/Utility/commands/InviteInfoCmd.ts b/backend/src/plugins/Utility/commands/InviteInfoCmd.ts index 262ab94a6..bbecd7941 100644 --- a/backend/src/plugins/Utility/commands/InviteInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InviteInfoCmd.ts @@ -18,7 +18,7 @@ export const InviteInfoCmd = utilityCmd({ const inviteCode = parseInviteCodeInput(args.inviteCode); const embed = await getInviteInfoEmbed(pluginData, inviteCode); if (!embed) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown invite"); + void pluginData.state.common.sendErrorMessage(message, "Unknown invite"); return; } diff --git a/backend/src/plugins/Utility/commands/JumboCmd.ts b/backend/src/plugins/Utility/commands/JumboCmd.ts index a8cf86785..d29dc608f 100644 --- a/backend/src/plugins/Utility/commands/JumboCmd.ts +++ b/backend/src/plugins/Utility/commands/JumboCmd.ts @@ -51,7 +51,7 @@ export const JumboCmd = utilityCmd({ let file: AttachmentBuilder | undefined; if (!isEmoji(args.emoji)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Invalid emoji"); + void pluginData.state.common.sendErrorMessage(msg, "Invalid emoji"); return; } @@ -87,7 +87,7 @@ export const JumboCmd = utilityCmd({ } } if (!image) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Error occurred while jumboing default emoji"); + void pluginData.state.common.sendErrorMessage(msg, "Error occurred while jumboing default emoji"); return; } diff --git a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts index 88df8eff8..4ea7da69f 100644 --- a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts @@ -16,13 +16,13 @@ export const MessageInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { if (!canReadChannel(args.message.channel, message.member)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown message"); + void pluginData.state.common.sendErrorMessage(message, "Unknown message"); return; } const embed = await getMessageInfoEmbed(pluginData, args.message.channel.id, args.message.messageId); if (!embed) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Unknown message"); + void pluginData.state.common.sendErrorMessage(message, "Unknown message"); return; } diff --git a/backend/src/plugins/Utility/commands/NicknameCmd.ts b/backend/src/plugins/Utility/commands/NicknameCmd.ts index 63d5eac89..fd33a7556 100644 --- a/backend/src/plugins/Utility/commands/NicknameCmd.ts +++ b/backend/src/plugins/Utility/commands/NicknameCmd.ts @@ -46,11 +46,9 @@ export const NicknameCmd = utilityCmd({ return; } - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - msg, - `Changed nickname of <@!${args.member.id}> from **${oldNickname}** to **${args.nickname}**`, - ); + void pluginData.state.common.sendSuccessMessage( + msg, + `Changed nickname of <@!${args.member.id}> from **${oldNickname}** to **${args.nickname}**`, + ); }, }); diff --git a/backend/src/plugins/Utility/commands/NicknameResetCmd.ts b/backend/src/plugins/Utility/commands/NicknameResetCmd.ts index 614174199..8fd23c846 100644 --- a/backend/src/plugins/Utility/commands/NicknameResetCmd.ts +++ b/backend/src/plugins/Utility/commands/NicknameResetCmd.ts @@ -32,6 +32,6 @@ export const NicknameResetCmd = utilityCmd({ return; } - pluginData.getPlugin(CommonPlugin).sendSuccessMessage(msg, `The nickname of <@!${args.member.id}> has been reset`); + void pluginData.state.common.sendSuccessMessage(msg, `The nickname of <@!${args.member.id}> has been reset`); }, }); diff --git a/backend/src/plugins/Utility/commands/RolesCmd.ts b/backend/src/plugins/Utility/commands/RolesCmd.ts index d04903f9e..08ec5dc15 100644 --- a/backend/src/plugins/Utility/commands/RolesCmd.ts +++ b/backend/src/plugins/Utility/commands/RolesCmd.ts @@ -62,7 +62,7 @@ export const RolesCmd = utilityCmd({ } else if (sort === "name") { roles.sort(sorter((r) => r.name.toLowerCase(), sortDir)); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown sorting method"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown sorting method"); return; } diff --git a/backend/src/plugins/Utility/commands/ServerInfoCmd.ts b/backend/src/plugins/Utility/commands/ServerInfoCmd.ts index 2e5d7ba53..0388d8cda 100644 --- a/backend/src/plugins/Utility/commands/ServerInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/ServerInfoCmd.ts @@ -17,7 +17,7 @@ export const ServerInfoCmd = utilityCmd({ const serverId = args.serverId || pluginData.guild.id; const serverInfoEmbed = await getServerInfoEmbed(pluginData, serverId); if (!serverInfoEmbed) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "Could not find information for that server"); + void pluginData.state.common.sendErrorMessage(message, "Could not find information for that server"); return; } diff --git a/backend/src/plugins/Utility/commands/SourceCmd.ts b/backend/src/plugins/Utility/commands/SourceCmd.ts index be3a4bab3..5fccc1095 100644 --- a/backend/src/plugins/Utility/commands/SourceCmd.ts +++ b/backend/src/plugins/Utility/commands/SourceCmd.ts @@ -17,13 +17,13 @@ export const SourceCmd = utilityCmd({ async run({ message: cmdMessage, args, pluginData }) { if (!canReadChannel(args.message.channel, cmdMessage.member)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(cmdMessage, "Unknown message"); + void pluginData.state.common.sendErrorMessage(cmdMessage, "Unknown message"); return; } const message = await args.message.channel.messages.fetch(args.message.messageId); if (!message) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(cmdMessage, "Unknown message"); + void pluginData.state.common.sendErrorMessage(cmdMessage, "Unknown message"); return; } diff --git a/backend/src/plugins/Utility/commands/UserInfoCmd.ts b/backend/src/plugins/Utility/commands/UserInfoCmd.ts index 6ba99548b..9f2698a92 100644 --- a/backend/src/plugins/Utility/commands/UserInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/UserInfoCmd.ts @@ -19,7 +19,7 @@ export const UserInfoCmd = utilityCmd({ const userId = args.user?.id || message.author.id; const embed = await getUserInfoEmbed(pluginData, userId, args.compact); if (!embed) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(message, "User not found"); + void pluginData.state.common.sendErrorMessage(message, "User not found"); return; } diff --git a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts index 6ad01cdb1..797f5dda5 100644 --- a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts +++ b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts @@ -18,12 +18,12 @@ export const VcdisconnectCmd = utilityCmd({ async run({ message: msg, args, pluginData }) { if (!canActOn(pluginData, msg.member, args.member)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cannot move: insufficient permissions"); + void pluginData.state.common.sendErrorMessage(msg, "Cannot move: insufficient permissions"); return; } if (!args.member.voice?.channelId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member is not in a voice channel"); + void pluginData.state.common.sendErrorMessage(msg, "Member is not in a voice channel"); return; } const channel = pluginData.guild.channels.cache.get(args.member.voice.channelId) as VoiceChannel; @@ -31,7 +31,7 @@ export const VcdisconnectCmd = utilityCmd({ try { await args.member.voice.disconnect(); } catch { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Failed to disconnect member"); + void pluginData.state.common.sendErrorMessage(msg, "Failed to disconnect member"); return; } @@ -41,8 +41,9 @@ export const VcdisconnectCmd = utilityCmd({ oldChannel: channel, }); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `**${renderUsername(args.member)}** disconnected from **${channel.name}**`); + pluginData.state.common.sendSuccessMessage( + msg, + `**${renderUsername(args.member)}** disconnected from **${channel.name}**` + ); }, }); diff --git a/backend/src/plugins/Utility/commands/VcmoveCmd.ts b/backend/src/plugins/Utility/commands/VcmoveCmd.ts index abf47bbb3..eee432c7f 100644 --- a/backend/src/plugins/Utility/commands/VcmoveCmd.ts +++ b/backend/src/plugins/Utility/commands/VcmoveCmd.ts @@ -24,7 +24,7 @@ export const VcmoveCmd = utilityCmd({ // Snowflake -> resolve channel directly const potentialChannel = pluginData.guild.channels.cache.get(args.channel as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown or non-voice channel"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown or non-voice channel"); return; } @@ -34,7 +34,7 @@ export const VcmoveCmd = utilityCmd({ const channelId = args.channel.match(channelMentionRegex)![1]; const potentialChannel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown or non-voice channel"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown or non-voice channel"); return; } @@ -46,7 +46,7 @@ export const VcmoveCmd = utilityCmd({ ); const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, (ch) => ch.name); if (!closestMatch) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No matching voice channels"); + void pluginData.state.common.sendErrorMessage(msg, "No matching voice channels"); return; } @@ -54,12 +54,12 @@ export const VcmoveCmd = utilityCmd({ } if (!args.member.voice?.channelId) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member is not in a voice channel"); + void pluginData.state.common.sendErrorMessage(msg, "Member is not in a voice channel"); return; } if (args.member.voice.channelId === channel.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Member is already on that channel!"); + void pluginData.state.common.sendErrorMessage(msg, "Member is already on that channel!"); return; } @@ -70,7 +70,7 @@ export const VcmoveCmd = utilityCmd({ channel: channel.id, }); } catch { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Failed to move member"); + void pluginData.state.common.sendErrorMessage(msg, "Failed to move member"); return; } @@ -81,9 +81,7 @@ export const VcmoveCmd = utilityCmd({ newChannel: channel, }); - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage(msg, `**${renderUsername(args.member)}** moved to **${channel.name}**`); + void pluginData.state.common.sendSuccessMessage(msg, `**${renderUsername(args.member)}** moved to **${channel.name}**`); }, }); @@ -105,7 +103,7 @@ export const VcmoveAllCmd = utilityCmd({ // Snowflake -> resolve channel directly const potentialChannel = pluginData.guild.channels.cache.get(args.channel as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown or non-voice channel"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown or non-voice channel"); return; } @@ -115,7 +113,7 @@ export const VcmoveAllCmd = utilityCmd({ const channelId = args.channel.match(channelMentionRegex)![1]; const potentialChannel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown or non-voice channel"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown or non-voice channel"); return; } @@ -127,7 +125,7 @@ export const VcmoveAllCmd = utilityCmd({ ); const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, (ch) => ch.name); if (!closestMatch) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No matching voice channels"); + void pluginData.state.common.sendErrorMessage(msg, "No matching voice channels"); return; } @@ -135,12 +133,12 @@ export const VcmoveAllCmd = utilityCmd({ } if (args.oldChannel.members.size === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Voice channel is empty"); + void pluginData.state.common.sendErrorMessage(msg, "Voice channel is empty"); return; } if (args.oldChannel.id === channel.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Cant move from and to the same channel!"); + void pluginData.state.common.sendErrorMessage(msg, "Cant move from and to the same channel!"); return; } @@ -153,12 +151,10 @@ export const VcmoveAllCmd = utilityCmd({ // Check for permissions but allow self-moves if (currMember.id !== msg.member.id && !canActOn(pluginData, msg.member, currMember)) { - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage( - msg, - `Failed to move ${renderUsername(currMember)} (${currMember.id}): You cannot act on this member`, - ); + void pluginData.state.common.sendErrorMessage( + msg, + `Failed to move ${renderUsername(currMember)} (${currMember.id}): You cannot act on this member`, + ); errAmt++; continue; } @@ -169,12 +165,10 @@ export const VcmoveAllCmd = utilityCmd({ }); } catch { if (msg.member.id === currMember.id) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "Unknown error when trying to move members"); + void pluginData.state.common.sendErrorMessage(msg, "Unknown error when trying to move members"); return; } - pluginData - .getPlugin(CommonPlugin) - .sendErrorMessage(msg, `Failed to move ${renderUsername(currMember)} (${currMember.id})`); + void pluginData.state.common.sendErrorMessage(msg, `Failed to move ${renderUsername(currMember)} (${currMember.id})`); errAmt++; continue; } @@ -188,14 +182,12 @@ export const VcmoveAllCmd = utilityCmd({ } if (moveAmt !== errAmt) { - pluginData - .getPlugin(CommonPlugin) - .sendSuccessMessage( - msg, - `${moveAmt - errAmt} members from **${args.oldChannel.name}** moved to **${channel.name}**`, - ); + void pluginData.state.common.sendSuccessMessage( + msg, + `${moveAmt - errAmt} members from **${args.oldChannel.name}** moved to **${channel.name}**`, + ); } else { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, `Failed to move any members.`); + void pluginData.state.common.sendErrorMessage(msg, `Failed to move any members.`); } }, }); diff --git a/backend/src/plugins/Utility/search.ts b/backend/src/plugins/Utility/search.ts index 191ea1ca4..4b50ab1fd 100644 --- a/backend/src/plugins/Utility/search.ts +++ b/backend/src/plugins/Utility/search.ts @@ -25,7 +25,6 @@ import { } from "../../utils"; import { asyncFilter } from "../../utils/async"; import { hasDiscordPermissions } from "../../utils/hasDiscordPermissions"; -import { CommonPlugin } from "../Common/CommonPlugin"; import { banSearchSignature } from "./commands/BanSearchCmd"; import { searchCmdSignature } from "./commands/SearchCmd"; import { getUserInfoEmbed } from "./functions/getUserInfoEmbed"; @@ -123,12 +122,12 @@ export async function displaySearch( } } catch (e) { if (e instanceof SearchError) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + void pluginData.state.common.sendErrorMessage(msg, e.message); return; } if (e instanceof InvalidRegexError) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + void pluginData.state.common.sendErrorMessage(msg, e.message); return; } @@ -136,7 +135,7 @@ export async function displaySearch( } if (searchResult.totalResults === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No results found"); + void pluginData.state.common.sendErrorMessage(msg, "No results found"); return; } @@ -267,12 +266,12 @@ export async function archiveSearch( } } catch (e) { if (e instanceof SearchError) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + void pluginData.state.common.sendErrorMessage(msg, e.message); return; } if (e instanceof InvalidRegexError) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, e.message); + void pluginData.state.common.sendErrorMessage(msg, e.message); return; } @@ -280,7 +279,7 @@ export async function archiveSearch( } if (results.totalResults === 0) { - pluginData.getPlugin(CommonPlugin).sendErrorMessage(msg, "No results found"); + void pluginData.state.common.sendErrorMessage(msg, "No results found"); return; } diff --git a/backend/src/plugins/Utility/types.ts b/backend/src/plugins/Utility/types.ts index 77b3d8acd..0bcb56dd7 100644 --- a/backend/src/plugins/Utility/types.ts +++ b/backend/src/plugins/Utility/types.ts @@ -1,4 +1,4 @@ -import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; +import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub"; import z from "zod"; import { RegExpRunner } from "../../RegExpRunner"; import { GuildArchives } from "../../data/GuildArchives"; @@ -6,6 +6,7 @@ import { GuildCases } from "../../data/GuildCases"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { Supporters } from "../../data/Supporters"; +import { CommonPlugin } from "../Common/CommonPlugin"; export const zUtilityConfig = z.strictObject({ can_roles: z.boolean(), @@ -48,6 +49,8 @@ export interface UtilityPluginType extends BasePluginType { regexRunner: RegExpRunner; lastReload: number; + + common: pluginUtils.PluginPublicInterface; }; } diff --git a/backend/src/utils/loadYamlSafely.ts b/backend/src/utils/loadYamlSafely.ts index 8cc0d90c3..6960a0bf6 100644 --- a/backend/src/utils/loadYamlSafely.ts +++ b/backend/src/utils/loadYamlSafely.ts @@ -5,7 +5,7 @@ import { validateNoObjectAliases } from "./validateNoObjectAliases"; * Loads a YAML file safely while removing object anchors/aliases (including arrays) */ export function loadYamlSafely(yamlStr: string): any { - let loaded = yaml.safeLoad(yamlStr); + let loaded = yaml.load(yamlStr); if (loaded == null || typeof loaded !== "object") { loaded = {}; } diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index 59054d727..1486bef32 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -34,13 +34,11 @@ export async function waitForButtonConfirm( .setCustomId(`cancelButton:${idMod}:${uuidv4()}`), ]); const sendMethod = () => { - return contextIsInteraction - ? context.replied - ? context.editReply.bind(context) - : context.reply.bind(context) - : "send" in context - ? context.send.bind(context) - : context.channel.send.bind(context.channel); + if (contextIsInteraction) { + return context.replied ? context.editReply.bind(context) : context.reply.bind(context); + } else { + return "send" in context ? context.send.bind(context) : context.channel.send.bind(context.channel); + } }; const extraParameters = contextIsInteraction ? { fetchReply: true, ephemeral: true } : {}; const message = (await sendMethod()({ ...toPost, components: [row], ...extraParameters })) as Message; From 1f0c7a4349c881a235729b918d1d1098a10a07e6 Mon Sep 17 00:00:00 2001 From: Lily Bergonzat Date: Sun, 12 May 2024 21:34:17 +0200 Subject: [PATCH 29/29] Ran prettier to fix style issues --- .../AutoReactions/AutoReactionsPlugin.ts | 4 +-- .../commands/DisableAutoReactionsCmd.ts | 1 - .../commands/NewAutoReactionsCmd.ts | 6 ++-- backend/src/plugins/Automod/AutomodPlugin.ts | 2 +- backend/src/plugins/Automod/types.ts | 2 +- .../commands/AddDashboardUserCmd.ts | 1 - .../commands/AddServerFromInviteCmd.ts | 1 - .../BotControl/commands/AllowServerCmd.ts | 1 - .../BotControl/commands/ChannelToServerCmd.ts | 1 - .../BotControl/commands/DisallowServerCmd.ts | 1 - .../BotControl/commands/EligibleCmd.ts | 1 - .../BotControl/commands/LeaveServerCmd.ts | 1 - .../commands/ListDashboardPermsCmd.ts | 1 - .../commands/ListDashboardUsersCmd.ts | 1 - .../commands/RateLimitPerformanceCmd.ts | 1 - .../commands/ReloadGlobalPluginsCmd.ts | 1 - .../BotControl/commands/ReloadServerCmd.ts | 1 - .../commands/RemoveDashboardUserCmd.ts | 5 +-- backend/src/plugins/BotControl/types.ts | 2 +- .../ChannelArchiver/ChannelArchiverPlugin.ts | 4 +-- .../commands/ArchiveChannelCmd.ts | 1 - backend/src/plugins/ChannelArchiver/types.ts | 2 +- backend/src/plugins/Common/CommonPlugin.ts | 4 +-- .../src/plugins/ContextMenus/actions/ban.ts | 4 +-- .../src/plugins/ContextMenus/actions/note.ts | 4 +-- .../src/plugins/ContextMenus/actions/warn.ts | 4 +-- .../commands/ModMenuUserCtxCmd.ts | 3 +- .../src/plugins/Counters/CountersPlugin.ts | 2 +- .../Counters/commands/AddCounterCmd.ts | 1 - .../Counters/commands/CountersListCmd.ts | 1 - .../commands/ResetAllCounterValuesCmd.ts | 11 +++++-- .../Counters/commands/ResetCounterCmd.ts | 1 - .../Counters/commands/SetCounterCmd.ts | 1 - .../Counters/commands/ViewCounterCmd.ts | 1 - backend/src/plugins/Counters/types.ts | 2 +- .../CustomEvents/CustomEventsPlugin.ts | 2 +- .../CustomEvents/functions/runEvent.ts | 1 - backend/src/plugins/CustomEvents/types.ts | 2 +- .../plugins/LocateUser/LocateUserPlugin.ts | 2 +- .../plugins/LocateUser/commands/FollowCmd.ts | 19 +++++------- .../LocateUser/commands/ListFollowCmd.ts | 1 - .../plugins/LocateUser/utils/moveMember.ts | 1 - .../src/plugins/LocateUser/utils/sendWhere.ts | 1 - .../MessageSaver/MessageSaverPlugin.ts | 2 +- .../MessageSaver/commands/SaveMessagesToDB.ts | 7 ++--- .../MessageSaver/commands/SavePinsToDB.ts | 7 ++--- .../plugins/ModActions/ModActionsPlugin.ts | 2 +- .../commands/addcase/AddCaseMsgCmd.ts | 2 +- .../commands/addcase/AddCaseSlashCmd.ts | 7 ++--- .../commands/addcase/actualAddCaseCmd.ts | 9 ++---- .../ModActions/commands/ban/BanMsgCmd.ts | 2 +- .../ModActions/commands/ban/BanSlashCmd.ts | 15 ++------- .../ModActions/commands/ban/actualBanCmd.ts | 20 ++++++------ .../ModActions/commands/case/CaseMsgCmd.ts | 2 +- .../ModActions/commands/case/CaseSlashCmd.ts | 2 +- .../commands/cases/CasesModMsgCmd.ts | 2 +- .../commands/cases/CasesSlashCmd.ts | 2 +- .../commands/cases/CasesUserMsgCmd.ts | 2 +- .../commands/cases/actualCasesCmd.ts | 8 +---- .../commands/deletecase/DeleteCaseMsgCmd.ts | 2 +- .../commands/deletecase/DeleteCaseSlashCmd.ts | 2 +- .../deletecase/actualDeleteCaseCmd.ts | 7 ++--- .../commands/forceban/ForceBanMsgCmd.ts | 2 +- .../commands/forceban/ForceBanSlashCmd.ts | 15 ++------- .../commands/forceban/actualForceBanCmd.ts | 7 +++-- .../commands/forcemute/ForceMuteMsgCmd.ts | 2 +- .../commands/forcemute/ForceMuteSlashCmd.ts | 15 ++------- .../commands/forceunmute/ForceUnmuteMsgCmd.ts | 2 +- .../forceunmute/ForceUnmuteSlashCmd.ts | 15 ++------- .../commands/hidecase/HideCaseMsgCmd.ts | 2 +- .../commands/hidecase/HideCaseSlashCmd.ts | 2 +- .../commands/hidecase/actualHideCaseCmd.ts | 6 ++-- .../ModActions/commands/kick/KickMsgCmd.ts | 2 +- .../ModActions/commands/kick/KickSlashCmd.ts | 15 ++------- .../ModActions/commands/kick/actualKickCmd.ts | 11 ++++--- .../commands/massban/MassBanMsgCmd.ts | 2 +- .../commands/massban/MassBanSlashCmd.ts | 10 ++---- .../commands/massban/actualMassBanCmd.ts | 19 ++++++------ .../commands/massmute/MassMuteMsgCmd.ts | 2 +- .../commands/massmute/MassMuteSlashCmd.ts | 10 ++---- .../commands/massmute/actualMassMuteCmd.ts | 18 +++++------ .../commands/massunban/MassUnbanMsgCmd.ts | 2 +- .../commands/massunban/MassUnbanSlashCmd.ts | 10 ++---- .../commands/massunban/actualMassUnbanCmd.ts | 13 +++----- .../ModActions/commands/mute/MuteMsgCmd.ts | 4 +-- .../ModActions/commands/mute/MuteSlashCmd.ts | 14 +++------ .../ModActions/commands/mute/actualMuteCmd.ts | 12 +++---- .../ModActions/commands/note/NoteMsgCmd.ts | 2 +- .../ModActions/commands/note/NoteSlashCmd.ts | 10 ++---- .../ModActions/commands/note/actualNoteCmd.ts | 14 ++++----- .../ModActions/commands/unban/UnbanMsgCmd.ts | 2 +- .../commands/unban/UnbanSlashCmd.ts | 15 ++------- .../commands/unban/actualUnbanCmd.ts | 7 ++--- .../commands/unhidecase/UnhideCaseMsgCmd.ts | 2 +- .../commands/unhidecase/UnhideCaseSlashCmd.ts | 2 +- .../unhidecase/actualUnhideCaseCmd.ts | 2 +- .../commands/unmute/UnmuteMsgCmd.ts | 4 +-- .../commands/unmute/UnmuteSlashCmd.ts | 22 +++---------- .../commands/unmute/actualUnmuteCmd.ts | 2 +- .../ModActions/commands/warn/WarnMsgCmd.ts | 2 +- .../ModActions/commands/warn/WarnSlashCmd.ts | 6 ++-- .../ModActions/commands/warn/actualWarnCmd.ts | 13 +++++--- backend/src/plugins/ModActions/types.ts | 5 +-- backend/src/plugins/Mutes/MutesPlugin.ts | 2 +- .../Mutes/commands/ClearBannedMutesCmd.ts | 1 - .../plugins/Mutes/commands/ClearMutesCmd.ts | 12 ++++--- .../commands/ClearMutesWithoutRoleCmd.ts | 6 ++-- .../plugins/NameHistory/NameHistoryPlugin.ts | 2 +- .../plugins/NameHistory/commands/NamesCmd.ts | 1 - .../PingableRoles/PingableRolesPlugin.ts | 2 +- .../commands/PingableRoleDisableCmd.ts | 11 +++++-- .../commands/PingableRoleEnableCmd.ts | 11 +++++-- backend/src/plugins/Post/PostPlugin.ts | 2 +- backend/src/plugins/Post/commands/EditCmd.ts | 1 - .../src/plugins/Post/commands/EditEmbedCmd.ts | 1 - .../src/plugins/Post/commands/PostEmbedCmd.ts | 1 - .../Post/commands/ScheduledPostsDeleteCmd.ts | 1 - .../Post/commands/ScheduledPostsShowCmd.ts | 1 - .../src/plugins/Post/util/actualPostCmd.ts | 21 ++++++++++--- .../ReactionRoles/ReactionRolesPlugin.ts | 2 +- .../commands/ClearReactionRolesCmd.ts | 1 - .../commands/InitReactionRolesCmd.ts | 16 +++++++--- .../commands/RefreshReactionRolesCmd.ts | 1 - .../src/plugins/Reminders/RemindersPlugin.ts | 2 +- .../plugins/Reminders/commands/RemindCmd.ts | 6 ++-- .../Reminders/commands/RemindersCmd.ts | 1 - .../Reminders/commands/RemindersDeleteCmd.ts | 1 - .../plugins/RoleButtons/RoleButtonsPlugin.ts | 2 +- .../RoleButtons/commands/resetButtons.ts | 1 - backend/src/plugins/RoleButtons/types.ts | 2 +- backend/src/plugins/Roles/RolesPlugin.ts | 2 +- .../src/plugins/Roles/commands/AddRoleCmd.ts | 6 ++-- .../plugins/Roles/commands/MassAddRoleCmd.ts | 6 ++-- .../Roles/commands/MassRemoveRoleCmd.ts | 6 ++-- .../plugins/Roles/commands/RemoveRoleCmd.ts | 11 +++++-- .../SelfGrantableRolesPlugin.ts | 2 +- .../SelfGrantableRoles/commands/RoleAddCmd.ts | 21 ++++++++----- .../commands/RoleRemoveCmd.ts | 31 +++++++++++++------ .../src/plugins/Slowmode/SlowmodePlugin.ts | 2 +- .../Slowmode/commands/SlowmodeClearCmd.ts | 11 +++++-- .../Slowmode/commands/SlowmodeSetCmd.ts | 26 ++++++++++++---- .../Slowmode/util/actualDisableSlowmodeCmd.ts | 6 ++-- .../src/plugins/Starboard/StarboardPlugin.ts | 2 +- .../Starboard/commands/MigratePinsCmd.ts | 6 ++-- backend/src/plugins/Tags/TagsPlugin.ts | 2 +- .../src/plugins/Tags/commands/TagCreateCmd.ts | 1 - .../src/plugins/Tags/commands/TagDeleteCmd.ts | 1 - .../src/plugins/Tags/commands/TagEvalCmd.ts | 1 - .../src/plugins/Tags/commands/TagSourceCmd.ts | 1 - .../plugins/TimeAndDate/TimeAndDatePlugin.ts | 2 +- .../TimeAndDate/commands/ResetTimezoneCmd.ts | 6 ++-- .../TimeAndDate/commands/SetTimezoneCmd.ts | 1 - backend/src/plugins/TimeAndDate/types.ts | 2 +- .../src/plugins/Utility/commands/AvatarCmd.ts | 1 - .../Utility/commands/ChannelInfoCmd.ts | 1 - .../src/plugins/Utility/commands/CleanCmd.ts | 22 ++++++++++--- .../plugins/Utility/commands/ContextCmd.ts | 1 - .../plugins/Utility/commands/EmojiInfoCmd.ts | 1 - .../src/plugins/Utility/commands/InfoCmd.ts | 1 - .../plugins/Utility/commands/InviteInfoCmd.ts | 1 - .../src/plugins/Utility/commands/JumboCmd.ts | 1 - .../Utility/commands/MessageInfoCmd.ts | 1 - .../plugins/Utility/commands/NicknameCmd.ts | 1 - .../Utility/commands/NicknameResetCmd.ts | 1 - .../src/plugins/Utility/commands/RolesCmd.ts | 1 - .../plugins/Utility/commands/ServerInfoCmd.ts | 1 - .../src/plugins/Utility/commands/SourceCmd.ts | 1 - .../plugins/Utility/commands/UserInfoCmd.ts | 1 - .../Utility/commands/VcdisconnectCmd.ts | 3 +- .../src/plugins/Utility/commands/VcmoveCmd.ts | 11 +++++-- 170 files changed, 396 insertions(+), 453 deletions(-) diff --git a/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts b/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts index df5ebd3a0..bd901d495 100644 --- a/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts +++ b/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts @@ -1,12 +1,12 @@ import { PluginOptions, guildPlugin } from "knub"; import { GuildAutoReactions } from "../../data/GuildAutoReactions"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd"; import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd"; import { AddReactionsEvt } from "./events/AddReactionsEvt"; import { AutoReactionsPluginType, zAutoReactionsConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { @@ -54,5 +54,5 @@ export const AutoReactionsPlugin = guildPlugin()({ beforeStart(pluginData) { pluginData.state.common = pluginData.getPlugin(CommonPlugin); - } + }, }); diff --git a/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts b/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts index f65391741..9d57a0dda 100644 --- a/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts +++ b/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { autoReactionsCmd } from "../types"; export const DisableAutoReactionsCmd = autoReactionsCmd({ diff --git a/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts b/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts index 7f3355fb3..420948cc5 100644 --- a/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts +++ b/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts @@ -4,7 +4,6 @@ import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; import { readChannelPermissions } from "../../../utils/readChannelPermissions"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { autoReactionsCmd } from "../types"; const requiredPermissions = readChannelPermissions | PermissionsBitField.Flags.AddReactions; @@ -44,7 +43,10 @@ export const NewAutoReactionsCmd = autoReactionsCmd({ if (customEmojiMatch) { // Custom emoji if (!canUseEmoji(pluginData.client, customEmojiMatch[2])) { - pluginData.state.common.sendErrorMessage(msg, "I can only use regular emojis and custom emojis from this server"); + pluginData.state.common.sendErrorMessage( + msg, + "I can only use regular emojis and custom emojis from this server", + ); return; } diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts index 5da233a1b..abe0990ce 100644 --- a/backend/src/plugins/Automod/AutomodPlugin.ts +++ b/backend/src/plugins/Automod/AutomodPlugin.ts @@ -8,6 +8,7 @@ import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; import { MINUTES, SECONDS } from "../../utils"; import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap"; import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { CountersPlugin } from "../Counters/CountersPlugin"; import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; @@ -32,7 +33,6 @@ import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChang import { clearOldRecentActions } from "./functions/clearOldRecentActions"; import { clearOldRecentSpam } from "./functions/clearOldRecentSpam"; import { AutomodPluginType, zAutomodConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions = { config: { diff --git a/backend/src/plugins/Automod/types.ts b/backend/src/plugins/Automod/types.ts index 75cd8daac..5028a3776 100644 --- a/backend/src/plugins/Automod/types.ts +++ b/backend/src/plugins/Automod/types.ts @@ -9,6 +9,7 @@ import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { SavedMessage } from "../../data/entities/SavedMessage"; import { entries, zBoundedRecord, zDelayString } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { CounterEvents } from "../Counters/types"; import { ModActionType, ModActionsEvents } from "../ModActions/types"; import { MutesEvents } from "../Mutes/types"; @@ -17,7 +18,6 @@ import { RecentActionType } from "./constants"; import { availableTriggers } from "./triggers/availableTriggers"; import Timeout = NodeJS.Timeout; -import { CommonPlugin } from "../Common/CommonPlugin"; export type ZTriggersMapHelper = { [TriggerName in keyof typeof availableTriggers]: (typeof availableTriggers)[TriggerName]["configSchema"]; diff --git a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts index 8e97cf56a..abe735957 100644 --- a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts @@ -2,7 +2,6 @@ import { ApiPermissions } from "@zeppelinbot/shared"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isStaffPreFilter } from "../../../pluginUtils"; import { renderUsername } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const AddDashboardUserCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts index e5245bb3c..3dfdaf812 100644 --- a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts @@ -2,7 +2,6 @@ import { ApiPermissions } from "@zeppelinbot/shared"; import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { DBDateFormat, isGuildInvite, resolveInvite } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { isEligible } from "../functions/isEligible"; import { botControlCmd } from "../types"; diff --git a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts index 9d0a6cea4..9580e7e2a 100644 --- a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts @@ -3,7 +3,6 @@ import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isStaffPreFilter } from "../../../pluginUtils"; import { DBDateFormat, isSnowflake } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const AllowServerCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts b/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts index f19a41c97..f2a622132 100644 --- a/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/ChannelToServerCmd.ts @@ -1,6 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isStaffPreFilter } from "../../../pluginUtils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const ChannelToServerCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts index 8a31ddf53..430333410 100644 --- a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts @@ -2,7 +2,6 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isStaffPreFilter } from "../../../pluginUtils"; import { noop } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const DisallowServerCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/EligibleCmd.ts b/backend/src/plugins/BotControl/commands/EligibleCmd.ts index 60adcea23..f934037f6 100644 --- a/backend/src/plugins/BotControl/commands/EligibleCmd.ts +++ b/backend/src/plugins/BotControl/commands/EligibleCmd.ts @@ -1,6 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isGuildInvite, resolveInvite } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { isEligible } from "../functions/isEligible"; import { botControlCmd } from "../types"; diff --git a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts index c22a885f9..652c049d0 100644 --- a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts @@ -1,7 +1,6 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isStaffPreFilter } from "../../../pluginUtils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const LeaveServerCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts index 83f0cc375..ea237ef29 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts @@ -2,7 +2,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { AllowedGuild } from "../../../data/entities/AllowedGuild"; import { ApiPermissionAssignment } from "../../../data/entities/ApiPermissionAssignment"; import { renderUsername, resolveUser } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const ListDashboardPermsCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts index 2892e6e65..34c98dd2e 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts @@ -1,6 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { renderUsername, resolveUser } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const ListDashboardUsersCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts b/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts index 9ba9d23c5..6114c681d 100644 --- a/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts +++ b/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts @@ -2,7 +2,6 @@ import moment from "moment-timezone"; import { GuildArchives } from "../../../data/GuildArchives"; import { getBaseUrl } from "../../../pluginUtils"; import { getRateLimitStats } from "../../../rateLimitStats"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const RateLimitPerformanceCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts index 957fca95d..dc13a45ea 100644 --- a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts @@ -1,5 +1,4 @@ import { isStaffPreFilter } from "../../../pluginUtils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getActiveReload, setActiveReload } from "../activeReload"; import { botControlCmd } from "../types"; diff --git a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts index a171af5f1..0ea4f59d5 100644 --- a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts @@ -1,7 +1,6 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isStaffPreFilter } from "../../../pluginUtils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const ReloadServerCmd = botControlCmd({ diff --git a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts index 502570a7b..4c0ed1d82 100644 --- a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts @@ -1,7 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isStaffPreFilter } from "../../../pluginUtils"; import { renderUsername } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { botControlCmd } from "../types"; export const RemoveDashboardUserCmd = botControlCmd({ @@ -37,8 +36,6 @@ export const RemoveDashboardUserCmd = botControlCmd({ const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`)`); - msg.channel.send( - `The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`, - ); + msg.channel.send(`The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`); }, }); diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts index 592189c16..1c1ccea64 100644 --- a/backend/src/plugins/BotControl/types.ts +++ b/backend/src/plugins/BotControl/types.ts @@ -1,4 +1,4 @@ -import { BasePluginType, globalPluginEventListener, globalPluginMessageCommand, pluginUtils } from "knub"; +import { BasePluginType, globalPluginEventListener, globalPluginMessageCommand } from "knub"; import z from "zod"; import { AllowedGuilds } from "../../data/AllowedGuilds"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; diff --git a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts index b9c310d74..79937321c 100644 --- a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts +++ b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts @@ -1,9 +1,9 @@ import { guildPlugin } from "knub"; import z from "zod"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd"; import { ChannelArchiverPluginType } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; export const ChannelArchiverPlugin = guildPlugin()({ name: "channel_archiver", @@ -18,5 +18,5 @@ export const ChannelArchiverPlugin = guildPlugin()({ beforeStart(pluginData) { pluginData.state.common = pluginData.getPlugin(CommonPlugin); - } + }, }); diff --git a/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts b/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts index 34bdeef74..5e0235a44 100644 --- a/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts +++ b/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts @@ -3,7 +3,6 @@ import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isOwner } from "../../../pluginUtils"; import { SECONDS, confirm, noop, renderUsername } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { rehostAttachment } from "../rehostAttachment"; import { channelArchiverCmd } from "../types"; diff --git a/backend/src/plugins/ChannelArchiver/types.ts b/backend/src/plugins/ChannelArchiver/types.ts index a560af398..5dffc8d7e 100644 --- a/backend/src/plugins/ChannelArchiver/types.ts +++ b/backend/src/plugins/ChannelArchiver/types.ts @@ -4,7 +4,7 @@ import { CommonPlugin } from "../Common/CommonPlugin"; export interface ChannelArchiverPluginType extends BasePluginType { state: { common: pluginUtils.PluginPublicInterface; - } + }; } export const channelArchiverCmd = guildPluginMessageCommand(); diff --git a/backend/src/plugins/Common/CommonPlugin.ts b/backend/src/plugins/Common/CommonPlugin.ts index 144c355cd..6482272e7 100644 --- a/backend/src/plugins/Common/CommonPlugin.ts +++ b/backend/src/plugins/Common/CommonPlugin.ts @@ -139,7 +139,7 @@ export const CommonPlugin = guildPlugin()({ if (!channel) { throw new Error( - 'Cannot store attachments: no attachment storing channel configured, and no backup channel passed' + "Cannot store attachments: no attachment storing channel configured, and no backup channel passed", ); } @@ -148,6 +148,6 @@ export const CommonPlugin = guildPlugin()({ files: attachments.map((a) => a.url), }); }, - } + }; }, }); diff --git a/backend/src/plugins/ContextMenus/actions/ban.ts b/backend/src/plugins/ContextMenus/actions/ban.ts index 9f7114c64..ab5951b50 100644 --- a/backend/src/plugins/ContextMenus/actions/ban.ts +++ b/backend/src/plugins/ContextMenus/actions/ban.ts @@ -9,11 +9,11 @@ import { } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { canActOn } from "../../../pluginUtils"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { logger } from "../../../logger"; +import { canActOn } from "../../../pluginUtils"; import { convertDelayStringToMS, renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType, ModMenuActionType } from "../types"; import { updateAction } from "./update"; diff --git a/backend/src/plugins/ContextMenus/actions/note.ts b/backend/src/plugins/ContextMenus/actions/note.ts index bbdc6a8ea..1065639db 100644 --- a/backend/src/plugins/ContextMenus/actions/note.ts +++ b/backend/src/plugins/ContextMenus/actions/note.ts @@ -8,13 +8,13 @@ import { TextInputStyle, } from "discord.js"; import { GuildPluginData } from "knub"; -import { canActOn } from "../../../pluginUtils"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { CaseTypes } from "../../../data/CaseTypes"; import { logger } from "../../../logger"; +import { canActOn } from "../../../pluginUtils"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; import { renderUserUsername } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType, ModMenuActionType } from "../types"; diff --git a/backend/src/plugins/ContextMenus/actions/warn.ts b/backend/src/plugins/ContextMenus/actions/warn.ts index 1e93f42da..94c094efa 100644 --- a/backend/src/plugins/ContextMenus/actions/warn.ts +++ b/backend/src/plugins/ContextMenus/actions/warn.ts @@ -8,11 +8,11 @@ import { TextInputStyle, } from "discord.js"; import { GuildPluginData } from "knub"; -import { canActOn } from "../../../pluginUtils"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { logger } from "../../../logger"; +import { canActOn } from "../../../pluginUtils"; import { renderUserUsername } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd"; import { ContextMenuPluginType, ModMenuActionType } from "../types"; import { updateAction } from "./update"; diff --git a/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts index fd5389979..dab9f87d7 100644 --- a/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts +++ b/backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts @@ -12,13 +12,12 @@ import { import { GuildPluginData, guildPluginUserContextMenuCommand } from "knub"; import { Case } from "../../../data/entities/Case"; import { logger } from "../../../logger"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; 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 { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { getUserInfoEmbed } from "../../Utility/functions/getUserInfoEmbed"; import { launchBanActionModal } from "../actions/ban"; import { launchMuteActionModal } from "../actions/mute"; diff --git a/backend/src/plugins/Counters/CountersPlugin.ts b/backend/src/plugins/Counters/CountersPlugin.ts index dd226865b..edee10bec 100644 --- a/backend/src/plugins/Counters/CountersPlugin.ts +++ b/backend/src/plugins/Counters/CountersPlugin.ts @@ -4,6 +4,7 @@ import { GuildCounters } from "../../data/GuildCounters"; import { CounterTrigger, parseCounterConditionString } from "../../data/entities/CounterTrigger"; import { makePublicFn } from "../../pluginUtils"; import { MINUTES, convertDelayStringToMS, values } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { AddCounterCmd } from "./commands/AddCounterCmd"; import { CountersListCmd } from "./commands/CountersListCmd"; import { ResetAllCounterValuesCmd } from "./commands/ResetAllCounterValuesCmd"; @@ -19,7 +20,6 @@ import { offCounterEvent } from "./functions/offCounterEvent"; import { onCounterEvent } from "./functions/onCounterEvent"; import { setCounterValue } from "./functions/setCounterValue"; import { CountersPluginType, zCountersConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const DECAY_APPLY_INTERVAL = 5 * MINUTES; diff --git a/backend/src/plugins/Counters/commands/AddCounterCmd.ts b/backend/src/plugins/Counters/commands/AddCounterCmd.ts index 558c23de4..9df3ea34d 100644 --- a/backend/src/plugins/Counters/commands/AddCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/AddCounterCmd.ts @@ -3,7 +3,6 @@ import { guildPluginMessageCommand } from "knub"; import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { UnknownUser, resolveUser } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { changeCounterValue } from "../functions/changeCounterValue"; import { CountersPluginType } from "../types"; diff --git a/backend/src/plugins/Counters/commands/CountersListCmd.ts b/backend/src/plugins/Counters/commands/CountersListCmd.ts index fc79b62c1..70ecb8104 100644 --- a/backend/src/plugins/Counters/commands/CountersListCmd.ts +++ b/backend/src/plugins/Counters/commands/CountersListCmd.ts @@ -1,7 +1,6 @@ import { guildPluginMessageCommand } from "knub"; import { trimMultilineString, ucfirst } from "../../../utils"; import { getGuildPrefix } from "../../../utils/getGuildPrefix"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { CountersPluginType } from "../types"; export const CountersListCmd = guildPluginMessageCommand()({ diff --git a/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts b/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts index 5f7aa5d41..45b46006c 100644 --- a/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts +++ b/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts @@ -1,7 +1,6 @@ import { guildPluginMessageCommand } from "knub"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { confirm, noop, trimMultilineString } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { resetAllCounterValues } from "../functions/resetAllCounterValues"; import { CountersPluginType } from "../types"; @@ -23,7 +22,10 @@ export const ResetAllCounterValuesCmd = guildPluginMessageCommand()({ diff --git a/backend/src/plugins/Counters/types.ts b/backend/src/plugins/Counters/types.ts index c75046eb1..a323abf44 100644 --- a/backend/src/plugins/Counters/types.ts +++ b/backend/src/plugins/Counters/types.ts @@ -9,8 +9,8 @@ import { parseCounterConditionString, } from "../../data/entities/CounterTrigger"; import { zBoundedCharacters, zBoundedRecord, zDelayString } from "../../utils"; -import Timeout = NodeJS.Timeout; import { CommonPlugin } from "../Common/CommonPlugin"; +import Timeout = NodeJS.Timeout; const MAX_COUNTERS = 5; const MAX_TRIGGERS_PER_COUNTER = 5; diff --git a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts index cdb9aae7d..41d8c1f2d 100644 --- a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts +++ b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts @@ -11,10 +11,10 @@ import { messageToTemplateSafeMessage, userToTemplateSafeUser, } from "../../utils/templateSafeObjects"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { runEvent } from "./functions/runEvent"; import { CustomEventsPluginType, zCustomEventsConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions = { config: { diff --git a/backend/src/plugins/CustomEvents/functions/runEvent.ts b/backend/src/plugins/CustomEvents/functions/runEvent.ts index 3f75b8946..a2bf0d57b 100644 --- a/backend/src/plugins/CustomEvents/functions/runEvent.ts +++ b/backend/src/plugins/CustomEvents/functions/runEvent.ts @@ -1,7 +1,6 @@ import { Message } from "discord.js"; import { GuildPluginData } from "knub"; import { TemplateSafeValueContainer } from "../../../templateFormatter"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { ActionError } from "../ActionError"; import { addRoleAction } from "../actions/addRoleAction"; import { createCaseAction } from "../actions/createCaseAction"; diff --git a/backend/src/plugins/CustomEvents/types.ts b/backend/src/plugins/CustomEvents/types.ts index 0372fd162..df325b98e 100644 --- a/backend/src/plugins/CustomEvents/types.ts +++ b/backend/src/plugins/CustomEvents/types.ts @@ -1,6 +1,7 @@ import { BasePluginType, pluginUtils } from "knub"; import z from "zod"; import { zBoundedCharacters, zBoundedRecord } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { zAddRoleAction } from "./actions/addRoleAction"; import { zCreateCaseAction } from "./actions/createCaseAction"; import { zMakeRoleMentionableAction } from "./actions/makeRoleMentionableAction"; @@ -8,7 +9,6 @@ import { zMakeRoleUnmentionableAction } from "./actions/makeRoleUnmentionableAct import { zMessageAction } from "./actions/messageAction"; import { zMoveToVoiceChannelAction } from "./actions/moveToVoiceChannelAction"; import { zSetChannelPermissionOverridesAction } from "./actions/setChannelPermissionOverrides"; -import { CommonPlugin } from "../Common/CommonPlugin"; const zCommandTrigger = z.strictObject({ type: z.literal("command"), diff --git a/backend/src/plugins/LocateUser/LocateUserPlugin.ts b/backend/src/plugins/LocateUser/LocateUserPlugin.ts index 39057b00a..0730919c8 100644 --- a/backend/src/plugins/LocateUser/LocateUserPlugin.ts +++ b/backend/src/plugins/LocateUser/LocateUserPlugin.ts @@ -1,6 +1,7 @@ import { PluginOptions, guildPlugin } from "knub"; import { onGuildEvent } from "../../data/GuildEvents"; import { GuildVCAlerts } from "../../data/GuildVCAlerts"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { FollowCmd } from "./commands/FollowCmd"; import { DeleteFollowCmd, ListFollowCmd } from "./commands/ListFollowCmd"; import { WhereCmd } from "./commands/WhereCmd"; @@ -9,7 +10,6 @@ import { VoiceStateUpdateAlertEvt } from "./events/SendAlertsEvts"; import { LocateUserPluginType, zLocateUserConfig } from "./types"; import { clearExpiredAlert } from "./utils/clearExpiredAlert"; import { fillActiveAlertsList } from "./utils/fillAlertsList"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/LocateUser/commands/FollowCmd.ts b/backend/src/plugins/LocateUser/commands/FollowCmd.ts index 921ec47fa..f0debc595 100644 --- a/backend/src/plugins/LocateUser/commands/FollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/FollowCmd.ts @@ -3,7 +3,6 @@ import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { registerExpiringVCAlert } from "../../../data/loops/expiringVCAlertsLoop"; import { MINUTES, SECONDS } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { locateUserCmd } from "../types"; export const FollowCmd = locateUserCmd({ @@ -47,18 +46,16 @@ export const FollowCmd = locateUserCmd({ if (active) { void pluginData.state.common.sendSuccessMessage( - msg, - `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( - time, - )} i will notify and move you.\nPlease make sure to be in a voice channel, otherwise i cannot move you!`, - ); + msg, + `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( + time, + )} i will notify and move you.\nPlease make sure to be in a voice channel, otherwise i cannot move you!`, + ); } else { void pluginData.state.common.sendSuccessMessage( - msg, - `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( - time, - )} i will notify you`, - ); + msg, + `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration(time)} i will notify you`, + ); } }, }); diff --git a/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts b/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts index c993c5c77..bbe305137 100644 --- a/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts @@ -1,7 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { clearExpiringVCAlert } from "../../../data/loops/expiringVCAlertsLoop"; import { createChunkedMessage, sorter } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { locateUserCmd } from "../types"; export const ListFollowCmd = locateUserCmd({ diff --git a/backend/src/plugins/LocateUser/utils/moveMember.ts b/backend/src/plugins/LocateUser/utils/moveMember.ts index f562fe307..6438bde1a 100644 --- a/backend/src/plugins/LocateUser/utils/moveMember.ts +++ b/backend/src/plugins/LocateUser/utils/moveMember.ts @@ -1,6 +1,5 @@ import { GuildMember, GuildTextBasedChannel, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LocateUserPluginType } from "../types"; export async function moveMember( diff --git a/backend/src/plugins/LocateUser/utils/sendWhere.ts b/backend/src/plugins/LocateUser/utils/sendWhere.ts index 78172509c..6ce4181f3 100644 --- a/backend/src/plugins/LocateUser/utils/sendWhere.ts +++ b/backend/src/plugins/LocateUser/utils/sendWhere.ts @@ -1,7 +1,6 @@ import { GuildMember, GuildTextBasedChannel, Invite, VoiceChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { getInviteLink } from "knub/helpers"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LocateUserPluginType } from "../types"; import { createOrReuseInvite } from "./createOrReuseInvite"; diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts index 9d62c2c7b..9ea040bf7 100644 --- a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -1,10 +1,10 @@ import { PluginOptions, guildPlugin } from "knub"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { SaveMessagesToDBCmd } from "./commands/SaveMessagesToDB"; import { SavePinsToDBCmd } from "./commands/SavePinsToDB"; import { MessageCreateEvt, MessageDeleteBulkEvt, MessageDeleteEvt, MessageUpdateEvt } from "./events/SaveMessagesEvts"; import { MessageSaverPluginType, zMessageSaverConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts index a34b103ea..6097bf140 100644 --- a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts +++ b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { saveMessagesToDB } from "../saveMessagesToDB"; import { messageSaverCmd } from "../types"; @@ -19,9 +18,9 @@ export const SaveMessagesToDBCmd = messageSaverCmd({ if (failed.length) { void pluginData.state.common.sendSuccessMessage( - msg, - `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, - ); + msg, + `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, + ); } else { void pluginData.state.common.sendSuccessMessage(msg, `Saved ${savedCount} messages!`); } diff --git a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts index d2910525a..29bc2cbbb 100644 --- a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts +++ b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { saveMessagesToDB } from "../saveMessagesToDB"; import { messageSaverCmd } from "../types"; @@ -20,9 +19,9 @@ export const SavePinsToDBCmd = messageSaverCmd({ if (failed.length) { void pluginData.state.common.sendSuccessMessage( - msg, - `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, - ); + msg, + `Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`, + ); } else { void pluginData.state.common.sendSuccessMessage(msg, `Saved ${savedCount} messages!`); } diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index bd6aa0c75..a501fbc9f 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -10,6 +10,7 @@ import { GuildTempbans } from "../../data/GuildTempbans"; import { makePublicFn, mapToPublicFn } from "../../pluginUtils"; import { MINUTES } from "../../utils"; import { CasesPlugin } from "../Cases/CasesPlugin"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; @@ -72,7 +73,6 @@ import { onModActionsEvent } from "./functions/onModActionsEvent"; import { updateCase } from "./functions/updateCase"; import { warnMember } from "./functions/warnMember"; import { AttachmentLinkReactionType, ModActionsPluginType, modActionsSlashGroup, zModActionsConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions = { config: { diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts index 9f1b819b0..bd9d85bf7 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseMsgCmd.ts @@ -2,8 +2,8 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { CaseTypes } from "../../../../data/CaseTypes"; import { hasPermission } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; -import { actualAddCaseCmd } from "./actualAddCaseCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualAddCaseCmd } from "./actualAddCaseCmd"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts index 828ec742e..23bb34a9a 100644 --- a/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/AddCaseSlashCmd.ts @@ -4,9 +4,9 @@ import { CaseTypes } from "../../../../data/CaseTypes"; import { hasPermission } from "../../../../pluginUtils"; import { resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualAddCaseCmd } from "./actualAddCaseCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualAddCaseCmd } from "./actualAddCaseCmd"; const opts = [ slashOptions.string({ name: "reason", description: "The reason", required: false }), @@ -48,10 +48,7 @@ export const AddCaseSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/addcase/actualAddCaseCmd.ts b/backend/src/plugins/ModActions/commands/addcase/actualAddCaseCmd.ts index 2c67dc2f8..ef676083f 100644 --- a/backend/src/plugins/ModActions/commands/addcase/actualAddCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/addcase/actualAddCaseCmd.ts @@ -6,9 +6,9 @@ import { canActOn } from "../../../../pluginUtils"; import { UnknownUser, renderUsername, resolveMember } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; -import { ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { ModActionsPluginType } from "../../types"; export async function actualAddCaseCmd( pluginData: GuildPluginData, @@ -27,10 +27,7 @@ export async function actualAddCaseCmd( // If the user exists as a guild member, make sure we can act on them first const member = await resolveMember(pluginData.client, pluginData.guild, user.id); if (member && !canActOn(pluginData, author, member)) { - pluginData.state.common.sendErrorMessage( - context, - "Cannot add case on this user: insufficient permissions" - ); + pluginData.state.common.sendErrorMessage(context, "Cannot add case on this user: insufficient permissions"); return; } @@ -49,7 +46,7 @@ export async function actualAddCaseCmd( if (user) { pluginData.state.common.sendSuccessMessage( context, - `Case #${theCase.case_number} created for **${renderUsername(user)}**` + `Case #${theCase.case_number} created for **${renderUsername(user)}**`, ); } else { pluginData.state.common.sendSuccessMessage(context, `Case #${theCase.case_number} created`); diff --git a/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts index 35b9f7377..b218c2e09 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanMsgCmd.ts @@ -1,9 +1,9 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveUser } from "../../../../utils"; -import { actualBanCmd } from "./actualBanCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; +import { actualBanCmd } from "./actualBanCmd"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts index 16bda55b8..12ea3f119 100644 --- a/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/BanSlashCmd.ts @@ -3,10 +3,10 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualBanCmd } from "./actualBanCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualBanCmd } from "./actualBanCmd"; const opts = [ slashOptions.string({ name: "time", description: "The duration of the ban", required: false }), @@ -51,13 +51,7 @@ export const BanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -70,10 +64,7 @@ export const BanSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts b/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts index 0b442239e..95706e6fb 100644 --- a/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts @@ -10,11 +10,14 @@ import { banLock } from "../../../../utils/lockNameHelpers"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; -import { ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; import { banUserId } from "../../functions/banUserId"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { + formatReasonWithAttachments, + formatReasonWithMessageLinkForAttachments, +} from "../../functions/formatReasonForAttachments"; import { isBanned } from "../../functions/isBanned"; +import { ModActionsPluginType } from "../../types"; export async function actualBanCmd( pluginData: GuildPluginData, @@ -74,10 +77,7 @@ export async function actualBanCmd( ); if (!reply) { - pluginData.state.common.sendErrorMessage( - context, - "User already banned, update cancelled by moderator" - ); + pluginData.state.common.sendErrorMessage(context, "User already banned, update cancelled by moderator"); lock.unlock(); return; } @@ -123,9 +123,9 @@ export async function actualBanCmd( } pluginData.state.common.sendSuccessMessage( - context, - `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, - ); + context, + `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, + ); lock.unlock(); return; } @@ -138,7 +138,7 @@ export async function actualBanCmd( pluginData.state.common.sendErrorMessage( context, `Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`, - ); + ); lock.unlock(); return; } diff --git a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts index 6c727e45f..c2e8d8b5e 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseMsgCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualCaseCmd } from "./actualCaseCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualCaseCmd } from "./actualCaseCmd"; const opts = { show: ct.switchOption({ def: false, shortcut: "sh" }), diff --git a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts index b86bfb011..220a9697a 100644 --- a/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/case/CaseSlashCmd.ts @@ -1,6 +1,6 @@ import { slashOptions } from "knub"; -import { actualCaseCmd } from "./actualCaseCmd"; import { modActionsSlashCmd } from "../../types"; +import { actualCaseCmd } from "./actualCaseCmd"; const opts = [ slashOptions.boolean({ name: "show", description: "To make the result visible to everyone", required: false }), diff --git a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts index d5f5f7fe0..e55f1661d 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualCasesCmd } from "./actualCasesCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualCasesCmd } from "./actualCasesCmd"; const opts = { mod: ct.userId({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts index fe9323110..accb20507 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -1,7 +1,7 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; -import { actualCasesCmd } from "./actualCasesCmd"; import { modActionsSlashCmd } from "../../types"; +import { actualCasesCmd } from "./actualCasesCmd"; const opts = [ slashOptions.user({ name: "user", description: "The user to show cases for", required: false }), diff --git a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts index b36b39c71..7d425f2c6 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts @@ -1,7 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { resolveMember, resolveUser, UnknownUser } from "../../../../utils"; -import { actualCasesCmd } from "./actualCasesCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualCasesCmd } from "./actualCasesCmd"; const opts = { mod: ct.userId({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts b/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts index 5973bcdb2..3e72303db 100644 --- a/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts @@ -159,13 +159,7 @@ async function casesModCmd( const totalCases = await casesPlugin.getTotalCasesByMod(modId ?? author.id, casesFilters); if (totalCases === 0) { - pluginData.state.common.sendErrorMessage( - context, - `No cases by **${modName}**`, - undefined, - undefined, - !show - ); + pluginData.state.common.sendErrorMessage(context, `No cases by **${modName}**`, undefined, undefined, !show); return; } diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts index dad54847f..396bd73f8 100644 --- a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseMsgCmd.ts @@ -1,7 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { trimLines } from "../../../../utils"; -import { actualDeleteCaseCmd } from "./actualDeleteCaseCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualDeleteCaseCmd } from "./actualDeleteCaseCmd"; export const DeleteCaseMsgCmd = modActionsMsgCmd({ trigger: ["delete_case", "deletecase"], diff --git a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts index c9038c74e..932f4cadb 100644 --- a/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/DeleteCaseSlashCmd.ts @@ -1,7 +1,7 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; -import { actualDeleteCaseCmd } from "./actualDeleteCaseCmd"; import { modActionsSlashCmd } from "../../types"; +import { actualDeleteCaseCmd } from "./actualDeleteCaseCmd"; const opts = [slashOptions.boolean({ name: "force", description: "Whether or not to force delete", required: false })]; diff --git a/backend/src/plugins/ModActions/commands/deletecase/actualDeleteCaseCmd.ts b/backend/src/plugins/ModActions/commands/deletecase/actualDeleteCaseCmd.ts index 707332e4a..64c146eec 100644 --- a/backend/src/plugins/ModActions/commands/deletecase/actualDeleteCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/deletecase/actualDeleteCaseCmd.ts @@ -82,15 +82,12 @@ export async function actualDeleteCaseCmd( : ""; const amt = validCases.length - cancelled; if (amt === 0) { - pluginData.state.common.sendErrorMessage( - context, - "All deletions were cancelled, no cases were deleted." - ); + pluginData.state.common.sendErrorMessage(context, "All deletions were cancelled, no cases were deleted."); return; } pluginData.state.common.sendSuccessMessage( context, - `${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}` + `${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}`, ); } diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts index 5224e905a..3546ee145 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanMsgCmd.ts @@ -1,9 +1,9 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; -import { actualForceBanCmd } from "./actualForceBanCmd"; import { isBanned } from "../../functions/isBanned"; import { modActionsMsgCmd } from "../../types"; +import { actualForceBanCmd } from "./actualForceBanCmd"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts index 8389233b5..14e275619 100644 --- a/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/ForceBanSlashCmd.ts @@ -3,9 +3,9 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualForceBanCmd } from "./actualForceBanCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualForceBanCmd } from "./actualForceBanCmd"; const opts = [ slashOptions.string({ name: "reason", description: "The reason", required: false }), @@ -29,13 +29,7 @@ export const ForceBanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -48,10 +42,7 @@ export const ForceBanSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/forceban/actualForceBanCmd.ts b/backend/src/plugins/ModActions/commands/forceban/actualForceBanCmd.ts index 448f41341..6b84901cf 100644 --- a/backend/src/plugins/ModActions/commands/forceban/actualForceBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceban/actualForceBanCmd.ts @@ -5,10 +5,13 @@ import { LogType } from "../../../../data/LogType"; import { DAYS, MINUTES, UnknownUser } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; -import { IgnoredEventType, ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { + formatReasonWithAttachments, + formatReasonWithMessageLinkForAttachments, +} from "../../functions/formatReasonForAttachments"; import { ignoreEvent } from "../../functions/ignoreEvent"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; export async function actualForceBanCmd( pluginData: GuildPluginData, diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts index 833fb1813..384b34e87 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteMsgCmd.ts @@ -1,9 +1,9 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; -import { actualMuteCmd } from "../mute/actualMuteCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; +import { actualMuteCmd } from "../mute/actualMuteCmd"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts index ba2628352..05505bab8 100644 --- a/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forcemute/ForceMuteSlashCmd.ts @@ -3,10 +3,10 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualMuteCmd } from "../mute/actualMuteCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualMuteCmd } from "../mute/actualMuteCmd"; const opts = [ slashOptions.string({ name: "time", description: "The duration of the mute", required: false }), @@ -46,13 +46,7 @@ export const ForceMuteSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -66,10 +60,7 @@ export const ForceMuteSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts index 396052fd9..439bc8778 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteMsgCmd.ts @@ -1,8 +1,8 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; -import { actualUnmuteCmd } from "../unmute/actualUnmuteCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualUnmuteCmd } from "../unmute/actualUnmuteCmd"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts index bb03ee897..1ddd49623 100644 --- a/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/forceunmute/ForceUnmuteSlashCmd.ts @@ -3,9 +3,9 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualUnmuteCmd } from "../unmute/actualUnmuteCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualUnmuteCmd } from "../unmute/actualUnmuteCmd"; const opts = [ slashOptions.string({ name: "time", description: "The duration of the unmute", required: false }), @@ -30,13 +30,7 @@ export const ForceUnmuteSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -50,10 +44,7 @@ export const ForceUnmuteSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts index c04bf9a1e..8b427275d 100644 --- a/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseMsgCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualHideCaseCmd } from "./actualHideCaseCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualHideCaseCmd } from "./actualHideCaseCmd"; export const HideCaseMsgCmd = modActionsMsgCmd({ trigger: ["hide", "hidecase", "hide_case"], diff --git a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts index b6261d739..0485c5dec 100644 --- a/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/HideCaseSlashCmd.ts @@ -1,6 +1,6 @@ import { slashOptions } from "knub"; -import { actualHideCaseCmd } from "./actualHideCaseCmd"; import { modActionsSlashCmd } from "../../types"; +import { actualHideCaseCmd } from "./actualHideCaseCmd"; export const HideCaseSlashCmd = modActionsSlashCmd({ name: "hidecase", diff --git a/backend/src/plugins/ModActions/commands/hidecase/actualHideCaseCmd.ts b/backend/src/plugins/ModActions/commands/hidecase/actualHideCaseCmd.ts index 49bee81bb..d38e099c5 100644 --- a/backend/src/plugins/ModActions/commands/hidecase/actualHideCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/hidecase/actualHideCaseCmd.ts @@ -30,7 +30,7 @@ export async function actualHideCaseCmd( const amt = caseNumbers.length - failed.length; pluginData.state.common.sendSuccessMessage( - context, - `${amt} case${amt === 1 ? " is" : "s are"} now hidden! Use \`unhidecase\` to unhide them.${failedAddendum}`, - ); + context, + `${amt} case${amt === 1 ? " is" : "s are"} now hidden! Use \`unhidecase\` to unhide them.${failedAddendum}`, + ); } diff --git a/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts index c494a7c76..d895bba46 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickMsgCmd.ts @@ -1,9 +1,9 @@ import { hasPermission } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { resolveUser } from "../../../../utils"; -import { actualKickCmd } from "./actualKickCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; +import { actualKickCmd } from "./actualKickCmd"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts index 5bd94f30a..4f9bf3927 100644 --- a/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/KickSlashCmd.ts @@ -3,10 +3,10 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualKickCmd } from "./actualKickCmd"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualKickCmd } from "./actualKickCmd"; const opts = [ slashOptions.string({ name: "reason", description: "The reason", required: false }), @@ -50,13 +50,7 @@ export const KickSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -69,10 +63,7 @@ export const KickSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/kick/actualKickCmd.ts b/backend/src/plugins/ModActions/commands/kick/actualKickCmd.ts index 4a6b22a40..3ee204cc6 100644 --- a/backend/src/plugins/ModActions/commands/kick/actualKickCmd.ts +++ b/backend/src/plugins/ModActions/commands/kick/actualKickCmd.ts @@ -3,12 +3,15 @@ import { GuildPluginData } from "knub"; import { LogType } from "../../../../data/LogType"; import { canActOn } from "../../../../pluginUtils"; import { DAYS, SECONDS, UnknownUser, UserNotificationMethod, renderUsername, resolveMember } from "../../../../utils"; -import { IgnoredEventType, ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { + formatReasonWithAttachments, + formatReasonWithMessageLinkForAttachments, +} from "../../functions/formatReasonForAttachments"; import { ignoreEvent } from "../../functions/ignoreEvent"; import { isBanned } from "../../functions/isBanned"; import { kickMember } from "../../functions/kickMember"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; export async function actualKickCmd( pluginData: GuildPluginData, @@ -71,9 +74,7 @@ export async function actualKickCmd( try { await pluginData.guild.bans.remove(memberToKick.id, "kick -clean"); } catch { - pluginData.state.common.sendErrorMessage( - context, - "Failed to unban the user after banning them (-clean)"); + pluginData.state.common.sendErrorMessage(context, "Failed to unban the user after banning them (-clean)"); } } diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts index d47c61d52..3267bf283 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanMsgCmd.ts @@ -1,8 +1,8 @@ import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; -import { actualMassBanCmd } from "./actualMassBanCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualMassBanCmd } from "./actualMassBanCmd"; export const MassBanMsgCmd = modActionsMsgCmd({ trigger: "massban", diff --git a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts index a358893e3..805a3d63b 100644 --- a/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/MassBanSlashCmd.ts @@ -1,9 +1,9 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualMassBanCmd } from "./actualMassBanCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualMassBanCmd } from "./actualMassBanCmd"; const opts = [ slashOptions.string({ name: "reason", description: "The reason", required: false }), @@ -30,13 +30,7 @@ export const MassBanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } diff --git a/backend/src/plugins/ModActions/commands/massban/actualMassBanCmd.ts b/backend/src/plugins/ModActions/commands/massban/actualMassBanCmd.ts index 207194dfa..277061058 100644 --- a/backend/src/plugins/ModActions/commands/massban/actualMassBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/massban/actualMassBanCmd.ts @@ -7,10 +7,13 @@ import { canActOn, getContextChannel, isContextInteraction, sendContextResponse import { DAYS, MINUTES, SECONDS, noop } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; -import { IgnoredEventType, ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { + formatReasonWithAttachments, + formatReasonWithMessageLinkForAttachments, +} from "../../functions/formatReasonForAttachments"; import { ignoreEvent } from "../../functions/ignoreEvent"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; export async function actualMassBanCmd( pluginData: GuildPluginData, @@ -37,9 +40,7 @@ export async function actualMassBanCmd( for (const userId of userIds) { const member = pluginData.guild.members.cache.get(userId as Snowflake); // TODO: Get members on demand? if (member && !canActOn(pluginData, author, member)) { - pluginData.state.common.sendErrorMessage( - context, - "Cannot massban one or more users: insufficient permissions"); + pluginData.state.common.sendErrorMessage(context, "Cannot massban one or more users: insufficient permissions"); return; } } @@ -157,14 +158,14 @@ export async function actualMassBanCmd( if (failedBans.length) { pluginData.state.common.sendSuccessMessage( context, - `Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${ - failedBans.length - } failed: ${failedBans.join(" ")}`, + `Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${failedBans.length} failed: ${failedBans.join( + " ", + )}`, ); } else { pluginData.state.common.sendSuccessMessage( context, - `Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}` + `Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}`, ); } } diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts index bbcfd7156..0ff53ab16 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteMsgCmd.ts @@ -1,8 +1,8 @@ import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; -import { actualMassMuteCmd } from "./actualMassMuteCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualMassMuteCmd } from "./actualMassMuteCmd"; export const MassMuteMsgCmd = modActionsMsgCmd({ trigger: "massmute", diff --git a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts index d7ad927a3..cb02ceee9 100644 --- a/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/MassMuteSlashCmd.ts @@ -1,9 +1,9 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualMassMuteCmd } from "./actualMassMuteCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualMassMuteCmd } from "./actualMassMuteCmd"; const opts = [ slashOptions.string({ name: "reason", description: "The reason", required: false }), @@ -30,13 +30,7 @@ export const MassMuteSlashSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } diff --git a/backend/src/plugins/ModActions/commands/massmute/actualMassMuteCmd.ts b/backend/src/plugins/ModActions/commands/massmute/actualMassMuteCmd.ts index e8ed71114..e7615044f 100644 --- a/backend/src/plugins/ModActions/commands/massmute/actualMassMuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/massmute/actualMassMuteCmd.ts @@ -5,9 +5,12 @@ import { logger } from "../../../../logger"; import { canActOn, isContextInteraction, sendContextResponse } from "../../../../pluginUtils"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; -import { ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { + formatReasonWithAttachments, + formatReasonWithMessageLinkForAttachments, +} from "../../functions/formatReasonForAttachments"; +import { ModActionsPluginType } from "../../types"; export async function actualMassMuteCmd( pluginData: GuildPluginData, @@ -34,10 +37,7 @@ export async function actualMassMuteCmd( for (const userId of userIds) { const member = pluginData.guild.members.cache.get(userId as Snowflake); if (member && !canActOn(pluginData, author, member)) { - pluginData.state.common.sendErrorMessage( - context, - "Cannot massmute one or more users: insufficient permissions" - ); + pluginData.state.common.sendErrorMessage(context, "Cannot massmute one or more users: insufficient permissions"); return; } } @@ -87,9 +87,9 @@ export async function actualMassMuteCmd( if (failedMutes.length) { pluginData.state.common.sendSuccessMessage( - context, - `Muted ${successfulMuteCount} users, ${failedMutes.length} failed: ${failedMutes.join(" ")}`, - ); + context, + `Muted ${successfulMuteCount} users, ${failedMutes.length} failed: ${failedMutes.join(" ")}`, + ); } else { pluginData.state.common.sendSuccessMessage(context, `Muted ${successfulMuteCount} users successfully`); } diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts index c10df41f0..2d2dd3063 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanMsgCmd.ts @@ -1,8 +1,8 @@ import { waitForReply } from "knub/helpers"; import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { getContextChannel, sendContextResponse } from "../../../../pluginUtils"; -import { actualMassUnbanCmd } from "./actualMassUnbanCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualMassUnbanCmd } from "./actualMassUnbanCmd"; export const MassUnbanMsgCmd = modActionsMsgCmd({ trigger: "massunban", diff --git a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts index 4a7335520..acf6a975f 100644 --- a/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/MassUnbanSlashCmd.ts @@ -1,9 +1,9 @@ import { GuildMember } from "discord.js"; import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualMassUnbanCmd } from "./actualMassUnbanCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualMassUnbanCmd } from "./actualMassUnbanCmd"; const opts = [ slashOptions.string({ name: "reason", description: "The reason", required: false }), @@ -30,13 +30,7 @@ export const MassUnbanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } diff --git a/backend/src/plugins/ModActions/commands/massunban/actualMassUnbanCmd.ts b/backend/src/plugins/ModActions/commands/massunban/actualMassUnbanCmd.ts index 6f4a0ebfd..e67e70669 100644 --- a/backend/src/plugins/ModActions/commands/massunban/actualMassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/massunban/actualMassUnbanCmd.ts @@ -6,11 +6,11 @@ import { isContextInteraction, sendContextResponse } from "../../../../pluginUti import { MINUTES, noop } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; -import { IgnoredEventType, ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; import { ignoreEvent } from "../../functions/ignoreEvent"; import { isBanned } from "../../functions/isBanned"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; export async function actualMassUnbanCmd( pluginData: GuildPluginData, @@ -75,10 +75,7 @@ export async function actualMassUnbanCmd( const successfulUnbanCount = userIds.length - failedUnbans.length; if (successfulUnbanCount === 0) { // All unbans failed - don't create a log entry and notify the user - pluginData.state.common.sendErrorMessage( - context, - "All unbans failed. Make sure the IDs are valid and banned." - ); + pluginData.state.common.sendErrorMessage(context, "All unbans failed. Make sure the IDs are valid and banned."); } else { // Some or all unbans were successful. Create a log entry for the mass unban and notify the user. pluginData.getPlugin(LogsPlugin).logMassUnban({ @@ -106,9 +103,9 @@ export async function actualMassUnbanCmd( } pluginData.state.common.sendSuccessMessage( - context, - `Unbanned ${successfulUnbanCount} users, ${failedUnbans.length} failed:\n${failedMsg}`, - ); + context, + `Unbanned ${successfulUnbanCount} users, ${failedUnbans.length} failed:\n${failedMsg}`, + ); } else { pluginData.state.common.sendSuccessMessage(context, `Unbanned ${successfulUnbanCount} users successfully`); } diff --git a/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts index d6aeb2845..0135b3e33 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteMsgCmd.ts @@ -2,10 +2,10 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; -import { actualMuteCmd } from "./actualMuteCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; +import { actualMuteCmd } from "./actualMuteCmd"; const opts = { mod: ct.member({ option: true }), @@ -49,7 +49,7 @@ export const MuteMsgCmd = modActionsMsgCmd({ if (_isBanned) { pluginData.state.common.sendErrorMessage( msg, - `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.` + `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`, ); return; } else { diff --git a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts index b662b33d0..d791b9d86 100644 --- a/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/MuteSlashCmd.ts @@ -4,11 +4,11 @@ import { canActOn, hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; -import { actualMuteCmd } from "./actualMuteCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualMuteCmd } from "./actualMuteCmd"; const opts = [ slashOptions.string({ name: "time", description: "The duration of the mute", required: false }), @@ -54,7 +54,7 @@ export const MuteSlashCmd = modActionsSlashCmd({ if (_isBanned) { pluginData.state.common.sendErrorMessage( interaction, - `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.` + `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`, ); return; } else { @@ -66,10 +66,7 @@ export const MuteSlashCmd = modActionsSlashCmd({ ); if (!reply) { - pluginData.state.common.sendErrorMessage( - interaction, - "User not on server, mute cancelled by moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "User not on server, mute cancelled by moderator"); return; } } @@ -90,10 +87,7 @@ export const MuteSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/mute/actualMuteCmd.ts b/backend/src/plugins/ModActions/commands/mute/actualMuteCmd.ts index 6500d0722..0a938d069 100644 --- a/backend/src/plugins/ModActions/commands/mute/actualMuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/mute/actualMuteCmd.ts @@ -12,9 +12,12 @@ import { } from "../../../../utils"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; import { MuteResult } from "../../../Mutes/types"; -import { ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { + formatReasonWithAttachments, + formatReasonWithMessageLinkForAttachments, +} from "../../functions/formatReasonForAttachments"; +import { ModActionsPluginType } from "../../types"; /** * The actual function run by both !mute and !forcemute. @@ -56,10 +59,7 @@ export async function actualMuteCmd( }); } catch (e) { if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - pluginData.state.common.sendErrorMessage( - context, - "Could not mute the user: no mute role set in config" - ); + pluginData.state.common.sendErrorMessage(context, "Could not mute the user: no mute role set in config"); } else if (isDiscordAPIError(e) && e.code === 10007) { pluginData.state.common.sendErrorMessage(context, "Could not mute the user: unknown member"); } else { diff --git a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts index 2cccbd6dc..4ef15c2e2 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteMsgCmd.ts @@ -1,7 +1,7 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { resolveUser } from "../../../../utils"; -import { actualNoteCmd } from "./actualNoteCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualNoteCmd } from "./actualNoteCmd"; export const NoteMsgCmd = modActionsMsgCmd({ trigger: "note", diff --git a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts index c13bbb607..e9069cc86 100644 --- a/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/NoteSlashCmd.ts @@ -1,8 +1,8 @@ import { slashOptions } from "knub"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualNoteCmd } from "./actualNoteCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualNoteCmd } from "./actualNoteCmd"; const opts = [ slashOptions.string({ name: "note", description: "The note to add to the user", required: false }), @@ -25,13 +25,7 @@ export const NoteSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.note || options.note.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } diff --git a/backend/src/plugins/ModActions/commands/note/actualNoteCmd.ts b/backend/src/plugins/ModActions/commands/note/actualNoteCmd.ts index 5c70cb046..b34f8821a 100644 --- a/backend/src/plugins/ModActions/commands/note/actualNoteCmd.ts +++ b/backend/src/plugins/ModActions/commands/note/actualNoteCmd.ts @@ -4,9 +4,9 @@ import { CaseTypes } from "../../../../data/CaseTypes"; import { UnknownUser, renderUsername } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; -import { ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { ModActionsPluginType } from "../../types"; export async function actualNoteCmd( pluginData: GuildPluginData, @@ -39,12 +39,12 @@ export async function actualNoteCmd( }); pluginData.state.common.sendSuccessMessage( - context, - `Note added on **${userName}** (Case #${createdCase.case_number})`, - undefined, - undefined, - true, - ); + context, + `Note added on **${userName}** (Case #${createdCase.case_number})`, + undefined, + undefined, + true, + ); pluginData.state.events.emit("note", user.id, reason); } diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts index ec346acc4..8720fadca 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanMsgCmd.ts @@ -1,8 +1,8 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { hasPermission } from "../../../../pluginUtils"; import { resolveUser } from "../../../../utils"; -import { actualUnbanCmd } from "./actualUnbanCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualUnbanCmd } from "./actualUnbanCmd"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts index a5b0124b3..8c4ef63ac 100644 --- a/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/UnbanSlashCmd.ts @@ -3,9 +3,9 @@ import { slashOptions } from "knub"; import { hasPermission } from "../../../../pluginUtils"; import { resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualUnbanCmd } from "./actualUnbanCmd"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualUnbanCmd } from "./actualUnbanCmd"; const opts = [ slashOptions.string({ name: "reason", description: "The reason", required: false }), @@ -29,13 +29,7 @@ export const UnbanSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -48,10 +42,7 @@ export const UnbanSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/unban/actualUnbanCmd.ts b/backend/src/plugins/ModActions/commands/unban/actualUnbanCmd.ts index e49b5b21f..5baa31ea7 100644 --- a/backend/src/plugins/ModActions/commands/unban/actualUnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/unban/actualUnbanCmd.ts @@ -6,10 +6,10 @@ import { clearExpiringTempban } from "../../../../data/loops/expiringTempbansLoo import { UnknownUser } from "../../../../utils"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; import { LogsPlugin } from "../../../Logs/LogsPlugin"; -import { IgnoredEventType, ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; import { ignoreEvent } from "../../functions/ignoreEvent"; +import { IgnoredEventType, ModActionsPluginType } from "../../types"; export async function actualUnbanCmd( pluginData: GuildPluginData, @@ -31,10 +31,7 @@ export async function actualUnbanCmd( ignoreEvent(pluginData, IgnoredEventType.Unban, user.id); await pluginData.guild.bans.remove(user.id as Snowflake, formattedReason ?? undefined); } catch { - pluginData.state.common.sendErrorMessage( - context, - "Failed to unban member; are you sure they're banned?" - ); + pluginData.state.common.sendErrorMessage(context, "Failed to unban member; are you sure they're banned?"); return; } diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts index 308c34f28..fe573ba1e 100644 --- a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseMsgCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; -import { actualHideCaseCmd } from "../hidecase/actualHideCaseCmd"; import { modActionsMsgCmd } from "../../types"; +import { actualHideCaseCmd } from "../hidecase/actualHideCaseCmd"; export const UnhideCaseMsgCmd = modActionsMsgCmd({ trigger: ["unhide", "unhidecase", "unhide_case"], diff --git a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts index f4b4b80f1..3bf4b61ce 100644 --- a/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/UnhideCaseSlashCmd.ts @@ -1,6 +1,6 @@ import { slashOptions } from "knub"; -import { actualUnhideCaseCmd } from "./actualUnhideCaseCmd"; import { modActionsSlashCmd } from "../../types"; +import { actualUnhideCaseCmd } from "./actualUnhideCaseCmd"; export const UnhideCaseSlashCmd = modActionsSlashCmd({ name: "unhidecase", diff --git a/backend/src/plugins/ModActions/commands/unhidecase/actualUnhideCaseCmd.ts b/backend/src/plugins/ModActions/commands/unhidecase/actualUnhideCaseCmd.ts index f1e8405e8..b1af7433f 100644 --- a/backend/src/plugins/ModActions/commands/unhidecase/actualUnhideCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/unhidecase/actualUnhideCaseCmd.ts @@ -32,6 +32,6 @@ export async function actualUnhideCaseCmd( const amt = caseNumbers.length - failed.length; pluginData.state.common.sendSuccessMessage( context, - `${amt} case${amt === 1 ? " is" : "s are"} no longer hidden!${failedAddendum}` + `${amt} case${amt === 1 ? " is" : "s are"} no longer hidden!${failedAddendum}`, ); } diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts index a0654d138..925a90a6e 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteMsgCmd.ts @@ -3,9 +3,9 @@ import { canActOn, hasPermission } from "../../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; -import { actualUnmuteCmd } from "./actualUnmuteCmd"; import { isBanned } from "../../functions/isBanned"; import { modActionsMsgCmd } from "../../types"; +import { actualUnmuteCmd } from "./actualUnmuteCmd"; const opts = { mod: ct.member({ option: true }), @@ -59,7 +59,7 @@ export const UnmuteMsgCmd = modActionsMsgCmd({ if (banned) { pluginData.state.common.sendErrorMessage( msg, - `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.` + `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`, ); return; } else { diff --git a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts index 609c74514..5420892c1 100644 --- a/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/UnmuteSlashCmd.ts @@ -5,10 +5,10 @@ import { convertDelayStringToMS, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; -import { actualUnmuteCmd } from "./actualUnmuteCmd"; import { isBanned } from "../../functions/isBanned"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualUnmuteCmd } from "./actualUnmuteCmd"; const opts = [ slashOptions.string({ name: "time", description: "The duration of the unmute", required: false }), @@ -33,13 +33,7 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ const attachments = retrieveMultipleOptions(NUMBER_ATTACHMENTS_CASE_CREATION, options, "attachment"); if ((!options.reason || options.reason.trim() === "") && attachments.length < 1) { - pluginData.state.common.sendErrorMessage( - interaction, - "Text or attachment required", - undefined, - undefined, - true - ); + pluginData.state.common.sendErrorMessage(interaction, "Text or attachment required", undefined, undefined, true); return; } @@ -64,7 +58,7 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ if (banned) { pluginData.state.common.sendErrorMessage( interaction, - `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.` + `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`, ); return; } else { @@ -76,10 +70,7 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ ); if (!reply) { - pluginData.state.common.sendErrorMessage( - interaction, - "User not on server, unmute cancelled by moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "User not on server, unmute cancelled by moderator"); return; } } @@ -100,10 +91,7 @@ export const UnmuteSlashCmd = modActionsSlashCmd({ if (options.mod) { if (!canActAsOther) { - pluginData.state.common.sendErrorMessage( - interaction, - "You don't have permission to act as another moderator" - ); + pluginData.state.common.sendErrorMessage(interaction, "You don't have permission to act as another moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/unmute/actualUnmuteCmd.ts b/backend/src/plugins/ModActions/commands/unmute/actualUnmuteCmd.ts index 3e3be3125..da34ed43a 100644 --- a/backend/src/plugins/ModActions/commands/unmute/actualUnmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/unmute/actualUnmuteCmd.ts @@ -3,9 +3,9 @@ import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import { UnknownUser, asSingleLine, renderUsername } from "../../../../utils"; import { MutesPlugin } from "../../../Mutes/MutesPlugin"; -import { ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; import { formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { ModActionsPluginType } from "../../types"; export async function actualUnmuteCmd( pluginData: GuildPluginData, diff --git a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts index 1d9e331f7..99309c227 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnMsgCmd.ts @@ -1,10 +1,10 @@ import { commandTypeHelpers as ct } from "../../../../commandTypes"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { errorMessage, resolveMember, resolveUser } from "../../../../utils"; -import { actualWarnCmd } from "./actualWarnCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsMsgCmd } from "../../types"; +import { actualWarnCmd } from "./actualWarnCmd"; export const WarnMsgCmd = modActionsMsgCmd({ trigger: "warn", diff --git a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts index 82f80ba0e..af7449f1d 100644 --- a/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/WarnSlashCmd.ts @@ -3,11 +3,11 @@ import { slashOptions } from "knub"; import { canActOn, hasPermission } from "../../../../pluginUtils"; import { UserNotificationMethod, resolveMember } from "../../../../utils"; import { generateAttachmentSlashOptions, retrieveMultipleOptions } from "../../../../utils/multipleSlashOptions"; -import { actualWarnCmd } from "./actualWarnCmd"; import { isBanned } from "../../functions/isBanned"; import { readContactMethodsFromArgs } from "../../functions/readContactMethodsFromArgs"; import { modActionsSlashCmd } from "../../types"; import { NUMBER_ATTACHMENTS_CASE_CREATION } from "../constants"; +import { actualWarnCmd } from "./actualWarnCmd"; const opts = [ slashOptions.string({ name: "reason", description: "The reason", required: false }), @@ -51,7 +51,7 @@ export const WarnSlashCmd = modActionsSlashCmd({ "Text or attachment required", undefined, undefined, - true + true, ); return; @@ -86,7 +86,7 @@ export const WarnSlashCmd = modActionsSlashCmd({ if (!canActAsOther) { await pluginData.state.common.sendErrorMessage( interaction, - "You don't have permission to act as another moderator" + "You don't have permission to act as another moderator", ); return; } diff --git a/backend/src/plugins/ModActions/commands/warn/actualWarnCmd.ts b/backend/src/plugins/ModActions/commands/warn/actualWarnCmd.ts index c764d29db..b505434a9 100644 --- a/backend/src/plugins/ModActions/commands/warn/actualWarnCmd.ts +++ b/backend/src/plugins/ModActions/commands/warn/actualWarnCmd.ts @@ -4,10 +4,13 @@ import { CaseTypes } from "../../../../data/CaseTypes"; import { UserNotificationMethod, renderUsername } from "../../../../utils"; import { waitForButtonConfirm } from "../../../../utils/waitForInteraction"; import { CasesPlugin } from "../../../Cases/CasesPlugin"; -import { ModActionsPluginType } from "../../types"; import { handleAttachmentLinkDetectionAndGetRestriction } from "../../functions/attachmentLinkReaction"; -import { formatReasonWithAttachments, formatReasonWithMessageLinkForAttachments } from "../../functions/formatReasonForAttachments"; +import { + formatReasonWithAttachments, + formatReasonWithMessageLinkForAttachments, +} from "../../functions/formatReasonForAttachments"; import { warnMember } from "../../functions/warnMember"; +import { ModActionsPluginType } from "../../types"; export async function actualWarnCmd( pluginData: GuildPluginData, @@ -62,7 +65,7 @@ export async function actualWarnCmd( const messageResultText = warnResult.notifyResult.text ? ` (${warnResult.notifyResult.text})` : ""; await pluginData.state.common.sendSuccessMessage( - context, - `Warned **${renderUsername(memberToWarn.user)}** (Case #${warnResult.case.case_number})${messageResultText}`, - ); + context, + `Warned **${renderUsername(memberToWarn.user)}** (Case #${warnResult.case.case_number})${messageResultText}`, + ); } diff --git a/backend/src/plugins/ModActions/types.ts b/backend/src/plugins/ModActions/types.ts index a3f36c116..14388ea98 100644 --- a/backend/src/plugins/ModActions/types.ts +++ b/backend/src/plugins/ModActions/types.ts @@ -1,11 +1,12 @@ import { ChatInputCommandInteraction, Message } from "discord.js"; import { EventEmitter } from "events"; import { - BasePluginType, pluginUtils, + BasePluginType, guildPluginEventListener, guildPluginMessageCommand, guildPluginSlashCommand, - guildPluginSlashGroup + guildPluginSlashGroup, + pluginUtils, } from "knub"; import z from "zod"; import { Queue } from "../../Queue"; diff --git a/backend/src/plugins/Mutes/MutesPlugin.ts b/backend/src/plugins/Mutes/MutesPlugin.ts index c1a8c84ba..eeae16df0 100644 --- a/backend/src/plugins/Mutes/MutesPlugin.ts +++ b/backend/src/plugins/Mutes/MutesPlugin.ts @@ -8,6 +8,7 @@ import { GuildLogs } from "../../data/GuildLogs"; import { GuildMutes } from "../../data/GuildMutes"; import { makePublicFn } from "../../pluginUtils"; import { CasesPlugin } from "../Cases/CasesPlugin"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../RoleManager/RoleManagerPlugin.js"; import { ClearBannedMutesCmd } from "./commands/ClearBannedMutesCmd"; @@ -24,7 +25,6 @@ import { onMutesEvent } from "./functions/onMutesEvent"; import { renewTimeoutMute } from "./functions/renewTimeoutMute"; import { unmuteUser } from "./functions/unmuteUser"; import { MutesPluginType, zMutesConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions = { config: { diff --git a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts index 64b77233b..43d2c58ab 100644 --- a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts @@ -1,5 +1,4 @@ import { Snowflake } from "discord.js"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { mutesCmd } from "../types"; export const ClearBannedMutesCmd = mutesCmd({ diff --git a/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts index b8f750228..430627549 100644 --- a/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { mutesCmd } from "../types"; export const ClearMutesCmd = mutesCmd({ @@ -23,14 +22,17 @@ export const ClearMutesCmd = mutesCmd({ } if (failed.length !== args.userIds.length) { - void pluginData.state.common.sendSuccessMessage(msg, `**${args.userIds.length - failed.length} active mute(s) cleared**`); + void pluginData.state.common.sendSuccessMessage( + msg, + `**${args.userIds.length - failed.length} active mute(s) cleared**`, + ); } if (failed.length) { void pluginData.state.common.sendErrorMessage( - msg, - `**${failed.length}/${args.userIds.length} IDs failed**, they are not muted: ${failed.join(" ")}`, - ); + msg, + `**${failed.length}/${args.userIds.length} IDs failed**, they are not muted: ${failed.join(" ")}`, + ); } }, }); diff --git a/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts b/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts index c5f7be130..b73d3b419 100644 --- a/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts @@ -1,6 +1,5 @@ import { Snowflake } from "discord.js"; import { resolveMember } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { mutesCmd } from "../types"; export const ClearMutesWithoutRoleCmd = mutesCmd({ @@ -26,6 +25,9 @@ export const ClearMutesWithoutRoleCmd = mutesCmd({ } } - void pluginData.state.common.sendSuccessMessage(msg, `Cleared ${cleared} mutes from members that don't have the mute role`); + void pluginData.state.common.sendSuccessMessage( + msg, + `Cleared ${cleared} mutes from members that don't have the mute role`, + ); }, }); diff --git a/backend/src/plugins/NameHistory/NameHistoryPlugin.ts b/backend/src/plugins/NameHistory/NameHistoryPlugin.ts index f8d5f9900..617ce019f 100644 --- a/backend/src/plugins/NameHistory/NameHistoryPlugin.ts +++ b/backend/src/plugins/NameHistory/NameHistoryPlugin.ts @@ -2,9 +2,9 @@ import { PluginOptions, guildPlugin } from "knub"; import { Queue } from "../../Queue"; import { GuildNicknameHistory } from "../../data/GuildNicknameHistory"; import { UsernameHistory } from "../../data/UsernameHistory"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { NamesCmd } from "./commands/NamesCmd"; import { NameHistoryPluginType, zNameHistoryConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/NameHistory/commands/NamesCmd.ts b/backend/src/plugins/NameHistory/commands/NamesCmd.ts index 9f3bb4188..3fadaadda 100644 --- a/backend/src/plugins/NameHistory/commands/NamesCmd.ts +++ b/backend/src/plugins/NameHistory/commands/NamesCmd.ts @@ -5,7 +5,6 @@ import { MAX_NICKNAME_ENTRIES_PER_USER } from "../../../data/GuildNicknameHistor import { MAX_USERNAME_ENTRIES_PER_USER } from "../../../data/UsernameHistory"; import { NICKNAME_RETENTION_PERIOD } from "../../../data/cleanup/nicknames"; import { DAYS, renderUsername } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { nameHistoryCmd } from "../types"; export const NamesCmd = nameHistoryCmd({ diff --git a/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts b/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts index 118cf0443..380a2a25e 100644 --- a/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts +++ b/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts @@ -1,9 +1,9 @@ import { PluginOptions, guildPlugin } from "knub"; import { GuildPingableRoles } from "../../data/GuildPingableRoles"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { PingableRoleDisableCmd } from "./commands/PingableRoleDisableCmd"; import { PingableRoleEnableCmd } from "./commands/PingableRoleEnableCmd"; import { PingableRolesPluginType, zPingableRolesConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts b/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts index 3bc1ad581..a66d41a7b 100644 --- a/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts +++ b/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { pingableRolesCmd } from "../types"; export const PingableRoleDisableCmd = pingableRolesCmd({ @@ -14,13 +13,19 @@ export const PingableRoleDisableCmd = pingableRolesCmd({ async run({ message: msg, args, pluginData }) { const pingableRole = await pluginData.state.pingableRoles.getByChannelAndRoleId(args.channelId, args.role.id); if (!pingableRole) { - void pluginData.state.common.sendErrorMessage(msg, `**${args.role.name}** is not set as pingable in <#${args.channelId}>`); + void pluginData.state.common.sendErrorMessage( + msg, + `**${args.role.name}** is not set as pingable in <#${args.channelId}>`, + ); return; } await pluginData.state.pingableRoles.delete(args.channelId, args.role.id); pluginData.state.cache.delete(args.channelId); - void pluginData.state.common.sendSuccessMessage(msg, `**${args.role.name}** is no longer set as pingable in <#${args.channelId}>`); + void pluginData.state.common.sendSuccessMessage( + msg, + `**${args.role.name}** is no longer set as pingable in <#${args.channelId}>`, + ); }, }); diff --git a/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts b/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts index 004c07414..31218b5da 100644 --- a/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts +++ b/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { pingableRolesCmd } from "../types"; export const PingableRoleEnableCmd = pingableRolesCmd({ @@ -17,13 +16,19 @@ export const PingableRoleEnableCmd = pingableRolesCmd({ args.role.id, ); if (existingPingableRole) { - void pluginData.state.common.sendErrorMessage(msg, `**${args.role.name}** is already set as pingable in <#${args.channelId}>`); + void pluginData.state.common.sendErrorMessage( + msg, + `**${args.role.name}** is already set as pingable in <#${args.channelId}>`, + ); return; } await pluginData.state.pingableRoles.add(args.channelId, args.role.id); pluginData.state.cache.delete(args.channelId); - void pluginData.state.common.sendSuccessMessage(msg, `**${args.role.name}** has been set as pingable in <#${args.channelId}>`); + void pluginData.state.common.sendSuccessMessage( + msg, + `**${args.role.name}** has been set as pingable in <#${args.channelId}>`, + ); }, }); diff --git a/backend/src/plugins/Post/PostPlugin.ts b/backend/src/plugins/Post/PostPlugin.ts index 93376dcfd..a96d21b49 100644 --- a/backend/src/plugins/Post/PostPlugin.ts +++ b/backend/src/plugins/Post/PostPlugin.ts @@ -3,6 +3,7 @@ import { onGuildEvent } from "../../data/GuildEvents"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildScheduledPosts } from "../../data/GuildScheduledPosts"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { EditCmd } from "./commands/EditCmd"; @@ -14,7 +15,6 @@ import { ScheduledPostsListCmd } from "./commands/ScheduledPostsListCmd"; import { ScheduledPostsShowCmd } from "./commands/ScheduledPostsShowCmd"; import { PostPluginType, zPostConfig } from "./types"; import { postScheduledPost } from "./util/postScheduledPost"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Post/commands/EditCmd.ts b/backend/src/plugins/Post/commands/EditCmd.ts index fb7859291..9dc7d2d01 100644 --- a/backend/src/plugins/Post/commands/EditCmd.ts +++ b/backend/src/plugins/Post/commands/EditCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; import { formatContent } from "../util/formatContent"; diff --git a/backend/src/plugins/Post/commands/EditEmbedCmd.ts b/backend/src/plugins/Post/commands/EditEmbedCmd.ts index 4cdfdd8b4..3ceff5377 100644 --- a/backend/src/plugins/Post/commands/EditEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/EditEmbedCmd.ts @@ -3,7 +3,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isValidEmbed, trimLines } from "../../../utils"; import { parseColor } from "../../../utils/parseColor"; import { rgbToInt } from "../../../utils/rgbToInt"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; import { formatContent } from "../util/formatContent"; diff --git a/backend/src/plugins/Post/commands/PostEmbedCmd.ts b/backend/src/plugins/Post/commands/PostEmbedCmd.ts index e54649e66..4a71d8afa 100644 --- a/backend/src/plugins/Post/commands/PostEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/PostEmbedCmd.ts @@ -3,7 +3,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isValidEmbed, trimLines } from "../../../utils"; import { parseColor } from "../../../utils/parseColor"; import { rgbToInt } from "../../../utils/rgbToInt"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; import { actualPostCmd } from "../util/actualPostCmd"; import { formatContent } from "../util/formatContent"; diff --git a/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts index d3c6063a0..75602f54d 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsDeleteCmd.ts @@ -1,7 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { clearUpcomingScheduledPost } from "../../../data/loops/upcomingScheduledPostsLoop"; import { sorter } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; export const ScheduledPostsDeleteCmd = postCmd({ diff --git a/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts index a990e6d91..38e450fc4 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts @@ -1,6 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sorter } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { postCmd } from "../types"; import { postMessage } from "../util/postMessage"; diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index b75bcc537..987be7721 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -4,7 +4,6 @@ import { GuildPluginData } from "knub"; import moment from "moment-timezone"; import { registerUpcomingScheduledPost } from "../../../data/loops/upcomingScheduledPostsLoop"; import { DBDateFormat, MINUTES, StrictMessageContent, errorMessage, renderUsername } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { PostPluginType } from "../types"; @@ -40,11 +39,17 @@ export async function actualPostCmd( if (opts.repeat) { if (opts.repeat < MIN_REPEAT_TIME) { - void pluginData.state.common.sendErrorMessage(msg, `Minimum time for -repeat is ${humanizeDuration(MIN_REPEAT_TIME)}`); + void pluginData.state.common.sendErrorMessage( + msg, + `Minimum time for -repeat is ${humanizeDuration(MIN_REPEAT_TIME)}`, + ); return; } if (opts.repeat > MAX_REPEAT_TIME) { - void pluginData.state.common.sendErrorMessage(msg, `Max time for -repeat is ${humanizeDuration(MAX_REPEAT_TIME)}`); + void pluginData.state.common.sendErrorMessage( + msg, + `Max time for -repeat is ${humanizeDuration(MAX_REPEAT_TIME)}`, + ); return; } } @@ -95,12 +100,18 @@ export async function actualPostCmd( } if (repeatUntil && repeatTimes) { - void pluginData.state.common.sendErrorMessage(msg, "You can only use one of -repeat-until or -repeat-times at once"); + void pluginData.state.common.sendErrorMessage( + msg, + "You can only use one of -repeat-until or -repeat-times at once", + ); return; } if (opts.repeat && !repeatUntil && !repeatTimes) { - void pluginData.state.common.sendErrorMessage(msg, "You must specify -repeat-until or -repeat-times for repeated messages"); + void pluginData.state.common.sendErrorMessage( + msg, + "You must specify -repeat-until or -repeat-times for repeated messages", + ); return; } diff --git a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts index 1848e6639..79bcbef08 100644 --- a/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts +++ b/backend/src/plugins/ReactionRoles/ReactionRolesPlugin.ts @@ -2,6 +2,7 @@ import { PluginOptions, guildPlugin } from "knub"; import { Queue } from "../../Queue"; import { GuildReactionRoles } from "../../data/GuildReactionRoles"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { ClearReactionRolesCmd } from "./commands/ClearReactionRolesCmd"; import { InitReactionRolesCmd } from "./commands/InitReactionRolesCmd"; @@ -9,7 +10,6 @@ import { RefreshReactionRolesCmd } from "./commands/RefreshReactionRolesCmd"; import { AddReactionRoleEvt } from "./events/AddReactionRoleEvt"; import { MessageDeletedEvt } from "./events/MessageDeletedEvt"; import { ReactionRolesPluginType, zReactionRolesConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const MIN_AUTO_REFRESH = 1000 * 60 * 15; // 15min minimum, let's not abuse the API diff --git a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts index 18baef48c..44f22c996 100644 --- a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts @@ -1,7 +1,6 @@ import { Message } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isDiscordAPIError } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { reactionRolesCmd } from "../types"; export const ClearReactionRolesCmd = reactionRolesCmd({ diff --git a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts index 1c166039f..35d238652 100644 --- a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts @@ -2,7 +2,6 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canUseEmoji, isDiscordAPIError, isValidEmoji, noop, trimPluginDescription } from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { TReactionRolePair, reactionRolesCmd } from "../types"; import { applyReactionRoleReactionsToMessage } from "../util/applyReactionRoleReactionsToMessage"; @@ -34,7 +33,10 @@ export const InitReactionRolesCmd = reactionRolesCmd({ async run({ message: msg, args, pluginData }) { if (!canReadChannel(args.message.channel, msg.member)) { - void pluginData.state.common.sendErrorMessage(msg, "You can't add reaction roles to channels you can't see yourself"); + void pluginData.state.common.sendErrorMessage( + msg, + "You can't add reaction roles to channels you can't see yourself", + ); return; } @@ -71,7 +73,10 @@ export const InitReactionRolesCmd = reactionRolesCmd({ // Verify the specified emojis and roles are valid and usable for (const pair of emojiRolePairs) { if (pair[0] === CLEAR_ROLES_EMOJI) { - void pluginData.state.common.sendErrorMessage(msg, `The emoji for clearing roles (${CLEAR_ROLES_EMOJI}) is reserved and cannot be used`); + void pluginData.state.common.sendErrorMessage( + msg, + `The emoji for clearing roles (${CLEAR_ROLES_EMOJI}) is reserved and cannot be used`, + ); return; } @@ -81,7 +86,10 @@ export const InitReactionRolesCmd = reactionRolesCmd({ } if (!canUseEmoji(pluginData.client, pair[0])) { - void pluginData.state.common.sendErrorMessage(msg, "I can only use regular emojis and custom emojis from servers I'm on"); + void pluginData.state.common.sendErrorMessage( + msg, + "I can only use regular emojis and custom emojis from servers I'm on", + ); return; } diff --git a/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts index fa0e8ac16..ad2e0c474 100644 --- a/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { reactionRolesCmd } from "../types"; import { refreshReactionRoles } from "../util/refreshReactionRoles"; diff --git a/backend/src/plugins/Reminders/RemindersPlugin.ts b/backend/src/plugins/Reminders/RemindersPlugin.ts index a60701126..0a4d723f5 100644 --- a/backend/src/plugins/Reminders/RemindersPlugin.ts +++ b/backend/src/plugins/Reminders/RemindersPlugin.ts @@ -1,13 +1,13 @@ import { PluginOptions, guildPlugin } from "knub"; import { onGuildEvent } from "../../data/GuildEvents"; import { GuildReminders } from "../../data/GuildReminders"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { RemindCmd } from "./commands/RemindCmd"; import { RemindersCmd } from "./commands/RemindersCmd"; import { RemindersDeleteCmd } from "./commands/RemindersDeleteCmd"; import { postReminder } from "./functions/postReminder"; import { RemindersPluginType, zRemindersConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Reminders/commands/RemindCmd.ts b/backend/src/plugins/Reminders/commands/RemindCmd.ts index d0fb4eb04..7999ad5ab 100644 --- a/backend/src/plugins/Reminders/commands/RemindCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindCmd.ts @@ -3,7 +3,6 @@ import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { registerUpcomingReminder } from "../../../data/loops/upcomingRemindersLoop"; import { convertDelayStringToMS, messageLink } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { remindersCmd } from "../types"; @@ -67,6 +66,9 @@ export const RemindCmd = remindersCmd({ pluginData.getPlugin(TimeAndDatePlugin).getDateFormat("pretty_datetime"), ); - void pluginData.state.common.sendSuccessMessage(msg, `I will remind you in **${timeUntilReminder}** at **${prettyReminderTime}**`); + void pluginData.state.common.sendSuccessMessage( + msg, + `I will remind you in **${timeUntilReminder}** at **${prettyReminderTime}**`, + ); }, }); diff --git a/backend/src/plugins/Reminders/commands/RemindersCmd.ts b/backend/src/plugins/Reminders/commands/RemindersCmd.ts index 9f6dacc79..393c5b249 100644 --- a/backend/src/plugins/Reminders/commands/RemindersCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindersCmd.ts @@ -1,7 +1,6 @@ import humanizeDuration from "humanize-duration"; import moment from "moment-timezone"; import { createChunkedMessage, DBDateFormat, sorter } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { remindersCmd } from "../types"; diff --git a/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts b/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts index 7a57f79b3..5e40f6923 100644 --- a/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts @@ -1,7 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { clearUpcomingReminder } from "../../../data/loops/upcomingRemindersLoop"; import { sorter } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { remindersCmd } from "../types"; export const RemindersDeleteCmd = remindersCmd({ diff --git a/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts b/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts index ecd25e8df..e71c7ed0f 100644 --- a/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts +++ b/backend/src/plugins/RoleButtons/RoleButtonsPlugin.ts @@ -1,12 +1,12 @@ import { guildPlugin } from "knub"; import { GuildRoleButtons } from "../../data/GuildRoleButtons"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../RoleManager/RoleManagerPlugin"; import { resetButtonsCmd } from "./commands/resetButtons"; import { onButtonInteraction } from "./events/buttonInteraction"; import { applyAllRoleButtons } from "./functions/applyAllRoleButtons"; import { RoleButtonsPluginType, zRoleButtonsConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; export const RoleButtonsPlugin = guildPlugin()({ name: "role_buttons", diff --git a/backend/src/plugins/RoleButtons/commands/resetButtons.ts b/backend/src/plugins/RoleButtons/commands/resetButtons.ts index 78b3e6a2a..49add73f3 100644 --- a/backend/src/plugins/RoleButtons/commands/resetButtons.ts +++ b/backend/src/plugins/RoleButtons/commands/resetButtons.ts @@ -1,6 +1,5 @@ import { guildPluginMessageCommand } from "knub"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { applyAllRoleButtons } from "../functions/applyAllRoleButtons"; import { RoleButtonsPluginType } from "../types"; diff --git a/backend/src/plugins/RoleButtons/types.ts b/backend/src/plugins/RoleButtons/types.ts index 42fa6bf9a..b863e7bc8 100644 --- a/backend/src/plugins/RoleButtons/types.ts +++ b/backend/src/plugins/RoleButtons/types.ts @@ -3,9 +3,9 @@ import { BasePluginType, pluginUtils } from "knub"; import z from "zod"; import { GuildRoleButtons } from "../../data/GuildRoleButtons"; import { zBoundedCharacters, zBoundedRecord, zMessageContent, zSnowflake } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { TooManyComponentsError } from "./functions/TooManyComponentsError"; import { createButtonComponents } from "./functions/createButtonComponents"; -import { CommonPlugin } from "../Common/CommonPlugin"; const zRoleButtonOption = z.strictObject({ role_id: zSnowflake, diff --git a/backend/src/plugins/Roles/RolesPlugin.ts b/backend/src/plugins/Roles/RolesPlugin.ts index 4d4bdbb97..117339b8a 100644 --- a/backend/src/plugins/Roles/RolesPlugin.ts +++ b/backend/src/plugins/Roles/RolesPlugin.ts @@ -1,5 +1,6 @@ import { PluginOptions, guildPlugin } from "knub"; import { GuildLogs } from "../../data/GuildLogs"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../RoleManager/RoleManagerPlugin"; import { AddRoleCmd } from "./commands/AddRoleCmd"; @@ -7,7 +8,6 @@ import { MassAddRoleCmd } from "./commands/MassAddRoleCmd"; import { MassRemoveRoleCmd } from "./commands/MassRemoveRoleCmd"; import { RemoveRoleCmd } from "./commands/RemoveRoleCmd"; import { RolesPluginType, zRolesConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Roles/commands/AddRoleCmd.ts b/backend/src/plugins/Roles/commands/AddRoleCmd.ts index 66506c9e6..9ce163469 100644 --- a/backend/src/plugins/Roles/commands/AddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/AddRoleCmd.ts @@ -2,7 +2,6 @@ import { GuildChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn } from "../../../pluginUtils"; import { resolveRoleId, verboseUserMention } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin"; import { rolesCmd } from "../types"; @@ -58,6 +57,9 @@ export const AddRoleCmd = rolesCmd({ roles: [role], }); - void pluginData.state.common.sendSuccessMessage(msg, `Added role **${role.name}** to ${verboseUserMention(args.member.user)}!`); + void pluginData.state.common.sendSuccessMessage( + msg, + `Added role **${role.name}** to ${verboseUserMention(args.member.user)}!`, + ); }, }); diff --git a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts index 991cdcdb6..4a73eaa9e 100644 --- a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts @@ -3,7 +3,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { logger } from "../../../logger"; import { canActOn } from "../../../pluginUtils"; import { resolveMember, resolveRoleId, successMessage } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin"; import { rolesCmd } from "../types"; @@ -30,7 +29,10 @@ export const MassAddRoleCmd = rolesCmd({ for (const member of members) { if (!canActOn(pluginData, msg.member, member, true)) { - void pluginData.state.common.sendErrorMessage(msg, "Cannot add roles to 1 or more specified members: insufficient permissions"); + void pluginData.state.common.sendErrorMessage( + msg, + "Cannot add roles to 1 or more specified members: insufficient permissions", + ); return; } } diff --git a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts index 6ba75d530..bc0d3233e 100644 --- a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts @@ -2,7 +2,6 @@ import { GuildMember } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn } from "../../../pluginUtils"; import { resolveMember, resolveRoleId, successMessage } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin"; import { rolesCmd } from "../types"; @@ -29,7 +28,10 @@ export const MassRemoveRoleCmd = rolesCmd({ for (const member of members) { if (!canActOn(pluginData, msg.member, member, true)) { - void pluginData.state.common.sendErrorMessage(msg, "Cannot add roles to 1 or more specified members: insufficient permissions"); + void pluginData.state.common.sendErrorMessage( + msg, + "Cannot add roles to 1 or more specified members: insufficient permissions", + ); return; } } diff --git a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts index d1b8a7fa0..a8b124798 100644 --- a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts @@ -2,7 +2,6 @@ import { GuildChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn } from "../../../pluginUtils"; import { resolveRoleId, verboseUserMention } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin"; import { rolesCmd } from "../types"; @@ -19,7 +18,10 @@ export const RemoveRoleCmd = rolesCmd({ async run({ message: msg, args, pluginData }) { if (!canActOn(pluginData, msg.member, args.member, true)) { - void pluginData.state.common.sendErrorMessage(msg, "Cannot remove roles from this user: insufficient permissions"); + void pluginData.state.common.sendErrorMessage( + msg, + "Cannot remove roles from this user: insufficient permissions", + ); return; } @@ -57,6 +59,9 @@ export const RemoveRoleCmd = rolesCmd({ roles: [role], }); - void pluginData.state.common.sendSuccessMessage(msg, `Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`); + void pluginData.state.common.sendSuccessMessage( + msg, + `Removed role **${role.name}** from ${verboseUserMention(args.member.user)}!`, + ); }, }); diff --git a/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts b/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts index a6a823d87..e7fbe875f 100644 --- a/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts +++ b/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts @@ -1,9 +1,9 @@ import { CooldownManager, PluginOptions, guildPlugin } from "knub"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { RoleAddCmd } from "./commands/RoleAddCmd"; import { RoleHelpCmd } from "./commands/RoleHelpCmd"; import { RoleRemoveCmd } from "./commands/RoleRemoveCmd"; import { SelfGrantableRolesPluginType, zSelfGrantableRolesConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts index 4dfcacbae..f4e718aae 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts @@ -1,7 +1,6 @@ import { Role, Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { selfGrantableRolesCmd } from "../types"; import { findMatchingRoles } from "../util/findMatchingRoles"; import { getApplyingEntries } from "../util/getApplyingEntries"; @@ -39,9 +38,13 @@ export const RoleAddCmd = selfGrantableRolesCmd({ }, new Map()); if (!rolesToAdd.size) { - void pluginData.state.common.sendErrorMessage(msg, `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, { - users: [msg.author.id], - }); + void pluginData.state.common.sendErrorMessage( + msg, + `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, + { + users: [msg.author.id], + }, + ); lock.unlock(); return; } @@ -81,9 +84,13 @@ export const RoleAddCmd = selfGrantableRolesCmd({ roles: Array.from(newRoleIds) as Snowflake[], }); } catch { - void pluginData.state.common.sendErrorMessage(msg, `<@!${msg.author.id}> Got an error while trying to grant you the roles`, { - users: [msg.author.id], - }); + void pluginData.state.common.sendErrorMessage( + msg, + `<@!${msg.author.id}> Got an error while trying to grant you the roles`, + { + users: [msg.author.id], + }, + ); return; } diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts index 11e4a4cda..1ca76b4da 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts @@ -1,7 +1,6 @@ import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { selfGrantableRolesCmd } from "../types"; import { findMatchingRoles } from "../util/findMatchingRoles"; import { getApplyingEntries } from "../util/getApplyingEntries"; @@ -53,19 +52,31 @@ export const RoleRemoveCmd = selfGrantableRolesCmd({ { users: [msg.author.id] }, ); } else { - void pluginData.state.common.sendSuccessMessage(msg, `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord}`, { - users: [msg.author.id], - }); + void pluginData.state.common.sendSuccessMessage( + msg, + `<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord}`, + { + users: [msg.author.id], + }, + ); } } catch { - void pluginData.state.common.sendSuccessMessage(msg, `<@!${msg.author.id}> Got an error while trying to remove the roles`, { - users: [msg.author.id], - }); + void pluginData.state.common.sendSuccessMessage( + msg, + `<@!${msg.author.id}> Got an error while trying to remove the roles`, + { + users: [msg.author.id], + }, + ); } } else { - void pluginData.state.common.sendErrorMessage(msg, `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, { - users: [msg.author.id], - }); + void pluginData.state.common.sendErrorMessage( + msg, + `<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`, + { + users: [msg.author.id], + }, + ); } lock.unlock(); diff --git a/backend/src/plugins/Slowmode/SlowmodePlugin.ts b/backend/src/plugins/Slowmode/SlowmodePlugin.ts index 8a0b7de98..e82d6d625 100644 --- a/backend/src/plugins/Slowmode/SlowmodePlugin.ts +++ b/backend/src/plugins/Slowmode/SlowmodePlugin.ts @@ -3,6 +3,7 @@ import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSlowmodes } from "../../data/GuildSlowmodes"; import { SECONDS } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { SlowmodeClearCmd } from "./commands/SlowmodeClearCmd"; import { SlowmodeDisableCmd } from "./commands/SlowmodeDisableCmd"; @@ -12,7 +13,6 @@ import { SlowmodeSetCmd } from "./commands/SlowmodeSetCmd"; import { SlowmodePluginType, zSlowmodeConfig } from "./types"; import { clearExpiredSlowmodes } from "./util/clearExpiredSlowmodes"; import { onMessageCreate } from "./util/onMessageCreate"; -import { CommonPlugin } from "../Common/CommonPlugin"; const BOT_SLOWMODE_CLEAR_INTERVAL = 60 * SECONDS; diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts index 29465e79c..c00f1ad97 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts @@ -3,7 +3,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { asSingleLine, renderUsername } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { BOT_SLOWMODE_CLEAR_PERMISSIONS } from "../requiredPermissions"; import { slowmodeCmd } from "../types"; import { clearBotSlowmodeFromUserId } from "../util/clearBotSlowmodeFromUserId"; @@ -29,7 +28,10 @@ export const SlowmodeClearCmd = slowmodeCmd({ const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_CLEAR_PERMISSIONS); if (missingPermissions) { - void pluginData.state.common.sendErrorMessage(msg, `Unable to clear slowmode. ${missingPermissionError(missingPermissions)}`); + void pluginData.state.common.sendErrorMessage( + msg, + `Unable to clear slowmode. ${missingPermissionError(missingPermissions)}`, + ); return; } @@ -57,6 +59,9 @@ export const SlowmodeClearCmd = slowmodeCmd({ return; } - void pluginData.state.common.sendSuccessMessage(msg, `Slowmode cleared from **${renderUsername(args.user)}** in <#${args.channel.id}>`); + void pluginData.state.common.sendSuccessMessage( + msg, + `Slowmode cleared from **${renderUsername(args.user)}** in <#${args.channel.id}>`, + ); }, }); diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts index 6f9510847..84cf6baa1 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts @@ -4,7 +4,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { asSingleLine, DAYS, HOURS, MINUTES } from "../../../utils"; import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { BOT_SLOWMODE_PERMISSIONS, NATIVE_SLOWMODE_PERMISSIONS } from "../requiredPermissions"; import { slowmodeCmd } from "../types"; import { actualDisableSlowmodeCmd } from "../util/actualDisableSlowmodeCmd"; @@ -67,7 +66,10 @@ export const SlowmodeSetCmd = slowmodeCmd({ } if (mode === "bot" && args.time > MAX_BOT_SLOWMODE) { - void pluginData.state.common.sendErrorMessage(msg, `Sorry, bot managed slowmodes can be at most 100 years long. Maybe 99 would be enough?`); + void pluginData.state.common.sendErrorMessage( + msg, + `Sorry, bot managed slowmodes can be at most 100 years long. Maybe 99 would be enough?`, + ); return; } @@ -91,7 +93,10 @@ export const SlowmodeSetCmd = slowmodeCmd({ NATIVE_SLOWMODE_PERMISSIONS, ); if (missingPermissions) { - void pluginData.state.common.sendErrorMessage(msg, `Unable to set native slowmode. ${missingPermissionError(missingPermissions)}`); + void pluginData.state.common.sendErrorMessage( + msg, + `Unable to set native slowmode. ${missingPermissionError(missingPermissions)}`, + ); return; } } @@ -102,7 +107,10 @@ export const SlowmodeSetCmd = slowmodeCmd({ BOT_SLOWMODE_PERMISSIONS, ); if (missingPermissions) { - void pluginData.state.common.sendErrorMessage(msg, `Unable to set bot managed slowmode. ${missingPermissionError(missingPermissions)}`); + void pluginData.state.common.sendErrorMessage( + msg, + `Unable to set bot managed slowmode. ${missingPermissionError(missingPermissions)}`, + ); return; } } @@ -121,7 +129,10 @@ export const SlowmodeSetCmd = slowmodeCmd({ try { await channel.setRateLimitPerUser(rateLimitSeconds); } catch (e) { - void pluginData.state.common.sendErrorMessage(msg, `Failed to set native slowmode: ${escapeInlineCode(e.message)}`); + void pluginData.state.common.sendErrorMessage( + msg, + `Failed to set native slowmode: ${escapeInlineCode(e.message)}`, + ); return; } } else { @@ -140,6 +151,9 @@ export const SlowmodeSetCmd = slowmodeCmd({ const humanizedSlowmodeTime = humanizeDuration(args.time); const slowmodeType = mode === "native" ? "native slowmode" : "bot-maintained slowmode"; - void pluginData.state.common.sendSuccessMessage(msg, `Set ${humanizedSlowmodeTime} slowmode for <#${channel.id}> (${slowmodeType})`); + void pluginData.state.common.sendSuccessMessage( + msg, + `Set ${humanizedSlowmodeTime} slowmode for <#${channel.id}> (${slowmodeType})`, + ); }, }); diff --git a/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts b/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts index da245eb48..642987630 100644 --- a/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts +++ b/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts @@ -2,7 +2,6 @@ import { Message } from "discord.js"; import { noop } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { BOT_SLOWMODE_DISABLE_PERMISSIONS } from "../requiredPermissions"; import { disableBotSlowmodeForChannel } from "./disableBotSlowmodeForChannel"; @@ -18,7 +17,10 @@ export async function actualDisableSlowmodeCmd(msg: Message, args, pluginData) { const me = pluginData.guild.members.cache.get(pluginData.client.user!.id); const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_DISABLE_PERMISSIONS); if (missingPermissions) { - void pluginData.state.common.sendErrorMessage(msg, `Unable to disable slowmode. ${missingPermissionError(missingPermissions)}`); + void pluginData.state.common.sendErrorMessage( + msg, + `Unable to disable slowmode. ${missingPermissionError(missingPermissions)}`, + ); return; } diff --git a/backend/src/plugins/Starboard/StarboardPlugin.ts b/backend/src/plugins/Starboard/StarboardPlugin.ts index 9888b0139..37ac23976 100644 --- a/backend/src/plugins/Starboard/StarboardPlugin.ts +++ b/backend/src/plugins/Starboard/StarboardPlugin.ts @@ -2,12 +2,12 @@ import { PluginOptions, guildPlugin } from "knub"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildStarboardMessages } from "../../data/GuildStarboardMessages"; import { GuildStarboardReactions } from "../../data/GuildStarboardReactions"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { MigratePinsCmd } from "./commands/MigratePinsCmd"; import { StarboardReactionAddEvt } from "./events/StarboardReactionAddEvt"; import { StarboardReactionRemoveAllEvt, StarboardReactionRemoveEvt } from "./events/StarboardReactionRemoveEvts"; import { StarboardPluginType, zStarboardConfig } from "./types"; import { onMessageDelete } from "./util/onMessageDelete"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts b/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts index 114b293f0..448ba465d 100644 --- a/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts +++ b/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts @@ -1,6 +1,5 @@ import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { starboardCmd } from "../types"; import { saveMessageToStarboard } from "../util/saveMessageToStarboard"; @@ -43,6 +42,9 @@ export const MigratePinsCmd = starboardCmd({ await saveMessageToStarboard(pluginData, pin, starboard); } - void pluginData.state.common.sendSuccessMessage(msg, `Pins migrated from <#${args.pinChannel.id}> to <#${starboardChannel.id}>!`); + void pluginData.state.common.sendSuccessMessage( + msg, + `Pins migrated from <#${args.pinChannel.id}> to <#${starboardChannel.id}>!`, + ); }, }); diff --git a/backend/src/plugins/Tags/TagsPlugin.ts b/backend/src/plugins/Tags/TagsPlugin.ts index 97d667c46..cf82440e7 100644 --- a/backend/src/plugins/Tags/TagsPlugin.ts +++ b/backend/src/plugins/Tags/TagsPlugin.ts @@ -8,6 +8,7 @@ import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildTags } from "../../data/GuildTags"; import { makePublicFn } from "../../pluginUtils"; import { convertDelayStringToMS } from "../../utils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { TagCreateCmd } from "./commands/TagCreateCmd"; @@ -20,7 +21,6 @@ import { findTagByName } from "./util/findTagByName"; import { onMessageCreate } from "./util/onMessageCreate"; import { onMessageDelete } from "./util/onMessageDelete"; import { renderTagBody } from "./util/renderTagBody"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Tags/commands/TagCreateCmd.ts b/backend/src/plugins/Tags/commands/TagCreateCmd.ts index 8aab96475..872c63bdc 100644 --- a/backend/src/plugins/Tags/commands/TagCreateCmd.ts +++ b/backend/src/plugins/Tags/commands/TagCreateCmd.ts @@ -1,6 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { TemplateParseError, parseTemplate } from "../../../templateFormatter"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { tagsCmd } from "../types"; export const TagCreateCmd = tagsCmd({ diff --git a/backend/src/plugins/Tags/commands/TagDeleteCmd.ts b/backend/src/plugins/Tags/commands/TagDeleteCmd.ts index 618b67960..66913a5eb 100644 --- a/backend/src/plugins/Tags/commands/TagDeleteCmd.ts +++ b/backend/src/plugins/Tags/commands/TagDeleteCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { tagsCmd } from "../types"; export const TagDeleteCmd = tagsCmd({ diff --git a/backend/src/plugins/Tags/commands/TagEvalCmd.ts b/backend/src/plugins/Tags/commands/TagEvalCmd.ts index 9cdab7749..3cc045e65 100644 --- a/backend/src/plugins/Tags/commands/TagEvalCmd.ts +++ b/backend/src/plugins/Tags/commands/TagEvalCmd.ts @@ -3,7 +3,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { logger } from "../../../logger"; import { TemplateParseError } from "../../../templateFormatter"; import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { tagsCmd } from "../types"; import { renderTagBody } from "../util/renderTagBody"; diff --git a/backend/src/plugins/Tags/commands/TagSourceCmd.ts b/backend/src/plugins/Tags/commands/TagSourceCmd.ts index 0a16c41b4..35be3515b 100644 --- a/backend/src/plugins/Tags/commands/TagSourceCmd.ts +++ b/backend/src/plugins/Tags/commands/TagSourceCmd.ts @@ -1,7 +1,6 @@ import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { getBaseUrl } from "../../../pluginUtils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { tagsCmd } from "../types"; export const TagSourceCmd = tagsCmd({ diff --git a/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts b/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts index 4c84c4ffc..daeaa2ad9 100644 --- a/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts +++ b/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts @@ -1,6 +1,7 @@ import { PluginOptions, guildPlugin } from "knub"; import { GuildMemberTimezones } from "../../data/GuildMemberTimezones"; import { makePublicFn } from "../../pluginUtils"; +import { CommonPlugin } from "../Common/CommonPlugin"; import { ResetTimezoneCmd } from "./commands/ResetTimezoneCmd"; import { SetTimezoneCmd } from "./commands/SetTimezoneCmd"; import { ViewTimezoneCmd } from "./commands/ViewTimezoneCmd"; @@ -11,7 +12,6 @@ import { getMemberTz } from "./functions/getMemberTz"; import { inGuildTz } from "./functions/inGuildTz"; import { inMemberTz } from "./functions/inMemberTz"; import { TimeAndDatePluginType, zTimeAndDateConfig } from "./types"; -import { CommonPlugin } from "../Common/CommonPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts index 88eddd18b..1a9c1daf1 100644 --- a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts @@ -1,4 +1,3 @@ -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getGuildTz } from "../functions/getGuildTz"; import { timeAndDateCmd } from "../types"; @@ -11,6 +10,9 @@ export const ResetTimezoneCmd = timeAndDateCmd({ async run({ pluginData, message }) { await pluginData.state.memberTimezones.reset(message.author.id); const serverTimezone = getGuildTz(pluginData); - void pluginData.state.common.sendSuccessMessage(message, `Your timezone has been reset to server default, **${serverTimezone}**`); + void pluginData.state.common.sendSuccessMessage( + message, + `Your timezone has been reset to server default, **${serverTimezone}**`, + ); }, }); diff --git a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts index 684f60f76..092edadc9 100644 --- a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts @@ -2,7 +2,6 @@ import { escapeInlineCode } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { trimLines } from "../../../utils"; import { parseFuzzyTimezone } from "../../../utils/parseFuzzyTimezone"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { timeAndDateCmd } from "../types"; export const SetTimezoneCmd = timeAndDateCmd({ diff --git a/backend/src/plugins/TimeAndDate/types.ts b/backend/src/plugins/TimeAndDate/types.ts index 5bb5253d3..1890e3d44 100644 --- a/backend/src/plugins/TimeAndDate/types.ts +++ b/backend/src/plugins/TimeAndDate/types.ts @@ -4,8 +4,8 @@ import z from "zod"; import { GuildMemberTimezones } from "../../data/GuildMemberTimezones"; import { keys } from "../../utils"; import { zValidTimezone } from "../../utils/zValidTimezone"; -import { defaultDateFormats } from "./defaultDateFormats"; import { CommonPlugin } from "../Common/CommonPlugin"; +import { defaultDateFormats } from "./defaultDateFormats"; const zDateFormatKeys = z.enum(keys(defaultDateFormats) as U.ListOf); diff --git a/backend/src/plugins/Utility/commands/AvatarCmd.ts b/backend/src/plugins/Utility/commands/AvatarCmd.ts index b0b7ce7ed..4fb146e76 100644 --- a/backend/src/plugins/Utility/commands/AvatarCmd.ts +++ b/backend/src/plugins/Utility/commands/AvatarCmd.ts @@ -1,7 +1,6 @@ import { APIEmbed, ImageFormat } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { UnknownUser, renderUsername } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const AvatarCmd = utilityCmd({ diff --git a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts index dc0901bc3..d39d53649 100644 --- a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; import { utilityCmd } from "../types"; diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index 6eac645eb..fb06f6897 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -8,7 +8,6 @@ import { humanizeDurationShort } from "../../../humanizeDurationShort"; import { getBaseUrl } from "../../../pluginUtils"; import { ModActionsPlugin } from "../../../plugins/ModActions/ModActionsPlugin"; import { DAYS, SECONDS, chunkArray, getInviteCodesInString, noop } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { UtilityPluginType, utilityCmd } from "../types"; @@ -94,7 +93,12 @@ export async function cleanCmd(pluginData: GuildPluginData, a const targetChannel = args.channel ? pluginData.guild.channels.cache.get(args.channel as Snowflake) : msg.channel; if (!targetChannel?.isTextBased()) { - void pluginData.state.common.sendErrorMessage(msg, `Invalid channel specified`, undefined, args["response-interaction"]); + void pluginData.state.common.sendErrorMessage( + msg, + `Invalid channel specified`, + undefined, + args["response-interaction"], + ); return; } @@ -217,10 +221,20 @@ export async function cleanCmd(pluginData: GuildPluginData, a } } - responseMsg = await pluginData.state.common.sendSuccessMessage(msg, responseText, undefined, args["response-interaction"]); + responseMsg = await pluginData.state.common.sendSuccessMessage( + msg, + responseText, + undefined, + args["response-interaction"], + ); } else { const responseText = `Found no messages to clean${note ? ` (${note})` : ""}!`; - responseMsg = await pluginData.state.common.sendErrorMessage(msg, responseText, undefined, args["response-interaction"]); + responseMsg = await pluginData.state.common.sendErrorMessage( + msg, + responseText, + undefined, + args["response-interaction"], + ); } cleaningMessage?.delete(); diff --git a/backend/src/plugins/Utility/commands/ContextCmd.ts b/backend/src/plugins/Utility/commands/ContextCmd.ts index 9db76606d..0bbb8d682 100644 --- a/backend/src/plugins/Utility/commands/ContextCmd.ts +++ b/backend/src/plugins/Utility/commands/ContextCmd.ts @@ -2,7 +2,6 @@ import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { messageLink } from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const ContextCmd = utilityCmd({ diff --git a/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts b/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts index e5806aec5..ff1a8fa71 100644 --- a/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getCustomEmojiId } from "../functions/getCustomEmojiId"; import { getEmojiInfoEmbed } from "../functions/getEmojiInfoEmbed"; import { utilityCmd } from "../types"; diff --git a/backend/src/plugins/Utility/commands/InfoCmd.ts b/backend/src/plugins/Utility/commands/InfoCmd.ts index 9314dbce1..75f121973 100644 --- a/backend/src/plugins/Utility/commands/InfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InfoCmd.ts @@ -4,7 +4,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isValidSnowflake, noop, parseInviteCodeInput, resolveInvite, resolveUser } from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; import { resolveMessageTarget } from "../../../utils/resolveMessageTarget"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; import { getCustomEmojiId } from "../functions/getCustomEmojiId"; import { getEmojiInfoEmbed } from "../functions/getEmojiInfoEmbed"; diff --git a/backend/src/plugins/Utility/commands/InviteInfoCmd.ts b/backend/src/plugins/Utility/commands/InviteInfoCmd.ts index bbecd7941..37d8493b3 100644 --- a/backend/src/plugins/Utility/commands/InviteInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InviteInfoCmd.ts @@ -1,6 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { parseInviteCodeInput } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getInviteInfoEmbed } from "../functions/getInviteInfoEmbed"; import { utilityCmd } from "../types"; diff --git a/backend/src/plugins/Utility/commands/JumboCmd.ts b/backend/src/plugins/Utility/commands/JumboCmd.ts index d29dc608f..11d42a048 100644 --- a/backend/src/plugins/Utility/commands/JumboCmd.ts +++ b/backend/src/plugins/Utility/commands/JumboCmd.ts @@ -4,7 +4,6 @@ import fs from "fs"; import twemoji from "twemoji"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { downloadFile, isEmoji, SECONDS } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; const fsp = fs.promises; diff --git a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts index 4ea7da69f..1b4496b83 100644 --- a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts @@ -1,6 +1,5 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canReadChannel } from "../../../utils/canReadChannel"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getMessageInfoEmbed } from "../functions/getMessageInfoEmbed"; import { utilityCmd } from "../types"; diff --git a/backend/src/plugins/Utility/commands/NicknameCmd.ts b/backend/src/plugins/Utility/commands/NicknameCmd.ts index fd33a7556..ec274703d 100644 --- a/backend/src/plugins/Utility/commands/NicknameCmd.ts +++ b/backend/src/plugins/Utility/commands/NicknameCmd.ts @@ -2,7 +2,6 @@ import { escapeBold } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn } from "../../../pluginUtils"; import { errorMessage } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const NicknameCmd = utilityCmd({ diff --git a/backend/src/plugins/Utility/commands/NicknameResetCmd.ts b/backend/src/plugins/Utility/commands/NicknameResetCmd.ts index 8fd23c846..34ae2cbb7 100644 --- a/backend/src/plugins/Utility/commands/NicknameResetCmd.ts +++ b/backend/src/plugins/Utility/commands/NicknameResetCmd.ts @@ -1,7 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn } from "../../../pluginUtils"; import { errorMessage } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const NicknameResetCmd = utilityCmd({ diff --git a/backend/src/plugins/Utility/commands/RolesCmd.ts b/backend/src/plugins/Utility/commands/RolesCmd.ts index 08ec5dc15..a9d9dc1d2 100644 --- a/backend/src/plugins/Utility/commands/RolesCmd.ts +++ b/backend/src/plugins/Utility/commands/RolesCmd.ts @@ -1,7 +1,6 @@ import { Role } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { chunkArray, sorter, trimLines } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { refreshMembersIfNeeded } from "../refreshMembers"; import { utilityCmd } from "../types"; diff --git a/backend/src/plugins/Utility/commands/ServerInfoCmd.ts b/backend/src/plugins/Utility/commands/ServerInfoCmd.ts index 0388d8cda..94a9c91d5 100644 --- a/backend/src/plugins/Utility/commands/ServerInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/ServerInfoCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getServerInfoEmbed } from "../functions/getServerInfoEmbed"; import { utilityCmd } from "../types"; diff --git a/backend/src/plugins/Utility/commands/SourceCmd.ts b/backend/src/plugins/Utility/commands/SourceCmd.ts index 5fccc1095..e51f94d3d 100644 --- a/backend/src/plugins/Utility/commands/SourceCmd.ts +++ b/backend/src/plugins/Utility/commands/SourceCmd.ts @@ -2,7 +2,6 @@ import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { getBaseUrl } from "../../../pluginUtils"; import { canReadChannel } from "../../../utils/canReadChannel"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { utilityCmd } from "../types"; export const SourceCmd = utilityCmd({ diff --git a/backend/src/plugins/Utility/commands/UserInfoCmd.ts b/backend/src/plugins/Utility/commands/UserInfoCmd.ts index 9f2698a92..274fbbbcd 100644 --- a/backend/src/plugins/Utility/commands/UserInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/UserInfoCmd.ts @@ -1,5 +1,4 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { getUserInfoEmbed } from "../functions/getUserInfoEmbed"; import { utilityCmd } from "../types"; diff --git a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts index 797f5dda5..299b27a62 100644 --- a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts +++ b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts @@ -2,7 +2,6 @@ import { VoiceChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn } from "../../../pluginUtils"; import { renderUsername } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { utilityCmd } from "../types"; @@ -43,7 +42,7 @@ export const VcdisconnectCmd = utilityCmd({ pluginData.state.common.sendSuccessMessage( msg, - `**${renderUsername(args.member)}** disconnected from **${channel.name}**` + `**${renderUsername(args.member)}** disconnected from **${channel.name}**`, ); }, }); diff --git a/backend/src/plugins/Utility/commands/VcmoveCmd.ts b/backend/src/plugins/Utility/commands/VcmoveCmd.ts index eee432c7f..a037d379d 100644 --- a/backend/src/plugins/Utility/commands/VcmoveCmd.ts +++ b/backend/src/plugins/Utility/commands/VcmoveCmd.ts @@ -2,7 +2,6 @@ import { ChannelType, Snowflake, VoiceChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn } from "../../../pluginUtils"; import { channelMentionRegex, isSnowflake, renderUsername, simpleClosestStringMatch } from "../../../utils"; -import { CommonPlugin } from "../../Common/CommonPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { utilityCmd } from "../types"; @@ -81,7 +80,10 @@ export const VcmoveCmd = utilityCmd({ newChannel: channel, }); - void pluginData.state.common.sendSuccessMessage(msg, `**${renderUsername(args.member)}** moved to **${channel.name}**`); + void pluginData.state.common.sendSuccessMessage( + msg, + `**${renderUsername(args.member)}** moved to **${channel.name}**`, + ); }, }); @@ -168,7 +170,10 @@ export const VcmoveAllCmd = utilityCmd({ void pluginData.state.common.sendErrorMessage(msg, "Unknown error when trying to move members"); return; } - void pluginData.state.common.sendErrorMessage(msg, `Failed to move ${renderUsername(currMember)} (${currMember.id})`); + void pluginData.state.common.sendErrorMessage( + msg, + `Failed to move ${renderUsername(currMember)} (${currMember.id})`, + ); errAmt++; continue; }