From 37da2ae5057d68dccb218af30e31545a9eced536 Mon Sep 17 00:00:00 2001 From: seeyebe Date: Sun, 16 Nov 2025 02:05:00 +0000 Subject: [PATCH] feat: add reason filtering to cases command --- backend/src/data/GuildCases.ts | 24 +++++++ .../commands/cases/CasesModMsgCmd.ts | 4 +- .../commands/cases/CasesSlashCmd.ts | 6 ++ .../commands/cases/CasesUserMsgCmd.ts | 4 +- .../commands/cases/actualCasesCmd.ts | 64 ++++++++++++++----- 5 files changed, 85 insertions(+), 17 deletions(-) diff --git a/backend/src/data/GuildCases.ts b/backend/src/data/GuildCases.ts index ea834351f..b75f666dd 100644 --- a/backend/src/data/GuildCases.ts +++ b/backend/src/data/GuildCases.ts @@ -144,6 +144,30 @@ export class GuildCases extends BaseGuildRepository { }); } + async getByModId( + modId: string, + 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, + order: { + case_number: "DESC", + }, + }); + } + async getMinCaseNumber(): Promise { const result = await this.cases .createQueryBuilder() diff --git a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts index 92fe53f3a..5e92f0b46 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesModMsgCmd.ts @@ -7,7 +7,8 @@ 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" }), + reverseFilters: ct.switchOption({ def: false, shortcut: "rf" }), + reason: ct.string({ option: true, shortcut: "r" }), notes: ct.switchOption({ def: false, shortcut: "n" }), warns: ct.switchOption({ def: false, shortcut: "w" }), mutes: ct.switchOption({ def: false, shortcut: "m" }), @@ -45,6 +46,7 @@ export const CasesModMsgCmd = modActionsMsgCmd({ args.bans, args.unbans, args.reverseFilters, + args.reason ?? null, 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 eec7f1916..feab16703 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesSlashCmd.ts @@ -13,6 +13,11 @@ const opts = [ description: "To treat case type filters as exclusive instead of inclusive", required: false, }), + slashOptions.string({ + name: "reason", + description: "Filter cases containing this text in the case reason", + 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 }), @@ -48,6 +53,7 @@ export const CasesSlashCmd = modActionsSlashCmd({ options.bans, options.unbans, options["reverse-filters"], + options.reason ?? null, 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 88cca4384..ed899844b 100644 --- a/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/CasesUserMsgCmd.ts @@ -8,7 +8,8 @@ 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" }), + reverseFilters: ct.switchOption({ def: false, shortcut: "rf" }), + reason: ct.string({ option: true, shortcut: "r" }), notes: ct.switchOption({ def: false, shortcut: "n" }), warns: ct.switchOption({ def: false, shortcut: "w" }), mutes: ct.switchOption({ def: false, shortcut: "m" }), @@ -58,6 +59,7 @@ export const CasesUserMsgCmd = modActionsMsgCmd({ args.bans, args.unbans, args.reverseFilters, + args.reason ?? null, args.hidden, args.expand, args.show, diff --git a/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts b/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts index b0be8439c..3ce95bc9d 100644 --- a/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts +++ b/backend/src/plugins/ModActions/commands/cases/actualCasesCmd.ts @@ -22,6 +22,14 @@ import { ModActionsPluginType } from "../../types.js"; const casesPerPage = 5; const maxExpandedCases = 8; +function filterCasesByReason(cases: Case[], reason: string): Case[] { + const normalizedReason = reason.toLowerCase(); + + return cases.filter((theCase) => + theCase.notes?.some((note) => note.body.toLowerCase().includes(normalizedReason)), + ); +} + async function sendExpandedCases( pluginData: GuildPluginData, context: Message | ChatInputCommandInteraction, @@ -54,6 +62,7 @@ async function casesUserCmd( user: GuildMember | User | UnknownUser, modName: string, typesToShow: CaseTypes[], + reason: string | null, hidden: boolean | null, expand: boolean | null, show: boolean | null, @@ -66,26 +75,29 @@ async function casesUserCmd( } 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 matchingCases = reason ? filterCasesByReason(cases, reason) : cases; + const normalCases = matchingCases.filter((c) => !c.is_hidden); + const hiddenCases = matchingCases.filter((c) => c.is_hidden); const userName = user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUsername(user); - if (cases.length === 0) { + if (matchingCases.length === 0) { await sendContextResponse(context, { - content: `No cases found for **${userName}**${modId ? ` by ${modName}` : ""}.`, + content: `No cases found for **${userName}**${modId ? ` by ${modName}` : ""}${ + reason ? ` with reason matching "${reason}"` : "" + }.`, ephemeral: !show, }); return; } - const casesToDisplay = hidden ? cases : normalCases; + const casesToDisplay = hidden ? matchingCases : normalCases; if (!casesToDisplay.length) { await sendContextResponse(context, { - content: `No normal cases found for **${userName}**. Use "-hidden" to show ${cases.length} hidden cases.`, + content: `No normal cases found for **${userName}**. Use "-hidden" to show ${matchingCases.length} hidden cases.`, ephemeral: !show, }); @@ -148,6 +160,7 @@ async function casesModCmd( mod: GuildMember | User | UnknownUser, modName: string, typesToShow: CaseTypes[], + reason: string | null, hidden: boolean | null, expand: boolean | null, show: boolean | null, @@ -155,10 +168,23 @@ async function casesModCmd( const casesPlugin = pluginData.getPlugin(CasesPlugin); const casesFilters = { type: In(typesToShow), is_hidden: !!hidden }; - const totalCases = await casesPlugin.getTotalCasesByMod(modId ?? author.id, casesFilters); + const filteredCases = reason + ? filterCasesByReason( + await pluginData.state.cases.with("notes").getByModId(modId ?? author.id, casesFilters), + reason, + ) + : null; + + const totalCases = filteredCases?.length ?? (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}**${reason ? ` with reason matching "${reason}"` : ""}`, + undefined, + undefined, + !show, + ); return; } @@ -168,7 +194,10 @@ async function casesModCmd( if (expand) { // Expanded view (= individual case embeds) - const cases = totalCases > 8 ? [] : await casesPlugin.getRecentCasesByMod(modId ?? author.id, 8, 0, casesFilters); + const cases = + filteredCases && totalCases > maxExpandedCases + ? filteredCases.slice(0, maxExpandedCases) + : filteredCases ?? (await casesPlugin.getRecentCasesByMod(modId ?? author.id, 8, 0, casesFilters)); await sendExpandedCases(pluginData, context, totalCases, cases, show); return; @@ -179,12 +208,14 @@ async function casesModCmd( context, totalPages, async (page) => { - const cases = await casesPlugin.getRecentCasesByMod( - modId ?? author.id, - casesPerPage, - (page - 1) * casesPerPage, - casesFilters, - ); + const cases = filteredCases + ? filteredCases.slice((page - 1) * casesPerPage, (page - 1) * casesPerPage + casesPerPage) + : await casesPlugin.getRecentCasesByMod( + modId ?? author.id, + casesPerPage, + (page - 1) * casesPerPage, + casesFilters, + ); const lines = await asyncMap(cases, (c) => casesPlugin.getCaseSummary(c, true, author.id)); const firstCaseNum = (page - 1) * casesPerPage + 1; @@ -230,6 +261,7 @@ export async function actualCasesCmd( bans: boolean | null, unbans: boolean | null, reverseFilters: boolean | null, + reason: string | null, hidden: boolean | null, expand: boolean | null, show: boolean | null, @@ -275,6 +307,7 @@ export async function actualCasesCmd( user, modName, typesToShow, + reason, hidden, expand, show === true, @@ -287,6 +320,7 @@ export async function actualCasesCmd( mod ?? author, modName, typesToShow, + reason, hidden, expand, show === true,