Performance testing
diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts
index 48ca49c..1c493bb 100644
--- a/src/actions/roleMenu.ts
+++ b/src/actions/roleMenu.ts
@@ -1,11 +1,10 @@
-import { Interaction, ButtonBuilder, CommandInteraction, Role, ButtonStyle } from "discord.js";
+import { Interaction, ButtonBuilder, CommandInteraction, Role, ButtonStyle, ButtonInteraction } from "discord.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import { ActionRowBuilder, SelectMenuBuilder } from "discord.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
 import client from "../utils/client.js";
 import { LoadingEmbed } from "../utils/defaultEmbeds.js";
 import type { GuildConfig } from "../utils/database.js";
-import type { ButtonComponent } from "@discordjs/builders";
 
 export interface RoleMenuSchema {
     guild: string;
@@ -17,7 +16,7 @@
     interaction: Interaction;
 }
 
-export async function callback(interaction: CommandInteraction) {
+export async function callback(interaction: CommandInteraction | ButtonInteraction) {
     if(!interaction.guild) return interaction.reply({ content: "This command can only be used in a server.", ephemeral: true });
     if(!interaction.member) return interaction.reply({ content: "You must be in a server to use this command.", ephemeral: true });
     const config = await client.database.guilds.read(interaction.guild.id);
diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts
index e1821ef..595a5b3 100644
--- a/src/actions/tickets/create.ts
+++ b/src/actions/tickets/create.ts
@@ -1,4 +1,4 @@
-import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js";
+import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, ButtonInteraction } from "discord.js";
 import { tickets, toHexArray } from "../../utils/calculate.js";
 import client from "../../utils/client.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@@ -9,7 +9,7 @@
     return s.length < 3 ? s.toUpperCase() : s[0].toUpperCase() + s.slice(1).toLowerCase();
 }
 
