Development (#11)

We need this NOW.

---------

Co-authored-by: PineaFan <ash@pinea.dev>
Co-authored-by: pineafan <pineapplefanyt@gmail.com>
Co-authored-by: PineappleFan <PineaFan@users.noreply.github.com>
Co-authored-by: Skyler <skyler3665@gmail.com>
diff --git a/src/commands/help.ts b/src/commands/help.ts
index 767ca46..90ef133 100644
--- a/src/commands/help.ts
+++ b/src/commands/help.ts
@@ -1,23 +1,195 @@
-import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import {
+    ActionRowBuilder,
+    CommandInteraction,
+    StringSelectMenuBuilder,
+    ApplicationCommandOptionType,
+    ApplicationCommandType,
+    StringSelectMenuOptionBuilder,
+    SlashCommandBuilder,
+    StringSelectMenuInteraction,
+    ComponentType,
+    APIMessageComponentEmoji,
+    ApplicationCommandSubGroup,
+    PermissionsBitField,
+    Interaction,
+    ApplicationCommandOption,
+    ApplicationCommandSubCommand
+} from "discord.js";
+import client from "../utils/client.js";
+import EmojiEmbed from "../utils/generateEmojiEmbed.js";
+import { LoadingEmbed } from "../utils/defaults.js";
+import { capitalize } from "../utils/generateKeyValueList.js";
+import { getCommandByName, getCommandMentionByName } from "../utils/getCommandDataByName.js";
+import getEmojiByName from "../utils/getEmojiByName.js";
 
 const command = new SlashCommandBuilder()
     .setName("help")
     .setDescription("Shows help for commands");
 
+const styles: Record<string, {emoji: string}> = {
+    "help": {emoji: "NUCLEUS.LOGO"},
+    "mod": {emoji: "PUNISH.BAN.RED"},
+    "nucleus": {emoji: "NUCLEUS.LOGO"},
+    "privacy": {emoji: "NUCLEUS.LOGO"},
+    "role": {emoji: "GUILD.ROLES.DELETE"},
+    "rolemenu": {emoji: "GUILD.ROLES.DELETE"},
+    "server": {emoji: "GUILD.RED"},
+    "settings": {emoji: "GUILD.SETTINGS.RED"},
+    "tag": {emoji: "PUNISH.NICKNAME.RED"},
+    "tags": {emoji: "PUNISH.NICKNAME.RED"},
+    "ticket": {emoji: "GUILD.TICKET.CLOSE"},
+    "user": {emoji: "MEMBER.LEAVE"},
+    "verify": {emoji: "CONTROL.REDTICK"}
+}
+
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    interaction.reply({components: [new ActionRowBuilder<ButtonBuilder>().addComponents(
-        new ButtonBuilder()
-            .setLabel("Create ticket")
-            .setStyle(ButtonStyle.Primary)
-            .setCustomId("createticket")
-    )]}); // TODO: FINISH THIS FOR RELEASE
+    const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
+    const commands = client.fetchedCommands;
+
+    let closed = false;
+    let currentPath: [string, string, string] = ["", "", ""]
+    do {
+        const commandRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("commandRow")
+                    .setPlaceholder("Select a command")
+                    .addOptions(
+                        ...commands.filter(command => command.type === ApplicationCommandType.ChatInput).map((command) => {
+                            const builder = new StringSelectMenuOptionBuilder()
+                                .setLabel(capitalize(command.name))
+                                .setValue(command.name)
+                                .setDescription(command.description)
+                                .setDefault(currentPath[0] === command.name)
+                            if (styles[command.name]) builder.setEmoji(getEmojiByName(styles[command.name]!.emoji, "id") as APIMessageComponentEmoji)
+                            return builder
+                        })
+                    )
+        );
+        const subcommandGroupRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("subcommandGroupRow")
+            );
+        const subcommandRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("subcommandRow")
+            );
+        const embed = new EmojiEmbed()
+            .setTitle("Help")
+            .setStatus("Danger")
+            .setEmoji("NUCLEUS.LOGO")
+
+        if(currentPath[0] === "" || currentPath[0] === "help") {
+            embed.setDescription(
+                `Welcome to Nucleus\n\n` +
+                `Select a command to get started${
+                    (interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ?
+                        `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : ``
+                    }\n\n\n` +
+                `Nucleus is fully [open source](https://github.com/clicksminuteper/Nucleus), and all currently free features will remain free forever.\n\n` +
+                `You can invite Nucleus to your server using ${getCommandMentionByName("nucleus/invite")}`
+            )
+        } else {
+            const currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/'));
+            const current = commands.find((command) => command.name === currentPath[0])!;
+
+            let optionString = ``
+            let options: (ApplicationCommandOption & {
+                nameLocalized?: string;
+                descriptionLocalized?: string;
+            })[] = [];
+            //options
+            if(currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none") {
+                const Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup
+                const Op2 = Op.options!.find(option => option.name === currentPath[2])!
+                options = Op2.options ?? []
+            } else if(currentPath[1] !== "" && currentPath[1] !== "none") {
+                let Op = current.options.find(option => option.name === currentPath[1])!
+                if(Op.type === ApplicationCommandOptionType.SubcommandGroup) {
+                    options = []
+                } else {
+                    Op = Op as ApplicationCommandSubCommand
+                    options = Op.options ?? []
+                }
+            } else {
+                options = current.options.filter(option => (option.type !== ApplicationCommandOptionType.SubcommandGroup) && (option.type !== ApplicationCommandOptionType.Subcommand));
+            }
+            for(const option of options) {
+                optionString += `> ${option.name} (${ApplicationCommandOptionType[option.type]})- ${option.description}\n`
+            }
+            const APICommand = client.commands["commands/" + currentPath.filter(value => value !== "" && value !== "none").join("/")]![0]
+            let allowedToRun = true;
+            if(APICommand?.check) {
+                allowedToRun = await APICommand.check(interaction as Interaction, true)
+            }
+            embed.setDescription(
+                `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}**\n> ${currentData.mention}\n\n` +
+                `> ${currentData.description}\n\n` +
+                (APICommand ? (`${getEmojiByName(allowedToRun ? "CONTROL.TICK" : "CONTROL.CROSS")} You ${allowedToRun ? "" : "don't "}` +
+                `have permission to use this command\n\n`) : "") +
+                ((optionString.length > 0) ? "**Options:**\n" + optionString : "")
+            )
+            const subcommands = current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand);
+            const subcommandGroups = current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup);
+
+            if(subcommandGroups.length > 0) {
+                subcommandGroupRow.components[0]!
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[1] === "none"),
+                        ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name))
+                    )
+                if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default && option.data.value !== "none")) {
+                    const subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options ?? [];
+                    subcommandRow.components[0]!
+                        .addOptions(
+                            new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[2] === "none"),
+                            ...subsubcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[2] === option.name))
+                        )
+                }
+            }
+            if(subcommands.length > 0) {
+                subcommandGroupRow.components[0]!
+                    .addOptions(
+                        ...subcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name))
+                    )
+            }
+        }
+
+        const cmps = [commandRow];
+        if(subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow);
+        if(subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow);
+
+        await interaction.editReply({ embeds: [embed], components: cmps });
+
+        let i: StringSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent<ComponentType.StringSelect>({filter: (newInteraction) => interaction.user.id === newInteraction.user.id,time: 300000})
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+        await i.deferUpdate();
+        const value = i.values[0]!;
+        switch(i.customId) {
+            case "commandRow": {
+                currentPath = [value, "", ""];
+                break;
+            }
+            case "subcommandGroupRow": {
+                currentPath = [currentPath[0], value , ""];
+                break;
+            }
+            case "subcommandRow": {
+                currentPath[2] = value;
+                break;
+            }
+        }
+
+    } while (!closed);
 };
 
-const check = () => {
-    return true;
-};
 
-export { command };
+export { command as command };
 export { callback };
-export { check };
diff --git a/src/commands/mod/_meta.ts b/src/commands/mod/_meta.ts
index af8006c..c5fcca5 100644
--- a/src/commands/mod/_meta.ts
+++ b/src/commands/mod/_meta.ts
@@ -5,4 +5,4 @@
 
 const subcommand = await command(name, description, `mod`);
 
-export { name, description, subcommand as command };
+export { name, description, subcommand as command };
\ No newline at end of file
diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts
index 130cdbc..0a9d962 100644
--- a/src/commands/mod/about.ts
+++ b/src/commands/mod/about.ts
@@ -3,17 +3,16 @@
 import Discord, {
     CommandInteraction,
     GuildMember,
-    Interaction,
     Message,
     ActionRowBuilder,
     ButtonBuilder,
     MessageComponentInteraction,
     ModalSubmitInteraction,
     ButtonStyle,
-    StringSelectMenuInteraction,
     TextInputStyle,
+    APIMessageComponentEmoji,
+    SlashCommandSubcommandBuilder
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import client from "../../utils/client.js";
@@ -167,8 +166,7 @@
                         .setLabel(value.text)
                         .setValue(key)
                         .setDefault(filteredTypes.includes(key))
-                        // @ts-expect-error
-                        .setEmoji(getEmojiByName(value.emoji, "id"))  // FIXME: This gives a type error but is valid
+                        .setEmoji(getEmojiByName(value.emoji, "id") as APIMessageComponentEmoji)
             )))
         ]);
         components = components.concat([new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
@@ -253,7 +251,7 @@
         try {
             i = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             interaction.editReply({
@@ -269,9 +267,9 @@
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
-        if (i.customId === "filter") {
-            filteredTypes = (i as StringSelectMenuInteraction).values;
+        await i.deferUpdate();
+        if (i.customId === "filter" && i.isStringSelectMenu()) {
+            filteredTypes = i.values;
             pageIndex = null;
             refresh = true;
         }
@@ -359,7 +357,7 @@
         try {
             i = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             timedOut = true;
@@ -402,17 +400,12 @@
             });
             let out;
             try {
-                out = await modalInteractionCollector(
-                    m,
-                    (m: Interaction) =>
-                        (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
-                    (m) => m.customId === "modify"
-                );
+                out = await modalInteractionCollector(m, interaction.user);
             } catch (e) {
                 timedOut = true;
                 continue;
             }
-            if (out === null) {
+            if (out === null || out.isButton()) {
                 continue;
             } else if (out instanceof ModalSubmitInteraction) {
                 let toAdd = out.fields.getTextInputValue("note") || null;
@@ -423,7 +416,7 @@
                 continue;
             }
         } else if (i.customId === "history") {
-            i.deferUpdate();
+            await i.deferUpdate();
             if (!(await showHistory(member, interaction))) return;
         }
     }
@@ -436,6 +429,8 @@
     return true;
 };
 
-export { command };
-export { callback };
-export { check };
+export { command, callback, check };
+export const metadata = {
+    longDescription: "Shows the moderation history (all previous bans, kicks, warns etc.), and moderator notes for a user.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index 70e904c..e8309fb 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -1,11 +1,11 @@
-import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle, SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import addPlurals from "../../utils/plurals.js";
 import client from "../../utils/client.js";
 import { LinkWarningFooter } from "../../utils/defaults.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -26,7 +26,7 @@
 const callback = async (interaction: CommandInteraction): Promise<void> => {
     if (!interaction.guild) return;
     const { renderUser } = client.logger;
-    // TODO:[Modals] Replace this with a modal
+    // TODO:[Modals] Replace the command arguments with a modal
     let reason = null;
     let notify = true;
     let confirmation;
@@ -123,17 +123,20 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.red,
                 emoji: "PUNISH.BAN.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(member.user.id, `\`${member.user.id}\``),
                 name: entry(member.user.id, renderUser(member.user)),
-                banned: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+                banned: entry(Date.now().toString(), renderDelta(Date.now())),
                 bannedBy: entry(interaction.user.id, renderUser(interaction.user)),
                 reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"),
                 accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)),
                 serverMemberCount: interaction.guild.memberCount
             },
+            separate: {
+                end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+            },
             hidden: {
                 guild: interaction.guild.id
             }
@@ -166,9 +169,12 @@
     });
 };
 
-const check = async (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has ban_members permission
+    if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
+    if(partial) return true;
     const me = interaction.guild.members.me!;
     let apply = interaction.options.getUser("user") as User | GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
@@ -181,21 +187,23 @@
         apply = apply as User
     }
     // Do not allow banning the owner
-    if (member.id === interaction.guild.ownerId) throw new Error("You cannot ban the owner of the server");
+    if (member.id === interaction.guild.ownerId) return "You cannot ban the owner of the server";
     // Check if Nucleus can ban the member
-    if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member");
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to ban
-    if (!me.permissions.has("BanMembers")) throw new Error("I do not have the *Ban Members* permission");
+    if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission";
     // Do not allow banning Nucleus
-    if (member.id === me.id) throw new Error("I cannot ban myself");
+    if (member.id === me.id) return "I cannot ban myself";
     // Allow the owner to ban anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has ban_members permission
-    if (!member.permissions.has("BanMembers")) throw new Error("You do not have the *Ban Members* permission");
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member");
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow ban
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Removes a member from the server - this will prevent them from rejoining until they are unbanned, and will delete a specified number of days of messages from them.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index 380bcc9..059bdb2 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -1,13 +1,13 @@
 import { LinkWarningFooter } from '../../utils/defaults.js';
-import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
+import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandSubcommandBuilder } from "discord.js";
 // @ts-expect-error
 import humanizeDuration from "humanize-duration";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import type Discord from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -102,8 +102,8 @@
         await client.database.history.create("kick", interaction.guild.id, member.user, interaction.user, reason);
         const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
         const timeInServer = member.joinedTimestamp ? entry(
-            (new Date().getTime() - member.joinedTimestamp).toString(),
-            humanizeDuration(new Date().getTime() - member.joinedTimestamp, {
+            (Date.now() - member.joinedTimestamp).toString(),
+            humanizeDuration(Date.now() - member.joinedTimestamp, {
                 round: true
             })
         ) : entry(null, "*Unknown*")
@@ -114,18 +114,21 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.red,
                 emoji: "PUNISH.KICK.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(member.id, `\`${member.id}\``),
                 name: entry(member.id, renderUser(member.user)),
                 joined: undefined as (unknown | typeof entry),
-                kicked: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+                kicked: entry(Date.now().toString(), renderDelta(Date.now())),
                 kickedBy: entry(interaction.user.id, renderUser(interaction.user)),
                 reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"),
                 timeInServer: timeInServer,
                 serverMemberCount: member.guild.memberCount
             },
+            separate: {
+                end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+            },
             hidden: {
                 guild: member.guild.id
             }
@@ -168,30 +171,37 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
+
     const member = interaction.member as GuildMember;
+    // Check if the user has kick_members permission
+    if (!member.permissions.has("KickMembers")) return "You do not have the *Kick Members* permission";
+    if (partial) return true;
+
     const me = interaction.guild.members.me!;
     const apply = interaction.options.getMember("user") as GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
     const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0;
     const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0;
+    // Check if Nucleus has permission to kick
+    if (!me.permissions.has("KickMembers")) return "I do not have the *Kick Members* permission";
+    // Allow the owner to kick anyone
+    if (member.id === interaction.guild.ownerId) return true;
     // Do not allow kicking the owner
     if (member.id === interaction.guild.ownerId) return "You cannot kick the owner of the server";
     // Check if Nucleus can kick the member
-    if (!(mePos > applyPos)) return "I do not have a role higher than that member";
-    // Check if Nucleus has permission to kick
-    if (!me.permissions.has("KickMembers")) return "I do not have the *Kick Members* permission";
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Do not allow kicking Nucleus
     if (member.id === interaction.guild.members.me!.id) return "I cannot kick myself";
-    // Allow the owner to kick anyone
-    if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has kick_members permission
-    if (!member.permissions.has("KickMembers")) return "You do not have the *Kick Members* permission";
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow kick
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Removes a member from the server. They will be able to rejoin if they have an invite link.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index 86291e5..c795456 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -1,6 +1,6 @@
 import { LinkWarningFooter, LoadingEmbed } from "../../utils/defaults.js";
 import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
@@ -103,7 +103,7 @@
         let component;
         try {
             component = await m.awaitMessageComponent({
-                filter: (m) => m.user.id === interaction.user.id,
+                filter: (i) => {return i.user.id === interaction.user.id && i.channelId === interaction.channelId},
                 time: 300000
             });
         } catch {
@@ -235,8 +235,8 @@
                         .setDescription(
                             `You have been muted in ${interaction.guild.name}` +
                                 (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") + "\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
+                            `You will be unmuted at: <t:${Math.round(Date.now() / 1000) + muteTime}:D> at ` +
+                            `<t:${Math.round(Date.now() / 1000) + muteTime}:T> (<t:${Math.round(Date.now() / 1000) + muteTime
                             }:R>)` + "\n\n" +
                             (createAppealTicket
                                 ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
@@ -267,10 +267,10 @@
             await member.timeout(muteTime * 1000, reason || "*No reason provided*");
             if (config.moderation.mute.role !== null) {
                 await member.roles.add(config.moderation.mute.role);
-                await client.database.eventScheduler.schedule("naturalUnmute", (new Date().getTime() + muteTime * 1000).toString(), {
+                await client.database.eventScheduler.schedule("naturalUnmute", (Date.now() + muteTime * 1000).toString(), {
                     guild: interaction.guild.id,
                     user: member.id,
-                    expires: new Date().getTime() + muteTime * 1000
+                    expires: Date.now() + muteTime * 1000
                 });
             }
         } else {
@@ -282,7 +282,7 @@
     try {
         if (config.moderation.mute.role !== null) {
             await member.roles.add(config.moderation.mute.role);
-            await client.database.eventScheduler.schedule("unmuteRole", (new Date().getTime() + muteTime * 1000).toString(), {
+            await client.database.eventScheduler.schedule("unmuteRole", (Date.now() + muteTime * 1000).toString(), {
                 guild: interaction.guild.id,
                 user: member.id,
                 role: config.moderation.mute.role
@@ -325,19 +325,22 @@
             calculateType: "guildMemberPunish",
             color: NucleusColors.yellow,
             emoji: "PUNISH.WARN.YELLOW",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(member.user.id, `\`${member.user.id}\``),
             name: entry(member.user.id, renderUser(member.user)),
             mutedUntil: entry(
-                (new Date().getTime() + muteTime * 1000).toString(),
-                renderDelta(new Date().getTime() + muteTime * 1000)
+                (Date.now() + muteTime * 1000).toString(),
+                renderDelta(Date.now() + muteTime * 1000)
             ),
-            muted: entry(new Date().getTime.toString(), renderDelta(new Date().getTime() - 1000)),
+            muted: entry(new Date().getTime.toString(), renderDelta(Date.now() - 1000)),
             mutedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
             reason: entry(reason, reason ? reason : "*No reason provided*")
         },
+        separate: {
+            end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+        },
         hidden: {
             guild: interaction.guild.id
         }
@@ -361,9 +364,12 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has moderate_members permission
+    if (!member.permissions.has("ModerateMembers")) return "You do not have the *Moderate Members* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     const apply = interaction.options.getMember("user") as GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
@@ -372,20 +378,21 @@
     // Do not allow muting the owner
     if (member.id === interaction.guild.ownerId) return "You cannot mute the owner of the server";
     // Check if Nucleus can mute the member
-    if (!(mePos > applyPos)) return "I do not have a role higher than that member";
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to mute
     if (!me.permissions.has("ModerateMembers")) return "I do not have the *Moderate Members* permission";
     // Do not allow muting Nucleus
     if (member.id === me.id) return "I cannot mute myself";
     // Allow the owner to mute anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has moderate_members permission
-    if (!member.permissions.has("ModerateMembers"))
-        return "You do not have the *Moderate Members* permission";
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow mute
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Stops a member from being able to send messages or join voice channels for a specified amount of time.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index 9dd9336..5511d19 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -1,16 +1,16 @@
 import { LinkWarningFooter } from './../../utils/defaults.js';
 import { ActionRowBuilder, ButtonBuilder, CommandInteraction, GuildMember, ButtonStyle, Message } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import client from "../../utils/client.js";
 import { areTicketsEnabled, create } from "../../actions/createModActionTicket.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 
 const command = (builder: SlashCommandSubcommandBuilder) => builder
     .setName("nick")
-    // .setNameLocalizations({"ru": "name", "zh-CN": "nickname"})
     .setDescription("Changes a users nickname")
     .addUserOption((option) => option.setName("user").setDescription("The user to change").setRequired(true))
     .addStringOption((option) =>
@@ -156,15 +156,18 @@
             calculateType: "guildMemberUpdate",
             color: NucleusColors.yellow,
             emoji: "PUNISH.NICKNAME.YELLOW",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(member.id, `\`${member.id}\``),
             before: entry(before, before ?? "*No nickname set*"),
             after: entry(nickname ?? null, nickname ?? "*No nickname set*"),
-            updated: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+            updated: entry(Date.now(), renderDelta(Date.now())),
             updatedBy: entry(interaction.user.id, renderUser(interaction.user))
         },
+        separate: {
+            end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+        },
         hidden: {
             guild: interaction.guild!.id
         }
@@ -189,8 +192,11 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, partial: boolean = false) => {
     const member = interaction.member as GuildMember;
+    // Check if the user has manage_nicknames permission
+    if (!member.permissions.has("ManageNicknames")) return "You do not have the *Manage Nicknames* permission";
+    if (partial) return true;
     const me = interaction.guild!.members.me!;
     const apply = interaction.options.getMember("user") as GuildMember;
     const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
@@ -200,20 +206,21 @@
     // Do not allow any changing of the owner
     if (member.id === interaction.guild.ownerId) return "You cannot change the owner's nickname";
     // Check if Nucleus can change the nickname
-    if (!(mePos > applyPos)) return "I do not have a role higher than that member";
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to change the nickname
     if (!me.permissions.has("ManageNicknames")) return "I do not have the *Manage Nicknames* permission";
     // Allow the owner to change anyone's nickname
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has manage_nicknames permission
-    if (!member.permissions.has("ManageNicknames"))
-        return "You do not have the *Manage Nicknames* permission";
     // Allow changing your own nickname
     if (member === apply) return true;
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow change
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Changes the nickname of a member. This is the name that shows in the member list and on messages.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index e6b4670..8644e26 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -1,6 +1,5 @@
-import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from '../../utils/logTranscripts.js';
-import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder, Message } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
@@ -30,7 +29,7 @@
     if (!interaction.guild) return;
     const user = (interaction.options.getMember("user") as GuildMember | null);
     const channel = interaction.channel as GuildChannel;
-    if (channel.isTextBased()) {
+    if (!channel.isTextBased()) {
         return await interaction.reply({
             embeds: [
                 new EmojiEmbed()
@@ -94,7 +93,7 @@
             let component;
             try {
                 component = m.awaitMessageComponent({
-                    filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id,
+                    filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
                     time: 300000
                 });
             } catch (e) {
@@ -148,7 +147,7 @@
                 calculateType: "messageDelete",
                 color: NucleusColors.red,
                 emoji: "CHANNEL.PURGE.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
@@ -161,7 +160,8 @@
             }
         };
         log(data);
-        const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(deleted)!);
+        const newOut = await client.database.transcripts.createTranscript(deleted, interaction, interaction.member as GuildMember);
+        const transcript = client.database.transcripts.toHumanReadable(newOut);
         const attachmentObject = {
             attachment: Buffer.from(transcript),
             name: `purge-${channel.id}-${Date.now()}.txt`,
@@ -188,7 +188,7 @@
         let component;
         try {
             component = await m.awaitMessageComponent({
-                filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id,
+                filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
                 time: 300000
             });
         } catch {
@@ -296,7 +296,7 @@
                 calculateType: "messageDelete",
                 color: NucleusColors.red,
                 emoji: "CHANNEL.PURGE.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
@@ -309,35 +309,17 @@
             }
         };
         log(data);
-        let out = "";
-        messages.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({
+        const messageArray: Message[] = messages.filter(message => !(
+            message!.components.some(
+                component => component.components.some(
+                    child => child.customId?.includes("transcript") ?? false
+                )
+            )
+        )).map(message => message as Message);
+        const newOut = await client.database.transcripts.createTranscript(messageArray, interaction, interaction.member as GuildMember);
+
+        const code = await client.database.transcripts.create(newOut);
+        await interaction.editReply({
             embeds: [
                 new EmojiEmbed()
                     .setEmoji("CHANNEL.PURGE.GREEN")
@@ -347,62 +329,30 @@
             ],
             components: [
                 new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new Discord.ButtonBuilder()
-                        .setCustomId("download")
-                        .setLabel("Download transcript")
-                        .setStyle(ButtonStyle.Success)
-                        .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
+                    new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`),
                 ])
             ]
-        })) as Discord.Message;
-        let component;
-        try {
-            component = await m.awaitMessageComponent({
-                filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.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 = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return false;
     const member = interaction.member as GuildMember;
+    // Check if the user has manage_messages permission
+    if (!member.permissions.has("ManageMessages")) return "You do not have the *Manage Messages* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     // Check if nucleus has the manage_messages permission
     if (!me.permissions.has("ManageMessages")) return "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("ManageMessages")) return "You do not have the *Manage Messages* permission";
     // Allow purge
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Deletes a specified amount of messages from a channel, optionally from a specific user. Without an amount, you can repeatedly choose a number of messages to delete.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts
index 9792827..f282e82 100644
--- a/src/commands/mod/slowmode.ts
+++ b/src/commands/mod/slowmode.ts
@@ -1,7 +1,7 @@
 // @ts-expect-error
 import humanizeDuration from "humanize-duration";
 import type { CommandInteraction, GuildMember, TextChannel } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@@ -47,7 +47,7 @@
             }) + "Are you sure you want to set the slowmode in this channel?"
         )
         .setColor("Danger")
-        .setFailedMessage("No changes were made", "Danger", "CHANNEL.SLOWMODE.ON")
+        .setFailedMessage("No changes were made", "Success", "CHANNEL.SLOWMODE.ON")
         .send();
     if (confirmation.cancelled || !confirmation.success) return;
     try {
@@ -76,14 +76,19 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     const member = interaction.member as GuildMember;
-    // Check if Nucleus can set the slowmode
-    if (!interaction.guild!.members.me!.permissions.has("ManageChannels")) return "I do not have the *Manage Channels* permission";
     // Check if the user has manage_channel permission
     if (!member.permissions.has("ManageChannels")) return "You do not have the *Manage Channels* permission";
+    if (partial) return true;
+    // Check if Nucleus can set the slowmode
+    if (!interaction.guild!.members.me!.permissions.has("ManageChannels")) return "I do not have the *Manage Channels* permission";
     // Allow slowmode
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Stops members from being able to send messages without waiting a certain amount of time between messages.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index 2787e91..1b404c9 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -1,11 +1,12 @@
 import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import addPlurals from "../../utils/plurals.js";
 import client from "../../utils/client.js";
 import { LinkWarningFooter } from "../../utils/defaults.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -124,17 +125,20 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.yellow,
                 emoji: "PUNISH.BAN.YELLOW",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(member.user.id, `\`${member.user.id}\``),
                 name: entry(member.user.id, renderUser(member.user)),
-                softbanned: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+                softbanned: entry(Date.now().toString(), renderDelta(Date.now())),
                 softbannedBy: entry(interaction.user.id, renderUser(interaction.user)),
                 reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"),
                 accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)),
                 serverMemberCount: interaction.guild.memberCount
             },
+            separate: {
+                end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+            },
             hidden: {
                 guild: interaction.guild.id
             }
@@ -167,9 +171,12 @@
     });
 };
 
-const check = async (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has ban_members permission
+    if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     let apply = interaction.options.getUser("user") as User | GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
@@ -182,19 +189,17 @@
         apply = apply as User
     }
     // Do not allow banning the owner
-    if (member.id === interaction.guild.ownerId) throw new Error("You cannot softban the owner of the server");
+    if (member.id === interaction.guild.ownerId) return "You cannot softban the owner of the server";
     // Check if Nucleus can ban the member
-    if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member");
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to ban
-    if (!me.permissions.has("BanMembers")) throw new Error("I do not have the *Ban Members* permission");
+    if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission";
     // Do not allow banning Nucleus
-    if (member.id === me.id) throw new Error("I cannot softban myself");
+    if (member.id === me.id) return "I cannot softban myself";
     // Allow the owner to ban anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has ban_members permission
-    if (!member.permissions.has("BanMembers")) throw new Error("You do not have the *Ban Members* permission");
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member");
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow ban
     return true;
 };
diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts
index 37fee99..40f4504 100644
--- a/src/commands/mod/unban.ts
+++ b/src/commands/mod/unban.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction, GuildMember, User } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
@@ -57,12 +57,12 @@
                     calculateType: "guildMemberPunish",
                     color: NucleusColors.green,
                     emoji: "PUNISH.BAN.GREEN",
-                    timestamp: new Date().getTime()
+                    timestamp: Date.now()
                 },
                 list: {
                     memberId: entry(member.id, `\`${member.id}\``),
                     name: entry(member.id, renderUser(member)),
-                    unbanned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    unbanned: entry(Date.now(), renderDelta(Date.now())),
                     unbannedBy: entry(interaction.user.id, renderUser(interaction.user)),
                     accountCreated: entry(member.createdTimestamp, renderDelta(member.createdTimestamp))
                 },
@@ -107,16 +107,17 @@
     }
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has ban_members permission
+    if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     // Check if Nucleus can unban members
     if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission";
     // Allow the owner to unban anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has ban_members permission
-    if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
     // Allow unban
     return true;
 };
diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts
index e2585e1..8562c4c 100644
--- a/src/commands/mod/unmute.ts
+++ b/src/commands/mod/unmute.ts
@@ -1,9 +1,10 @@
 import type { CommandInteraction, GuildMember } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -105,14 +106,17 @@
             calculateType: "guildMemberPunish",
             color: NucleusColors.green,
             emoji: "PUNISH.MUTE.GREEN",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(member.user.id, `\`${member.user.id}\``),
             name: entry(member.user.id, renderUser(member.user)),
-            unmuted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+            unmuted: entry(Date.now().toString(), renderDelta(Date.now())),
             unmutedBy: entry(interaction.user.id, renderUser(interaction.user))
         },
+        separate: {
+            end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+        },
         hidden: {
             guild: interaction.guild.id
         }
@@ -131,9 +135,13 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has moderate_members permission
+    if (!member.permissions.has("ModerateMembers"))
+        return "You do not have the *Moderate Members* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     const apply = interaction.options.getMember("user") as GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
@@ -142,16 +150,13 @@
     // Do not allow unmuting the owner
     if (member.id === interaction.guild.ownerId) return "You cannot unmute the owner of the server";
     // Check if Nucleus can unmute the member
-    if (!(mePos > applyPos)) return "I do not have a role higher than that member";
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to unmute
     if (!me.permissions.has("ModerateMembers")) return "I do not have the *Moderate Members* permission";
     // Allow the owner to unmute anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has moderate_members permission
-    if (!member.permissions.has("ModerateMembers"))
-        return "You do not have the *Moderate Members* permission";
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow unmute
     return true;
 };
diff --git a/src/commands/mod/viewas.ts b/src/commands/mod/viewas.ts
index b176dd4..ef62816 100644
--- a/src/commands/mod/viewas.ts
+++ b/src/commands/mod/viewas.ts
@@ -7,9 +7,10 @@
     ButtonStyle,
     NonThreadGuildBasedChannel,
     StringSelectMenuOptionBuilder,
-    StringSelectMenuBuilder
+    StringSelectMenuBuilder,
+    APIMessageComponentEmoji
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import type { GuildBasedChannel } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
@@ -126,8 +127,7 @@
                     return new StringSelectMenuOptionBuilder()
                         .setLabel(c)
                         .setValue((set * 25 + i).toString())
-                        // @ts-expect-error
-                        .setEmoji(getEmojiByName("ICONS.CHANNEL.CATEGORY", "id"))  // Again, this is valid but TS doesn't think so
+                        .setEmoji(getEmojiByName("ICONS.CHANNEL.CATEGORY", "id") as APIMessageComponentEmoji)  // Again, this is valid but TS doesn't think so
                         .setDefault((set * 25 + i) === page)
                 }))
             )}
