loads of new commands, updates and bug fixes
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) {