loads of new commands, updates and bug fixes
diff --git a/src/api/index.ts b/src/api/index.ts
index 570ca5b..07131ff 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -2,6 +2,8 @@
 import express from 'express';
 import bodyParser from 'body-parser';
 import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
+import structuredClone from '@ungap/structured-clone';
+
 
 const jsonParser = bodyParser.json();
 const app = express();
@@ -9,7 +11,7 @@
 
 const runServer = (client: HaikuClient) => {
     app.get('/', (req, res) => {
-        res.send(client.ws.ping);
+        res.status(200).send(client.ws.ping);
     });
 
     app.post('/verify/:code', jsonParser, async function (req, res) {
@@ -32,9 +34,9 @@
                     .setEmoji("MEMBER.JOIN")
                 ], components: []});
             }
-            res.status(200).send();
+            res.sendStatus(200);
         } else {
-            res.status(403).send();
+            res.sendStatus(403);
         }
     });
 
@@ -51,17 +53,17 @@
                 ]});
             }
         } catch {}
-        res.status(200).send();
+        res.sendStatus(200);
     })
 
     app.get('/verify/:code', jsonParser, function (req, res) {
         const code = req.params.code;
         if (client.verify[code]) {
-            // let data = structuredClone(client.verify[code])
-            // delete data.interaction;
-            // return res.status(200).send(data);
+            let data = structuredClone(client.verify[code])
+            delete data.interaction;
+            return res.status(200).send(data);
         }
-        return res.status(404).send();
+        return res.sendStatus(404);
     })
 
     app.listen(port);
diff --git a/src/automations/createModActionTicket.ts b/src/automations/createModActionTicket.ts
index ef317b7..a9a5a27 100644
--- a/src/automations/createModActionTicket.ts
+++ b/src/automations/createModActionTicket.ts
@@ -1,10 +1,10 @@
 import Discord, { MessageActionRow, MessageButton } from 'discord.js';
-import readConfig from '../utils/readConfig.js'
 import generateEmojiEmbed from '../utils/generateEmojiEmbed.js';
 import getEmojiByName from "../utils/getEmojiByName.js";
+import client from "../utils/client.js";
 
-export async function create(guild: Discord.Guild, member: Discord.User, createdBy: Discord.User, client) {
-    let config = await readConfig(guild.id);
+export async function create(guild: Discord.Guild, member: Discord.User, createdBy: Discord.User, reason: string) {
+    let config = await client.database.read(guild.id);
     // @ts-ignore
     const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger
     let overwrites = [{
@@ -12,6 +12,11 @@
         allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"],
         type: "member"
     }] as Discord.OverwriteResolvable[];
+    overwrites.push({
+        id: guild.roles.everyone,
+        deny: ["VIEW_CHANNEL"],
+        type: "role"
+    })
     if (config.tickets.supportRole != null) {
         overwrites.push({
             id: guild.roles.cache.get(config.tickets.supportRole),
@@ -47,7 +52,7 @@
             .setTitle("New Ticket")
             .setDescription(
                 `Ticket created by a Moderator\n` +
-                `**Support type:** Appeal submission\n` +
+                `**Support type:** Appeal submission\n` + (reason != null ? `**Reason:**\n> ${reason}\n` : "") +
                 `**Ticket ID:** \`${c.id}\`\n` +
                 `Type \`/ticket close\` to archive this ticket.`,
             )
@@ -84,6 +89,6 @@
 }
 
 export async function areTicketsEnabled(guild: string) {
-    let config = await readConfig(guild);
+    let config = await client.database.read(guild);
     return config.tickets.enabled;
 }
\ No newline at end of file
diff --git a/src/automations/guide.ts b/src/automations/guide.ts
index 6a9b0b5..d443a42 100644
--- a/src/automations/guide.ts
+++ b/src/automations/guide.ts
@@ -96,7 +96,20 @@
                 )
                 .setEmoji("NUCLEUS.LOGO")
                 .setStatus("Danger")
-            ).setTitle("Tickets").setDescription("Ticket system").setPageId(5)
+            ).setTitle("Tickets").setDescription("Ticket system").setPageId(5),
+        new Embed()
+            .setEmbed(new generateEmojiEmbed()
+                .setTitle("Tags")
+                .setDescription(
+                    "Add a tag system to your server with the `/tag` and `/tags` commands.\n" +
+                    "To create a tag, type `/tags create <tag name> <tag content>`.\n" +
+                    "Tag names and content can be edited with `/tags edit`.\n" +
+                    "To delete a tag, type `/tags delete <tag name>`.\n" +
+                    "To view all tags, type `/tags list`.\n"
+                )
+                .setEmoji("NUCLEUS.LOGO")
+                .setStatus("Danger")
+            ).setTitle("Tags").setDescription("Tag system").setPageId(6)
     ]
     let m;
     if (interaction) {
@@ -144,30 +157,25 @@
                     .setPlaceholder("Choose a page...")
             ])]
         }
+        let components = selectPane.concat([new MessageActionRow().addComponents([
+            new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(page === 0),
+            new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle(selectPaneOpen ? "PRIMARY" : "SECONDARY").setDisabled(false),
+            new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(page === pages.length - 1),
+            new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER")
+        ])])
         if (interaction) {
             let em = new Discord.MessageEmbed(pages[page].embed)
             em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
             await interaction.editReply({
                 embeds: [em],
-                components: selectPane.concat([new MessageActionRow().addComponents([
-                    new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(page === 0),
-                    new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle(selectPaneOpen ? "PRIMARY" : "SECONDARY").setDisabled(false),
-                    new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(page === pages.length - 1),
-                    new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER")
-                ])]),
-                fetchReply: true
+                components: components
             });
         } else {
             let em = new Discord.MessageEmbed(pages[page].embed)
             em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
             await m.edit({
                 embeds: [em],
-                components: selectPane.concat([new MessageActionRow().addComponents([
-                    new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(page === 0),
-                    new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle(selectPaneOpen ? "PRIMARY" : "SECONDARY").setDisabled(false),
-                    new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(page === pages.length - 1),
-                    new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER")
-                ])]),
+                components: components,
                 fetchReply: true
             });
         }
diff --git a/src/automations/roleMenu.ts b/src/automations/roleMenu.ts
index c7073c5..cac15a5 100644
--- a/src/automations/roleMenu.ts
+++ b/src/automations/roleMenu.ts
@@ -1,18 +1,18 @@
 import { Message, MessageButton } from "discord.js";
-import readConfig from '../utils/readConfig.js'
 import generateEmojiEmbed from '../utils/generateEmojiEmbed.js'
 import { MessageActionRow, MessageSelectMenu } from 'discord.js';
 import getEmojiByName from "../utils/getEmojiByName.js";
+import client from "../utils/client.js";
 
 export async function callback(interaction) {
-    let config = await readConfig(interaction.guild.id);
-    if (!config.roleMenu.enabled) await interaction.reply({embeds: [new generateEmojiEmbed()
+    let config = await client.database.read(interaction.guild.id);
+    if (!config.roleMenu.enabled) return await interaction.reply({embeds: [new generateEmojiEmbed()
         .setTitle("Roles")
         .setDescription("Self roles are currently disabled. Please contact a staff member or try again later.")
         .setStatus("Danger")
         .setEmoji("CONTROL.BLOCKCROSS")
     ], ephemeral: true})
-    if (config.roleMenu.options.length === 0) await interaction.reply({embeds: [new generateEmojiEmbed()
+    if (config.roleMenu.options.length === 0) return await interaction.reply({embeds: [new generateEmojiEmbed()
         .setTitle("Roles")
         .setDescription("There are no roles available. Please contact a staff member or try again later.")
         .setStatus("Danger")
@@ -136,7 +136,7 @@
             .setDescription("Something went wrong and your roles were not added. Please contact a staff member or try again later.")
             .setStatus("Danger")
             .setEmoji("GUILD.RED")
-        ]})
+        ], components: []})
     }
     await interaction.editReply({embeds: [new generateEmojiEmbed()
         .setTitle("Roles")
diff --git a/src/automations/statsChannelAdd.ts b/src/automations/statsChannelAdd.ts
index 42ec580..01dfef1 100644
--- a/src/automations/statsChannelAdd.ts
+++ b/src/automations/statsChannelAdd.ts
@@ -1,19 +1,28 @@
-import log from '../utils/log.js'
-import readConfig from '../utils/readConfig.js'
 import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
+import singleNotify from '../utils/singleNotify.js';
+import client from '../utils/client.js';
 
 export async function callback(_, member) {
-    let config = await readConfig(member.guild.id);
+    let config = await client.database.read(member.guild.id);
 
     config.stats.forEach(async element => {
         if (element.enabled) {
             let string = element.text
             if (!string) return
             string = await convertCurlyBracketString(string, member.id, member.displayName, member.guild.name, member.guild.members)
-
-            let channel = await member.client.channels.fetch(element.channel)
+            let channel;
+            try {
+                channel = await member.client.channels.fetch(element.channel)
+            } catch (error) { channel = null }
+            if (!channel) {
+                return singleNotify(
+                    "statsChannelDeleted",
+                    member.guild.id,
+                    "One or more of your stats channels have been deleted. Please open the settings menu to change this.",
+                    "Critical"
+                )
+            }
             if (channel.guild.id !== member.guild.id) return
-            if (!channel) return // TODO: Notify mods
             try {
                 await channel.edit({ name: string })
             } catch (err) {
@@ -21,4 +30,4 @@
             }
         }
     });
-}
\ No newline at end of file
+}
diff --git a/src/automations/statsChannelRemove.ts b/src/automations/statsChannelRemove.ts
index 4b24768..fee0d2d 100644
--- a/src/automations/statsChannelRemove.ts
+++ b/src/automations/statsChannelRemove.ts
@@ -1,10 +1,9 @@
-import log from '../utils/log.js'
-import readConfig from '../utils/readConfig.js'
+import client from '../utils/client.js';
 import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
 import singleNotify from '../utils/singleNotify.js';
 
-export async function callback(interaction, member) {
-    let config = await readConfig(member.guild.id);
+export async function callback(_, member) {
+    let config = await client.database.read(member.guild.id);
 
     config.stats.forEach(async element => {
         if (element.enabled) {
@@ -14,10 +13,10 @@
 
             let channel = await member.client.channels.fetch(element.channel)
             if (channel.guild.id !== member.guild.id) return
-            if (!channel) return await singleNotify(interaction.client,
+            if (!channel) return singleNotify(
                 "statsChannelDeleted",
                 member.guild.id,
-                "The stats channel has been deleted. Please set a new channel to use this feature.",
+                "One or more of your stats channels have been deleted. Please open the settings menu to change this.",
                 "Critical"
             )
             try {
diff --git a/src/automations/tickets/create.ts b/src/automations/tickets/create.ts
index fd0ae57..9642089 100644
--- a/src/automations/tickets/create.ts
+++ b/src/automations/tickets/create.ts
@@ -1,6 +1,6 @@
 import Discord, { MessageActionRow, MessageButton } from "discord.js";
 import { tickets, toHexArray } from "../../utils/calculate.js";
-import readConfig from "../../utils/readConfig.js";
+import client from "../../utils/client.js";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 
@@ -13,7 +13,7 @@
     // @ts-ignore
     const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = interaction.client.logger
 
-    let config = await readConfig(interaction.guild.id);
+    let config = await client.database.read(interaction.guild.id);
     if (!config.tickets.enabled || !config.tickets.category) {
         return await interaction.reply({embeds: [new generateEmojiEmbed()
             .setTitle("Tickets are disabled")
@@ -63,8 +63,8 @@
                     .setEmoji(getEmojiByName(("TICKETS." + type.toString().toUpperCase()), "id"));
             }
         });
-        for (let i = 0; i < formattedTicketTypes.length; i += 4) {
-            splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 4)));
+        for (let i = 0; i < formattedTicketTypes.length; i += 5) {
+            splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 5)));
         }
         let m = await interaction.reply({embeds: [new generateEmojiEmbed()
             .setTitle("Create Ticket")
@@ -97,8 +97,8 @@
                     .setDisabled(true)
             }
         });
-        for (let i = 0; i < formattedTicketTypes.length; i += 4) {
-            splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 4)));
+        for (let i = 0; i < formattedTicketTypes.length; i += 5) {
+            splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 5)));
         }
         component.update({embeds: [new generateEmojiEmbed()
             .setTitle("Create Ticket")
diff --git a/src/automations/tickets/delete.ts b/src/automations/tickets/delete.ts
index 17d889a..1d577a4 100644
--- a/src/automations/tickets/delete.ts
+++ b/src/automations/tickets/delete.ts
@@ -1,18 +1,20 @@
 import Discord, { MessageButton, MessageActionRow } from "discord.js";
+import client from "../../utils/client.js";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
-import readConfig from "../../utils/readConfig.js";
 
 export default async function (interaction) {
     // @ts-ignore
     const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = interaction.client.logger
 
-    let config = await readConfig(interaction.guild.id);
+    let config = await client.database.read(interaction.guild.id);
+    let thread = false; let threadChannel
+    if (interaction.channel instanceof Discord.ThreadChannel) thread = true; threadChannel = interaction.channel as Discord.ThreadChannel
     let channel = (interaction.channel as Discord.TextChannel)
-    if (!channel.parent || config.tickets.category != channel.parent.id) {
+    if (!channel.parent || config.tickets.category != channel.parent.id || (thread ? (threadChannel.parent.parent.id != config.tickets.category) : false)) {
         return interaction.reply({embeds: [new generateEmojiEmbed()
             .setTitle("Deleting Ticket...")
-            .setDescription("This ticket is not in your tickets category, so cannot be deleted. You cannot run close in a thread.") // TODO bridge to cross later!
+            .setDescription("This ticket is not in your tickets category, so cannot be deleted. You cannot run close in a thread.")
             .setStatus("Danger")
             .setEmoji("CONTROL.BLOCKCROSS")
         ], ephemeral: true});
@@ -110,7 +112,7 @@
 }
 
 async function purgeByUser(member, guild) {
-    let config = await readConfig(guild.id);
+    let config = await client.database.read(guild.id);
     if (!config.tickets.category) return;
     let tickets = guild.channels.cache.get(config.tickets.category);
     if (!tickets) return;
diff --git a/src/automations/verify.ts b/src/automations/verify.ts
index 3d4e658..bf26505 100644
--- a/src/automations/verify.ts
+++ b/src/automations/verify.ts
@@ -1,9 +1,9 @@
-import Discord, { CommandInteraction, GuildMember } from "discord.js";
+import Discord, { GuildMember } from "discord.js";
 import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
-import readConfig from "../utils/readConfig.js";
 import fetch from "node-fetch";
 import { TestString, NSFWCheck } from "../automations/unscan.js";
 import createPageIndicator from "../utils/createPageIndicator.js";
+import client from "../utils/client.js";
 
 function step(i) {
     return "\n\n" + createPageIndicator(5, i);
@@ -18,7 +18,13 @@
         .setStatus("Danger")
         .setEmoji("NUCLEUS.LOADING")
     ], ephemeral: true, fetchReply: true});
-    let config = await readConfig(interaction.guild.id);
+    let config = await client.database.read(interaction.guild.id);
+    if ((!config.verify.enabled ) || (!config.verify.role)) return interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Verify")
+        .setDescription(`Verify is not enabled on this server`)
+        .setStatus("Danger")
+        .setEmoji("CONTROL.BLOCKCROSS")
+    ], ephemeral: true, fetchReply: true});
     if ((interaction.member as GuildMember).roles.cache.has(config.verify.role)) {
         return await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setTitle("Verify")
@@ -34,7 +40,7 @@
         .setEmoji("NUCLEUS.LOADING")
     ]});
     try {
-        let status = await fetch(`https://clicks.codes/`).then(res => res.status);
+        let status = await fetch(client.config.baseUrl).then(res => res.status);
         if (status != 200) {
             return await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setTitle("Verify")
@@ -53,7 +59,7 @@
             new Discord.MessageButton()
                 .setLabel("Check webpage")
                 .setStyle("LINK")
-                .setURL("https://clicks.codes/"),
+                .setURL(client.config.baseUrl),
             new Discord.MessageButton()
                 .setLabel("Support")
                 .setStyle("LINK")
@@ -132,7 +138,6 @@
     ], components: [new Discord.MessageActionRow().addComponents([new Discord.MessageButton()
         .setLabel("Verify")
         .setStyle("LINK")
-        // .setURL(`https://clicks.codes/nucleus/verify?code=${code}`)
-        .setURL(`https://insulation-coin-hoping-nevertheless.trycloudflare.com/nucleus/verify?code=${code}`)
+        .setURL(`${client.config.baseUrl}/nucleus/verify?code=${code}`)
     ])]});
 }