@@ -157,19 +157,19 @@
         });
         let i;
         try {
-            i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id, time: 30000});
+            i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 30000});
         } catch (e) {
             closed = true;
             continue;
         }
-        i.deferUpdate();
+        await i.deferUpdate();
         if (i.customId === "back") page--;
         else if (i.customId === "right") page++;
         else if (i.customId === "category" && i.isStringSelectMenu()) page = parseInt(i.values[0]!);
     }
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as GuildMember;
     if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
     return true;
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 38aa4ad..ea4f084 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -1,10 +1,11 @@
 import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import { create, areTicketsEnabled } from "../../actions/createModActionTicket.js";
 import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 import { LinkWarningFooter } from "../../utils/defaults.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -116,7 +117,7 @@
             calculateType: "guildMemberPunish",
             color: NucleusColors.yellow,
             emoji: "PUNISH.WARN.YELLOW",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             user: entry(
@@ -124,7 +125,10 @@
                 renderUser((interaction.options.getMember("user") as GuildMember).user)
             ),
             warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
-            reason: reason ? `\n> ${reason}` : "*No reason provided*"
+            reason: reason ? reason : "*No reason provided*"
+        },
+        separate: {
+            end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
         },
         hidden: {
             guild: interaction.guild.id
@@ -186,7 +190,7 @@
         let component;
         try {
             component = await m.awaitMessageComponent({
-                filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id,
+                filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
                 time: 300000
             });
         } catch (e) {
@@ -275,9 +279,12 @@
     }
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    if (!member.permissions.has("ModerateMembers"))
+        return "You do not have the *Moderate Members* permission";
+    if(partial) return true;
     const apply = interaction.options.getMember("user") as GuildMember | null;
     if (apply === null) return "That member is not in the server";
     const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
@@ -287,10 +294,8 @@
     // Allow the owner to warn anyone
     if (member.id === interaction.guild.ownerId) return true;
     // Check if the user has moderate_members permission
-    if (!member.permissions.has("ModerateMembers"))
-        return "You do not have the *Moderate Members* permission";
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow warn
     return true;
 };
diff --git a/src/commands/nucleus/_meta.ts b/src/commands/nucleus/_meta.ts
index 521b338..bd7fd14 100644
--- a/src/commands/nucleus/_meta.ts
+++ b/src/commands/nucleus/_meta.ts
@@ -3,8 +3,6 @@
 const name = "nucleus";
 const description = "Commands relating to Nucleus itself";
 
-const subcommand = await command(name, description, `nucleus`)
+const subcommand = await command(name, description, `nucleus`, undefined, undefined, undefined, undefined, true);
 
-const allowedInDMs = true;
-
-export { name, description, subcommand as command, allowedInDMs };
+export { name, description, subcommand as command };
diff --git a/src/commands/nucleus/guide.ts b/src/commands/nucleus/guide.ts
index d3370ba..270ee62 100644
--- a/src/commands/nucleus/guide.ts
+++ b/src/commands/nucleus/guide.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from 'discord.js';
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import guide from "../../reflex/guide.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -9,10 +9,5 @@
     guide(interaction.guild!, interaction);
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/invite.ts b/src/commands/nucleus/invite.ts
index fd65e51..b89425a 100644
--- a/src/commands/nucleus/invite.ts
+++ b/src/commands/nucleus/invite.ts
@@ -1,5 +1,5 @@
 import { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
@@ -29,10 +29,5 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/ping.ts b/src/commands/nucleus/ping.ts
index 12f1c6b..3e02a8f 100644
--- a/src/commands/nucleus/ping.ts
+++ b/src/commands/nucleus/ping.ts
@@ -1,6 +1,6 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
@@ -10,9 +10,9 @@
 const callback = async (interaction: CommandInteraction): Promise<void> => {
     // WEBSOCKET | Nucleus -> Discord
     // EDITING   | Nucleus -> discord -> nucleus | edit time / 2
-    const initial = new Date().getTime();
+    const initial = Date.now();
     await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
-    const ping = new Date().getTime() - initial;
+    const ping = Date.now() - initial;
     interaction.editReply({
         embeds: [
             new EmojiEmbed()
@@ -28,10 +28,5 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts
index 745f167..c431c8e 100644
--- a/src/commands/nucleus/premium.ts
+++ b/src/commands/nucleus/premium.ts
@@ -1,32 +1,211 @@
-import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, ComponentType, Message, StringSelectMenuBuilder, StringSelectMenuInteraction } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import client from "../../utils/client.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder.setName("premium").setDescription("Information about Nucleus Premium");
+//TODO: Allow User to remove Premium
+
+const dmcallback = async (interaction: CommandInteraction, firstDescription: string, msg: Message): Promise<void> => {
+    let closed = false;
+    do {
+        const dbUser = await client.database.premium.fetchUser(interaction.user.id);
+        if(!dbUser) {
+            await interaction.editReply({embeds: [
+                new EmojiEmbed()
+                    .setTitle("Premium")
+                    .setDescription(`*You do not have premium! You can't activate premium on any servers.*` + firstDescription)
+                    .setEmoji("NUCLEUS.LOGO")
+                    .setStatus("Danger")
+            ]});
+            return;
+        }
+        const premiumGuilds = dbUser.appliesTo.map((guildID) => {
+            const guild = client.guilds.cache.get(guildID);
+            if(!guild) return undefined;
+            return guild.name;
+        });
+
+        const options = premiumGuilds.filter((guild) => guild !== undefined) as string[];
+
+        const removeRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("currentPremium")
+                    .setPlaceholder("Select a server to remove premium from")
+                    .setDisabled(premiumGuilds.length === 0)
+                    .addOptions(options.slice(0, Math.min(options.length, 24)).map((guild) => {
+                        return {label: guild, value: guild}
+                    }))
+            );
+        const cancel = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("cancel")
+                    .setLabel("Close")
+                    .setStyle(ButtonStyle.Danger)
+            );
+
+        const components: ActionRowBuilder<StringSelectMenuBuilder | ButtonBuilder>[] = [cancel];
+        if(options.length > 0) components.unshift(removeRow);
+        await interaction.editReply(
+        {
+            embeds: [
+                new EmojiEmbed()
+                    .setTitle("Premium")
+                    .setDescription(
+                        `*You have premium on the following servers:*\n\n` +
+                        (options.length > 0 ? options.join(', ') : `You have not activated premium in any guilds`) +
+                        firstDescription)
+                    .setEmoji("NUCLEUS.LOGO")
+                    .setStatus("Success")
+            ],
+            components: components
+        });
+
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            const filter = (i: StringSelectMenuInteraction | ButtonInteraction) => i.user.id === interaction.user.id;
+            i = await msg.awaitMessageComponent<ComponentType.StringSelect | ComponentType.Button>({time: 300000, filter})
+        } catch (e) {
+            await interaction.deleteReply();
+            closed = true;
+            break;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            closed = true;
+        } else {
+            const response = client.database.premium.removePremium(interaction.user.id, i.values[0]!);
+            console.log(response)
+        }
+    } while (!closed);
+    await interaction.deleteReply();
+}
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    interaction.reply({
+    if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {});
+    const m = await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true})
+    const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id).catch(() => {
+        interaction.editReply({ embeds: [
+            new EmojiEmbed()
+                .setTitle("Premium")
+                .setDescription(`*You are not currently in the Clicks Server. To gain access to premium please join.*` + firstDescription)
+                .setEmoji("NUCLEUS.LOGO")
+                .setStatus("Danger")
+        ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel("Join").setURL("https://discord.gg/bPaNnxe"))] });
+    })
+    if (!member) return;
+    const firstDescription = "\n\nPremium allows servers of your choice to get access to extra features for a fixed price per month.\nThis includes:\n" +
+        `${getEmojiByName("MOD.IMAGES.TOOSMALL")}  Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n` +
+        `${getEmojiByName("GUILD.TICKET.ARCHIVED")}  Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n`
+    const dbMember = await client.database.premium.fetchUser(interaction.user.id)
+    let premium = `You do not have premium! You can't activate premium on any servers.`;
+    let count = 0;
+    const {level, appliesTo} = dbMember ?? {level: 0, appliesTo: []}
+    if (level === 99) {
+        premium = `You have Infinite Premium! You have been gifted this by the developers as a thank you. You can give premium to any and all servers you are in.`;
+        count = 200;
+    } else if (level === 1) {
+        premium = `You have Premium tier 1! You can give premium to ${1 - appliesTo.length} more server(s).`;
+        count = 1;
+    } else if (level === 2) {
+        premium = `You have Premium tier 2! You can give premium to ${3 - appliesTo.length} more server(s).`;
+        count = 3;
+    } else if (level === 3) {
+        premium = `You have Premium Mod! You can give premium to ${3 - appliesTo.length} more server(s), as well as automatically giving premium to all servers you have a "manage" permission in.`
+        count = 3;
+    }
+    if (dbMember?.expiresAt) {
+        premium = `**You can't give servers premium anymore because your subscription ended or was cancelled.** To get premium again please subscribe in the Clicks server`
+        count = 0;
+    }
+    if(!interaction.guild) return await dmcallback(interaction, firstDescription, m);
+    const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id);
+    let premiumGuild = ""
+    if (hasPremium) {
+        premiumGuild = `**This server has premium! It was ${hasPremium[2] === 3 && hasPremium[3] ? `automatically applied by <@${hasPremium[1]}>` : `given by <@${hasPremium[1]}>`}**\n\n`
+    }
+
+    const components: ActionRowBuilder<ButtonBuilder>[] = []
+    if (level === 0 || dbMember?.expiresAt) {
+        components.push(
+            new ActionRowBuilder<ButtonBuilder>()
+                .addComponents(
+                    new ButtonBuilder()
+                    .setStyle(ButtonStyle.Link)
+                    .setLabel("Join Clicks")
+                    .setURL("https://discord.gg/bPaNnxe")
+                )
+        )
+    } else {
+        components.push(
+            new ActionRowBuilder<ButtonBuilder>()
+                .addComponents(
+                    new ButtonBuilder()
+                        .setStyle(premiumGuild.length > 0 ? ButtonStyle.Secondary : ButtonStyle.Success)
+                        .setLabel(premiumGuild.length > 0 ? "This server has premium" : "Activate premium here")
+                        .setCustomId("premiumActivate")
+                        .setDisabled(count <= 0 || (hasPremium ? hasPremium[0] : false))
+                )
+        )
+    }
+
+    interaction.editReply({
         embeds: [
             new EmojiEmbed()
                 .setTitle("Premium")
                 .setDescription(
-                    "*Nucleus Premium is currently not available.*\n\n" +
-                        "Premium allows your server to get access to extra features, for a fixed price per month.\nThis includes:\n" +
-                        "- Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n" +
-                        "- Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n"
+                    premiumGuild + premium + firstDescription
                 )
                 .setEmoji("NUCLEUS.LOGO")
                 .setStatus("Danger")
+                .setImage("https://assets.clicks.codes/ads/ads/nucleus-premium.png")
         ],
-        ephemeral: true
+        components: components
     });
-};
 
-const check = () => {
-    return true;
+    const filter = (i: ButtonInteraction) => i.customId === "premiumActivate" && i.user.id === interaction.user.id;
+    let i;
+    try {
+        i = await interaction.channel!.awaitMessageComponent<2>({ filter, time: 60000 });
+    } catch (e) {
+        return;
+    }
+    i.deferUpdate();
+    const guild = i.guild!;
+    if (count - appliesTo.length <= 0) {
+        interaction.editReply({
+            embeds: [
+                new EmojiEmbed()
+                    .setTitle("Premium")
+                    .setDescription(
+                        `You have already activated premium on the maximum amount of servers!` + firstDescription
+                    )
+                    .setEmoji("NUCLEUS.PREMIUMACTIVATE")
+                    .setStatus("Danger")
+            ],
+            components: []
+        });
+    } else {
+        await client.database.premium.addPremium(interaction.user.id, guild.id);
+        interaction.editReply({
+            embeds: [
+                new EmojiEmbed()
+                    .setTitle("Premium")
+                    .setDescription(
+                        `You have activated premium on this server!` + firstDescription
+                    )
+                    .setEmoji("NUCLEUS.LOGO")
+                    .setStatus("Danger")
+            ],
+            components: []
+        });
+    }
 };
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts
index d8b2807..19c0949 100644
--- a/src/commands/nucleus/stats.ts
+++ b/src/commands/nucleus/stats.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
@@ -13,16 +13,11 @@
                 .setTitle("Stats")
                 .setDescription(`**Servers:** ${client.guilds.cache.size}\n` + `**Ping:** \`${client.ws.ping * 2}ms\``)
                 .setStatus("Success")
-                .setEmoji("GUILD.GRAPHS")
+                .setEmoji("SETTINGS.STATS.GREEN")
         ],
         ephemeral: true
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index de0e69b..6ba3445 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -1,7 +1,7 @@
 import { LoadingEmbed } from '../../utils/defaults.js';
 import { ButtonStyle, CommandInteraction } from "discord.js";
 import Discord from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
@@ -66,10 +66,5 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts
index 9100302..46784f5 100644
--- a/src/commands/privacy.ts
+++ b/src/commands/privacy.ts
@@ -1,6 +1,5 @@
 import { LoadingEmbed } from "../utils/defaults.js";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, SelectMenuOptionBuilder, StringSelectMenuBuilder } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import Discord, { SlashCommandBuilder, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, SelectMenuOptionBuilder, StringSelectMenuBuilder } from "discord.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
 import createPageIndicator from "../utils/createPageIndicator.js";
@@ -22,8 +21,7 @@
                     .setDescription(
                         "Nucleus is a bot that naturally needs to store data about servers.\n" +
                             "We are entirely [open source](https://github.com/ClicksMinutePer/Nucleus), so you can check exactly what we store, and how it works.\n\n" +
-                            "If you are a server administrator, you can view the options page in the dropdown under this message.\n\n" +
-                            "Any questions about Nucleus, how it works and data stored can be asked in [our server](https://discord.gg/bPaNnxe)."
+                            "Any questions about Nucleus, how it works, and what data is stored can be asked in [our server](https://discord.gg/bPaNnxe)."
                     )
                     .setEmoji("NUCLEUS.LOGO")
                     .setStatus("Danger")
@@ -50,39 +48,37 @@
                 new EmojiEmbed()
                     .setTitle("Link scanning and Transcripts")
                     .setDescription(
-                        "**Facebook** - Facebook trackers include data such as your date of birth, and guess your age if not entered, your preferences, who you interact with and more.\n" +
-                            "**AMP** - AMP is a technology that allows websites to be served by Google. This means Google can store and track data, and are pushing this to as many pages as possible.\n\n" +
-                            "Transcripts allow you to store all messages sent in a channel. This could be an issue in some cases, as they are hosted on [Pastebin](https://pastebin.com), so a leaked link could show all messages sent in the channel.\n"  // TODO: Not on pastebin
+                            "Transcripts allow you to store all messages sent in a channel. Transcripts are stored in our database along with the rest of the server's settings but is accessible by anyone with the link, so a leaked link could show all messages sent in the channel.\n"
                     )
                     .setEmoji("NUCLEUS.LOGO")
                     .setStatus("Danger")
             )
             .setTitle("Link scanning and Transcripts")
-            .setDescription("Regarding Facebook and AMP filter types, and ticket transcripts")
+            .setDescription("Information about how links and images are scanned, and transcripts are stored")
             .setPageId(2)
     ].concat(
         (interaction.member as Discord.GuildMember).permissions.has("Administrator")
             ? [
-                  new Embed()
-                      .setEmbed(
-                          new EmojiEmbed()
-                              .setTitle("Options")
-                              .setDescription("Below are buttons for controlling this servers privacy settings")
-                              .setEmoji("NUCLEUS.LOGO")
-                              .setStatus("Danger")
-                      )
-                      .setTitle("Options")
-                      .setDescription("Options")
-                      .setPageId(3)
-                      .setComponents([
-                          new ActionRowBuilder<ButtonBuilder>().addComponents([
-                              new ButtonBuilder()
-                                  .setLabel("Clear all data")
-                                  .setCustomId("clear-all-data")
-                                  .setStyle(ButtonStyle.Danger)
-                          ])
-                      ])
-              ]
+                new Embed()
+                    .setEmbed(
+                        new EmojiEmbed()
+                            .setTitle("Options")
+                            .setDescription("Below are buttons for controlling this servers privacy settings")
+                            .setEmoji("NUCLEUS.LOGO")
+                            .setStatus("Danger")
+                    )
+                    .setTitle("Options")
+                    .setDescription("Options")
+                    .setPageId(3)
+                    .setComponents([
+                        new ActionRowBuilder<ButtonBuilder>().addComponents([
+                            new ButtonBuilder()
+                                .setLabel("Clear all data")
+                                .setCustomId("clear-all-data")
+                                .setStyle(ButtonStyle.Danger)
+                        ])
+                    ])
+            ]
             : []
     );
     const m = await interaction.reply({
@@ -150,14 +146,14 @@
         try {
             i = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             timedOut = true;
             continue;
         }
         nextFooter = null;
-        i.deferUpdate();
+        await i.deferUpdate();
         if (i.customId === "left") {
             if (page > 0) page--;
             selectPaneOpen = false;
@@ -180,11 +176,12 @@
                 .setColor("Danger")
                 .send(true);
             if (confirmation.cancelled) {
-                break;
+                continue;
             }
             if (confirmation.success) {
                 client.database.guilds.delete(interaction.guild!.id);
                 client.database.history.delete(interaction.guild!.id);
+                client.database.notes.delete(interaction.guild!.id);
                 nextFooter = "All data cleared";
                 continue;
             } else {
@@ -208,10 +205,6 @@
     });
 };
 
-const check = () => {
-    return true;
-};
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/role/_meta.ts b/src/commands/role/_meta.ts
deleted file mode 100644
index f546d51..0000000
--- a/src/commands/role/_meta.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { command } from "../../utils/commandRegistration/slashCommandBuilder.js";
-
-const name = "role";
-const description = "Change roles for users";
-
-const subcommand = await command(name, description, `role`);
-
-export { name, description, subcommand as command };
diff --git a/src/commands/role/user.ts b/src/commands/role/user.ts
deleted file mode 100644
index ad29811..0000000
--- a/src/commands/role/user.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import type { CommandInteraction, GuildMember, Role, User } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import client from "../../utils/client.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import keyValueList from "../../utils/generateKeyValueList.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-        .setName("user")
-        .setDescription("Gives or removes a role from someone")
-        .addUserOption((option) =>
-            option.setName("user").setDescription("The member to give or remove the role from").setRequired(true)
-        )
-        .addRoleOption((option) =>
-            option.setName("role").setDescription("The role to give or remove").setRequired(true)
-        )
-        .addStringOption((option) =>
-            option
-                .setName("action")
-                .setDescription("The action to perform")
-                .setRequired(true)
-                .addChoices(
-                    {name: "Add", value: "give"},
-                    {name: "Remove", value: "remove"}
-                )
-        );
-
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const { renderUser, renderRole } = client.logger;
-    const action = interaction.options.get("action")?.value as string;
-    const role: Role = (await interaction.guild!.roles.fetch(interaction.options.get("role")?.value as string))!;
-    // TODO:[Modals] Replace this with a modal
-    const confirmation = await new confirmationMessage(interaction)
-        .setEmoji("GUILD.ROLES.DELETE")
-        .setTitle("Role")
-        .setDescription(
-            keyValueList({
-                user: renderUser(interaction.options.getUser("user")! as User),
-                role: renderRole(role)
-            }) +
-                `\nAre you sure you want to ${
-                    action === "give" ? "give the role to" : "remove the role from"
-                } ${interaction.options.getUser("user")}?`
-        )
-        .setColor("Danger")
-        .setFailedMessage("No changes were made", "Success", "GUILD.ROLES.CREATE")
-        .send();
-    if (confirmation.cancelled || !confirmation.success) return;
-    try {
-        const member = interaction.options.getMember("user") as GuildMember;
-        if ((interaction.options.get("action")?.value as string) === "give") {
-            member.roles.add(role);
-        } else {
-            member.roles.remove(role);
-        }
-    } catch (e) {
-        return await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Role")
-                    .setDescription("Something went wrong and the role could not be added")
-                    .setStatus("Danger")
-                    .setEmoji("CONTROL.BLOCKCROSS")
-            ],
-            components: []
-        });
-    }
-    return await interaction.editReply({
-        embeds: [
-            new EmojiEmbed()
-                .setTitle("Role")
-                .setDescription(`The role has been ${action === "give" ? "given" : "removed"} successfully`)
-                .setStatus("Success")
-                .setEmoji("GUILD.ROLES.CREATE")
-        ],
-        components: []
-    });
-};
-
-const check = (interaction: CommandInteraction) => {
-    const member = interaction.member as GuildMember;
-    if (!interaction.guild) return
-    const me = interaction.guild.members.me!;
-    const apply = interaction.options.getMember("user") as GuildMember | null;
-    if (apply === null) return "That member is not in the server";
-    // Check if Nucleus has permission to role
-    if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission";
-    // Allow the owner to role anyone
-    if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has manage_roles permission
-    if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
-    // Allow role
-    return true;
-};
-
-export { command };
-export { callback };
-export { check };
diff --git a/src/commands/rolemenu.ts b/src/commands/rolemenu.ts
index c1ceb2e..2861e05 100644
--- a/src/commands/rolemenu.ts
+++ b/src/commands/rolemenu.ts
@@ -1,5 +1,4 @@
-import type { CommandInteraction } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import { CommandInteraction, SlashCommandBuilder } from "discord.js";
 import { callback as roleMenu } from "../actions/roleMenu.js";
 
 const command = new SlashCommandBuilder()
@@ -10,10 +9,5 @@
     await roleMenu(interaction);
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/server/about.ts b/src/commands/server/about.ts
index 23a53b7..4c88365 100644
--- a/src/commands/server/about.ts
+++ b/src/commands/server/about.ts
@@ -1,5 +1,5 @@
 import { CommandInteraction, GuildMFALevel } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import generateKeyValueList, { toCapitals } from "../../utils/generateKeyValueList.js";
