From 4448659dc0a7ceaff3028f3373ba76c2b07dfa88 Mon Sep 17 00:00:00 2001
From: Almeida <almeidx@pm.me>
Date: Wed, 28 Apr 2021 19:59:56 +0100
Subject: [PATCH 1/7] fix(SetCounterCmd): misleading messages (#188)

---
 backend/src/plugins/Counters/commands/SetCounterCmd.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/backend/src/plugins/Counters/commands/SetCounterCmd.ts b/backend/src/plugins/Counters/commands/SetCounterCmd.ts
index edabe2e7..0503b2fd 100644
--- a/backend/src/plugins/Counters/commands/SetCounterCmd.ts
+++ b/backend/src/plugins/Counters/commands/SetCounterCmd.ts
@@ -67,7 +67,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
 
     let channel = args.channel;
     if (!channel && counter.per_channel) {
-      message.channel.createMessage(`Which channel's counter value would you like to add to?`);
+      message.channel.createMessage(`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");
@@ -85,7 +85,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
 
     let user = args.user;
     if (!user && counter.per_user) {
-      message.channel.createMessage(`Which user's counter value would you like to add to?`);
+      message.channel.createMessage(`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");
@@ -103,7 +103,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
 
     let value = args.value;
     if (!value) {
-      message.channel.createMessage("How much would you like to add to the counter's value?");
+      message.channel.createMessage("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");

From 00f368d62bab6a73a780b6fea6590c0620c89ca0 Mon Sep 17 00:00:00 2001
From: Shoaib Sajid <zodpixel@gmail.com>
Date: Thu, 29 Apr 2021 00:02:25 +0500
Subject: [PATCH 2/7] Fix typo in automod docs (#185)

---
 backend/src/plugins/Automod/info.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/backend/src/plugins/Automod/info.ts b/backend/src/plugins/Automod/info.ts
index c2c87dbf..9fcdced0 100644
--- a/backend/src/plugins/Automod/info.ts
+++ b/backend/src/plugins/Automod/info.ts
@@ -64,9 +64,9 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
                   reason: 'Auto-muted for spam'
             my_second_filter:
               triggers:
-              - message_spam:
-                  amount: 5
-                  within: 10s
+              - emoji_spam:
+                  amount: 2
+                  within: 5s
               actions:
                 clean: true
         overrides:

From 6b9131c353a578eb3af839a620f7edd9e4072306 Mon Sep 17 00:00:00 2001
From: Usoka <27248545+Usoka@users.noreply.github.com>
Date: Thu, 29 Apr 2021 07:03:26 +1200
Subject: [PATCH 3/7] Fix 0 not being accepted in SetCounterCmd (#186)

---
 backend/src/plugins/Counters/commands/SetCounterCmd.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/backend/src/plugins/Counters/commands/SetCounterCmd.ts b/backend/src/plugins/Counters/commands/SetCounterCmd.ts
index 0503b2fd..f74c2045 100644
--- a/backend/src/plugins/Counters/commands/SetCounterCmd.ts
+++ b/backend/src/plugins/Counters/commands/SetCounterCmd.ts
@@ -111,7 +111,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
       }
 
       const potentialValue = parseInt(reply.content, 10);
-      if (!potentialValue) {
+      if (Number.isNaN(potentialValue)) {
         sendErrorMessage(pluginData, message.channel, "Not a number, cancelling");
         return;
       }

From 90b6f4bc86d6e5756dbcc97956cfc05b99176643 Mon Sep 17 00:00:00 2001
From: Shoaib Sajid <zodpixel@gmail.com>
Date: Thu, 29 Apr 2021 00:04:01 +0500
Subject: [PATCH 4/7] Add !reminder as an alias for !remind (#181)

---
 backend/src/plugins/Reminders/commands/RemindCmd.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/backend/src/plugins/Reminders/commands/RemindCmd.ts b/backend/src/plugins/Reminders/commands/RemindCmd.ts
index 67a2cfc1..ad9cd355 100644
--- a/backend/src/plugins/Reminders/commands/RemindCmd.ts
+++ b/backend/src/plugins/Reminders/commands/RemindCmd.ts
@@ -7,7 +7,7 @@ import { remindersCmd } from "../types";
 import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
 
 export const RemindCmd = remindersCmd({
-  trigger: ["remind", "remindme"],
+  trigger: ["remind", "remindme", "reminder"],
   usage: "!remind 3h Remind me of this in 3 hours please",
   permission: "can_use",
 

From 51db942d977cfae2b0411248e0991d30b0b398cb Mon Sep 17 00:00:00 2001
From: Nils <7890309+DarkView@users.noreply.github.com>
Date: Wed, 28 Apr 2021 21:06:33 +0200
Subject: [PATCH 5/7] Allow Automod to distinguish whether mod actions are
 manual or automatic (#179)

---
 backend/src/plugins/Automod/AutomodPlugin.ts  | 24 +++++++++++++------
 backend/src/plugins/Automod/actions/ban.ts    |  7 +++++-
 backend/src/plugins/Automod/actions/kick.ts   |  2 +-
 backend/src/plugins/Automod/actions/mute.ts   |  9 ++++++-
 backend/src/plugins/Automod/actions/warn.ts   |  2 +-
 .../Automod/events/runAutomodOnModAction.ts   |  2 ++
 backend/src/plugins/Automod/triggers/ban.ts   | 18 +++++++++++---
 backend/src/plugins/Automod/triggers/kick.ts  | 17 ++++++++++---
 backend/src/plugins/Automod/triggers/mute.ts  | 17 ++++++++++---
 backend/src/plugins/Automod/triggers/warn.ts  | 17 ++++++++++---
 backend/src/plugins/Automod/types.ts          |  1 +
 .../plugins/ModActions/commands/WarnCmd.ts    |  2 --
 .../plugins/ModActions/functions/banUserId.ts |  2 +-
 .../ModActions/functions/kickMember.ts        |  2 +-
 .../ModActions/functions/warnMember.ts        |  2 ++
 backend/src/plugins/ModActions/types.ts       |  9 ++++---
 .../src/plugins/Mutes/functions/muteUser.ts   |  2 +-
 backend/src/plugins/Mutes/types.ts            |  3 ++-
 18 files changed, 106 insertions(+), 32 deletions(-)

diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts
index 7bebd606..2a6cf870 100644
--- a/backend/src/plugins/Automod/AutomodPlugin.ts
+++ b/backend/src/plugins/Automod/AutomodPlugin.ts
@@ -235,14 +235,20 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
     pluginData.state.modActionsListeners.set("note", (userId: string) =>
       runAutomodOnModAction(pluginData, "note", userId),
     );
-    pluginData.state.modActionsListeners.set("warn", (userId: string) =>
-      runAutomodOnModAction(pluginData, "warn", userId),
+    pluginData.state.modActionsListeners.set(
+      "warn",
+      (userId: string, reason: string | undefined, isAutomodAction: boolean) =>
+        runAutomodOnModAction(pluginData, "warn", userId, reason, isAutomodAction),
     );
-    pluginData.state.modActionsListeners.set("kick", (userId: string) =>
-      runAutomodOnModAction(pluginData, "kick", userId),
+    pluginData.state.modActionsListeners.set(
+      "kick",
+      (userId: string, reason: string | undefined, isAutomodAction: boolean) =>
+        runAutomodOnModAction(pluginData, "kick", userId, reason, isAutomodAction),
     );
-    pluginData.state.modActionsListeners.set("ban", (userId: string) =>
-      runAutomodOnModAction(pluginData, "ban", userId),
+    pluginData.state.modActionsListeners.set(
+      "ban",
+      (userId: string, reason: string | undefined, isAutomodAction: boolean) =>
+        runAutomodOnModAction(pluginData, "ban", userId, reason, isAutomodAction),
     );
     pluginData.state.modActionsListeners.set("unban", (userId: string) =>
       runAutomodOnModAction(pluginData, "unban", userId),
@@ -251,7 +257,11 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
 
     const mutesEvents = pluginData.getPlugin(MutesPlugin).getEventEmitter();
     pluginData.state.mutesListeners = new Map();
-    pluginData.state.mutesListeners.set("mute", (userId: string) => runAutomodOnModAction(pluginData, "mute", userId));
+    pluginData.state.mutesListeners.set(
+      "mute",
+      (userId: string, reason: string | undefined, isAutomodAction: boolean) =>
+        runAutomodOnModAction(pluginData, "mute", userId, reason, isAutomodAction),
+    );
     pluginData.state.mutesListeners.set("unmute", (userId: string) =>
       runAutomodOnModAction(pluginData, "unmute", userId),
     );
diff --git a/backend/src/plugins/Automod/actions/ban.ts b/backend/src/plugins/Automod/actions/ban.ts
index 99b6c25b..ab9e811a 100644
--- a/backend/src/plugins/Automod/actions/ban.ts
+++ b/backend/src/plugins/Automod/actions/ban.ts
@@ -33,7 +33,12 @@ export const BanAction = automodAction({
 
     const modActions = pluginData.getPlugin(ModActionsPlugin);
     for (const userId of userIdsToBan) {
-      await modActions.banUserId(userId, reason, { contactMethods, caseArgs, deleteMessageDays });
+      await modActions.banUserId(userId, reason, {
+        contactMethods,
+        caseArgs,
+        deleteMessageDays,
+        isAutomodAction: true,
+      });
     }
   },
 });
diff --git a/backend/src/plugins/Automod/actions/kick.ts b/backend/src/plugins/Automod/actions/kick.ts
index c25684f4..b11c44ae 100644
--- a/backend/src/plugins/Automod/actions/kick.ts
+++ b/backend/src/plugins/Automod/actions/kick.ts
@@ -33,7 +33,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 });
+      await modActions.kickMember(member, reason, { contactMethods, caseArgs, isAutomodAction: true });
     }
   },
 });
diff --git a/backend/src/plugins/Automod/actions/mute.ts b/backend/src/plugins/Automod/actions/mute.ts
index ad964f44..f09fbe34 100644
--- a/backend/src/plugins/Automod/actions/mute.ts
+++ b/backend/src/plugins/Automod/actions/mute.ts
@@ -49,7 +49,14 @@ export const MuteAction = automodAction({
     const mutes = pluginData.getPlugin(MutesPlugin);
     for (const userId of userIdsToMute) {
       try {
-        await mutes.muteUser(userId, duration, reason, { contactMethods, caseArgs }, rolesToRemove, rolesToRestore);
+        await mutes.muteUser(
+          userId,
+          duration,
+          reason,
+          { contactMethods, caseArgs, isAutomodAction: true },
+          rolesToRemove,
+          rolesToRestore,
+        );
       } catch (e) {
         if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) {
           pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, {
diff --git a/backend/src/plugins/Automod/actions/warn.ts b/backend/src/plugins/Automod/actions/warn.ts
index c705b77c..70240080 100644
--- a/backend/src/plugins/Automod/actions/warn.ts
+++ b/backend/src/plugins/Automod/actions/warn.ts
@@ -33,7 +33,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 });
+      await modActions.warnMember(member, reason, { contactMethods, caseArgs, isAutomodAction: true });
     }
   },
 });
diff --git a/backend/src/plugins/Automod/events/runAutomodOnModAction.ts b/backend/src/plugins/Automod/events/runAutomodOnModAction.ts
index 5831f1eb..59b2b650 100644
--- a/backend/src/plugins/Automod/events/runAutomodOnModAction.ts
+++ b/backend/src/plugins/Automod/events/runAutomodOnModAction.ts
@@ -9,6 +9,7 @@ export async function runAutomodOnModAction(
   modAction: ModActionType,
   userId: string,
   reason?: string,
+  isAutomodAction: boolean = false,
 ) {
   const user = await resolveUser(pluginData.client, userId);
 
@@ -18,6 +19,7 @@ export async function runAutomodOnModAction(
     modAction: {
       type: modAction,
       reason,
+      isAutomodAction,
     },
   };
 
diff --git a/backend/src/plugins/Automod/triggers/ban.ts b/backend/src/plugins/Automod/triggers/ban.ts
index 64559e74..a7c16742 100644
--- a/backend/src/plugins/Automod/triggers/ban.ts
+++ b/backend/src/plugins/Automod/triggers/ban.ts
@@ -5,13 +5,25 @@ import { automodTrigger } from "../helpers";
 interface BanTriggerResultType {}
 
 export const BanTrigger = automodTrigger<BanTriggerResultType>()({
-  configType: t.type({}),
-  defaultConfig: {},
+  configType: t.type({
+    manual: t.boolean,
+    automatic: t.boolean,
+  }),
 
-  async match({ context }) {
+  defaultConfig: {
+    manual: true,
+    automatic: true,
+  },
+
+  async match({ context, triggerConfig }) {
     if (context.modAction?.type !== "ban") {
       return;
     }
+    console.log(context);
+    // If automatic && automatic turned off -> return
+    if (context.modAction.isAutomodAction && !triggerConfig.automatic) return;
+    // If manual && manual turned off -> return
+    if (!context.modAction.isAutomodAction && !triggerConfig.manual) return;
 
     return {
       extra: {},
diff --git a/backend/src/plugins/Automod/triggers/kick.ts b/backend/src/plugins/Automod/triggers/kick.ts
index 284a867d..116f1252 100644
--- a/backend/src/plugins/Automod/triggers/kick.ts
+++ b/backend/src/plugins/Automod/triggers/kick.ts
@@ -5,13 +5,24 @@ import { automodTrigger } from "../helpers";
 interface KickTriggerResultType {}
 
 export const KickTrigger = automodTrigger<KickTriggerResultType>()({
-  configType: t.type({}),
-  defaultConfig: {},
+  configType: t.type({
+    manual: t.boolean,
+    automatic: t.boolean,
+  }),
 
-  async match({ context }) {
+  defaultConfig: {
+    manual: true,
+    automatic: true,
+  },
+
+  async match({ context, triggerConfig }) {
     if (context.modAction?.type !== "kick") {
       return;
     }
+    // If automatic && automatic turned off -> return
+    if (context.modAction.isAutomodAction && !triggerConfig.automatic) return;
+    // If manual && manual turned off -> return
+    if (!context.modAction.isAutomodAction && !triggerConfig.manual) return;
 
     return {
       extra: {},
diff --git a/backend/src/plugins/Automod/triggers/mute.ts b/backend/src/plugins/Automod/triggers/mute.ts
index 94d14437..c5e2d2ba 100644
--- a/backend/src/plugins/Automod/triggers/mute.ts
+++ b/backend/src/plugins/Automod/triggers/mute.ts
@@ -5,13 +5,24 @@ import { automodTrigger } from "../helpers";
 interface MuteTriggerResultType {}
 
 export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({
-  configType: t.type({}),
-  defaultConfig: {},
+  configType: t.type({
+    manual: t.boolean,
+    automatic: t.boolean,
+  }),
 
-  async match({ context }) {
+  defaultConfig: {
+    manual: true,
+    automatic: true,
+  },
+
+  async match({ context, triggerConfig }) {
     if (context.modAction?.type !== "mute") {
       return;
     }
+    // If automatic && automatic turned off -> return
+    if (context.modAction.isAutomodAction && !triggerConfig.automatic) return;
+    // If manual && manual turned off -> return
+    if (!context.modAction.isAutomodAction && !triggerConfig.manual) return;
 
     return {
       extra: {},
diff --git a/backend/src/plugins/Automod/triggers/warn.ts b/backend/src/plugins/Automod/triggers/warn.ts
index 711f5cd7..545c4437 100644
--- a/backend/src/plugins/Automod/triggers/warn.ts
+++ b/backend/src/plugins/Automod/triggers/warn.ts
@@ -5,13 +5,24 @@ import { automodTrigger } from "../helpers";
 interface WarnTriggerResultType {}
 
 export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({
-  configType: t.type({}),
-  defaultConfig: {},
+  configType: t.type({
+    manual: t.boolean,
+    automatic: t.boolean,
+  }),
 
-  async match({ context }) {
+  defaultConfig: {
+    manual: true,
+    automatic: true,
+  },
+
+  async match({ context, triggerConfig }) {
     if (context.modAction?.type !== "warn") {
       return;
     }
+    // If automatic && automatic turned off -> return
+    if (context.modAction.isAutomodAction && !triggerConfig.automatic) return;
+    // If manual && manual turned off -> return
+    if (!context.modAction.isAutomodAction && !triggerConfig.manual) return;
 
     return {
       extra: {},
diff --git a/backend/src/plugins/Automod/types.ts b/backend/src/plugins/Automod/types.ts
index f85b8429..cd7ebacf 100644
--- a/backend/src/plugins/Automod/types.ts
+++ b/backend/src/plugins/Automod/types.ts
@@ -122,6 +122,7 @@ export interface AutomodContext {
   modAction?: {
     type: ModActionType;
     reason?: string;
+    isAutomodAction: boolean;
   };
   antiraid?: {
     level: string | null;
diff --git a/backend/src/plugins/ModActions/commands/WarnCmd.ts b/backend/src/plugins/ModActions/commands/WarnCmd.ts
index e36dbb86..45526a0c 100644
--- a/backend/src/plugins/ModActions/commands/WarnCmd.ts
+++ b/backend/src/plugins/ModActions/commands/WarnCmd.ts
@@ -112,7 +112,5 @@ export const WarnCmd = modActionsCmd({
       msg.channel,
       `Warned **${memberToWarn.user.username}#${memberToWarn.user.discriminator}** (Case #${warnResult.case.case_number})${messageResultText}`,
     );
-
-    pluginData.state.events.emit("warn", user.id, reason);
   },
 });
