More commands fixed and purge to here
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index e32a720..7346bcc 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -53,6 +53,7 @@
                 false,
                 undefined,
                 "The user will be sent a DM",
+                null,
                 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
                 notify
             )
diff --git a/src/commands/mod/info.ts b/src/commands/mod/info.ts
index 12c414f..6e9abed 100644
--- a/src/commands/mod/info.ts
+++ b/src/commands/mod/info.ts
@@ -1,3 +1,4 @@
+import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
 import type { HistorySchema } from "../../utils/database.js";
 import Discord, {
     CommandInteraction,
@@ -8,11 +9,12 @@
     ButtonBuilder,
     MessageComponentInteraction,
     ModalSubmitInteraction,
-    TextInputComponent,
     ButtonStyle,
-    StringSelectMenuInteraction
+    StringSelectMenuInteraction,
+    TextInputStyle,
+    APIMessageComponentEmoji
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import client from "../../utils/client.js";
@@ -153,60 +155,59 @@
             }
         }
         if (pageIndex === null) pageIndex = 0;
-        const components = (
-            openFilterPane
-                ? [
-                      new ActionRowBuilder<Discord.StringSelectMenuBuilder>().addComponents(
-                          new Discord.SelectMenuBuilder()
-                              .setOptions(
-                                  Object.entries(types).map(([key, value]) => ({
-                                      label: value.text,
-                                      value: key,
-                                      default: filteredTypes.includes(key),
-                                      emoji: client.emojis.resolve(getEmojiByName(value.emoji, "id"))
-                                  }))
-                              )
-                              .setMinValues(1)
-                              .setMaxValues(Object.keys(types).length)
-                              .setCustomId("filter")
-                              .setPlaceholder("Select at least one event")
-                      )
-                  ]
-                : []
-        ).concat([
-            new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
-                new ButtonBuilder()
-                    .setCustomId("prevYear")
-                    .setLabel((currentYear - 1).toString())
-                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
-                    .setStyle(ButtonStyle.Secondary),
-                new ButtonBuilder().setCustomId("prevPage").setLabel("Previous page").setStyle(ButtonStyle.Primary),
-                new ButtonBuilder().setCustomId("today").setLabel("Today").setStyle(ButtonStyle.Primary),
-                new ButtonBuilder()
-                    .setCustomId("nextPage")
-                    .setLabel("Next page")
-                    .setStyle(ButtonStyle.Primary)
-                    .setDisabled(pageIndex >= groups.length - 1 && currentYear === new Date().getFullYear()),
-                new ButtonBuilder()
-                    .setCustomId("nextYear")
-                    .setLabel((currentYear + 1).toString())
-                    .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
-                    .setStyle(ButtonStyle.Secondary)
-                    .setDisabled(currentYear === new Date().getFullYear())
-            ]),
-            new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
-                new ButtonBuilder()
-                    .setLabel("Mod notes")
-                    .setCustomId("modNotes")
-                    .setStyle(ButtonStyle.Primary)
-                    .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
-                new ButtonBuilder()
-                    .setLabel("Filter")
-                    .setCustomId("openFilter")
-                    .setStyle(openFilterPane ? ButtonStyle.Success : ButtonStyle.Primary)
-                    .setEmoji(getEmojiByName("ICONS.FILTER", "id"))
-            ])
-        ]);
+        let components: (ActionRowBuilder<Discord.StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[] = []
+        if (openFilterPane) components = components.concat([
+            new ActionRowBuilder<Discord.StringSelectMenuBuilder>().addComponents(
+                new Discord.StringSelectMenuBuilder().setOptions(
+                    ...Object.entries(types).map(([key, value]) => new StringSelectMenuOptionBuilder()
+                        .setLabel(value.text)
+                        .setValue(key)
+                        .setDefault(filteredTypes.includes(key))
+                        .setEmoji(client.emojis.resolve(getEmojiByName(value.emoji, "id"))! as APIMessageComponentEmoji)
+                    )
+                )
+                .setMinValues(1)
+                .setMaxValues(Object.keys(types).length)
+                .setCustomId("filter")
+                .setPlaceholder("Select events to show")
+            )
+        ])
+        components = components.concat([new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
+            new ButtonBuilder()
+                .setCustomId("prevYear")
+                .setLabel((currentYear - 1).toString())
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                .setStyle(ButtonStyle.Secondary),
+            new ButtonBuilder()
+                .setCustomId("prevPage")
+                .setLabel("Previous page")
+                .setStyle(ButtonStyle.Primary),
+            new ButtonBuilder().setCustomId("today").setLabel("Today").setStyle(ButtonStyle.Primary),
+            new ButtonBuilder()
+                .setCustomId("nextPage")
+                .setLabel("Next page")
+                .setStyle(ButtonStyle.Primary)
+                .setDisabled(pageIndex >= groups.length - 1 && currentYear === new Date().getFullYear()),
+            new ButtonBuilder()
+                .setCustomId("nextYear")
+                .setLabel((currentYear + 1).toString())
+                .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
+                .setStyle(ButtonStyle.Secondary)
+                .setDisabled(currentYear === new Date().getFullYear())
+        ])])
+        components = components.concat([new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
+            new ButtonBuilder()
+                .setLabel("Mod notes")
+                .setCustomId("modNotes")
+                .setStyle(ButtonStyle.Primary)
+                .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
+            new ButtonBuilder()
+                .setLabel("Filter")
+                .setCustomId("openFilter")
+                .setStyle(openFilterPane ? ButtonStyle.Success : ButtonStyle.Primary)
+                .setEmoji(getEmojiByName("ICONS.FILTER", "id"))
+        ])])
+
         const end =
             "\n\nJanuary " +
             currentYear.toString() +