@@ -74,10 +74,5 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts
new file mode 100644
index 0000000..3297616
--- /dev/null
+++ b/src/commands/server/buttons.ts
@@ -0,0 +1,252 @@
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType, CommandInteraction, MessageCreateOptions, ModalBuilder, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
+import type Discord from "discord.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import lodash from "lodash";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
+
+export const command = new SlashCommandSubcommandBuilder()
+    .setName("buttons")
+    .setDescription("Create clickable buttons for verifying, role menus etc.");
+
+interface Data {
+    buttons: string[],
+    title: string | null,
+    description: string | null,
+    color: number,
+    channel: string | null
+}
+
+const colors: Record<string, number> =  {
+    RED: 0xF27878,
+    ORANGE: 0xE5AB71,
+    YELLOW: 0xF2D478,
+    GREEN: 0x65CC76,
+    BLUE: 0x72AEF5,
+    PURPLE: 0xA358B2,
+    PINK: 0xD46899,
+    GRAY: 0x999999,
+}
+
+const buttonNames: Record<string, string> = {
+    verifybutton: "Verify",
+    rolemenu: "Role Menu",
+    createticket: "Create Ticket"
+}
+
+export const callback = async (interaction: CommandInteraction): Promise<void> => {
+
+    const m = await interaction.reply({
+        embeds: LoadingEmbed,
+        fetchReply: true,
+        ephemeral: true
+    });
+
+    let closed = false;
+    const data: Data = {
+        buttons: [],
+        title: null,
+        description: null,
+        color: colors["RED"]!,
+        channel: interaction.channelId
+    }
+    do {
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("edit")
+                    .setLabel("Edit Embed")
+                    .setStyle(ButtonStyle.Secondary),
+                new ButtonBuilder()
+                    .setCustomId("send")
+                    .setLabel("Send")
+                    .setStyle(ButtonStyle.Primary)
+                    .setDisabled(!data.channel)
+            );
+
+        const colorSelect = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("color")
+                    .setPlaceholder("Select a color")
+                    .setMinValues(1)
+                    .addOptions(
+                        Object.keys(colors).map((color: string) => {
+                            return new StringSelectMenuOptionBuilder()
+                                .setLabel(lodash.capitalize(color))
+                                .setValue(color)
+                                .setEmoji(getEmojiByName("COLORS." + color, "id") as APIMessageComponentEmoji)
+                                .setDefault(data.color === colors[color])
+                            }
+                        )
+                    )
+            );
+
+        const buttonSelect = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("button")
+                    .setPlaceholder("Select buttons to add")
+                    .setMinValues(1)
+                    .setMaxValues(3)
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Verify")
+                            .setValue("verifybutton")
+                            .setDescription("Click to get verified in the server")
+                            .setDefault(data.buttons.includes("verifybutton")),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Role Menu")
+                            .setValue("rolemenu")
+                            .setDescription("Click to customize your roles")
+                            .setDefault(data.buttons.includes("rolemenu")),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Ticket")
+                            .setValue("createticket")
+                            .setDescription("Click to create a support ticket")
+                            .setDefault(data.buttons.includes("createticket"))
+                    )
+            )
+
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel")
+                    .setChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement, ChannelType.PublicThread, ChannelType.AnnouncementThread)
+            )
+        let channelName = interaction.guild!.channels.cache.get(data.channel!)?.name;
+        if (data.channel === interaction.channelId) channelName = "this channel";
+        const embed = new EmojiEmbed()
+            .setTitle(data.title ?? "No title set")
+            .setDescription(data.description ?? "*No description set*")
+            .setColor(data.color)
+            .setFooter({text: `Click the button below to edit the embed | The embed will be sent in ${channelName}`});
+
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [colorSelect, buttonSelect, channelMenu, buttons]
+        });
+
+        let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
+        try {
+            i = await interaction.channel!.awaitMessageComponent({
+                filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
+                time: 300000
+            }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "edit": {
+                    await i.showModal(
+                        new ModalBuilder()
+                            .setCustomId("modal")
+                            .setTitle(`Options for ${i.customId}`)
+                            .addComponents(
+                                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("title")
+                                        .setLabel("Title")
+                                        .setMaxLength(256)
+                                        .setRequired(false)
+                                        .setStyle(TextInputStyle.Short)
+                                        .setValue(data.title ?? "")
+                                ),
+                                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("description")
+                                        .setLabel("The text to display below the title")
+                                        .setMaxLength(4000)
+                                        .setRequired(false)
+                                        .setStyle(TextInputStyle.Paragraph)
+                                        .setValue(data.description ?? "")
+                                )
+                            )
+                    );
+                    await interaction.editReply({
+                        embeds: [
+                            new EmojiEmbed()
+                                .setTitle("Button Editor")
+                                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                .setStatus("Success")
+                                .setEmoji("GUILD.TICKET.OPEN")
+                        ],
+                        components: [
+                            new ActionRowBuilder<ButtonBuilder>().addComponents([
+                                new ButtonBuilder()
+                                    .setLabel("Back")
+                                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                    .setStyle(ButtonStyle.Primary)
+                                    .setCustomId("back")
+                            ])
+                        ]
+                    });
+                    let out: Discord.ModalSubmitInteraction | null;
+                    try {
+                        out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
+                    } catch (e) {
+                        closed = true;
+                        continue;
+                    }
+                    if (!out || out.isButton()) continue
+                    data.title = out.fields.getTextInputValue("title").length === 0 ? null : out.fields.getTextInputValue("title");
+                    data.description = out.fields.getTextInputValue("description").length === 0 ? null : out.fields.getTextInputValue("description");
+                    break;
+                }
+                case "send": {
+                    await i.deferUpdate();
+                    const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel;
+                    const components = new ActionRowBuilder<ButtonBuilder>();
+                    for(const button of data.buttons) {
+                        components.addComponents(
+                            new ButtonBuilder()
+                            .setCustomId(button)
+                            .setLabel(buttonNames[button]!)
+                            .setStyle(ButtonStyle.Primary)
+                            );
+                        }
+                    const messageData: MessageCreateOptions = {components: [components]}
+                    if (data.title || data.description) {
+                        const e = new EmojiEmbed()
+                        if(data.title) e.setTitle(data.title);
+                        if(data.description) e.setDescription(data.description);
+                        if(data.color) e.setColor(data.color);
+                        messageData.embeds = [e];
+                    }
+                    await channel.send(messageData);
+                    break;
+                }
+            }
+        } else if(i.isStringSelectMenu()) {
+            try {await i.deferUpdate();} catch (err) {console.log(err)}
+            switch(i.customId) {
+                case "color": {
+                    data.color = colors[i.values[0]!]!;
+                    break;
+                }
+                case "button": {
+                    data.buttons = i.values;
+                    break;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            data.channel = i.values[0]!;
+        }
+
+    } while (!closed);
+    await interaction.deleteReply();
+}
+
+export const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as Discord.GuildMember;
+    if (!member.permissions.has("ManageMessages"))
+        return "You must have the *Manage Messages* permission to use this command";
+    return true;
+};
diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts
new file mode 100644
index 0000000..09b8914
--- /dev/null
+++ b/src/commands/settings/automod.ts
@@ -0,0 +1,922 @@
+import type Discord from "discord.js";
+import { ActionRowBuilder,
+    AnySelectMenuInteraction,
+    APIMessageComponentEmoji,
+    ButtonBuilder,
+    ButtonInteraction,
+    ButtonStyle,
+    ChannelSelectMenuBuilder,
+    ChannelSelectMenuInteraction,
+    CommandInteraction,
+    Message,
+    ModalBuilder,
+    RoleSelectMenuBuilder,
+    RoleSelectMenuInteraction,
+    StringSelectMenuBuilder,
+    StringSelectMenuInteraction,
+    StringSelectMenuOptionBuilder,
+    TextInputBuilder,
+    TextInputStyle,
+    UserSelectMenuBuilder,
+    UserSelectMenuInteraction
+} from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
+import listToAndMore from "../../utils/listToAndMore.js";
+
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder.setName("automod").setDescription("Setting for automatic moderation features");
+
+
+const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id);
+
+const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise<string[]> => {
+
+    const back = new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder().setCustomId("back").setLabel("Back").setStyle(ButtonStyle.Secondary).setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji));
+    let closed;
+    do {
+        let render: string[] = []
+        let mapped: string[] = [];
+        let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder;
+        switch(type) {
+            case "member": {
+                menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25);
+                mapped = await Promise.all(ids.map(async (id) => { return (await client.users.fetch(id).then(user => user.tag)) || "Unknown User" }));
+                render = ids.map(id => client.logger.renderUser(id))
+                break;
+            }
+            case "role": {
+                menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25);
+                mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.roles.fetch(id).then(role => role?.name ?? "Unknown Role"))}));
+                render = ids.map(id => client.logger.renderRole(id, interaction.guild!))
+                break;
+            }
+            case "channel": {
+                menu = new ChannelSelectMenuBuilder().setCustomId("channel").setPlaceholder("Select channels").setMaxValues(25);
+                mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.channels.fetch(id).then(channel => channel?.name ?? "Unknown Role")) }));
+                render = ids.map(id => client.logger.renderChannel(id))
+                break;
+            }
+        }
+        const removeOptions = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("remove")
+                    .setPlaceholder("Remove")
+                    .addOptions(mapped.map((name, i) => new StringSelectMenuOptionBuilder().setLabel(name).setValue(ids[i]!)))
+                    .setDisabled(ids.length === 0)
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle(title)
+            .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN"))
+            .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None"))
+            .setStatus("Success");
+        const components: ActionRowBuilder<
+            StringSelectMenuBuilder |
+            ButtonBuilder |
+            ChannelSelectMenuBuilder |
+            UserSelectMenuBuilder |
+            RoleSelectMenuBuilder
+        >[] = [new ActionRowBuilder<typeof menu>().addComponents(menu)]
+        if(ids.length > 0) components.push(removeOptions);
+        components.push(back);
+
+        await interaction.editReply({embeds: [embed], components: components})
+
+        let i: AnySelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000});
+        } catch(e) {
+            closed = true;
+            continue;
+        }
+
+        if(i.isButton()) {
+            await i.deferUpdate();
+            if(i.customId === "back") {
+                closed = true;
+                break;
+            }
+        } else if(i.isStringSelectMenu()) {
+            await i.deferUpdate();
+            if(i.customId === "remove") {
+                ids = ids.filter(id => id !== (i as StringSelectMenuInteraction).values[0]);
+                if(ids.length === 0) {
+                    menu.data.disabled = true;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            if(i.customId === "user") {
+                ids = ids.concat((i as UserSelectMenuInteraction).values);
+            } else if(i.customId === "role") {
+                ids = ids.concat((i as RoleSelectMenuInteraction).values);
+            } else if(i.customId === "channel") {
+                ids = ids.concat((i as ChannelSelectMenuInteraction).values);
+            }
+        }
+
+    } while(!closed)
+    return ids;
+}
+
+const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
+    NSFW: boolean,
+    size: boolean
+}): Promise<{NSFW: boolean, size: boolean}> => {
+    let closed = false;
+    do {
+        const options = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setLabel("Back")
+                    .setStyle(ButtonStyle.Secondary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("nsfw")
+                    .setLabel("NSFW")
+                    .setStyle(current.NSFW ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.NSFW, "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("size")
+                    .setLabel("Size")
+                    .setStyle(current.size ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.size, "id") as APIMessageComponentEmoji)
+            )
+
+        const embed = new EmojiEmbed()
+            .setTitle("Image Settings")
+            .setDescription(
+                `${emojiFromBoolean(current.NSFW)} **NSFW**\n` +
+                `${emojiFromBoolean(current.size)} **Size**\n`
+            )
+
+        await interaction.editReply({embeds: [embed], components: [options]});
+
+        let i: ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
+        } catch (e) {
+            return current;
+        }
+        await i.deferUpdate();
+        switch(i.customId) {
+            case "back": {
+                closed = true;
+                break;
+            }
+            case "nsfw": {
+                current.NSFW = !current.NSFW;
+                break;
+            }
+            case "size": {
+                current.size = !current.size;
+                break;
+            }
+        }
+    } while(!closed);
+    return current;
+}
+
+const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
+    enabled: boolean,
+    words: {strict: string[], loose: string[]},
+    allowed: {users: string[], roles: string[], channels: string[]}
+}): Promise<{
+    enabled: boolean,
+    words: {strict: string[], loose: string[]},
+    allowed: {users: string[], roles: string[], channels: string[]}
+}> => {
+    let closed = false;
+    do {
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setLabel("Back")
+                    .setStyle(ButtonStyle.Secondary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("enabled")
+                    .setLabel("Enabled")
+                    .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji),
+            );
+
+        const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("edit")
+                    .setPlaceholder("Edit... ")
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Words")
+                            .setDescription("Edit your list of words to filter")
+                            .setValue("words"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Allowed Users")
+                            .setDescription("Users who will be unaffected by the word filter")
+                            .setValue("allowedUsers"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Allowed Roles")
+                            .setDescription("Roles that will be unaffected by the word filter")
+                            .setValue("allowedRoles"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Allowed Channels")
+                            .setDescription("Channels where the word filter will not apply")
+                            .setValue("allowedChannels")
+                    )
+                    .setDisabled(!current.enabled)
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Word Filters")
+            .setDescription(
+                `${emojiFromBoolean(current.enabled)} **Enabled**\n` +
+                `**Strict Words:** ${listToAndMore(current.words.strict, 5)}\n` +
+                `**Loose Words:** ${listToAndMore(current.words.loose, 5)}\n\n` +
+                `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
+                `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
+                `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+
+        await interaction.editReply({embeds: [embed], components: [selectMenu, buttons]});
+
+        let i: ButtonInteraction | StringSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+
+        if(i.isButton()) {
+            await i.deferUpdate();
+            switch(i.customId) {
+                case "back": {
+                    closed = true;
+                    break;
+                }
+                case "enabled": {
+                    current.enabled = !current.enabled;
+                    break;
+                }
+            }
+        } else {
+            switch(i.values[0]) {
+                case "words": {
+                    await interaction.editReply({embeds: [new EmojiEmbed()
+                        .setTitle("Word Filter")
+                        .setDescription("Modal opened. If you can't see it, click back and try again.")
+                        .setStatus("Success")
+                        .setEmoji("GUILD.SETTINGS.GREEN")
+                    ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
+                        .setLabel("Back")
+                        .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                        .setStyle(ButtonStyle.Primary)
+                        .setCustomId("back")
+                    )]})
+                    const modal = new ModalBuilder()
+                        .setTitle("Word Filter")
+                        .setCustomId("wordFilter")
+                        .addComponents(
+                            new ActionRowBuilder<TextInputBuilder>()
+                                .addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("wordStrict")
+                                        .setLabel("Strict Words")
+                                        .setPlaceholder("Matches anywhere in the message, including surrounded by other characters")
+                                        .setValue(current.words.strict.join(", "))
+                                        .setStyle(TextInputStyle.Paragraph)
+                                        .setRequired(false)
+                                ),
+                            new ActionRowBuilder<TextInputBuilder>()
+                                .addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("wordLoose")
+                                        .setLabel("Loose Words")
+                                        .setPlaceholder("Matches only if the word is by itself, surrounded by spaces or punctuation")
+                                        .setValue(current.words.loose.join(", "))
+                                        .setStyle(TextInputStyle.Paragraph)
+                                        .setRequired(false)
+                                )
+                        )
+
+                    await i.showModal(modal);
+                    let out;
+                    try {
+                        out = await modalInteractionCollector(m, interaction.user);
+                    } catch (e) {
+                        break;
+                    }
+                    if (!out) break;
+                    if(out.isButton()) break;
+                    current.words.strict = out.fields.getTextInputValue("wordStrict")
+                        .split(",").map(s => s.trim()).filter(s => s.length > 0);
+                    current.words.loose = out.fields.getTextInputValue("wordLoose")
+                        .split(",").map(s => s.trim()).filter(s => s.length > 0);
+                    break;
+                }
+                case "allowedUsers": {
+                    await i.deferUpdate();
+                    current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter");
+                    break;
+                }
+                case "allowedRoles": {
+                    await i.deferUpdate();
+                    current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter");
+                    break;
+                }
+                case "allowedChannels": {
+                    await i.deferUpdate();
+                    current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter");
+                    break;
+                }
+            }
+        }
+    } while(!closed);
+    return current;
+}
+
+const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
+    enabled: boolean,
+    allowed: {users: string[], roles: string[], channels: string[]}
+}): Promise<{
+    enabled: boolean,
+    allowed: {users: string[], roles: string[], channels: string[]}
+}> => {
+
+    let closed = false;
+    do {
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setLabel("Back")
+                    .setStyle(ButtonStyle.Secondary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("enabled")
+                    .setLabel(current.enabled ? "Enabled" : "Disabled")
+                    .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji)
+            );
+        const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
+                .addComponents(
+                    new StringSelectMenuBuilder()
+                        .setCustomId("toEdit")
+                        .setPlaceholder("Edit your allow list")
+                        .addOptions(
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Users")
+                                .setDescription("Users that are allowed to send invites")
+                                .setValue("users"),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Roles")
+                                .setDescription("Roles that are allowed to send invites")
+                                .setValue("roles"),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Channels")
+                                .setDescription("Channels that anyone is allowed to send invites in")
+                                .setValue("channels")
+                        ).setDisabled(!current.enabled)
+                )
+
+        const embed = new EmojiEmbed()
+            .setTitle("Invite Settings")
+            .setDescription(
+                "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + `\n\n` +
+                `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` +
+                `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
+                `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
+                `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+
+
+        await interaction.editReply({embeds: [embed], components: [menu, buttons]});
+
+        let i: ButtonInteraction | StringSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            return current;
+        }
+
+        if(i.isButton()) {
+            await i.deferUpdate();
+            switch(i.customId) {
+                case "back": {
+                    closed = true;
+                    break;
+                }
+                case "enabled": {
+                    current.enabled = !current.enabled;
+                    break;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            switch(i.values[0]) {
+                case "users": {
+                    current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings");
+                    break;
+                }
+                case "roles": {
+                    current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings");
+                    break;
+                }
+                case "channels": {
+                    current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings");
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+    return current;
+}
+
+const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
+    mass: number,
+    everyone: boolean,
+    roles: boolean,
+    allowed: {
+        roles: string[],
+        rolesToMention: string[],
+        users: string[],
+        channels: string[]
+    }
+}): Promise<{
+    mass: number,
+    everyone: boolean,
+    roles: boolean,
+    allowed: {
+        roles: string[],
+        rolesToMention: string[],
+        users: string[],
+        channels: string[]
+    }
+}> => {
+    let closed = false;
+
+    do {
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setLabel("Back")
+                    .setStyle(ButtonStyle.Secondary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("everyone")
+                    .setLabel(current.everyone ? "Everyone" : "No one")
+                    .setStyle(current.everyone ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.everyone, "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("roles")
+                    .setLabel(current.roles ? "Roles" : "No roles")
+                    .setStyle(current.roles ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.roles, "id") as APIMessageComponentEmoji)
+            );
+        const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("toEdit")
+                    .setPlaceholder("Edit mention settings")
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Mass Mention Amount")
+                            .setDescription("The amount of mentions before the bot will delete the message")
+                            .setValue("mass"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Roles")
+                            .setDescription("Roles that are able to be mentioned")
+                            .setValue("roles"),
+                    )
+            )
+
+        const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("allowed")
+                    .setPlaceholder("Edit exceptions")
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                                .setLabel("Users")
+                                .setDescription("Users that are unaffected by the mention filter")
+                                .setValue("users"),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Roles")
+                                .setDescription("Roles that are unaffected by the mention filter")
+                                .setValue("roles"),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Channels")
+                                .setDescription("Channels where anyone is unaffected by the mention filter")
+                                .setValue("channels")
+                    )
+            )
+
+        const embed = new EmojiEmbed()
+            .setTitle("Mention Settings")
+            .setDescription(
+                `Log when members mention:\n` +
+                `${emojiFromBoolean(true)} **${current.mass}+ members** in one message\n` +
+                `${emojiFromBoolean(current.everyone)} **Everyone**\n` +
+                `${emojiFromBoolean(current.roles)} **Roles**\n` +
+                (current.allowed.rolesToMention.length > 0 ? `> *Except for ${listToAndMore(current.allowed.rolesToMention.map(r => `<@&${r}>`), 3)}*\n` : "") +
+                "\n" +
+                `Except if...\n` +
+                `> ${current.allowed.users.length > 0 ? `Member is: ${listToAndMore(current.allowed.users.map(u => `<@${u}>`), 3)}\n` : ""}` +
+                `> ${current.allowed.roles.length > 0 ? `Member has role: ${listToAndMore(current.allowed.roles.map(r => `<@&${r}>`), 3)}\n` : ""}` +
+                `> ${current.allowed.channels.length > 0 ? `In channel: ${listToAndMore(current.allowed.channels.map(c => `<#${c}>`), 3)}\n` : ""}`
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+
+        await interaction.editReply({embeds: [embed], components: [menu, allowedMenu, buttons]});
+
+        let i: ButtonInteraction | StringSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+
+        if(i.isButton()) {
+            await i.deferUpdate();
+            switch (i.customId) {
+                case "back": {
+                    closed = true;
+                    break;
+                }
+                case "everyone": {
+                    current.everyone = !current.everyone;
+                    break;
+                }
+                case "roles": {
+                    current.roles = !current.roles;
+                    break;
+                }
+            }
+        } else {
+            switch (i.customId) {
+                case "toEdit": {
+                    switch (i.values[0]) {
+                        case "mass": {
+                            await interaction.editReply({embeds: [new EmojiEmbed()
+                                .setTitle("Word Filter")
+                                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                .setStatus("Success")
+                                .setEmoji("GUILD.SETTINGS.GREEN")
+                            ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
+                                .setLabel("Back")
+                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                .setStyle(ButtonStyle.Primary)
+                                .setCustomId("back")
+                            )]})
+                            const modal = new ModalBuilder()
+                                .setTitle("Mass Mention Amount")
+                                .setCustomId("mass")
+                                .addComponents(
+                                    new ActionRowBuilder<TextInputBuilder>()
+                                        .addComponents(
+                                            new TextInputBuilder()
+                                                .setCustomId("mass")
+                                                .setPlaceholder("Amount")
+                                                .setMinLength(1)
+                                                .setMaxLength(3)
+                                                .setStyle(TextInputStyle.Short)
+                                        )
+                                )
+                            await i.showModal(modal);
+                            let out;
+                            try {
+                                out = await modalInteractionCollector(m, interaction.user);
+                            } catch (e) {
+                                break;
+                            }
+                            if (!out) break;
+                            if(out.isButton()) break;
+                            current.mass = parseInt(out.fields.getTextInputValue("mass"));
+                            break;
+                        }
+                        case "roles": {
+                            await i.deferUpdate();
+                            current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings");
+                            break;
+                        }
+                    }
+                    break;
+                }
+                case "allowed": {
+                    await i.deferUpdate();
+                    switch (i.values[0]) {
+                        case "users": {
+                            current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
+                            break;
+                        }
+                        case "roles": {
+                            current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
+                            break;
+                        }
+                        case "channels": {
+                            current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings");
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+    return current
+}
+
+const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, current?: {
+    channels?: string[],
+    allowed?: {
+        roles: string[],
+        users: string[]
+    }
+}): Promise<{
+    channels: string[],
+    allowed: {
+        roles: string[],
+        users: string[]
+    }
+}> => {
+    let closed = false;
+    if(!current) current = {channels: [], allowed: {roles: [], users: []}};
+    if(!current.channels) current.channels = [];
+    if(!current.allowed) current.allowed = {roles: [], users: []};
+
+    const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+        .addComponents(
+            new ChannelSelectMenuBuilder()
+                .setCustomId("toAdd")
+                .setPlaceholder("Select a channel")
+        )
+
+    const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+        .addComponents(
+            new StringSelectMenuBuilder()
+                .setCustomId("allowed")
+                .setPlaceholder("Edit exceptions")
+                .addOptions(
+                    new StringSelectMenuOptionBuilder()
+                            .setLabel("Users")
+                            .setDescription("Users that are unaffected by the mention filter")
+                            .setValue("users"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Roles")
+                            .setDescription("Roles that are unaffected by the mention filter")
+                            .setValue("roles")
+                )
+        )
+
+    do {
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setLabel("Back")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+            )
+
+        const embed = new EmojiEmbed()
+            .setTitle("Clean Settings")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+            .setDescription(
+                `Current clean channels:\n\n` +
+                `${current.channels.length > 0 ? listToAndMore(current.channels.map(c => `<#${c}>`), 10) : "None"}\n\n`
+            )
+            .setStatus("Success")
+
+
+        await interaction.editReply({embeds: [embed], components: [channelMenu, allowedMenu, buttons]});
+
+        let i: ButtonInteraction | ChannelSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | ChannelSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    closed = true;
+                    break;
+                }
+            }
+        } else {
+            switch (i.customId) {
+                case "toAdd": {
+                    const channelEmbed = new EmojiEmbed()
+                        .setTitle("Clean Settings")
+                        .setDescription(`Editing <#${i.values[0]}>`)
+                        .setEmoji("GUILD.SETTINGS.GREEN")
+                        .setStatus("Success")
+                    const channelButtons = new ActionRowBuilder<ButtonBuilder>()
+                        .addComponents(
+                            new ButtonBuilder()
+                                .setCustomId("back")
+                                .setLabel("Back")
+                                .setStyle(ButtonStyle.Primary)
+                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id")),
+                            new ButtonBuilder()
+                                .setCustomId("switch")
+                                .setLabel(current.channels.includes(i.values[0]!) ? "Remove" : "Add")
+                                .setStyle(current.channels.includes(i.values[0]!) ? ButtonStyle.Danger : ButtonStyle.Success)
+                        )
+
+                    await i.editReply({embeds: [channelEmbed], components: [channelButtons]});
+                    let j: ButtonInteraction;
+                    try {
+                        j = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
+                    } catch (e) {
+                        closed = true;
+                        break;
+                    }
+                    await j.deferUpdate();
+                    switch (j.customId) {
+                        case "back": {
+                            break;
+                        }
+                        case "switch": {
+                            if(current.channels.includes(i.values[0]!)) {
+                                current.channels.splice(current.channels.indexOf(i.values[0]!), 1);
+                            } else {
+                                current.channels.push(i.values[0]!);
+                            }
+                        }
+                    }
+                    break;
+                }
+                case "allowed": {
+                    switch (i.values[0]) {
+                        case "users": {
+                            current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
+                            break;
+                        }
+                        case "roles": {
+                            current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+
+    return current as {
+        channels: string[],
+        allowed: {
+            roles: string[],
+            users: string[]
+        }
+    };
+
+}
+
+const callback = async (interaction: CommandInteraction): Promise<void> => {
+    if (!interaction.guild) return;
+    const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
+    const config = (await client.database.guilds.read(interaction.guild.id)).filters;
+
+    let closed = false;
+
+    const button = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("save")
+                .setLabel("Save")
+                .setStyle(ButtonStyle.Success)
+        )
+
+    do {
+
+        const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("filter")
+                    .setPlaceholder("Select a filter to edit")
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Invites")
+                            .setDescription("Automatically delete messages containing server invites")
+                            .setValue("invites"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Mentions")
+                            .setDescription("Deletes messages with excessive mentions")
+                            .setValue("mentions"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Words")
+                            .setDescription("Delete messages containing filtered words")
+                            .setValue("words"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Malware")
+                            .setDescription("Automatically delete files and links containing malware")
+                            .setValue("malware"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Images")
+                            .setDescription("Checks performed on images (NSFW, size checking, etc.)")
+                            .setValue("images"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Clean")
+                            .setDescription("Automatically delete new messages in specific channels")
+                            .setValue("clean")
+                    )
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Automod Settings")
+            .setDescription(
+                `${emojiFromBoolean(config.invite.enabled)} **Invites**\n` +
+                `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` +
+                `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` +
+                `${emojiFromBoolean(config.malware)} **Malware**\n` +
+                `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n` +
+                `${emojiFromBoolean(config.clean.channels.length > 0)} **Clean**\n`
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+
+
+        await interaction.editReply({embeds: [embed], components: [selectMenu, button]});
+
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 300000}) as StringSelectMenuInteraction | ButtonInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            await client.database.guilds.write(interaction.guild.id, {filters: config});
+        } else {
+            switch(i.values[0]) {
+                case "invites": {
+                    config.invite = await inviteMenu(i, m, config.invite);
+                    break;
+                }
+                case "mentions": {
+                    config.pings = await mentionMenu(i, m, config.pings);
+                    break;
+                }
+                case "words": {
+                    config.wordFilter = await wordMenu(i, m, config.wordFilter);
+                    break;
+                }
+                case "malware": {
+                    config.malware = !config.malware;
+                    break;
+                }
+                case "images": {
+                    const next = await imageMenu(i, m, config.images);
+                    config.images = next;
+                    break;
+                }
+                case "clean": {
+                    const next = await cleanMenu(i, m, config.clean);
+                    config.clean = next;
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+    await interaction.deleteReply()
+
+};
+
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as Discord.GuildMember;
+    if (!member.permissions.has("ManageMessages"))
+        return "You must have the *Manage Messages* permission to use this command";
+    return true;
+};
+
+export { command };
+export { callback };
+export { check };
diff --git a/src/commands/settings/autopublish.ts b/src/commands/settings/autopublish.ts
new file mode 100644
index 0000000..1dc97e0
--- /dev/null
+++ b/src/commands/settings/autopublish.ts
@@ -0,0 +1,96 @@
+import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js";
+import type Discord from "discord.js";
+import client from "../../utils/client.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import compare from "lodash"
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+
+export const command = new SlashCommandSubcommandBuilder()
+    .setName("autopublish")
+    .setDescription("Automatically publish messages posted in announcement channels");
+
+export const callback = async (interaction: CommandInteraction): Promise<void> => {
+    await interaction.reply({
+        embeds: LoadingEmbed,
+        ephemeral: true,
+        fetchReply: true
+    });
+
+    let closed = false;
+    let config = await client.database.guilds.read(interaction.guild!.id);
+    let data = Object.assign({}, config.autoPublish);
+    do {
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("switch")
+                    .setLabel(data.enabled ? "Disabled" : "Enabled")
+                    .setStyle(data.enabled ? ButtonStyle.Danger : ButtonStyle.Success)
+                    .setEmoji(data.enabled ? "✅" : "❌"),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji("💾")
+                    .setDisabled(compare.isEqual(data, config.autoPublish))
+            );
+
+        const channelSelect = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel")
+                    .setMinValues(1)
+            );
+
+        const embed = new EmojiEmbed()
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [channelSelect, buttons]
+        });
+
+        let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction;
+        try {
+            i = await interaction.channel!.awaitMessageComponent({
+                filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
+                time: 300000
+            }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "switch": {
+                    data.enabled = !data.enabled;
+                    break;
+                }
+                case "save": {
+                    await client.database.guilds.write(interaction.guild!.id, { "autoPublish": data });
+                    config = await client.database.guilds.read(interaction.guild!.id);
+                    data = Object.assign({}, config.autoPublish);
+                    break;
+                }
+            }
+        } else {
+            for(const channel of i.values) {
+                data.channels.includes(channel) ? data.channels.splice(data.channels.indexOf(channel), 1) : data.channels.push(channel);
+            }
+        }
+
+    } while (!closed);
+
+    await interaction.deleteReply();
+}
+
+export const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as Discord.GuildMember;
+    const me = interaction.guild!.members.me!;
+    if (!member.permissions.has("ManageMessages"))
+        return "You must have the *Manage Messages* permission to use this command";
+    if (_partial) return true;
+    if (!me.permissions.has("ManageMessages")) return "I do not have the *Manage Messages* permission";
+    return true;
+};
diff --git a/src/commands/settings/filters.ts b/src/commands/settings/filters.ts
deleted file mode 100644
index 2e6f4c5..0000000
--- a/src/commands/settings/filters.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type Discord from "discord.js";
-import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder.setName("filter").setDescription("Setting for message filters");
-
-const callback = async (_interaction: CommandInteraction): Promise<void> => {
-    console.log("Filters");
-};
-
-const check = (interaction: CommandInteraction) => {
-    const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("ManageMessages"))
-        return "You must have the *Manage Messages* permission to use this command";
-    return true;
-};
-
-export { command };
-export { callback };
-export { check };
diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts
index 2709bee..238b8b9 100644
--- a/src/commands/settings/logs/attachment.ts
+++ b/src/commands/settings/logs/attachment.ts
@@ -1,198 +1,110 @@
 import { LoadingEmbed } from "../../../utils/defaults.js";
-import { ChannelType } from "discord-api-types/v9";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction } from "discord.js";
+import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType } from "discord.js";
 import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../../utils/confirmationMessage.js";
 import getEmojiByName from "../../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../../utils/client.js";
+import { getCommandMentionByName } from "../../../utils/getCommandDataByName.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("attachments")
         .setDescription("Where attachments should be logged to (Premium only)")
-        .addChannelOption((option) =>
-            option
-                .setName("channel")
-                .setDescription("The channel to log attachments in")
-                .addChannelTypes(ChannelType.GuildText)
-                .setRequired(false)
-        );
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const m = (await interaction.reply({
+    if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {});
+    await interaction.reply({
         embeds: LoadingEmbed,
         ephemeral: true,
         fetchReply: true
-    })) as Discord.Message;
-    if (interaction.options.get("channel")?.channel) {
-        let channel;
-        try {
-            channel = interaction.options.get("channel")?.channel;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Attachment Log 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("Attachment Log Channel")
-                        .setDescription("You must choose a channel in this server")
-                        .setStatus("Danger")
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                ]
-            });
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("CHANNEL.TEXT.EDIT")
-            .setTitle("Attachment Log Channel")
-            .setDescription(
-                "This will be the channel all attachments will be sent to.\n\n" +
-                    `Are you sure you want to set the attachment log channel to <#${channel.id}>?`
-            )
-            .setColor("Warning")
-            .setFailedMessage("Attachment log channel not set", "Warning", "CHANNEL.TEXT.DELETE")
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "logging.attachments.channel": channel.id
-                });
-                const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
-                const data = {
-                    meta: {
-                        type: "attachmentChannelUpdate",
-                        displayName: "Attachment Log 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 (e) {
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Attachment Log Channel")
-                            .setDescription("Something went wrong and the attachment log channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Attachment Log Channel")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("CHANNEL.TEXT.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let clicks = 0;
-    const data = await client.database.guilds.read(interaction.guild!.id);
-    let channel = data.logging.staff.channel;
+    })
 
-    let timedOut = false;
-    while (!timedOut) {
-        await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Attachment Log Channel")
-                    .setDescription(
-                        channel
-                            ? `Your attachment log channel is currently set to <#${channel}>`
-                            : "This server does not have an attachment log channel" +
-                                  (await client.database.premium.hasPremium(interaction.guild!.id)
-                                      ? ""
-                                      : "\n\nThis server does not have premium, so this feature is disabled")
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setCustomId("clear")
-                        .setLabel(clicks ? "Click again to confirm" : "Reset channel")
-                        .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!channel)
-                ])
-            ]
-        });
-        let i;
-        try {
-            i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            });
-        } catch (e) {
-            timedOut = true;
-            continue;
-        }
-        i.deferUpdate();
-        if ((i.component as unknown as ButtonInteraction).customId === "clear") {
-            clicks += 1;
-            if (clicks === 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild!.id, null, ["logging.announcements.channel"]);
-                channel = null;
-            }
-        }
-    }
-    await interaction.editReply({
+    if(!await client.database.premium.hasPremium(interaction.guild!.id)) return interaction.editReply({
         embeds: [
             new EmojiEmbed()
-                .setTitle("Attachment Log Channel")
-                .setDescription(
-                    channel
-                        ? `Your attachment log channel is currently set to <#${channel}>`
-                        : "This server does not have an attachment log channel"
-                )
-                .setStatus("Success")
-                .setEmoji("CHANNEL.TEXT.CREATE")
-                .setFooter({ text: "Message closed" })
-        ],
-        components: [
-            new ActionRowBuilder<ButtonBuilder>().addComponents([
+                .setTitle("Premium Required")
+                .setDescription(`This feature is exclusive to ${getCommandMentionByName("nucleus/premium")} servers.`)
+                .setStatus("Danger")
+                .setEmoji("NUCLEUS.PREMIUM")
+        ]
+    });
+
+    let data = await client.database.guilds.read(interaction.guild!.id);
+    let channel = data.logging.staff.channel;
+
+    let closed = false;
+    do {
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel")
+                    .setChannelTypes(ChannelType.GuildText)
+            );
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
                 new ButtonBuilder()
                     .setCustomId("clear")
                     .setLabel("Clear")
-                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                    .setStyle(ButtonStyle.Secondary)
-                    .setDisabled(true)
-            ])
-        ]
-    });
+                    .setStyle(ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as Discord.APIMessageComponentEmoji)
+                    .setDisabled(!channel),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as Discord.APIMessageComponentEmoji)
+                    .setDisabled(channel === data.logging.staff.channel)
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Attachments")
+            .setDescription(
+                `The channel to send all attachments from the server, allowing you to check them if they are deleted\n` +
+                `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n`
+            )
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [channelMenu, buttons]
+        });
+
+        let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction;
+        try {
+            i = (await interaction.channel!.awaitMessageComponent({
+                filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
+                time: 300000
+            })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            switch (i.customId) {
+                case "clear": {
+                    channel = null;
+                    break;
+                }
+                case "save": {
+                    await client.database.guilds.write(interaction.guild!.id, {
+                        "logging.attachments.channel": channel
+                    });
+                    data = await client.database.guilds.read(interaction.guild!.id);
+                    break;
+                }
+            }
+        } else {
+            channel = i.values[0]!;
+        }
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts
deleted file mode 100644
index 992491a..0000000
--- a/src/commands/settings/logs/channel.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-import { LoadingEmbed } from "../../../utils/defaults.js";
-import { ChannelType } from "discord-api-types/v9";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction, ButtonComponent } from "discord.js";
-import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../../utils/confirmationMessage.js";
-import getEmojiByName from "../../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import client from "../../../utils/client.js";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-        .setName("channel")
-        .setDescription("Sets or shows the log channel")
-        .addChannelOption((option) =>
-            option
-                .setName("channel")
-                .setDescription("The channel to set the log channel to")
-                .addChannelTypes(ChannelType.GuildText)
-        );
-
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const m = (await interaction.reply({
-        embeds: LoadingEmbed,
-        ephemeral: true,
-        fetchReply: true
-    })) as Discord.Message;
-    if (interaction.options.get("channel")?.channel) {
-        let channel;
-        try {
-            channel = interaction.options.get("channel")?.channel;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Log 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("Log Channel")
-                        .setDescription("You must choose a channel in this server")
-                        .setStatus("Danger")
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                ]
-            });
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("CHANNEL.TEXT.EDIT")
-            .setTitle("Log Channel")
-            .setDescription(`Are you sure you want to set the log channel to <#${channel.id}>?`)
-            .setColor("Warning")
-            .setFailedMessage("The log channel was not changed", "Danger", "CHANNEL.TEXT.DELETE")
-            .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
-                });
-                const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
-                const data = {
-                    meta: {
-                        type: "logChannelUpdate",
-                        displayName: "Log Channel Changed",
-                        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: channel.guild.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                console.log(e);
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Log Channel")
-                            .setDescription("Something went wrong and the log channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Log Channel")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("CHANNEL.TEXT.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let clicks = 0;
-    const data = await client.database.guilds.read(interaction.guild!.id);
-    let channel = data.logging.logs.channel;
-    let timedOut = false;
-    while (!timedOut) {
-        await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Log channel")
-                    .setDescription(
-                        channel
-                            ? `Your log channel is currently set to <#${channel}>`
-                            : "This server does not have a log channel"
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setCustomId("clear")
-                        .setLabel(clicks ? "Click again to confirm" : "Reset channel")
-                        .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!channel)
-                ])
-            ]
-        });
-        let i: ButtonInteraction;
-        try {
-            i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            }) as ButtonInteraction;
-        } catch (e) {
-            timedOut = true;
-        }
-        i = i!
-        i.deferUpdate();
-        if ((i.component as ButtonComponent).customId === "clear") {
-            clicks += 1;
-            if (clicks === 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild!.id, null, ["logging.logs.channel"]);
-                channel = null;
-            }
-        }
-    }
-    await interaction.editReply({
-        embeds: [
-            new EmojiEmbed()
-                .setTitle("Log channel")
-                .setDescription(
-                    channel
-                        ? `Your log channel is currently set to <#${channel}>`
-                        : "This server does not have a log channel"
-                )
-                .setStatus("Success")
-                .setEmoji("CHANNEL.TEXT.CREATE")
-                .setFooter({ text: "Message closed" })
-        ],
-        components: [
-            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                new ButtonBuilder()
-                    .setCustomId("clear")
-                    .setLabel("Clear")
-                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                    .setStyle(ButtonStyle.Secondary)
-                    .setDisabled(true)
-            ])
-        ]
-    });
-};
-
-const check = (interaction: CommandInteraction) => {
-    const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("ManageGuild"))
-        return "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/logs/events.ts b/src/commands/settings/logs/events.ts
index fbe79fa..eeef8fb 100644
--- a/src/commands/settings/logs/events.ts
+++ b/src/commands/settings/logs/events.ts
@@ -1,9 +1,11 @@
 import { LoadingEmbed } from "../../../utils/defaults.js";
