Development (#85)

- updated versioning
- added myself to contributors list :(
- I'm not sure if this works yet. DO NOT PR UNTIL TESTED
- Squashed Bugs
- Fixed User Context Menus
- added flags and fixed user context menus
- fixed server buttons
- added permission
- nothing notable
- fixed lowercase messageCreate logs
diff --git a/.eslintignore b/.eslintignore
index f7770da..0b193f6 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
 ClicksMigratingProblems/**/*
 src/reflex/nsfwjs/**/*
-ecosystem.config.cjs
\ No newline at end of file
+ecosystem.config.cjs
+test.ts
\ No newline at end of file
diff --git a/package.json b/package.json
index 08ca2f2..3591ee0 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
         "discord-api-types": "0.37.23"
     },
     "name": "nucleus",
-    "version": "0.0.1",
+    "version": "1.1.0",
     "description": "Nucleus: The core of your server",
     "main": "dist/index.js",
     "scripts": {
@@ -61,7 +61,8 @@
     "author": "Clicks",
     "contributors": [
         "Minion3665",
-        "PineappleFan"
+        "PineappleFan",
+        "TheCodedProf"
     ],
     "license": "SEE LICENSE IN LICENSE",
     "bugs": {
diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts
index 9630288..3328d64 100644
--- a/src/commands/mod/about.ts
+++ b/src/commands/mod/about.ts
@@ -1,5 +1,5 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
-import type { HistorySchema } from "../../utils/database.js";
+import type { HistorySchema, FlagColors } from "../../utils/database.js";
 import Discord, {
     CommandInteraction,
     GuildMember,
@@ -7,11 +7,13 @@
     ActionRowBuilder,
     ButtonBuilder,
     MessageComponentInteraction,
-    ModalSubmitInteraction,
     ButtonStyle,
     TextInputStyle,
     APIMessageComponentEmoji,
-    SlashCommandSubcommandBuilder
+    SlashCommandSubcommandBuilder,
+    StringSelectMenuBuilder,
+    StringSelectMenuOptionBuilder,
+    ContextMenuCommandInteraction
 } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
@@ -327,35 +329,41 @@
     return timedOut ? 0 : 1;
 }
 
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
+export const noteMenu = async (
+    member: GuildMember,
+    interaction: CommandInteraction | ContextMenuCommandInteraction
+): Promise<unknown> => {
     let m: Message;
-    const member = interaction.options.getMember("user") as Discord.GuildMember;
     await interaction.reply({
         embeds: LoadingEmbed,
         ephemeral: true,
         fetchReply: true
     });
     let note;
-    let firstLoad = true;
     let timedOut = false;
     while (!timedOut) {
         note = await client.database.notes.read(member.guild.id, member.id);
-        if (firstLoad && !note) {
-            await showHistory(member, interaction);
-        }
-        firstLoad = false;
+        const colors: Record<string, Discord.ColorResolvable> = {
+            none: "#424242",
+            red: "#F27878",
+            yellow: "#EDC575",
+            green: "#68D49E",
+            blue: "#6576CC",
+            purple: "#D46899",
+            gray: "#C4C4C4"
+        };
         m = (await interaction.editReply({
             embeds: [
                 new EmojiEmbed()
-                    .setEmoji("MEMBER.JOIN")
+                    .setEmoji(`ICONS.FLAGS.${(note?.flag ?? "none").toUpperCase()}`)
                     .setTitle("Mod notes for " + member.user.username)
-                    .setDescription(note ? note : "*No note set*")
-                    .setStatus("Success")
+                    .setDescription(note?.note ? note.note : "*No note set*")
+                    .setColor(colors[note?.flag ?? "none"]!)
             ],
             components: [
                 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
                     new ButtonBuilder()
-                        .setLabel(`${note ? "Modify" : "Create"} note`)
+                        .setLabel(`${note?.note ? "Modify" : "Create"} note`)
                         .setStyle(ButtonStyle.Primary)
                         .setCustomId("modify")
                         .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
@@ -364,6 +372,49 @@
                         .setStyle(ButtonStyle.Primary)
                         .setCustomId("history")
                         .setEmoji(getEmojiByName("ICONS.HISTORY", "id"))
+                ]),
+                new ActionRowBuilder<Discord.StringSelectMenuBuilder>().addComponents([
+                    new StringSelectMenuBuilder()
+                        .setCustomId("flag")
+                        .setPlaceholder("Select a flag")
+                        .addOptions(
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("None")
+                                .setDefault(!note?.flag)
+                                .setValue("none")
+                                .setDescription("Clear")
+                                .setEmoji(getEmojiByName("ICONS.FLAGS.NONE", "id")),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Red")
+                                .setDefault(note?.flag === "red")
+                                .setValue("red")
+                                .setEmoji(getEmojiByName("ICONS.FLAGS.RED", "id")),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Yellow")
+                                .setDefault(note?.flag === "yellow")
+                                .setValue("yellow")
+                                .setEmoji(getEmojiByName("ICONS.FLAGS.YELLOW", "id")),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Green")
+                                .setDefault(note?.flag === "green")
+                                .setValue("green")
+                                .setEmoji(getEmojiByName("ICONS.FLAGS.GREEN", "id")),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Blue")
+                                .setDefault(note?.flag === "blue")
+                                .setValue("blue")
+                                .setEmoji(getEmojiByName("ICONS.FLAGS.BLUE", "id")),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Purple")
+                                .setDefault(note?.flag === "purple")
+                                .setValue("purple")
+                                .setEmoji(getEmojiByName("ICONS.FLAGS.PURPLE", "id")),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Gray")
+                                .setDefault(note?.flag === "gray")
+                                .setValue("gray")
+                                .setEmoji(getEmojiByName("ICONS.FLAGS.GRAY", "id"))
+                        )
                 ])
             ]
         })) as Message;
@@ -383,65 +434,74 @@
             timedOut = true;
             continue;
         }
-        if (i.customId === "modify") {
-            await i.showModal(
-                new Discord.ModalBuilder()
-                    .setCustomId("modal")
-                    .setTitle("Editing moderator note")
-                    .addComponents(
-                        new ActionRowBuilder<Discord.TextInputBuilder>().addComponents(
-                            new Discord.TextInputBuilder()
-                                .setCustomId("note")
-                                .setLabel("Note")
-                                .setMaxLength(4000)
-                                .setRequired(false)
-                                .setStyle(TextInputStyle.Paragraph)
-                                .setValue(note ? note : " ")
+        if (i.isButton()) {
+            if (i.customId === "modify") {
+                await i.showModal(
+                    new Discord.ModalBuilder()
+                        .setCustomId("modal")
+                        .setTitle("Editing moderator note")
+                        .addComponents(
+                            new ActionRowBuilder<Discord.TextInputBuilder>().addComponents(
+                                new Discord.TextInputBuilder()
+                                    .setCustomId("note")
+                                    .setLabel("Note")
+                                    .setMaxLength(4000)
+                                    .setRequired(false)
+                                    .setStyle(TextInputStyle.Paragraph)
+                                    .setValue(note?.note ? note.note : " ")
+                            )
                         )
-                    )
-            );
-            await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Mod notes for " + member.user.username)
-                        .setDescription("Modal opened. If you can't see it, click back and try again.")
-                        .setStatus("Success")
-                        .setEmoji("GUILD.TICKET.OPEN")
-                ],
-                components: [
-                    new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
-                        new ButtonBuilder()
-                            .setLabel("Back")
-                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
-                            .setStyle(ButtonStyle.Primary)
-                            .setCustomId("back")
-                    ])
-                ]
-            });
-            let out;
-            try {
-                out = await modalInteractionCollector(m, interaction.user);
-            } catch (e) {
-                timedOut = true;
-                continue;
+                );
+                await interaction.editReply({
+                    embeds: [
+                        new EmojiEmbed()
+                            .setTitle("Mod notes for " + member.user.username)
+                            .setDescription("Modal opened. If you can't see it, click back and try again.")
+                            .setStatus("Success")
+                            .setEmoji("GUILD.TICKET.OPEN")
+                    ],
+                    components: [
+                        new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
+                            new ButtonBuilder()
+                                .setLabel("Back")
+                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                .setStyle(ButtonStyle.Primary)
+                                .setCustomId("back")
+                        ])
+                    ]
+                });
+                let out;
+                try {
+                    out = await modalInteractionCollector(m, interaction.user);
+                } catch (e) {
+                    timedOut = true;
+                    continue;
+                }
+                if (out === null || out.isButton()) {
+                    continue;
+                } else {
+                    let toAdd = out.fields.getTextInputValue("note").trim() || null;
+                    if (toAdd === "") toAdd = null;
+                    await client.database.notes.create(member.guild.id, member.id, toAdd);
+                }
+            } else if (i.customId === "history") {
+                await i.deferUpdate();
+                if (!(await showHistory(member, interaction))) return;
             }