-export default async function (interaction: CommandInteraction) {
+export default async function (interaction: CommandInteraction | ButtonInteraction) {
     if (!interaction.guild) return;
     const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger;
 
diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts
index 33860b7..cab38ec 100644
--- a/src/actions/tickets/delete.ts
+++ b/src/actions/tickets/delete.ts
@@ -1,9 +1,9 @@
-import Discord, { ButtonBuilder, ActionRowBuilder, ButtonStyle } from "discord.js";
+import Discord, { ButtonBuilder, ActionRowBuilder, ButtonStyle, ButtonInteraction } from "discord.js";
 import client from "../../utils/client.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 
-export default async function (interaction: Discord.CommandInteraction) {
+export default async function (interaction: Discord.CommandInteraction | ButtonInteraction) {
     if (!interaction.guild) return;
     const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger;
 
diff --git a/src/commands/help.ts b/src/commands/help.ts
index e85cf6b..c9bba7d 100644
--- a/src/commands/help.ts
+++ b/src/commands/help.ts
@@ -1,4 +1,4 @@
-import type { CommandInteraction } from "discord.js";
+import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js";
 import { SlashCommandBuilder } from "@discordjs/builders";
 
 const command = new SlashCommandBuilder()
@@ -6,7 +6,12 @@
     .setDescription("Shows help for commands");
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    interaction.reply("hel p D:"); // TODO: FINISH THIS FOR RELEASE
+    interaction.reply({components: [new ActionRowBuilder<ButtonBuilder>().addComponents(
+        new ButtonBuilder()
+            .setLabel("Verify")
+            .setStyle(ButtonStyle.Primary)
+            .setCustomId("verifybutton")
+    )]}); // TODO: FINISH THIS FOR RELEASE
 };
 
 const check = (_interaction: CommandInteraction) => {
diff --git a/src/commands/mod/_meta.ts b/src/commands/mod/_meta.ts
index 987a1c1..af8006c 100644
--- a/src/commands/mod/_meta.ts
+++ b/src/commands/mod/_meta.ts
@@ -3,6 +3,6 @@
 const name = "mod";
 const description = "Perform moderator actions";
 
-const subcommand = await command(name, description, `mod`)
+const subcommand = await command(name, description, `mod`);
 
 export { name, description, subcommand as command };
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index 7346bcc..ba60d1e 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -74,6 +74,7 @@
         const config = await client.database.guilds.read(interaction.guild.id);
         try {
             if (notify) {
+                if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") }
                 const messageData: {
                     embeds: EmojiEmbed[];
                     components: ActionRowBuilder<ButtonBuilder>[];
@@ -83,7 +84,8 @@
                             .setEmoji("PUNISH.BAN.RED")
                             .setTitle("Banned")
                             .setDescription(
-                                `You have been banned in ${interaction.guild.name}` + (reason ? ` for:\n> ${reason}` : ".")
+                                `You have been banned in ${interaction.guild.name}` +
+                                    (reason ? ` for:\n${reason}` : ".\n*No reason was provided.*")
                             )
                             .setStatus("Danger")
                     ],
diff --git a/src/commands/mod/info.ts b/src/commands/mod/info.ts
index 6b5c81e..fac87a0 100644
--- a/src/commands/mod/info.ts
+++ b/src/commands/mod/info.ts
@@ -23,6 +23,7 @@
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("info")
+        // .setNameLocalizations({"ru": "about", "zh-CN": "history", "zh-TW": "notes", "pt-BR": "flags"})
         .setDescription("Shows moderator information about a user")
         .addUserOption((option) =>
             option.setName("user").setDescription("The user to get information about").setRequired(true)
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index dd71892..88b6e14 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -75,6 +75,7 @@
     const config = await client.database.guilds.read(interaction.guild.id);
     try {
         if (notify) {
+            if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") }
             const messageData: {
                 embeds: EmojiEmbed[];
                 components: ActionRowBuilder<ButtonBuilder>[];
@@ -85,7 +86,7 @@
                         .setTitle("Kicked")
                         .setDescription(
                             `You have been kicked from ${interaction.guild.name}` +
-                                (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided.*")
+                                (reason ? ` for:\n${reason}` : ".\n*No reason was provided.*")
                         )
                         .setStatus("Danger")
                 ],
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index 05a9ec2..d68272b 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -13,6 +13,7 @@
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("mute")
+        // .setNameLocalizations({"ru": "silence"})
         .setDescription("Mutes a member, stopping them from talking in the server")
         .addUserOption((option) => option.setName("user").setDescription("The user to mute").setRequired(true))
         .addIntegerOption((option) =>
@@ -234,6 +235,7 @@
     let dmMessage;
     try {
         if (notify) {
+            if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") }
             const messageData: {
                 embeds: EmojiEmbed[];
                 components: ActionRowBuilder<ButtonBuilder>[];
@@ -244,7 +246,7 @@
                         .setTitle("Muted")
                         .setDescription(
                             `You have been muted in ${interaction.guild.name}` +
-                                (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided*") + "\n\n" +
+                                (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") + "\n\n" +
                             `You will be unmuted at: <t:${Math.round(new Date().getTime() / 1000) + muteTime}:D> at ` +
                             `<t:${Math.round(new Date().getTime() / 1000) + muteTime}:T> (<t:${Math.round(new Date().getTime() / 1000) + muteTime
                             }:R>)` + "\n\n" +
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index 0975375..81935a2 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -1,4 +1,4 @@
-import { CommandInteraction, GuildMember } from "discord.js";
+import type { CommandInteraction, GuildMember } from "discord.js";
 import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@@ -8,6 +8,7 @@
 
 const command = (builder: SlashCommandSubcommandBuilder) => builder
     .setName("nick")
+    // .setNameLocalizations({"ru": "name", "zh-CN": "nickname"})
     .setDescription("Changes a users nickname")
     .addUserOption((option) => option.setName("user").setDescription("The user to change").setRequired(true))
     .addStringOption((option) =>
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index 6087890..3020eb8 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -29,11 +29,7 @@
     if (!interaction.guild) return;
     const user = (interaction.options.getMember("user") as GuildMember | null);
     const channel = interaction.channel as GuildChannel;
-    if (
-        !["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(
-            channel.type.toString()
-        )
-    ) {
+    if (channel.isTextBased()) {
         return await interaction.reply({
             embeds: [
                 new EmojiEmbed()
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 390baa5..d920bb0 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -84,6 +84,7 @@
     const config = await client.database.guilds.read(interaction.guild.id);
     try {
         if (notify) {
+            if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") }
             const messageData: {
                 embeds: EmojiEmbed[];
                 components: ActionRowBuilder<ButtonBuilder>[];
@@ -94,7 +95,7 @@
                         .setTitle("Warned")
                         .setDescription(
                             `You have been warned in ${interaction.guild.name}` +
-                                (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided*") +
+                                (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") +
                                 "\n\n" +
                                 (createAppealTicket
                                     ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
diff --git a/src/commands/nucleus/_meta.ts b/src/commands/nucleus/_meta.ts
index d66b5d2..521b338 100644
--- a/src/commands/nucleus/_meta.ts
+++ b/src/commands/nucleus/_meta.ts
@@ -5,4 +5,6 @@
 
 const subcommand = await command(name, description, `nucleus`)
 
-export { name, description, subcommand as command };
+const allowedInDMs = true;
+
+export { name, description, subcommand as command, allowedInDMs };
diff --git a/src/commands/nucleus/guide.ts b/src/commands/nucleus/guide.ts
index ffc441a..d3370ba 100644
--- a/src/commands/nucleus/guide.ts
+++ b/src/commands/nucleus/guide.ts
@@ -1,11 +1,12 @@
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { CommandInteraction } from 'discord.js';
+import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import guide from "../../reflex/guide.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder.setName("guide").setDescription("Shows the welcome guide for the bot");
 
-const callback = async (interaction) => {
-    guide(interaction.guild, interaction);
+const callback = async (interaction: CommandInteraction) => {
+    guide(interaction.guild!, interaction);
 };
 
 const check = () => {
diff --git a/src/commands/nucleus/invite.ts b/src/commands/nucleus/invite.ts
index 7c36d62..fd65e51 100644
--- a/src/commands/nucleus/invite.ts
+++ b/src/commands/nucleus/invite.ts
@@ -1,5 +1,5 @@
 import { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
@@ -16,12 +16,12 @@
                 .setStatus("Danger")
         ],
         components: [
-            new ActionRowBuilder().addComponents([
+            new ActionRowBuilder<ButtonBuilder>().addComponents([
                 new ButtonBuilder()
                     .setLabel("Invite")
                     .setStyle(ButtonStyle.Link)
                     .setURL(
-                        `https://discord.com/api/oauth2/authorize?client_id=${client.user.id}&permissions=295157886134&scope=bot%20applications.commands`
+                        `https://discord.com/api/oauth2/authorize?client_id=${client.user!.id}&permissions=295157886134&scope=bot%20applications.commands`
                     )
             ])
         ],
diff --git a/src/commands/nucleus/ping.ts b/src/commands/nucleus/ping.ts
index 59b6393..3cbd049 100644
--- a/src/commands/nucleus/ping.ts
+++ b/src/commands/nucleus/ping.ts
@@ -1,6 +1,6 @@
 import { LoadingEmbed } from "./../../utils/defaultEmbeds.js";
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { CommandInteraction } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
@@ -19,8 +19,8 @@
                 .setTitle("Ping")
                 .setDescription(
                     `**Ping:** \`${ping}ms\`\n` +
-                        `**To Discord:** \`${client.ws.ping}ms\`\n` +
-                        `**From Expected:** \`±${Math.abs(ping / 2 - client.ws.ping)}ms\``
+                    `**To Discord:** \`${client.ws.ping}ms\`\n` +
+                    `**From Expected:** \`±${Math.abs(ping / 2 - client.ws.ping)}ms\``
                 )
                 .setEmoji("CHANNEL.SLOWMODE.OFF")
                 .setStatus("Danger")
diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts
index a67cd36..d8b2807 100644
--- a/src/commands/nucleus/stats.ts
+++ b/src/commands/nucleus/stats.ts
@@ -1,5 +1,5 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { CommandInteraction } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index 49c80fb..a75e8a0 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -1,4 +1,5 @@
-import Discord, { CommandInteraction } from "discord.js";
+import type { CommandInteraction } from "discord.js";
+import type Discord from "discord.js";
 import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@@ -13,8 +14,9 @@
         );
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
+    await interaction.guild?.members.fetch(interaction.member!.user.id)
     const { renderUser } = client.logger;
-    const suggestion = interaction.options.getString("suggestion");
+    const suggestion = interaction.options.get("suggestion")?.value as string;
     const confirmation = await new confirmationMessage(interaction)
         .setEmoji("ICONS.OPP.ADD")
         .setTitle("Suggest")
@@ -32,7 +34,7 @@
                 new EmojiEmbed()
                     .setTitle("Suggestion")
                     .setDescription(
-                        `**From:** ${renderUser(interaction.member.user)}\n**Suggestion:**\n> ${suggestion}`
+                        `**From:** ${renderUser(interaction.member!.user as Discord.User)}\n**Suggestion:**\n> ${suggestion}`
                     )
                     .setStatus("Danger")
                     .setEmoji("NUCLEUS.LOGO")
diff --git a/src/commands/server/about.ts b/src/commands/server/about.ts
index b895768..e5bea60 100644
--- a/src/commands/server/about.ts
+++ b/src/commands/server/about.ts
@@ -1,4 +1,4 @@
-import type { CommandInteraction } from "discord.js";
+import { CommandInteraction, GuildMFALevel } from "discord.js";
 import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
@@ -8,6 +8,28 @@
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder.setName("about").setDescription("Shows info about the server");
 
+
+const verificationTypes = {
+    0: "None - Unrestricted",
+    1: "Low - Must have a verified email",
+    2: "Medium - Must be registered for 5 minutes",
+    3: "High - Must be a member for 10 minutes",
+    4: "Highest - Must have a verified phone"
+}
+
+const premiumTiers = {
+    0: "None",
+    1: "Tier 1",
+    2: "Tier 2",
+    3: "Tier 3"
+}
+
+const filterLevels = {
+    0: "Disabled",
+    1: "Members without roles",
+    2: "All members"
+}
+
 const callback = async (interaction: CommandInteraction): Promise<void> => {
     const guild = interaction.guild!;
     const { renderUser, renderDelta } = client.logger;
@@ -27,28 +49,26 @@
                             `${guild.emojis.cache.size}` +
                             (guild.emojis.cache.size > 1
                                 ? `\n> ${guild.emojis.cache
-                                      .first(10)
-                                      .map((emoji) => `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`)
-                                      .join(" ")}` +
-                                  (guild.emojis.cache.size > 10 ? ` and ${guild.emojis.cache.size - 10} more` : "")
+                                        .first(10)
+                                        .map((emoji) => `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`)
+                                        .join(" ")}` +
+                                    (guild.emojis.cache.size > 10 ? ` and ${guild.emojis.cache.size - 10} more` : "")
                                 : ""),
                         icon: `[Discord](${guild.iconURL()})`,
                         "2 factor authentication": `${
-                            guild.mfaLevel === "NONE"
+                            guild.mfaLevel === GuildMFALevel.None
                                 ? `${getEmojiByName("CONTROL.CROSS")} No`
                                 : `${getEmojiByName("CONTROL.TICK")} Yes`
                         }`,
-                        "verification level": `${toCapitals(guild.verificationLevel)}`,
-                        "explicit content filter": `${toCapitals(
-                            guild.explicitContentFilter.toString().replace(/_/, " ")
-                        )}`,
-                        "nitro boost level": `${guild.premiumTier !== "NONE" ? guild.premiumTier.toString()[-1] : "0"}`,
+                        "verification level": `${toCapitals(verificationTypes[guild.verificationLevel])}`,
+                        "explicit content filter": `${filterLevels[guild.explicitContentFilter]}`,
+                        "nitro boost level": `${premiumTiers[guild.premiumTier]}`,
                         channels: `${guild.channels.cache.size}`,
                         roles: `${guild.roles.cache.size}`,
                         members: `${guild.memberCount}`
                     })
                 )
-                .setThumbnail(guild.iconURL({ dynamic: true }))
+                .setThumbnail(guild.iconURL())
         ],
         ephemeral: true
     });
diff --git a/src/commands/settings/_meta.ts b/src/commands/settings/_meta.ts
index 76e570d..666dbf4 100644
--- a/src/commands/settings/_meta.ts
+++ b/src/commands/settings/_meta.ts
@@ -4,6 +4,8 @@
 const description = "Change bot settings";
 
 
-const subcommand = await command(name, description, "settings")
+const subcommand = await command(name, description, "settings", undefined, undefined, undefined, [
+    "ManageGuild"
+])
 
 export { name, description, subcommand as command};
diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts
index f19998a..a768cb8 100644
--- a/src/commands/settings/stats.ts
+++ b/src/commands/settings/stats.ts
@@ -224,6 +224,16 @@
     return true;
 };
 
+const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
+    if (!interaction.guild) return [];
+    const prompt = interaction.options.getString("tag");
+    // generateStatsChannelAutocomplete(int.options.getString("name") ?? "")
+    const results = generateStatsChannelAutocomplete(prompt ?? "");
+    return results;
+};
+
+
+
 export { command };
 export { callback };
 export { check };
diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts
index 5284f8a..7f02cd7 100644
--- a/src/commands/settings/welcome.ts
+++ b/src/commands/settings/welcome.ts
@@ -7,7 +7,8 @@
     ButtonBuilder,
     MessageComponentInteraction,
     Role,
-    ButtonStyle
+    ButtonStyle,
+    AutocompleteInteraction
 } from "discord.js";
 import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@@ -204,7 +205,7 @@
                     .setEmoji("CHANNEL.TEXT.CREATE")
             ],
             components: [
-                new ActionRowBuilder().addComponents([
+                new ActionRowBuilder<ButtonBuilder>().addComponents([
                     new ButtonBuilder()
                         .setLabel(lastClicked == "clear-message" ? "Click again to confirm" : "Clear Message")
                         .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
@@ -296,11 +297,42 @@
 
 const check = (interaction: CommandInteraction) => {
     const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("MANAGE_GUILD"))
+    if (!member.permissions.has("ManageGuild"))
         throw new Error("You must have the *Manage Server* permission to use this command");
     return true;
 };
 
-export { command };
-export { callback };
-export { check };
+const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
+    const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
+    if (!interaction.guild) return [];
+    const prompt = interaction.options.getString("message");
+    const autocompletions = [];
+    if ( prompt === null ) {
+        for (const replacement of validReplacements) {
+            autocompletions.push(`{${replacement}}`);
+        };
+        return autocompletions;
+    };
+    const beforeLastOpenBracket = prompt.match(/(.*){[^{}]{0,15}$/);
+    const afterLastOpenBracket = prompt.match(/{[^{}]{0,15}$/);
+    if (beforeLastOpenBracket !== null) {
+        if (afterLastOpenBracket !== null) {
+            for (const replacement of validReplacements) {
+                if (replacement.startsWith(afterLastOpenBracket[0].slice(1))) {
+                    autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
+                }
+            }
+        } else {
+            for (const replacement of validReplacements) {
+                autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
+            }
+        }
+    } else {
+        for (const replacement of validReplacements) {
+            autocompletions.push(`${prompt} {${replacement}}`);
+        }
+    }
+    return autocompletions;
+};
+
+export { command, callback, check, autocomplete };
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index 859b7fc..a65947c 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -58,10 +58,8 @@
 const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
     if (!interaction.guild) return [];
     const prompt = interaction.options.getString("tag");
-    console.log(prompt)
     const possible = Object.keys((await client.memory.readGuildInfo(interaction.guild.id)).tags);
     const results = getResults(prompt ?? "", possible);
-    console.log(results)
     return results;
 };
 
diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts
index 72ad1eb..aa45690 100644
--- a/src/commands/user/about.ts
+++ b/src/commands/user/about.ts
@@ -79,7 +79,7 @@
         Staff: "Discord Staff",
         VerifiedDeveloper: "Verified Bot Developer",
         ActiveDeveloper: "Active Developer",
-        Quarantined: "Quarantined [What does this mean?](https://support.discord.com/hc/en-us/articles/6461420677527)",
+        Quarantined: "Quarantined [[What does this mean?]](https://support.discord.com/hc/en-us/articles/6461420677527)",
         Spammer: "Likely Spammer"
         // CertifiedModerator
         // VerifiedBot
diff --git a/src/config/default.json b/src/config/default.json
index 824f12c..8e4197c 100644
--- a/src/config/default.json
+++ b/src/config/default.json
@@ -19,7 +19,11 @@
         },
         "invite": {
             "enabled": false,
-            "channels": []
+            "allowed": {
+                "users": [],
+                "roles": [],
+                "channels": []
+            }
         },
         "pings": {
             "mass": 5,
diff --git a/src/config/emojis.json b/src/config/emojis.json
index 168d84b..cbf3dc3 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -41,7 +41,8 @@
             "GUILD_STAGE_VOICE": "853668786842763294",
             "THREAD_CHANNEL": "990210005108158514",
             "THREAD_PIPE": "990213168183779348",
-            "RULES": "990213153080115250"
+            "RULES": "990213153080115250",
+            "FORUM": "1061706437526552716"
         }
     },
     "CONTROL": {
diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts
index b3cba33..dda37af 100644
--- a/src/events/channelCreate.ts
+++ b/src/events/channelCreate.ts
@@ -1,47 +1,54 @@
-import type { GuildAuditLogsEntry } from "discord.js";
+import { AuditLogEvent, ChannelType, GuildAuditLogsEntry } from "discord.js";
 import type { GuildBasedChannel } from "discord.js";
 import type { NucleusClient } from "../utils/client.js";
 export const event = "channelCreate";
 
 export async function callback(client: NucleusClient, channel: GuildBasedChannel) {
     const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
-    const auditLog = await getAuditLog(channel.guild, "CHANNEL_CREATE");
-    const audit = auditLog.entries.filter((entry: GuildAuditLogsEntry) => entry.target!.id === channel.id).first();
-    if (audit.executor.id === client.user.id) return;
+    const auditLog = (await getAuditLog(channel.guild, AuditLogEvent.ChannelCreate))
+        .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildBasedChannel)!.id === channel.id)[0];
+    if (!auditLog) return;
+    if (auditLog.executor!.id === client.user!.id) return;
     let emoji;
     let readableType;
     let displayName;
     switch (channel.type) {
-        case "GUILD_TEXT": {
+        case ChannelType.GuildText: {
             emoji = "CHANNEL.TEXT.CREATE";
             readableType = "Text";
             displayName = "Text Channel";
             break;
         }
-        case "GUILD_NEWS": {
+        case ChannelType.GuildAnnouncement: {
             emoji = "CHANNEL.TEXT.CREATE";
             readableType = "Announcement";
             displayName = "Announcement Channel";
             break;
         }
-        case "GUILD_VOICE": {
+        case ChannelType.GuildVoice: {
             emoji = "CHANNEL.VOICE.CREATE";
             readableType = "Voice";
             displayName = "Voice Channel";
             break;
         }
-        case "GUILD_STAGE_VOICE": {
+        case ChannelType.GuildStageVoice: {
             emoji = "CHANNEL.VOICE.CREATE";
             readableType = "Stage";
             displayName = "Stage Channel";
             break;
         }
-        case "GUILD_CATEGORY": {
+        case ChannelType.GuildCategory: {
             emoji = "CHANNEL.CATEGORY.CREATE";
             readableType = "Category";
             displayName = "Category";
             break;
         }
+        case ChannelType.GuildForum: {
+            emoji = "CHANNEL.TEXT.CREATE";
+            readableType = "Forum";
+            displayName = "Forum Channel";
+            break;
+        }
         default: {
             emoji = "CHANNEL.TEXT.CREATE";
             readableType = "Channel";
@@ -65,12 +72,12 @@
                 channel.parent ? channel.parent.id : null,
                 channel.parent ? channel.parent.name : "Uncategorised"
             ),
-            createdBy: entry(audit.executor.id, renderUser(audit.executor)),
-            created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp))
+            createdBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
+            created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp!))
         },
         hidden: {
             guild: channel.guild.id
         }
     };
     log(data);
-}
+};
diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts
index a7af453..df212a2 100644
--- a/src/events/channelUpdate.ts
+++ b/src/events/channelUpdate.ts
@@ -5,6 +5,7 @@
 
 export async function callback(client, oc, nc) {
     const config = await client.memory.readGuildInfo(nc.guild.id);
+    return;
     const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderChannel } = client.logger;
 
     if (nc.parent && nc.parent.id === config.tickets.category) return;
diff --git a/src/events/guildUpdate.ts b/src/events/guildUpdate.ts
index e463007..eefab4b 100644
--- a/src/events/guildUpdate.ts
+++ b/src/events/guildUpdate.ts
@@ -78,7 +78,7 @@
     const data = {
         meta: {
             type: "guildUpdate",
-            displayName: "Guild Edited",
+            displayName: "Server Edited",
             calculateType: "guildUpdate",
             color: NucleusColors.yellow,
             emoji: "GUILD.YELLOW",
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
index 1796146..62e9609 100644
--- a/src/events/interactionCreate.ts
+++ b/src/events/interactionCreate.ts
@@ -4,7 +4,7 @@
 import close from "../actions/tickets/delete.js";
 import createTranscript from "../premium/createTranscript.js";
 
-import type { Interaction, MessageComponentInteraction } from "discord.js";
+import type { Interaction } from "discord.js";
 import type { NucleusClient } from "../utils/client.js";
 
 export const event = "interactionCreate";
@@ -12,30 +12,16 @@
 
 async function interactionCreate(interaction: Interaction) {
     if (interaction.isButton()) {
-        const int = interaction as MessageComponentInteraction;
-        switch (int.customId) {
-            case "rolemenu": {
-                return await roleMenu(interaction);
-            }
-            case "verifybutton": {
-                return verify(int);
-            }
-            case "createticket": {
-                return create(interaction);
-            }
-            case "closeticket": {
-                return close(interaction);
-            }
-            case "createtranscript": {
-                return createTranscript(int);
-            }
+        switch (interaction.customId) {
+            case "rolemenu":         { return await roleMenu(interaction); }
+            case "verifybutton":     { return await verify(interaction); }
+            case "createticket":     { return await create(interaction); }
+            case "closeticket":      { return await close(interaction); }
+            case "createtranscript": { return await createTranscript(interaction); }
         }
     // } else if (interaction.type === "APPLICATION_COMMAND_AUTOCOMPLETE") {
     //     const int = interaction as AutocompleteInteraction;
     //     switch (`${int.commandName} ${int.options.getSubcommandGroup(false)} ${int.options.getSubcommand(false)}`) {
-    //         case "tag null null": {
-    //             return int.respond(getAutocomplete(int.options.getString("tag") ?? "", await tagAutocomplete(int)));
-    //         }
     //         case "settings null stats": {
     //             return int.respond(generateStatsChannelAutocomplete(int.options.getString("name") ?? ""));
     //         }
diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts
index 3f85de6..2f3a077 100644
--- a/src/events/messageCreate.ts
+++ b/src/events/messageCreate.ts
@@ -12,7 +12,7 @@
 export async function callback(_client: NucleusClient, message: Message) {
     if (!message.guild) return;
     if (message.author.bot) return;
-    if (message.channel.type === "DM") return;
+    if (message.channel.isDMBased()) return;
     try {
         await statsChannelUpdate(client, await message.guild.members.fetch(message.author.id));
     } catch (e) {
@@ -38,7 +38,7 @@
         mentions: message.mentions.users.size,
         attachments: entry(message.attachments.size, message.attachments.size + attachmentJump),
         repliedTo: entry(
-            message.reference ? message.reference.messageId : null,
+            (message.reference ? message.reference.messageId : null) ?? null,
             message.reference
                 ? `[[Jump to message]](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.reference.messageId})`
                 : "None"
diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts
index ddeff96..f8433fc 100644
--- a/src/events/messageDelete.ts
+++ b/src/events/messageDelete.ts
@@ -1,27 +1,24 @@
 import type { NucleusClient } from "../utils/client.js";
-import type { GuildAuditLogsEntry, Message } from "discord.js";
+import Discord, { AuditLogEvent, GuildAuditLogsEntry, Message, User } from "discord.js";
 
 export const event = "messageDelete";
 
 export async function callback(client: NucleusClient, message: Message) {
     try {
-        if (message.author.id === client.user.id) return;
+        if (message.author.id === client.user!.id) return;
         if (client.noLog.includes(`${message.id}/${message.channel.id}/${message.id}`)) return;
         const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
-        const auditLog = await getAuditLog(message.guild, "MEMBER_BAN_ADD");
-        const audit = auditLog.entries
-            .filter((entry: GuildAuditLogsEntry) => entry.target!.id === message.author.id)
-            .first();
-        if (audit) {
-            if (audit.createdAt - 100 < new Date().getTime()) return;
+        const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd))
+            .filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0];
+        if (auditLog) {
+            if (auditLog.createdTimestamp - 1000 < new Date().getTime()) return;
         }
         const replyTo = message.reference;
         let content = message.cleanContent;
         content.replace("`", "\\`");
         if (content.length > 256) content = content.substring(0, 253) + "...";
         const attachments =
-            message.attachments.size +
-            (
+            message.attachments.size + (
                 message.content.match(
                     /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi
                 ) ?? []
@@ -30,9 +27,7 @@
         const config = (await client.database.guilds.read(message.guild!.id)).logging.attachments.saved[
             message.channel.id + message.id
         ];
-        if (config) {
-            attachmentJump = ` [[View attachments]](${config})`;
-        }
+        if (config) { attachmentJump = ` [[View attachments]](${config})`; }
         const data = {
             meta: {
                 type: "messageDelete",
@@ -48,17 +43,14 @@
             list: {
                 messageId: entry(message.id, `\`${message.id}\``),
                 sentBy: entry(message.author.id, renderUser(message.author)),
-                sentIn: entry(message.channel.id, renderChannel(message.channel)),
+                sentIn: entry(message.channel.id, renderChannel(message.channel as Discord.GuildChannel | Discord.ThreadChannel)),
                 deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
                 mentions: message.mentions.users.size,
                 attachments: entry(attachments, attachments + attachmentJump),
                 repliedTo: entry(
-                    replyTo,
-                    replyTo
-                        ? `[[Jump to message]](https://discord.com/channels/${message.guild!.id}/${
-                              message.channel.id
-                          }/${replyTo.messageId})`
-                        : "None"
+                    replyTo ? replyTo.messageId! : null,
+                    replyTo ? `[[Jump to message]](https://discord.com/channels/${message.guild!.id}/${message.channel.id}/${replyTo.messageId})`
+                            : "None"
                 )
             },
             hidden: {
diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts
index 37178a7..20624fe 100644
--- a/src/events/messageEdit.ts
+++ b/src/events/messageEdit.ts
@@ -1,10 +1,11 @@
 import type { NucleusClient } from "../utils/client.js";
 import type { Message, MessageReference } from "discord.js";
+import type Discord from "discord.js";
 
 export const event = "messageUpdate";
 
 export async function callback(client: NucleusClient, oldMessage: Message, newMessage: Message) {
-    if (newMessage.author.id === client.user.id) return;
+    if (newMessage.author.id === client.user!.id) return;
     if (!newMessage.guild) return;
     const { log, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = client.logger;
     const replyTo: MessageReference | null = newMessage.reference;
@@ -17,8 +18,8 @@
     if (config) {
         attachmentJump = ` [[View attachments]](${config})`;
     }
-    if (newContent === oldContent) {
-        if (!oldMessage.flags.has("CROSSPOSTED") && newMessage.flags.has("CROSSPOSTED")) {
+    if (newContent === oldContent && newMessage.attachments.size === oldMessage.attachments.size) {
+        if (!replyTo) {
             const data = {
                 meta: {
                     type: "messageAnnounce",
@@ -34,14 +35,14 @@
                 list: {
                     messageId: entry(newMessage.id, `\`${newMessage.id}\``),
                     sentBy: entry(newMessage.author.id, renderUser(newMessage.author)),
-                    sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel)),
+                    sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel as Discord.GuildBasedChannel)),
                     sent: entry(
-                        new Date(newMessage.createdTimestamp),
-                        renderDelta(new Date(newMessage.createdTimestamp))
+                        newMessage.createdTimestamp,
+                        renderDelta(newMessage.createdTimestamp)
                     ),
                     published: entry(
-                        new Date(newMessage.editedTimestamp!),
-                        renderDelta(new Date(newMessage.editedTimestamp!))
+                        newMessage.editedTimestamp!,
+                        renderDelta(newMessage.editedTimestamp!)
                     ),
                     mentions: renderNumberDelta(oldMessage.mentions.users.size, newMessage.mentions.users.size),
                     attachments: entry(
@@ -81,16 +82,16 @@
         list: {
             messageId: entry(newMessage.id, `\`${newMessage.id}\``),
             sentBy: entry(newMessage.author.id, renderUser(newMessage.author)),
-            sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel)),
-            sent: entry(new Date(newMessage.createdTimestamp), renderDelta(new Date(newMessage.createdTimestamp))),
-            edited: entry(new Date(newMessage.editedTimestamp), renderDelta(new Date(newMessage.editedTimestamp))),
+            sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel as Discord.GuildBasedChannel)),
+            sent: entry(newMessage.createdTimestamp, renderDelta(newMessage.createdTimestamp)),
+            edited: entry(newMessage.editedTimestamp, renderDelta(newMessage.editedTimestamp)),
             mentions: renderNumberDelta(oldMessage.mentions.users.size, newMessage.mentions.users.size),
             attachments: entry(
                 renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size),
                 renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size) + attachmentJump
             ),
             repliedTo: entry(
-                replyTo,
+                replyTo ? replyTo.messageId! : null,
                 replyTo
                     ? `[[Jump to message]](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${replyTo.messageId})`
                     : "None"
diff --git a/src/index.ts b/src/index.ts
index f7fdde5..aff1b2b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,8 +1,8 @@
 import runServer from "./api/index.js";
 import client from "./utils/client.js";
-// @ts-expect-error
 import config from "./config/main.json" assert { type: "json" };
 import register from "./utils/commandRegistration/register.js";
+import { record as recordPerformance } from "./utils/performanceTesting/record.js";
 
 client.on("ready", () => {
     console.log(`Logged in as ${client.user!.tag}!`);
@@ -18,3 +18,5 @@
 
 if (config.enableDevelopment) { await client.login(config.developmentToken); }
 else { await client.login(config.token); }
+
+await recordPerformance();
\ No newline at end of file
diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts
index 0c491b7..abda27d 100644
--- a/src/premium/attachmentLogs.ts
+++ b/src/premium/attachmentLogs.ts
@@ -4,7 +4,7 @@
 import { saveAttachment } from "../reflex/scanners.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import addPlural from "../utils/plurals.js";
-import type { Message } from "discord.js";
+import type { GuildTextBasedChannel, Message } from "discord.js";
 
 export default async function logAttachment(message: Message): Promise<AttachmentLogSchema> {
     if (!message.guild) throw new Error("Tried to log an attachment in a non-guild message");
@@ -31,7 +31,7 @@
         }
     }
     if (attachments.length === 0) return { files: [] };
-    if (client.database.premium.hasPremium(message.guild.id)) {
+    if (await client.database.premium.hasPremium(message.guild.id)) {
         const channel = (await client.database.guilds.read(message.guild.id)).logging.attachments.channel;
         if (!channel) {
             singleNotify(
@@ -52,7 +52,7 @@
             );
             return { files: attachments };
         }
-        const m = await channelObj.send({
+        const m = await (channelObj as GuildTextBasedChannel).send({
             embeds: [
                 new EmojiEmbed()
                     .setTitle(`${addPlural(attachments.length, "Attachment")} Sent`)
@@ -60,8 +60,8 @@
                         keyValueList({
                             messageId: `\`${message.id}\``,
                             sentBy: renderUser(message.author),
-                            sentIn: renderChannel(message.channel),
-                            sent: renderDelta(new Date(message.createdTimestamp))
+                            sentIn: renderChannel(message.channel as GuildTextBasedChannel),
+                            sent: renderDelta((new Date(message.createdTimestamp)).getTime())
                         }) + `\n[[Jump to message]](${message.url})`
                     )
                     .setEmoji("ICONS.ATTACHMENT")
diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts
index 6805017..e755b83 100644
--- a/src/premium/createTranscript.ts
+++ b/src/premium/createTranscript.ts
@@ -6,7 +6,8 @@
     ButtonBuilder,
     MessageComponentInteraction,
     TextChannel,
-    ButtonStyle
+    ButtonStyle,
+    User
 } from "discord.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
@@ -76,7 +77,7 @@
                     .setEmoji("CONTROL.DOWNLOAD")
             ],
             components: [
-                new ActionRowBuilder().addComponents([
+                new ActionRowBuilder<ButtonBuilder>().addComponents([
                     new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(url),
                     new ButtonBuilder()
                         .setLabel("Delete")
@@ -99,7 +100,7 @@
                     .setEmoji("CONTROL.DOWNLOAD")
             ],
             components: [
-                new ActionRowBuilder().addComponents([
+                new ActionRowBuilder<ButtonBuilder>().addComponents([
                     new ButtonBuilder()
                         .setLabel("Delete")
                         .setStyle(ButtonStyle.Danger)
@@ -128,8 +129,8 @@
         },
         list: {
             ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"),
-            deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user)),
-            deleted: entry(new Date().getTime(), renderDelta(new Date().getTime()))
+            deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)),
+            deleted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime()))
         },
         hidden: {
             guild: interaction.guild!.id
diff --git a/src/reflex/verify.ts b/src/reflex/verify.ts
index 6458439..2372130 100644
--- a/src/reflex/verify.ts
+++ b/src/reflex/verify.ts
@@ -5,7 +5,8 @@
     MessageComponentInteraction,
     Role,
     ButtonStyle,
-    PermissionsBitField
+    PermissionsBitField,
+    ButtonInteraction
 } from "discord.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import fetch from "node-fetch";
@@ -28,7 +29,7 @@
     return "\n\n" + createPageIndicator(5, i);
 }
 
-export default async function (interaction: CommandInteraction | MessageComponentInteraction) {
+export default async function (interaction: CommandInteraction | ButtonInteraction) {
     const verify = client.verify;
     await interaction.reply({
         embeds: LoadingEmbed,
diff --git a/src/utils/client.ts b/src/utils/client.ts
index a57c639..43cbe11 100644
--- a/src/utils/client.ts
+++ b/src/utils/client.ts
@@ -1,8 +1,8 @@
-import Discord, { Client, Interaction, AutocompleteInteraction } from 'discord.js';
+import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits } from 'discord.js';
 import { Logger } from "../utils/log.js";
 import Memory from "../utils/memory.js";
 import type { VerifySchema } from "../reflex/verify.js";
-import { Guilds, History, ModNotes, Premium } from "../utils/database.js";
+import { Guilds, History, ModNotes, Premium, PerformanceTest } from "../utils/database.js";
 import EventScheduler from "../utils/eventScheduler.js";
 import type { RoleMenuSchema } from "../actions/roleMenu.js";
 import config from "../config/main.json" assert { type: "json" };
@@ -21,6 +21,7 @@
         notes: ModNotes;
         premium: Premium;
         eventScheduler: EventScheduler;
+        performanceTest: PerformanceTest;
     };
     commands: Record<string, {
         command: Discord.SlashCommandBuilder |
@@ -32,7 +33,13 @@
     }> = {};
 
     constructor(database: typeof NucleusClient.prototype.database) {
-        super({ intents: 32767 });
+        super({ intents: [
+            GatewayIntentBits.Guilds,
+            GatewayIntentBits.GuildMessages,
+            GatewayIntentBits.MessageContent,
+            GatewayIntentBits.GuildPresences,
+            GatewayIntentBits.GuildMembers
+        ]});
         this.database = database;
     }
 }
@@ -42,7 +49,8 @@
     history: new History(),
     notes: new ModNotes(),
     premium: new Premium(),
-    eventScheduler: new EventScheduler()
+    eventScheduler: new EventScheduler(),
+    performanceTest: new PerformanceTest()
 });
 
 export default client;
diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts
index a4c57c4..d96ca90 100644
--- a/src/utils/commandRegistration/register.ts
+++ b/src/utils/commandRegistration/register.ts
@@ -16,7 +16,7 @@
 async function registerCommands() {
     const commands = [];
 
-    const files = fs.readdirSync(config.commandsFolder, { withFileTypes: true }).filter(
+    const files: fs.Dirent[] = fs.readdirSync(config.commandsFolder, { withFileTypes: true }).filter(
         file => !file.name.endsWith(".ts") && !file.name.endsWith(".map")
     );
     console.log(`Registering ${files.length} commands`)
@@ -25,10 +25,15 @@
         const last = i === files.length - 1 ? "└" : "├";
         if (file.isDirectory()) {
             console.log(`${last}─ ${colours.yellow}Loading subcommands of ${file.name}${colours.none}`)
-            commands.push((await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)).command);
+            const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)).command;
+            commands.push(fetched);
         } else if (file.name.endsWith(".js")) {
             console.log(`${last}─ ${colours.yellow}Loading command ${file.name}${colours.none}`)
             const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`));
+            fetched.command.setDMPermission(fetched.allowedInDMs ?? false)
+            fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {})
+            fetched.command.setDescriptionLocalizations(fetched.descriptionLocalizations ?? {})
+            if (fetched.nameLocalizations || fetched.descriptionLocalizations) console.log("AAAAA")
             commands.push(fetched.command);
             client.commands["commands/" + fetched.command.name] = fetched;
         }
@@ -97,6 +102,8 @@
             console.log(`${last}─ ${colours.yellow}Loading message context menu ${file.name}${colours.none}`)
             const context = (await import(`../../../${config.messageContextFolder}/${file.name}`));
             context.command.setType(ApplicationCommandType.Message);
+            context.command.setDMPermission(context.allowedInDMs ?? false)
+            context.command.setNameLocalizations(context.nameLocalizations ?? {})
             commands.push(context.command);
 
             client.commands["contextCommands/message/" + context.command.name] = context;
@@ -182,6 +189,7 @@
     callback(data);
 }
 
+
 export default async function register() {
     let commandList: ( Discord.SlashCommandBuilder | Discord.ContextMenuCommandBuilder )[] = [];
     commandList = commandList.concat(await registerCommands());
diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts
index 76ecabe..b2927d6 100644
--- a/src/utils/commandRegistration/slashCommandBuilder.ts
+++ b/src/utils/commandRegistration/slashCommandBuilder.ts
@@ -13,7 +13,13 @@
 }
 
 
-export async function group(name: string, description: string, path: string) {
+export async function group(
+    name: string,
+    description: string,
+    path: string,
+    nameLocalizations?: Record<string, string>,
+    descriptionLocalizations?: Record<string, string>
+) {
     // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
     console.log(`│  ├─ Loading group ${name}`)
     const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│  ")
@@ -22,6 +28,8 @@
         subcommandGroup
             .setName(name)
             .setDescription(description)
+        if (nameLocalizations) { subcommandGroup.setNameLocalizations(nameLocalizations) }
+        if (descriptionLocalizations) { subcommandGroup.setDescriptionLocalizations(descriptionLocalizations) }
 
         for (const subcommand of fetched.subcommands) {
             subcommandGroup.addSubcommand(subcommand.command);
@@ -31,7 +39,16 @@
     };
 }
 
-export async function command(name: string, description: string, path: string, commandString: string | undefined = undefined) {
+export async function command(
+    name: string,
+    description: string,
+    path: string,
+    commandString: string | undefined = undefined,
+    nameLocalizations?: Record<string, string>,
+    descriptionLocalizations?: Record<string, string>,
+    userPermissions?: Discord.PermissionsString[],
+    allowedInDMs?: boolean
+) {
     // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
     commandString = "commands/" + (commandString ?? path);
     const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path);
@@ -39,6 +56,14 @@
     return (command: SlashCommandBuilder) => {
         command.setName(name)
         command.setDescription(description)
+        command.setNameLocalizations(nameLocalizations ?? {})
+        command.setDescriptionLocalizations(descriptionLocalizations ?? {})
+        command.setDMPermission(allowedInDMs ?? false)
+        if (userPermissions) {
+            const bitfield = new Discord.PermissionsBitField()
+                bitfield.add(userPermissions)
+            command.setDefaultMemberPermissions(bitfield.bitfield)
+        }
 
         for (const subcommand of fetched.subcommands) {
             let fetchedCommand;
diff --git a/src/utils/database.ts b/src/utils/database.ts
index b14c5c4..ba1d89d 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -149,6 +149,33 @@
     }
 }
 
+export class PerformanceTest {
+    performanceData: Collection<PerformanceDataSchema>;
+
+    constructor() {
+        this.performanceData = database.collection<PerformanceDataSchema>("performance");
+    }
+
+    async record(data: PerformanceDataSchema) {
+        data.timestamp = new Date();
+        await this.performanceData.insertOne(data);
+    }
+    async read() {
+        return await this.performanceData.find({}).toArray();
+    }
+}
+
+export interface PerformanceDataSchema {
+    timestamp?: Date;
+    discord: number;
+    databaseRead: number;
+    resources: {
+        cpu: number;
+        memory: number;
+        temperature: number;
+    }
+}
+
 export class ModNotes {
     modNotes: Collection<ModNoteSchema>;
 
@@ -205,7 +232,11 @@
         };
         invite: {
             enabled: boolean;
-            channels: string[];
+            allowed: {
+                channels: string[];
+                roles: string[];
+                users: string[];
+            };
         };
         pings: {
             mass: number;
diff --git a/src/utils/log.ts b/src/utils/log.ts
index b097798..7ab7903 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -25,7 +25,7 @@
         const delta = num2 - num1;
         return `${num1} -> ${num2} (${delta > 0 ? "+" : ""}${delta})`;
     },
-    entry(value: string | null, displayValue: string): { value: string | null; displayValue: string } {
+    entry(value: string | number | null, displayValue: string): { value: string | null; displayValue: string } {
         return { value: value, displayValue: displayValue };
     },
     renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel) {
@@ -44,16 +44,15 @@
     },
     async getAuditLog(guild: Discord.Guild, event: Discord.GuildAuditLogsResolvable): Promise<Discord.GuildAuditLogsEntry[]> {
         await wait(250);
-        const auditLog = await guild.fetchAuditLogs({ type: event });
-        return auditLog as unknown as Discord.GuildAuditLogsEntry[];
+        const auditLog = (await guild.fetchAuditLogs({ type: event })).entries.map(m => m)
+        return auditLog as Discord.GuildAuditLogsEntry[];
     },
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
     async log(log: any): Promise<void> {
         const config = await client.database.guilds.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))
-                console.log("Not logging this type of event");
+        if (!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) {
+            console.log("Not logging this type of event");
             return;
         }
         if (config.logging.logs.channel) {
diff --git a/src/utils/logTranscripts.ts b/src/utils/logTranscripts.ts
new file mode 100644
index 0000000..d5ab0b2
--- /dev/null
+++ b/src/utils/logTranscripts.ts
@@ -0,0 +1,3 @@
+function JSONTranscriptFromMessageArray(messages: Discord.Message[]) {
+    
+}
\ No newline at end of file
diff --git a/src/utils/performanceTesting/record.ts b/src/utils/performanceTesting/record.ts
new file mode 100644
index 0000000..2d9524b
--- /dev/null
+++ b/src/utils/performanceTesting/record.ts
@@ -0,0 +1,47 @@
+import client from "../client.js";
+import { resourceUsage } from "process";
+import { spawn } from "child_process";
+import config from "../../config/main.json" assert { type: "json" };
+
+
+const discordPing = () => {
+    return client.ws.ping;
+}
+
+const databaseReadTime = async () => {
+    const guild = await client.guilds.fetch(config.managementGuildID);
+    const user = guild.ownerId;
+    const currentYear = new Date().getFullYear();
+    const start = Date.now();
+    client.database.history.read(guild.id, user, currentYear - 1);
+    const end = Date.now();
+    return end - start;
+}
+
+const resources = () => {
+    const current = resourceUsage();
+    const temperatureRaw = spawn("acpi", ["-t"])
+    let temperatureData: number = 0;
+    temperatureRaw.stdout.on("data", (data) => {
+        return temperatureData = data.toString().split(", ")[1].split(" ")[0];  // °C
+    })
+    return {
+        memory: current.sharedMemorySize,
+        cpu: current.userCPUTime + current.systemCPUTime,
+        temperature: temperatureData
+    }
+}
+
+const record = async () => {
+    const results = {
+        discord: discordPing(),
+        databaseRead: await databaseReadTime(),
+        resources: resources()
+    }
+    client.database.performanceTest.record(results)
+    setInterval(async () => {
+        record();
+    }, 10 * 1000);
+}
+
+export { record };
\ No newline at end of file