-import Discord, { CommandInteraction, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, EmbedBuilder, StringSelectMenuInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders";
-import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import Discord, { CommandInteraction, ActionRowBuilder, ChannelSelectMenuBuilder, ChannelType, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ButtonInteraction, StringSelectMenuInteraction, ChannelSelectMenuInteraction, APIMessageComponentEmoji } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../../utils/client.js";
+import compare from "lodash";
 import { toHexArray, toHexInteger } from "../../../utils/calculate.js";
+import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../../utils/getEmojiByName.js";
 
 const logs: Record<string, string> = {
     channelUpdate: "Channels created, deleted or modified",
@@ -24,88 +26,138 @@
     webhookUpdate: "Webhooks created or deleted",
     guildMemberVerify: "Member runs verify",
     autoModeratorDeleted: "Messages auto deleted by Nucleus",
-    nucleusSettingsUpdated: "Nucleus' settings updated by a moderator",
-    ticketUpdate: "Tickets created or deleted"
+    ticketUpdate: "Tickets created or deleted",
+    //nucleusSettingsUpdated: "Nucleus' settings updated by a moderator"  // TODO
 };
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder.setName("events").setDescription("Sets what events should be logged");
+    builder
+        .setName("events")
+        .setDescription("The general log channel for the server, and setting what events to show")
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    await interaction.reply({
+    const m = (await interaction.reply({
         embeds: LoadingEmbed,
-        fetchReply: true,
-        ephemeral: true
-    });
-    let m: Message;
-    let timedOut = false;
+        ephemeral: true,
+        fetchReply: true
+    })) as Discord.Message;
+
+    let config = await client.database.guilds.read(interaction.guild!.id);
+    let data = Object.assign({}, config.logging.logs);
+    let closed = false;
+    let show = false;
     do {
-        const config = await client.database.guilds.read(interaction.guild!.id);
-        const converted = toHexArray(config.logging.logs.toLog);
-        const selectPane = new StringSelectMenuBuilder()
-            .setPlaceholder("Set events to log")
-            .setMaxValues(Object.keys(logs).length)
-            .setCustomId("logs")
-            .setMinValues(0)
-        Object.keys(logs).map((e, i) => {
-            selectPane.addOptions(new StringSelectMenuOptionBuilder()
-                .setLabel(logs[e]!)
-                .setValue(i.toString())
-                .setDefault(converted.includes(e))
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel")
+                    .setChannelTypes(ChannelType.GuildText)
+            )
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("switch")
+                    .setLabel(data.enabled ? "Enabled" : "Disabled")
+                    .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName((data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS"), "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("remove")
+                    .setLabel("Remove")
+                    .setStyle(ButtonStyle.Danger)
+                    .setDisabled(!data.channel),
+                new ButtonBuilder()
+                    .setCustomId("show")
+                    .setLabel("Manage Events")
+                    .setStyle(ButtonStyle.Primary),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(compare.isEqual(data, config.logging.logs))
+            )
+
+        const converted = toHexArray(data.toLog);
+        const toLogMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setPlaceholder("Set events to log")
+                    .setMaxValues(Object.keys(logs).length)
+                    .setCustomId("logs")
+                    .setMinValues(0)
+            )
+        Object.keys(logs).map((e) => {
+            toLogMenu.components[0]!.addOptions(
+                new StringSelectMenuOptionBuilder()
+                    .setLabel(logs[e]!)
+                    .setValue(e)
+                    .setDefault(converted.includes(e))
             )
         });
-        m = (await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Logging Events")
-                    .setDescription(
-                        "Below are the events being logged in the server. You can toggle them on and off in the dropdown"
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(selectPane),
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder().setLabel("Select all").setStyle(ButtonStyle.Primary).setCustomId("all"),
-                    new ButtonBuilder().setLabel("Select none").setStyle(ButtonStyle.Danger).setCustomId("none")
-                ])
-            ]
-        })) as Message;
-        let i;
+
+        const embed = new EmojiEmbed()
+            .setTitle("General Log Channel")
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+            .setDescription(
+                `This is the channel that all events you set to be logged will be stored\n` +
+                `**Channel:** ${data.channel ? `<#${data.channel}>` : "None"}\n`
+            )
+
+        const components: ActionRowBuilder<ButtonBuilder | ChannelSelectMenuBuilder | StringSelectMenuBuilder>[] = [channelMenu, buttons];
+        if(show) components.push(toLogMenu);
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: components
+        });
+
+        let i: ButtonInteraction | StringSelectMenuInteraction | ChannelSelectMenuInteraction;
         try {
             i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            });
+                filter: (i) => i.user.id === interaction.user.id,
+                time: 300000
+            }) as ButtonInteraction | StringSelectMenuInteraction | ChannelSelectMenuInteraction;
         } catch (e) {
-            timedOut = true;
+            closed = true;
             continue;
         }
-        i.deferUpdate();
-        if (i.customId === "logs") {
-            const selected = (i as StringSelectMenuInteraction).values;
-            const newLogs = toHexInteger(selected.map((e: string) => Object.keys(logs)[parseInt(e)]!));
-            await client.database.guilds.write(interaction.guild!.id, {
-                "logging.logs.toLog": newLogs
-            });
-        } else if (i.customId === "all") {
-            const newLogs = toHexInteger(Object.keys(logs).map((e) => e));
-            await client.database.guilds.write(interaction.guild!.id, {
-                "logging.logs.toLog": newLogs
-            });
-        } else if (i.customId === "none") {
-            await client.database.guilds.write(interaction.guild!.id, {
-                "logging.logs.toLog": 0
-            });
-        }
-    } while (!timedOut);
 
-    await interaction.editReply({ embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })] });
-    return;
+        await i.deferUpdate();
+
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "show": {
+                    show = !show;
+                    break;
+                }
+                case "switch": {
+                    data.enabled = !data.enabled;
+                    break;
+                }
+                case "save": {
+                    await client.database.guilds.write(interaction.guild!.id, {"logging.logs": data});
+                    config = await client.database.guilds.read(interaction.guild!.id);
+                    data = Object.assign({}, config.logging.logs);
+                    break;
+                }
+                case "remove": {
+                    data.channel = null;
+                    break;
+                }
+            }
+        } else if(i.isStringSelectMenu()) {
+            const hex = toHexInteger(i.values);
+            data.toLog = hex;
+        } else if(i.isChannelSelectMenu()) {
+            data.channel = i.values[0]!;
+        }
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts
deleted file mode 100644
index 13125ef..0000000
--- a/src/commands/settings/logs/staff.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-import { LoadingEmbed } from "../../../utils/defaults.js";
-import { ChannelType } from "discord-api-types/v9";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent } from "discord.js";
-import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../../utils/confirmationMessage.js";
-import getEmojiByName from "../../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-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.GuildText)
-                .setRequired(false)
-        );
-
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    if (!interaction.guild) return;
-    const m = (await interaction.reply({
-        embeds: LoadingEmbed,
-        ephemeral: true,
-        fetchReply: true
-    })) as Discord.Message;
-    if (interaction.options.get("channel")?.channel) {
-        let channel;
-        try {
-            channel = interaction.options.get("channel")?.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")
-                ]
-            });
-        }
-        const 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")
-            .setFailedMessage("Staff notifications channel not set", "Warning", "CHANNEL.TEXT.DELETE")
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        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;
-                const data = {
-                    meta: {
-                        type: "staffChannelUpdate",
-                        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 (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;
-    const data = await client.database.guilds.read(interaction.guild.id);
-    let channel = data.logging.staff.channel;
-    let timedOut = false;
-    while (!timedOut) {
-        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 ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setCustomId("clear")
-                        .setLabel(clicks ? "Click again to confirm" : "Reset channel")
-                        .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!channel)
-                ])
-            ]
-        });
-        let i;
-        try {
-            i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            });
-        } catch (e) {
-            timedOut = true;
-            continue;
-        }
-        i.deferUpdate();
-        if ((i.component as ButtonComponent).customId === "clear") {
-            clicks += 1;
-            if (clicks === 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild.id, null, ["logging.staff.channel"]);
-                channel = null;
-            }
-        }
-    }
-    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 ActionRowBuilder<ButtonBuilder>().addComponents([
-                new ButtonBuilder()
-                    .setCustomId("clear")
-                    .setLabel("Clear")
-                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                    .setStyle(ButtonStyle.Secondary)
-                    .setDisabled(true)
-            ])
-        ]
-    });
-};
-
-const check = (interaction: CommandInteraction) => {
-    const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("ManageGuild"))
-        return "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/logs/warnings.ts b/src/commands/settings/logs/warnings.ts
new file mode 100644
index 0000000..84772e6
--- /dev/null
+++ b/src/commands/settings/logs/warnings.ts
@@ -0,0 +1,104 @@
+import { LoadingEmbed } from "../../../utils/defaults.js";
+import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType } from "discord.js";
+import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../../utils/getEmojiByName.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
+import client from "../../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+        .setName("warnings")
+        .setDescription("Settings for the staff notifications channel")
+
+const callback = async (interaction: CommandInteraction): Promise<unknown> => {
+    if (!interaction.guild) return;
+    await interaction.reply({
+        embeds: LoadingEmbed,
+        ephemeral: true,
+        fetchReply: true
+    })
+
+    let data = await client.database.guilds.read(interaction.guild.id);
+    let channel = data.logging.staff.channel;
+    let closed = false;
+    do {
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+        .addComponents(
+            new ChannelSelectMenuBuilder()
+                .setCustomId("channel")
+                .setPlaceholder("Select a channel")
+                .setChannelTypes(ChannelType.GuildText)
+        );
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("clear")
+                    .setLabel("Clear")
+                    .setStyle(ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as Discord.APIMessageComponentEmoji)
+                    .setDisabled(!channel),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as Discord.APIMessageComponentEmoji)
+                    .setDisabled(channel === data.logging.staff.channel)
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Staff Notifications Channel")
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+            .setDescription(
+                `Logs which require an action from a moderator or administrator will be sent to this channel.\n` +
+                `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n`
+            )
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [channelMenu, buttons]
+        });
+
+        let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction;
+        try {
+            i = (await interaction.channel!.awaitMessageComponent({
+                filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
+                time: 300000
+            })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            switch (i.customId) {
+                case "clear": {
+                    channel = null;
+                    break;
+                }
+                case "save": {
+                    await client.database.guilds.write(interaction.guild!.id, {
+                        "logging.warnings.channel": channel
+                    });
+                    data = await client.database.guilds.read(interaction.guild!.id);
+                    break;
+                }
+            }
+        } else {
+            channel = i.values[0]!;
+        }
+    } while (!closed);
+
+    await interaction.deleteReply()
+};
+
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as Discord.GuildMember;
+    if (!member.permissions.has("ManageGuild"))
+        return "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/commands.ts b/src/commands/settings/moderation.ts
similarity index 73%
rename from src/commands/settings/commands.ts
rename to src/commands/settings/moderation.ts
index 25034b2..336e53a 100644
--- a/src/commands/settings/commands.ts
+++ b/src/commands/settings/moderation.ts
@@ -1,50 +1,28 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, Role, ButtonStyle, ButtonComponent, TextInputBuilder } from "discord.js";
+import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent, TextInputBuilder, RoleSelectMenuBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../utils/client.js";
 import { modalInteractionCollector } from "../../utils/dualCollector.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import keyValueList from "../../utils/generateKeyValueList.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
-        .setName("commands")
+        .setName("moderation")
         .setDescription("Links and text shown to a user after a moderator action is performed")
-        .addRoleOption((o) => o.setName("role").setDescription("The role given when a member is muted"));
 
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    await interaction.reply({
+const callback = async (interaction: CommandInteraction): Promise<void> => {
+    const m = await interaction.reply({
         embeds: LoadingEmbed,
         ephemeral: true,
         fetchReply: true
     });
-    let m;
-    let clicked = "";
-    if (interaction.options.get("role")) {
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("GUILD.ROLES.DELETE")
-            .setTitle("Moderation Commands")
-            .setDescription(
-                keyValueList({
-                    role: `<@&${(interaction.options.get("role") as unknown as Role).id}>`
-                })
-            )
-            .setColor("Danger")
-            .send(true);
-        if (confirmation.cancelled) return
-        if (confirmation.success) {
-            await client.database.guilds.write(interaction.guild!.id, {
-                ["moderation.mute.role"]: (interaction.options.get("role") as unknown as Role).id
-            });
-        }
-    }
     let timedOut = false;
     while (!timedOut) {
         const config = await client.database.guilds.read(interaction.guild!.id);
         const moderation = config.moderation;
-        m = await interaction.editReply({
+        console.log(moderation)
+        await interaction.editReply({
             embeds: [
                 new EmojiEmbed()
                     .setTitle("Moderation Commands")
@@ -52,8 +30,7 @@
                     .setStatus("Success")
                     .setDescription(
                         "These links are shown below the message sent in a user's DM when they are punished.\n\n" +
-                            "**Mute Role:** " +
-                            (moderation.mute.role ? `<@&${moderation.mute.role}>` : "*None set*")
+                            "**Mute Role:** " + (moderation.mute.role ? `<@&${moderation.mute.role}>` : "*None set*")
                     )
             ],
             components: [
@@ -93,24 +70,23 @@
                 ]),
                 new ActionRowBuilder<ButtonBuilder>().addComponents([
                     new ButtonBuilder()
-                        .setLabel(clicked === "clearMuteRole" ? "Click again to confirm" : "Clear mute role")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clearMuteRole")
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!moderation.mute.role),
-                    new ButtonBuilder()
                         .setCustomId("timeout")
                         .setLabel("Mute timeout " + (moderation.mute.timeout ? "Enabled" : "Disabled"))
                         .setStyle(moderation.mute.timeout ? ButtonStyle.Success : ButtonStyle.Danger)
                         .setEmoji(getEmojiByName("CONTROL." + (moderation.mute.timeout ? "TICK" : "CROSS"), "id"))
-                ])
+                ]),
+                new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(
+                    new RoleSelectMenuBuilder()
+                        .setCustomId("muteRole")
+                        .setPlaceholder("Select a new mute role")
+                )
             ]
         });
         let i;
         try {
             i = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             timedOut = true;
@@ -118,20 +94,13 @@
         }
         type modIDs = "mute" | "kick" | "ban" | "softban" | "warn" | "role";
         let chosen = moderation[i.customId as modIDs];
-        if ((i.component as ButtonComponent).customId === "clearMuteRole") {
-            i.deferUpdate();
-            if (clicked === "clearMuteRole") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "moderation.mute.role": null
-                });
-            } else {
-                clicked = "clearMuteRole";
-            }
+        if (i.isRoleSelectMenu()) {
+            await i.deferUpdate();
+            await client.database.guilds.write(interaction.guild!.id, {
+                "moderation.mute.role": i.values[0]!
+            });
             continue;
-        } else {
-            clicked = "";
-        }
-        if ((i.component as ButtonComponent).customId === "timeout") {
+        } else if ((i.component as ButtonComponent).customId === "timeout") {
             await i.deferUpdate();
             await client.database.guilds.write(interaction.guild!.id, {
                 "moderation.mute.timeout": !moderation.mute.timeout
@@ -183,15 +152,11 @@
             });
             let out: Discord.ModalSubmitInteraction | null;
             try {
-                out = await modalInteractionCollector(
-                    m,
-                    (m) => m.channel!.id === interaction.channel!.id,
-                    (_) => true
-                ) as Discord.ModalSubmitInteraction | null;
+                out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
             } catch (e) {
                 continue;
             }
-            if (!out) continue
+            if (!out || out.isButton()) continue
             const buttonText = out.fields.getTextInputValue("name");
             const buttonLink = out.fields.getTextInputValue("url").replace(/{id}/gi, "{id}");
             const current = chosen;
@@ -206,9 +171,10 @@
             }
         }
     }
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts
index b62d962..cccb6f6 100644
--- a/src/commands/settings/rolemenu.ts
+++ b/src/commands/settings/rolemenu.ts
@@ -1,19 +1,478 @@
 import type Discord from "discord.js";