-            if (out === null || out.isButton()) {
-                continue;
-            } else if (out instanceof ModalSubmitInteraction) {
-                let toAdd = out.fields.getTextInputValue("note") || null;
-                if (toAdd === " ") toAdd = null;
-                if (toAdd) toAdd = toAdd.trim();
-                await client.database.notes.create(member.guild.id, member.id, toAdd);
-            } else {
-                continue;
-            }
-        } else if (i.customId === "history") {
+        } else if (i.isStringSelectMenu()) {
             await i.deferUpdate();
-            if (!(await showHistory(member, interaction))) return;
+            let flag: string | null = i.values[0]!;
+            if (flag === "none") flag = null;
+            await client.database.notes.flag(member.guild.id, member.id, flag as FlagColors | null);
         }
     }
 };
 
+const callback = async (interaction: CommandInteraction): Promise<void> => {
+    const member = interaction.options.getMember("user") as Discord.GuildMember;
+    await noteMenu(member, interaction);
+};
+
 const check = (interaction: CommandInteraction) => {
     const member = interaction.member as GuildMember;
     if (!member.permissions.has("ManageMessages")) return "You do not have the *Manage Messages* permission";
diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts
index f07f3ce..b98e821 100644
--- a/src/commands/server/buttons.ts
+++ b/src/commands/server/buttons.ts
@@ -6,7 +6,6 @@
     ChannelSelectMenuBuilder,
     ChannelType,
     CommandInteraction,
-    MessageCreateOptions,
     ModalBuilder,
     SlashCommandSubcommandBuilder,
     StringSelectMenuBuilder,
@@ -98,7 +97,7 @@
                 .setCustomId("send")
                 .setLabel("Send")
                 .setStyle(ButtonStyle.Primary)
-                .setDisabled(!data.channel)
+                .setDisabled(!(data.channel && (data.title ?? data.description ?? data.buttons.length)))
         );
 
         const colorSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