diff --git a/backend/src/plugins/ModActions/functions/banUserId.ts b/backend/src/plugins/ModActions/functions/banUserId.ts
index 85d05de9..4620ec0f 100644
--- a/backend/src/plugins/ModActions/functions/banUserId.ts
+++ b/backend/src/plugins/ModActions/functions/banUserId.ts
@@ -127,7 +127,7 @@ export async function banUserId(
     banTime: banTime ? humanizeDuration(banTime) : null,
   });
 
-  pluginData.state.events.emit("ban", user.id, reason);
+  pluginData.state.events.emit("ban", user.id, reason, banOptions.isAutomodAction);
 
   return {
     status: "success",
diff --git a/backend/src/plugins/ModActions/functions/kickMember.ts b/backend/src/plugins/ModActions/functions/kickMember.ts
index 9e297af8..b682018e 100644
--- a/backend/src/plugins/ModActions/functions/kickMember.ts
+++ b/backend/src/plugins/ModActions/functions/kickMember.ts
@@ -85,7 +85,7 @@ export async function kickMember(
     reason,
   });
 
-  pluginData.state.events.emit("kick", member.id, reason);
+  pluginData.state.events.emit("kick", member.id, reason, kickOptions.isAutomodAction);
 
   return {
     status: "success",
diff --git a/backend/src/plugins/ModActions/functions/warnMember.ts b/backend/src/plugins/ModActions/functions/warnMember.ts
index 0c07c838..d61b183d 100644
--- a/backend/src/plugins/ModActions/functions/warnMember.ts
+++ b/backend/src/plugins/ModActions/functions/warnMember.ts
@@ -82,6 +82,8 @@ export async function warnMember(
     reason,
   });
 