-import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, Message, ModalBuilder, RoleSelectMenuBuilder, RoleSelectMenuInteraction, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import createPageIndicator from "../../utils/createPageIndicator.js";
+import { configToDropdown } from "../../actions/roleMenu.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
+import ellipsis from "../../utils/ellipsis.js";
+import lodash from 'lodash';
+
+const isEqual = lodash.isEqual;
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("rolemenu")
-        .setDescription("rolemenu") // TODO
-        .addRoleOption((option) => option.setName("role").setDescription("The role to give after verifying")); // FIXME FOR FUCK SAKE
+        .setDescription("rolemenu")
+
+interface ObjectSchema {
+    name: string;
+    description: string;
+    min: number;
+    max: number;
+    options: {
+        name: string;
+        description: string | null;
+        role: string;
+    }[];
+}
+
+const defaultRolePageConfig = {
+    name: "Role Menu Page",
+    description: "A new role menu page",
+    min: 0,
+    max: 0,
+    options: [
+        {name: "Role 1", description: null, role: "No role set"}
+    ]
+}
+
+const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, currentObj: ObjectSchema[]) => {
+    const reorderRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+        .addComponents(
+            new StringSelectMenuBuilder()
+                .setCustomId("reorder")
+                .setPlaceholder("Select all pages in the order you want them to appear.")
+                .setMinValues(currentObj.length)
+                .setMaxValues(currentObj.length)
+                .addOptions(
+                    currentObj.map((o, i) => new StringSelectMenuOptionBuilder()
+                        .setLabel(o.name)
+                        .setValue(i.toString())
+                    )
+                )
+        );
+    const buttonRow = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+        )
+    await interaction.editReply({
+        embeds: [
+            new EmojiEmbed()
+                .setTitle("Role Menu")
+                .setDescription("Select pages in the order you want them to appear.")
+                .setStatus("Success")
+        ],
+        components: [reorderRow, buttonRow]
+    });
+    let out: StringSelectMenuInteraction | ButtonInteraction | null;
+    try {
+        out = await m.awaitMessageComponent({
+            filter: (i) => i.channel!.id === interaction.channel!.id,
+            time: 300000
+        }) as StringSelectMenuInteraction | ButtonInteraction | null;
+    } catch (e) {
+        console.error(e);
+        out = null;
+    }
+    if(!out) return;
+    out.deferUpdate();
+    if (out.isButton()) return;
+    const values = out.values;
+
+    const newOrder: ObjectSchema[] = currentObj.map((_, i) => {
+        const index = values.findIndex(v => v === i.toString());
+        return currentObj[index];
+    }) as ObjectSchema[];
+
+    return newOrder;
+}
+
+const editNameDescription = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => {
+
+    let {name, description} = data;
+    const modal = new ModalBuilder()
+        .setTitle("Edit Name and Description")
+        .setCustomId("editNameDescription")
+        .addComponents(
+            new ActionRowBuilder<TextInputBuilder>()
+                .addComponents(
+                    new TextInputBuilder()
+                        .setLabel("Name")
+                        .setCustomId("name")
+                        .setPlaceholder("The name of the role (e.g. Programmer)")
+                        .setStyle(TextInputStyle.Short)
+                        .setValue(name ?? "")
+                        .setRequired(true)
+                ),
+            new ActionRowBuilder<TextInputBuilder>()
+                .addComponents(
+                    new TextInputBuilder()
+                        .setLabel("Description")
+                        .setCustomId("description")
+                        .setPlaceholder("A short description of the role (e.g. A role for people who code)")
+                        .setStyle(TextInputStyle.Short)
+                        .setValue(description ?? "")
+                )
+        )
+    const button = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+        )
+
+    await i.showModal(modal)
+    await interaction.editReply({
+        embeds: [
+            new EmojiEmbed()
+                .setTitle("Role Menu")
+                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                .setStatus("Success")
+        ],
+        components: [button]
+    });
+
+    let out: Discord.ModalSubmitInteraction | null;
+    try {
+        out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
+    } catch (e) {
+        console.error(e);
+        out = null;
+    }
+    if(!out) return [name, description];
+    if (out.isButton()) return [name, description];
+    name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
+    description = out.fields.fields.find((f) => f.customId === "description")?.value ?? description;
+    return [name, description]
+
+}
+
+const editRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise<ObjectSchema | null> => {
+    if (!data) data = {
+        name: "Role Menu Page",
+        description: "A new role menu page",
+        min: 0,
+        max: 0,
+        options: []
+    };
+    const buttons = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+            new ButtonBuilder()
+                .setCustomId("edit")
+                .setLabel("Edit")
+                .setStyle(ButtonStyle.Primary)
+                .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+            new ButtonBuilder()
+                .setCustomId("addRole")
+                .setLabel("Add Role")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+        );
+
+    let back = false
+    if(data.options.length === 0) {
+        data.options = [
+            {name: "Role 1", description: null, role: "No role set"}
+        ]
+    }
+    do {
+        const previewSelect = configToDropdown("Edit Roles", {name: data.name, description: data.description, min: 1, max: 1, options: data.options});
+        const embed = new EmojiEmbed()
+            .setTitle(`${data.name}`)
+            .setStatus("Success")
+            .setDescription(
+                `**Description:**\n> ${data.description}\n\n` +
+                `**Min:** ${data.min}` + (data.min === 0 ? " (Members will be given a skip button)" : "") + "\n" +
+                `**Max:** ${data.max}\n`
+            )
+
+        interaction.editReply({embeds: [embed], components: [previewSelect, buttons]});
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            back = true;
+            break;
+        }
+
+        if (i.isStringSelectMenu()) {
+            if(i.customId === "roles") {
+                await i.deferUpdate();
+                await createRoleMenuOptionPage(interaction, m, data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0]));
+            }
+        } else if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    await i.deferUpdate();
+                    back = true;
+                    break;
+                }
+                case "edit": {
+                    const [name, description] = await editNameDescription(i, interaction, m, data);
+                    data.name = name ? name : data.name;
+                    data.description = description ? description : data.description;
+                    break;
+                }
+                case "addRole": {
+                    await i.deferUpdate();
+                    data.options.push(await createRoleMenuOptionPage(interaction, m));
+                    break;
+                }
+            }
+        }
+
+    } while (!back);
+    if(isEqual(data, defaultRolePageConfig)) return null;
+    return data;
+}
+
+const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: {name: string; description: string | null; role: string}) => {
+    const { renderRole} = client.logger;
+    if (!data) data = {
+        name: "New role Menu Option",
+        description: null,
+        role: ""
+    };
+    let back = false;
+    const buttons = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+            new ButtonBuilder()
+                .setCustomId("edit")
+                .setLabel("Edit Details")
+                .setStyle(ButtonStyle.Primary)
+                .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji)
+        );
+    do {
+        const roleSelect = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder(data.role ? "Set role to" : "Set the role");
+        const embed = new EmojiEmbed()
+            .setTitle(`${data.name}`)
+            .setStatus("Success")
+            .setDescription(
+                `**Description:**\n> ${data.description ?? "No description set"}\n\n` +
+                `**Role:** ${data.role ? renderRole((await interaction.guild!.roles.fetch(data.role))!) : "No role set"}\n`
+            )
+
+        interaction.editReply({embeds: [embed], components: [new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(roleSelect), buttons]});
+
+        let i: RoleSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | RoleSelectMenuInteraction;
+        } catch (e) {
+            back = true;
+            break;
+        }
+
+        if (i.isRoleSelectMenu()) {
+            if(i.customId === "role") {
+                await i.deferUpdate();
+                data.role = (i as RoleSelectMenuInteraction).values[0]!;
+            }
+        } else if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    await i.deferUpdate();
+                    back = true;
+                    break;
+                }
+                case "edit": {
+                    await i.deferUpdate();
+                    const [name, description] = await editNameDescription(i, interaction, m, data as {name: string; description: string});
+                    data.name = name ? name : data.name;
+                    data.description = description ? description : data.description;
+                    break;
+                }
+            }
+        }
+    } while (!back);
+    return data;
+}
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    console.log("we changed the charger again because fuck you");
-    await interaction.reply("You're mum");
+    if (!interaction.guild) return;
+    const m = await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true});
+
+    let page = 0;
+    let closed = false;
+    const config = await client.database.guilds.read(interaction.guild.id);
+    let currentObject: ObjectSchema[] = config.roleMenu.options;
+    let modified = false;
+    do {
+        const embed = new EmojiEmbed()
+            .setTitle("Role Menu")
+            .setEmoji("GUILD.GREEN")
+            .setStatus("Success");
+        const noRoleMenus = currentObject.length === 0;
+        let current: ObjectSchema;
+
+        const pageSelect = new StringSelectMenuBuilder()
+            .setCustomId("page")
+            .setPlaceholder("Select a Role Menu page to manage");
+        const actionSelect = new StringSelectMenuBuilder()
+            .setCustomId("action")
+            .setPlaceholder("Perform an action")
+            .addOptions(
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Edit")
+                    .setDescription("Edit this page")
+                    .setValue("edit")
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Delete")
+                    .setDescription("Delete this page")
+                    .setValue("delete")
+                    .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
+        );
+        const buttonRow = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+                    .setDisabled(page === 0),
+                new ButtonBuilder()
+                    .setCustomId("next")
+                    .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Primary)
+                    .setDisabled(page === Object.keys(currentObject).length - 1),
+                new ButtonBuilder()
+                    .setCustomId("add")
+                    .setLabel("New Page")
+                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(Object.keys(currentObject).length >= 24),
+                new ButtonBuilder()
+                    .setCustomId("reorder")
+                    .setLabel("Reorder Pages")
+                    .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(Object.keys(currentObject).length <= 1),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(!modified),
+            );
+        if(noRoleMenus) {
+            embed.setDescription("No role menu pages have been set up yet. Use the button below to add one.\n\n" +
+                createPageIndicator(1, 1, undefined, true)
+            );
+            pageSelect.setDisabled(true);
+            actionSelect.setDisabled(true);
+            pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+                .setLabel("No role menu pages")
+                .setValue("none")
+            );
+        } else {
+            page = Math.min(page, Object.keys(currentObject).length - 1);
+            current = currentObject[page]!;
+            embed.setDescription(`**Currently Editing:** ${current.name}\n\n` +
+                `**Description:**\n> ${current.description}\n` +
+                `\n\n${createPageIndicator(Object.keys(config.roleMenu.options).length, page)}`
+            );
+
+            pageSelect.addOptions(
+                currentObject.map((key: ObjectSchema, index) => {
+                    return new StringSelectMenuOptionBuilder()
+                        .setLabel(ellipsis(key.name, 50))
+                        .setDescription(ellipsis(key.description, 50))
+                        .setValue(index.toString());
+                })
+            );
+
+        }
+
+        await interaction.editReply({embeds: [embed], components: [new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect), new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect), buttonRow]});
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+
+        await i.deferUpdate();
+        if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    page--;
+                    break;
+                }
+                case "next": {
+                    page++;
+                    break;
+                }
+                case "add": {
+                    const newPage = await editRoleMenuPage(i, m)
+                    if(!newPage) break;
+                    currentObject.push();
+                    page = currentObject.length - 1;
+                    break;
+                }
+                case "reorder": {
+                    const reordered = await reorderRoleMenuPages(interaction, m, currentObject);
+                    if(!reordered) break;
+                    currentObject = reordered;
+                    break;
+                }
+                case "save": {
+                    client.database.guilds.write(interaction.guild.id, {"roleMenu.options": currentObject});
+                    modified = false;
+                    break;
+                }
+            }
+        } else if (i.isStringSelectMenu()) {
+            switch (i.customId) {
+                case "action": {
+                    switch(i.values[0]) {
+                        case "edit": {
+                            const edited = await editRoleMenuPage(i, m, current!);
+                            if(!edited) break;
+                            currentObject[page] = edited;
+                            modified = true;
+                            break;
+                        }
+                        case "delete": {
+                            if(page === 0 && currentObject.keys.length - 1 > 0) page++;
+                            else page--;
+                            currentObject.splice(page, 1);
+                            break;
+                        }
+                    }
+                    break;
+                }
+                case "page": {
+                    page = parseInt(i.values[0]!);
+                    break;
+                }
+            }
+        }
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageRoles"))
         return "You must have the *Manage Roles* permission to use this command";
diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts
index cdd218b..d46b57e 100644
--- a/src/commands/settings/stats.ts
+++ b/src/commands/settings/stats.ts
@@ -1,249 +1,403 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
-import Discord, { CommandInteraction, Message, ActionRowBuilder, GuildMember, StringSelectMenuBuilder, StringSelectMenuInteraction, AutocompleteInteraction } from "discord.js";
+import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, ModalBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../utils/client.js";
 import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js";
-import { callback as statsChannelAddCallback } from "../../reflex/statsChannelUpdate.js";
 import singleNotify from "../../utils/singleNotify.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import createPageIndicator from "../../utils/createPageIndicator.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
+
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("stats")
         .setDescription("Controls channels which update when someone joins or leaves the server")
-        .addChannelOption((option) => option.setName("channel").setDescription("The channel to modify"))
-        .addStringOption((option) =>
-            option
-                .setName("name")
-                .setDescription("The new channel name | Enter any text or use the extra variables like {memberCount}")
-                .setAutocomplete(true)
-        );
 
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {  // TODO: This command feels unintuitive. Clicking a channel in the select menu deletes it
-    // instead, it should give a submenu to edit the channel, enable/disable or delete it
-    singleNotify("statsChannelDeleted", interaction.guild!.id, true);
-    const m = (await interaction.reply({
-        embeds: LoadingEmbed,
-        ephemeral: true,
-        fetchReply: true
-    })) as Message;
-    let config = await client.database.guilds.read(interaction.guild!.id);
-    if (interaction.options.get("name")?.value as string) {
-        let channel;
-        if (Object.keys(config.stats).length >= 25) {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Stats Channel")
-                        .setDescription("You can only have 25 stats channels in a server")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        try {
-            channel = interaction.options.get("channel")?.channel as Discord.Channel;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Stats 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("Stats Channel")
-                        .setDescription("You must choose a channel in this server")
-                        .setStatus("Danger")
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                ]
-            });
-        }
-        let newName = await convertCurlyBracketString(
-            interaction.options.get("name")?.value as string,
-            "",
-            "",
-            interaction.guild!.name,
-            interaction.guild!.members
-        );
-        if (interaction.options.get("channel")?.channel!.type === Discord.ChannelType.GuildText) {
-            newName = newName.toLowerCase().replace(/[\s]/g, "-");
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("CHANNEL.TEXT.EDIT")
-            .setTitle("Stats Channel")
-            .setDescription(
-                `Are you sure you want to set <#${channel.id}> to a stats channel?\n\n*Preview: ${newName.replace(
-                    /^ +| $/g,
-                    ""
-                )}*`
-            )
-            .setColor("Warning")
-            .setInverted(true)
-            .setFailedMessage(`Could not convert <#${channel.id}> to a stats chanel.`, "Danger", "CHANNEL.TEXT.DELETE")
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                const name = interaction.options.get("name")?.value as string;
-                const channel = interaction.options.get("channel")?.channel as Discord.TextChannel;
-                await client.database.guilds.write(interaction.guild!.id, {
-                    [`stats.${channel.id}`]: { name: name, enabled: true }
-                });
-                const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
-                const data = {
-                    meta: {
-                        type: "statsChannelUpdate",
-                        displayName: "Stats 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)),
-                        name: entry(
-                            interaction.options.get("name")?.value as string,
-                            `\`${interaction.options.get("name")?.value as string}\``
+
+const showModal = async (interaction: MessageComponentInteraction, current: { enabled: boolean; name: string; }) => {
+    await interaction.showModal(
+        new ModalBuilder()
+            .setCustomId("modal")
+            .setTitle(`Stats channel name`)
+            .addComponents(
+                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                    new TextInputBuilder()
+                        .setCustomId("ex1")
+                        .setLabel("Server Info (1/3)")
+                        .setPlaceholder(
+                            `{serverName} - This server's name\n\n` +
+                            `These placeholders will be replaced with the server's name, etc..`
                         )
-                    },
-                    hidden: {
-                        guild: interaction.guild!.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                console.log(e);
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Stats Channel")
-                            .setDescription("Something went wrong and the stats channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Stats Channel")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("CHANNEL.TEXT.CREATE")
-                ],
-                components: []
-            });
-        }
-        await statsChannelAddCallback(client, interaction.member as GuildMember);
-    }
-    let timedOut = false;
-    while (!timedOut) {
-        config = await client.database.guilds.read(interaction.guild!.id);
-        const stats = config.stats;
-        const selectMenu = new StringSelectMenuBuilder()
-            .setCustomId("remove")
-            .setMinValues(1)
-            .setMaxValues(Math.max(1, Object.keys(stats).length));
-        await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Stats Channel")
-                    .setDescription(
-                        "The following channels update when someone joins or leaves the server. You can select a channel to remove it from the list."
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
-                    Object.keys(stats).length
-                        ? [
-                              selectMenu
-                                  .setPlaceholder("Select a stats channel to remove, stopping it updating")
-                                  .addOptions(
-                                      Object.keys(stats).map((key) => ({
-                                          label: interaction.guild!.channels.cache.get(key)!.name,
-                                          value: key,
-                                          description: `${stats[key]!.name}`
-                                      }))
-                                  )
-                          ]
-                        : [
-                              selectMenu
-                                  .setPlaceholder("The server has no stats channels")
-                                  .setDisabled(true)
-                                  .setOptions([
-                                      {
-                                          label: "*Placeholder*",
-                                          value: "placeholder",
-                                          description: "No stats channels"
-                                      }
-                                  ])
-                          ]
+                        .setMaxLength(1)
+                        .setRequired(false)
+                        .setStyle(Discord.TextInputStyle.Paragraph)
+                ),
+                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                    new TextInputBuilder()
+                        .setCustomId("ex2")
+                        .setLabel("Member Counts (2/3) - {MemberCount:...}")
+                        .setPlaceholder(
+                            `{:all} - Total member count\n` +
+                            `{:humans} - Total non-bot users\n` +
+                            `{:bots} - Number of bots\n`
+                        )
+                        .setMaxLength(1)
+                        .setRequired(false)
+                        .setStyle(Discord.TextInputStyle.Paragraph)
+                ),
+                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                    new TextInputBuilder()
+                        .setCustomId("ex3")
+                        .setLabel("Latest Member (3/3) - {member:...}")
+                        .setPlaceholder(
+                                `{:name} - The members name\n`
+                        )
+                        .setMaxLength(1)
+                        .setRequired(false)
+                        .setStyle(Discord.TextInputStyle.Paragraph)
+                ),
+                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                    new TextInputBuilder()
+                        .setCustomId("text")
+                        .setLabel("Channel name input")
+                        .setMaxLength(1000)
+                        .setRequired(true)
+                        .setStyle(Discord.TextInputStyle.Short)
+                        .setValue(current.name)
+                )
+            )
+    );
+}
+
+type ObjectSchema = Record<string, {name: string, enabled: boolean}>
+
+
+const addStatsChannel = async (interaction: CommandInteraction, m: Message, currentObject: ObjectSchema): Promise<ObjectSchema> => {
+    let closed = false;
+    let cancelled = false;
+    const originalObject = Object.fromEntries(Object.entries(currentObject).map(([k, v]) => [k, {...v}]));
+    let newChannel: string | undefined;
+    let newChannelName: string = "{memberCount:all}-members";
+    let newChannelEnabled: boolean = true;
+    do {
+        m = await interaction.editReply({
+            embeds: [new EmojiEmbed()
+                .setTitle("Stats Channel")
+                .setDescription(
+                    `New stats channel` + (newChannel ? ` in <#${newChannel}>` : "") + "\n\n" +
+                    `**Name:** \`${newChannelName}\`\n` +
+                    `**Preview:** ${await convertCurlyBracketString(newChannelName, interaction.user!.id, interaction.user.username, interaction.guild!.name, interaction.guild!.members)}\n` +
+                    `**Enabled:** ${newChannelEnabled ? "Yes" : "No"}\n\n`
+                )
+                .setEmoji("SETTINGS.STATS.GREEN")
+                .setStatus("Success")
+            ], components: [
+                new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
+                    new ChannelSelectMenuBuilder()
+                        .setCustomId("channel")
+                        .setPlaceholder("Select a channel to use")
+                ),
+                new ActionRowBuilder<ButtonBuilder>().addComponents(
+                    new ButtonBuilder()
+                        .setLabel("Cancel")
+                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                        .setStyle(ButtonStyle.Danger)
+                        .setCustomId("back"),
+                    new ButtonBuilder()
+                        .setLabel("Save")
+                        .setEmoji(getEmojiByName("ICONS.SAVE", "id"))
+                        .setStyle(ButtonStyle.Success)
+                        .setCustomId("save"),
+                    new ButtonBuilder()
+                        .setLabel("Edit name")
+                        .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
+                        .setStyle(ButtonStyle.Primary)
+                        .setCustomId("editName"),
+                    new ButtonBuilder()
+                        .setLabel(newChannelEnabled ? "Enabled" : "Disabled")
+                        .setEmoji(getEmojiByName(newChannelEnabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id"))
+                        .setStyle(ButtonStyle.Secondary)
+                        .setCustomId("toggleEnabled")
                 )
             ]
         });
-        let i: StringSelectMenuInteraction;
+        let i: ButtonInteraction | ChannelSelectMenuInteraction;
         try {
-            i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            }) as StringSelectMenuInteraction;
+            i = await m.awaitMessageComponent({ time: 300000, filter: (i) => {
+                return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id;
+            }}) as ButtonInteraction | ChannelSelectMenuInteraction;
         } catch (e) {
-            timedOut = true;
+            closed = true;
+            cancelled = true;
+            break;
+        }
+        if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    await i.deferUpdate();
+                    closed = true;
+                    break;
+                }
+                case "save": {
+                    await i.deferUpdate();
+                    if (newChannel) {
+                        currentObject[newChannel] = {
+                            name: newChannelName,
+                            enabled: newChannelEnabled
+                        }
+                    }
+                    closed = true;
+                    break;
+                }
+                case "editName": {
+                    await interaction.editReply({
+                        embeds: [new EmojiEmbed()
+                                    .setTitle("Stats Channel")
+                                    .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                    .setStatus("Success")
+                                    .setEmoji("SETTINGS.STATS.GREEN")
+                        ],
+                        components: [
+                            new ActionRowBuilder<ButtonBuilder>().addComponents(
+                                new ButtonBuilder()
+                                    .setLabel("Back")
+                                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                    .setStyle(ButtonStyle.Primary)
+                                    .setCustomId("back")
+                            )
+                        ]
+                    });
+                    showModal(i, {name: newChannelName, enabled: newChannelEnabled})
+
+                    const out: Discord.ModalSubmitInteraction | ButtonInteraction| null = await modalInteractionCollector(m, interaction.user);
+                    if (!out) continue;
+                    if (out.isButton()) continue;
+                    newChannelName = out.fields.getTextInputValue("text");
+                    break;
+                }
+                case "toggleEnabled": {
+                    await i.deferUpdate();
+                    newChannelEnabled = !newChannelEnabled;
+                    break;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            if (i.customId === "channel") {
+                newChannel = i.values[0];
+            }
+        }
+    } while (!closed)
+    if (cancelled) return originalObject;
+    if (!(newChannel && newChannelName && newChannelEnabled)) return originalObject;
+    return currentObject;
+}
+const callback = async (interaction: CommandInteraction) => {
+    if (!interaction.guild) return;
+    const { renderChannel } = client.logger;
+    const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
+    let page = 0;
+    let closed = false;
+    const config = await client.database.guilds.read(interaction.guild.id);
+    let currentObject: ObjectSchema = config.stats;
+    let modified = false;
+    do {
+        const embed = new EmojiEmbed()
+            .setTitle("Stats Settings")
+            .setEmoji("SETTINGS.STATS.GREEN")
+            .setStatus("Success");
+        const noStatsChannels = Object.keys(currentObject).length === 0;
+        let current: { enabled: boolean; name: string; };
+
+        const pageSelect = new StringSelectMenuBuilder()
+            .setCustomId("page")
+            .setPlaceholder("Select a stats channel to manage");
+        const actionSelect = new StringSelectMenuBuilder()
+            .setCustomId("action")
+            .setPlaceholder("Perform an action")
+            .addOptions(
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Edit")
+                    .setDescription("Edit the stats channel")
+                    .setValue("edit")
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Delete")
+                    .setDescription("Delete the stats channel")
+                    .setValue("delete")
+                    .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
+        );
+        const buttonRow = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+                    .setDisabled(page === 0),
+                new ButtonBuilder()
+                    .setCustomId("next")
+                    .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Primary)
+                    .setDisabled(page === Object.keys(currentObject).length - 1),
+                new ButtonBuilder()
+                    .setCustomId("add")
+                    .setLabel("Create new")
+                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(Object.keys(currentObject).length >= 24),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(modified),
+            );
+        if (noStatsChannels) {
+            embed.setDescription("No stats channels have been set up yet. Use the button below to add one.\n\n" +
+                createPageIndicator(1, 1, undefined, true)
+            );
+            pageSelect.setDisabled(true);
+            actionSelect.setDisabled(true);
+            pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+                .setLabel("No stats channels")
+                .setValue("none")
+            );
+        } else {
+            page = Math.min(page, Object.keys(currentObject).length - 1);
+            current = currentObject[Object.keys(config.stats)[page]!]!
+            actionSelect.addOptions(new StringSelectMenuOptionBuilder()
+                .setLabel(current.enabled ? "Disable" : "Enable")
+                .setValue("toggleEnabled")
+                .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`)
+                .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
+            );
+            embed.setDescription(`**Currently Editing:** ${renderChannel(Object.keys(currentObject)[page]!)}\n\n` +
+                `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` +
+                `**Name:** \`${current.name}\`\n` +
+                `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` + '\n\n' +
+                createPageIndicator(Object.keys(config.stats).length, page)
+            );
+            for (const [id, { name, enabled }] of Object.entries(currentObject)) {
+                pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+                    .setLabel(`${name} (${renderChannel(id)})`)
+                    .setEmoji(getEmojiByName(enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
+                    .setDescription(`${enabled ? "Enabled" : "Disabled"}`)
+                    .setValue(id)
+                );
+            }
+        }
+
+        interaction.editReply({embeds: [embed], components: [
+            new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
+            new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
+            buttonRow
+        ]});
+
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({ filter: (interaction) => interaction.user.id === interaction.user.id, time: 60000 }) as StringSelectMenuInteraction | ButtonInteraction;
+        } catch (e) {
+            closed = true;
             continue;
         }