@@ -216,7 +217,7 @@
             currentYear.toString();
         if (groups.length > 0) {
             const toRender = groups[Math.min(pageIndex, groups.length - 1)]!;
-            m = (await interaction.editReply({
+            m = await interaction.editReply({
                 embeds: [
                     new EmojiEmbed()
                         .setEmoji("MEMBER.JOIN")
@@ -226,25 +227,25 @@
                         )
                         .setStatus("Success")
                         .setFooter({
-                            text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : ""
+                            text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : "No filters selected"
                         })
                 ],
                 components: components
-            })) as Message;
+            });
         } else {
-            m = (await interaction.editReply({
+            m = await interaction.editReply({
                 embeds: [
                     new EmojiEmbed()
                         .setEmoji("MEMBER.JOIN")
                         .setTitle("Moderation history for " + member.user.username)
-                        .setDescription(`**${currentYear}**\n\n*No events*` + "\n\n" + end)
+                        .setDescription(`**${currentYear}**\n\n*No events*`)
                         .setStatus("Success")
                         .setFooter({
-                            text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : ""
+                            text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : "No filters selected"
                         })
                 ],
                 components: components
-            })) as Message;
+            })
         }
         let i: MessageComponentInteraction;
         try {
@@ -315,7 +316,7 @@
     let m: Message;
     const member = interaction.options.getMember("user") as Discord.GuildMember;
     await interaction.reply({
-        embeds: [new EmojiEmbed().setEmoji("NUCLEUS.LOADING").setTitle("Downloading Data").setStatus("Danger")],
+        embeds: LoadingEmbed,
         ephemeral: true,
         fetchReply: true
     });
@@ -324,9 +325,7 @@
     let timedOut = false;
     while (!timedOut) {
         note = await client.database.notes.read(member.guild.id, member.id);
-        if (firstLoad && !note) {
-            await showHistory(member, interaction);
-        }
+        if (firstLoad && !note) { await showHistory(member, interaction); }
         firstLoad = false;
         m = (await interaction.editReply({
             embeds: [
@@ -344,7 +343,7 @@
                         .setCustomId("modify")
                         .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
                     new ButtonBuilder()
-                        .setLabel("View moderation history")
+                        .setLabel("Moderation history")
                         .setStyle(ButtonStyle.Primary)
                         .setCustomId("history")
                         .setEmoji(getEmojiByName("ICONS.HISTORY", "id"))
@@ -364,14 +363,14 @@
                     .setCustomId("modal")
                     .setTitle("Editing moderator note")
                     .addComponents(
-                        new ActionRowBuilder<Discord.TextInputComponent>().addComponents(
-                            new TextInputComponent()
+                        new ActionRowBuilder<Discord.TextInputBuilder>().addComponents(
+                            new Discord.TextInputBuilder()
                                 .setCustomId("note")
                                 .setLabel("Note")
                                 .setMaxLength(4000)
                                 .setRequired(false)
-                                .setStyle("PARAGRAPH")
-                                .setValue(note ? note : "")
+                                .setStyle(TextInputStyle.Paragraph)
+                                .setValue(note ? note : " ")
                         )
                     )
             );
@@ -408,7 +407,9 @@
             if (out === null) {
                 continue;
             } else if (out instanceof ModalSubmitInteraction) {
-                const toAdd = out.fields.getTextInputValue("note") || null;
+                let toAdd = out.fields.getTextInputValue("note") || null;
+                if (toAdd === " ") toAdd = null;
+                if (toAdd) toAdd = toAdd.trim();
                 await client.database.notes.create(member.guild.id, member.id, toAdd);
             } else {
                 continue;
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index 2feb5d7..dd71892 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -1,4 +1,4 @@
-import { LinkWarningFooter } from './../../utils/defaultEmbeds';
+import { LinkWarningFooter } from './../../utils/defaultEmbeds.js';
 import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
 // @ts-expect-error
 import humanizeDuration from "humanize-duration";
@@ -42,6 +42,7 @@
                 false,
                 null,
                 "The user will be sent a DM",
+                null,
                 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
                 notify
             )
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index 0e76cba..95c8c5a 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -192,6 +192,7 @@
                 async () =>
                     await create(interaction.guild, interaction.options.getUser("user")!, interaction.user, reason),
                 "An appeal ticket will be created when Confirm is clicked",
+                null,
                 "CONTROL.TICKET",
                 createAppealTicket
             )
@@ -201,6 +202,7 @@
                 false,
                 undefined,
                 null,
+                null,
                 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
                 notify
             )
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index b5e5554..0975375 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -45,6 +45,7 @@
                 false,
                 null,
                 null,
+                null,
                 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
                 notify
             )
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index e9aa41a..6087890 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -1,4 +1,4 @@
-import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle } from "discord.js";
+import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder } from "discord.js";
 import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@@ -26,7 +26,8 @@
         );
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const user = (interaction.options.getMember("user") as GuildMember);
+    if (!interaction.guild) return;
+    const user = (interaction.options.getMember("user") as GuildMember | null);
     const channel = interaction.channel as GuildChannel;
     if (
         !["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(
@@ -46,7 +47,7 @@
         });
     }
     // TODO:[Modals] Replace this with a modal