+  pluginData.state.events.emit("warn", member.id, reason, warnOptions.isAutomodAction);
+
   return {
     status: "success",
     case: createdCase,
diff --git a/backend/src/plugins/ModActions/types.ts b/backend/src/plugins/ModActions/types.ts
index af4fd8d6..083e5642 100644
--- a/backend/src/plugins/ModActions/types.ts
+++ b/backend/src/plugins/ModActions/types.ts
@@ -48,9 +48,9 @@ export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
 
 export interface ModActionsEvents {
   note: (userId: string, reason?: string) => void;
-  warn: (userId: string, reason?: string) => void;
-  kick: (userId: string, reason?: string) => void;
-  ban: (userId: string, reason?: string) => void;
+  warn: (userId: string, reason?: string, isAutomodAction?: boolean) => void;
+  kick: (userId: string, reason?: string, isAutomodAction?: boolean) => void;
+  ban: (userId: string, reason?: string, isAutomodAction?: boolean) => void;
   unban: (userId: string, reason?: string) => void;
   // mute/unmute are in the Mutes plugin
 }
@@ -126,17 +126,20 @@ export interface WarnOptions {
   caseArgs?: Partial<CaseArgs> | null;
   contactMethods?: UserNotificationMethod[] | null;
   retryPromptChannel?: TextChannel | null;
+  isAutomodAction?: boolean;
 }
 
 export interface KickOptions {
   caseArgs?: Partial<CaseArgs>;
   contactMethods?: UserNotificationMethod[];
+  isAutomodAction?: boolean;
 }
 
 export interface BanOptions {
   caseArgs?: Partial<CaseArgs>;
   contactMethods?: UserNotificationMethod[];
   deleteMessageDays?: number;
+  isAutomodAction?: boolean;
 }
 
 export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" | "unban";
diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts
index 29ba54ab..622a5b1a 100644
--- a/backend/src/plugins/Mutes/functions/muteUser.ts
+++ b/backend/src/plugins/Mutes/functions/muteUser.ts
@@ -247,7 +247,7 @@ export async function muteUser(
 
   lock.unlock();
 
-  pluginData.state.events.emit("mute", user.id, reason);
+  pluginData.state.events.emit("mute", user.id, reason, muteOptions.isAutomodAction);
 
   return {
     case: theCase,
diff --git a/backend/src/plugins/Mutes/types.ts b/backend/src/plugins/Mutes/types.ts
index 7da2cb0d..ea180e90 100644
--- a/backend/src/plugins/Mutes/types.ts
+++ b/backend/src/plugins/Mutes/types.ts
@@ -34,7 +34,7 @@ export const ConfigSchema = t.type({
 export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
 
 export interface MutesEvents {
-  mute: (userId: string, reason?: string) => void;
+  mute: (userId: string, reason?: string, isAutomodAction?: boolean) => void;
   unmute: (userId: string, reason?: string) => void;
 }
 
@@ -75,6 +75,7 @@ export type UnmuteResult = {
 export interface MuteOptions {
   caseArgs?: Partial<CaseArgs>;
   contactMethods?: UserNotificationMethod[];
+  isAutomodAction?: boolean;
 }
 
 export const mutesCmd = guildCommand<MutesPluginType>();

From dfc1bf2ba09e317583f27bf5a9e5522b5f12d3b3 Mon Sep 17 00:00:00 2001
From: Shoaib Sajid <zodpixel@gmail.com>
Date: Thu, 29 Apr 2021 00:08:37 +0500
Subject: [PATCH 6/7] Add !infractions as an alias for !cases (#177)

---
 backend/src/plugins/ModActions/commands/CasesModCmd.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/backend/src/plugins/ModActions/commands/CasesModCmd.ts b/backend/src/plugins/ModActions/commands/CasesModCmd.ts
index 6d84fc77..f9f4667d 100644
--- a/backend/src/plugins/ModActions/commands/CasesModCmd.ts
+++ b/backend/src/plugins/ModActions/commands/CasesModCmd.ts
@@ -17,7 +17,7 @@ const opts = {
 const casesPerPage = 5;
 
 export const CasesModCmd = modActionsCmd({
-  trigger: ["cases", "modlogs"],
+  trigger: ["cases", "modlogs", "infractions"],
   permission: "can_view",
   description: "Show the most recent 5 cases by the specified -mod",
 

From 20b1c869cd040d1d8bbf7e9a490153db4ca6d861 Mon Sep 17 00:00:00 2001
From: Nils <7890309+DarkView@users.noreply.github.com>
Date: Wed, 28 Apr 2021 21:15:16 +0200
Subject: [PATCH 7/7] Add -update/-up argument to automatically update
 latest/chosen case with !clean (#173)

---
 .../plugins/ModActions/ModActionsPlugin.ts    |  9 +++-
 .../plugins/ModActions/commands/UpdateCmd.ts  | 42 +----------------
 .../ModActions/functions/updateCase.ts        | 44 +++++++++++++++++
 backend/src/plugins/Utility/UtilityPlugin.ts  |  3 +-
 .../src/plugins/Utility/commands/CleanCmd.ts  | 47 +++++++++++++++----
 5 files changed, 93 insertions(+), 52 deletions(-)
 create mode 100644 backend/src/plugins/ModActions/functions/updateCase.ts

diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts
index 672e4630..2d583e06 100644
--- a/backend/src/plugins/ModActions/ModActionsPlugin.ts
+++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts
@@ -30,7 +30,7 @@ import { GuildCases } from "../../data/GuildCases";
 import { GuildLogs } from "../../data/GuildLogs";
 import { ForceUnmuteCmd } from "./commands/ForceunmuteCmd";
 import { warnMember } from "./functions/warnMember";
-import { Member } from "eris";
+import { Member, Message } from "eris";
 import { kickMember } from "./functions/kickMember";
 import { banUserId } from "./functions/banUserId";
 import { MassmuteCmd } from "./commands/MassmuteCmd";
@@ -43,6 +43,7 @@ import { EventEmitter } from "events";
 import { mapToPublicFn } from "../../pluginUtils";
 import { onModActionsEvent } from "./functions/onModActionsEvent";
 import { offModActionsEvent } from "./functions/offModActionsEvent";
+import { updateCase } from "./functions/updateCase";
 
 const defaultOptions = {
   config: {
@@ -170,6 +171,12 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()("mod
       };
     },
 
+    updateCase(pluginData) {
+      return (msg: Message, caseNumber: number | null, note: string) => {
+        updateCase(pluginData, msg, { caseNumber, note });
+      };
+    },
+
     on: mapToPublicFn(onModActionsEvent),
     off: mapToPublicFn(offModActionsEvent),
     getEventEmitter(pluginData) {
diff --git a/backend/src/plugins/ModActions/commands/UpdateCmd.ts b/backend/src/plugins/ModActions/commands/UpdateCmd.ts
index 3a048b42..6c8d78ca 100644
--- a/backend/src/plugins/ModActions/commands/UpdateCmd.ts
+++ b/backend/src/plugins/ModActions/commands/UpdateCmd.ts
@@ -1,11 +1,6 @@
 import { modActionsCmd } from "../types";
 import { commandTypeHelpers as ct } from "../../../commandTypes";
-import { Case } from "../../../data/entities/Case";
-import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
-import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
-import { CasesPlugin } from "../../Cases/CasesPlugin";
-import { LogType } from "../../../data/LogType";
-import { CaseTypes } from "../../../data/CaseTypes";
+import { updateCase } from "../functions/updateCase";
 
 export const UpdateCmd = modActionsCmd({
   trigger: ["update", "reason"],
@@ -24,39 +19,6 @@ export const UpdateCmd = modActionsCmd({
   ],
 
   async run({ pluginData, message: msg, args }) {
-    let theCase: Case | undefined;
-    if (args.caseNumber != null) {
-      theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber);
-    } else {
-      theCase = await pluginData.state.cases.findLatestByModId(msg.author.id);
-    }
-
-    if (!theCase) {
-      sendErrorMessage(pluginData, msg.channel, "Case not found");
-      return;
-    }
-
-    if (!args.note && msg.attachments.length === 0) {
-      sendErrorMessage(pluginData, msg.channel, "Text or attachment required");
-      return;
-    }
-
-    const note = formatReasonWithAttachments(args.note, msg.attachments);
-
-    const casesPlugin = pluginData.getPlugin(CasesPlugin);
-    await casesPlugin.createCaseNote({
-      caseId: theCase.id,
-      modId: msg.author.id,
-      body: note,
-    });
-
-    pluginData.state.serverLogs.log(LogType.CASE_UPDATE, {
-      mod: msg.author,
-      caseNumber: theCase.case_number,
-      caseType: CaseTypes[theCase.type],
-      note,
-    });
-
-    sendSuccessMessage(pluginData, msg.channel, `Case \`#${theCase.case_number}\` updated`);
+    await updateCase(pluginData, msg, args);
   },
 });
diff --git a/backend/src/plugins/ModActions/functions/updateCase.ts b/backend/src/plugins/ModActions/functions/updateCase.ts
new file mode 100644
index 00000000..12257269
--- /dev/null
+++ b/backend/src/plugins/ModActions/functions/updateCase.ts
@@ -0,0 +1,44 @@
+import { Message } from "eris";
+import { CaseTypes } from "../../../data/CaseTypes";
+import { Case } from "../../../data/entities/Case";
+import { LogType } from "../../../data/LogType";
+import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin";
+import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
+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);
+  } else {
+    theCase = await pluginData.state.cases.findLatestByModId(msg.author.id);
+  }
+
+  if (!theCase) {
+    sendErrorMessage(pluginData, msg.channel, "Case not found");
+    return;
+  }
+
+  if (!args.note && msg.attachments.length === 0) {
+    sendErrorMessage(pluginData, msg.channel, "Text or attachment required");
+    return;
+  }
+
+  const note = formatReasonWithAttachments(args.note, msg.attachments);
+
+  const casesPlugin = pluginData.getPlugin(CasesPlugin);
+  await casesPlugin.createCaseNote({
+    caseId: theCase.id,
+    modId: msg.author.id,
+    body: note,
+  });
+
+  pluginData.state.serverLogs.log(LogType.CASE_UPDATE, {
+    mod: msg.author,
+    caseNumber: theCase.case_number,
+    caseType: CaseTypes[theCase.type],
+    note,
+  });
+
+  sendSuccessMessage(pluginData, msg.channel, `Case \`#${theCase.case_number}\` updated`);
+}
diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts
index 90c44c49..e2ba30b0 100644
--- a/backend/src/plugins/Utility/UtilityPlugin.ts
+++ b/backend/src/plugins/Utility/UtilityPlugin.ts
@@ -35,6 +35,7 @@ import { SnowflakeInfoCmd } from "./commands/SnowflakeInfoCmd";
 import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
 import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
 import { VcdisconnectCmd } from "./commands/VcdisconnectCmd";
+import { ModActionsPlugin } from "../ModActions/ModActionsPlugin";
 import { refreshMembersIfNeeded } from "./refreshMembers";
 
 const defaultOptions: PluginOptions<UtilityPluginType> = {
@@ -106,7 +107,7 @@ export const UtilityPlugin = zeppelinGuildPlugin<UtilityPluginType>()("utility",
     prettyName: "Utility",
   },
 
-  dependencies: [TimeAndDatePlugin],
+  dependencies: [TimeAndDatePlugin, ModActionsPlugin],
   configSchema: ConfigSchema,
   defaultOptions,
 
diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts
index e75da2f8..8a1a2328 100644
--- a/backend/src/plugins/Utility/commands/CleanCmd.ts
+++ b/backend/src/plugins/Utility/commands/CleanCmd.ts
@@ -8,6 +8,7 @@ import { GuildPluginData } from "knub";
 import { SavedMessage } from "../../../data/entities/SavedMessage";
 import { LogType } from "../../../data/LogType";
 import { allowTimeout } from "../../../RegExpRunner";
+import { ModActionsPlugin } from "../../../plugins/ModActions/ModActionsPlugin";
 
 const MAX_CLEAN_COUNT = 150;
 const MAX_CLEAN_TIME = 1 * DAYS;
@@ -49,23 +50,36 @@ async function cleanMessages(
   return { archiveUrl };
 }
 
+const opts = {
+  user: ct.userId({ option: true, shortcut: "u" }),
+  channel: ct.channelId({ option: true, shortcut: "c" }),
+  bots: ct.switchOption({ shortcut: "b" }),
+  "delete-pins": ct.switchOption({ shortcut: "p" }),
+  "has-invites": ct.switchOption({ shortcut: "i" }),
+  match: ct.regex({ option: true, shortcut: "m" }),
+  "to-id": ct.anyId({ option: true, shortcut: "id" }),
+};
+
 export const CleanCmd = utilityCmd({
   trigger: ["clean", "clear"],
   description: "Remove a number of recent messages",
   usage: "!clean 20",
   permission: "can_clean",
 
-  signature: {
-    count: ct.number(),
+  signature: [
+    {
+      count: ct.number(),
+      update: ct.number({ option: true, shortcut: "up" }),
 
-    user: ct.userId({ option: true, shortcut: "u" }),
-    channel: ct.channelId({ option: true, shortcut: "c" }),
-    bots: ct.switchOption({ shortcut: "b" }),
-    "delete-pins": ct.switchOption({ shortcut: "p" }),
-    "has-invites": ct.switchOption({ shortcut: "i" }),
-    match: ct.regex({ option: true, shortcut: "m" }),
-    "to-id": ct.anyId({ option: true, shortcut: "id" }),
-  },
+      ...opts,
+    },
+    {
+      count: ct.number(),
+      update: ct.switchOption({ shortcut: "up" }),
+
+      ...opts,
+    },
+  ],
 
   async run({ message: msg, args, pluginData }) {
     if (args.count > MAX_CLEAN_COUNT || args.count <= 0) {
@@ -155,6 +169,19 @@ export const CleanCmd = utilityCmd({
         responseText += ` in <#${targetChannel.id}>\n${cleanResult.archiveUrl}`;
       }
 
+      if (args.update) {
+        const modActions = pluginData.getPlugin(ModActionsPlugin);
+        const channelId = targetChannel.id !== msg.channel.id ? targetChannel.id : msg.channel.id;
+        const updateMessage = `Cleaned ${messagesToClean.length} ${
+          messagesToClean.length === 1 ? "message" : "messages"
+        } in <#${channelId}>: ${cleanResult.archiveUrl}`;
+        if (typeof args.update === "number") {
+          modActions.updateCase(msg, args.update, updateMessage);
+        } else {
+          modActions.updateCase(msg, null, updateMessage);
+        }
+      }
+
       responseMsg = await sendSuccessMessage(pluginData, msg.channel, responseText);
     } else {
       responseMsg = await sendErrorMessage(pluginData, msg.channel, `Found no messages to clean!`);