stats channels
diff --git a/TODO.json b/TODO.json
index b4b2524..d95afcb 100644
--- a/TODO.json
+++ b/TODO.json
@@ -1,4 +1,15 @@
 {
+  "logging": {
+    "logs": {
+      "enabled": true,
+      "toLog": "3fffff",
+      "ignore": {
+        "users": [],
+        "roles": [],
+        "channels": []
+      }
+    }
+  },
   "filters": {
     "images": {
       "NSFW": false,
@@ -6,9 +17,12 @@
     },
     "malware": false,
     "wordFilter": {
-      "enabled": false,
+      "enabled": true,
       "words": {
-        "strict": [],
+        "strict": [
+          "meat",
+          "noon"
+        ],
         "loose": []
       },
       "allowed": {
@@ -37,23 +51,10 @@
       }
     }
   },
-  "welcome": {
-    "enabled": false,
-    "welcomeRole": null,
-    "channel": null,
-    "message": null
-  },
-  "stats": [
-    {
-      "enabled": true,
-      "channel": "9849175359",
-      "text": "{count} members | {count:bots} bots | {count:humans} humans"
-    }
-  ],
   "moderation": {
     "mute": {
       "timeout": true,
-      "role": null,
+      "role": "934941408849186856",
       "text": null,
       "link": null
     },
@@ -70,25 +71,24 @@
       "link": null
     },
     "warn": {
-      "text": null,
-      "link": null
+      "text": "Test",
+      "link": "https://google.com"
     },
     "role": {
       "role": null
     }
   },
-  "tracks": [
-  ],
-  "logging": {
-    "logs": {
-      "enabled": true,
-      "toLog": "3fffff"
-    }
-  },
-  "roleMenu": {
-    "enabled": true,
-    "allowWebUI": true,
-    "options": [
-    ]
+  "roleMenu": [],
+  "stats": [],
+  "tracks": [],
+  "welcome": {
+    "enabled": false,
+    "verificationRequired": {
+      "message": null,
+      "role": null
+    },
+    "welcomeRole": null,
+    "channel": null,
+    "message": null
   }
 }
\ No newline at end of file
diff --git a/package.json b/package.json
index 0320d64..ca786a7 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
     "body-parser": "^1.20.0",
     "discord.js": "13.8.1",
     "express": "^4.18.1",
+    "fuse.js": "^6.6.2",
     "humanize": "^0.0.9",
     "humanize-duration": "^3.27.1",
     "jshaiku": "file:../haiku",