diff --git a/src/automations/welcome.ts b/src/automations/welcome.ts
index 84a87ec..11c4844 100644
--- a/src/automations/welcome.ts
+++ b/src/automations/welcome.ts
@@ -1,10 +1,10 @@
 import log from '../utils/log.js'
-import readConfig from '../utils/readConfig.js'
 import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
+import client from '../utils/client.js';
 
 export async function callback(_, member) {
     if (member.bot) return
-    let config = await readConfig(member.guild.id);
+    let config = await client.database.read(member.guild.id);
     if (!config.welcome.enabled) return
 
     if (!config.welcome.verificationRequired.role) {
diff --git a/src/commands/categorisationTest.ts b/src/commands/categorisationTest.ts
index 2852a2b..33f99e9 100644
--- a/src/commands/categorisationTest.ts
+++ b/src/commands/categorisationTest.ts
@@ -60,7 +60,6 @@
             .setCustomId("category")
             .setMinValues(0)
             .setMaxValues(1)
-            // .setMaxValues(Object.keys(types).length)
             .setOptions(Object.keys(types).map(type => {return {label: toCapitals(type), value: type}}))
         ])]});
     }
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index 1e17e53..8b17db3 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -4,8 +4,8 @@
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js';
 import addPlurals from "../../utils/plurals.js";