@@ -137,7 +136,7 @@
             new StringSelectMenuBuilder()
                 .setCustomId("button")
                 .setPlaceholder("Select buttons to add")
-                .setMinValues(1)
+                .setMinValues(0)
                 .setMaxValues(3)
                 .addOptions(
                     new StringSelectMenuOptionBuilder()
@@ -175,7 +174,7 @@
             .setTitle(data.title ?? "No title set")
             .setDescription(data.description ?? "*No description set*")
             .setColor(data.color)
-            .setFooter({ text: `Click the button below to edit the embed | The embed will be sent in ${channelName}` });
+            .setFooter({ text: `The embed will be sent in ${channelName} | Click the button below to edit the embed` });
 
         await interaction.editReply({
             embeds: [embed],
@@ -266,9 +265,10 @@
                 case "send": {
                     await i.deferUpdate();
                     const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel;
-                    const messageData: MessageCreateOptions = {};
+                    let components: ActionRowBuilder<ButtonBuilder>[] = [];
+                    let embeds: EmojiEmbed[] = [];
                     for (const button of data.buttons) {
-                        messageData.components = [
+                        components = [
                             new ActionRowBuilder<ButtonBuilder>().addComponents(
                                 new ButtonBuilder()
                                     .setCustomId(button)
@@ -277,14 +277,14 @@
                             )
                         ];
                     }
-                    if (data.title || data.description || data.color) {
+                    if (data.title || data.description) {
                         const e = new EmojiEmbed();
                         if (data.title) e.setTitle(data.title);
                         if (data.description) e.setDescription(data.description);
                         if (data.color) e.setColor(data.color);
-                        messageData.embeds = [e];
+                        embeds = [e];
                     }
-                    await channel.send(messageData);
+                    await channel.send({ embeds, components });
                     break;
                 }
             }
diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts
index 5b4198f..de8e740 100644
--- a/src/commands/settings/automod.ts
+++ b/src/commands/settings/automod.ts
@@ -757,8 +757,9 @@
                                 .addComponents(
                                     new ActionRowBuilder<TextInputBuilder>().addComponents(
                                         new TextInputBuilder()
+                                            .setLabel("Amount")
                                             .setCustomId("mass")
-                                            .setPlaceholder("Amount")
+                                            .setPlaceholder(current.mass === 5 ? "Amount" : current.mass.toString())
                                             .setMinLength(1)
                                             .setMaxLength(3)
                                             .setStyle(TextInputStyle.Short)
diff --git a/src/config/emojis.ts b/src/config/emojis.ts
index b7ed91a..b04c569 100644
--- a/src/config/emojis.ts
+++ b/src/config/emojis.ts
@@ -51,6 +51,7 @@
             CATEGORY: "1064943289708597348"
         },
         FLAGS: {
+            NONE: "1099782406417940520",
             RED: "1082719687219101800",
             YELLOW: "1082719684060794890",
             GREEN: "1082719681326108763",
diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts
index a75a281..0b7723e 100644
--- a/src/context/messages/purgeto.ts
+++ b/src/context/messages/purgeto.ts
@@ -9,12 +9,15 @@
     GuildMember,
     GuildTextBasedChannel,
     Message,
-    MessageContextMenuCommandInteraction
+    MessageContextMenuCommandInteraction,
+    PermissionFlagsBits
 } from "discord.js";
 import client from "../../utils/client.js";
 import { messageException } from "../../utils/createTemporaryStorage.js";
 
-const command = new ContextMenuCommandBuilder().setName("Purge up to here");
+const command = new ContextMenuCommandBuilder()
+    .setName("Purge up to Here")
+    .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages);
 
 async function waitForButton(m: Discord.Message, member: Discord.GuildMember): Promise<boolean> {
     let component;
@@ -38,9 +41,6 @@
     const channel = interaction.channel;
     if (!channel) return;
     await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
-    // Option for "include this message"?
-    // Option for "Only selected user"?
-
     const history: Discord.Collection<string, Discord.Message> = await channel.messages.fetch({ limit: 100 });
     if (Date.now() - targetMessage.createdTimestamp > 2 * 7 * 24 * 60 * 60 * 1000) {
         const m = await interaction.editReply({
diff --git a/src/context/users/flaguser.ts b/src/context/users/flaguser.ts
new file mode 100644
index 0000000..e0578a0
--- /dev/null
+++ b/src/context/users/flaguser.ts
@@ -0,0 +1,24 @@
+import {
+    ContextMenuCommandBuilder,
+    GuildMember,
+    PermissionFlagsBits,
+    UserContextMenuCommandInteraction
+} from "discord.js";
+import { noteMenu } from "../../commands/mod/about.js";
+
+const command = new ContextMenuCommandBuilder()
+    .setName("Flag User")
+    .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages);
+
+const callback = async (interaction: UserContextMenuCommandInteraction) => {
+    const guild = interaction.guild!;
+    let member = interaction.targetMember as GuildMember | null;
+    if (!member) member = await guild.members.fetch(interaction.targetId);
+    await noteMenu(member, interaction);
+};
+
+const check = async (_interaction: UserContextMenuCommandInteraction) => {
+    return true;
+};
+
+export { command, callback, check };
diff --git a/src/context/users/userinfo.ts b/src/context/users/userinfo.ts
index 9c7433f..a02766f 100644
--- a/src/context/users/userinfo.ts
+++ b/src/context/users/userinfo.ts
@@ -5,12 +5,14 @@
 
 const callback = async (interaction: UserContextMenuCommandInteraction) => {
     const guild = interaction.guild!;
-    let member = interaction.targetMember;
+    let member = interaction.targetMember as GuildMember | null;
     if (!member) member = await guild.members.fetch(interaction.targetId);
     await userAbout(guild, member as GuildMember, interaction);
 };
 
-const check = async (_interaction: UserContextMenuCommandInteraction) => {
+const check = async (interaction: UserContextMenuCommandInteraction) => {
+    if (!interaction.inGuild()) return "You must be in a server to use this command.";
+
     return true;
 };
 
diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts
index b79c2da..e396c03 100644
--- a/src/events/channelDelete.ts
+++ b/src/events/channelDelete.ts
@@ -8,12 +8,50 @@
     ThreadChannel,
     VoiceChannel
 } from "discord.js";
-import type { NucleusClient } from "../utils/client.js";
+import _client, { NucleusClient } from "../utils/client.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
 
 export const event = "channelDelete";
 
+// function getPropFromObject(splitProp: string[], object: Record<string, unknown>) {
+//     if (splitProp.length === 0) return null
+//     if (splitProp.length === 1) {
+//         return object[splitProp[0]!]
+//     }
+//     const property: string = splitProp[0]!
+//     if (! Object.keys(object).includes(property)) return null;
+//     splitProp = splitProp.splice(1)
+//     return getPropFromObject(splitProp, object[property] as Record<string, unknown>)
+// }
+
+// async function deleteFromGuildConfig(channel: GuildBasedChannel) {
+//     const guildConfig = await client.database.guilds.read(channel.guild.id);
+//     const lists = [
+//         "filters.wordFilter.allowed.channels",
+//         "filters.invite.allowed.channels",
+//         "filters.pings.allowed.channels",
+//         "filters.clean.allowed.channels",
+//         "filters.autoPublish.allowed.channels"
+//     ]
+//     const single = [
+//         "welcome.channel",
+//         "logging.logs.channel",
+//         "logging.staff.channel",
+//         "logging.attachments.channel",
+//         "tickets.category"
+//     ]
+//     console.log(guildConfig, lists, single)
+//     // for (const list of lists) {
+//     //     const index = guildConfig[list].indexOf(channel.id);
+//     //     if (index !== -1) guildConfig[list].splice(index, 1);
+//     // }
+// };
+
 export async function callback(client: NucleusClient, channel: GuildBasedChannel) {
+    // In future, please avoid using client from the outer scope. If you import client separately this
+    // parameter should shadow it.
+
+    // await deleteFromGuildConfig(channel)
     const { getAuditLog, log, isLogging, NucleusColors, entry, renderDelta, renderUser } = client.logger;
     if (!(await isLogging(channel.guild.id, "channelUpdate"))) return;
     const auditLog = (await getAuditLog(channel.guild, AuditLogEvent.ChannelDelete)).filter(
diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts
index cc69bf0..e1aa359 100644
--- a/src/events/messageCreate.ts
+++ b/src/events/messageCreate.ts
@@ -5,11 +5,19 @@
 import getEmojiByName from "../utils/getEmojiByName.js";
 import client from "../utils/client.js";
 import { callback as statsChannelUpdate } from "../reflex/statsChannelUpdate.js";
-import { ChannelType, Message, ThreadChannel } from "discord.js";
+import { ChannelType, GuildMember, Message, ThreadChannel } from "discord.js";
 import singleNotify from "../utils/singleNotify.js";
 
 export const event = "messageCreate";
 
+function checkUserForExceptions(user: GuildMember, exceptions: { roles: string[]; users: string[] }) {
+    if (exceptions.users.includes(user.id)) return true;
+    for (const role of user.roles.cache.values()) {
+        if (exceptions.roles.includes(role.id)) return true;
+    }
+    return false;
+}
+
 export async function callback(_client: NucleusClient, message: Message) {
     if (!message.guild) return;
     const config = await client.memory.readGuildInfo(message.guild.id);
@@ -42,17 +50,14 @@
     const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
 
     const fileNames = await logAttachment(message);
-
-    const content = message.content.toLowerCase() || "";
+    const lowerCaseContent = message.content.toLowerCase() || "";
     if (config.filters.clean.channels.includes(message.channel.id)) {
-        const memberRoles = message.member!.roles.cache.map((role) => role.id);
-        const roleAllow = config.filters.clean.allowed.roles.some((role) => memberRoles.includes(role));
-        const userAllow = config.filters.clean.allowed.users.includes(message.author.id);
-        if (!roleAllow && !userAllow) return await message.delete();
+        if (!checkUserForExceptions(message.member!, config.filters.clean.allowed)) return await message.delete();
     }
 
     const filter = getEmojiByName("ICONS.FILTER");
     let attachmentJump = "";
+    console.log(config.logging.attachments.saved);
     if (config.logging.attachments.saved[message.channel.id + message.id]) {
         attachmentJump = ` [[View attachments]](${config.logging.attachments.saved[message.channel.id + message.id]})`;
     }
@@ -72,32 +77,38 @@
     };
 
     if (config.filters.invite.enabled) {
-        if (!config.filters.invite.allowed.channels.includes(message.channel.id)) {
-            if (/(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/.test(content)) {
-                messageException(message.guild.id, message.channel.id, message.id);
-                await message.delete();
-                const data = {
-                    meta: {
-                        type: "messageDelete",
-                        displayName: "Message Deleted",
-                        calculateType: "autoModeratorDeleted",
-                        color: NucleusColors.red,
-                        emoji: "MESSAGE.DELETE",
-                        timestamp: Date.now()
-                    },
-                    separate: {
-                        start:
-                            filter +
-                            " Contained invite\n\n" +
-                            (content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*")
-                    },
-                    list: list,
-                    hidden: {
-                        guild: message.channel.guild.id
-                    }
-                };
-                return log(data);
-            }
+        if (
+            !(
+                config.filters.invite.allowed.channels.includes(message.channel.id) ||
+                checkUserForExceptions(message.member!, config.filters.invite.allowed)
+            ) &&
+            /(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/.test(lowerCaseContent)
+        ) {
+            messageException(message.guild.id, message.channel.id, message.id);
+            await message.delete();
+            const data = {
+                meta: {
+                    type: "messageDelete",
+                    displayName: "Message Deleted",
+                    calculateType: "autoModeratorDeleted",
+                    color: NucleusColors.red,
+                    emoji: "MESSAGE.DELETE",
+                    timestamp: Date.now()
+                },
+                separate: {
+                    start:
+                        filter +
+                        " Contained invite\n\n" +
+                        (lowerCaseContent
+                            ? `**Message:**\n\`\`\`${message.content}\`\`\``
+                            : "**Message:** *Message had no content*")
+                },
+                list: list,
+                hidden: {
+                    guild: message.channel.guild.id
+                }
+            };
+            return log(data);
         }
     }
 
@@ -131,8 +142,8 @@
                             start:
                                 filter +
                                 " Image detected as NSFW\n\n" +
-                                (content
-                                    ? `**Message:**\n\`\`\`${content}\`\`\``
+                                (lowerCaseContent
+                                    ? `**Message:**\n\`\`\`${message.content}\`\`\``
                                     : "**Message:** *Message had no content*")
                         },
                         list: list,
@@ -149,7 +160,11 @@
                         config.filters.wordFilter.words.loose,
                         config.filters.wordFilter.words.strict
                     );
-                    if (check !== null) {
+                    if (
+                        check !== null &&
+                        (!checkUserForExceptions(message.member!, config.filters.wordFilter.allowed) ||
+                            !config.filters.wordFilter.allowed.channels.includes(message.channel.id))
+                    ) {
                         messageException(message.guild.id, message.channel.id, message.id);
                         await message.delete();
                         const data = {
@@ -165,8 +180,8 @@
                                 start:
                                     filter +
                                     " Image contained filtered word\n\n" +
-                                    (content
-                                        ? `**Message:**\n\`\`\`${content}\`\`\``
+                                    (lowerCaseContent
+                                        ? `**Message:**\n\`\`\`${message.content}\`\`\``
                                         : "**Message:** *Message had no content*")
                             },
                             list: list,
@@ -195,8 +210,8 @@
                                     start:
                                         filter +
                                         " Image was too small\n\n" +
-                                        (content
-                                            ? `**Message:**\n\`\`\`${content}\`\`\``
+                                        (lowerCaseContent
+                                            ? `**Message:**\n\`\`\`${message.content}\`\`\``
                                             : "**Message:** *Message had no content*")
                                 },
                                 list: list,
@@ -225,7 +240,9 @@
                         start:
                             filter +
                             " File detected as malware\n\n" +
-                            (content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*")
+                            (lowerCaseContent
+                                ? `**Message:**\n\`\`\`${message.content}\`\`\``
+                                : "**Message:** *Message had no content*")
                     },
                     list: list,
                     hidden: {
@@ -254,7 +271,9 @@
                 start:
                     filter +
                     ` Link filtered as ${linkDetectionTypes[0]?.toLowerCase()}\n\n` +
-                    (content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*")
+                    (lowerCaseContent
+                        ? `**Message:**\n\`\`\`${message.content}\`\`\``
+                        : "**Message:** *Message had no content*")
             },
             list: list,
             hidden: {
@@ -264,9 +283,15 @@
         return log(data);
     }
 
-    if (config.filters.wordFilter.enabled) {
+    if (
+        config.filters.wordFilter.enabled &&
+        !(
+            checkUserForExceptions(message.member!, config.filters.wordFilter.allowed) ||
+            config.filters.wordFilter.allowed.channels.includes(message.channel.id)
+        )
+    ) {
         const check = TestString(
-            content,
+            lowerCaseContent,
             config.filters.wordFilter.words.loose,
             config.filters.wordFilter.words.strict
         );
@@ -286,7 +311,9 @@
                     start:
                         filter +
                         " Message contained filtered word\n\n" +
-                        (content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*")
+                        (lowerCaseContent
+                            ? `**Message:**\n\`\`\`${message.content}\`\`\``
+                            : "**Message:** *Message had no content*")
                 },
                 list: list,
                 hidden: {
@@ -297,7 +324,11 @@
         }
     }
 
-    if (config.filters.pings.everyone && message.mentions.everyone) {
+    if (
+        config.filters.pings.everyone &&
+        message.mentions.everyone &&
+        !checkUserForExceptions(message.member!, config.filters.pings.allowed)
+    ) {
         if (!(await isLogging(message.guild.id, "messageMassPing"))) return;
         const data = {
             meta: {
@@ -309,7 +340,9 @@
                 timestamp: Date.now()
             },
             separate: {
-                start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*"
+                start: lowerCaseContent
+                    ? `**Message:**\n\`\`\`${message.content}\`\`\``
+                    : "**Message:** *Message had no content*"
             },
             list: list,
             hidden: {
@@ -318,7 +351,7 @@
         };
         return log(data);
     }
-    if (config.filters.pings.roles) {
+    if (config.filters.pings.roles && !checkUserForExceptions(message.member!, config.filters.pings.allowed)) {
         for (const roleId in message.mentions.roles) {
             if (!config.filters.pings.allowed.roles.includes(roleId)) {
                 messageException(message.guild.id, message.channel.id, message.id);
@@ -334,8 +367,8 @@
                         timestamp: Date.now()
                     },
                     separate: {
-                        start: content
-                            ? `**Message:**\n\`\`\`${content}\`\`\``
+                        start: lowerCaseContent
+                            ? `**Message:**\n\`\`\`${message.content}\`\`\``
                             : "**Message:** *Message had no content*"
                     },
                     list: list,
@@ -347,7 +380,11 @@
             }
         }
     }
-    if (message.mentions.users.size >= config.filters.pings.mass && config.filters.pings.mass) {
+    if (
+        message.mentions.users.size >= config.filters.pings.mass &&
+        config.filters.pings.mass &&
+        !checkUserForExceptions(message.member!, config.filters.pings.allowed)
+    ) {
         messageException(message.guild.id, message.channel.id, message.id);
         await message.delete();
         if (!(await isLogging(message.guild.id, "messageMassPing"))) return;
@@ -361,7 +398,9 @@
                 timestamp: Date.now()
             },
             separate: {
-                start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*"
+                start: lowerCaseContent
+                    ? `**Message:**\n\`\`\`${message.content}\`\`\``
+                    : "**Message:** *Message had no content*"
             },
             list: list,
             hidden: {
diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts
index d76617c..5889d54 100644
--- a/src/utils/commandRegistration/register.ts
+++ b/src/utils/commandRegistration/register.ts
@@ -156,7 +156,13 @@
             context.command.setType(ApplicationCommandType.User);
             commands.push(context.command);
 
-            client.commands["contextCommands/user/" + context.command.name] = context;
+            client.commands["contextCommands/user/" + context.command.name] = [
+                context,
+                {
+                    name: context.name ?? context.command.name,
+                    description: context.description ?? context.command.description
+                }
+            ];
 
             console.log(
                 `${last.replace("└", " ").replace("├", "│")}  └─ ${colors.green}Loaded ${
diff --git a/src/utils/database.ts b/src/utils/database.ts
index b42b8b2..77ff875 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -696,15 +696,25 @@
         this.modNotes = database.collection<ModNoteSchema>("modNotes");
     }
 
+    async flag(guild: string, user: string, flag: FlagColors | null) {
+        const modNote = await this.modNotes.findOne({ guild: guild, user: user });
+        modNote
+            ? await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { flag: flag } }, collectionOptions)
+            : await this.modNotes.insertOne({ guild: guild, user: user, note: null, flag: flag }, collectionOptions);
+    }
+
     async create(guild: string, user: string, note: string | null) {
         // console.log("ModNotes create");
-        await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
+        const modNote = await this.modNotes.findOne({ guild: guild, user: user });
+        modNote
+            ? await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, collectionOptions)
+            : await this.modNotes.insertOne({ guild: guild, user: user, note: note, flag: null }, collectionOptions);
     }
 
     async read(guild: string, user: string) {
         // console.log("ModNotes read");
         const entry = await this.modNotes.findOne({ guild: guild, user: user });
-        return entry?.note ?? null;
+        return entry ?? null;
     }
 
     async delete(guild: string) {
@@ -1033,10 +1043,13 @@
     amount: string | null;
 }
 
+export type FlagColors = "red" | "yellow" | "green" | "blue" | "purple" | "gray";
+
 export interface ModNoteSchema {
     guild: string;
     user: string;
     note: string | null;
+    flag: FlagColors | null;
 }
 
 export interface PremiumSchema {