diff --git a/backend/src/data/GuildCases.ts b/backend/src/data/GuildCases.ts index 37396c3f..965d040f 100644 --- a/backend/src/data/GuildCases.ts +++ b/backend/src/data/GuildCases.ts @@ -62,6 +62,19 @@ export class GuildCases extends BaseGuildRepository { }); } + async findFirstByModId(modId: string): Promise { + return this.cases.findOne({ + relations: this.getRelations(), + where: { + guild_id: this.guildId, + mod_id: modId, + }, + order: { + case_number: "ASC", + }, + }); + } + async findByAuditLogId(auditLogId: string): Promise { return this.cases.findOne({ relations: this.getRelations(), @@ -82,6 +95,16 @@ export class GuildCases extends BaseGuildRepository { }); } + async getByModId(modId: string): Promise { + return this.cases.find({ + relations: this.getRelations(), + where: { + guild_id: this.guildId, + mod_id: modId, + }, + }); + } + async getRecentByModId(modId: string, count: number): Promise { return this.cases.find({ relations: this.getRelations(), diff --git a/backend/src/plugins/Cases/CasesPlugin.ts b/backend/src/plugins/Cases/CasesPlugin.ts index b4ce8e7f..529269c9 100644 --- a/backend/src/plugins/Cases/CasesPlugin.ts +++ b/backend/src/plugins/Cases/CasesPlugin.ts @@ -14,6 +14,7 @@ import { trimPluginDescription } from "../../utils"; import { getCaseSummary } from "./functions/getCaseSummary"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { mapToPublicFn } from "../../pluginUtils"; +import { ModStatsCmd } from "./commands/ModStatsCmd"; const defaultOptions = { config: { @@ -23,7 +24,16 @@ const defaultOptions = { relative_time_cutoff: "7d", case_colors: null, case_icons: null, + can_modstats: false, }, + overrides: [ + { + level: ">=100", + config: { + can_modstats: true, + }, + }, + ], }; export const CasesPlugin = zeppelinGuildPlugin()("cases", { @@ -35,6 +45,8 @@ export const CasesPlugin = zeppelinGuildPlugin()("cases", { `), }, + commands: [ModStatsCmd], + dependencies: [TimeAndDatePlugin], configSchema: ConfigSchema, defaultOptions, diff --git a/backend/src/plugins/Cases/commands/ModStatsCmd.ts b/backend/src/plugins/Cases/commands/ModStatsCmd.ts new file mode 100644 index 00000000..8faafa78 --- /dev/null +++ b/backend/src/plugins/Cases/commands/ModStatsCmd.ts @@ -0,0 +1,54 @@ +import { casesCommand } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { getAllCaseTypeAmountsForUserId } from "../functions/getAllCaseTypeAmountsForUserId"; +import { sendErrorMessage } from "src/pluginUtils"; +import { EmbedOptions } from "eris"; +import { CaseTypes } from "src/data/CaseTypes"; +import moment from "moment-timezone"; + +export const ModStatsCmd = casesCommand({ + trigger: "modstats", + permission: "can_modstats", + description: "Show moderation statistics for a given user", + + signature: [ + { + moderator: ct.resolvedUser(), + }, + ], + + async run({ pluginData, message: msg, args }) { + const firstCase = await pluginData.state.cases.findFirstByModId(args.moderator.id); + if (!firstCase) { + sendErrorMessage(pluginData, msg.channel, "The specified user never created any cases"); + return; + } + const latestCase = await pluginData.state.cases.findLatestByModId(args.moderator.id); + + const allAmounts = await getAllCaseTypeAmountsForUserId(pluginData, args.moderator.id); + + const embed: EmbedOptions = { + fields: [], + }; + + embed.author = { + name: `${args.moderator.username}#${args.moderator.discriminator}`, + icon_url: args.moderator.avatarURL, + }; + + let embedDesc = `**The user created ${allAmounts.total} cases:**\n`; + for (const type in CaseTypes) { + if (!isNaN(Number(type))) { + const typeAmount = allAmounts.typeAmounts.get(Number(type)).amount; + if (typeAmount <= 0) continue; + + embedDesc += `\n**${CaseTypes[type]}:** ${typeAmount}`; + } + } + embedDesc += `\n\n**First case on:** ${moment(firstCase.created_at).format("LL")}`; + embedDesc += `\n**Latest case on:** ${moment(latestCase.created_at).format("LL")}`; + embed.description = embedDesc; + + msg.channel.createMessage({ embed }); + }, +}); diff --git a/backend/src/plugins/Cases/functions/getAllCaseTypeAmountsForUserId.ts b/backend/src/plugins/Cases/functions/getAllCaseTypeAmountsForUserId.ts new file mode 100644 index 00000000..edec45b4 --- /dev/null +++ b/backend/src/plugins/Cases/functions/getAllCaseTypeAmountsForUserId.ts @@ -0,0 +1,31 @@ +import { PluginData } from "knub"; +import { CasesPluginType } from "../types"; +import { CaseTypes } from "../../../data/CaseTypes"; + +export async function getAllCaseTypeAmountsForUserId( + pluginData: PluginData, + userID: string, +): Promise { + const allAmounts = new Map(); + for (const type in CaseTypes) { + if (!isNaN(Number(type))) allAmounts.set(Number(type), { amount: 0 }); + } + + let total = 0; + + const cases = (await pluginData.state.cases.getByModId(userID)).filter(c => !c.is_hidden); + + if (cases.length > 0) { + cases.forEach(singleCase => { + allAmounts.get(singleCase.type).amount++; + total++; + }); + } + + return { typeAmounts: allAmounts, total }; +} + +class AllTypeAmounts { + typeAmounts: Map; + total: number; +} diff --git a/backend/src/plugins/Cases/types.ts b/backend/src/plugins/Cases/types.ts index 1a00a814..31c7df46 100644 --- a/backend/src/plugins/Cases/types.ts +++ b/backend/src/plugins/Cases/types.ts @@ -14,6 +14,7 @@ export const ConfigSchema = t.type({ relative_time_cutoff: tDelayString, case_colors: tNullable(tPartialDictionary(t.keyof(CaseNameToType), tColor)), case_icons: tNullable(tPartialDictionary(t.keyof(CaseNameToType), t.string)), + can_modstats: t.boolean, }); export type TConfigSchema = t.TypeOf; @@ -50,3 +51,5 @@ export type CaseNoteArgs = { postInCaseLogOverride?: boolean; noteDetails?: string[]; }; + +export const casesCommand = command();