+import client from "../../utils/client.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -31,13 +31,11 @@
         + `${addPlurals(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
         + `Are you sure you want to ban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
     .send()
     if (confirmation.success) {
         let dmd = false
         let dm;
-        let config = await readConfig(interaction.guild.id);
+        let config = await client.database.read(interaction.guild.id);
         try {
             if (interaction.options.getString("notify") != "no") {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index 9dc447b..20fbc01 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -5,7 +5,7 @@
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js'
+import client from "../../utils/client.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -29,13 +29,11 @@
         + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
         + `Are you sure you want to kick <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
     .send()
     if (confirmation.success) {
         let dmd = false
         let dm;
-        let config = await readConfig(interaction.guild.id);
+        let config = await client.database.read(interaction.guild.id);
         try {
             if (interaction.options.getString("notify") != "no") {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index b2cc94e..854f38f 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -6,7 +6,7 @@
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import humanizeDuration from "humanize-duration";
-import readConfig from "../../utils/readConfig.js";
+import client from "../../utils/client.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -21,7 +21,7 @@
     .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are muted | Default yes").setRequired(false)
         .addChoices([["Yes", "yes"], ["No", "no"]]))
 
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     // @ts-ignore
     const { log, NucleusColors, renderUser, entry } = interaction.client.logger
     const user = interaction.options.getMember("user") as GuildMember
@@ -32,7 +32,7 @@
         minutes: interaction.options.getInteger("minutes") || 0,
         seconds: interaction.options.getInteger("seconds") || 0
     }
-    let config = await readConfig(interaction.guild.id)
+    let config = await client.database.read(interaction.guild.id)
     let serverSettingsDescription = (config.moderation.mute.timeout ? "given a timeout" : "")
     if (config.moderation.mute.role) serverSettingsDescription += (serverSettingsDescription ? " and " : "") + `given the <@&${config.moderation.mute.role}> role`
 
@@ -132,13 +132,11 @@
         + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
         + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
     .send(true)
     if (confirmation.success) {
         let dmd = false
         let dm;
-        let config = await readConfig(interaction.guild.id);
+        let config = await client.database.read(interaction.guild.id);
         try {
             if (interaction.options.getString("notify") != "no") {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index 6a0410a..96738c6 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -4,7 +4,6 @@
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js';
 import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -31,10 +30,8 @@
         .setColor("Danger")
         .addCustomBoolean(
             "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
-            async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.client),
+            async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.options.getString("reason")),
             "An appeal ticket will be created when Confirm is clicked")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
     .send()
     if (confirmation.success) {
         let dmd = false
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index 924e507..8e6762e 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -19,7 +19,7 @@
     .addUserOption(option => option.setName("user").setDescription("The user to purge messages from").setRequired(false))
     .addStringOption(option => option.setName("reason").setDescription("The reason for the purge").setRequired(false))
 
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     let user = interaction.options.getMember("user") as GuildMember ?? null
     let channel = (interaction.channel as GuildChannel)
     if (!(["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(channel.type.toString()))) {
@@ -205,8 +205,6 @@
                 "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
             }))
             .setColor("Danger")
-    //        pluralize("day", interaction.options.getInteger("amount"))
-    //        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
         .send()
         if (confirmation.success) {
             let messages;
diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts
index b91f065..2498746 100644
--- a/src/commands/mod/slowmode.ts
+++ b/src/commands/mod/slowmode.ts
@@ -29,8 +29,6 @@
         })
         + `Are you sure you want to set the slowmode in this channel?`)
         .setColor("Danger")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
     .send()
     if (confirmation.success) {
         try {
@@ -39,7 +37,7 @@
             await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("CHANNEL.SLOWMODE.RED")
                 .setTitle(`Slowmode`)
-                .setDescription("An error occurred while setting the slowmode")
+                .setDescription("Something went wrong while setting the slowmode")
                 .setStatus("Danger")
             ], components: []})
         }
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index 29d3bef..5a01287 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -4,8 +4,8 @@
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js';
-import addPlurals from '../../utils/plurals.js';
+import client from "../../utils/client.js";
+import addPlural from "../../utils/plurals.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -28,15 +28,13 @@
             "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
         })
         + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n`
-        + `${addPlurals(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
+        + `${addPlural(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
         + `Are you sure you want to softban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
     .send()
     if (confirmation.success) {
         let dmd = false;
-        let config = await readConfig(interaction.guild.id);
+        let config = await client.database.read(interaction.guild.id);
         try {
             if (interaction.options.getString("notify") != "no") {
                 await (interaction.options.getMember("user") as GuildMember).send({
diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts
index 8231752..f201142 100644
--- a/src/commands/mod/unban.ts
+++ b/src/commands/mod/unban.ts
@@ -11,7 +11,7 @@
     .setDescription("Unbans a user")
     .addStringOption(option => option.setName("user").setDescription("The user to unban (Username or ID)").setRequired(true))
 
-const callback = async (interaction: CommandInteraction) => { // TODO: User search
+const callback = async (interaction: CommandInteraction) => { // TODO: User search UI
     let bans = await interaction.guild.bans.fetch()
     let user = interaction.options.getString("user")
     let resolved = bans.find(ban => ban.user.id == user)
diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts
index b2f8234..2a98c54 100644
--- a/src/commands/mod/unmute.ts
+++ b/src/commands/mod/unmute.ts
@@ -27,8 +27,6 @@
         + `The user **will${interaction.options.getString("notify") === "yes" ? '' : ' not'}** be notified\n\n`
         + `Are you sure you want to unmute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
     .send()
     if (confirmation.success) {
         let dmd = false
diff --git a/src/commands/mod/unnamed.ts b/src/commands/mod/unnamed.ts
deleted file mode 100644
index ca0bcef..0000000
--- a/src/commands/mod/unnamed.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-import Discord, { CommandInteraction, GuildMember, MessageActionRow } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import getEmojiByName from "../../utils/getEmojiByName.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import keyValueList from "../../utils/generateKeyValueList.js";
-import humanizeDuration from "humanize-duration";
-import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
-import readConfig from '../../utils/readConfig.js'
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("unnamed")
-    .setDescription("Gives a user a role")
-    .addUserOption(option => option.setName("user").setDescription("The user to UNNAMED").setRequired(true)) // TODO
-    .addIntegerOption(option => option.setName("days").setDescription("The number of days to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(27).setRequired(false))
-    .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(23).setRequired(false))
-    .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
-    .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
-    .addStringOption(option => option.setName("reason").setDescription("The reason for the UNNAMED").setRequired(false))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are UNNAMED | Default yes").setRequired(false)
-        .addChoices([["Yes", "yes"], ["No", "no"]]))
-
-const callback = async (interaction: CommandInteraction) => {
-    // @ts-ignore
-    const { log, NucleusColors, renderUser, entry } = interaction.client.logger
-    let config = await readConfig(interaction.guild.id);
-    const user = interaction.options.getMember("user") as GuildMember
-    const reason = interaction.options.getString("reason")
-    const time = {
-        days: interaction.options.getInteger("days") || 0,
-        hours: interaction.options.getInteger("hours") || 0,
-        minutes: interaction.options.getInteger("minutes") || 0,
-        seconds: interaction.options.getInteger("seconds") || 0
-    }
-    let muteTime = (time.days * 24 * 60 * 60) + (time.hours * 60 * 60) + (time.minutes * 60) + time.seconds
-    if (muteTime == 0) {
-        let m = await interaction.reply({embeds: [
-            new generateEmojiEmbed()
-                .setEmoji("PUNISH.MUTE.GREEN") // TODO
-                .setTitle("UNNAMED")
-                .setDescription("How long should the user be UNNAMED")
-                .setStatus("Success")
-        ], components: [
-            new MessageActionRow().addComponents([
-                new Discord.MessageButton()
-                    .setCustomId("1m")
-                    .setLabel("1 Minute")
-                    .setStyle("SECONDARY"),
-                new Discord.MessageButton()
-                    .setCustomId("10m")
-                    .setLabel("10 Minutes")
-                    .setStyle("SECONDARY"),
-                new Discord.MessageButton()
-                    .setCustomId("30m")
-                    .setLabel("30 Minutes")
-                    .setStyle("SECONDARY"),
-                new Discord.MessageButton()
-                    .setCustomId("1h")
-                    .setLabel("1 Hour")
-                    .setStyle("SECONDARY")
-            ]),
-            new MessageActionRow().addComponents([
-                new Discord.MessageButton()
-                    .setCustomId("6h")
-                    .setLabel("6 Hours")
-                    .setStyle("SECONDARY"),
-                new Discord.MessageButton()
-                    .setCustomId("12h")
-                    .setLabel("12 Hours")
-                    .setStyle("SECONDARY"),
-                new Discord.MessageButton()
-                    .setCustomId("1d")
-                    .setLabel("1 Day")
-                    .setStyle("SECONDARY"),
-                new Discord.MessageButton()
-                    .setCustomId("1w")
-                    .setLabel("1 Week")
-                    .setStyle("SECONDARY")
-            ]),
-            new MessageActionRow().addComponents([
-                new Discord.MessageButton()
-                    .setCustomId("cancel")
-                    .setLabel("Cancel")
-                    .setStyle("DANGER")
-                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-            ])
-        ], ephemeral: true, fetchReply: true})
-        let component;
-        try {
-            component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
-        } catch { return }
-        component.deferUpdate();
-        if (component.customId == "cancel") return interaction.editReply({embeds: [new generateEmojiEmbed()
-            .setEmoji("PUNISH.MUTE.RED") // TODO
-            .setTitle("UNNAMED")
-            .setDescription("UNNAMED cancelled")
-            .setStatus("Danger")
-        ]})
-        switch (component.customId) {
-            case "1m": { muteTime = 60; break; }
-            case "10m": { muteTime = 60 * 10; break; }
-            case "30m": { muteTime = 60 * 30; break; }
-            case "1h": { muteTime = 60 * 60; break; }
-            case "6h": { muteTime = 60 * 60 * 6; break; }
-            case "12h": { muteTime = 60 * 60 * 12; break; }
-            case "1d": { muteTime = 60 * 60 * 24; break; }
-            case "1w": { muteTime = 60 * 60 * 24 * 7; break; }
-        }
-    } else {
-        await interaction.reply({embeds: [
-            new generateEmojiEmbed()
-                .setEmoji("PUNISH.MUTE.GREEN") // TODO
-                .setTitle("UNNAMED")
-                .setDescription("Loading...")
-                .setStatus("Success")
-        ], ephemeral: true, fetchReply: true})
-    }
-    // TODO:[Modals] Replace this with a modal
-    let confirmation = await new confirmationMessage(interaction)
-        .setEmoji("PUNISH.MUTE.RED") // TODO
-        .setTitle("UNNAMED")
-        .setDescription(keyValueList({
-            "user": `<@!${user.id}> (${user.user.username})`,
-            "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
-            "reason": `\n> ${reason ? reason : "*No reason provided*"}`
-        })
-        + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
-        + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`) // TODO
-        .setColor("Danger")
-        .addCustomBoolean(
-            "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
-            async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.client),
-            "An appeal ticket will be created when Confirm is clicked")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
-    .send()
-    if (confirmation.success) {
-        let dmd = false
-        let dm;
-        try {
-            if (interaction.options.getString("notify") != "no") {
-                dm = await (interaction.options.getMember("user") as GuildMember).send({
-                    embeds: [new generateEmojiEmbed()
-                        .setEmoji("PUNISH.MUTE.RED") // TODO
-                        .setTitle("UNNAMED")
-                        .setDescription(`You have been muted in ${interaction.guild.name}` + // TODO
-                                    (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".\n\n" +
-                                    `You will be unmuted at: <t:${Math.round((new Date).getTime() / 1000) + muteTime}:D> at <t:${Math.round((new Date).getTime() / 1000) + muteTime}:T> (<t:${Math.round((new Date).getTime() / 1000) + muteTime}:R>)`)) // TODO
-                        .setStatus("Danger")
-                    ]
-                })
-                dmd = true
-            }
-        } catch {}
-        try {
-            await ((interaction.options.getMember("user") as GuildMember).roles.add(interaction.guild.roles.cache.find((r) => r.id === config.moderation.role.role)))
-            // TODO: Store when to remove the role
-        } catch {
-            await interaction.editReply({embeds: [new generateEmojiEmbed()
-                .setEmoji("PUNISH.MUTE.RED")
-                .setTitle(`Mute`)
-                .setDescription("Something went wrong and the user was not UNNAMED")
-                .setStatus("Danger")
-            ], components: []})
-            if (dmd) await dm.delete()
-            return
-        }
-        let failed = (dmd == false && interaction.options.getString("notify") != "no")
-        await interaction.editReply({embeds: [new generateEmojiEmbed()
-            .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`) // TODO
-            .setTitle(`Mute`) // TODO
-            .setDescription("The member was muted" + (failed ? ", but could not be notified" : "")) // TODO
-            .setStatus(failed ? "Warning" : "Success")
-        ], components: []})
-        let data = {
-            meta:{
-                type: 'memberMute', // TODO
-                displayName: 'Member Muted', // TODO
-                calculateType: 'guildMemberPunish',
-                color: NucleusColors.yellow,
-                emoji: 'PUNISH.WARN.YELLOW', // TODO
-                timestamp: new Date().getTime()
-            },
-            list: {
-                user: entry((interaction.options.getMember("user") as GuildMember).user.id, renderUser((interaction.options.getMember("user") as GuildMember).user)),
-                mutedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)), // TODO
-                time: entry(muteTime, `${humanizeDuration(muteTime * 1000, {round: true})}`),
-                reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
-            },
-            hidden: {
-                guild: interaction.guild.id
-            }
-        }
-        log(data, interaction.client);
-    } else {
-        await interaction.editReply({embeds: [new generateEmojiEmbed()
-            .setEmoji("PUNISH.MUTE.GREEN") // TODO
-            .setTitle(`Mute`) // TODO
-            .setDescription("No changes were made")
-            .setStatus("Success")
-        ], components: []})
-    }
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    let member = (interaction.member as GuildMember)
-    let me = (interaction.guild.me as GuildMember)
-    let apply = (interaction.options.getMember("user") as GuildMember)
-    if (member == null || me == null || apply == null) throw "That member is not in the server"
-    let memberPos = member.roles ? member.roles.highest.position : 0
-    let mePos = me.roles ? me.roles.highest.position : 0
-    let applyPos = apply.roles ? apply.roles.highest.position : 0
-    // Check if Nucleus can UNNAMED the member
-    if (! (mePos > applyPos)) throw "I do not have a role higher than that member"
-    // Check if Nucleus has permission to UNNAMED
-    if (! me.permissions.has("MANAGE_ROLES")) throw "I do not have the `manage_roles` permission";
-    // Do not allow the user to have admin or be the owner
-    if (apply.permissions.has("ADMINISTRATOR") || apply.id == interaction.guild.ownerId) throw "You cannot mute an admin or the owner"
-    // Do not allow muting Nucleus
-    if (member.id == me.id) throw "I cannot UNNAMED myself"
-    // Allow the owner to UNNAMED anyone
-    if (member.id == interaction.guild.ownerId) return true
-    // Check if the user has moderate_members permission
-    if (! member.permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission";
-    // Check if the user is below on the role list
-    if (! (memberPos > applyPos)) throw "You do not have a role higher than that member"
-    // Allow UNNAMED
-    return true
-}
-
-export { command, callback, check };
\ No newline at end of file
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 715777e..46e2871 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -16,7 +16,7 @@
         .addChoices([["Yes", "yes"], ["No", "no"]])
     )
 
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     // @ts-ignore
     const { log, NucleusColors, renderUser, entry } = interaction.client.logger
     // TODO:[Modals] Replace this with a modal
@@ -32,10 +32,8 @@
         .setColor("Danger")
         .addCustomBoolean(
             "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
-            async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.client),
+            async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.options.getString("reason")),
             "An appeal ticket will be created when Confirm is clicked")