diff --git a/src/actions/createModActionTicket.ts b/src/actions/createModActionTicket.ts
index 0162523..40f99b9 100644
--- a/src/actions/createModActionTicket.ts
+++ b/src/actions/createModActionTicket.ts
@@ -5,7 +5,6 @@
 
 export async function create(guild: Discord.Guild, member: Discord.User, createdBy: Discord.User, reason: string) {
     let config = await client.database.guilds.read(guild.id);
-    // @ts-ignore
     const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger
     let overwrites = [{
         id: member,
diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts
index 1985a08..c14cf0c 100644
--- a/src/actions/tickets/delete.ts
+++ b/src/actions/tickets/delete.ts
@@ -134,29 +134,27 @@
         }
     });
     if (deleted) {
-        try {
-            const { log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
-            let data = {
-                meta:{
-                    type: 'ticketPurge',
-                    displayName: 'Tickets Purged',
-                    calculateType: "ticketUpdate",
-                    color: NucleusColors.red,
-                    emoji: 'GUILD.TICKET.DELETE',
-                    timestamp: new Date().getTime()
-                },
-                list: {
-                    ticketFor: entry(member, renderUser(member)),
-                    deletedBy: entry(null, "Member left server"),
-                    deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-                    ticketsDeleted: deleted,
-                },
-                hidden: {
-                    guild: guild.id
-                }
+        const { log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
+        let data = {
+            meta:{
+                type: 'ticketPurge',
+                displayName: 'Tickets Purged',
+                calculateType: "ticketUpdate",
+                color: NucleusColors.red,
+                emoji: 'GUILD.TICKET.DELETE',
+                timestamp: new Date().getTime()
+            },
+            list: {
+                ticketFor: entry(member, renderUser(member)),
+                deletedBy: entry(null, "Member left server"),
+                deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                ticketsDeleted: deleted,
+            },
+            hidden: {
+                guild: guild.id
             }
-            log(data);
-        } catch {}
+        }
+        log(data);
     }
 }
 
diff --git a/src/api/index.ts b/src/api/index.ts
index c429452..5cc4a99 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -89,7 +89,6 @@
         const secret = req.body.secret;
         const data = req.body.data;
         if (secret === client.config.verifySecret) {
-            console.table(data)
             let guild = await client.guilds.fetch(client.roleMenu[code].guild);
             if (!guild) { return res.status(404) }
             let member = await guild.members.fetch(client.roleMenu[code].user);
diff --git a/src/commands/categorisationTest.ts b/src/commands/categorisationTest.ts
index f89a899..b052783 100644
--- a/src/commands/categorisationTest.ts
+++ b/src/commands/categorisationTest.ts
@@ -6,7 +6,7 @@
 import addPlural from "../utils/plurals.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
 
-const command = new SlashCommandBuilder()
+const command = new SlashCommandBuilder() // TODO: remove for release
     .setName("categorise")
     .setDescription("Categorises your servers channels")
 
@@ -44,10 +44,8 @@
     for (let c of channels) {
         // convert channel to a channel if its a string
         let channel: any
-        console.log(c)
         if (typeof c === "string") channel = interaction.guild.channels.cache.get(channel).id
-        // @ts-ignore
-        else channel = c[0].id
+        else channel = (c[0] as unknown as GuildChannel).id
         console.log(channel)
         if (!predicted[channel]) predicted[channel] = []
         m = await interaction.editReply({embeds: [new EmojiEmbed()
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index 0239951..2068b15 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -12,16 +12,14 @@
     .setName("ban")
     .setDescription("Bans a user from the server")
     .addUserOption(option => option.setName("user").setDescription("The user to ban").setRequired(true))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are banned | Default: Yes").setRequired(false)
-        .addChoices([["Yes", "yes"], ["No", "no"]])
-    )
-    .addIntegerOption(option => option.setName("delete").setDescription("The days of messages to delete | Default: 0").setMinValue(0).setMaxValue(7).setRequired(false))
+    .addNumberOption(option => option.setName("delete").setDescription("The days of messages to delete | Default: 0").setMinValue(0).setMaxValue(7).setRequired(false))
 
 const callback = async (interaction: CommandInteraction): Promise<any> => {
     const { renderUser } = client.logger
     // TODO:[Modals] Replace this with a modal
     let reason = null
-    let confirmation
+    let notify = true;
+    let confirmation;
     while (true) {
         confirmation = await new confirmationMessage(interaction)
             .setEmoji("PUNISH.BAN.RED")
@@ -30,22 +28,24 @@
                 "user": renderUser(interaction.options.getUser("user")),
                 "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
             })
-            + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n`
+            + `The user **will${notify ? '' : ' not'}** be notified\n`
             + `${addPlurals(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
             + `Are you sure you want to ban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
             .setColor("Danger")
             .addReasonButton(reason ?? "")
             .send(reason !== null)
         reason = reason ?? ""
-        if (confirmation.newReason === undefined) break
-        reason = confirmation.newReason
+        if (confirmation.cancelled) return
+        if (confirmation.success) break
+        if (confirmation.newReason) reason = confirmation.newReason
+        if (confirmation.components) notify = confirmation.components.notify.active
     }
     if (confirmation.success) {
         let dmd = false
         let dm;
         let config = await client.database.guilds.read(interaction.guild.id);
         try {
-            if (interaction.options.getString("notify") != "no") {
+            if (notify) {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
                     embeds: [new EmojiEmbed()
                         .setEmoji("PUNISH.BAN.RED")
@@ -66,7 +66,7 @@
         try {
             let member = (interaction.options.getMember("user") as GuildMember)
             member.ban({
-                days: Number(interaction.options.getInteger("delete") ?? 0),
+                days: Number(interaction.options.getNumber("delete") ?? 0),
                 reason: reason ?? "No reason provided"
             })
             try { await client.database.history.create("ban", interaction.guild.id, member.user, interaction.user, reason) } catch {}
@@ -104,7 +104,7 @@
             if (dmd) await dm.delete()
             return
         }
-        let failed = (dmd == false && interaction.options.getString("notify") != "no")
+        let failed = (dmd == false && notify)
         await interaction.editReply({embeds: [new EmojiEmbed()
             .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Ban`)
diff --git a/src/commands/mod/info.ts b/src/commands/mod/info.ts
index b7f4b74..0ea93d8 100644
--- a/src/commands/mod/info.ts
+++ b/src/commands/mod/info.ts
@@ -14,7 +14,6 @@
     .setDescription("Shows moderator information about a user")
     .addUserOption(option => option.setName("user").setDescription("The user to get information about").setRequired(true))
 
-
 const types = {
     "warn": {emoji: "PUNISH.WARN.YELLOW", text: "Warned"},
     "mute": {emoji: "PUNISH.MUTE.YELLOW", text: "Muted"},
@@ -237,8 +236,7 @@
         } catch (e) { return }
         if (i.customId === "modify") {
             await i.showModal(new Discord.Modal().setCustomId("modal").setTitle(`Editing moderator note`).addComponents(
-                // @ts-ignore
-                new MessageActionRow().addComponents(new TextInputComponent()
+                new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
                     .setCustomId("note")
                     .setLabel("Note")
                     .setMaxLength(4000)
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index 793a630..eac7ca3 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -12,14 +12,12 @@
     .setName("kick")
     .setDescription("Kicks a user from the server")
     .addUserOption(option => option.setName("user").setDescription("The user to kick").setRequired(true))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are kicked | Default: Yes").setRequired(false)
-        .addChoices([["Yes", "yes"], ["No", "no"]])
-    )
 
 const callback = async (interaction: CommandInteraction): Promise<any> => {
     const { renderUser } = client.logger
     // TODO:[Modals] Replace this with a modal
     let reason = null;
+    let notify = true;
     let confirmation
     while (true) {
         confirmation = await new confirmationMessage(interaction)
@@ -29,21 +27,25 @@
                 "user": renderUser(interaction.options.getUser("user")),
                 "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
             })
-            + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
+            + `The user **will${notify ? '' : ' not'}** be notified\n\n`
             + `Are you sure you want to kick <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
             .setColor("Danger")
             .addReasonButton(reason ?? "")
         .send(reason !== null)
         reason = reason ?? ""
-        if (confirmation.newReason === undefined) break
-        reason = confirmation.newReason
+        if (confirmation.cancelled) return
+        if (confirmation.success) break
+        if (confirmation.newReason) reason = confirmation.newReason
+        if (confirmation.components) {
+            notify = confirmation.components.notify.active
+        }
     }
     if (confirmation.success) {
         let dmd = false
         let dm;
         let config = await client.database.guilds.read(interaction.guild.id);
         try {
-            if (interaction.options.getString("notify") != "no") {
+            if (notify) {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
                     embeds: [new EmojiEmbed()
                         .setEmoji("PUNISH.KICK.RED")
@@ -65,8 +67,7 @@
             (interaction.options.getMember("user") as GuildMember).kick(reason ?? "No reason provided.")
             let member = (interaction.options.getMember("user") as GuildMember)
             try { await client.database.history.create("kick", interaction.guild.id, member.user, interaction.user, reason) } catch {}
-            // @ts-ignore
-            const { log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
+            const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger
             let data = {
                 meta: {
                     type: 'memberKick',
@@ -102,7 +103,7 @@
             if (dmd) await dm.delete()
             return
         }
-        let failed = (dmd == false && interaction.options.getString("notify") != "no")
+        let failed = (dmd == false && notify)
         await interaction.editReply({embeds: [new EmojiEmbed()
             .setEmoji(`PUNISH.KICK.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Kick`)
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index 5e1a18b..f98bd6a 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -18,8 +18,6 @@
     .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to mute the user for | Default: 0").setMinValue(0).setMaxValue(23).setRequired(false))
     .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default: 0").setMinValue(0).setMaxValue(59).setRequired(false))
     .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default: 0").setMinValue(0).setMaxValue(59).setRequired(false))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are muted | Default: yes").setRequired(false)
-        .addChoices([["Yes", "yes"], ["No", "no"]]))
 
 const callback = async (interaction: CommandInteraction): Promise<any> => {
     const { log, NucleusColors, renderUser, entry, renderDelta } = client.logger
@@ -119,6 +117,8 @@
     }
     // TODO:[Modals] Replace this with a modal
     let reason = null;
+    let notify = true;
+    let createAppealTicket = false;
     let confirmation;
     while (true) {
         confirmation = await new confirmationMessage(interaction)
@@ -130,32 +130,39 @@
                 "reason": reason ?  ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
             })
             + `The user will be ` + serverSettingsDescription + "\n"
-            + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
+            + `The user **will${notify ? '' : ' not'}** be notified\n\n`
             + `Are you sure you want to mute <@!${user.id}>?`)
             .setColor("Danger")
             .addCustomBoolean(
-                "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
-                async () => await create(interaction.guild, user.user, interaction.user, reason),
-                "An appeal ticket will be created when Confirm is clicked")
+                "appeal", "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
+                async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, reason),
+                "An appeal ticket will be created when Confirm is clicked", "CONTROL.TICKET", createAppealTicket)
+            .addCustomBoolean("notify", "Notify user", false, null, null, "ICONS.NOTIFY." + (notify ? "ON" : "OFF" ), notify)
             .addReasonButton(reason ?? "")
         .send(true)
         reason = reason ?? ""
-        if (confirmation.newReason === undefined) break
-        reason = confirmation.newReason
+        if (confirmation.cancelled) return
+        if (confirmation.success) break
+        if (confirmation.newReason) reason = confirmation.newReason
+        if (confirmation.components) {
+            notify = confirmation.components.notify.active
+            createAppealTicket = confirmation.components.appeal.active
+        }
     }
     if (confirmation.success) {
         let dmd = false
         let dm;
         let config = await client.database.guilds.read(interaction.guild.id);
         try {
-            if (interaction.options.getString("notify") != "no") {
+            if (notify) {
                 dm = await user.send({
                     embeds: [new EmojiEmbed()
                         .setEmoji("PUNISH.MUTE.RED")
                         .setTitle("Muted")
                         .setDescription(`You have been muted in ${interaction.guild.name}` +
                                     (reason ? ` for:\n> ${reason}` : ".\n\n" +
-                                    `You will be unmuted at: <t:${Math.round((new Date).getTime() / 1000) + muteTime}:D> at <t:${Math.round((new Date).getTime() / 1000) + muteTime}:T> (<t:${Math.round((new Date).getTime() / 1000) + muteTime}:R>)`))
+                                    `You will be unmuted at: <t:${Math.round((new Date).getTime() / 1000) + muteTime}:D> at <t:${Math.round((new Date).getTime() / 1000) + muteTime}:T> (<t:${Math.round((new Date).getTime() / 1000) + muteTime}:R>)`) +
+                                    (confirmation.components.appeal.response ? `You can appeal this here: <#${confirmation.components.appeal.response}>` : ``))
                         .setStatus("Danger")
                     ],
                     components: [new MessageActionRow().addComponents(config.moderation.mute.text ? [new MessageButton()
@@ -198,16 +205,16 @@
                 .setTitle(`Mute`)
                 .setDescription("Something went wrong and the user was not muted")
                 .setStatus("Danger")
-            ], components: []})
+            ], components: []}) // TODO: make this clearer
             if (dmd) await dm.delete()
             return
         }
         try { await client.database.history.create("mute", interaction.guild.id, member.user, interaction.user, reason) } catch {}
-        let failed = (dmd == false && interaction.options.getString("notify") != "no")
+        let failed = (dmd == false && notify)
         await interaction.editReply({embeds: [new EmojiEmbed()
             .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Mute`)
-            .setDescription("The member was muted" + (failed ? ", but could not be notified" : ""))
+            .setDescription("The member was muted" + (failed ? ", but could not be notified" : "") + (confirmation.components.appeal.response ? ` and an appeal ticket was opened in <#${confirmation.components.appeal.response}>` : ``))
             .setStatus(failed ? "Warning" : "Success")
         ], components: []})
         let data = {
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index f842d76..3ff18ec 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -13,40 +13,43 @@
     .setDescription("Changes a users nickname")
     .addUserOption(option => option.setName("user").setDescription("The user to change").setRequired(true))
     .addStringOption(option => option.setName("name").setDescription("The name to set | Leave blank to clear").setRequired(false))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when their nickname is changed | Default: No").setRequired(false)
-        .addChoices([["Yes", "yes"], ["No", "no"]])
-    )
 
 const callback = async (interaction: CommandInteraction): Promise<any> => {
     const { renderUser } = client.logger
     // TODO:[Modals] Replace this with a modal
-    let confirmation = await new confirmationMessage(interaction)
-        .setEmoji("PUNISH.NICKNAME.RED")
-        .setTitle("Nickname")
-        .setDescription(keyValueList({
-            "user": renderUser(interaction.options.getUser("user")),
-            "new nickname": `${interaction.options.getString("name") ? interaction.options.getString("name") : "*No nickname*"}`
-        })
-        + `The user **will${interaction.options.getString("notify") == "yes" ? '' : ' not'}** be notified\n\n`
-        + `Are you sure you want to ${interaction.options.getString("name") ? "change" : "clear"} <@!${(interaction.options.getMember("user") as GuildMember).id}>'s nickname?`)
-        .setColor("Danger")
-        .addCustomBoolean(
-            "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
-            async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, null),
-            "An appeal ticket will be created when Confirm is clicked")
-    .send()
+    let notify = true;
+    let confirmation;
+    while (true) {
+        confirmation = await new confirmationMessage(interaction)
+            .setEmoji("PUNISH.NICKNAME.RED")
+            .setTitle("Nickname")
+            .setDescription(keyValueList({
+                "user": renderUser(interaction.options.getUser("user")),
+                "new nickname": `${interaction.options.getString("name") ? interaction.options.getString("name") : "*No nickname*"}`
+            })
+            + `The user **will${notify ? '' : ' not'}** be notified\n\n`
+            + `Are you sure you want to ${interaction.options.getString("name") ? "change" : "clear"} <@!${(interaction.options.getMember("user") as GuildMember).id}>'s nickname?`)
+            .setColor("Danger")
+            .addCustomBoolean("notify", "Notify user", false, null, null, "ICONS.NOTIFY." + (notify ? "ON" : "OFF" ), notify)
+        .send(interaction.options.getString("name") !== null)
+        if (confirmation.cancelled) return
+        if (confirmation.success) break
+        if (confirmation.components) {
+            notify = confirmation.components.notify.active
+        }
+    }
     if (confirmation.success) {
         let dmd = false
         let dm;
         try {
-            if (interaction.options.getString("notify") == "yes") {
+            if (notify) {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
                     embeds: [new EmojiEmbed()
                         .setEmoji("PUNISH.NICKNAME.RED")
                         .setTitle("Nickname changed")
                         .setDescription(`Your nickname was ${interaction.options.getString("name") ? "changed" : "cleared"} in ${interaction.guild.name}.` +
                                     (interaction.options.getString("name") ? ` it is now: ${interaction.options.getString("name")}` : "") + "\n\n" +
-                                    (confirmation.buttonClicked ? `You can appeal this here: <#${confirmation.response}>` : ``))
+                                    (confirmation.components.appeal.response ? `You can appeal this here: <#${confirmation.components.appeal.response}>` : ``))
                         .setStatus("Danger")
                     ]
                 })
@@ -61,8 +64,7 @@
             try { await client.database.history.create(
                 "nickname", interaction.guild.id, member.user, interaction.user,
                 null, before, nickname) } catch {}
-            // @ts-ignore
-            const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger
+            const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger
             let data = {
                 meta: {
                     type: 'memberUpdate',
@@ -94,11 +96,11 @@
             if (dmd) await dm.delete()
             return
         }
-        let failed = (dmd == false && interaction.options.getString("notify") == "yes")
+        let failed = (dmd == false && notify)
         await interaction.editReply({embeds: [new EmojiEmbed()
             .setEmoji(`PUNISH.NICKNAME.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Nickname`)
-            .setDescription("The members nickname was changed" + (failed ? ", but was not notified" : "") + (confirmation.response ? ` and an appeal ticket was opened in <#${confirmation.response}>` : ``))
+            .setDescription("The members nickname was changed" + (failed ? ", but was not notified" : "") + (confirmation.components.appeal.response ? ` and an appeal ticket was opened in <#${confirmation.components.appeal.response}>` : ``))
             .setStatus(failed ? "Warning" : "Success")
         ], components: []})
     } else {
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index fd8e6b8..af7beb3 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -132,7 +132,6 @@
         }
         let attachmentObject;
         try {
-            // @ts-ignore
             const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger
             let data = {
                 meta: {
@@ -210,6 +209,7 @@
             }))
             .setColor("Danger")
         .send()
+        if (confirmation.cancelled) return
         if (confirmation.success) {
             let messages;
             try {
@@ -234,7 +234,6 @@
             }
             let attachmentObject;
             try {
-                // @ts-ignore
                 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger
                 let data = {
                     meta: {
diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts
index d9a8421..2b386fd 100644
--- a/src/commands/mod/slowmode.ts
+++ b/src/commands/mod/slowmode.ts
@@ -31,6 +31,7 @@
         + `Are you sure you want to set the slowmode in this channel?`)
         .setColor("Danger")
     .send()
+    if (confirmation.cancelled) return
     if (confirmation.success) {
         try {
             (interaction.channel as TextChannel).setRateLimitPerUser(time)
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index a368e7a..7fefb1b 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -13,14 +13,12 @@
     .setDescription("Kicks a user and deletes their messages")
     .addUserOption(option => option.setName("user").setDescription("The user to softban").setRequired(true))
     .addIntegerOption(option => option.setName("delete").setDescription("The days of messages to delete | Default: 0").setMinValue(0).setMaxValue(7).setRequired(false))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are softbanned | Default: Yes").setRequired(false)
-        .addChoices([["Yes", "yes"], ["No", "no"]])
-    )
 
 const callback = async (interaction: CommandInteraction): Promise<any> => {
     const { renderUser } = client.logger
     // TODO:[Modals] Replace this with a modal
     let reason = null;
+    let notify = true;
     let confirmation;
     while (true) {
         let confirmation = await new confirmationMessage(interaction)
@@ -30,21 +28,26 @@
                 "user": renderUser(interaction.options.getUser("user")),
                 "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
             })
-            + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n`
+            + `The user **will${notify ? '' : ' not'}** be notified\n`
             + `${addPlural(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
             + `Are you sure you want to softban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
             .setColor("Danger")
+            .addCustomBoolean("notify", "Notify user", false, null, null, "ICONS.NOTIFY." + (notify ? "ON" : "OFF" ), notify)
             .addReasonButton(reason ?? "")
         .send(reason !== null)
         reason = reason ?? ""
-        if (confirmation.newReason === undefined) break
-        reason = confirmation.newReason
+        if (confirmation.cancelled) return
+        if (confirmation.success) break
+        if (confirmation.newReason) reason = confirmation.newReason
+        if (confirmation.components) {
+            notify = confirmation.components.notify.active
+        }
     }
     if (confirmation.success) {
         let dmd = false;
         let config = await client.database.guilds.read(interaction.guild.id);
         try {
-            if (interaction.options.getString("notify") != "no") {
+            if (notify) {
                 await (interaction.options.getMember("user") as GuildMember).send({
                     embeds: [new EmojiEmbed()
                         .setEmoji("PUNISH.BAN.RED")
@@ -78,7 +81,7 @@
             ], components: []})
         }
         try { await client.database.history.create("softban", interaction.guild.id, member.user, reason) } catch {}
-        let failed = (dmd == false && interaction.options.getString("notify") != "no")
+        let failed = (dmd == false && notify)
         await interaction.editReply({embeds: [new EmojiEmbed()
             .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Softban`)
diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts
index e512084..035b809 100644
--- a/src/commands/mod/unban.ts
+++ b/src/commands/mod/unban.ts
@@ -36,12 +36,12 @@
         + `Are you sure you want to unban <@${resolved.user.id}>?`)
         .setColor("Danger")
     .send()
+    if (confirmation.cancelled) return
     if (confirmation.success) {
         try {
             await interaction.guild.members.unban(resolved.user as User, "Unban");
             let member = (resolved.user as User)
             try { await client.database.history.create("unban", interaction.guild.id, member, interaction.user) } catch {}
-            // @ts-ignore
             const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger
             let data = {
                 meta: {
diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts
index d5f4205..56a0b56 100644
--- a/src/commands/mod/unmute.ts
+++ b/src/commands/mod/unmute.ts
@@ -11,14 +11,12 @@
     .setName("unmute")
     .setDescription("Unmutes a user")
     .addUserOption(option => option.setName("user").setDescription("The user to unmute").setRequired(true))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are unmuted | Default: No").setRequired(false)
-        .addChoices([["Yes", "yes"], ["No", "no"]])
-    )
 
 const callback = async (interaction: CommandInteraction): Promise<any> => {
     const { log, NucleusColors, renderUser, entry, renderDelta } = client.logger
     // TODO:[Modals] Replace this with a modal
     let reason = null;
+    let notify = false;
     let confirmation;
     while (true) {
         confirmation =  await new confirmationMessage(interaction)
@@ -28,20 +26,23 @@
                 "user": renderUser(interaction.options.getUser("user")),
                 "reason": `\n> ${reason ? reason : "*No reason provided*"}`
             })
-            + `The user **will${interaction.options.getString("notify") === "yes" ? '' : ' not'}** be notified\n\n`
+            + `The user **will${notify ? '' : ' not'}** be notified\n\n`
             + `Are you sure you want to unmute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
             .setColor("Danger")
             .addReasonButton(reason ?? "")
         .send(reason !== null)
-        reason = reason ?? ""
-        if (confirmation.newReason === undefined) break
-        reason = confirmation.newReason
+        if (confirmation.success) break
+        if (confirmation.newReason) reason = confirmation.newReason
+        if (confirmation.components) {
+            notify = confirmation.components.notify.active
+        }
     }
+    if (confirmation.cancelled) return
     if (confirmation.success) {
         let dmd = false
         let dm;
         try {
-            if (interaction.options.getString("notify") != "no") {
+            if (notify) {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
                     embeds: [new EmojiEmbed()
                         .setEmoji("PUNISH.MUTE.GREEN")
@@ -88,7 +89,7 @@
             }
         }
         log(data);
-        let failed = (dmd == false && interaction.options.getString("notify") != "no")
+        let failed = (dmd == false && notify)
         await interaction.editReply({embeds: [new EmojiEmbed()
             .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Unmute`)
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 379d49c..3e76321 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -1,4 +1,4 @@
-import Discord, { CommandInteraction, GuildMember, MessageActionRow } from "discord.js";
+import Discord, { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
@@ -12,14 +12,13 @@
     .setName("warn")
     .setDescription("Warns a user")
     .addUserOption(option => option.setName("user").setDescription("The user to warn").setRequired(true))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are warned | Default: Yes").setRequired(false)
-        .addChoices([["Yes", "yes"], ["No", "no"]])
-    )
 
 const callback = async (interaction: CommandInteraction): Promise<any> => {
     const { log, NucleusColors, renderUser, entry } = client.logger
     // TODO:[Modals] Replace this with a modal
     let reason = null;
+    let notify = true;
+    let createAppealTicket = false;
     let confirmation;
     while (true) {
         confirmation = await new confirmationMessage(interaction)
@@ -29,44 +28,52 @@
                 "user": renderUser(interaction.options.getUser("user")),
                 "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
             })
-            + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
+            + `The user **will${notify ? '' : ' not'}** be notified\n\n`
             + `Are you sure you want to warn <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
             .setColor("Danger")
             .addCustomBoolean(
-                "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
+                "appeal", "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
                 async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, reason),
-                "An appeal ticket will be created when Confirm is clicked")
-                .addReasonButton(reason)
+                "An appeal ticket will be created when Confirm is clicked", "CONTROL.TICKET", createAppealTicket)
+            .addCustomBoolean("notify", "Notify user", false, null, null, "ICONS.NOTIFY." + (notify ? "ON" : "OFF" ), notify)
             .addReasonButton(reason ?? "")
         .send(reason !== null)
         reason = reason ?? ""
-        if (confirmation.newReason === undefined) break
-        reason = confirmation.newReason
+        if (confirmation.cancelled) return
+        if (confirmation.success) break
+        if (confirmation.newReason) reason = confirmation.newReason
+        if (confirmation.components) {
+            notify = confirmation.components.notify.active
+            createAppealTicket = confirmation.components.appeal.active
+        }
     }
     if (confirmation.success) {
         let dmd = false
         try {
-            if (interaction.options.getString("notify") != "no") {
+            if (notify) {
+                const config = await client.database.guilds.read(interaction.guild.id)
                 await (interaction.options.getMember("user") as GuildMember).send({
                     embeds: [new EmojiEmbed()
                         .setEmoji("PUNISH.WARN.RED")
                         .setTitle("Warned")
                         .setDescription(`You have been warned in ${interaction.guild.name}` +
                                     (reason ? ` for:\n> ${reason}` : ".") + "\n\n" +
-                                    (confirmation.buttonClicked ? `You can appeal this here ticket: <#${confirmation.response}>` : ``))
+                                    (confirmation.components.appeal.response ? `You can appeal this here ticket: <#${confirmation.components.appeal.response}>` : ``))
                         .setStatus("Danger")
-                    ]
+                        .setFooter({
+                            text: config.moderation.warn.text ? "The button below is set by the server admins. Do not enter any passwords or other account details on the linked site." : "",
+                            iconURL: "https://cdn.discordapp.com/emojis/952295894370369587.webp?size=128&quality=lossless"
+                        })
+                    ],
+                    components: config.moderation.warn.text ? [new MessageActionRow().addComponents([new MessageButton()
+                        .setStyle("LINK")
+                        .setLabel(config.moderation.warn.text)
+                        .setURL(config.moderation.warn.link)
+                    ])] : []
                 })
                 dmd = true
             }
-        } catch {
-            await interaction.editReply({embeds: [new EmojiEmbed()
-                .setEmoji("PUNISH.WARN.RED")
-                .setTitle(`Warn`)
-                .setDescription("Something went wrong and the user was not warned")
-                .setStatus("Danger")
-            ], components: []})
-        }
+        } catch {}
         let data = {
             meta:{
                 type: 'memberWarn',
@@ -91,12 +98,12 @@
             interaction.user, reason
         )} catch {}
         log(data);
-        let failed = (dmd == false && interaction.options.getString("notify") != "no")
+        let failed = (dmd == false && notify)
         if (!failed) {
             await interaction.editReply({embeds: [new EmojiEmbed()
                 .setEmoji(`PUNISH.WARN.GREEN`)
                 .setTitle(`Warn`)
-                .setDescription("The user was warned" + (confirmation.response ? ` and an appeal ticket was opened in <#${confirmation.response}>` : ``))
+                .setDescription("The user was warned" + (confirmation.components.appeal.response ? ` and an appeal ticket was opened in <#${confirmation.components.appeal.response}>` : ``))
                 .setStatus("Success")
             ], components: []})
         } else {
@@ -118,7 +125,7 @@
                             .setStyle("SECONDARY")
                             .setDisabled((interaction.options.getMember("user") as GuildMember).permissionsIn(interaction.channel as Discord.TextChannel).has("VIEW_CHANNEL") === false),
                     ])
-                ],
+                ]
             })
             let component;
             try {
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index 4e3a1c8..0c596ff 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -12,7 +12,6 @@
     .addStringOption(option => option.setName("suggestion").setDescription("The suggestion to send").setRequired(true))
 
 const callback = async (interaction: CommandInteraction): Promise<any> => {
-    // @ts-ignore
     const { renderUser } = client.logger
     let suggestion = interaction.options.getString("suggestion");
     let confirmation = await new confirmationMessage(interaction)
@@ -23,6 +22,7 @@
         .setColor("Danger")
         .setInverted(true)
     .send()
+    if (confirmation.cancelled) return
     if (confirmation.success) {
         await (client.channels.cache.get('955161206459600976') as Discord.TextChannel).send({
             embeds: [
diff --git a/src/commands/role/user.ts b/src/commands/role/user.ts
index c2ade39..c431d39 100644
--- a/src/commands/role/user.ts
+++ b/src/commands/role/user.ts
@@ -28,10 +28,10 @@
         .setDescription(keyValueList({
             "user": renderUser(interaction.options.getUser("user")),
             "role": renderRole(interaction.options.getRole("role"))
-        })
-        + `\nAre you sure you want to ${action == "give" ? "give the role to" : "remove the role from"} ${interaction.options.getUser("user")}?`)
+        }) + `\nAre you sure you want to ${action == "give" ? "give the role to" : "remove the role from"} ${interaction.options.getUser("user")}?`)
         .setColor("Danger")
     .send()
+    if (confirmation.cancelled) return
     if (confirmation.success) {
         try {
             let member = interaction.options.getMember("user") as GuildMember
diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts
index 10a5887..aacc2e7 100644
--- a/src/commands/settings/logs/channel.ts
+++ b/src/commands/settings/logs/channel.ts
@@ -50,6 +50,7 @@
             .setColor("Warning")
             .setInverted(true)
         .send(true)
+        if (confirmation.cancelled) return
         if (confirmation.success) {
             try {
                 await client.database.guilds.write(interaction.guild.id, {"logging.logs.channel": channel.id})
diff --git a/src/commands/settings/logs/ignore.ts b/src/commands/settings/logs/ignore.ts
index 3b81d42..59f6621 100644
--- a/src/commands/settings/logs/ignore.ts
+++ b/src/commands/settings/logs/ignore.ts
@@ -96,6 +96,7 @@
             + `Are you sure you want to **${interaction.options.getString("action") == "add" ? "add" : "remove"}** these to the ignore list?`)
             .setColor("Warning")
         .send(true)
+        if (confirmation.cancelled) return
         if (confirmation.success) {
             let data = client.database.guilds.read(interaction.guild.id)
             if (channel) data.logging.logs.ignore.channels.concat([channel.id])
diff --git a/src/commands/settings/staff.ts b/src/commands/settings/staff.ts
deleted file mode 100644
index e0d2776..0000000
--- a/src/commands/settings/staff.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import { ChannelType } from 'discord-api-types';
-import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import getEmojiByName from "../../utils/getEmojiByName.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-import client from "../../utils/client.js";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("staff")
-    .setDescription("Settings for the staff notifications channel")
-    .addChannelOption(option => option.setName("channel").setDescription("The channel to set the staff notifications channel to").addChannelTypes([
-        ChannelType.GuildNews, ChannelType.GuildText
-    ]).setRequired(false))
-
-const callback = async (interaction: CommandInteraction): Promise<any> => {
-    let m;
-    m = await interaction.reply({embeds: [new EmojiEmbed()
-        .setTitle("Loading")
-        .setStatus("Danger")
-        .setEmoji("NUCLEUS.LOADING")
-    ], ephemeral: true, fetchReply: true});
-    if (interaction.options.getChannel("channel")) {
-        let channel
-        try {
-            channel = interaction.options.getChannel("channel")
-        } catch {
-            return await interaction.editReply({embeds: [new EmojiEmbed()
-                .setEmoji("CHANNEL.TEXT.DELETE")
-                .setTitle("Staff Notifications Channel")
-                .setDescription("The channel you provided is not a valid channel")
-                .setStatus("Danger")
-            ]})
-        }
-        channel = channel as Discord.TextChannel
-        if (channel.guild.id != interaction.guild.id) {
-            return interaction.editReply({embeds: [new EmojiEmbed()
-                .setTitle("Staff Notifications Channel")
-                .setDescription(`You must choose a channel in this server`)
-                .setStatus("Danger")
-                .setEmoji("CHANNEL.TEXT.DELETE")
-            ]});
-        }
-        let confirmation = await new confirmationMessage(interaction)
-            .setEmoji("CHANNEL.TEXT.EDIT")
-            .setTitle("Staff Notifications Channel")
-            .setDescription(
-                `This will be the channel all notifications, updates, user reports etc. will be sent to.\n\n` +
-                `Are you sure you want to set the staff notifications channel to <#${channel.id}>?`
-            )
-            .setColor("Warning")
-            .setInverted(true)
-        .send(true)
-        if (confirmation.success) {
-            try {
-                await client.database.guilds.write(interaction.guild.id, {"logging.staff.channel": channel.id})
-                const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger
-                try {
-                    let data = {
-                        meta:{
-                            type: 'logIgnoreUpdated',
-                            displayName: 'Staff Notifications Channel Updated',
-                            calculateType: 'nucleusSettingsUpdated',
-                            color: NucleusColors.yellow,
-                            emoji: "CHANNEL.TEXT.EDIT",
-                            timestamp: new Date().getTime()
-                        },
-                        list: {
-                            memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                            changedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                            channel: entry(channel.id, renderChannel(channel)),
-                        },
-                        hidden: {
-                            guild: interaction.guild.id
-                        }
-                    }
-                    log(data);
-                } catch {}
-            } catch (e) {
-                return interaction.editReply({embeds: [new EmojiEmbed()
-                    .setTitle("Staff Notifications Channel")
-                    .setDescription(`Something went wrong and the staff notifications channel could not be set`)
-                    .setStatus("Danger")
-                    .setEmoji("CHANNEL.TEXT.DELETE")
-                ], components: []});
-            }
-        } else {
-            return interaction.editReply({embeds: [new EmojiEmbed()
-                .setTitle("Staff Notifications Channel")
-                .setDescription(`No changes were made`)
-                .setStatus("Success")
-                .setEmoji("CHANNEL.TEXT.CREATE")
-            ], components: []});
-        }
-    }
-    let clicks = 0;
-    let data = await client.database.guilds.read(interaction.guild.id);
-    let channel = data.logging.staff.channel;
-    while (true) {
-        await interaction.editReply({embeds: [new EmojiEmbed()
-            .setTitle("Staff Notifications channel")
-            .setDescription(channel ? `Your staff notifications channel is currently set to <#${channel}>` : "This server does not have a staff notifications channel")
-            .setStatus("Success")
-            .setEmoji("CHANNEL.TEXT.CREATE")
-        ], components: [new MessageActionRow().addComponents([new MessageButton()
-            .setCustomId("clear")
-            .setLabel(clicks ? "Click again to confirm" : "Reset channel")
-            .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-            .setStyle("DANGER")
-            .setDisabled(!channel)
-        ])]});
-        let i;
-        try {
-            i = await m.awaitMessageComponent({time: 300000});
-        } catch(e) { break }
-        i.deferUpdate()
-        if (i.component.customId == "clear") {
-            clicks += 1;
-            if (clicks == 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild.id, {}, ["logging.staff.channel"])
-                channel = undefined;
-            }
-        } else {
-            break
-        }
-    }
-    await interaction.editReply({embeds: [new EmojiEmbed()
-        .setTitle("Staff Notifications channel")
-        .setDescription(channel ? `Your staff notifications channel is currently set to <#${channel}>` : "This server does not have a staff notifications channel")
-        .setStatus("Success")
-        .setEmoji("CHANNEL.TEXT.CREATE")
-        .setFooter({text: "Message closed"})
-    ], components: [new MessageActionRow().addComponents([new MessageButton()
-        .setCustomId("clear")
-        .setLabel("Clear")
-        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-        .setStyle("SECONDARY")
-        .setDisabled(true)
-    ])]});
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    let member = (interaction.member as Discord.GuildMember)
-    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the Manage Server permission to use this command"
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index ba16751..16d5f3b 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -109,6 +109,7 @@
             .setColor("Warning")
             .setInverted(true)
             .send(true)
+        if (confirmation.cancelled) return
         if (confirmation.success) {
             let toUpdate = {}
             if (options.enabled !== null) toUpdate["tickets.enabled"] = options.enabled
@@ -345,8 +346,7 @@
             }
         } else if (i.component.customId == "addType") {
             await i.showModal(new Discord.Modal().setCustomId("modal").setTitle("Enter a name for the new type").addComponents(
-                // @ts-ignore
-                new MessageActionRow().addComponents(new TextInputComponent()
+                new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
                     .setCustomId("type")
                     .setLabel("Name")
                     .setMaxLength(100)
diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts
index 7a68c64..d71fdf0 100644
--- a/src/commands/settings/verify.ts
+++ b/src/commands/settings/verify.ts
@@ -47,6 +47,7 @@
             .setColor("Warning")
             .setInverted(true)
         .send(true)
+        if (confirmation.cancelled) return
         if (confirmation.success) {
             try {
                 await client.database.guilds.write(interaction.guild.id, {"verify.role": role.id, "verify.enabled": true});
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index d032598..0c44e37 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -1,21 +1,58 @@
-import { CommandInteraction } from "discord.js";
+import { AutocompleteInteraction, CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
 import { SlashCommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
-import { callback as statsChannelAdd } from '../reflex/statsChannelAdd.js';
 import client from "../utils/client.js"
+import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 
 const command = new SlashCommandBuilder()
     .setName("tag")
     .setDescription("Get and manage the servers tags")
+    .addStringOption(o => o.setName("tag").setDescription("The tag to get").setAutocomplete(true).setRequired(true))
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [tag]");
+const callback = async (interaction: CommandInteraction) => {
+    const config = await client.database.guilds.read(interaction.guild.id)
+    const tags = config.getKey("tags")
+    const tag = tags[interaction.options.getString("tag")]
+    if (!tag) {
+        return await interaction.reply({embeds: [new EmojiEmbed()
+            .setTitle("Tag")
+            .setDescription(`Tag \`${interaction.options.getString("tag")}\` does not exist`)
+            .setEmoji("PUNISH.NICKNAME.RED")
+            .setStatus("Danger")
+        ], ephemeral: true})
+    }
+    let url = ""
+    let components = []
+    if (tag.match(/^(http|https):\/\/[^ "]+$/)) {
+        url = tag
+        components = [new MessageActionRow().addComponents([new MessageButton()
+            .setLabel("Open")
+            .setURL(url)
+            .setStyle("LINK")
+        ])]
+    }
+    return await interaction.reply({embeds: [new EmojiEmbed()
+        .setTitle(interaction.options.getString("tag"))
+        .setDescription(tag)
+        .setEmoji("PUNISH.NICKNAME.GREEN")
+        .setStatus("Success")
+        .setImage(url)
+    ], components: components, ephemeral: true})
+
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
     return true;
 }
 
+const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
+    if (!interaction.guild) return [];
+    const config = await client.database.guilds.read(interaction.guild.id)
+    const tags = Object.keys(config.getKey("tags"));
+    return tags
+}
+
 export { command };
 export { callback };
 export { check };
+export { autocomplete };
\ No newline at end of file
diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts
index 6dbecf4..0e0d54d 100644
--- a/src/commands/tags/create.ts
+++ b/src/commands/tags/create.ts
@@ -52,6 +52,7 @@
         .setColor("Warning")
         .setInverted(true)
     .send()
+    if (confirmation.cancelled) return
     if (!confirmation) return await interaction.editReply({embeds: [new EmojiEmbed()
         .setTitle("Tag Create")
         .setDescription("No changes were made")
diff --git a/src/commands/tags/delete.ts b/src/commands/tags/delete.ts
index 2707465..74ebb25 100644
--- a/src/commands/tags/delete.ts
+++ b/src/commands/tags/delete.ts
@@ -32,6 +32,7 @@
         .setColor("Warning")
         .setInverted(true)
     .send()
+    if (confirmation.cancelled) return
     if (!confirmation) return await interaction.editReply({embeds: [new EmojiEmbed()
         .setTitle("Tag Delete")
         .setDescription("No changes were made")
@@ -39,9 +40,7 @@
         .setEmoji("PUNISH.NICKNAME.GREEN")
     ]});
     try {
-        data = await client.database.guilds.read(interaction.guild.id);
-        delete data.tags[name];
-        await client.database.guilds.write(interaction.guild.id, {tags: data});
+        await client.database.guilds.write(interaction.guild.id, null, [`tags.${name}`]);
     } catch (e) {
         return await interaction.editReply({embeds: [new EmojiEmbed()
             .setTitle("Tag Delete")
diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts
index 77e21ae..2ecdfbf 100644
--- a/src/commands/tags/edit.ts
+++ b/src/commands/tags/edit.ts
@@ -60,6 +60,7 @@
         .setColor("Warning")
         .setInverted(true)
     .send()
+    if (confirmation.cancelled) return
     if (!confirmation) return await interaction.editReply({embeds: [new EmojiEmbed()
         .setTitle("Tag Edit")
         .setDescription("No changes were made")
diff --git a/src/config/default.json b/src/config/default.json
index 81c7880..2f95e94 100644
--- a/src/config/default.json
+++ b/src/config/default.json
@@ -52,7 +52,7 @@
         "channel": null,
         "message": null
     },
-    "stats": [],
+    "stats": {},
     "logging": {
         "logs": {
             "enabled": true,
diff --git a/src/config/emojis.json b/src/config/emojis.json
index 2f32df1..7024a73 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -22,6 +22,10 @@
         "FILTER": "990242059451514902",
         "ATTACHMENT": "997570687193587812",
         "LOGGING": "999613304446144562",
+        "NOTIFY": {
+            "ON": "1000726394579464232",
+            "OFF": "1000726363495477368"
+        },
         "OPP": {
             "ADD": "837355918831124500",
             "REMOVE": "837355918420869162"
diff --git a/src/events/guildBanAdd.ts b/src/events/guildBanAdd.ts
index 64a1604..d095049 100644
--- a/src/events/guildBanAdd.ts
+++ b/src/events/guildBanAdd.ts
@@ -1,5 +1,5 @@
 import { purgeByUser } from '../actions/tickets/delete.js';
-import { callback as statsChannelRemove } from '../reflex/statsChannelRemove.js';
+import { callback as statsChannelRemove } from '../reflex/statsChannelUpdate.js';
 
 export const event = 'guildBanAdd';
 
diff --git a/src/events/guildBanRemove.ts b/src/events/guildBanRemove.ts
index a6d1c14..4d6d1f8 100644
--- a/src/events/guildBanRemove.ts
+++ b/src/events/guildBanRemove.ts
@@ -1,6 +1,6 @@
 import humanizeDuration from 'humanize-duration';
 import { purgeByUser } from '../actions/tickets/delete.js';
-import { callback as statsChannelRemove } from '../reflex/statsChannelRemove.js';
+import { callback as statsChannelRemove } from '../reflex/statsChannelUpdate.js';
 
 export const event = 'guildBanRemove';
 
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
index 1fd1e1f..900c774 100644
--- a/src/events/interactionCreate.ts
+++ b/src/events/interactionCreate.ts
@@ -3,18 +3,43 @@
 import create from "../actions/tickets/create.js";
 import close from "../actions/tickets/delete.js";
 import createTranscript from "../premium/createTranscript.js";
+import Fuse from "fuse.js";
+import { autocomplete as tagAutocomplete } from "../commands/tag.js"
 
 export const event = 'interactionCreate';
 
+
+function getAutocomplete(typed: string, options: string[]): object[] {
+    options = options.filter(option => option.length <= 100) // thanks discord. 6000 character limit on slash command inputs but only 100 for autocomplete.
+    if (!typed) return options.slice(0, 25).sort().map(option => ({name: option, value: option}))
+    const fuse = new Fuse(options, {useExtendedSearch: true, findAllMatches: true, minMatchCharLength: 0}).search(typed)
+    return fuse.slice(0, 25).map(option => ({name: option.item, value: option.item}))
+}
+
+const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
+function generateStatsChannelAutocomplete(typed) {
+    let autocompletions = []
+    const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/)
+    if (beforeLastOpenBracket !== null) { for (let replacement of validReplacements) { autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`) } }
+    else { for (let replacement of validReplacements) { autocompletions.push(`${typed} {${replacement}}`) } }
+    return getAutocomplete(typed, autocompletions)
+}
+
 async function interactionCreate(interaction) {
     if (interaction.componentType === "BUTTON") {
-        if (interaction.customId === "rolemenu") return await roleMenu(interaction)
-        if (interaction.customId === "verifybutton") return verify(interaction)
-        if (interaction.customId === "createticket") return create(interaction)
-        if (interaction.customId === "closeticket") return close(interaction)
-        if (interaction.customId === "createtranscript") return createTranscript(interaction)
+        switch (interaction.customId) {
+            case "rolemenu": { return await roleMenu(interaction) }
+            case "verifybutton": { return verify(interaction) }
+            case "createticket": { return create(interaction) }
+            case "closeticket": { return close(interaction) }
+            case "createtranscript": { return createTranscript(interaction) }
+        }
     } else if (interaction.componentType === "MESSAGE_COMPONENT") {
-        console.table(interaction)
+    } else if (interaction.type === "APPLICATION_COMMAND_AUTOCOMPLETE") {
+        switch (`${interaction.commandName} ${interaction.options.getSubcommandGroup(false)} ${interaction.options.getSubcommand(false)}`) {
+            case `tag null null`: { return interaction.respond(getAutocomplete(interaction.options.getString("tag"), (await tagAutocomplete(interaction)))) }
+            case `settings stats set`: { return interaction.respond(generateStatsChannelAutocomplete(interaction.options.getString("name"))) }
+        }
     }
 }
 
diff --git a/src/events/memberJoin.ts b/src/events/memberJoin.ts
index b1c2700..7bee084 100644
--- a/src/events/memberJoin.ts
+++ b/src/events/memberJoin.ts
@@ -1,4 +1,4 @@
-import { callback as statsChannelAdd } from '../reflex/statsChannelAdd.js';
+import { callback as statsChannelAdd } from '../reflex/statsChannelUpdate.js';
 import { callback as welcome } from '../reflex/welcome.js';
 import log from '../utils/log.js';
 import client from '../utils/client.js';
@@ -7,7 +7,6 @@
 
 export async function callback(_, member) {
     try { welcome(_, member); } catch {}
-    try { statsChannelAdd(_, member); } catch {}
     try {
         const { log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
         try { await client.database.history.create("join", member.guild.id, member.user, null, null) } catch {}
@@ -33,4 +32,5 @@
         }
         log(data);
     } catch {}
+    try { statsChannelAdd(_, member, ); } catch {}
 }
diff --git a/src/events/memberLeave.ts b/src/events/memberLeave.ts
index 592a630..122e01a 100644
--- a/src/events/memberLeave.ts
+++ b/src/events/memberLeave.ts
@@ -1,11 +1,10 @@
 import humanizeDuration from 'humanize-duration';
 import { purgeByUser } from '../actions/tickets/delete.js';
-import { callback as statsChannelRemove } from '../reflex/statsChannelRemove.js';
+import { callback as statsChannelRemove } from '../reflex/statsChannelUpdate.js';
 
 export const event = 'guildMemberRemove'
 
 export async function callback(client, member) {
-    try { await statsChannelRemove(client, member); } catch {}
     try { purgeByUser(member.id, member.guild); } catch {}
     try {
         const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
@@ -72,4 +71,5 @@
         }
         log(data);
     } catch (e) { console.log(e) }
+    try { await statsChannelRemove(client, member); } catch {}
 }
diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts
index 7db7d39..dce1959 100644
--- a/src/events/messageCreate.ts
+++ b/src/events/messageCreate.ts
@@ -1,7 +1,6 @@
 import { LinkCheck, MalwareCheck, NSFWCheck, SizeCheck, TestString, TestImage } from '../reflex/scanners.js'
 import logAttachment from '../premium/attachmentLogs.js'
 import createLogException from '../utils/createLogException.js'
-import { capitalize } from '../utils/generateKeyValueList.js'
 import getEmojiByName from '../utils/getEmojiByName.js'
 
 export const event = 'messageCreate'
@@ -19,7 +18,7 @@
     let config = await client.memory.readGuildInfo(message.guild.id);
     const filter = getEmojiByName("ICONS.FILTER")
     let attachmentJump = ""
-    if (config.logging.attachments.saved[message.channel.id + message.id]) { attachmentJump = ` [[View attachments]](${config})` }
+    if (config.logging.attachments.saved[message.channel.id + message.id]) { attachmentJump = ` [[View attachments]](${config.logging.attachments.saved[message.channel.id + message.id]})` }
     let list = {
         messageId: entry(message.id, `\`${message.id}\``),
         sentBy: entry(message.author.id, renderUser(message.author)),
@@ -64,7 +63,7 @@
     }
 
     if (fileNames.files.length > 0) {
-        fileNames.files.forEach(async element => {
+        for (let element of fileNames.files) {
             if(!message) return;
             let url = element.url ? element.url : element.local
             if (url != undefined) {
@@ -173,7 +172,7 @@
                     }
                 }
             }
-        });
+        };
     }
     if(!message) return;
 
diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts
index 2263d51..3c23739 100644
--- a/src/events/messageDelete.ts
+++ b/src/events/messageDelete.ts
@@ -14,6 +14,7 @@
         let content = message.cleanContent
         content.replace(`\``, `\\\``)
         if (content.length > 256) content = content.substring(0, 253) + '...'
+        let attachments = message.attachments.size + (message.content.match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi) ?? []).length
         let attachmentJump = ""
         let config = (await client.database.guilds.read(message.guild.id)).logging.attachments.saved[message.channel.id + message.id];
         if (config) { attachmentJump = ` [[View attachments]](${config})` }
@@ -35,7 +36,7 @@
                 sentIn: entry(message.channel.id, renderChannel(message.channel)),
                 deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
                 mentions: message.mentions.users.size,
-                attachments: entry(message.attachments.size, message.attachments.size + attachmentJump),
+                attachments: entry(attachments, attachments + attachmentJump),
                 repliedTo: entry(
                     message.reference.messageId || null,
                     message.reference.messageId ? `[[Jump to message]](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.reference.messageId})` : "None"
diff --git a/src/index.ts b/src/index.ts
index 0ae917c..5f5987e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,6 +11,9 @@
 client.on("ready", () => {
     runServer(client);
 });
+process.on("unhandledRejection", (err) => {
+    console.error(err);
+});
 
 client.logger = new Logger()
 client.verify = {}
diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts
index 8ea22f7..a1a5974 100644
--- a/src/reflex/scanners.ts
+++ b/src/reflex/scanners.ts
@@ -4,14 +4,16 @@
 import generateFileName from '../utils/temp/generateFileName.js'
 import Tesseract from 'node-tesseract-ocr';
 
+interface NSFWSchema { nsfw: boolean }
+interface MalwareSchema { safe: boolean }
 
-export async function testNSFW(link: string): Promise<JSON> {
+export async function testNSFW(link: string): Promise<NSFWSchema> {
     let p = await saveAttachment(link)
     let result = await us.nsfw.file(p)
     return result
 }
 
-export async function testMalware(link: string): Promise<JSON> {
+export async function testMalware(link: string): Promise<MalwareSchema> {
     let p = await saveAttachment(link)
     let result = await us.malware.file(p)
     return result
@@ -24,7 +26,7 @@
     return fileName
 }
 
-export async function testLink(link: string): Promise<JSON> {
+export async function testLink(link: string): Promise<unknown> {
     return await us.link.scan(link)
 }
 
@@ -75,7 +77,6 @@
 export async function NSFWCheck(element): Promise<boolean> {
     try {
         let test = (await testNSFW(element))
-        //@ts-ignore
         return test.nsfw
     } catch {
         return false
@@ -90,8 +91,7 @@
 
 export async function MalwareCheck(element): Promise<boolean> {
     try {
-        //@ts-ignore
-        return (await scan.testMalware(element)).safe
+        return (await testMalware(element)).safe
     } catch {
         return true
     }
diff --git a/src/reflex/statsChannelAdd.ts b/src/reflex/statsChannelAdd.ts
deleted file mode 100644
index 32de0ff..0000000
--- a/src/reflex/statsChannelAdd.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
-import singleNotify from '../utils/singleNotify.js';
-import client from '../utils/client.js';
-
-export async function callback(_, member) {
-    let config = await client.database.guilds.read(member.guild.id);
-
-    config.stats.forEach(async element => {
-        if (element.enabled) {
-            let string = element.text
-            if (!string) return
-            string = await convertCurlyBracketString(string, member.id, member.displayName, member.guild.name, member.guild.members)
-            let channel;
-            try {
-                channel = await member.client.channels.fetch(element.channel)
-            } catch (error) { channel = null }
-            if (!channel) {
-                return singleNotify(
-                    "statsChannelDeleted",
-                    member.guild.id,
-                    "One or more of your stats channels have been deleted. Please open the settings menu to change this.",
-                    "Critical"
-                )
-            }
-            if (channel.guild.id !== member.guild.id) return
-            try {
-                await channel.edit({ name: string })
-            } catch (err) {
-                console.error(err)
-            }
-        }
-    });
-}
diff --git a/src/reflex/statsChannelRemove.ts b/src/reflex/statsChannelRemove.ts
deleted file mode 100644
index c6d4e65..0000000
--- a/src/reflex/statsChannelRemove.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import client from '../utils/client.js';
-import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
-import singleNotify from '../utils/singleNotify.js';
-
-export async function callback(_, member) {
-    let config = await client.database.guilds.read(member.guild.id);
-
-    config.stats.forEach(async element => {
-        if (element.enabled) {
-            let string = element.text
-            if (!string) return
-            string = await convertCurlyBracketString(string, member.id, member.displayName, member.guild.name, member.guild.members)
-            let channel;
-            try {
-                channel = await member.client.channels.fetch(element.channel)
-            } catch { channel = null }
-            if (!channel) return singleNotify(
-                "statsChannelDeleted",
-                member.guild.id,
-                "One or more of your stats channels have been deleted. Please open the settings menu to change this.",
-                "Critical"
-            )
-            try {
-                await channel.edit({ name: string })
-            } catch (err) {
-                console.error(err)
-            }
-        }
-    });
-}
\ No newline at end of file
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index da10cfb..988fd37 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -3,20 +3,24 @@
 import EmojiEmbed from "./generateEmojiEmbed.js"
 import getEmojiByName from "./getEmojiByName.js";
 
+
+interface CustomBoolean<T> {
+    title: string;
+    disabled: boolean;
+    value: string | null;
+    emoji: string | null;
+    active: boolean;
+    onClick: () => Promise<T>;
+    response: T | null;
+}
+
 class confirmationMessage {
     interaction: CommandInteraction;
     title: string = "";
     emoji: string = "";
     description: string = "";
     color: string = "";
-    customCallback: () => any = () => {};
-    customButtonTitle: string;
-    customButtonDisabled: boolean;
-    customCallbackString: string = "";
-    customCallbackClicked: boolean = false;
-    customCallbackResponse: any = null;
-    customBoolean: () => any = () => {}; // allow multiple booleans
-    customBooleanClicked: boolean = null;
+    customButtons: {[index:string]: CustomBoolean<unknown>} = {};
     inverted: boolean = false;
     reason: string | null = null;
 
@@ -29,21 +33,15 @@
     setDescription(description: string) { this.description = description; return this }
     setColor(color: string) { this.color = color; return this }
     setInverted(inverted: boolean) { this.inverted = inverted; return this }
-    addCustomCallback(title: string, disabled: boolean, callback: () => any, callbackClicked: string) {
-        if (this.customButtonTitle) return this
-        this.customButtonTitle = title;
-        this.customButtonDisabled = disabled;
-        this.customCallback = callback;
-        this.customCallbackString = callbackClicked;
-        return this;
-    }
-    addCustomBoolean(title: string, disabled: boolean, callback: () => any, callbackClicked: string) {
-        if (this.customButtonTitle) return this
-        this.customButtonTitle = title;
-        this.customButtonDisabled = disabled;
-        this.customBoolean = callback;
-        this.customCallbackString = callbackClicked;
-        this.customBooleanClicked = false;
+    addCustomBoolean(customId: string, title: string, disabled: boolean, callback: () => Promise<unknown> | null, callbackClicked: string | null, emoji?: string, initial?: boolean) {        this.customButtons[customId] = {
+            title: title,
+            disabled: disabled,
+            value: callbackClicked,
+            emoji: emoji,
+            active: initial ?? false,
+            onClick: callback ?? (() => null),
+            response: null,
+        }
         return this;
     }
     addReasonButton(reason: string) {
@@ -52,92 +50,80 @@
     }
     async send(editOnly?: boolean) {
         while (true) {
+            let fullComponents = [
+                new Discord.MessageButton()
+                    .setCustomId("yes")
+                    .setLabel("Confirm")
+                    .setStyle(this.inverted ? "SUCCESS" : "DANGER")
+                    .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
+                new Discord.MessageButton()
+                    .setCustomId("no")
+                    .setLabel("Cancel")
+                    .setStyle("SECONDARY")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+            ]
+            Object.entries(this.customButtons).forEach(([k, v]) => {
+                fullComponents.push(new Discord.MessageButton()
+                    .setCustomId(k)
+                    .setLabel(v.title)
+                    .setStyle(v.active ? "SUCCESS" : "PRIMARY")
+                    .setEmoji(getEmojiByName(v.emoji, "id"))
+                    .setDisabled(v.disabled))
+            })
+            if (this.reason !== null) fullComponents.push(new Discord.MessageButton()
+                .setCustomId("reason")
+                .setLabel(`Edit Reason`)
+                .setStyle("PRIMARY")
+                .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
+                .setDisabled(false)
+            )
+            let components = []
+            for (let i = 0; i < fullComponents.length; i += 5) {
+                components.push(new MessageActionRow().addComponents(fullComponents.slice(i, i + 5)));
+            }
             let object = {
                 embeds: [
                     new EmojiEmbed()
                         .setEmoji(this.emoji)
                         .setTitle(this.title)
-                        .setDescription(this.description)
+                        .setDescription(this.description + "\n\n" + Object.values(this.customButtons).map(v => {
+                            if (v.value === null) return "";
+                            return v.active ? `*${v.value}*\n` : "";
+                        }).join(""))
                         .setStatus(this.color)
-                        .setFooter({text: (this.customBooleanClicked ?? this.customCallbackClicked) ? this.customCallbackString : ""})
                 ],
-                components: [
-                    new MessageActionRow().addComponents([
-                        new Discord.MessageButton()
-                            .setCustomId("yes")
-                            .setLabel("Confirm")
-                            .setStyle(this.inverted ? "SUCCESS" : "DANGER")
-                            .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
-                        new Discord.MessageButton()
-                            .setCustomId("no")
-                            .setLabel("Cancel")
-                            .setStyle("SECONDARY")
-                            .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                    ].concat(this.customButtonTitle ? [new Discord.MessageButton()
-                        .setCustomId("custom")
-                        .setLabel(this.customButtonTitle)
-                        .setStyle(this.customBooleanClicked !== null ?
-                            ( this.customBooleanClicked ? "SUCCESS" : "PRIMARY" ) :
-                            "PRIMARY"
-                        )
-                        .setDisabled(this.customButtonDisabled)
-                        .setEmoji(getEmojiByName("CONTROL.TICKET", "id"))
-                    ] : [])
-                    .concat(this.reason !== null ? [new Discord.MessageButton()
-                        .setCustomId("reason")
-                        .setLabel(`Edit Reason`)
-                        .setStyle("PRIMARY")
-                        .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
-                    ] : []))
-                ],
+                components: components,
                 ephemeral: true,
                 fetchReply: true
             }
             let m;
-            if ( editOnly ) {
-                m = await this.interaction.editReply(object);
-            } else {
-                m = await this.interaction.reply(object)
-            }
+            try {
+                if ( editOnly ) {
+                    m = await this.interaction.editReply(object);
+                } else {
+                    m = await this.interaction.reply(object)
+                }
+            } catch { return { cancelled: true } }
             let component;
             try {
                 component = await (m as Message).awaitMessageComponent({filter: (m) => m.user.id === this.interaction.user.id, time: 300000});
             } catch (e) {
-                return {
-                    success: false,
-                    buttonClicked: this.customBooleanClicked ?? this.customCallbackClicked,
-                    response: this.customCallbackResponse
-                };
+                return { success: false, components: this.customButtons };
             }
             if (component.customId === "yes") {
                 component.deferUpdate();
-                if (this.customBooleanClicked === true) this.customCallbackResponse = await this.customBoolean();
-                return {
-                    success: true,
-                    buttonClicked: this.customBooleanClicked ?? this.customCallbackClicked,
-                    response: this.customCallbackResponse
+                for (let [k, v] of Object.entries(this.customButtons)) {
+                    if (!v.active) continue
+                    try { v.response = await v.onClick(); }
+                    catch (e) { console.log(e) }
                 };
+                return { success: true, components: this.customButtons };
             } else if (component.customId === "no") {
                 component.deferUpdate();
-                return {
-                    success: false,
-                    buttonClicked: this.customBooleanClicked ?? this.customCallbackClicked,
-                    response: this.customCallbackResponse
-                };
-            } else if (component.customId === "custom") {
-                component.deferUpdate();
-                if (this.customBooleanClicked !== null) {
-                    this.customBooleanClicked = !this.customBooleanClicked;
-                } else {
-                    this.customCallbackResponse = await this.customCallback();
-                    this.customCallbackClicked = true;
-                    this.customButtonDisabled = true;
-                }
-                editOnly = true;
+                return { success: false, components: this.customButtons };
             } else if (component.customId === "reason") {
                 await component.showModal(new Discord.Modal().setCustomId("modal").setTitle(`Editing reason`).addComponents(
-                    // @ts-ignore
-                    new MessageActionRow().addComponents(new TextInputComponent()
+                    new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
                         .setCustomId("reason")
                         .setLabel("Reason")
                         .setMaxLength(2000)
@@ -163,10 +149,13 @@
                 let out;
                 try {
                     out = await modalInteractionCollector(m, (m) => m.channel.id == this.interaction.channel.id, (m) => m.customId == "reason")
-                } catch (e) { continue }
-                if (out.fields) {
-                    return {newReason: out.fields.getTextInputValue("reason") ?? ""};
-                } else { return { newReason: this.reason } }
+                } catch (e) { return {} }
+                if (out.fields) { return { newReason: out.fields.getTextInputValue("reason") ?? "" }; }
+                else { return { newReason: this.reason } }
+            } else {
+                component.deferUpdate();
+                this.customButtons[component.customId].active = !this.customButtons[component.customId].active;
+                return { components: this.customButtons };
             }
         }
     }
diff --git a/src/utils/convertCurlyBracketString.ts b/src/utils/convertCurlyBracketString.ts
index 0277751..6ffdc8f 100644
--- a/src/utils/convertCurlyBracketString.ts
+++ b/src/utils/convertCurlyBracketString.ts
@@ -2,12 +2,12 @@
     let memberCount = (await members.fetch()).size
     let bots = (await members.fetch()).filter(m => m.user.bot).size
     str = str
-        .replace("{@}", `<@${memberID}>`)
-        .replace("{server}", `${serverName}`)
-        .replace("{name}", `${memberName}`)
-        .replace("{count}", `${memberCount}`)
-        .replace("{count:bots}", `${bots}`)
-        .replace("{count:humans}", `${memberCount - bots}`);
+        .replace("{member:mention}", memberID ? `<@${memberID}>` : "{member:mention}")
+        .replace("{member:name}", memberName ? `${memberName}` : "{member:name}")
+        .replace("{serverName}", serverName ? `${serverName}` : "{serverName}")
+        .replace("{memberCount}", memberCount ? `${memberCount}` : "{memberCount}")
+        .replace("{memberCount:bots}", bots ? `${bots}` : "{memberCount:bots}")
+        .replace("{memberCount:humans}", (memberCount && bots) ? `${memberCount - bots}` : "{memberCount:humans}");
 
     return str
 }
diff --git a/src/utils/database.ts b/src/utils/database.ts
index c0ae9be..5b1d6d9 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -63,8 +63,13 @@
         }
     }
 
-    async remove(guild: string, key: string, value: any) {
-        if (Array.isArray(value)) {
+    async remove(guild: string, key: string, value: any, innerKey?: string) {
+        if (innerKey) {
+            await this.guilds.updateOne({ id: guild }, {
+                $pull: { [key]: { [innerKey]: { $eq: value } } }
+            }, { upsert: true });
+        }
+        else if (Array.isArray(value)) {
             await this.guilds.updateOne({ id: guild }, {
                 $pullAll: { [key]: value }
             }, { upsert: true });
@@ -200,11 +205,7 @@
         channel: string | null,
         message: string | null,
     }
-    stats: {
-        enabled: boolean,
-        channel: string | null,
-        text: string | null,
-    }[]
+    stats: {}
     logging: {
         logs: {
             enabled: boolean,