-    if (!interaction.options.getInteger("amount")) {
+    if (!interaction.options.get("amount")) {
         await interaction.reply({
             embeds: [
                 new EmojiEmbed()
@@ -74,17 +75,17 @@
                         .setStatus("Danger")
                 ],
                 components: [
-                    new Discord.ActionRowBuilder().addComponents([
+                    new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
                         new Discord.ButtonBuilder().setCustomId("1").setLabel("1").setStyle(ButtonStyle.Secondary),
                         new Discord.ButtonBuilder().setCustomId("3").setLabel("3").setStyle(ButtonStyle.Secondary),
                         new Discord.ButtonBuilder().setCustomId("5").setLabel("5").setStyle(ButtonStyle.Secondary)
                     ]),
-                    new Discord.ActionRowBuilder().addComponents([
+                    new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
                         new Discord.ButtonBuilder().setCustomId("10").setLabel("10").setStyle(ButtonStyle.Secondary),
                         new Discord.ButtonBuilder().setCustomId("25").setLabel("25").setStyle(ButtonStyle.Secondary),
                         new Discord.ButtonBuilder().setCustomId("50").setLabel("50").setStyle(ButtonStyle.Secondary)
                     ]),
-                    new Discord.ActionRowBuilder().addComponents([
+                    new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
                         new Discord.ButtonBuilder()
                             .setCustomId("done")
                             .setLabel("Done")
@@ -110,16 +111,14 @@
             }
             const amount = parseInt((await component).customId);
 
-            let messages;
+            let messages: Discord.Message[] = [];
             await (interaction.channel as TextChannel).messages.fetch({ limit: amount }).then(async (ms) => {
                 if (user) {
                     ms = ms.filter((m) => m.author.id === user.id);
                 }
-                messages = await (channel as TextChannel).bulkDelete(ms, true);
+                messages = (await (channel as TextChannel).bulkDelete(ms, true)).map(m => m as Discord.Message);
             });
-            if (messages) {
-                deleted = deleted.concat(messages.map((m) => m));
-            }
+            deleted = deleted.concat(messages);
         }
         if (deleted.length === 0)
             return await interaction.editReply({
@@ -136,11 +135,12 @@
             await client.database.history.create(
                 "purge",
                 interaction.guild.id,
-                user,
-                interaction.options.getString("reason"),
+                user.user,
+                interaction.user,
+                (interaction.options.get("reason")?.value as (string | null)) ?? "*No reason provided*",
                 null,
                 null,
-                deleted.length
+                deleted.length.toString()
             );
         }
         const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
@@ -156,8 +156,8 @@
             list: {
                 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
                 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
-                messagesCleared: entry(deleted.length, deleted.length)
+                channel: entry(interaction.channel!.id, renderChannel(interaction.channel! as GuildChannel)),
+                messagesCleared: entry(deleted.length.toString(), deleted.length.toString())
             },
             hidden: {
                 guild: interaction.guild.id
@@ -189,7 +189,7 @@
                     .setStatus("Success")
             ],
             components: [
-                new Discord.ActionRowBuilder().addComponents([
+                new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
                     new Discord.ButtonBuilder()
                         .setCustomId("download")
                         .setLabel("Download transcript")
@@ -207,7 +207,7 @@
         } catch {
             return;
         }
-        if (component && component.customId === "download") {
+        if (component.customId === "download") {
             interaction.editReply({
                 embeds: [
                     new EmojiEmbed()
@@ -239,12 +239,8 @@
             .setDescription(
                 keyValueList({
                     channel: `<#${channel.id}>`,
-                    amount: interaction.options.getInteger("amount").toString(),
-                    reason: `\n> ${
-                        interaction.options.getString("reason")
-                            ? interaction.options.getString("reason")
-                            : "*No reason provided*"
-                    }`
+                    amount: (interaction.options.get("amount")?.value as number).toString(),
+                    reason: `\n> ${interaction.options.get("reason")?.value ? interaction.options.get("reason")?.value : "*No reason provided*"}`
                 })
             )
             .setColor("Danger")
@@ -255,7 +251,7 @@
             try {
                 if (!user) {
                     const toDelete = await (interaction.channel as TextChannel).messages.fetch({
-                        limit: interaction.options.getInteger("amount")
+                        limit: interaction.options.get("amount")?.value as number
                     });
                     messages = await (channel as TextChannel).bulkDelete(toDelete, true);
                 } else {
@@ -265,7 +261,7 @@
                                 limit: 100
                             })
                         ).filter((m) => m.author.id === user.id)
-                    ).first(interaction.options.getInteger("amount"));
+                    ).first(interaction.options.get("amount")?.value as number);
                     messages = await (channel as TextChannel).bulkDelete(toDelete, true);
                 }
             } catch (e) {
@@ -280,15 +276,29 @@
                     components: []
                 });
             }
+            if (!messages) {
+                await interaction.editReply({
+                    embeds: [
+                        new EmojiEmbed()
+                            .setEmoji("CHANNEL.PURGE.RED")
+                            .setTitle("Purge")
+                            .setDescription("No messages could be deleted")
+                            .setStatus("Danger")
+                    ],
+                    components: []
+                });
+                return;
+            }
             if (user) {
                 await client.database.history.create(
                     "purge",
                     interaction.guild.id,
-                    user,
-                    interaction.options.getString("reason"),
+                    user.user,
+                    interaction.user,
+                    (interaction.options.get("reason")?.value as (string | null)) ?? "*No reason provided*",
                     null,
                     null,
-                    messages.size
+                    messages.size.toString()
                 );
             }
             const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
@@ -304,8 +314,8 @@
                 list: {
                     memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
                     purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                    channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
-                    messagesCleared: entry(messages.size, messages.size)
+                    channel: entry(interaction.channel!.id, renderChannel(interaction.channel! as GuildChannel)),
+                    messagesCleared: entry(messages.size.toString(), messages.size.toString())
                 },
                 hidden: {
                     guild: interaction.guild.id
@@ -314,14 +324,26 @@
             log(data);
             let out = "";
             messages.reverse().forEach((message) => {
-                out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(
-                    message.createdTimestamp
-                ).toISOString()}]\n`;
-                const lines = message.content.split("\n");
-                lines.forEach((line) => {
-                    out += `> ${line}\n`;
-                });
-                out += "\n\n";
+                if (!message) {
+                    out += "Unknown message\n\n"
+                } else {
+                    const author = message.author ?? { username: "Unknown", discriminator: "0000", id: "Unknown" };
+                    out += `${author.username}#${author.discriminator} (${author.id}) [${new Date(
+                        message.createdTimestamp
+                    ).toISOString()}]\n`;
+                    if (message.content) {
+                        const lines = message.content.split("\n");
+                        lines.forEach((line) => {
+                            out += `> ${line}\n`;
+                        });
+                    }
+                    if (message.attachments.size > 0) {
+                        message.attachments.forEach((attachment) => {
+                            out += `Attachment > ${attachment.url}\n`;
+                        });
+                    }
+                    out += "\n\n";
+                }
             });
             const attachmentObject = {
                 attachment: Buffer.from(out),
@@ -337,7 +359,7 @@
                         .setStatus("Success")
                 ],
                 components: [
-                    new Discord.ActionRowBuilder().addComponents([
+                    new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
                         new Discord.ButtonBuilder()
                             .setCustomId("download")
                             .setLabel("Download transcript")
@@ -355,7 +377,7 @@
             } catch {
                 return;
             }
-            if (component && component.customId === "download") {
+            if (component.customId === "download") {
                 interaction.editReply({
                     embeds: [
                         new EmojiEmbed()
@@ -395,14 +417,15 @@
 };
 
 const check = (interaction: CommandInteraction) => {
+    if (!interaction.guild) return false;
     const member = interaction.member as GuildMember;
-    const me = interaction.guild.me!;
+    const me = interaction.guild.members.me!;
     // Check if nucleus has the manage_messages permission
-    if (!me.permissions.has("MANAGE_MESSAGES")) throw new Error("I do not have the *Manage Messages* permission");
+    if (!me.permissions.has("ManageMessages")) throw new Error("I do not have the *Manage Messages* permission");
     // Allow the owner to purge
     if (member.id === interaction.guild.ownerId) return true;
     // Check if the user has manage_messages permission
-    if (!member.permissions.has("MANAGE_MESSAGES")) throw new Error("You do not have the *Manage Messages* permission");
+    if (!member.permissions.has("ManageMessages")) throw new Error("You do not have the *Manage Messages* permission");
     // Allow purge
     return true;
 };
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index c9a71f6..12bfc3e 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -51,6 +51,7 @@
                 false,
                 null,
                 null,
+                null,
                 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
                 notify
             )
diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts
index 40510a8..c93f8cc 100644
--- a/src/commands/mod/unmute.ts
+++ b/src/commands/mod/unmute.ts
@@ -38,6 +38,7 @@
                 false,
                 null,
                 "The user will be sent a DM",
+                null,
                 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
                 notify
             )
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 1c5223f..390baa5 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -41,6 +41,7 @@
                 !(await areTicketsEnabled(interaction.guild.id)),
                 async () => await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
                 "An appeal ticket will be created",
+                null,
                 "CONTROL.TICKET",
                 createAppealTicket
             )
@@ -50,6 +51,7 @@
                 false,
                 null,
                 "The user will be sent a DM",
+                null,
                 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
                 notify
             )
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index c54d7ab..859b7fc 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -2,6 +2,8 @@
 import { SlashCommandBuilder } from "@discordjs/builders";
 import client from "../utils/client.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
+import { capitalize } from "../utils/generateKeyValueList.js";
+import { getResults } from "../utils/search.js";
 
 const command = new SlashCommandBuilder()
     .setName("tag")
@@ -9,41 +11,44 @@
     .addStringOption((o) => o.setName("tag").setDescription("The tag to get").setAutocomplete(true).setRequired(true));
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    const config = await client.database.guilds.read(interaction.guild.id);
-    const tags = config.getKey("tags");
-    const tag = tags[interaction.options.getString("tag")];
+    const config = await client.database.guilds.read(interaction.guild!.id);
+    const tags = config.tags;
+    const search = interaction.options.get("tag")?.value as string;
+    const tag = tags[search];
     if (!tag) {
-        return await interaction.reply({
+        await interaction.reply({
             embeds: [
                 new EmojiEmbed()
                     .setTitle("Tag")
-                    .setDescription(`Tag \`${interaction.options.get("tag")}\` does not exist`)
+                    .setDescription(`Tag \`${search}\` does not exist`)
                     .setEmoji("PUNISH.NICKNAME.RED")
                     .setStatus("Danger")
             ],
             ephemeral: true
         });
+        return;
     }
     let url = "";
-    let components: ActionRowBuilder[] = [];
+    let components: ActionRowBuilder<ButtonBuilder>[] = [];
     if (tag.match(/^(http|https):\/\/[^ "]+$/)) {
         url = tag;
         components = [
-            new ActionRowBuilder().addComponents([new ButtonBuilder().setLabel("Open").setURL(url).setStyle(ButtonStyle.Link)])
+            new ActionRowBuilder<ButtonBuilder>().addComponents([new ButtonBuilder().setLabel("Open").setURL(url).setStyle(ButtonStyle.Link)])
         ];
     }
-    return await interaction.reply({
-        embeds: [
-            new EmojiEmbed()
-                .setTitle(interaction.options.get("tag").value)
-                .setDescription(tag)
-                .setEmoji("PUNISH.NICKNAME.GREEN")
-                .setStatus("Success")
-                .setImage(url)
-        ],
+    const em = new EmojiEmbed()
+        .setTitle(capitalize(search))
+        .setEmoji("PUNISH.NICKNAME.GREEN")
+        .setStatus("Success")
+    if (url) em.setImage(url)
+    else em.setDescription(tag);
+
+    await interaction.reply({
+        embeds: [em],
         components: components,
         ephemeral: true
     });
+    return;
 };
 
 const check = () => {
@@ -52,9 +57,12 @@
 
 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;
+    const prompt = interaction.options.getString("tag");
+    console.log(prompt)
+    const possible = Object.keys((await client.memory.readGuildInfo(interaction.guild.id)).tags);
+    const results = getResults(prompt ?? "", possible);
+    console.log(results)
+    return results;
 };
 
 export { command };
diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts
index b922da3..5379bf8 100644
--- a/src/commands/tags/create.ts
+++ b/src/commands/tags/create.ts
@@ -16,8 +16,8 @@
         );
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const name = interaction.options.getString("name");
-    const value = interaction.options.getString("value");
+    const name = interaction.options.get("name")?.value as string;
+    const value = interaction.options.get("value")?.value as string;
     if (name!.length > 100)
         return await interaction.reply({
             embeds: [
@@ -41,7 +41,7 @@
             ephemeral: true
         });
     const data = await client.database.guilds.read(interaction.guild!.id);
-    if (data.tags.length >= 100)
+    if (Object.keys(data.tags).length >= 100)
         return await interaction.reply({
             embeds: [
                 new EmojiEmbed()
@@ -90,6 +90,7 @@
         await client.database.guilds.write(interaction.guild!.id, {
             [`tags.${name}`]: value
         });
+        await client.memory.forceUpdate(interaction.guild!.id);
     } catch (e) {
         return await interaction.editReply({
             embeds: [
@@ -116,7 +117,7 @@
 
 const check = (interaction: CommandInteraction) => {
     const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("MANAGE_MESSAGES"))
+    if (!member.permissions.has("ManageMessages"))
         throw new Error("You must have the *Manage Messages* permission to use this command");
     return true;
 };
diff --git a/src/commands/tags/delete.ts b/src/commands/tags/delete.ts
index 2fff637..6d76eb1 100644
--- a/src/commands/tags/delete.ts
+++ b/src/commands/tags/delete.ts
@@ -13,7 +13,7 @@
         .addStringOption((o) => o.setName("name").setRequired(true).setDescription("The name of the tag"));
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const name = interaction.options.getString("name");
+    const name = interaction.options.get("name")?.value as string;
     const data = await client.database.guilds.read(interaction.guild!.id);
     if (!data.tags[name])
         return await interaction.reply({
@@ -51,6 +51,7 @@
         });
     try {
         await client.database.guilds.write(interaction.guild!.id, null, ["tags." + name]);
+        await client.memory.forceUpdate(interaction.guild!.id);
     } catch (e) {
         console.log(e);
         return await interaction.editReply({
@@ -78,7 +79,7 @@
 
 const check = (interaction: CommandInteraction) => {
     const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("MANAGE_MESSAGES"))
+    if (!member.permissions.has("ManageMessages"))
         throw new Error("You must have the *Manage Messages* permission to use this command");
     return true;
 };
diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts
index 376abbd..018a0bb 100644
--- a/src/commands/tags/edit.ts
+++ b/src/commands/tags/edit.ts
@@ -110,6 +110,7 @@
             toSet[`tags.${newname}`] = data.tags[name];
         }
         await client.database.guilds.write(interaction.guild.id, toSet === {} ? null : toSet, toUnset);
+        await client.memory.forceUpdate(interaction.guild!.id);
     } catch (e) {
         return await interaction.editReply({
             embeds: [
@@ -136,7 +137,7 @@
 
 const check = (interaction: CommandInteraction) => {
     const member = interaction.member as GuildMember;
-    if (!member.permissions.has("MANAGE_MESSAGES"))
+    if (!member.permissions.has("ManageMessages"))
         throw new Error("You must have the *Manage Messages* permission to use this command");
     return true;
 };
diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts
index f94fb24..72ad1eb 100644
--- a/src/commands/user/about.ts
+++ b/src/commands/user/about.ts
@@ -1,4 +1,4 @@
-import { LoadingEmbed } from "./../../utils/defaultEmbeds.js";
+import { LoadingEmbed, Embed } from "./../../utils/defaultEmbeds.js";
 import Discord, {
     CommandInteraction,
     GuildMember,
@@ -25,30 +25,6 @@
             option.setName("user").setDescription("The user to get info about | Default: Yourself")
         );
 
-class Embed {
-    embed: EmojiEmbed = new EmojiEmbed();
-    title: string = "";
-    description = "";
-    pageId = 0;
-
-    setEmbed(embed: EmojiEmbed) {
-        this.embed = embed;
-        return this;
-    }
-    setTitle(title: string) {
-        this.title = title;
-        return this;
-    }
-    setDescription(description: string) {
-        this.description = description;
-        return this;
-    }
-    setPageId(pageId: number) {
-        this.pageId = pageId;
-        return this;
-    }
-}
-
 const callback = async (interaction: CommandInteraction): Promise<void> => {
     const guild = interaction.guild!;
     const member = (interaction.options.getMember("user") ?? interaction.member) as Discord.GuildMember;
@@ -101,11 +77,11 @@
         BugHunterLevel2: "Bug Hunter Level 2",
         Partner: "Partnered Server Owner",
         Staff: "Discord Staff",
-        VerifiedDeveloper: "Verified Bot Developer"
-        // ActiveDeveloper
+        VerifiedDeveloper: "Verified Bot Developer",
+        ActiveDeveloper: "Active Developer",
+        Quarantined: "Quarantined [What does this mean?](https://support.discord.com/hc/en-us/articles/6461420677527)",
+        Spammer: "Likely Spammer"
         // CertifiedModerator
-        // Quarantined https://discord-api-types.dev/api/discord-api-types-v10/enum/UserFlags#Quarantined
-        // Spammer https://discord-api-types.dev/api/discord-api-types-v10/enum/UserFlags#Spammer
         // VerifiedBot
     };
     const members = await guild.members.fetch();
@@ -121,8 +97,8 @@
     let s = "";
     let count = 0;
     let ended = false;
-    for (const roleId in roles) {
-        const string = `<@&${roleId}>, `;
+    for (const roleId of roles) {
+        const string = `<@&${roleId[1].id}>, `;
         if (s.length + string.length > 1000) {
             ended = true;
             s += `and ${roles.size - count} more`;
diff --git a/src/config/emojis.json b/src/config/emojis.json
index f29f383..168d84b 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -190,7 +190,10 @@
         "BugHunterLevel2": "775783766130950234",
         "Partner": "775783766178005033",
         "Staff": "775783766383788082",
-        "VerifiedDeveloper": "775783766425600060"
+        "VerifiedDeveloper": "775783766425600060",
+        "Quarantined": "1059794708638994474",
+        "Spammer": "1059794708638994474",
+        "ActiveDeveloper": "1059795592966053918"
     },
     "VOICE": {
         "CONNECT": "784785219391193138",
diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts
new file mode 100644
index 0000000..e2ec6e4
--- /dev/null
+++ b/src/context/messages/purgeto.ts
@@ -0,0 +1,279 @@
+import confirmationMessage from '../../utils/confirmationMessage.js';
+import EmojiEmbed from '../../utils/generateEmojiEmbed.js';
+import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
+import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, GuildTextBasedChannel, MessageContextMenuCommandInteraction } from "discord.js";
+import client from "../../utils/client.js";
+import getEmojiByName from '../../utils/getEmojiByName.js';
+
+const command = new ContextMenuCommandBuilder()
+    .setName("Purge up to here")
+
+
+async function waitForButton(m: Discord.Message, member: Discord.GuildMember): Promise<boolean> {
+    let component;
+    try {
+        component = m.awaitMessageComponent({ time: 200000, filter: (i) => i.user.id === member.id });
+    } catch (e) {
+        return false;
+    }
+    (await component).deferUpdate();
+    return true;
+}
+
+
+const callback = async (interaction: MessageContextMenuCommandInteraction) => {
+    await interaction.targetMessage.fetch();
+    const targetMessage = interaction.targetMessage;
+    const targetMember: Discord.User = targetMessage.author;
+    let allowedMessage: Discord.Message | undefined = undefined;
+    const channel = interaction.channel;
+    if (!channel) return;
+    await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
+    // Option for "include this message"?
+    // Option for "Only selected user"?
+
+    const history: Discord.Collection<string, Discord.Message> = await channel.messages.fetch({ limit: 100 });
+    if (Date.now() - targetMessage.createdTimestamp > 2 * 7 * 24 * 60 * 60 * 1000) {
+        const m = await interaction.editReply({ embeds: [new EmojiEmbed()
+            .setTitle("Purge")
+            .setDescription("The message you selected is older than 2 weeks. Discord only allows bots to delete messages that are 2 weeks old or younger.")
+            .setEmoji("CHANNEL.PURGE.RED")
+            .setStatus("Danger")
+        ], components: [
+            new ActionRowBuilder<ButtonBuilder>().addComponents(
+                new ButtonBuilder()
+                    .setCustomId("oldest")
+                    .setLabel("Select first allowed message")
+                    .setStyle(ButtonStyle.Primary),
+            )
+        ]});
+        if (!await waitForButton(m, interaction.member as Discord.GuildMember)) return;
+    } else if (!history.has(targetMessage.id)) {
+        const m = await interaction.editReply({ embeds: [new EmojiEmbed()
+            .setTitle("Purge")
+            .setDescription("The message you selected is not in the last 100 messages in this channel. Discord only allows bots to delete 100 messages at a time.")
+            .setEmoji("CHANNEL.PURGE.YELLOW")
+            .setStatus("Warning")
+        ], components: [
+            new ActionRowBuilder<ButtonBuilder>().addComponents(
+                new ButtonBuilder()
+                    .setCustomId("oldest")
+                    .setLabel("Select first allowed message")
+                    .setStyle(ButtonStyle.Primary),
+            )
+        ]});
+        if (!await waitForButton(m, interaction.member as Discord.GuildMember)) return;
+    } else {
+        allowedMessage = targetMessage;
+    }
+
+    if (!allowedMessage) {
+        // Find the oldest message thats younger than 2 weeks
+        const messages = history.filter(m => Date.now() - m.createdTimestamp < 2 * 7 * 24 * 60 * 60 * 1000);
+        allowedMessage = messages.sort((a, b) => a.createdTimestamp - b.createdTimestamp).first();
+    }
+
+    if (!allowedMessage) {
+        await interaction.editReply({ embeds: [new EmojiEmbed()
+            .setTitle("Purge")
+            .setDescription("There are no valid messages in the last 100 messages. (No messages younger than 2 weeks)")
+            .setEmoji("CHANNEL.PURGE.RED")
+            .setStatus("Danger")
+        ], components: [] });
+        return;
+    }
+
+    let reason: string | null = null
+    let confirmation;
+    let chosen = false;
+    let timedOut = false;
+    let deleteSelected = true;
+    let deleteUser = false;
+    do {
+        confirmation = await new confirmationMessage(interaction)
+            .setEmoji("CHANNEL.PURGE.RED")
+            .setTitle("Purge")
+            .setDescription(
+                `[[Selected Message]](${allowedMessage.url})\n\n` +
+                (reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*") + "\n\n" +
+                `Are you sure you want to delete all messages from below the selected message?`
+            )
+            .addCustomBoolean(
+                "includeSelected",
+                "Include selected message",
+                false,
+                undefined,
+                "The selected message will be deleted as well.",
+                "The selected message will not be deleted.",
+                "CONTROL." + (deleteSelected ? "TICK" : "CROSS"),
+                deleteSelected
+            )
+            .addCustomBoolean(
+                "onlySelectedUser",
+                "Only selected user",
+                false,
+                undefined,
+                `Only messages from <@${targetMember.id}> will be deleted.`,
+                `All messages will be deleted.`,
+                "CONTROL." + (deleteUser ? "TICK" : "CROSS"),
+                deleteUser
+            )
+            .setColor("Danger")
+            .addReasonButton(reason ?? "")
+            .send(true)
+        reason = reason ?? ""
+        if (confirmation.cancelled) timedOut = true;
+        else if (confirmation.success !== undefined) chosen = true;
+        else if (confirmation.newReason) reason = confirmation.newReason;
+        else if (confirmation.components) {
+            deleteSelected = confirmation.components["includeSelected"]!.active;
+            deleteUser = confirmation.components["onlySelectedUser"]!.active;
+        }
+    } while (!chosen && !timedOut);
+    if (timedOut) return;
+    if (!confirmation.success) {
+        await interaction.editReply({ embeds: [new EmojiEmbed()
+            .setTitle("Purge")
+            .setDescription("No changes were made")
+            .setEmoji("CHANNEL.PURGE.GREEN")
+            .setStatus("Success")
+        ], components: [] });
+        return;
+    }
+    const filteredMessages = history
+        .filter(m => m.createdTimestamp >= allowedMessage!.createdTimestamp)  // older than selected
+        .filter(m => deleteUser ? m.author.id === targetMember.id : true)  // only selected user
+        .filter(m => deleteSelected ? true : m.id !== allowedMessage!.id)  // include selected
+
+    const deleted = await (channel as GuildTextBasedChannel).bulkDelete(filteredMessages, true);
+    if (deleted.size === 0) {
+        return await interaction.editReply({
+            embeds: [
+                new EmojiEmbed()
+                    .setEmoji("CHANNEL.PURGE.RED")
+                    .setTitle("Purge")
+                    .setDescription("No messages were deleted")
+                    .setStatus("Danger")
+            ],
+            components: []
+        });
+    }
+    if (deleteUser) {
+        await client.database.history.create(
+            "purge",
+            interaction.guild!.id,
+            targetMember,
+            interaction.user,
+            reason === "" ? "*No reason provided*" : reason,
+            null,
+            null,
+            deleted.size.toString()
+        );
+    }
+    const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
+    const data = {
+        meta: {
+            type: "channelPurge",
+            displayName: "Channel Purged",
+            calculateType: "messageDelete",
+            color: NucleusColors.red,
+            emoji: "PUNISH.BAN.RED",
+            timestamp: new Date().getTime()
+        },
+        list: {
+            memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
+            purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
+            channel: entry(interaction.channel!.id, renderChannel(interaction.channel! as Discord.GuildChannel)),
+            messagesCleared: entry(deleted.size.toString(), deleted.size.toString())
+        },
+        hidden: {
+            guild: interaction.guild!.id
+        }
+    };
+    log(data);
+    let out = "";
+    deleted.reverse().forEach((message) => {
+        if (!message) {
+            out += "Unknown message\n\n"
+        } else {
+            const author = message.author ?? { username: "Unknown", discriminator: "0000", id: "Unknown" };
+            out += `${author.username}#${author.discriminator} (${author.id}) [${new Date(
+                message.createdTimestamp
+            ).toISOString()}]\n`;
+            if (message.content) {
+                const lines = message.content.split("\n");
+                lines.forEach((line) => {
+                    out += `> ${line}\n`;
+                });
+            }
+            if (message.attachments.size > 0) {
+                message.attachments.forEach((attachment) => {
+                    out += `Attachment > ${attachment.url}\n`;
+                });
+            }
+            out += "\n\n";
+        }
+    });
+    const attachmentObject = {
+        attachment: Buffer.from(out),
+        name: `purge-${channel.id}-${Date.now()}.txt`,
+        description: "Purge log"
+    };
+    const m = (await interaction.editReply({
+        embeds: [
+            new EmojiEmbed()
+                .setEmoji("CHANNEL.PURGE.GREEN")
+                .setTitle("Purge")
+                .setDescription("Messages cleared")
+                .setStatus("Success")
+        ],
+        components: [
+            new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
+                new Discord.ButtonBuilder()
+                    .setCustomId("download")
+                    .setLabel("Download transcript")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
+            ])
+        ]
+    })) as Discord.Message;
+    let component;
+    try {
+        component = await m.awaitMessageComponent({
+            filter: (m) => m.user.id === interaction.user.id,
+            time: 300000
+        });
+    } catch {
+        return;
+    }
+    if (component.customId === "download") {
+        interaction.editReply({
+            embeds: [
+                new EmojiEmbed()
+                    .setEmoji("CHANNEL.PURGE.GREEN")
+                    .setTitle("Purge")
+                    .setDescription("Transcript uploaded above")
+                    .setStatus("Success")
+            ],
+            components: [],
+            files: [attachmentObject]
+        });
+    } else {
+        interaction.editReply({
+            embeds: [
+                new EmojiEmbed()
+                    .setEmoji("CHANNEL.PURGE.GREEN")
+                    .setTitle("Purge")
+                    .setDescription("Messages cleared")
+                    .setStatus("Success")
+            ],
+            components: []
+        });
+    }
+}
+
+const check = async (_interaction: MessageContextMenuCommandInteraction) => {
+    return true;
+}
+
+export { command, callback, check }
diff --git a/src/context/users/userinfo.ts b/src/context/users/userinfo.ts
new file mode 100644
index 0000000..3b1a6bd
--- /dev/null
+++ b/src/context/users/userinfo.ts
@@ -0,0 +1,18 @@
+import { ContextMenuCommandBuilder, GuildMember, UserContextMenuCommandInteraction } from "discord.js";
+import { userAbout } from "../../commands/user/about.js";
+
+const command = new ContextMenuCommandBuilder()
+    .setName("User info")
+
+const callback = async (interaction: UserContextMenuCommandInteraction) => {
+    const guild = interaction.guild!
+    let member = interaction.targetMember
+    if (!member) member = await guild.members.fetch(interaction.targetId)
+    await userAbout(guild, member as GuildMember, interaction)
+}
+
+const check = async (_interaction: UserContextMenuCommandInteraction) => {
+    return true;
+}
+
+export { command, callback, check }
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
index ddc143b..1796146 100644
--- a/src/events/interactionCreate.ts
+++ b/src/events/interactionCreate.ts
@@ -3,71 +3,15 @@
 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";
-import type { AutocompleteInteraction, Interaction, MessageComponentInteraction } from "discord.js";
+
+import type { Interaction, MessageComponentInteraction } from "discord.js";
 import type { NucleusClient } from "../utils/client.js";
 
 export const event = "interactionCreate";
 
-function getAutocomplete(typed: string, options: string[]): { name: string; value: string }[] {
-    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 }));
-}
-
-function generateStatsChannelAutocomplete(typed: string) {
-    const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"];
-    const autocompletions = [];
-    const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/);
-    if (beforeLastOpenBracket !== null) {
-        for (const replacement of validReplacements) {
-            autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`);
-        }
-    } else {
-        for (const replacement of validReplacements) {
-            autocompletions.push(`${typed} {${replacement}}`);
-        }
-    }
-    return getAutocomplete(typed, autocompletions);
-}
-function generateWelcomeMessageAutocomplete(typed: string) {
-    const validReplacements = [
-        "serverName",
-        "memberCount",
-        "memberCount:bots",
-        "memberCount:humans",
-        "member:mention",
-        "member:name"
-    ];
-    const autocompletions = [];
-    const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/);
-    if (beforeLastOpenBracket !== null) {
-        for (const replacement of validReplacements) {
-            autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`);
-        }
-    } else {
-        for (const replacement of validReplacements) {
-            autocompletions.push(`${typed} {${replacement}}`);
-        }
-    }
-    return getAutocomplete(typed, autocompletions);
-}
 
 async function interactionCreate(interaction: Interaction) {
-    if (
-        interaction.type === "MESSAGE_COMPONENT" &&
-        (interaction as MessageComponentInteraction).componentType === "BUTTON"
-    ) {
+    if (interaction.isButton()) {
         const int = interaction as MessageComponentInteraction;
         switch (int.customId) {
             case "rolemenu": {
@@ -86,19 +30,19 @@
                 return createTranscript(int);
             }
         }
-    } else if (interaction.type === "APPLICATION_COMMAND_AUTOCOMPLETE") {
-        const int = interaction as AutocompleteInteraction;
-        switch (`${int.commandName} ${int.options.getSubcommandGroup(false)} ${int.options.getSubcommand(false)}`) {
-            case "tag null null": {
-                return int.respond(getAutocomplete(int.options.getString("tag") ?? "", await tagAutocomplete(int)));
-            }
-            case "settings null stats": {
-                return int.respond(generateStatsChannelAutocomplete(int.options.getString("name") ?? ""));
-            }
-            case "settings null welcome": {
-                return int.respond(generateWelcomeMessageAutocomplete(int.options.getString("message") ?? ""));
-            }
-        }
+    // } else if (interaction.type === "APPLICATION_COMMAND_AUTOCOMPLETE") {
+    //     const int = interaction as AutocompleteInteraction;
+    //     switch (`${int.commandName} ${int.options.getSubcommandGroup(false)} ${int.options.getSubcommand(false)}`) {
+    //         case "tag null null": {
+    //             return int.respond(getAutocomplete(int.options.getString("tag") ?? "", await tagAutocomplete(int)));
+    //         }
+    //         case "settings null stats": {
+    //             return int.respond(generateStatsChannelAutocomplete(int.options.getString("name") ?? ""));
+    //         }
+    //         case "settings null welcome": {
+    //             return int.respond(generateWelcomeMessageAutocomplete(int.options.getString("message") ?? ""));
+    //         }
+    //     }
     }
 }
 
diff --git a/src/reflex/statsChannelUpdate.ts b/src/reflex/statsChannelUpdate.ts
index d807267..2e12429 100644
--- a/src/reflex/statsChannelUpdate.ts
+++ b/src/reflex/statsChannelUpdate.ts
@@ -12,10 +12,9 @@
 export async function callback(client: NucleusClient, member?: GuildMember, guild?: Guild, user?: User) {
     if (!member && !guild) return;
     guild = await client.guilds.fetch(member ? member.guild.id : guild!.id);
-    if (!guild) return;
     user = user ?? member!.user;
     const config = await client.database.guilds.read(guild.id);
-    Object.entries(config.getKey("stats")).forEach(async ([channel, props]) => {
+    Object.entries(config.stats).forEach(async ([channel, props]) => {
         if ((props as PropSchema).enabled) {
             let string = (props as PropSchema).name;
             if (!string) return;
@@ -27,13 +26,13 @@
                 fetchedChannel = null;
             }
             if (!fetchedChannel) {
-                const deleted = config.getKey("stats")[channel];
+                const deleted = config.stats[channel];
                 await client.database.guilds.write(guild!.id, null, `stats.${channel}`);
                 return singleNotify(
                     "statsChannelDeleted",
                     guild!.id,
                     "One or more of your stats channels have been deleted. Please use `/settings stats` if you wish to add the channel again.\n" +
-                        `The channels name was: ${deleted.name}`,
+                        `The channels name was: ${deleted!.name}`,
                     "Critical"
                 );
             }
diff --git a/src/utils/client.ts b/src/utils/client.ts
index 53267f2..a57c639 100644
--- a/src/utils/client.ts
+++ b/src/utils/client.ts
@@ -1,11 +1,10 @@
-import Discord, { Client, Interaction } from 'discord.js';
+import Discord, { Client, Interaction, AutocompleteInteraction } from 'discord.js';
 import { Logger } from "../utils/log.js";
 import Memory from "../utils/memory.js";
 import type { VerifySchema } from "../reflex/verify.js";
 import { Guilds, History, ModNotes, Premium } from "../utils/database.js";
 import EventScheduler from "../utils/eventScheduler.js";
 import type { RoleMenuSchema } from "../actions/roleMenu.js";
-// @ts-expect-error
 import config from "../config/main.json" assert { type: "json" };
 
 
@@ -28,9 +27,9 @@
                 ((builder: Discord.SlashCommandBuilder) => Discord.SlashCommandBuilder) |
                 Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder),
         callback: (interaction: Interaction) => Promise<void>,
-        check: (interaction: Interaction) => Promise<boolean> | boolean
+        check: (interaction: Interaction) => Promise<boolean> | boolean,
+        autocomplete: (interaction: AutocompleteInteraction) => Promise<string[]>
     }> = {};
-    // commands: Discord.Collection<string, [Function, Function]> = new Discord.Collection();
 
     constructor(database: typeof NucleusClient.prototype.database) {
         super({ intents: 32767 });
diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts
index 0193f6c..a4c57c4 100644
--- a/src/utils/commandRegistration/register.ts
+++ b/src/utils/commandRegistration/register.ts
@@ -1,5 +1,4 @@
 import Discord, { Interaction, SlashCommandBuilder, ApplicationCommandType } from 'discord.js';
-// @ts-expect-error
 import config from "../../config/main.json" assert { type: "json" };
 import client from "../client.js";
 import fs from "fs";
@@ -140,19 +139,31 @@
             const commandName = "contextCommands/message/" + interaction.commandName;
             execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction)
             return;
+        } else if (interaction.isAutocomplete()) {
+            const commandName = interaction.commandName;
+            const subcommandGroupName = interaction.options.getSubcommandGroup(false);
+            const subcommandName = interaction.options.getSubcommand(false);
+
+            const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : "");
+
+            const choices = await client.commands[fullCommandName]?.autocomplete(interaction);
+
+            const formatted = (choices ?? []).map(choice => {
+                return { name: choice, value: choice }
+            })
+            interaction.respond(formatted)
+        } else if (interaction.isChatInputCommand()) {
+            const commandName = interaction.commandName;
+            const subcommandGroupName = interaction.options.getSubcommandGroup(false);
+            const subcommandName = interaction.options.getSubcommand(false);
+
+            const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : "");
+
+            const command = client.commands[fullCommandName];
+            const callback = command?.callback;
+            const check = command?.check;
+            execute(check, callback, interaction);
         }
-        if (!interaction.isChatInputCommand()) return;
-
-        const commandName = interaction.commandName;
-        const subcommandGroupName = interaction.options.getSubcommandGroup(false);
-        const subcommandName = interaction.options.getSubcommand(false);
-
-        const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : "");
-
-        const command = client.commands[fullCommandName];
-        const callback = command?.callback;
-        const check = command?.check;
-        execute(check, callback, interaction);
     });
 }
 
diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts
index 45cb8f1..76ecabe 100644
--- a/src/utils/commandRegistration/slashCommandBuilder.ts
+++ b/src/utils/commandRegistration/slashCommandBuilder.ts
@@ -1,6 +1,5 @@
 import type { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
 import type { SlashCommandBuilder } from "discord.js";
-// @ts-expect-error
 import config from "../../config/main.json" assert { type: "json" };
 import getSubcommandsInFolder from "./getFilesInFolder.js";
 import client from "../client.js";
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index 87724f3..6682be0 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -18,6 +18,7 @@
     title: string;
     disabled: boolean;
     value: string | null;
+    notValue: string | null;
     emoji: string | undefined;
     active: boolean;
     onClick: () => Promise<T>;
@@ -68,6 +69,7 @@
         disabled: boolean,
         callback: (() => Promise<unknown>) | null = async () => null,
         callbackClicked: string | null,
+        callbackNotClicked: string | null,
         emoji?: string,
         initial?: boolean
     ) {
@@ -75,6 +77,7 @@
             title: title,
             disabled: disabled,
             value: callbackClicked,
+            notValue: callbackNotClicked,
             emoji: emoji,
             active: initial ?? false,
             onClick: callback ?? (async () => null),
@@ -145,10 +148,12 @@
                                 "\n\n" +
                                 Object.values(this.customButtons)
                                     .map((v) => {
-                                        if (v.value === null) return "";
-                                        return v.active ? `*${v.value}*\n` : "";
-                                    })
-                                    .join("")
+                                        if (v.active) {
+                                            return v.value ? `*${v.value}*\n` : "";
+                                        } else {
+                                            return v.notValue ? `*${v.notValue}*\n` : "";
+                                        }
+                                    }).join("")
                         )
                         .setStatus(this.color)
                 ],
@@ -163,7 +168,8 @@
                 } else {
                     m = (await this.interaction.reply(object)) as unknown as Message;
                 }
-            } catch {
+            } catch (e) {
+                console.log(e);
                 cancelled = true;
                 continue;
             }
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 2624fc9..b14c5c4 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -1,6 +1,5 @@
 import type Discord from "discord.js";
 import { Collection, MongoClient } from "mongodb";