-//        pluralize("day", interaction.options.getInteger("delete"))
-//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
     .send()
     if (confirmation.success) {
         let dmd = false
diff --git a/src/commands/settings/automation.ts b/src/commands/settings/automation.ts
deleted file mode 100644
index 0053f76..0000000
--- a/src/commands/settings/automation.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("automation")
-    .setDescription("Shows all automation options")
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/automation]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/log/channel.ts b/src/commands/settings/log/channel.ts
deleted file mode 100644
index 5843438..0000000
--- a/src/commands/settings/log/channel.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("channel")
-    .setDescription("Sets the log channel")
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/log/channel]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/log/ignore.ts b/src/commands/settings/log/ignore.ts
deleted file mode 100644
index 1b4d245..0000000
--- a/src/commands/settings/log/ignore.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("ignore")
-    .setDescription("Sets which users, channels and roles should be ignored")
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/log/ignore]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/log/ignored.ts b/src/commands/settings/log/ignored.ts
deleted file mode 100644
index bf4a30c..0000000
--- a/src/commands/settings/log/ignored.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("ignored")
-    .setDescription("Gets the ignored users, channels and roles")
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/log/ignored]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/log/_meta.ts b/src/commands/settings/logs/_meta.ts
similarity index 100%
rename from src/commands/settings/log/_meta.ts
rename to src/commands/settings/logs/_meta.ts
diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts
new file mode 100644
index 0000000..18ed8d9
--- /dev/null
+++ b/src/commands/settings/logs/channel.ts
@@ -0,0 +1,129 @@
+import { ChannelType } from 'discord-api-types';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import generateEmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../../utils/confirmationMessage.js";
+import getEmojiByName from "../../../utils/getEmojiByName.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import client from "../../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+    .setName("channel")
+    .setDescription("Sets or shows the log channel")
+    .addChannelOption(option => option.setName("channel").setDescription("The channel to set the log channel to").addChannelTypes([
+        ChannelType.GuildNews, ChannelType.GuildText
+    ]))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    let m;
+    m = await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Loading")
+        .setStatus("Danger")
+        .setEmoji("NUCLEUS.LOADING")
+    ], ephemeral: true, fetchReply: true});
+    if (interaction.options.getChannel("channel")) {
+        let channel
+        try {
+            channel = interaction.options.getChannel("channel")
+        } catch {
+            return await interaction.editReply({embeds: [new generateEmojiEmbed()
+                .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 generateEmojiEmbed()
+                .setTitle("Log Channel")
+                .setDescription(`You must choose a channel in this server`)
+                .setStatus("Danger")
+                .setEmoji("CHANNEL.TEXT.DELETE")
+            ]});
+        }
+        let confirmation = await new confirmationMessage(interaction)
+            .setEmoji("CHANNEL.TEXT.EDIT")
+            .setTitle("Log Channel")
+            .setDescription(`Are you sure you want to set the log channel to <#${channel.id}>?`)
+            .setColor("Warning")
+            .setInverted(true)
+        .send(true)
+        if (confirmation.success) {
+            try {
+                await client.database.write(interaction.guild.id, {"logging.logs.channel": channel.id})
+            } catch (e) {
+                console.log(e)
+                return interaction.editReply({embeds: [new generateEmojiEmbed()
+                    .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 generateEmojiEmbed()
+                .setTitle("Log Channel")
+                .setDescription(`No changes were made`)
+                .setStatus("Success")
+                .setEmoji("CHANNEL.TEXT.CREATE")
+            ], components: []});
+        }
+    }
+    let clicks = 0;
+    let data = await client.database.read(interaction.guild.id);
+    let channel = data.logging.logs.channel;
+    while (true) {
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .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 MessageActionRow().addComponents([new MessageButton()
+            .setCustomId("clear")
+            .setLabel(clicks ? "Click again to confirm" : "Reset channel")
+            .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
+            .setStyle("DANGER")
+            .setDisabled(!channel)
+        ])]});
+        let i;
+        try {
+            i = await m.awaitMessageComponent({time: 600000});
+        } catch(e) { break }
+        i.deferUpdate()
+        if (i.component.customId == "clear") {
+            clicks += 1;
+            if (clicks == 2) {
+                clicks = 0;
+                await client.database.write(interaction.guild.id, {}, ["logging.logs.channel"])
+                channel = undefined;
+            }
+        } else {
+            break
+        }
+    }
+    await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .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 MessageActionRow().addComponents([new MessageButton()
+        .setCustomId("clear")
+        .setLabel("Clear")
+        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+        .setStyle("SECONDARY")
+        .setDisabled(true)
+    ])]});
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the `manage_server` permission to use this command"
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
diff --git a/src/commands/settings/log/events.ts b/src/commands/settings/logs/events.ts
similarity index 100%
rename from src/commands/settings/log/events.ts
rename to src/commands/settings/logs/events.ts
diff --git a/src/commands/settings/logs/ignore.ts b/src/commands/settings/logs/ignore.ts
new file mode 100644
index 0000000..4b66307
--- /dev/null
+++ b/src/commands/settings/logs/ignore.ts
@@ -0,0 +1,119 @@
+import { ChannelType } from 'discord-api-types';
+import Discord, { CommandInteraction } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import generateEmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import { WrappedCheck } from "jshaiku";
+import confirmationMessage from '../../../utils/confirmationMessage.js';
+import keyValueList from '../../../utils/generateKeyValueList.js';
+import client from '../../../utils/client.js';
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+    .setName("ignore")
+    .setDescription("Sets which users, channels and roles should be ignored")
+    .addStringOption(o => o.setName("action").setDescription("Add or remove from the list").addChoices([
+        ["Add", "add"], ["Remove", "remove"]
+    ]).setRequired(true))
+    .addChannelOption(o => o.setName("addchannel").setDescription("Add a channel that should be ignored").addChannelTypes([
+        ChannelType.GuildText, ChannelType.GuildVoice, ChannelType.GuildNews, ChannelType.GuildPublicThread, ChannelType.GuildPrivateThread, ChannelType.GuildNewsThread
+    ]))
+    .addUserOption(o => o.setName("adduser").setDescription("Add a user that should be ignored"))
+    .addRoleOption(o => o.setName("addrole").setDescription("Add a role that should be ignored"))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    let channel = interaction.options.getChannel("addchannel")
+    let user = interaction.options.getUser("adduser")
+    let role = interaction.options.getRole("addrole")
+    await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Loading")
+        .setStatus("Danger")
+        .setEmoji("NUCLEUS.LOADING")
+    ], ephemeral: true, fetchReply: true});
+    if (channel || user || role) {
+        if (channel) {
+            try {
+                channel = interaction.guild.channels.cache.get(channel.id)
+            } catch {
+                return await interaction.editReply({embeds: [new generateEmojiEmbed()
+                    .setEmoji("CHANNEL.TEXT.DELETE")
+                    .setTitle("Logs > Ignore")
+                    .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 generateEmojiEmbed()
+                    .setTitle("Logs > Ignore")
+                    .setDescription(`You must choose a channel in this server`)
+                    .setStatus("Danger")
+                    .setEmoji("CHANNEL.TEXT.DELETE")
+                ]});
+            }
+        }
+        if (user) {
+            try {
+                user = interaction.guild.members.cache.get(user.id).user
+            } catch {
+                return await interaction.editReply({embeds: [new generateEmojiEmbed()
+                    .setEmoji("USER.DELETE")
+                    .setTitle("Logs > Ignore")
+                    .setDescription("The user you provided is not a valid user")
+                    .setStatus("Danger")
+                ]})
+            }
+            user = user as Discord.User
+        }
+        if (role) {
+            try {
+                role = interaction.guild.roles.cache.get(role.id)
+            } catch {
+                return await interaction.editReply({embeds: [new generateEmojiEmbed()
+                    .setEmoji("ROLE.DELETE")
+                    .setTitle("Logs > Ignore")
+                    .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 generateEmojiEmbed()
+                    .setTitle("Logs > Ignore")
+                    .setDescription(`You must choose a role in this server`)
+                    .setStatus("Danger")
+                    .setEmoji("ROLE.DELETE")
+                ]});
+            }
+        }
+        let changes = {}
+        if (channel) changes["channel"] = channel.id
+        if (user) changes["user"] = user.id
+        if (role) changes["role"] = role.id
+        let confirmation = await new confirmationMessage(interaction)
+            .setEmoji("NUCLEUS.COMMANDS.IGNORE")
+            .setTitle("Logs > Ignore")
+            .setDescription(keyValueList(changes)
+            + `Are you sure you want to **${interaction.options.getString("action") == "add" ? "add" : "remove"}** these to the ignore list?`)
+            .setColor("Warning")
+        .send(true)
+        if (confirmation.success) {
+            let data = client.database.read(interaction.guild.id)
+            if (channel) data.logging.logs.ignore.channels.concat([channel.id])
+            if (user) data.logging.logs.ignore.users.concat([user.id])
+            if (role) data.logging.logs.ignore.roles.concat([role.id])
+            if (interaction.options.getString("action") == "add") {
+                await client.database.append(interaction.guild.id, data)
+            }
+        }
+    }
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the `manage_server` permission to use this command"
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/settings/menu.ts b/src/commands/settings/menu.ts
deleted file mode 100644
index 9950fe4..0000000
--- a/src/commands/settings/menu.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { CommandInteraction, MessageEmbed, MessageSelectMenu } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-import readConfig from "../../utils/readConfig.js";
-import { toHexArray, toHexInteger, logs } from "../../utils/calculate.js"
-import { capitalize } from "../../utils/generateKeyValueList.js";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("menu")
-    .setDescription("Shows a full UI of all settings")
-
-const callback = async (interaction: CommandInteraction) => {
-    return
-    let config = await readConfig(interaction.guild.id);
-
-    let currentValues = toHexArray(config.logging.log.toLog);
-
-    let toLogDropdownOptions = []
-
-    for(let i of logs) {
-        if(currentValues.includes(i)) {
-            toLogDropdownOptions.push({
-                name: capitalize(i),
-                value: i,
-                emoji: "TICK"
-            })
-        } else {
-            toLogDropdownOptions.push({
-                label: capitalize(i),
-                value: i,
-                emoji: "CROSS"
-            })
-        }
-    }
-
-    let toLogDropdown = new MessageSelectMenu()
-        .setCustomId("log")
-        .setMaxValues(22)
-        .addOptions()
-
-    let embed = new MessageEmbed()
-
-    interaction.reply("This command is not yet finished [settings/all]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/mod/channel.ts b/src/commands/settings/mod/channel.ts
deleted file mode 100644
index 88c8396..0000000
--- a/src/commands/settings/mod/channel.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("channel")
-    .setDescription("Sets the channel for staff messages to go to")
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/mod/channel]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/mod/events.ts b/src/commands/settings/mod/events.ts
deleted file mode 100644
index be5de57..0000000
--- a/src/commands/settings/mod/events.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("events")
-    .setDescription("Sets which events mods should be notified about")
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/mod/events]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/mod/_meta.ts b/src/commands/settings/staff/_meta.ts
similarity index 100%
rename from src/commands/settings/mod/_meta.ts
rename to src/commands/settings/staff/_meta.ts
diff --git a/src/commands/settings/staff/channel.ts b/src/commands/settings/staff/channel.ts
new file mode 100644
index 0000000..69ace92
--- /dev/null
+++ b/src/commands/settings/staff/channel.ts
@@ -0,0 +1,131 @@
+import { ChannelType } from 'discord-api-types';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import generateEmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../../utils/confirmationMessage.js";
+import getEmojiByName from "../../../utils/getEmojiByName.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import client from "../../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+    .setName("channel")
+    .setDescription("Sets or shows the staff notifications channel")
+    .addChannelOption(option => option.setName("channel").setDescription("The channel to set the staff notifications channel to").addChannelTypes([
+        ChannelType.GuildNews, ChannelType.GuildText
+    ]))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    let m;
+    m = await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Loading")
+        .setStatus("Danger")
+        .setEmoji("NUCLEUS.LOADING")
+    ], ephemeral: true, fetchReply: true});
+    if (interaction.options.getChannel("channel")) {
+        let channel
+        try {
+            channel = interaction.options.getChannel("channel")
+        } catch {
+            return await interaction.editReply({embeds: [new generateEmojiEmbed()
+                .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 generateEmojiEmbed()
+                .setTitle("Staff Notifications Channel")
+                .setDescription(`You must choose a channel in this server`)
+                .setStatus("Danger")
+                .setEmoji("CHANNEL.TEXT.DELETE")
+            ]});
+        }
+        let confirmation = await new confirmationMessage(interaction)
+            .setEmoji("CHANNEL.TEXT.EDIT")
+            .setTitle("Staff Notifications Channel")
+            .setDescription(
+                `This will be the channel all notifications, updates, user reports etc. will be sent to.\n\n` +
+                `Are you sure you want to set the staff notifications channel to <#${channel.id}>?`
+            )
+            .setColor("Warning")
+            .setInverted(true)
+        .send(true)
+        if (confirmation.success) {
+            try {
+                await client.database.write(interaction.guild.id, {"logging.staff.channel": channel.id})
+            } catch (e) {
+                return interaction.editReply({embeds: [new generateEmojiEmbed()
+                    .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 generateEmojiEmbed()
+                .setTitle("Staff Notifications Channel")
+                .setDescription(`No changes were made`)
+                .setStatus("Success")
+                .setEmoji("CHANNEL.TEXT.CREATE")
+            ], components: []});
+        }
+    }
+    let clicks = 0;
+    let data = await client.database.read(interaction.guild.id);
+    let channel = data.logging.staff.channel;
+    while (true) {
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setTitle("Staff Notifications channel")
+            .setDescription(channel ? `Your staff notifications channel is currently set to <#${channel}>` : "This server does not have a staff notifications channel")
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+        ], components: [new MessageActionRow().addComponents([new MessageButton()
+            .setCustomId("clear")
+            .setLabel(clicks ? "Click again to confirm" : "Reset channel")
+            .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
+            .setStyle("DANGER")
+            .setDisabled(!channel)
+        ])]});
+        let i;
+        try {
+            i = await m.awaitMessageComponent({time: 600000});
+        } catch(e) { break }
+        i.deferUpdate()
+        if (i.component.customId == "clear") {
+            clicks += 1;
+            if (clicks == 2) {
+                clicks = 0;
+                await client.database.write(interaction.guild.id, {}, ["logging.staff.channel"])
+                channel = undefined;
+            }
+        } else {
+            break
+        }
+    }
+    await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Staff Notifications channel")
+        .setDescription(channel ? `Your staff notifications channel is currently set to <#${channel}>` : "This server does not have a staff notifications channel")
+        .setStatus("Success")
+        .setEmoji("CHANNEL.TEXT.CREATE")
+        .setFooter({text: "Message closed"})
+    ], components: [new MessageActionRow().addComponents([new MessageButton()
+        .setCustomId("clear")
+        .setLabel("Clear")
+        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+        .setStyle("SECONDARY")
+        .setDisabled(true)
+    ])]});
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the `manage_server` permission to use this command"
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index 64dc980..aac271e 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -1,24 +1,410 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import Discord, { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu, TextInputComponent } from "discord.js";
+import { SelectMenuOption, SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import { ChannelType } from 'discord-api-types';
+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";
 
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
+const command = (builder: SlashCommandSubcommandBuilder) => builder
     .setName("tickets")
-    .setDescription("Shows settings for tickets")
-    .addStringOption(option => option.setName("enabled").setDescription("If users should be able to create tickets | Default yes").setRequired(false)
+    .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([["Yes", "yes"], ["No", "no"]]))
     .addChannelOption(option => option.setName("category").setDescription("The category where tickets are created").addChannelType(ChannelType.GuildCategory).setRequired(false))
-    .addNumberOption(option => option.setName("maxtickets").setDescription("The maximum amount of tickets a user can create | Default 5").setRequired(false).setMinValue(1))
-    .addRoleOption(option => option.setName("supportping").setDescription("The role pinged when a ticket is created").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))
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/tickets]");
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    let m;
+    m = await interaction.reply({
+        embeds: [new generateEmojiEmbed()
+            .setTitle("Loading")
+            .setStatus("Danger")
+            .setEmoji("NUCLEUS.LOADING")
+        ], ephemeral: true, fetchReply: true
+    });
+    let options = {
+        enabled: interaction.options.getString("enabled") as string | boolean,
+        category: interaction.options.getChannel("category"),
+        maxtickets: interaction.options.getNumber("maxticketsperuser"),
+        supportping: interaction.options.getRole("supportrole")
+    }
+    if (options.enabled !== null || options.category || options.maxtickets || options.supportping) {
+        options.enabled = options.enabled === "yes" ? true : false;
+        if (options.category) {
+            let channel
+            try {
+                channel = interaction.guild.channels.cache.get(options.category.id)
+            } catch {
+                return await interaction.editReply({
+                    embeds: [new generateEmojiEmbed()
+                        .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 generateEmojiEmbed()
+                    .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 generateEmojiEmbed()
+                    .setTitle("Tickets > Max Tickets")
+                    .setDescription(`You must choose a number greater than 0`)
+                    .setStatus("Danger")
+                    .setEmoji("CHANNEL.TEXT.DELETE")
+                ]
+            });
+        }
+        let role
+        if (options.supportping) {
+            try {
+                role = interaction.guild.roles.cache.get(options.supportping.id)
+            } catch {
+                return await interaction.editReply({
+                    embeds: [new generateEmojiEmbed()
+                        .setEmoji("GUILD.ROLE.DELETE")
+                        .setTitle("Tickets > Support Ping")
+                        .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 generateEmojiEmbed()
+                    .setTitle("Tickets > Support Ping")
+                    .setDescription(`You must choose a role in this server`)
+                    .setStatus("Danger")
+                    .setEmoji("GUILD.ROLE.DELETE")
+                ]
+            });
+        }
+
+        let 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")
+            .setInverted(true)
+            .send(true)
+        if (confirmation.success) {
+            let toUpdate = {}
+            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.write(interaction.guild.id, toUpdate)
+            } catch (e) {
+                return interaction.editReply({
+                    embeds: [new generateEmojiEmbed()
+                        .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 generateEmojiEmbed()
+                    .setTitle("Tickets")
+                    .setDescription(`No changes were made`)
+                    .setStatus("Success")
+                    .setEmoji("GUILD.TICKET.OPEN")
+                ], components: []
+            });
+        }
+    }
+    let data = await client.database.read(interaction.guild.id);
+    data.tickets.customTypes = data.tickets.customTypes.filter((v, i, a) => a.indexOf(v) === i)
+    let lastClicked = "";
+    let embed;
+    data = {
+        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
+    }
+    while (true) {
+        embed = new generateEmojiEmbed()
+            .setTitle("Tickets")
+            .setDescription(
+                `${data.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${data.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`}\n` +
+                `${data.category ? "" : getEmojiByName("TICKETS.REPORT")} **Category:** ${data.category ? `<#${data.category}>` : "*None set*"}\n` +
+                `**Max Tickets:** ${data.maxTickets ? data.maxTickets : "*No limit*"}\n` +
+                `**Support Ping:** ${data.supportRole ? `<@&${data.supportRole}>` : "*None set*"}\n\n` +
+                ((data.useCustom && data.customTypes === null) ? `${getEmojiByName("TICKETS.REPORT")} ` : "") +
+                `${data.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")
+        m = await interaction.editReply({
+            embeds: [embed], components: [new MessageActionRow().addComponents([
+                new MessageButton()
+                    .setLabel("Tickets " + (data.enabled ? "enabled" : "disabled"))
+                    .setEmoji(getEmojiByName("CONTROL." + (data.enabled ? "TICK" : "CROSS"), "id"))
+                    .setStyle(data.enabled ? "SUCCESS" : "DANGER")
+                    .setCustomId("enabled"),
+                new MessageButton()
+                    .setLabel(lastClicked == "cat" ? "Click again to confirm" : "Clear category")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    .setStyle("DANGER")
+                    .setCustomId("clearCategory")
+                    .setDisabled(data.category == null),
+                new MessageButton()
+                    .setLabel(lastClicked == "max" ? "Click again to confirm" : "Reset max tickets")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    .setStyle("DANGER")
+                    .setCustomId("clearMaxTickets")
+                    .setDisabled(data.maxTickets == 5),
+                new MessageButton()
+                    .setLabel(lastClicked == "sup" ? "Click again to confirm" : "Clear support ping")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    .setStyle("DANGER")
+                    .setCustomId("clearSupportPing")
+                    .setDisabled(data.supportRole == null),
+            ]), new MessageActionRow().addComponents([
+                new MessageButton()
+                    .setLabel("Manage types")
+                    .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
+                    .setStyle("SECONDARY")
+                    .setCustomId("manageTypes"),
+            ])]
+        });
+        let i;
+        try {
+            i = await m.awaitMessageComponent({ time: 600000 });
+        } catch (e) { break }
+        i.deferUpdate()
+        if (i.component.customId == "clearCategory") {
+            if (lastClicked == "cat") {
+                lastClicked = "";
+                await client.database.write(interaction.guild.id, {}, ["tickets.category"])
+                data.category = undefined;
+            } else lastClicked = "cat";
+        } else if (i.component.customId == "clearMaxTickets") {
+            if (lastClicked == "max") {
+                lastClicked = "";
+                await client.database.write(interaction.guild.id, {}, ["tickets.maxTickets"])
+                data.maxTickets = 5;
+            } else lastClicked = "max";
+        } else if (i.component.customId == "clearSupportPing") {
+            if (lastClicked == "sup") {
+                lastClicked = "";
+                await client.database.write(interaction.guild.id, {}, ["tickets.supportRole"])
+                data.supportRole = undefined;
+            } else lastClicked = "sup";
+        } else if (i.component.customId == "enabled") {
+            await client.database.write(interaction.guild.id, { "tickets.enabled": !data.enabled })
+            data.enabled = !data.enabled;
+        } else if (i.component.customId == "manageTypes") {
+            data = await manageTypes(interaction, data, m);
+        } else {
+            break
+        }
+    }
+    await interaction.editReply({ embeds: [embed.setFooter({ text: "Message closed" })], components: [] });
 }
 
+async function manageTypes(interaction, data, m) {
+    while (true) {
+        if (data.useCustom) {
+            let customTypes = data.customTypes;
+            interaction.editReply({
+                embeds: [new generateEmojiEmbed()
+                    .setTitle("Tickets > Types")
+                    .setDescription(
+                        "**Custom types enabled**\n\n" +
+                        "**Types in use:**\n" + ((customTypes !== null) ?
+                            (customTypes.map((t) => `> ${t}`).join("\n")) :
+                            "*None set*"
+                        ) + "\n\n" + (customTypes === null ?
+                            `${getEmojiByName("TICKETS.REPORT")} Having no types will disable tickets. Please add at least 1 type or use default types` : ""
+                        )
+                    )
+                    .setStatus("Success")
+                    .setEmoji("GUILD.TICKET.OPEN")
+                ], components: (customTypes ? [
+                    new MessageActionRow().addComponents([new Discord.MessageSelectMenu()
+                        .setCustomId("removeTypes")
+                        .setPlaceholder("Select types to remove")
+                        .setMaxValues(customTypes.length)
+                        .setMinValues(1)
+                        .addOptions(customTypes.map((t) => new SelectMenuOption().setLabel(t).setValue(t)))
+                    ])
+                ] : []).concat([
+                    new MessageActionRow().addComponents([
+                        new MessageButton()
+                            .setLabel("Back")
+                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                            .setStyle("PRIMARY")
+                            .setCustomId("back"),
+                        new MessageButton()
+                            .setLabel("Add new type")
+                            .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+                            .setStyle("PRIMARY")
+                            .setCustomId("addType")
+                            .setDisabled(customTypes !== null && customTypes.length >= 25),
+                        new MessageButton()
+                            .setLabel("Switch to default types")
+                            .setStyle("SECONDARY")
+                            .setCustomId("switchToDefault"),
+                    ])
+                ])
+            });
+        } else {
+            let inUse = toHexArray(data.types, ticketTypes)
+            let options = [];
+            ticketTypes.forEach(type => {
+                options.push(new SelectMenuOption({
+                    label: capitalize(type),
+                    value: type,
+                    emoji: interaction.client.emojis.cache.get(getEmojiByName(`TICKETS.${type.toUpperCase()}`, "id")),
+                    default: inUse.includes(type)
+                }))
+            })
+            let selectPane = new MessageActionRow().addComponents([
+                new Discord.MessageSelectMenu()
+                    .addOptions(options)
+                    .setCustomId("types")
+                    .setMaxValues(ticketTypes.length)
+                    .setMinValues(1)
+                    .setPlaceholder("Select types to use")
+            ])
+            interaction.editReply({
+                embeds: [new generateEmojiEmbed()
+                    .setTitle("Tickets > Types")
+                    .setDescription(
+                        "**Default types enabled**\n\n" +
+                        "**Types in use:**\n" +
+                        (inUse.map((t) => `> ${getEmojiByName("TICKETS." + t.toUpperCase())} ${capitalize(t)}`).join("\n"))
+                    )
+                    .setStatus("Success")
+                    .setEmoji("GUILD.TICKET.OPEN")
+                ], components: [
+                    selectPane,
+                    new MessageActionRow().addComponents([
+                        new MessageButton()
+                            .setLabel("Back")
+                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                            .setStyle("PRIMARY")
+                            .setCustomId("back"),
+                        new MessageButton()
+                            .setLabel("Switch to custom types")
+                            .setStyle("SECONDARY")
+                            .setCustomId("switchToCustom"),
+                    ])
+                ]
+            });
+        }
+        let i;
+        try {
+            i = await m.awaitMessageComponent({ time: 600000 });
+        } catch (e) { break }
+        if (i.component.customId == "types") {
+            i.deferUpdate()
+            let types = toHexInteger(i.values, ticketTypes);
+            await client.database.write(interaction.guild.id, { "tickets.types": types })
+            data.types = types;
+        } else if (i.component.customId == "removeTypes") {
+            i.deferUpdate()
+            let 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.write(interaction.guild.id, { "tickets.customTypes": customTypes })
+                data.customTypes = customTypes;
+            }
+        } else if (i.component.customId == "addType") {
+            await i.showModal(new Discord.Modal().setCustomId("modal").setTitle("Enter a name for the new type").addComponents(
+                // @ts-ignore
+                new MessageActionRow().addComponents(new TextInputComponent()
+                    .setCustomId("type")
+                    .setLabel("Name")
+                    .setMaxLength(100)
+                    .setMinLength(1)
+                    .setPlaceholder("E.g. \"Server Idea\"")
+                    .setRequired(true)
+                    .setStyle("SHORT")
+                )
+            ))
+            await interaction.editReply({
+                embeds: [new generateEmojiEmbed()
+                    .setTitle("Tickets > Types")
+                    .setDescription("Modal opened. If you can't see it, click back and try again.")
+                    .setStatus("Success")
+                    .setEmoji("GUILD.TICKET.OPEN")
+                ], components: [new MessageActionRow().addComponents([new MessageButton()
+                    .setLabel("Back")
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                    .setStyle("PRIMARY")
+                    .setCustomId("back")
+                ])]
+            });
+            let out
+            try {
+                out = await modalInteractionCollector(m, (m) => m.channel.id == interaction.channel.id, (m) => m.customId == "addType")
+            } catch (e) { continue }
+            if (out.fields) {
+                let toAdd = out.fields.getTextInputValue("type");
+                if (!toAdd) { continue }
+                try {
+                    await client.database.append(interaction.guild.id, "tickets.customTypes", toAdd)
+                } catch { continue }
+                data.customTypes = data.customTypes || [];
+                if (!data.customTypes.includes(toAdd)) {
+                    data.customTypes.push(toAdd);
+                }
+            } else { continue }
+        } else if (i.component.customId == "switchToDefault") {
+            i.deferUpdate()
+            await client.database.write(interaction.guild.id, { "tickets.useCustom": false }, [])
+            data.useCustom = false;
+        } else if (i.component.customId == "switchToCustom") {
+            i.deferUpdate()
+            await client.database.write(interaction.guild.id, { "tickets.useCustom": true }, [])
+            data.useCustom = true;
+        } else {
+            i.deferUpdate()
+            break
+        }
+    }
+    return data
+}
+
+
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return interaction.memberPermissions.has("MANAGE_GUILD");
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the `manage_server` permission to use this command"
+    return true;
 }
 
 export { command };