-        i.deferUpdate();
-        if (i.customId === "remove") {
-            const toRemove = i.values;
-            await client.database.guilds.write(
-                interaction.guild!.id,
-                null,
-                toRemove.map((k) => `stats.${k}`)
-            );
+
+        if(i.isStringSelectMenu()) {
+            switch(i.customId) {
+                case "page": {
+                    await i.deferUpdate();
+                    page = Object.keys(currentObject).indexOf(i.values[0]!);
+                    break;
+                }
+                case "action": {
+                    modified = true;
+                    switch(i.values[0]!) {
+                        case "edit": {
+                            showModal(i, current!)
+                            await interaction.editReply({
+                                embeds: [
+                                    new EmojiEmbed()
+                                        .setTitle("Stats Channel")
+                                        .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                        .setStatus("Success")
+                                        .setEmoji("SETTINGS.STATS.GREEN")
+                                ],
+                                components: [
+                                    new ActionRowBuilder<ButtonBuilder>().addComponents(
+                                        new ButtonBuilder()
+                                            .setLabel("Back")
+                                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                            .setStyle(ButtonStyle.Primary)
+                                            .setCustomId("back")
+                                    )
+                                ]
+                            });
+                            let out: Discord.ModalSubmitInteraction | null;
+                            try {
+                                out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
+                            } catch (e) {
+                                continue;
+                            }
+                            if (!out) continue
+                            if (out.isButton()) continue;
+                            currentObject[Object.keys(currentObject)[page]!]!.name = out.fields.getTextInputValue("text");
+                            break;
+                        }
+                        case "toggleEnabled": {
+                            await i.deferUpdate();
+                            currentObject[Object.keys(currentObject)[page]!]!.enabled = !currentObject[Object.keys(currentObject)[page]!]!.enabled;
+                            modified = true;
+                            break;
+                        }
+                        case "delete": {
+                            await i.deferUpdate();
+                            currentObject = Object.fromEntries(Object.entries(currentObject).filter(([k]) => k !== Object.keys(currentObject)[page]!));
+                            page = Math.min(page, Object.keys(currentObject).length - 1);
+                            modified = true;
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            switch(i.customId) {
+                case "back": {
+                    page--;
+                    break;
+                }
+                case "next": {
+                    page++;
+                    break;
+                }
+                case "add": {
+                    currentObject = await addStatsChannel(interaction, m, currentObject);
+                    page = Object.keys(currentObject).length - 1;
+                    break;
+                }
+                case "save": {
+                    client.database.guilds.write(interaction.guild.id, {stats: currentObject});
+                    singleNotify("statsChannelDeleted", interaction.guild.id, true);
+                    modified = false;
+                    break;
+                }
+            }
         }
-    }
-    await interaction.editReply({
-        embeds: [new Discord.EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })],
-        components: []
-    });
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageChannels"))
         return "You must have the *Manage Channels* permission to use this command";
     return true;
 };
 
-const generateStatsChannelAutocomplete = (prompt: string): string[] => {
-    return [prompt];
-};
-
-const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
-    if (!interaction.guild) return [];
-    const prompt = interaction.options.getString("tag");
-    // generateStatsChannelAutocomplete(int.options.getString("name") ?? "")
-    const results = generateStatsChannelAutocomplete(prompt ?? "");
-    return results;
-};
-
-
 
 export { command };
 export { callback };
-export { check };
-export { autocomplete };
\ No newline at end of file
+export { check };
\ No newline at end of file
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index 892a420..2e046bf 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -1,68 +1,38 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
 import Discord, {
     CommandInteraction,
-    GuildChannel,
     Message,
     ActionRowBuilder,
     ButtonBuilder,
-    MessageComponentInteraction,
     StringSelectMenuBuilder,
-    Role,
-    StringSelectMenuInteraction,
     ButtonStyle,
     TextInputBuilder,
     ButtonComponent,
-    StringSelectMenuComponent,
     ModalSubmitInteraction,
-    APIMessageComponentEmoji
+    APIMessageComponentEmoji,
+    RoleSelectMenuBuilder,
+    ChannelSelectMenuBuilder,
+    RoleSelectMenuInteraction,
+    ButtonInteraction,
+    ChannelSelectMenuInteraction,
+    TextInputStyle,
+    ModalBuilder,
+    ChannelType
 } from "discord.js";
-import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders";
-import { ChannelType } from "discord-api-types/v9";
+import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "discord.js";
 import client from "../../utils/client.js";
 import { toHexInteger, toHexArray, tickets as ticketTypes } from "../../utils/calculate.js";
 import { capitalize } from "../../utils/generateKeyValueList.js";
 import { modalInteractionCollector } from "../../utils/dualCollector.js";
 import type { GuildConfig } from "../../utils/database.js";
+import { LinkWarningFooter } from "../../utils/defaults.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("tickets")
-        .setDescription("Shows settings for tickets | Use no arguments to manage custom types")
-        .addStringOption((option) =>
-            option
-                .setName("enabled")
-                .setDescription("If users should be able to create tickets")
-                .setRequired(false)
-                .addChoices(
-                    {name: "Yes", value: "yes"},
-                    {name: "No",value:  "no"}
-                )
-        )
-        .addChannelOption((option) =>
-            option
-                .setName("category")
-                .setDescription("The category where tickets are created")
-                .addChannelTypes(ChannelType.GuildCategory)
-                .setRequired(false)
-        )
-        .addNumberOption((option) =>
-            option
-                .setName("maxticketsperuser")
-                .setDescription("The maximum amount of tickets a user can create | Default: 5")
-                .setRequired(false)
-                .setMinValue(1)
-        )
-        .addRoleOption((option) =>
-            option
-                .setName("supportrole")
-                .setDescription(
-                    "This role will have view access to all tickets and will be pinged when a ticket is created"
-                )
-                .setRequired(false)
-        );
+        .setDescription("Shows settings for tickets")
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
     if (!interaction.guild) return;
@@ -71,392 +41,130 @@
         ephemeral: true,
         fetchReply: true
     })) as Message;
-    const options = {
-        enabled: (interaction.options.get("enabled")?.value as string).startsWith("yes") as boolean | null,
-        category: interaction.options.get("category")?.channel as Discord.CategoryChannel | null,
-        maxtickets: interaction.options.get("maxticketsperuser")?.value as number | null,
-        supportping: interaction.options.get("supportrole")?.role as Role | null
-    };
-    if (options.enabled !== null || options.category || options.maxtickets || options.supportping) {
-        if (options.category) {
-            let channel: GuildChannel | null;
-            try {
-                channel = await interaction.guild.channels.fetch(options.category.id) as GuildChannel;
-            } catch {
-                return await interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                            .setTitle("Tickets > Category")
-                            .setDescription("The channel you provided is not a valid category")
-                            .setStatus("Danger")
-                    ]
-                });
-            }
-            channel = channel as Discord.CategoryChannel;
-            if (channel.guild.id !== interaction.guild.id)
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Tickets > Category")
-                            .setDescription("You must choose a category in this server")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ]
-                });
-        }
-        if (options.maxtickets) {
-            if (options.maxtickets < 1)
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Tickets > Max Tickets")
-                            .setDescription("You must choose a number greater than 0")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ]
-                });
-        }
-        let role: Role | null;
-        if (options.supportping) {
-            try {
-                role = await interaction.guild.roles.fetch(options.supportping.id);
-            } catch {
-                return await interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setEmoji("GUILD.ROLE.DELETE")
-                            .setTitle("Tickets > Support Ping")
-                            .setDescription("The role you provided is not a valid role")
-                            .setStatus("Danger")
-                    ]
-                });
-            }
-            if (!role) return;
-            role = role as Discord.Role;
-            if (role.guild.id !== interaction.guild.id)
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Tickets > Support Ping")
-                            .setDescription("You must choose a role in this server")
-                            .setStatus("Danger")
-                            .setEmoji("GUILD.ROLE.DELETE")
-                    ]
-                });
-        }
-
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("GUILD.TICKET.ARCHIVED")
-            .setTitle("Tickets")
-            .setDescription(
-                (options.category ? `**Category:** ${options.category.name}\n` : "") +
-                    (options.maxtickets ? `**Max Tickets:** ${options.maxtickets}\n` : "") +
-                    (options.supportping ? `**Support Ping:** ${options.supportping.name}\n` : "") +
-                    (options.enabled !== null
-                        ? `**Enabled:** ${
-                            options.enabled
-                                ? `${getEmojiByName("CONTROL.TICK")} Yes`
-                                : `${getEmojiByName("CONTROL.CROSS")} No`
-                        }\n`
-                        : "") +
-                    "\nAre you sure you want to apply these settings?"
-            )
-            .setColor("Warning")
-            .setFailedMessage("Cancelled", "Warning", "GUILD.TICKET.CLOSE") // TODO: Set Actual Message
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            const toUpdate: Record<string, string | boolean | number> = {};
-            if (options.enabled !== null) toUpdate["tickets.enabled"] = options.enabled;
-            if (options.category) toUpdate["tickets.category"] = options.category.id;
-            if (options.maxtickets) toUpdate["tickets.maxTickets"] = options.maxtickets;
-            if (options.supportping) toUpdate["tickets.supportRole"] = options.supportping.id;
-            try {
-                await client.database.guilds.write(interaction.guild.id, toUpdate);
-            } catch (e) {
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Tickets")
-                            .setDescription("Something went wrong and the staff notifications channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("GUILD.TICKET.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Tickets")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("GUILD.TICKET.OPEN")
-                ],
-                components: []
-            });
-        }
-    }
     const data = await client.database.guilds.read(interaction.guild.id);
     data.tickets.customTypes = (data.tickets.customTypes ?? []).filter(
         (value: string, index: number, array: string[]) => array.indexOf(value) === index
     );
-    let lastClicked = "";
-    const embed: EmojiEmbed = new EmojiEmbed();
-    const compiledData = {
-        enabled: data.tickets.enabled,
-        category: data.tickets.category,
-        maxTickets: data.tickets.maxTickets,
-        supportRole: data.tickets.supportRole,
-        useCustom: data.tickets.useCustom,
-        types: data.tickets.types,
-        customTypes: data.tickets.customTypes as string[] | null
-    };
+    let ticketData = (await client.database.guilds.read(interaction.guild.id)).tickets
+    let changesMade = false;
     let timedOut = false;