-// @ts-expect-error
 import config from "../config/main.json" assert { type: "json" };
 
 const mongoClient = new MongoClient(config.mongoUrl);
@@ -17,7 +16,6 @@
     }
 
     async setup(): Promise<Guilds> {
-        // @ts-expect-error
         this.defaultData = (await import("../config/default.json", { assert: { type: "json" } }))
             .default as unknown as GuildConfig;
         return this;
diff --git a/src/utils/defaultEmbeds.ts b/src/utils/defaultEmbeds.ts
index 39a6080..a027c76 100644
--- a/src/utils/defaultEmbeds.ts
+++ b/src/utils/defaultEmbeds.ts
@@ -8,4 +8,30 @@
 export const LinkWarningFooter = {
     text: "The button below will take you to a website set by the server moderators. Do not enter any passwords unless it is from a trusted website.",
     iconURL: "https://cdn.discordapp.com/emojis/952295894370369587.webp?size=128&quality=lossless"
-}
\ No newline at end of file
+}
+
+class Embed {
+    embed: EmojiEmbed = new EmojiEmbed();
+    title: string = "";
+    description = "";
+    pageId = 0;
+
+    setEmbed(embed: EmojiEmbed) {
+        this.embed = embed;
+        return this;
+    }
+    setTitle(title: string) {
+        this.title = title;
+        return this;
+    }
+    setDescription(description: string) {
+        this.description = description;
+        return this;
+    }
+    setPageId(pageId: number) {
+        this.pageId = pageId;
+        return this;
+    }
+}
+
+export { Embed };
diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts
index 0a32a1e..3c9d6ca 100644
--- a/src/utils/eventScheduler.ts
+++ b/src/utils/eventScheduler.ts
@@ -2,7 +2,6 @@
 import client from "./client.js";
 import * as fs from "fs";
 import * as path from "path";