diff --git a/src/commands/settings/verify/role.ts b/src/commands/settings/verify/role.ts
index c4de7af..dc1d4d9 100644
--- a/src/commands/settings/verify/role.ts
+++ b/src/commands/settings/verify/role.ts
@@ -1,20 +1,126 @@
-import { CommandInteraction } from "discord.js";
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import generateEmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../../utils/confirmationMessage.js";
+import getEmojiByName from "../../../utils/getEmojiByName.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
+import client from "../../../utils/client.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("role")
-    .setDescription("Sets the role given after verifying")
+    .setDescription("Sets or shows the role given to users after using /verify")
+    .addRoleOption(option => option.setName("role").setDescription("The role to give after verifying"))
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/verify/role]");
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    let m;
+    m = await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Loading")
+        .setStatus("Danger")
+        .setEmoji("NUCLEUS.LOADING")
+    ], ephemeral: true, fetchReply: true});
+    if (interaction.options.getRole("role")) {
+        let role
+        try {
+            role = interaction.options.getRole("role")
+        } catch {
+            return await interaction.editReply({embeds: [new generateEmojiEmbed()
+                .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 generateEmojiEmbed()
+                .setTitle("Verify Role")
+                .setDescription(`You must choose a role in this server`)
+                .setStatus("Danger")
+                .setEmoji("GUILD.ROLES.DELETE")
+            ]});
+        }
+        let 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")
+            .setInverted(true)
+        .send(true)
+        if (confirmation.success) {
+            try {
+                await client.database.write(interaction.guild.id, {"verify.role": role.id, "verify.enabled": true});
+            } catch (e) {
+                console.log(e)
+                return interaction.editReply({embeds: [new generateEmojiEmbed()
+                    .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 generateEmojiEmbed()
+                .setTitle("Verify Role")
+                .setDescription(`No changes were made`)
+                .setStatus("Success")
+                .setEmoji("GUILD.ROLES.CREATE")
+            ], components: []});
+        }
+    }
+    let clicks = 0;
+    let data = await client.database.read(interaction.guild.id);
+    let role = data.verify.role;
+    while (true) {
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .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 MessageActionRow().addComponents([new MessageButton()
+            .setCustomId("clear")
+            .setLabel(clicks ? "Click again to confirm" : "Reset role")
+            .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
+            .setStyle("DANGER")
+            .setDisabled(!role)
+        ])]});
+        let i;
+        try {
+            i = await m.awaitMessageComponent({time: 600000});
+        } catch(e) { break }
+        i.deferUpdate()
+        if (i.component.customId == "clear") {
+            clicks += 1;
+            if (clicks == 2) {
+                clicks = 0;
+                await client.database.write(interaction.guild.id, {}, ["verify.role", "verify.enabled"])
+                role = undefined;
+            }
+        } else {
+            break
+        }
+    }
+    await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Verify Role")
+        .setDescription(role ? `Your verify role is currently set to <@&${role}}>` : `You have not set a verify role`)
+        .setStatus("Success")
+        .setEmoji("GUILD.ROLE.CREATE")
+        .setFooter({text: "Message closed"})
+    ], components: [new MessageActionRow().addComponents([new MessageButton()
+        .setCustomId("clear")
+        .setLabel("Clear")
+        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+        .setStyle("SECONDARY")
+        .setDisabled(true)
+    ])]});
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the `manage_server` permission to use this command"
     return true;
 }
 
 export { command };
 export { callback };