+    let errorMessage = "";
     while (!timedOut) {
-            embed
+        const embed: EmojiEmbed = new EmojiEmbed()
             .setTitle("Tickets")
             .setDescription(
-                `${compiledData.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${
-                    compiledData.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
+                `${ticketData.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${
+                    ticketData.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
                 }\n` +
-                    `${compiledData.category ? "" : getEmojiByName("TICKETS.REPORT")} **Category:** ${
-                        compiledData.category ? `<#${compiledData.category}>` : "*None set*"
-                    }\n` +
-                    `**Max Tickets:** ${compiledData.maxTickets ? compiledData.maxTickets : "*No limit*"}\n` +
-                    `**Support Ping:** ${compiledData.supportRole ? `<@&${compiledData.supportRole}>` : "*None set*"}\n\n` +
-                    (compiledData.useCustom && compiledData.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") +
-                    `${compiledData.useCustom ? "Custom" : "Default"} types in use` +
+                    `${ticketData.category ? "" : getEmojiByName("TICKETS.REPORT")}` +
+                    ((await interaction.guild.channels.fetch(ticketData.category!))!.type === ChannelType.GuildCategory ?
+                    `**Category:** ` : `**Channel:** `) +  // TODO: Notify if permissions are wrong
+                    `${ticketData.category ? `<#${ticketData.category}>` : "*None set*"}\n` +
+                    `**Max Tickets:** ${ticketData.maxTickets ? ticketData.maxTickets : "*No limit*"}\n` +
+                    `**Support Ping:** ${ticketData.supportRole ? `<@&${ticketData.supportRole}>` : "*None set*"}\n\n` +
+                    (ticketData.useCustom && ticketData.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") +
+                    `${ticketData.useCustom ? "Custom" : "Default"} types in use` +
                     "\n\n" +
                     `${getEmojiByName("TICKETS.REPORT")} *Indicates a setting stopping tickets from being used*`
             )
             .setStatus("Success")
             .setEmoji("GUILD.TICKET.OPEN");
+        if (errorMessage) embed.setFooter({text: errorMessage, iconURL: LinkWarningFooter.iconURL});
         m = (await interaction.editReply({
             embeds: [embed],
             components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
+                new ActionRowBuilder<ButtonBuilder>().addComponents(
                     new ButtonBuilder()
-                        .setLabel("Tickets " + (compiledData.enabled ? "enabled" : "disabled"))
-                        .setEmoji(getEmojiByName("CONTROL." + (compiledData.enabled ? "TICK" : "CROSS"), "id"))
-                        .setStyle(compiledData.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                        .setLabel("Tickets " + (ticketData.enabled ? "enabled" : "disabled"))
+                        .setEmoji(getEmojiByName("CONTROL." + (ticketData.enabled ? "TICK" : "CROSS"), "id"))
+                        .setStyle(ticketData.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
                         .setCustomId("enabled"),
                     new ButtonBuilder()
-                        .setLabel(lastClicked === "cat" ? "Click again to confirm" : "Clear category")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setCustomId("clearCategory")
-                        .setDisabled(compiledData.category === null),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked === "max" ? "Click again to confirm" : "Reset max tickets")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setCustomId("clearMaxTickets")
-                        .setDisabled(compiledData.maxTickets === 5),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked === "sup" ? "Click again to confirm" : "Clear support ping")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setCustomId("clearSupportPing")
-                        .setDisabled(compiledData.supportRole === null)
-                ]),
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
+                        .setLabel("Set max tickets")
+                        .setEmoji(getEmojiByName("CONTROL.TICKET", "id"))
+                        .setStyle(ButtonStyle.Primary)
+                        .setCustomId("setMaxTickets")
+                        .setDisabled(!ticketData.enabled),
                     new ButtonBuilder()
                         .setLabel("Manage types")
                         .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
                         .setStyle(ButtonStyle.Secondary)
-                        .setCustomId("manageTypes"),
+                        .setCustomId("manageTypes")
+                        .setDisabled(!ticketData.enabled),
                     new ButtonBuilder()
-                        .setLabel("Add create ticket button")
-                        .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
-                        .setStyle(ButtonStyle.Primary)
-                        .setCustomId("send")
-                ])
+                        .setLabel("Save")
+                        .setEmoji(getEmojiByName("ICONS.SAVE", "id"))
+                        .setStyle(ButtonStyle.Success)
+                        .setCustomId("save")
+                        .setDisabled(!changesMade)
+                ),
+                new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(
+                    new RoleSelectMenuBuilder()
+                        .setCustomId("supportRole")
+                        .setPlaceholder("Select a support role")
+                        .setDisabled(!ticketData.enabled)
+                ),
+                new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
+                    new ChannelSelectMenuBuilder()
+                        .setCustomId("category")
+                        .setPlaceholder("Select a category or channel")
+                        .setDisabled(!ticketData.enabled)
+                )
             ]
-        })) as Message;
-        let i: MessageComponentInteraction;
+        }));
+        let i: RoleSelectMenuInteraction | ButtonInteraction | ChannelSelectMenuInteraction;
         try {
-            i = await m.awaitMessageComponent({
+            i = await m.awaitMessageComponent<2 | 6 | 8>({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
-        if ((i.component as ButtonComponent).customId === "clearCategory") {
-            if (lastClicked === "cat") {
-                lastClicked = "";
-                await client.database.guilds.write(interaction.guild.id, null, ["tickets.category"]);
-                compiledData.category = null;
-            } else lastClicked = "cat";
-        } else if ((i.component as ButtonComponent).customId === "clearMaxTickets") {
-            if (lastClicked === "max") {
-                lastClicked = "";
-                await client.database.guilds.write(interaction.guild.id, null, ["tickets.maxTickets"]);
-                compiledData.maxTickets = 5;
-            } else lastClicked = "max";
-        } else if ((i.component as ButtonComponent).customId === "clearSupportPing") {
-            if (lastClicked === "sup") {
-                lastClicked = "";
-                await client.database.guilds.write(interaction.guild.id, null, ["tickets.supportRole"]);
-                compiledData.supportRole = null;
-            } else lastClicked = "sup";
-        } else if ((i.component as ButtonComponent).customId === "send") {
-            const ticketMessages = [
-                {
-                    label: "Create ticket",
-                    description: "Click the button below to create a ticket"
-                },
-                {
-                    label: "Issues, questions or feedback?",
-                    description: "Click below to open a ticket and get help from our staff team"
-                },
-                {
-                    label: "Contact Us",
-                    description: "Click the button below to speak to us privately"
+        changesMade = true;
+        if (i.isRoleSelectMenu()) {
+            await i.deferUpdate();
+            ticketData.supportRole = i.values[0] ?? null;
+        } else if (i.isChannelSelectMenu()) {
+            await i.deferUpdate();
+            ticketData.category = i.values[0] ?? null;
+        } else {
+            switch(i.customId) {
+                case "save": {
+                    await i.deferUpdate();
+                    await client.database.guilds.write(interaction.guild.id, { tickets: ticketData });
+                    changesMade = false;
+                    break;
                 }
-            ];
-            let innerTimedOut = false;
-            let templateSelected = false;
-            while (!innerTimedOut && !templateSelected) {
-                const enabled = compiledData.enabled && compiledData.category !== null;
-                await interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Ticket Button")
-                            .setDescription("Select a message template to send in this channel")
-                            .setFooter({
-                                text: enabled
-                                    ? ""
-                                    : "Tickets are not set up correctly so the button may not work for users. Check the main menu to find which options must be set."
-                            })
-                            .setStatus(enabled ? "Success" : "Warning")
-                            .setEmoji("GUILD.ROLES.CREATE")
-                    ],
-                    components: [
-                        new ActionRowBuilder<StringSelectMenuBuilder>().addComponents([
-                            new StringSelectMenuBuilder()
-                                .setOptions(
-                                    ticketMessages.map(
-                                        (
-                                            t: {
-                                                label: string;
-                                                description: string;
-                                                value?: string;
-                                            },
-                                            index
-                                        ) => {
-                                            t.value = index.toString();
-                                            return t as {
-                                                value: string;
-                                                label: string;
-                                                description: string;
-                                            };
-                                        }
-                                    )
-                                )
-                                .setCustomId("template")
-                                .setMaxValues(1)
-                                .setMinValues(1)
-                                .setPlaceholder("Select a message template")
-                        ]),
-                        new ActionRowBuilder<ButtonBuilder>().addComponents([
-                            new ButtonBuilder()
-                                .setCustomId("back")
-                                .setLabel("Back")
-                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
-                                .setStyle(ButtonStyle.Danger),
-                            new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary),
-                            new ButtonBuilder()
-                                .setCustomId("custom")
-                                .setLabel("Custom")
-                                .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
-                                .setStyle(ButtonStyle.Primary)
-                        ])
-                    ]
-                });
-                let i: MessageComponentInteraction;
-                try {
-                    i = await m.awaitMessageComponent({
-                        time: 300000,
-                        filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-                    });
-                } catch (e) {
-                    innerTimedOut = true;
-                    continue;
+                case "enabled": {
+                    await i.deferUpdate();
+                    ticketData.enabled = !ticketData.enabled;
+                    break;
                 }
-                if ((i.component as StringSelectMenuComponent).customId === "template") {
-                    i.deferUpdate();
-                    await interaction.channel!.send({
-                        embeds: [
-                            new EmojiEmbed()
-                                .setTitle(ticketMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.label)
-                                .setDescription(
-                                    ticketMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.description
-                                )
-                                .setStatus("Success")
-                                .setEmoji("GUILD.TICKET.OPEN")
-                        ],
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Create Ticket")
-                                    .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("createticket")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
-                    continue;
-                } else if ((i.component as ButtonComponent).customId === "blank") {
-                    i.deferUpdate();
-                    await interaction.channel!.send({
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Create Ticket")
-                                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("createticket")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
-                    continue;
-                } else if ((i.component as ButtonComponent).customId === "custom") {
+                case "setMaxTickets": {
                     await i.showModal(
-                        new Discord.ModalBuilder()
-                            .setCustomId("modal")
-                            .setTitle("Enter embed details")
+                        new ModalBuilder()
+                            .setCustomId("maxTickets")
+                            .setTitle("Set max tickets")
                             .addComponents(
-                                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                new ActionRowBuilder<TextInputBuilder>().setComponents(
                                     new TextInputBuilder()
-                                        .setCustomId("title")
-                                        .setLabel("Title")
-                                        .setMaxLength(256)
-                                        .setRequired(true)
-                                        .setStyle(Discord.TextInputStyle.Short)
-                                ),
-                                new ActionRowBuilder<TextInputBuilder>().addComponents(
-                                    new TextInputBuilder()
-                                        .setCustomId("description")
-                                        .setLabel("Description")
-                                        .setMaxLength(4000)
-                                        .setRequired(true)
-                                        .setStyle(Discord.TextInputStyle.Paragraph)
+                                        .setLabel("Max tickets - Leave blank for no limit")
+                                        .setCustomId("maxTickets")
+                                        .setPlaceholder("Enter a number")
+                                        .setRequired(false)
+                                        .setValue(ticketData.maxTickets.toString())
+                                        .setMinLength(1)
+                                        .setMaxLength(3)
+                                        .setStyle(TextInputStyle.Short)
                                 )
                             )
-                    );
-                    await interaction.editReply({
+                    )
+                    await i.editReply({
                         embeds: [
                             new EmojiEmbed()
-                                .setTitle("Ticket Button")
+                                .setTitle("Tickets")
                                 .setDescription("Modal opened. If you can't see it, click back and try again.")
                                 .setStatus("Success")
                                 .setEmoji("GUILD.TICKET.OPEN")
@@ -473,54 +181,33 @@
                     });
                     let out;
                     try {
-                        out = await modalInteractionCollector(
-                            m,
-                            (m) => m.channel!.id === interaction.channel!.id,
-                            (m) => m.customId === "modify"
-                        );
+                        out = await modalInteractionCollector(m, interaction.user);
                     } catch (e) {
-                        innerTimedOut = true;
                         continue;
                     }
+                    if (!out || out.isButton()) continue;
                     out = out as ModalSubmitInteraction;
-                    const title = out.fields.getTextInputValue("title");
-                    const description = out.fields.getTextInputValue("description");
-                    await interaction.channel!.send({
-                        embeds: [
-                            new EmojiEmbed()
-                                .setTitle(title)
-                                .setDescription(description)
-                                .setStatus("Success")
-                                .setEmoji("GUILD.TICKET.OPEN")
-                        ],
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Create Ticket")
-                                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("createticket")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
+                    const toAdd = out.fields.getTextInputValue("maxTickets");
+                    if(isNaN(parseInt(toAdd))) {
+                        errorMessage = "You entered an invalid number - No changes were made";
+                        break;
+                    }
+                    ticketData.maxTickets = toAdd === "" ? 0 : parseInt(toAdd);
+                    break;
+                }
+                case "manageTypes": {
+                    await i.deferUpdate();
+                    ticketData = await manageTypes(interaction, data.tickets, m);
+                    break;
                 }
             }
-        } else if ((i.component as ButtonComponent).customId === "enabled") {
-            await client.database.guilds.write(interaction.guild.id, {
-                "tickets.enabled": !compiledData.enabled
-            });
-            compiledData.enabled = !compiledData.enabled;
-        } else if ((i.component as ButtonComponent).customId === "manageTypes") {
-            data.tickets = await manageTypes(interaction, data.tickets, m as Message);
         }
     }
-    await interaction.editReply({
-        embeds: [ embed.setFooter({ text: "Message timed out" })],
-        components: []
-    });
+    await interaction.deleteReply()
 };
 
+
+
 async function manageTypes(interaction: CommandInteraction, data: GuildConfig["tickets"], m: Message) {
     let timedOut = false;
     let backPressed = false;
@@ -545,7 +232,7 @@
                         .setStatus("Success")
                         .setEmoji("GUILD.TICKET.OPEN")
                 ],
-                components: (customTypes
+                components: (customTypes && customTypes.length > 0
                     ? [
                           new ActionRowBuilder<StringSelectMenuBuilder | ButtonBuilder>().addComponents([
                               new Discord.StringSelectMenuBuilder()
@@ -637,29 +324,23 @@
         try {
             i = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             timedOut = true;
             continue;
         }
-        if ((i.component as StringSelectMenuComponent).customId === "types") {
-            i.deferUpdate();
-            const types = toHexInteger((i as StringSelectMenuInteraction).values, ticketTypes);
-            await client.database.guilds.write(interaction.guild!.id, {
-                "tickets.types": types
-            });
+        if (i.isStringSelectMenu() && i.customId === "types") {
+            await i.deferUpdate();
+            const types = toHexInteger(i.values, ticketTypes);
             data.types = types;
-        } else if ((i.component as StringSelectMenuComponent).customId === "removeTypes") {
-            i.deferUpdate();
-            const types = (i as StringSelectMenuInteraction).values;
+        } else if (i.isStringSelectMenu() && i.customId === "removeTypes") {
+            await i.deferUpdate();
+            const types = i.values;
             let customTypes = data.customTypes;
             if (customTypes) {
                 customTypes = customTypes.filter((t) => !types.includes(t));
                 customTypes = customTypes.length > 0 ? customTypes : null;
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "tickets.customTypes": customTypes
-                });
                 data.customTypes = customTypes;
             }
         } else if ((i.component as ButtonComponent).customId === "addType") {
@@ -680,7 +361,7 @@
                         )
                     )
             );
-            await interaction.editReply({
+            await i.editReply({
                 embeds: [
                     new EmojiEmbed()
                         .setTitle("Tickets > Types")
@@ -700,14 +381,11 @@
             });
             let out;
             try {
-                out = await modalInteractionCollector(
-                    m,
-                    (m) => m.channel!.id === interaction.channel!.id,
-                    (m) => m.customId === "addType"
-                );
+                out = await modalInteractionCollector(m, interaction.user);
             } catch (e) {
                 continue;
             }
+            if (!out || out.isButton()) continue;
             out = out as ModalSubmitInteraction;
             let toAdd = out.fields.getTextInputValue("type");
             if (!toAdd) {
@@ -715,31 +393,31 @@
             }
             toAdd = toAdd.substring(0, 80);
             try {
-                await client.database.guilds.append(interaction.guild!.id, "tickets.customTypes", toAdd);
+                if(!data.customTypes) data.customTypes = [];
+                data.customTypes.push(toAdd);
             } catch {
                 continue;
             }
-            data.customTypes = data.customTypes ?? [];
             if (!data.customTypes.includes(toAdd)) {
                 data.customTypes.push(toAdd);
             }
         } else if ((i.component as ButtonComponent).customId === "switchToDefault") {
-            i.deferUpdate();
+            await i.deferUpdate();
             await client.database.guilds.write(interaction.guild!.id, { "tickets.useCustom": false }, []);
             data.useCustom = false;
         } else if ((i.component as ButtonComponent).customId === "switchToCustom") {
-            i.deferUpdate();
+            await i.deferUpdate();
             await client.database.guilds.write(interaction.guild!.id, { "tickets.useCustom": true }, []);
             data.useCustom = true;
         } else {
-            i.deferUpdate();
+            await i.deferUpdate();
             backPressed = true;
         }
     }
     return data;
 }
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts
new file mode 100644
index 0000000..d9d485d
--- /dev/null
+++ b/src/commands/settings/tracks.ts
@@ -0,0 +1,459 @@
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, GuildMember, Message, ModalBuilder, ModalSubmitInteraction, PermissionsBitField, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
+import client from "../../utils/client.js";
+import createPageIndicator, { createVerticalTrack } from "../../utils/createPageIndicator.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import ellipsis from "../../utils/ellipsis.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
+
+const { renderRole } = client.logger
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+        .setName("tracks")
+        .setDescription("Manage the tracks for the server")
+
+interface ObjectSchema {
+    name: string;
+    retainPrevious: boolean;
+    nullable: boolean;
+    track: string[];
+    manageableBy: string[];
+}
+
+
+const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, current?: string) => {
+
+    let name = current ?? "";
+    const modal = new ModalBuilder()
+        .setTitle("Edit Name and Description")
+        .setCustomId("editNameDescription")
+        .addComponents(
+            new ActionRowBuilder<TextInputBuilder>()
+                .addComponents(
+                    new TextInputBuilder()
+                        .setLabel("Name")
+                        .setCustomId("name")
+                        .setPlaceholder("The name of the track (e.g. Moderators)")
+                        .setStyle(TextInputStyle.Short)
+                        .setValue(name)
+                        .setRequired(true)
+                )
+        )
+    const button = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+        )
+
+    await i.showModal(modal)
+    await interaction.editReply({
+        embeds: [
+            new EmojiEmbed()
+                .setTitle("Tracks")
+                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                .setStatus("Success")
+        ],
+        components: [button]
+    });
+
+    let out: ModalSubmitInteraction | null;
+    try {
+        out = await modalInteractionCollector(m, interaction.user) as ModalSubmitInteraction | null;
+    } catch (e) {
+        console.error(e);
+        out = null;
+    }
+    if(!out) return name;
+    if (out.isButton()) return name;
+    name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
+    return name
+
+}
+
+const reorderTracks = async (interaction: ButtonInteraction, m: Message, roles: Collection<string, Role>, currentObj: string[]) => {
+    const reorderRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+        .addComponents(
+            new StringSelectMenuBuilder()
+                .setCustomId("reorder")
+                .setPlaceholder("Select all roles in the order you want users to gain them (Lowest to highest rank).")
+                .setMinValues(currentObj.length)
+                .setMaxValues(currentObj.length)
+                .addOptions(
+                    currentObj.map((o, i) => new StringSelectMenuOptionBuilder()
+                        .setLabel(roles.get(o)!.name)
+                        .setValue(i.toString())
+                    )
+                )
+        );
+    const buttonRow = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+        )
+    await interaction.editReply({
+        embeds: [
+            new EmojiEmbed()
+                .setTitle("Tracks")
+                .setDescription("Select all roles in the order you want users to gain them (Lowest to highest rank).")
+                .setStatus("Success")
+        ],
+        components: [reorderRow, buttonRow]
+    });
+    let out: StringSelectMenuInteraction | ButtonInteraction | null;
+    try {
+        out = await m.awaitMessageComponent({
+            filter: (i) => i.channel!.id === interaction.channel!.id,
+            time: 300000
+        }) as StringSelectMenuInteraction | ButtonInteraction | null;
+    } catch (e) {
+        console.error(e);
+        out = null;
+    }
+    if(!out) return;
+    out.deferUpdate();
+    if (out.isButton()) return;
+    const values = out.values;
+
+    const newOrder: string[] = currentObj.map((_, i) => {
+        const index = values.findIndex(v => v === i.toString());
+        return currentObj[index];
+    }) as string[];
+
+    return newOrder;
+}
+
+const editTrack = async (interaction: ButtonInteraction | StringSelectMenuInteraction, message: Message, roles: Collection<string, Role>, current?: ObjectSchema) => {
+    const isAdmin = (interaction.member!.permissions as PermissionsBitField).has("Administrator");
+    if(!current) {
+        current = {
+            name: "",
+            retainPrevious: false,
+            nullable: false,
+            track: [],
+            manageableBy: []
+        }
+    }
+
+    const roleSelect = new ActionRowBuilder<RoleSelectMenuBuilder>()
+        .addComponents(
+            new RoleSelectMenuBuilder()
+                .setCustomId("addRole")
+                .setPlaceholder("Select a role to add")
+                .setDisabled(!isAdmin)
+        );
+    let closed = false;
+    do {
+        const editableRoles: string[] = current.track.map((r) => {
+            if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position) || interaction.user.id === interaction.guild?.ownerId) return roles.get(r)!.name;
+        }).filter(v => v !== undefined) as string[];
+        const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("removeRole")
+                    .setPlaceholder("Select a role to remove")
+                    .setDisabled(!isAdmin)
+                    .addOptions(
+                        editableRoles.map((r, i) => {
+                            return new StringSelectMenuOptionBuilder()
+                            .setLabel(r)
+                            .setValue(i.toString())}
+                        )
+                    )
+            );
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setLabel("Back")
+                    .setStyle(ButtonStyle.Secondary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("edit")
+                    .setLabel("Edit Name")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("reorder")
+                    .setLabel("Reorder")
+                    .setDisabled(!isAdmin)
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("retainPrevious")
+                    .setLabel("Retain Previous")
+                    .setStyle(current.retainPrevious ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"), "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("nullable")
+                    .setLabel(`Role ${current.nullable ? "Not " : ""}Required`)
+                    .setStyle(current.nullable ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL." + (current.nullable ? "TICK" : "CROSS"), "id") as APIMessageComponentEmoji)
+        );
+
+        const allowed: boolean[] = [];
+        for (const role of current.track) {
+            const disabled: boolean =
+                roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position;
+            allowed.push(disabled)
+        }
+        const mapped = current.track.map(role => roles.find(aRole => aRole.id === role)!);
+
+        const embed = new EmojiEmbed()
+            .setTitle("Tracks")
+            .setDescription(
+                `**Currently Editing:** ${current.name}\n\n` +
+                `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${current.nullable ? "don't " : ""}need a role in this track\n` +
+                `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${current.retainPrevious ? "" : "don't "}keep all roles below their current highest\n\n` +
+                createVerticalTrack(
+                    mapped.map(role => renderRole(role)), new Array(current.track.length).fill(false), allowed)
+            )
+            .setStatus("Success")
+
+        const comps: ActionRowBuilder<RoleSelectMenuBuilder | ButtonBuilder | StringSelectMenuBuilder>[] = [roleSelect, buttons];
+        if(current.track.length >= 1) comps.splice(1, 0, selectMenu);
+
+        interaction.editReply({embeds: [embed], components: comps});
+
+        let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
+
+        try {
+            out = await message.awaitMessageComponent({
+                filter: (i) => i.channel!.id === interaction.channel!.id,
+                time: 300000
+            }) as ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
+        } catch (e) {
+            console.error(e);
+            out = null;
+        }
+
+        if(!out) return;
+        if (out.isButton()) {
+            switch(out.customId) {
+                case "back": {
+                    out.deferUpdate();
+                    closed = true;
+                    break;
+                }
+                case "edit": {
+                    current.name = (await editName(out, interaction, message, current.name))!;
+                    break;
+                }
+                case "reorder": {
+                    out.deferUpdate();
+                    current.track = (await reorderTracks(out, message, roles, current.track))!;
+                    break;
+                }
+                case "retainPrevious": {
+                    out.deferUpdate();
+                    current.retainPrevious = !current.retainPrevious;
+                    break;
+                }
+                case "nullable": {
+                    out.deferUpdate();
+                    current.nullable = !current.nullable;
+                    break;
+                }
+            }
+        } else if (out.isStringSelectMenu()) {
+            out.deferUpdate();
+            switch(out.customId) {
+                case "removeRole": {
+                    const index = current.track.findIndex(v => v === editableRoles[parseInt((out! as StringSelectMenuInteraction).values![0]!)]);
+                    current.track.splice(index, 1);
+                    break;
+                }
+            }
+        } else {
+            switch(out.customId) {
+                case "addRole": {
+                    const role = out.values![0]!;
+                    if(!current.track.includes(role)) {
+                        current.track.push(role);
+                    } else {
+                        out.reply({content: "That role is already on this track", ephemeral: true})
+                    }
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+    return current;
+}
+
+const callback = async (interaction: CommandInteraction) => {
+
+    const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true})
+    const config = await client.database.guilds.read(interaction.guild!.id);
+    const tracks: ObjectSchema[] = config.tracks;
+    const roles = await interaction.guild!.roles.fetch();
+
+    let page = 0;
+    let closed = false;
+    let modified = false;
+
+    do {
+        const embed = new EmojiEmbed()
+            .setTitle("Track Settings")
+            .setEmoji("TRACKS.ICON")
+            .setStatus("Success");
+        const noTracks = config.tracks.length === 0;
+        let current: ObjectSchema;
+
+        const pageSelect = new StringSelectMenuBuilder()
+            .setCustomId("page")
+            .setPlaceholder("Select a track to manage");
+        const actionSelect = new StringSelectMenuBuilder()
+            .setCustomId("action")
+            .setPlaceholder("Perform an action")
+            .addOptions(
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Edit")
+                    .setDescription("Edit this track")
+                    .setValue("edit")
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Delete")
+                    .setDescription("Delete this track")
+                    .setValue("delete")
+                    .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
+        );
+        const buttonRow = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+                    .setDisabled(page === 0),
+                new ButtonBuilder()
+                    .setCustomId("next")
+                    .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Primary)
+                    .setDisabled(page === tracks.length - 1),
+                new ButtonBuilder()
+                    .setCustomId("add")
+                    .setLabel("New Track")
+                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(Object.keys(tracks).length >= 24),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(!modified),
+            );
+        if(noTracks) {
+            embed.setDescription("No tracks have been set up yet. Use the button below to add one.\n\n" +
+                createPageIndicator(1, 1, undefined, true)
+            );
+            pageSelect.setDisabled(true);
+            actionSelect.setDisabled(true);
+            pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+                .setLabel("No tracks")
+                .setValue("none")
+            );
+        } else {
+            page = Math.min(page, Object.keys(tracks).length - 1);
+            current = tracks[page]!;
+            const mapped = current.track.map(role => roles.find(aRole => aRole.id === role)!);
+            embed.setDescription(`**Currently Editing:** ${current.name}\n\n` +
+                `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${current.nullable ? "don't " : ""}need a role in this track\n` +
+                `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${current.retainPrevious ? "" : "don't "}keep all roles below their current highest\n\n` +
+                createVerticalTrack(mapped.map(role => renderRole(role)), new Array(current.track.length).fill(false)) +
+                `\n${createPageIndicator(config.tracks.length, page)}`
+            );
+
+            pageSelect.addOptions(
+                tracks.map((key: ObjectSchema, index) => {
+                    return new StringSelectMenuOptionBuilder()
+                        .setLabel(ellipsis(key.name, 50))
+                        .setDescription(ellipsis(roles.get(key.track[0]!)?.name!, 50))
+                        .setValue(index.toString());
+                })
+            );
+
+        }
+
+        await interaction.editReply({embeds: [embed], components: [new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect), new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect), buttonRow]});
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+
+        await i.deferUpdate();
+        if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    page--;
+                    break;
+                }
+                case "next": {
+                    page++;
+                    break;
+                }
+                case "add": {
+                    const newPage = await editTrack(i, m, roles)
+                    if(!newPage) break;
+                    tracks.push();
+                    page = tracks.length - 1;
+                    break;
+                }
+                case "save": {
+                    client.database.guilds.write(interaction.guild!.id, {tracks: tracks});
+                    modified = false;
+                    break;
+                }
+            }
+        } else if (i.isStringSelectMenu()) {
+            switch (i.customId) {
+                case "action": {
+                    switch(i.values[0]) {
+                        case "edit": {
+                            const edited = await editTrack(i, m, roles, current!);
+                            if(!edited) break;
+                            tracks[page] = edited;
+                            modified = true;
+                            break;
+                        }
+                        case "delete": {
+                            if(page === 0 && tracks.keys.length - 1 > 0) page++;
+                            else page--;
+                            tracks.splice(page, 1);
+                            break;
+                        }
+                    }
+                    break;
+                }
+                case "page": {
+                    page = parseInt(i.values[0]!);
+                    break;
+                }
+            }
+        }
+
+    } while (!closed);
+    await interaction.deleteReply()
+}
+
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as GuildMember;
+    if (!member.permissions.has("ManageRoles"))
+        return "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/verify.ts b/src/commands/settings/verify.ts
index 0f9f4a0..c440b75 100644
--- a/src/commands/settings/verify.ts
+++ b/src/commands/settings/verify.ts
@@ -1,35 +1,25 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
 import Discord, {
     CommandInteraction,
-    Interaction,
     Message,
     ActionRowBuilder,
     ButtonBuilder,
-    MessageComponentInteraction,
-    ModalSubmitInteraction,
-    Role,
     ButtonStyle,
-    StringSelectMenuBuilder,
-    StringSelectMenuComponent,
-    TextInputBuilder,
-    EmbedBuilder,
-    StringSelectMenuInteraction,
-    ButtonComponent
+    RoleSelectMenuBuilder,
+    APIMessageComponentEmoji
 } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../utils/client.js";
-import { modalInteractionCollector } from "../../utils/dualCollector.js";
+import { getCommandMentionByName } from "../../utils/getCommandDataByName.js";
+import lodash from "lodash";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("verify")
-        .setDescription("Manage the role given after typing /verify")
-        .addRoleOption((option) =>
-            option.setName("role").setDescription("The role to give after verifying").setRequired(false)
-        );
+        .setDescription("Manage the role given after a user runs /verify")
+
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
     if (!interaction.guild) return;
@@ -38,356 +28,82 @@
         ephemeral: true,
         fetchReply: true
     })) as Message;
-    if (interaction.options.get("role")?.role) {
-        let role: Role;
-        try {
-            role = interaction.options.get("role")?.role as Role;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("GUILD.ROLES.DELETE")
-                        .setTitle("Verify Role")
-                        .setDescription("The role you provided is not a valid role")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        role = role as Discord.Role;
-        if (role.guild.id !== interaction.guild.id) {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Verify Role")
-                        .setDescription("You must choose a role in this server")
-                        .setStatus("Danger")
-                        .setEmoji("GUILD.ROLES.DELETE")
-                ]
-            });
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("GUILD.ROLES.EDIT")
-            .setTitle("Verify Role")
-            .setDescription(`Are you sure you want to set the verify role to <@&${role.id}>?`)
-            .setColor("Warning")
-            .setFailedMessage("No changes were made", "Warning", "GUILD.ROLES.DELETE")
-            .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
-                });
-                const { log, NucleusColors, entry, renderUser, renderRole } = client.logger;
-                const data = {
-                    meta: {
-                        type: "verifyRoleChanged",
-                        displayName: "Verify Role Changed",
-                        calculateType: "nucleusSettingsUpdated",
-                        color: NucleusColors.green,
-                        emoji: "CONTROL.BLOCKTICK",
-                        timestamp: new Date().getTime()
-                    },
-                    list: {
-                        memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                        changedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                        role: entry(role.id, renderRole(role))
-                    },
-                    hidden: {
-                        guild: interaction.guild.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                console.log(e);
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Verify Role")
-                            .setDescription("Something went wrong while setting the verify role")
-                            .setStatus("Danger")
-                            .setEmoji("GUILD.ROLES.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Verify Role")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("GUILD.ROLES.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let clicks = 0;
-    const data = await client.database.guilds.read(interaction.guild.id);
-    let role = data.verify.role;
 
-    let timedOut = false;
-    while (!timedOut) {
+    let closed = false;
+    let config = await client.database.guilds.read(interaction.guild.id);
+    let data = Object.assign({}, config.verify);
+    do {
+        const selectMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
+        .addComponents(
+            new RoleSelectMenuBuilder()
+                .setCustomId("role")
+                .setPlaceholder("Select a role")
+        );
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("switch")
+                    .setLabel(data.enabled ? "Enabled" : "Disabled")
+                    .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setDisabled(lodash.isEqual(config.verify, data))
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Verify Role")
+            .setDescription(
+                `Select a role to be given to users after they run ${getCommandMentionByName("verify")}` +
+                `\n\nCurrent role: ${config.verify.role ? `<@&${config.verify.role}>` : "None"}`
+            )
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE");
+
         await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Verify Role")
-                    .setDescription(
-                        role ? `Your verify role is currently set to <@&${role}>` : "You have not set a verify role"
-                    )
-                    .setStatus("Success")
-                    .setEmoji("GUILD.ROLES.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setCustomId("clear")
-                        .setLabel(clicks ? "Click again to confirm" : "Reset role")
-                        .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!role),
-                    new ButtonBuilder()
-                        .setCustomId("send")
-                        .setLabel("Add verify button")
-                        .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
-                        .setStyle(ButtonStyle.Primary)
-                ])
-            ]
+            embeds: [embed],
+            components: [selectMenu, buttons]
         });
-        let i: MessageComponentInteraction;
+
+        let i;
         try {
             i = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id }
             });
         } catch (e) {
-            timedOut = true;
+            closed = true;
             continue;
         }
-        i.deferUpdate();
-        if ((i.component as ButtonComponent).customId === "clear") {
-            clicks += 1;
-            if (clicks === 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"]);
-                role = null;
-            }
-        } else if ((i.component as ButtonComponent).customId === "send") {
-            const verifyMessages = [
-                {
-                    label: "Verify",
-                    description: "Click the button below to get verified"
-                },
-                {
-                    label: "Get verified",
-                    description: "To get access to the rest of the server, click the button below"
-                },
-                {
-                    label: "Ready to verify?",
-                    description: "Click the button below to verify yourself"
+
+        await i.deferUpdate();
+
+        if(i.isButton()) {
+            switch (i.customId) {
+                case "save": {
+                    client.database.guilds.write(interaction.guild.id, {"verify": data} )
+                    config = await client.database.guilds.read(interaction.guild.id);
+                    data = Object.assign({}, config.verify);
+                    break
                 }
-            ];
-            let innerTimedOut = false;
-            let templateSelected = false;
-            while (!innerTimedOut && !templateSelected) {
-                await interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Verify Button")
-                            .setDescription("Select a message template to send in this channel")
-                            .setFooter({
-                                text: role ? "" : "You do no have a verify role set so the button will not work."
-                            })
-                            .setStatus(role ? "Success" : "Warning")
-                            .setEmoji("GUILD.ROLES.CREATE")
-                    ],
-                    components: [
-                        new ActionRowBuilder<StringSelectMenuBuilder>().addComponents([
-                            new StringSelectMenuBuilder()
-                                .setOptions(
-                                    verifyMessages.map(
-                                        (
-                                            t: {
-                                                label: string;
-                                                description: string;
-                                                value?: string;
-                                            },
-                                            index
-                                        ) => {
-                                            t.value = index.toString();
-                                            return t as {
-                                                value: string;
-                                                label: string;
-                                                description: string;
-                                            };
-                                        }
-                                    )
-                                )
-                                .setCustomId("template")
-                                .setMaxValues(1)
-                                .setMinValues(1)
-                                .setPlaceholder("Select a message template")
-                        ]),
-                        new ActionRowBuilder<ButtonBuilder>().addComponents([
-                            new ButtonBuilder()
-                                .setCustomId("back")
-                                .setLabel("Back")
-                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
-                                .setStyle(ButtonStyle.Danger),
-                            new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary),
-                            new ButtonBuilder()
-                                .setCustomId("custom")
-                                .setLabel("Custom")
-                                .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
-                                .setStyle(ButtonStyle.Primary)
-                        ])
-                    ]
-                });
-                let i: MessageComponentInteraction;
-                try {
-                    i = await m.awaitMessageComponent({
-                        time: 300000,
-                        filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-                    });
-                } catch (e) {
-                    innerTimedOut = true;
-                    continue;
-                }
-                if ((i.component as StringSelectMenuComponent).customId === "template") {
-                    i.deferUpdate();
-                    await interaction.channel!.send({
-                        embeds: [
-                            new EmojiEmbed()
-                                .setTitle(verifyMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.label)
-                                .setDescription(
-                                    verifyMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.description
-                                )
-                                .setStatus("Success")
-                                .setEmoji("CONTROL.BLOCKTICK")
-                        ],
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Verify")
-                                    .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("verifybutton")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
-                    continue;
-                } else if ((i.component as ButtonComponent).customId === "blank") {
-                    i.deferUpdate();
-                    await interaction.channel!.send({
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Verify")
-                                    .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("verifybutton")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
-                    continue;
-                } else if ((i.component as ButtonComponent).customId === "custom") {
-                    await i.showModal(
-                        new Discord.ModalBuilder()
-                            .setCustomId("modal")
-                            .setTitle("Enter embed details")
-                            .addComponents(
-                                new ActionRowBuilder<TextInputBuilder>().addComponents(
-                                    new TextInputBuilder()
-                                        .setCustomId("title")
-                                        .setLabel("Title")
-                                        .setMaxLength(256)
-                                        .setRequired(true)
-                                        .setStyle(Discord.TextInputStyle.Short)
-                                ),
-                                new ActionRowBuilder<TextInputBuilder>().addComponents(
-                                    new TextInputBuilder()
-                                        .setCustomId("description")
-                                        .setLabel("Description")
-                                        .setMaxLength(4000)
-                                        .setRequired(true)
-                                        .setStyle(Discord.TextInputStyle.Paragraph)
-                                )
-                            )
-                    );
-                    await interaction.editReply({
-                        embeds: [
-                            new EmojiEmbed()
-                                .setTitle("Verify Button")
-                                .setDescription("Modal opened. If you can't see it, click back and try again.")
-                                .setStatus("Success")
-                                .setEmoji("GUILD.TICKET.OPEN")
-                        ],
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Back")
-                                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
-                                    .setStyle(ButtonStyle.Primary)
-                                    .setCustomId("back")
-                            ])
-                        ]
-                    });
-                    let out;
-                    try {
-                        out = await modalInteractionCollector(
-                            m,
-                            (m: Interaction) =>
-                                (m as MessageComponentInteraction | ModalSubmitInteraction).channelId ===
-                                interaction.channelId,
-                            (m) => m.customId === "modify"
-                        );
-                    } catch (e) {
-                        innerTimedOut = true;
-                        continue;
-                    }
-                    if (out !== null && out instanceof ModalSubmitInteraction) {
-                        const title = out.fields.getTextInputValue("title");
-                        const description = out.fields.getTextInputValue("description");
-                        await interaction.channel!.send({
-                            embeds: [
-                                new EmojiEmbed()
-                                    .setTitle(title)
-                                    .setDescription(description)
-                                    .setStatus("Success")
-                                    .setEmoji("CONTROL.BLOCKTICK")
-                            ],
-                            components: [
-                                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                    new ButtonBuilder()
-                                        .setLabel("Verify")
-                                        .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
-                                        .setStyle(ButtonStyle.Success)
-                                        .setCustomId("verifybutton")
-                                ])
-                            ]
-                        });
-                        templateSelected = true;
-                    }
+                case "switch": {
+                    data.enabled = !data.enabled;
+                    break
                 }
             }
         } else {
-            i.deferUpdate();
-            break;
+            data.role = i.values[0]!;
         }
-    }
-    await interaction.editReply({
-        embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message closed" })],
-        components: []
-    });
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts
index e7143fb..7584624 100644
--- a/src/commands/settings/welcome.ts
+++ b/src/commands/settings/welcome.ts
@@ -1,307 +1,263 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
 import Discord, {
-    Channel,
     CommandInteraction,
-    Message,
+    AutocompleteInteraction,
     ActionRowBuilder,
     ButtonBuilder,
-    MessageComponentInteraction,
-    Role,
     ButtonStyle,
-    AutocompleteInteraction,
-    GuildChannel,
-    EmbedBuilder
+    APIMessageComponentEmoji,
+    ChannelSelectMenuBuilder,
+    RoleSelectMenuBuilder,
+    RoleSelectMenuInteraction,
+    ChannelSelectMenuInteraction,
+    ButtonInteraction,
+    ModalBuilder,
+    TextInputBuilder,
+    TextInputStyle,
+    ModalSubmitInteraction,
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import generateKeyValueList from "../../utils/generateKeyValueList.js";
-import { ChannelType } from "discord-api-types/v9";
 import getEmojiByName from "../../utils/getEmojiByName.js";
+import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("welcome")
         .setDescription("Messages and roles sent or given when someone joins the server")
-        .addStringOption((option) =>
-            option
-                .setName("message")
-                .setDescription("The message to send when someone joins the server")
-                .setAutocomplete(true)
-        )
-        .addRoleOption((option) =>
-            option.setName("role").setDescription("The role given when someone joins the server")
-        )
-        .addRoleOption((option) =>
-            option.setName("ping").setDescription("The role pinged when someone joins the server")
-        )
-        .addChannelOption((option) =>
-            option
-                .setName("channel")
-                .setDescription("The channel the welcome message should be sent to")
-                .addChannelTypes(ChannelType.GuildText)
-        );
 
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const { renderRole, renderChannel, log, NucleusColors, entry, renderUser } = client.logger;
-    await interaction.reply({
+const callback = async (interaction: CommandInteraction): Promise<void> => {
+    const { renderChannel } = client.logger;
+    const m = await interaction.reply({
         embeds: LoadingEmbed,
         fetchReply: true,
         ephemeral: true
     });
-    let m: Message;
-    if (
-        interaction.options.get("role")?.role ||
-        interaction.options.get("channel")?.channel ||
-        interaction.options.get("message")?.value as string
-    ) {
-        let role: Role | null;
-        let ping: Role | null;
-        let channel: Channel | null;
-        const message: string | null = interaction.options.get("message")?.value as string | null;
-        try {
-            role = interaction.options.get("role")?.role as Role | null;
-            ping = interaction.options.get("ping")?.role as Role | null;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("GUILD.ROLES.DELETE")
-                        .setTitle("Welcome Events")
-                        .setDescription("The role you provided is not a valid role")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        try {
-            channel = interaction.options.get("channel")?.channel as Channel | null;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("GUILD.ROLES.DELETE")
-                        .setTitle("Welcome Events")
-                        .setDescription("The channel you provided is not a valid channel")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        const options: {
-            role?: string;
-            ping?: string;
-            channel?: string;
-            message?: string;
-        } = {};
-
-        if (role) options.role = renderRole(role);
-        if (ping) options.ping = renderRole(ping);
-        if (channel) options.channel = renderChannel(channel as GuildChannel);
-        if (message) options.message = "\n> " + message;
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("GUILD.ROLES.EDIT")
-            .setTitle("Welcome Events")
-            .setDescription(generateKeyValueList(options))
-            .setColor("Warning")
-            .setFailedMessage("Cancelled", "Warning", "GUILD.ROLES.DELETE") //TODO: Actual Message Needed
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                const toChange: {
-                    "welcome.role"?: string;
-                    "welcome.ping"?: string;
-                    "welcome.channel"?: string;
-                    "welcome.message"?: string;
-                } = {};
-                if (role) toChange["welcome.role"] = role.id;
-                if (ping) toChange["welcome.ping"] = ping.id;
-                if (channel) toChange["welcome.channel"] = channel.id;
-                if (message) toChange["welcome.message"] = message;
-                await client.database.guilds.write(interaction.guild!.id, toChange);
-                const list: {
-                    memberId: ReturnType<typeof entry>;
-                    changedBy: ReturnType<typeof entry>;
-                    role?: ReturnType<typeof entry>;
-                    ping?: ReturnType<typeof entry>;
-                    channel?: ReturnType<typeof entry>;
-                    message?: ReturnType<typeof entry>;
-                } = {
-                    memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                    changedBy: entry(interaction.user.id, renderUser(interaction.user))
-                };
-                if (role) list.role = entry(role.id, renderRole(role));
-                if (ping) list.ping = entry(ping.id, renderRole(ping));
-                if (channel) list.channel = entry(channel.id, renderChannel(channel as GuildChannel));
-                if (message) list.message = entry(message, `\`${message}\``);
-                const data = {
-                    meta: {
-                        type: "welcomeSettingsUpdated",
-                        displayName: "Welcome Settings Changed",
-                        calculateType: "nucleusSettingsUpdated",
-                        color: NucleusColors.green,
-                        emoji: "CONTROL.BLOCKTICK",
-                        timestamp: new Date().getTime()
-                    },
-                    list: list,
-                    hidden: {
-                        guild: interaction.guild!.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                console.log(e);
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Welcome Events")
-                            .setDescription("Something went wrong while updating welcome settings")
-                            .setStatus("Danger")
-                            .setEmoji("GUILD.ROLES.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Welcome Events")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("GUILD.ROLES.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let lastClicked = null;
-    let timedOut = false;
+    let closed = false;
+    let config = await client.database.guilds.read(interaction.guild!.id);
+    let data = Object.assign({}, config.welcome);
     do {
-        const config = await client.database.guilds.read(interaction.guild!.id);
-        m = (await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Welcome Events")
-                    .setDescription(
-                        `**Message:** ${config.welcome.message ? `\n> ${config.welcome.message}` : "*None set*"}\n` +
-                            `**Role:** ${
-                                config.welcome.role
-                                    ? renderRole((await interaction.guild!.roles.fetch(config.welcome.role))!)
-                                    : "*None set*"
-                            }\n` +
-                            `**Ping:** ${
-                                config.welcome.ping
-                                    ? renderRole((await interaction.guild!.roles.fetch(config.welcome.ping))!)
-                                    : "*None set*"
-                            }\n` +
-                            `**Channel:** ${
-                                config.welcome.channel
-                                    ? config.welcome.channel == "dm"
-                                        ? "DM"
-                                        : renderChannel((await interaction.guild!.channels.fetch(config.welcome.channel))!)
-                                    : "*None set*"
-                            }`
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("switch")
+                    .setLabel(data.enabled ? "Enabled" : "Disabled")
+                    .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("message")
+                    .setLabel((data.message ? "Change" : "Set") + "Message")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("channelDM")
+                    .setLabel("Send in DMs")
+                    .setStyle(ButtonStyle.Primary)
+                    .setDisabled(data.channel === "dm"),
+                new ButtonBuilder()
+                    .setCustomId("role")
+                    .setLabel("Clear Role")
+                    .setStyle(ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setDisabled(
+                        data.enabled === config.welcome.enabled &&
+                        data.message === config.welcome.message &&
+                        data.role === config.welcome.role &&
+                        data.ping === config.welcome.ping &&
+                        data.channel === config.welcome.channel
                     )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setLabel(lastClicked == "clear-message" ? "Click again to confirm" : "Clear Message")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clear-message")
-                        .setDisabled(!config.welcome.message)
-                        .setStyle(ButtonStyle.Danger),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked == "clear-role" ? "Click again to confirm" : "Clear Role")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clear-role")
-                        .setDisabled(!config.welcome.role)
-                        .setStyle(ButtonStyle.Danger),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked == "clear-ping" ? "Click again to confirm" : "Clear Ping")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clear-ping")
-                        .setDisabled(!config.welcome.ping)
-                        .setStyle(ButtonStyle.Danger),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked == "clear-channel" ? "Click again to confirm" : "Clear Channel")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clear-channel")
-                        .setDisabled(!config.welcome.channel)
-                        .setStyle(ButtonStyle.Danger),
-                    new ButtonBuilder()
-                        .setLabel("Set Channel to DM")
-                        .setCustomId("set-channel-dm")
-                        .setDisabled(config.welcome.channel == "dm")
-                        .setStyle(ButtonStyle.Secondary)
-                ])
-            ]
-        })) as Message;
-        let i: MessageComponentInteraction;
+            );
+
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel to send welcome messages to")
+            );
+        const roleMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
+            .addComponents(
+                new RoleSelectMenuBuilder()
+                    .setCustomId("roleToGive")
+                    .setPlaceholder("Select a role to give to the member when they join the server")
+            );
+        const pingMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
+            .addComponents(
+                new RoleSelectMenuBuilder()
+                    .setCustomId("roleToPing")
+                    .setPlaceholder("Select a role to ping when a member joins the server")
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Welcome Settings")
+            .setStatus("Success")
+            .setDescription(
+                `${getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Welcome messages and roles are ${data.enabled ? "enabled" : "disabled"}\n` +
+                `**Welcome message:** ${data.message ?
+                    `\n> ` +
+                    await convertCurlyBracketString(
+                        data.message,
+                        interaction.user.id,
+                        interaction.user.username,
+                        interaction.guild!.name,
+                        interaction.guild!.members
+                    )
+                    : "*None*"}\n` +
+                `**Send message in:** ` + (data.channel ? (data.channel == "dm" ? "DMs" : renderChannel(data.channel)) : `*None set*`) + `\n` +
+                `**Role to ping:** ` + (data.ping ? `<@&${data.ping}>` : `*None set*`) + `\n` +
+                `**Role given on join:** ` + (data.role ? `<@&${data.role}>` : `*None set*`)
+            )
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [buttons, channelMenu, roleMenu, pingMenu]
+        });
+
+        let i: RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction;
         try {
             i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            });