-// @ts-expect-error
 import config from "../config/main.json" assert { type: "json" };
 
 class EventScheduler {
diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts
index 7824982..3fa2b53 100644
--- a/src/utils/getEmojiByName.ts
+++ b/src/utils/getEmojiByName.ts
@@ -1,4 +1,3 @@
-// @ts-expect-error
 import emojis from "../config/emojis.json" assert { type: "json" };
 
 interface EmojisIndex {
diff --git a/src/utils/memory.ts b/src/utils/memory.ts
index a397d09..870ffaf 100644
--- a/src/utils/memory.ts
+++ b/src/utils/memory.ts
@@ -6,6 +6,7 @@
     filters: GuildConfig["filters"];
     logging: GuildConfig["logging"];
     tickets: GuildConfig["tickets"];
+    tags: GuildConfig["tags"];
 }
 
 class Memory {
@@ -20,7 +21,7 @@
                 }
             }
         }, 1000 * 60 * 30);
-    }
+    };
 
     async readGuildInfo(guild: string): Promise<GuildData> {
         if (!this.memory.has(guild)) {
@@ -29,10 +30,15 @@
                 lastUpdated: Date.now(),
                 filters: guildData.filters,
                 logging: guildData.logging,
-                tickets: guildData.tickets
+                tickets: guildData.tickets,
+                tags: guildData.tags
             });
         }
         return this.memory.get(guild)!;
+    };
+
+    async forceUpdate(guild: string) {
+        if (this.memory.has(guild)) this.memory.delete(guild);
     }
 }
 
diff --git a/src/utils/search.ts b/src/utils/search.ts
new file mode 100644
index 0000000..310dbf8
--- /dev/null
+++ b/src/utils/search.ts
@@ -0,0 +1,18 @@
+import Fuse from "fuse.js";
+
+function getResults(typed: string, options: string[]): string[] {
+    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()
+    // @ts-expect-error
+    const fuse = new Fuse(options, {
+        useExtendedSearch: true,
+        findAllMatches: true,
+        minMatchCharLength: typed.length > 3 ? 3 : typed.length,
+    }).search(typed);
+    return fuse.slice(0, 25).map((option: {item: string }) => option.item );
+}
+
+export { getResults }
\ No newline at end of file