-export { check };
\ No newline at end of file
+export { check };
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index a5fcfc1..8d6d8c9 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -1,12 +1,14 @@
 import { CommandInteraction } from "discord.js";
 import { SlashCommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
+import { callback as statsChannelAdd } from '../automations/statsChannelAdd.js';
 
 const command = new SlashCommandBuilder()
     .setName("tag")
     .setDescription("Get and manage the servers tags")
 
 const callback = (interaction: CommandInteraction) => {
+    try { statsChannelAdd(interaction.client, interaction.member); } catch {} // TODO: REMOVE THIS FOR PRODUCTION
     interaction.reply("This command is not yet finished [tag]");
 }
 
diff --git a/src/commands/tags/_meta.ts b/src/commands/tags/_meta.ts
new file mode 100644
index 0000000..8c07682
--- /dev/null
+++ b/src/commands/tags/_meta.ts
@@ -0,0 +1,4 @@
+const name = "tags";
+const description = "manage server tags";
+
+export { name, description };
\ No newline at end of file
diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts
new file mode 100644
index 0000000..e53f94f
--- /dev/null
+++ b/src/commands/tags/create.ts
@@ -0,0 +1,87 @@
+import Discord, { CommandInteraction } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import client from "../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+    .setName("create")
+    .setDescription("Creates a tag")
+    .addStringOption(o => o.setName("name").setRequired(true).setDescription("The name of the tag"))
+    .addStringOption(o => o.setName("value").setRequired(true).setDescription("The value of the tag, shown after running /tag name"))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    let name = interaction.options.getString("name");
+    let value = interaction.options.getString("value");
+    if (name.length > 100) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Create")
+        .setDescription("Tag names cannot be longer than 100 characters")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    if (value.length > 1000) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Create")
+        .setDescription("Tag values cannot be longer than 1000 characters")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    let data = await client.database.read(interaction.guild.id);
+    if (data.tags.length >= 100) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Create")
+        .setDescription("You cannot have more than 100 tags")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    if (data.tags[name]) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Create")
+        .setDescription("That tag already exists")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    let confirmation = await new confirmationMessage(interaction)
+    .setEmoji("PUNISH.NICKNAME.YELLOW")
+        .setTitle("Tag create")
+        .setDescription(keyValueList({
+            "name": `${name}`,
+            "value": `\n> ${value}`
+        })
+        + `\nAre you sure you want to create this tag?`)
+        .setColor("Warning")
+        .setInverted(true)
+    .send()
+    if (!confirmation) return await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Create")
+        .setDescription("No changes were made")
+        .setStatus("Success")
+        .setEmoji("PUNISH.NICKNAME.GREEN")
+    ]});
+    try {
+        await client.database.write(interaction.guild.id, {[`tags.${name}`]: value});
+    } catch (e) {
+        return await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setTitle("Tag Create")
+            .setDescription("Something went wrong and the tag was not created")
+            .setStatus("Danger")
+            .setEmoji("PUNISH.NICKNAME.RED")
+        ], components: []});
+    }
+    return await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Create")
+        .setDescription("Tag created")
+        .setStatus("Success")
+        .setEmoji("PUNISH.NICKNAME.GREEN")
+    ], components: []});
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_MESSAGES")) throw "You must have the `manage_messages` permission to use this command"
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/tags/delete.ts b/src/commands/tags/delete.ts
new file mode 100644
index 0000000..ff54ee5
--- /dev/null
+++ b/src/commands/tags/delete.ts
@@ -0,0 +1,69 @@
+import Discord, { CommandInteraction } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import client from "../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+    .setName("delete")
+    .setDescription("Deletes a tag")
+    .addStringOption(o => o.setName("name").setRequired(true).setDescription("The name of the tag"))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    let name = interaction.options.getString("name");
+    let data = await client.database.read(interaction.guild.id);
+    if (!data.tags[name]) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tags")
+        .setDescription("That tag does not exist")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    let confirmation = await new confirmationMessage(interaction)
+        .setEmoji("PUNISH.NICKNAME.YELLOW")
+        .setTitle("Tag Delete")
+        .setDescription(keyValueList({
+            "name": `${name}`,
+            "value": `\n> ${data.tags[name]}`
+        })
+        + `\nAre you sure you want to delete this tag?`)
+        .setColor("Warning")
+        .setInverted(true)
+    .send()
+    if (!confirmation) return await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Delete")
+        .setDescription("No changes were made")
+        .setStatus("Success")
+        .setEmoji("PUNISH.NICKNAME.GREEN")
+    ]});
+    try {
+        data = await client.database.read(interaction.guild.id);
+        delete data.tags[name];
+        await client.database.write(interaction.guild.id, {tags: data});
+    } catch (e) {
+        return await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setTitle("Tag Delete")
+            .setDescription("Something went wrong and the tag was not deleted")
+            .setStatus("Danger")
+            .setEmoji("PUNISH.NICKNAME.RED")
+        ], components: []});
+    }
+    return await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Delete")
+        .setDescription("Tag deleted")
+        .setStatus("Success")
+        .setEmoji("PUNISH.NICKNAME.GREEN")
+    ], components: []});
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_MESSAGES")) throw "You must have the `manage_messages` permission to use this command"
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts
new file mode 100644
index 0000000..3cf73e5
--- /dev/null
+++ b/src/commands/tags/edit.ts
@@ -0,0 +1,102 @@
+import Discord, { CommandInteraction } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import client from "../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+    .setName("edit")
+    .setDescription("Edits or renames a tag")
+    .addStringOption(o => o.setName("name").setRequired(true).setDescription("The tag to edit"))
+    .addStringOption(o => o.setName("value").setRequired(false).setDescription("The new value of the tag / Rename"))
+    .addStringOption(o => o.setName("newname").setRequired(false).setDescription("The new name of the tag / Edit"))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    let name = interaction.options.getString("name");
+    let value = interaction.options.getString("value") || "";
+    let newname = interaction.options.getString("newname") || "";
+    if (!newname && !value) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Edit")
+        .setDescription("You must specify a value or a new name")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    if (newname.length > 100) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Edit")
+        .setDescription("Tag names cannot be longer than 100 characters")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    if (value.length > 2000) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Edit")
+        .setDescription("Tag values cannot be longer than 2000 characters")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    let data = await client.database.read(interaction.guild.id);
+    if (!data.tags[name]) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Edit")
+        .setDescription("That tag does not exist")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    if (newname && newname !== name && data.tags[newname]) return await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Edit")
+        .setDescription("A tag with that name already exists")
+        .setStatus("Danger")
+        .setEmoji("PUNISH.NICKNAME.RED")
+    ], ephemeral: true});
+    let confirmation = await new confirmationMessage(interaction)
+    .setEmoji("PUNISH.NICKNAME.YELLOW")
+        .setTitle("Tag Edit")
+        .setDescription(keyValueList({
+            "name": `${name}` + (newname ? ` -> ${newname}` : ""),
+            "value": `\n> ${value ? value : data.tags[name]}`
+        })
+        + `\nAre you sure you want to edit this tag?`)
+        .setColor("Warning")
+        .setInverted(true)
+    .send()
+    if (!confirmation) return await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tag Edit")
+        .setDescription("No changes were made")
+        .setStatus("Success")
+        .setEmoji("PUNISH.NICKNAME.GREEN")
+    ]});
+    try {
+        let toSet = {};
+        let toUnset = []
+        if (value) toSet[`tags.${name}`] = value;
+        if (newname) {
+            toUnset.push(`tags.${name}`);
+            toSet[`tags.${newname}`] = data.tags[name];
+        }
+        await client.database.write(interaction.guild.id, toSet, toUnset);
+    } catch (e) {
+        return await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setTitle("Tag Edit")
+            .setDescription("Something went wrong and the tag was not edited")
+            .setStatus("Danger")
+            .setEmoji("PUNISH.NICKNAME.RED")
+        ], components: []});
+    }
+    return await interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Tags")
+        .setDescription("Tag edited successfully")
+        .setStatus("Success")
+        .setEmoji("PUNISH.NICKNAME.GREEN")
+    ], components: []});
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_MESSAGES")) throw "You must have the `manage_messages` permission to use this command"
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/tags/list.ts b/src/commands/tags/list.ts
new file mode 100644
index 0000000..bf234d5
--- /dev/null
+++ b/src/commands/tags/list.ts
@@ -0,0 +1,150 @@
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import client from "../../utils/client.js";
+import { SelectMenuOption } from '@discordjs/builders';
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import createPageIndicator from "../../utils/createPageIndicator.js";
+
+
+class Embed {
+    embed: Discord.MessageEmbed;
+    title: string;
+    description: string = "";
+    pageId: number = 0;
+    setEmbed(embed: Discord.MessageEmbed) { this.embed = embed; return this; }
+    setTitle(title: string) { this.title = title; return this; }
+    setDescription(description: string) { this.description = description; return this; }
+    setPageId(pageId: number) { this.pageId = pageId; return this; }
+}
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+        .setName("list")
+        .setDescription("Lists all tags in the server")
+
+const callback = async (interaction: CommandInteraction) => {
+    let data = await client.database.read(interaction.guild.id);
+    let tags = data.getKey("tags");
+    console.log(tags)
+    let strings = []
+    if (data === {}) strings = ["*No tags exist*"]
+    else {
+        let string = ""
+        for (let tag in tags) {
+            let proposed = `**${tag}:** ${tags[tag]}\n`
+            if (string.length + proposed.length > 2000) {
+                strings.push(string.slice(0, -1))
+                string = ""
+            }
+            console.log(string)
+            string += proposed
+        }
+        strings.push(string.slice(0, -1))
+    }
+
+    let pages = []
+    for (let string of strings) {
+        pages.push(new Embed()
+            .setEmbed(new generateEmojiEmbed()
+                .setTitle("Tags")
+                .setDescription(string)
+                .setEmoji("PUNISH.NICKNAME.GREEN")
+                .setStatus("Success")
+            ).setTitle(`Page ${pages.length + 1}`).setPageId(pages.length))
+    }
+    let m;
+    m = await interaction.reply({
+        embeds: [
+            new generateEmojiEmbed()
+                .setTitle("Welcome")
+                .setDescription(`One moment...`)
+                .setStatus("Danger")
+                .setEmoji("NUCLEUS.LOADING")
+        ], fetchReply: true, ephemeral: true
+    });
+    let page = 0;
+    let selectPaneOpen = false;
+    while (true) {
+        let selectPane = []
+
+        if (selectPaneOpen) {
+            let options = [];
+            pages.forEach(embed => {
+                options.push(new SelectMenuOption({
+                    label: embed.title,
+                    value: embed.pageId.toString(),
+                    description: embed.description || "",
+                }))
+            })
+            selectPane = [new MessageActionRow().addComponents([
+                new Discord.MessageSelectMenu()
+                    .addOptions(options)
+                    .setCustomId("page")
+                    .setMaxValues(1)
+                    .setPlaceholder("Choose a page...")
+            ])]
+        }
+        let em = new Discord.MessageEmbed(pages[page].embed)
+        em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
+        await interaction.editReply({
+            embeds: [em],
+            components: selectPane.concat([new MessageActionRow().addComponents([
+                new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(page === 0),
+                new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle(selectPaneOpen ? "PRIMARY" : "SECONDARY").setDisabled(false),
+                new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(page === pages.length - 1),
+                new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER")
+            ])])
+        });
+        let i
+        try {
+            i = await m.awaitMessageComponent({time: 600000 });
+        } catch (e) { break }
+        i.deferUpdate()
+        if (i.component.customId == "left") {
+            if (page > 0) page--;
+            selectPaneOpen = false;
+        } else if (i.component.customId == "right") {
+            if (page < pages.length - 1) page++;
+            selectPaneOpen = false;
+        } else if (i.component.customId == "select") {
+            selectPaneOpen = !selectPaneOpen;
+        } else if (i.component.customId == "page") {
+            page = parseInt(i.values[0]);
+            selectPaneOpen = false;
+        } else {
+            let em = new Discord.MessageEmbed(pages[page].embed)
+            em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message closed");
+            await interaction.editReply({
+                embeds: [em], components: [new MessageActionRow().addComponents([
+                    new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(true),
+                    new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle(selectPaneOpen ? "PRIMARY" : "SECONDARY").setDisabled(true),
+                    new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(true),
+                    new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER").setDisabled(true)
+                ])]
+            })
+            return;
+        }
+    }
+    let em = new Discord.MessageEmbed(pages[page])
+    em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message timed out");
+    await interaction.editReply({
+        embeds: [em],
+        components: [new MessageActionRow().addComponents([
+            new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(true),
+            new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle("SECONDARY").setDisabled(true),
+            new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(true),
+            new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER").setDisabled(true)
+        ])]
+    });
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts
index be32f41..f96f718 100644
--- a/src/commands/user/track.ts
+++ b/src/commands/user/track.ts
@@ -3,9 +3,8 @@
 import { WrappedCheck } from "jshaiku";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
-import generateKeyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from "../../utils/readConfig.js";
 import addPlural from "../../utils/plurals.js";
+import client from "../../utils/client.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -24,10 +23,10 @@
 
 const callback = async (interaction: CommandInteraction) => {
     // @ts-ignore
-    const { renderUser } = interaction.client.logger
+    const { renderUser } = interaction.client.logger;
     const member = interaction.options.getMember("user") as GuildMember;
     const guild = interaction.guild;
-    let config = await readConfig(guild.id);
+    let config = await client.database.read(guild.id);
     await interaction.reply({embeds: [new generateEmojiEmbed()
         .setEmoji("NUCLEUS.LOADING")
         .setTitle("Loading")
@@ -161,14 +160,19 @@
     }
 }
 
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+const check = async (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
     let member = (interaction.member as GuildMember)
     // Allow the owner to promote anyone
     if (member.id == interaction.guild.ownerId) return true
+    // Check if the user can manage any of the tracks
+    // @ts-ignore
+    let tracks = (await interaction.client.database.get(interaction.guild.id)).tracks
+    let managed = false
+    tracks.forEach(element => { if (element.track.manageableBy.some(role => member.roles.cache.has(role))) managed = true });
     // Check if the user has manage_roles permission
-    if (! member.permissions.has("MANAGE_ROLES")) throw "You do not have the `manage_roles` permission";
+    if (!managed && ! member.permissions.has("MANAGE_ROLES")) throw "You do not have the `manage_roles` permission";
     // Allow track
-    return true // TODO: allow if the member has manage perms
+    return true;
 }
 
 export { command };
diff --git a/src/config/default.json b/src/config/default.json
index 129bfe9..3b0882f 100644
--- a/src/config/default.json
+++ b/src/config/default.json
@@ -57,14 +57,18 @@
         "logs": {
             "enabled": true,
             "channel": null,
-            "toLog": "3fffff"
+            "toLog": "3fffff",
+            "ignore": {
+                "users": [],
+                "roles": [],
+                "channels": []
+            }
         },
         "staff": {
             "channel": null
         }
     },
     "verify": {
-        "enabled": false,
         "role": null
     },
     "tickets": {
@@ -72,6 +76,7 @@
         "category": null,
         "types": "3f",
         "customTypes": null,
+        "useCustom": false,
         "supportRole": null,
         "maxTickets": 5
     },
diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts
index 883efd9..ec788f7 100644
--- a/src/events/channelCreate.ts
+++ b/src/events/channelCreate.ts
@@ -1,5 +1,3 @@
-import { Interaction } from "discord.js";
-
 export const event = 'channelCreate'
 
 export async function callback(client, channel) {
diff --git a/src/index.ts b/src/index.ts
index 2616f40..ca14cdb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -18,5 +18,4 @@
 client.memory = new Memory()
 client.database = await new Database(config.mongoUrl).connect()
 
-
 await client.login();
\ No newline at end of file
diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts
index 26c231c..8a297b9 100644
--- a/src/utils/calculate.ts
+++ b/src/utils/calculate.ts
@@ -1,13 +1,13 @@
 const logs = [
     "channelUpdate",
-    "channelPinsUpdate",
+    "channelPinsUpdate", // TODO
     "emojiUpdate",
-    "stickerUpdate",
+    "stickerUpdate", // TODO
     "guildUpdate",
     "guildMemberUpdate",
     "guildMemberPunish",
-    "guildEventUpdate",
-    "guildEventMemberUpdate",
+    "guildEventUpdate", // TODO
+    "guildEventMemberUpdate", // TODO
     "guildRoleUpdate",
     "guildInviteUpdate",
     "messageUpdate",
@@ -16,11 +16,11 @@
     "messageReactionUpdate",
     "messagePing",
     "messageMassPing",
-    "messageAnnounce",
+    "messageAnnounce", // TODO
     "stageUpdate",
     "threadUpdate",
-    "voiceStateUpdate",
-    "webhookUpdate"
+    "voiceStateUpdate", // TODO
+    "webhookUpdate" // TODO
 ]
 
 const tickets = [
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 53ccb97..4e37652 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -1,8 +1,10 @@
 import { Collection, Db, MongoClient } from 'mongodb';
+import structuredClone from '@ungap/structured-clone';
 
 
 export const Entry = data => {
     data = data ?? {};
+    data.getKey = key => data[key]
     return {
         get(target, prop, receiver) {
             let dataToReturn = data[prop]
@@ -38,11 +40,42 @@
 
     async read(guild: string) {
         let entry = await this.guilds.findOne({ id: guild });
-        return new Proxy(this.defaultData, Entry(entry)) as unknown as GuildConfig
+        return new Proxy(structuredClone(this.defaultData), Entry(entry)) as unknown as GuildConfig
     }
 
-    async write(guild: string, config: GuildConfig) {
-        await this.guilds.updateOne({ id: guild }, { $set: config }, { upsert: true });
+    async write(guild: string, set: object = {}, unset: string[] = []) {
+        let uo = {}
+        for (let key of unset) {
+            uo[key] = "";
+        }
+        await this.guilds.updateOne({ id: guild }, {
+            $unset: uo,
+            $set: set
+        }, { upsert: true });
+    }
+
+    async append(guild: string, key: string, value: any) {
+        if (Array.isArray(value)) {
+            await this.guilds.updateOne({ id: guild }, {
+                $addToSet: { [key]: { $each: value } }
+            }, { upsert: true });
+        } else {
+            await this.guilds.updateOne({ id: guild }, {
+                $addToSet: { [key]: value }
+            }, { upsert: true });
+        }
+    }
+
+    async remove(guild: string, key: string, value: any) {
+        if (Array.isArray(value)) {
+            await this.guilds.updateOne({ id: guild }, {
+                $pullAll: { [key]: value }
+            }, { upsert: true });
+        } else {
+            await this.guilds.updateOne({ id: guild }, {
+                $pullAll: { [key]: [value] }
+            }, { upsert: true });
+        }
     }
 }
 
@@ -94,64 +127,65 @@
         enabled: boolean,
         verificationRequired: {
             message: boolean,
-            role: string
+            role: string | null
         },
-        welcomeRole: string,
-        channel: string,
-        message: string
+        welcomeRole: string | null,
+        channel: string | null,
+        message: string | null,
     }
     stats: {
         enabled: boolean,
-        channel: string,
-        text: string
+        channel: string | null,
+        text: string | null,
     }[]
     logging: {
         logs: {
             enabled: boolean,
-            channel: string,
-            toLog: string
+            channel: string | null,
+            toLog: string | null,
         },
         staff: {
-            channel: string
+            channel: string | null,
         }
     }
     verify: {
         enabled: boolean,
-        role: string
+        role: string | null,
     }
     tickets: {
         enabled: boolean,
-        category: string,
-        types: string,
+        category: string | null,
+        types: string | null,
         customTypes: string[],
-        supportRole: string,
+        useCustom: boolean,
+        supportRole: string | null,
         maxTickets: number
     }
     moderation: {
         mute: {
             timeout: boolean,
-            role: string,
-            text: string,
-            link: string
+            role: string | null,
+            text: string | null,
+            link: string | null
         },
         kick: {
-            text: string,
-            link: string
+            text: string | null,
+            link: string | null
         },
         ban: {
-            text: string,
-            link: string
+            text: string | null,
+            link: string | null
         },
         softban: {
-            text: string,
-            link: string
+            text: string | null,
+            link: string | null
         },
         warn: {
-            text: string,
-            link: string
+            text: string | null,
+            link: string | null
         },
         role: {
-            role: string
+            role: string | null,
         }
     }
     tracks: {
diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts
new file mode 100644
index 0000000..ae63757
--- /dev/null
+++ b/src/utils/dualCollector.ts
@@ -0,0 +1,49 @@
+import Discord from 'discord.js';
+import client from './client.js';
+import generateEmojiEmbed from "./generateEmojiEmbed.js";
+
+export default async function (m, interactionFilter, messageFilter) {
+    let out;
+    try {
+        out = await new Promise((resolve, reject) => {
+            let mes, int;
+            mes = m.createMessageComponentCollector({filter: (m) => interactionFilter(m), time: 600000})
+                .on("collect", (m) => { resolve(m); })
+            int = m.channel.createMessageCollector({filter: (m) => messageFilter(m), time: 600000})
+                .then("collect", (m) => { try {m.delete();} catch {}; resolve(m); })
+            mes.on("end", () => { int.stop(); })
+            int.on("end", () => { mes.stop(); })
+        })
+    } catch(e) {
+        console.log(e)
+        return null;
+    }
+
+    return out;
+}
+
+export async function modalInteractionCollector(m, modalFilter, interactionFilter) {
+    let out;
+    try {
+        out = await new Promise((resolve, reject) => {
+            let mod, int;
+            int = m.createMessageComponentCollector({filter: (m) => interactionFilter(m), time: 600000})
+                .on("collect", (m) => { resolve(m); })
+            mod = new Discord.InteractionCollector(
+                client, {
+                    filter: (m) => modalFilter(m),
+                    time: 600000
+                })
+                .on("collect", async (m) => {
+                    int.stop();
+                    (m as Discord.ModalSubmitInteraction).deferUpdate()
+                    resolve((m as Discord.ModalSubmitInteraction)); })
+            int.on("end", () => { mod.stop(); })
+            mod.on("end", () => { int.stop(); })
+        })
+    } catch(e) {
+        console.log(e)
+        return null;
+    }
+    return out;
+}
\ No newline at end of file
diff --git a/src/utils/generateConfig.ts b/src/utils/generateConfig.ts
deleted file mode 100644
index 39b28b0..0000000
--- a/src/utils/generateConfig.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as fs from 'fs';
-
-function writeLogConfig(guild, logs) {
-    if( !fs.existsSync(`./data/guilds/${guild.id}/config.json`) ) {
-        fs.rmSync(`./data/guilds/${guild.id}/config.json`);
-    }
-    if( !fs.existsSync(`./data/guilds/${guild.id}/pins.json`) ) {
-        let pins = guild.channels.cache.filter(c => c.type === "GUILD_TEXT").map(
-            c => c.messages.fetchPinned().then(m => m.map(m => m.id))
-        );
-        fs.writeFileSync(`./data/guilds/${guild.id}/pins.json`, JSON.stringify(pins));
-    }
-    if( !fs.existsSync(`./data/guilds/${guild.id}/logs.json`) ) {
-        fs.writeFileSync(`./data/guilds/${guild.id}/logs.json`, JSON.stringify([]));
-    } else if( logs ) {
-        fs.rmSync(`./data/guilds/${guild.id}/logs.json`);
-        fs.writeFileSync(`./data/guilds/${guild.id}/logs.json`, JSON.stringify([]));
-    }
-    fs.writeFileSync(`./data/guilds/${guild.id}/config.json`, JSON.stringify({
-        metadata: {
-            premium: false
-        },
-        logs: {
-            enabled: true,
-            logChannel: guild.systemChannelId,
-            toLog: "8be71",
-            toIgnore: {
-                bots: false,
-                channels: [],
-                members: [],
-                roles: []
-            }
-        },
-        userVerification: {
-            enabled: false,
-            roleID: null,
-            customMessage: null
-        },
-        modmail: {
-            enabled: false,
-            categoryId: null,
-            namingScheme: "rsm-{user}-{discriminator}",
-        },
-        welcome: {
-            enabled: false,
-            channelId: null,
-            message: null,
-            messageType: "embed",
-        },
-        filters: {
-            images: {
-                NSFW: true,
-                size: true
-            },
-            malware: true,
-            wordFilter: {
-                enabled: true,
-                words: {
-                    strict: [],
-                    loose: []
-                },
-                allowed: {
-                    users: [],
-                    roles: [],
-                    channels: []
-                }
-            },
-            invite: {
-                enabled: true,
-                allowed: {
-                    users: [],
-                    channels: [],
-                    roles: []
-                }
-            },
-            pings: {
-                mass: 5,
-                everyone: true,
-                roles: true,
-                allowed: {
-                    roles: [],
-                    rolesToMention: [],
-                    users: [],
-                    channels: []
-                }
-            }
-        },
-        tags: {}
-    }));
-}
-
-export default writeLogConfig;
\ No newline at end of file
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 238b6f4..19eb2b6 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -1,10 +1,10 @@
 import * as fs from 'fs';
 import * as Discord from 'discord.js';
 import getEmojiByName from './getEmojiByName.js';
-import readConfig from './readConfig.js';
 import { toHexArray } from './calculate.js';
 import { promisify } from 'util';
 import generateKeyValueList from './generateKeyValueList.js';
+import client from './client.js';
 
 const wait = promisify(setTimeout);
 
@@ -52,8 +52,8 @@
         return auditLog;
     }
 
-    async log(log: any, client): Promise<void> {
-        let config = await readConfig(log.hidden.guild);
+    async log(log: any): Promise<void> {
+        let config = await client.database.read(log.hidden.guild);
         if (!config.logging.logs.enabled) return;
         if (!(log.meta.calculateType == true)) {
             if(!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) return console.log('Not logging this type of event');
diff --git a/src/utils/memory.ts b/src/utils/memory.ts
index 0cbf955..7e21fa9 100644
--- a/src/utils/memory.ts
+++ b/src/utils/memory.ts
@@ -1,22 +1,31 @@
-import readConfig from "./readConfig.js";
+import client from "./client.js";
 
 class Memory {
     memory: {};
     constructor() {
         this.memory = {};
+
+        setInterval(() => {
+            for (let guild in this.memory) {
+                if (this.memory[guild].updated + 15 * 60 * 1000 < Date.now()) {
+                    delete this.memory[guild];
+                }
+            }
+        }, 1000 * 60 * 30)
     }
 
     async readGuildInfo(guild: string): Promise<object> {
         if (!this.memory[guild]) {
-            let guildData = await readConfig(guild);
+            let guildData = await client.database.read(guild);
             this.memory[guild] = {
+                lastUpdated: Date.now(),
                 filters: guildData.filters,
                 logging: guildData.logging,
                 tickets: guildData.tickets,
-            }; // TODO: REMOVE GUILD FROM MEMORY WHEN THESE UPDATE
-        } // TODO: Add a "lastAccessed" prop, delete after 15 minutes
+            };
+        };
         return this.memory[guild];
     }
 }
 
-export default Memory;
\ No newline at end of file
+export default Memory;
diff --git a/src/utils/readConfig.ts b/src/utils/readConfig.ts
deleted file mode 100644
index b363fc0..0000000
--- a/src/utils/readConfig.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import client from './client.js';
-
-export default async function readConfig(guild: string): Promise<any> {
-    return await client.database.read(guild);
-}
diff --git a/src/utils/singleNotify.ts b/src/utils/singleNotify.ts
index 4e9e6fe..a983478 100644
--- a/src/utils/singleNotify.ts
+++ b/src/utils/singleNotify.ts
@@ -1,4 +1,4 @@
-import readConfig from "./readConfig.js";
+import client from './client.js';
 import generateEmojiEmbed from "./generateEmojiEmbed.js";
 
 let severities = {
@@ -7,17 +7,18 @@
     "Info": "Success"
 }
 
-export default async function(client, type: string, guild: string, message: string, severity: string) {
-    let config = await readConfig(guild);
-    if (config.singleEventNotifications[type]) return;
-    // TODO: Set config.singleEventNotifications[type] to true
-    let channel = await client.channels.fetch(config.logging.staff);
-    if (!channel) return;
+export default async function(type: string, guild: string, message: string, severity: string) {
+    let data = await client.database.read(guild);
+    if (data.singleEventNotifications[type]) return;
+    data.singleEventNotifications[type] = true;
+    client.database.write(guild, data);
     try {
+        let channel = await client.channels.fetch(data.logging.staff.channel);
+        if (!channel) return;
         await channel.send({embeds: [new generateEmojiEmbed()
             .setTitle(`${severity} notification`)
             .setDescription(message)
-            .setColor(severities[severity])
+            .setStatus(severities[severity])
             .setEmoji("CONTROL.BLOCKCROSS")
         ]})
     } catch (err) {