Merge branch 'main' into development
diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts
index 1cbcee8..b7594ba 100644
--- a/src/commands/nucleus/stats.ts
+++ b/src/commands/nucleus/stats.ts
@@ -189,8 +189,8 @@
                     return;
                 }
                 const guild = (await client.guilds.fetch(GuildID)) as Guild | null;
+                await i.deferUpdate();
                 if (!guild) {
-                    await i.deferUpdate();
                     await interaction.editReply({
                         embeds: [
                             new EmojiEmbed().setTitle("Admin").setDescription("Not in server").setStatus("Danger")
@@ -200,7 +200,6 @@
                     return;
                 }
                 if (i.customId === "stats") {
-                    await i.deferUpdate();
                     await interaction.editReply({
                         embeds: [
                             new EmojiEmbed()
@@ -238,7 +237,6 @@
                         components: []
                     });
                 } else if (i.customId === "data") {
-                    await i.deferUpdate();
                     // Get all the data and convert to a string
                     const data = await client.database.guilds.read(guild.id);
                     const stringified = JSON.stringify(data, null, 2);
@@ -277,7 +275,6 @@
                         components: []
                     });
                 } else if (i.customId === "cache") {
-                    await i.deferUpdate();
                     await client.memory.forceUpdate(guild.id);
                     await interaction.editReply({
                         embeds: [
diff --git a/src/utils/database.ts b/src/utils/database.ts
index ddee338..abb638f 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -144,12 +144,14 @@
     }
 
     async staffChannels(): Promise<string[]> {
-        const entries = (await this.guilds
-            .find(
-                { "logging.staff.channel": { $exists: true } },
-                { projection: { "logging.staff.channel": 1, _id: 0 } }
-            )
-            .toArray()).map((e) => e.logging.staff.channel);
+        const entries = (
+            await this.guilds
+                .find(
+                    { "logging.staff.channel": { $exists: true } },
+                    { projection: { "logging.staff.channel": 1, _id: 0 } }
+                )
+                .toArray()
+        ).map((e) => e.logging.staff.channel);
         const out: string[] = [];
         for (const entry of entries) {
             if (entry) out.push(entry);
diff --git a/src/utils/getCommandDataByName.ts b/src/utils/getCommandDataByName.ts
index 46f4362..dcf45ca 100644
--- a/src/utils/getCommandDataByName.ts
+++ b/src/utils/getCommandDataByName.ts
@@ -1,22 +1,40 @@
 import type Discord from "discord.js";
 import client from "./client.js";
 
+/**
+ * @param name The name of the command, not including a leading slash. This can be space or slash separated e.g. "mod/about" or "mod about"
+ * @returns A string which when put into Discord will mention the command if the command exists or a codeblock with the command name if it does not
+ *
+ * @throws Will throw an error if as empty string is passed
+ **/
 export const getCommandMentionByName = (name: string): string => {
     const split = name.replaceAll("/", " ").split(" ");
-    const commandName: string = split[0]!;
+    const commandName: string | undefined = split[0];
+    if (commandName === undefined) throw new RangeError(`Invalid command ${name} provided to getCommandByName`);
 
     const filterCommand = (command: Discord.ApplicationCommand) => command.name === commandName;
 
     const command = client.fetchedCommands.filter((c) => filterCommand(c));
-    if (command.size === 0) return `\`/${name.replaceAll("/", " ")}\``;
-    const commandID = command.first()!.id;
+    const commandID = command.first()?.id;
+
+    if (commandID === undefined) return `\`/${name.replaceAll("/", " ")}\``;
+
     return `</${split.join(" ")}:${commandID}>`;
 };
 
+/**
+ * @param name The name of the command, not including a leading slash. This can be space or slash separated e.g. "mod/about" or "mod about"
+ * @returns An object containing the command name, the command description and a string which when put into Discord will mention the command
+ *
+ * @throws Will throw an error if the command doesn't exist
+ * @throws Will throw an error if as empty string is passed
+ **/
 export const getCommandByName = (name: string): { name: string; description: string; mention: string } => {
     const split = name.replaceAll(" ", "/");
-    const command = client.commands["commands/" + split]!;
-    // console.log(command)
+    const command = client.commands["commands/" + split];
+
+    if (command === undefined) throw new RangeError(`Invalid command ${name} provided to getCommandByName`);
+
     const mention = getCommandMentionByName(name);
     return {
         name: command[1].name,
diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts
index ebcb257..56f1444 100644
--- a/src/utils/getEmojiByName.ts
+++ b/src/utils/getEmojiByName.ts
@@ -18,7 +18,7 @@
 }
 getEmojiPaths(emojis);
 
-function getEmojiByName(name: (typeof EMOJIPATHS)[number], format?: string): string {
+function getEmojiByName(name: typeof EMOJIPATHS[number], format?: string): string {
     const parts = name.split(".");
     let id: string | EmojisIndex | EmojisIndex[] | undefined = emojis;
     for (const part of parts) {
diff --git a/src/utils/migration/migration.ts b/src/utils/migration/migration.ts
deleted file mode 100644
index 6286465..0000000
--- a/src/utils/migration/migration.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as fs from "fs";
-import client from "../client.js";
-import _ from "lodash";
-
-const dir = "./data";
-const files = fs.readdirSync(dir);
-
-for (const file of files) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    let rsmData: any;
-    try {
-        rsmData = JSON.parse(fs.readFileSync(`${dir}/${file}`, "utf8"));
-    } catch {
-        continue;
-    }
-    if (!rsmData.version || rsmData.version < 3) continue;
-    const nucleusData = await client.database.guilds.readOld(rsmData.guild_info.id);
-    const rsmToNucleus = {
-        id: rsmData.guild_info.id,
-        version: 1,
-        singleEventNotifications: {},
-        filters: {
-            images: {
-                NSFW: rsmData.images?.nsfw,
-                size: rsmData.images?.toosmall
-            },
-            malware: null,
-            wordFilter: {
-                enabled: true,
-                words: {
-                    strict: rsmData.wordfilter?.strict,
-                    loose: rsmData.wordfilter?.soft
-                },
-                allowed: {
-                    users: rsmData.wordfilter?.ignore?.members,
-                    roles: rsmData.wordfilter?.ignore?.roles,
-                    channels: rsmData.wordfilter?.ignore?.channels
-                }
-            },
-            invite: {
-                enabled: rsmData.invite?.enabled,
-                allowed: {
-                    channels: rsmData.invite?.whitelist?.members,
-                    roles: rsmData.invite?.whitelist?.roles,
-                    users: rsmData.invite?.whitelist?.channels
-                }
-            }
-        },
-        welcome: {
-            enabled: true,
-            role: rsmData.welcome?.role,
-            channel: rsmData.welcome?.message?.channel,
-            message: rsmData.welcome?.message?.text ?? null
-        },
-        logging: {
-            logs: {
-                enabled: true,
-                channel: rsmData.log_info?.log_channel
-            },
-            staff: {
-                channel: rsmData.log_info?.staff
-            }
-        },
-        verify: {
-            enabled: true,
-            role: rsmData.verify_role
-        },
-        tickets: {
-            enabled: true,
-            category: rsmData.modmail?.cat,
-            supportRole: rsmData.modmail?.mention,
-            maxTickets: rsmData.modmail?.max
-        },
-        tags: rsmData.tags
-    } as Partial<ReturnType<typeof client.database.guilds.read>>;
-    // console.log(rsmToNucleus)
-    const merged = _.merge(nucleusData, rsmToNucleus);
-    // console.log(merged)
-    await client.database.guilds.write(merged.id!, merged);
-}
diff --git a/src/utils/types/recursivePartial.d.ts b/src/utils/types/recursivePartial.d.ts
new file mode 100644
index 0000000..a578bb4
--- /dev/null
+++ b/src/utils/types/recursivePartial.d.ts
@@ -0,0 +1,7 @@
+type RecursivePartial<T> = {
+    [P in keyof T]?: T[P] extends (infer U)[]
+        ? RecursivePartial<U>[]
+        : T[P] extends object
+        ? RecursivePartial<T[P]>
+        : T[P];
+};