+                filter: (interaction) => interaction.user.id === interaction.user.id,
+                time: 300000
+            }) as RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction;
         } catch (e) {
-            timedOut = true;
+            closed = true;
             continue;
         }
-        i.deferUpdate();
-        if (i.customId == "clear-message") {
-            if (lastClicked == "clear-message") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "welcome.message": null
-                });
-                lastClicked = null;
-            } else {
-                lastClicked = "clear-message";
+
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "switch": {
+                    await i.deferUpdate();
+                    data.enabled = !data.enabled;
+                    break;
+                }
+                case "message": {
+                    const modal = new ModalBuilder()
+                        .setCustomId("modal")
+                        .setTitle("Welcome Message")
+                        .addComponents(
+                            new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                new TextInputBuilder()
+                                    .setCustomId("ex1")
+                                    .setLabel("Server Info (1/3)")
+                                    .setPlaceholder(
+                                        `{serverName} - This server's name\n\n` +
+                                        `These placeholders will be replaced with the server's name, etc..`
+                                    )
+                                    .setMaxLength(1)
+                                    .setRequired(false)
+                                    .setStyle(TextInputStyle.Paragraph)
+                            ),
+                            new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                new TextInputBuilder()
+                                    .setCustomId("ex2")
+                                    .setLabel("Member Counts (2/3) - {MemberCount:...}")
+                                    .setPlaceholder(
+                                        `{:all} - Total member count\n` +
+                                        `{:humans} - Total non-bot users\n` +
+                                        `{:bots} - Number of bots\n`
+                                    )
+                                    .setMaxLength(1)
+                                    .setRequired(false)
+                                    .setStyle(TextInputStyle.Paragraph)
+                            ),
+                            new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                new TextInputBuilder()
+                                    .setCustomId("ex3")
+                                    .setLabel("Member who joined (3/3) - {member:...}")
+                                    .setPlaceholder(
+                                            `{:name} - The members name\n`
+                                    )
+                                    .setMaxLength(1)
+                                    .setRequired(false)
+                                    .setStyle(TextInputStyle.Paragraph)
+                            ),
+                            new ActionRowBuilder<TextInputBuilder>()
+                                .addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("message")
+                                        .setPlaceholder("Enter a message to send when someone joins the server")
+                                        .setValue(data.message ?? "")
+                                        .setLabel("Message")
+                                        .setStyle(TextInputStyle.Paragraph)
+                                )
+                        )
+                    const button = new ActionRowBuilder<ButtonBuilder>()
+                        .addComponents(
+                            new ButtonBuilder()
+                                .setCustomId("back")
+                                .setLabel("Back")
+                                .setStyle(ButtonStyle.Secondary)
+                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+                        )
+                    await i.showModal(modal)
+                    await i.editReply({
+                        embeds: [
+                            new EmojiEmbed()
+                                .setTitle("Welcome Settings")
+                                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                .setStatus("Success")
+                        ],
+                        components: [button]
+                    });
+
+                    let out: ModalSubmitInteraction | null;
+                    try {
+                        out = await modalInteractionCollector(m, interaction.user) as ModalSubmitInteraction | null;
+                    } catch (e) {
+                        console.error(e);
+                        out = null;
+                    }
+                    if(!out) break;
+                    data.message = out.fields.getTextInputValue("message");
+                    break;
+                }
+                case "save": {
+                    await i.deferUpdate();
+                    await client.database.guilds.write(interaction.guild!.id, {"welcome": data});
+                    config = await client.database.guilds.read(interaction.guild!.id);
+                    data = Object.assign({}, config.welcome);
+                    break;
+                }
+                case "channelDM": {
+                    await i.deferUpdate();
+                    data.channel = "dm";
+                    break;
+                }
+                case "role": {
+                    await i.deferUpdate();
+                    data.role = null;
+                    break;
+                }
             }
-        } else if (i.customId == "clear-role") {
-            if (lastClicked == "clear-role") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "welcome.role": null
-                });
-                lastClicked = null;
-            } else {
-                lastClicked = "clear-role";
+        } else if (i.isRoleSelectMenu()) {
+            await i.deferUpdate();
+            switch(i.customId) {
+                case "roleToGive": {
+                    data.role = i.values[0]!;
+                    break
+                }
+                case "roleToPing": {
+                    data.ping = i.values[0]!;
+                    break
+                }
             }
-        } else if (i.customId == "clear-ping") {
-            if (lastClicked == "clear-ping") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "welcome.ping": null
-                });
-                lastClicked = null;
-            } else {
-                lastClicked = "clear-ping";
-            }
-        } else if (i.customId == "clear-channel") {
-            if (lastClicked == "clear-channel") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "welcome.channel": null
-                });
-                lastClicked = null;
-            } else {
-                lastClicked = "clear-channel";
-            }
-        } else if (i.customId == "set-channel-dm") {
-            await client.database.guilds.write(interaction.guild!.id, {
-                "welcome.channel": "dm"
-            });
-            lastClicked = null;
+        } else {
+            await i.deferUpdate();
+            data.channel = i.values[0]!;
         }
-    } while (!timedOut);
-    await interaction.editReply({
-        embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })],
-        components: []
-    });
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
@@ -309,7 +265,7 @@
 };
 
 const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
-    const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
+    const validReplacements = ["serverName", "memberCount:all", "memberCount:bots", "memberCount:humans"]
     if (!interaction.guild) return [];
     const prompt = interaction.options.getString("message");
     const autocompletions = [];
@@ -324,7 +280,7 @@
     if (beforeLastOpenBracket !== null) {
         if (afterLastOpenBracket !== null) {
             for (const replacement of validReplacements) {
-                if (replacement.startsWith(afterLastOpenBracket[0].slice(1))) {
+                if (replacement.startsWith(afterLastOpenBracket[0]!.slice(1))) {
                     autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
                 }
             }
@@ -341,4 +297,4 @@
     return autocompletions;
 };
 
-export { command, callback, check, autocomplete };
+export { command, callback, check, autocomplete };
\ No newline at end of file
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index a65947c..6ffecca 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -1,5 +1,4 @@
-import { AutocompleteInteraction, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import { AutocompleteInteraction, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from "discord.js";
 import client from "../utils/client.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import { capitalize } from "../utils/generateKeyValueList.js";
@@ -51,10 +50,6 @@
     return;
 };
 
-const check = () => {
-    return true;
-};
-
 const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
     if (!interaction.guild) return [];
     const prompt = interaction.options.getString("tag");
@@ -65,5 +60,4 @@
 
 export { command };
 export { callback };
-export { check };
 export { autocomplete };
diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts
index 788902e..1a1f695 100644
--- a/src/commands/tags/create.ts
+++ b/src/commands/tags/create.ts
@@ -1,6 +1,6 @@
 import type Discord from "discord.js";
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
@@ -106,7 +106,7 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageMessages"))
         return "You must have the *Manage Messages* permission to use this command";
diff --git a/src/commands/tags/delete.ts b/src/commands/tags/delete.ts
index 18143d3..4fdb10f 100644
--- a/src/commands/tags/delete.ts
+++ b/src/commands/tags/delete.ts
@@ -1,6 +1,6 @@
 import type Discord from "discord.js";
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
@@ -68,7 +68,7 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageMessages"))
         return "You must have the *Manage Messages* permission to use this command";
diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts
index e15f9ac..7e297c8 100644
--- a/src/commands/tags/edit.ts
+++ b/src/commands/tags/edit.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction, GuildMember } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
@@ -19,9 +19,9 @@
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
     if (!interaction.guild) return;
-    const name = interaction.options.get("name")?.value as string;
-    const value = interaction.options.get("value")?.value as string;
-    const newname = interaction.options.get("newname")?.value as string;
+    const name = (interaction.options.get("name")?.value ?? "") as string;
+    const value = (interaction.options.get("value")?.value ?? "") as string;
+    const newname = (interaction.options.get("newname")?.value ?? "") as string;
     if (!newname && !value)
         return await interaction.reply({
             embeds: [
@@ -126,7 +126,7 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as GuildMember;
     if (!member.permissions.has("ManageMessages"))
         return "You must have the *Manage Messages* permission to use this command";
diff --git a/src/commands/tags/list.ts b/src/commands/tags/list.ts
index f0563c7..dbb1200 100644
--- a/src/commands/tags/list.ts
+++ b/src/commands/tags/list.ts
@@ -10,7 +10,7 @@
     ButtonComponent,
     StringSelectMenuBuilder
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
@@ -139,13 +139,13 @@
         try {
             i = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
+        await i.deferUpdate();
         if ((i.component as ButtonComponent).customId === "left") {
             if (page > 0) page--;
             selectPaneOpen = false;
@@ -173,10 +173,6 @@
     });
 };
 
-const check = () => {
-    return true;
-};
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/ticket/close.ts b/src/commands/ticket/close.ts
index d2ffaf9..ff9da8b 100644
--- a/src/commands/ticket/close.ts
+++ b/src/commands/ticket/close.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import close from "../../actions/tickets/delete.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("close").setDescription("Closes a ticket");
@@ -8,10 +8,5 @@
     await close(interaction);
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/ticket/create.ts b/src/commands/ticket/create.ts
index 91442b5..2f3ddc6 100644
--- a/src/commands/ticket/create.ts
+++ b/src/commands/ticket/create.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import create from "../../actions/tickets/create.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -14,10 +14,6 @@
     await create(interaction);
 };
 
-const check = () => {
-    return true;
-};
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts
index e43ecb7..0eb8580 100644
--- a/src/commands/user/about.ts
+++ b/src/commands/user/about.ts
@@ -10,7 +10,7 @@
     APISelectMenuOption,
     StringSelectMenuBuilder
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import generateKeyValueList from "../../utils/generateKeyValueList.js";
@@ -173,11 +173,8 @@
                         generateKeyValueList({
                             member: renderUser(member.user),
                             id: `\`${member.id}\``,
-                            roles: `${member.roles.cache.size - 1}`  // FIXME
-                        }) +
-                            "\n" +
-                            (s.length > 0 ? s : "*None*") +
-                            "\n"
+                            roles: `${member.roles.cache.size - 1}`
+                        }) + "\n" + (s.length > 0 ? s : "*None*") + "\n"
                     )
             )
             .setTitle("Roles")
@@ -258,13 +255,13 @@
         try {
             i = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch {
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
+        await i.deferUpdate();
         if (i.customId === "left") {
             if (page > 0) page--;
             selectPaneOpen = false;
@@ -286,11 +283,6 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
 export { userAbout };
\ No newline at end of file
diff --git a/src/commands/user/avatar.ts b/src/commands/user/avatar.ts
index 88b3270..da33f51 100644
--- a/src/commands/user/avatar.ts
+++ b/src/commands/user/avatar.ts
@@ -1,6 +1,6 @@
 import type { CommandInteraction } from "discord.js";
 import type Discord from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import generateKeyValueList from "../../utils/generateKeyValueList.js";
 import client from "../../utils/client.js";
@@ -35,10 +35,6 @@
     });
 };
 
-const check = () => {
-    return true;
-};
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/user/role.ts b/src/commands/user/role.ts
new file mode 100644
index 0000000..41820ac
--- /dev/null
+++ b/src/commands/user/role.ts
@@ -0,0 +1,160 @@
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, GuildMember, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, UserSelectMenuBuilder, UserSelectMenuInteraction } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
+import client from "../../utils/client.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import listToAndMore from "../../utils/listToAndMore.js"
+
+const { renderUser } = client.logger;
+
+const canEdit = (role: Role, member: GuildMember, me: GuildMember): [string, boolean] => {
+    if(role.position >= me.roles.highest.position ||
+       role.position >= member.roles.highest.position
+    ) return [`~~<@&${role.id}>~~`, false];
+    return [`<@&${role.id}>`, true];
+};
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+        .setName("role")
+        .setDescription("Gives or removes a role from someone")
+        .addUserOption((option) => option.setName("user").setDescription("The user to give or remove the role from"))
+
+const callback = async (interaction: CommandInteraction): Promise<unknown> => {
+    const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true });
+
+    let member = interaction.options.getMember("user") as GuildMember | null;
+
+    if(!member) {
+        const memberEmbed = new EmojiEmbed()
+            .setTitle("Role")
+            .setDescription(`Please choose a member to edit the roles of.`)
+            .setEmoji("GUILD.ROLES.CREATE")
+            .setStatus("Success");
+        const memberChooser = new ActionRowBuilder<UserSelectMenuBuilder>().addComponents(
+            new UserSelectMenuBuilder()
+                .setCustomId("memberChooser")
+                .setPlaceholder("Select a member")
+        );
+        await interaction.editReply({embeds: [memberEmbed], components: [memberChooser]});
+
+        const filter = (i: UserSelectMenuInteraction) => i.customId === "memberChooser" && i.user.id === interaction.user.id;
+
+        let i: UserSelectMenuInteraction | null;
+        try {
+            i = await m.awaitMessageComponent<5>({ filter, time: 300000});
+        } catch (e) {
+            return;
+        }
+
+        memberEmbed.setDescription(`Editing roles for ${renderUser(i.values[0]!)}`);
+        await i.deferUpdate();
+        await interaction.editReply({ embeds: LoadingEmbed, components: [] })
+        member = await interaction.guild?.members.fetch(i.values[0]!)!;
+
+    }
+
+    let closed = false;
+    let rolesToChange: string[] = [];
+    const roleAdd = new ActionRowBuilder<RoleSelectMenuBuilder>()
+        .addComponents(
+            new RoleSelectMenuBuilder()
+                .setCustomId("roleAdd")
+                .setPlaceholder("Select a role to add")
+                .setMaxValues(25)
+        );
+
+    do {
+
+        const removing = rolesToChange.filter((r) => member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0])
+        const adding = rolesToChange.filter((r) => !member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0])
+        const embed = new EmojiEmbed()
+        .setTitle("Role")
+        .setDescription(
+            `${getEmojiByName("ICONS.EDIT")} Editing roles for <@${member.id}>\n\n` +
+            `Adding:\n` +
+            `${listToAndMore(adding.length > 0 ? adding : ["None"], 5)}\n` +
+            `Removing:\n` +
+            `${listToAndMore(removing.length > 0 ? removing : ["None"], 5)}\n`
+        )
+        .setEmoji("GUILD.ROLES.CREATE")
+        .setStatus("Success");
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("roleSave")
+                    .setLabel("Apply")
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Success),
+                new ButtonBuilder()
+                    .setCustomId("roleDiscard")
+                    .setLabel("Reset")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Danger)
+            );
+
+        await interaction.editReply({ embeds: [embed], components: [roleAdd, buttons] });
+
+        let i: RoleSelectMenuInteraction | ButtonInteraction | null;
+        try {
+            i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 300000 }) as RoleSelectMenuInteraction | ButtonInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+
+        i.deferUpdate();
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "roleSave": {
+                    const roles = rolesToChange.map((r) => interaction.guild?.roles.cache.get(r)!);
+                    await interaction.editReply({ embeds: LoadingEmbed, components: [] });
+                    const rolesToAdd: Role[] = [];
+                    const rolesToRemove: Role[] = [];
+                    for(const role of roles) {
+                        if(!canEdit(role, interaction.member as GuildMember, interaction.guild?.members.me!)[1]) continue;
+                        if(member.roles.cache.has(role.id)) {
+                            rolesToRemove.push(role);
+                        } else {
+                            rolesToAdd.push(role);
+                        }
+                    }
+                    await member.roles.add(rolesToAdd);
+                    await member.roles.remove(rolesToRemove);
+                    rolesToChange = [];
+                    break;
+                }
+                case "roleDiscard": {
+                    rolesToChange = [];
+                    await interaction.editReply({ embeds: LoadingEmbed, components: [] });
+                    break;
+                }
+            }
+        } else {
+            rolesToChange = i.values;
+        }
+
+    } while (!closed);
+
+};
+
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
+    const member = interaction.member as GuildMember;
+    // Check if the user has manage_roles permission
+    if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
+    if (partial) return true;
+    if (!interaction.guild) return
+    const me = interaction.guild.members.me!;
+    // Check if Nucleus has permission to role
+    if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission";
+    // Allow the owner to role anyone
+    if (member.id === interaction.guild.ownerId) return true;
+    // Allow role
+    return true;
+};
+
+export { command };
+export { callback };
+export { check };
diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts
index 0814cfa..c7f441f 100644
--- a/src/commands/user/track.ts
+++ b/src/commands/user/track.ts
@@ -1,10 +1,11 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
-import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, SelectMenuOptionBuilder, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction, StringSelectMenuOptionBuilder } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import addPlural from "../../utils/plurals.js";
 import client from "../../utils/client.js";
+import { createVerticalTrack } from "../../utils/createPageIndicator.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -12,17 +13,8 @@
         .setDescription("Moves a user along a role track")
         .addUserOption((option) => option.setName("user").setDescription("The user to manage").setRequired(true));
 
-const generateFromTrack = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
-    active = active ? "ACTIVE" : "INACTIVE";
-    disabled = disabled ? "GREY." : "";
-    if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
-    if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
-    if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
-    return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
-};
-
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const { renderUser } = client.logger;
+    const { renderUser, renderRole} = client.logger;
     const member = interaction.options.getMember("user") as GuildMember;
     const guild = interaction.guild;
     if (!guild) return;
@@ -44,10 +36,10 @@
         const dropdown = new Discord.StringSelectMenuBuilder()
             .addOptions(
                 config.tracks.map((option, index) => {
-                    const hasRoleInTrack = option.track.some((element: string) => {
+                    const hasRoleInTrack: boolean = option.track.some((element: string) => {
                         return memberRoles.cache.has(element);
                     });
-                    return new SelectMenuOptionBuilder({
+                    return new StringSelectMenuOptionBuilder({
                         default: index === track,
                         label: option.name,
                         value: index.toString(),
@@ -68,33 +60,23 @@
             (data.retainPrevious
                 ? "When promoted, the user keeps previous roles"
                 : "Members will lose their current role when promoted") + "\n";
-        generated +=
-            "\n" +
-            data.track
-                .map((role, index) => {
-                    const allow: boolean =
-                        roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position &&
-                        !managed;
-                    allowed.push(!allow);
-                    return (
-                        getEmojiByName(
-                            generateFromTrack(index, memberRoles.cache.has(role), data.track.length, allow)
-                        ) +
-                        " " +
-                        roles.get(role)!.name +
-                        " [<@&" +
-                        roles.get(role)!.id +
-                        ">]"
-                    );
-                })
-                .join("\n");
+        for (const role of data.track) {
+            const disabled: boolean =
+                roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position && !managed;
+            allowed.push(!disabled)
+        }
+        generated += "\n" + createVerticalTrack(
+            data.track.map((role) => renderRole(roles.get(role)!)),
+            data.track.map((role) => memberRoles.cache.has(role)),
+            allowed.map((allow) => !allow)
+        );
         const selected = [];
         for (const position of data.track) {
             if (memberRoles.cache.has(position)) selected.push(position);
         }
         const conflict = data.retainPrevious ? false : selected.length > 1;
         let conflictDropdown: StringSelectMenuBuilder[] = [];
-        const conflictDropdownOptions: SelectMenuOptionBuilder[] = [];
+        const conflictDropdownOptions: StringSelectMenuOptionBuilder[] = [];
         let currentRoleIndex: number = -1;
         if (conflict) {
             generated += `\n\n${getEmojiByName(`PUNISH.WARN.${managed ? "YELLOW" : "RED"}`)} This user has ${
@@ -106,10 +88,9 @@
                     "In order to promote or demote this user, you must select which role the member should keep.";
                 selected.forEach((role) => {
                     conflictDropdownOptions.push(
-                        new SelectMenuOptionBuilder({
-                            label: roles.get(role)!.name,
-                            value: roles.get(role)!.id
-                        })
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel(roles.get(role)!.name)
+                            .setValue(roles.get(role)!.id)
                     );
                 });
                 conflictDropdown = [
@@ -169,7 +150,7 @@
         try {
             component = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             timedOut = true;
@@ -207,9 +188,9 @@
     }
 };
 
-const check = async (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, _partial: boolean = false) => {
     const tracks = (await client.database.guilds.read(interaction.guild!.id)).tracks;
-    if (tracks.length === 0) throw new Error("This server does not have any tracks");
+    if (tracks.length === 0) return "This server does not have any tracks";
     const member = interaction.member as GuildMember;
     // Allow the owner to promote anyone
     if (member.id === interaction.guild!.ownerId) return true;
@@ -223,8 +204,7 @@
         break;
     }
     // Check if the user has manage_roles permission
-    if (!managed && !member.permissions.has("ManageRoles"))
-        throw new Error("You do not have the *Manage Roles* permission");
+    if (!managed && !member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
     // Allow track
     return true;
 };
diff --git a/src/commands/verify.ts b/src/commands/verify.ts
index 4fafe69..0dd8b24 100644
--- a/src/commands/verify.ts
+++ b/src/commands/verify.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import { SlashCommandBuilder } from "discord.js";
 import verify from "../reflex/verify.js";
 
 const command = new SlashCommandBuilder().setName("verify").setDescription("Get verified in the server");
@@ -8,10 +8,5 @@
     verify(interaction);
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };