Development (#91)

diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts
index 6997393..9c9430d 100644
--- a/src/actions/roleMenu.ts
+++ b/src/actions/roleMenu.ts
@@ -45,7 +45,8 @@
 export const configToDropdown = (
     placeholder: string,
     currentPageData: ObjectSchema,
-    selectedRoles?: string[]
+    selectedRoles?: string[],
+    disabled?: boolean
 ): ActionRowBuilder<StringSelectMenuBuilder> => {
     return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
         new StringSelectMenuBuilder()
@@ -53,6 +54,7 @@
             .setPlaceholder(placeholder)
             .setMinValues(currentPageData.min)
             .setMaxValues(currentPageData.max)
+            .setDisabled(disabled)
             .addOptions(
                 currentPageData.options.map((option: { name: string; description: string | null; role: string }) => {
                     const builder = new StringSelectMenuOptionBuilder()
diff --git a/src/commands/nucleus/guide.ts b/src/commands/nucleus/guide.ts
index c95e78c..8c32ef6 100644
--- a/src/commands/nucleus/guide.ts
+++ b/src/commands/nucleus/guide.ts
@@ -6,7 +6,7 @@
     builder.setName("guide").setDescription("Shows the welcome guide for the bot");
 
 const callback = async (interaction: CommandInteraction) => {
-    await guide(interaction.guild!, interaction);
+    await guide(interaction.guild, interaction);
 };
 
 export { command };
diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts
index fed2964..5894850 100644
--- a/src/commands/nucleus/premium.ts
+++ b/src/commands/nucleus/premium.ts
@@ -165,7 +165,10 @@
         const gaveUser = await client.users.fetch(hasPremium[1]);
         premiumGuild = `**This server has premium! It was ${
             hasPremium[2] === 3 && hasPremium[3]
-                ? `automatically applied by ${gaveUser.username}#${gaveUser.discriminator}`
+                ? `automatically applied by ` +
+                  (gaveUser.discriminator !== "0"
+                      ? `${gaveUser.username}#${gaveUser.discriminator}`
+                      : `@${gaveUser.username}`)
                 : `given by <@${hasPremium[1]}>`
         }**\n\n`;
     }
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index c1f0312..1110b9a 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -19,7 +19,6 @@
     builder.setName("suggest").setDescription("Sends a suggestion to the developers");
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    await interaction.guild?.members.fetch(interaction.member!.user.id);
     await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
     let closed = false;
     let suggestionTitle: string | null = null;
@@ -90,18 +89,25 @@
     suggestionTitle = suggestionTitle ? suggestionTitle : `${suggestionDesc.substring(0, 70)}`;
     const channel = client.channels.cache.get(config.suggestionChannel) as Discord.TextChannel;
     const m = await channel.send({ embeds: LoadingEmbed });
-    const issue = await client.GitHub.rest.issues.create({
-        owner: "ClicksMinutePer",
-        repo: "Nucleus",
-        title: suggestionTitle,
-        body: `Linked Suggestion in Private Developer Channel: [Message](${
-            m.url
-        })\n\n**Suggestion:**\n> ${suggestionDesc
-            .replaceAll("@", "@<!-- -->")
-            .replaceAll("/issues", "/issues<!-- -->")
-            .replaceAll("/pull", "/pull<!-- -->")}\n\n`,
-        labels: ["🤖 Auto", "📝 Suggestion"]
-    });
+    let issueNumber: number | null = null;
+    try {
+        const issue = await client.GitHub.rest.issues.create({
+            owner: "ClicksMinutePer",
+            repo: "Nucleus",
+            title: suggestionTitle,
+            body: `Linked Suggestion in Private Developer Channel: [Message](${
+                m.url
+            })\n\n**Suggestion:**\n> ${suggestionDesc
+                .replaceAll("@", "@<!-- -->")
+                .replaceAll("/issues", "/issues<!-- -->")
+                .replaceAll("/pull", "/pull<!-- -->")}\n\n`,
+            labels: ["🤖 Auto", "📝 Suggestion"]
+        });
+        issueNumber = issue.data.number;
+    } catch (_e) {
+        console.log("Could not connect to GitHub");
+    }
+    const disabled = issueNumber ? false : true;
     await m.edit({
         embeds: [
             new EmojiEmbed()
@@ -109,25 +115,47 @@
                 .setTitle(`Suggestion from ${interaction.user.tag} (${interaction.user.id})`)
                 .setDescription(`**Suggestion:**\n> ${suggestionDesc}\n\n`)
                 .setStatus("Success")
-                .setFooter({ text: `${issue.data.number}` })
+                .setFooter({ text: `${issueNumber ? issueNumber : "Could not connect to GitHub"}` })
         ],
         components: [
             new Discord.ActionRowBuilder<ButtonBuilder>().addComponents(
-                new ButtonBuilder().setCustomId("accept:Suggestion").setLabel("Accept").setStyle(ButtonStyle.Success),
-                new ButtonBuilder().setCustomId("deny:Suggestion").setLabel("Deny").setStyle(ButtonStyle.Danger),
-                new ButtonBuilder().setCustomId("close:Suggestion").setLabel("Close").setStyle(ButtonStyle.Secondary),
+                new ButtonBuilder()
+                    .setCustomId("accept:Suggestion")
+                    .setLabel("Accept")
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(disabled),
+                new ButtonBuilder()
+                    .setCustomId("deny:Suggestion")
+                    .setLabel("Deny")
+                    .setStyle(ButtonStyle.Danger)
+                    .setDisabled(disabled),
+                new ButtonBuilder()
+                    .setCustomId("close:Suggestion")
+                    .setLabel("Close")
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(disabled),
                 new ButtonBuilder()
                     .setCustomId("implemented:Suggestion")
                     .setLabel("Implemented")
-                    .setStyle(ButtonStyle.Secondary),
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(disabled),
                 new ButtonBuilder()
-                    .setLabel(`Open Issue #${issue.data.number}`)
+                    .setLabel(`Open Issue #${issueNumber ? issueNumber : "0"}`)
                     .setStyle(ButtonStyle.Link)
-                    .setURL(`https://github.com/ClicksMinutePer/Nucleus/issues/${issue.data.number}`)
+                    .setURL(`https://github.com/ClicksMinutePer/Nucleus/issues/${issueNumber}`)
+                    .setDisabled(disabled)
             ),
             new Discord.ActionRowBuilder<ButtonBuilder>().addComponents(
-                new ButtonBuilder().setCustomId("lock:Suggestion").setLabel("Lock").setStyle(ButtonStyle.Danger),
-                new ButtonBuilder().setCustomId("spam:Suggestion").setLabel("Mark as Spam").setStyle(ButtonStyle.Danger)
+                new ButtonBuilder()
+                    .setCustomId("lock:Comment")
+                    .setLabel("Lock")
+                    .setStyle(ButtonStyle.Danger)
+                    .setDisabled(disabled),
+                new ButtonBuilder()
+                    .setCustomId("spam:Suggestion")
+                    .setLabel("Mark as Spam")
+                    .setStyle(ButtonStyle.Danger)
+                    .setDisabled(disabled)
             )
         ]
     });
diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts
index 7da9dfe..0b41854 100644
--- a/src/commands/settings/rolemenu.ts
+++ b/src/commands/settings/rolemenu.ts
@@ -120,6 +120,7 @@
                     .setStyle(TextInputStyle.Short)
                     .setValue(name ?? "")
                     .setRequired(true)
+                    .setMaxLength(100)
             ),
             new ActionRowBuilder<TextInputBuilder>().addComponents(
                 new TextInputBuilder()
@@ -128,6 +129,8 @@
                     .setPlaceholder("A short description of the role (e.g. A role for people who code)")
                     .setStyle(TextInputStyle.Short)
                     .setValue(description ?? "")
+                    .setRequired(false)
+                    .setMaxLength(100)
             )
         );
     const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
@@ -159,15 +162,15 @@
     if (!out) return [name, description];
     if (out.isButton()) return [name, description];
     name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
-    description = out.fields.fields.find((f) => f.customId === "description")?.value ?? description;
+    description = out.fields.fields.find((f) => f.customId === "description")?.value ?? "";
     return [name, description];
 };
 
 const defaultRoleMenuData = {
-    name: "Role Menu Page",
-    description: "A new role menu page",
+    name: "New Page",
+    description: "",
     min: 0,
-    max: 0,
+    max: 1,
     options: []
 };
 
@@ -196,17 +199,20 @@
     );
 
     let back = false;
-    if (data.options.length === 0) {
-        data.options = [{ name: "Role 1", description: null, role: "No role set" }];
-    }
     do {
-        const previewSelect = configToDropdown("Edit Roles", {
-            name: data.name,
-            description: data.description,
-            min: 1,
-            max: 1,
-            options: data.options
-        });
+        const noRoles = data.options.length === 0;
+        const previewSelect = configToDropdown(
+            "Edit Roles",
+            {
+                name: data.name,
+                description: data.description,
+                min: 1,
+                max: 1,
+                options: noRoles ? [{ name: "Role 1", description: null, role: "No role set" }] : data.options
+            },
+            undefined,
+            noRoles
+        );
         const embed = new EmojiEmbed()
             .setTitle(`${data.name}`)
             .setStatus("Success")
@@ -215,7 +221,8 @@
                     `**Min:** ${data.min}` +
                     (data.min === 0 ? " (Members will be given a skip button)" : "") +
                     "\n" +
-                    `**Max:** ${data.max}\n`
+                    `**Max:** ${data.max}\n` +
+                    `\n**Roles:** ${data.options.length === 0 ? "*No roles set*" : data.options.length}`
             );
 
         await interaction.editReply({ embeds: [embed], components: [previewSelect, buttons] });
@@ -237,7 +244,8 @@
                 await createRoleMenuOptionPage(
                     interaction,
                     m,
-                    data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0])
+                    data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0]),
+                    false
                 );
             }
         } else if (i.isButton()) {
@@ -255,7 +263,8 @@
                 }
                 case "addRole": {
                     await i.deferUpdate();
-                    data.options.push(await createRoleMenuOptionPage(interaction, m));
+                    const out = await createRoleMenuOptionPage(interaction, m, undefined, true);
+                    if (out) data.options.push(out);
                     break;
                 }
             }
@@ -268,8 +277,10 @@
 const createRoleMenuOptionPage = async (
     interaction: StringSelectMenuInteraction | ButtonInteraction,
     m: Message,
-    data?: { name: string; description: string | null; role: string }
+    data?: { name: string; description: string | null; role: string },
+    newRole: boolean = false
 ) => {
+    const initialData = _.cloneDeep(data);
     const { renderRole } = client.logger;
     if (!data)
         data = {
@@ -281,19 +292,29 @@
     const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
         new ButtonBuilder()
             .setCustomId("back")
-            .setLabel("Back")
-            .setStyle(ButtonStyle.Secondary)
-            .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+            .setLabel(newRole ? "Add" : "Back")
+            .setStyle(newRole ? ButtonStyle.Success : ButtonStyle.Secondary)
+            .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji),
         new ButtonBuilder()
             .setCustomId("edit")
             .setLabel("Edit Details")
             .setStyle(ButtonStyle.Primary)
-            .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji)
+            .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+        new ButtonBuilder()
+            .setCustomId("delete")
+            .setLabel("Delete")
+            .setStyle(ButtonStyle.Danger)
+            .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji),
+        new ButtonBuilder()
+            .setCustomId("cancel")
+            .setLabel("Cancel")
+            .setStyle(ButtonStyle.Secondary)
+            .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
     );
     do {
         const roleSelect = new RoleSelectMenuBuilder()
             .setCustomId("role")
-            .setPlaceholder(data.role ? "Set role to" : "Set the role");
+            .setPlaceholder(data.role ? "Change role to" : "Select a role");
         const embed = new EmojiEmbed()
             .setTitle(`${data.name}`)
             .setStatus("Success")
@@ -325,6 +346,12 @@
             if (i.customId === "role") {
                 await i.deferUpdate();
                 data.role = (i as RoleSelectMenuInteraction).values[0]!;
+                await interaction.editReply({
+                    embeds: [
+                        new EmojiEmbed().setTitle(`Applying changes`).setStatus("Danger").setEmoji("NUCLEUS.LOADING")
+                    ],
+                    components: []
+                });
             }
         } else if (i.isButton()) {
             switch (i.customId) {
@@ -334,7 +361,6 @@
                     break;
                 }
                 case "edit": {
-                    await i.deferUpdate();
                     const [name, description] = await editNameDescription(
                         i,
                         interaction,
@@ -345,6 +371,15 @@
                     data.description = description ? description : data.description;
                     break;
                 }
+                case "delete": {
+                    await i.deferUpdate();
+                    return null;
+                }
+                case "cancel": {
+                    await i.deferUpdate();
+                    if (newRole) return null;
+                    else return initialData;
+                }
             }
         }
     } while (!back);
@@ -383,6 +418,7 @@
                     .setValue("delete")
                     .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
             );
+        console.log(page);
         const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
             new ButtonBuilder()
                 .setCustomId("back")
@@ -422,7 +458,7 @@
             actionSelect.setDisabled(true);
             pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No role menu pages").setValue("none"));
         } else {
-            page = Math.min(page, Object.keys(currentObject).length - 1);
+            page = Math.max(Math.min(page, currentObject.length - 1), 0);
             current = currentObject[page]!;
             embed.setDescription(
                 `**Currently Editing:** ${current.name}\n\n` +
diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts
index 98ffcff..cd4778a 100644
--- a/src/events/messageCreate.ts
+++ b/src/events/messageCreate.ts
@@ -32,9 +32,10 @@
             await message.crosspost();
         } else {
             await singleNotify(
-                `Nucleus does not have Manage Messages in <#${message.channel.id}>`,
+                "crosspost.noManageMessages",
                 message.guild.id,
-                true
+                `Nucleus does not have Manage Messages in <#${message.channel.id}>`,
+                "Warning"
             );
         }
     }
diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts
index 59c12c7..93978e6 100644
--- a/src/reflex/guide.ts
+++ b/src/reflex/guide.ts
@@ -16,35 +16,49 @@
 import createPageIndicator from "../utils/createPageIndicator.js";
 import { Embed } from "../utils/defaults.js";
 
-export default async (guild: Guild, interaction?: CommandInteraction) => {
-    let c: GuildTextBasedChannel | null = guild.publicUpdatesChannel ? guild.publicUpdatesChannel : guild.systemChannel;
-    c = c
-        ? c
-        : (guild.channels.cache.find(
-              (ch) =>
-                  [
-                      ChannelType.GuildText,
-                      ChannelType.GuildAnnouncement,
-                      ChannelType.PublicThread,
-                      ChannelType.PrivateThread,
-                      ChannelType.AnnouncementThread
-                  ].includes(ch.type) &&
-                  ch.permissionsFor(guild.roles.everyone).has("SendMessages") &&
-                  ch.permissionsFor(guild.members.me!).has("EmbedLinks")
-          ) as GuildTextBasedChannel | undefined) ?? null;
-    if (interaction) c = interaction.channel as GuildTextBasedChannel;
-    if (!c) {
-        return;
-    }
+export default async (guild: Guild | null, interaction?: CommandInteraction) => {
     let m: Message;
-    if (interaction) {
-        m = (await interaction.reply({
-            embeds: LoadingEmbed,
-            fetchReply: true,
-            ephemeral: true
-        })) as Message;
+    if (guild) {
+        let c: GuildTextBasedChannel | null = guild.publicUpdatesChannel
+            ? guild.publicUpdatesChannel
+            : guild.systemChannel;
+        c = c
+            ? c
+            : (guild.channels.cache.find(
+                  (ch) =>
+                      [
+                          ChannelType.GuildText,
+                          ChannelType.GuildAnnouncement,
+                          ChannelType.PublicThread,
+                          ChannelType.PrivateThread,
+                          ChannelType.AnnouncementThread
+                      ].includes(ch.type) &&
+                      ch.permissionsFor(guild.roles.everyone).has("SendMessages") &&
+                      ch.permissionsFor(guild.members.me!).has("EmbedLinks")
+              ) as GuildTextBasedChannel | undefined) ?? null;
+        if (interaction) c = interaction.channel as GuildTextBasedChannel;
+        if (!c) {
+            return;
+        }
+        if (interaction) {
+            m = (await interaction.reply({
+                embeds: LoadingEmbed,
+                fetchReply: true,
+                ephemeral: true
+            })) as Message;
+        } else {
+            m = await c.send({ embeds: LoadingEmbed });
+        }
     } else {
-        m = await c.send({ embeds: LoadingEmbed });
+        if (interaction) {
+            m = (await interaction.reply({
+                embeds: LoadingEmbed,
+                fetchReply: true,
+                ephemeral: true
+            })) as Message;
+        } else {
+            return;
+        }
     }
     let page = 0;
     const pages = [
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index 0e4a9b4..fc29b56 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -226,7 +226,8 @@
             try {
                 component = await m.awaitMessageComponent({
                     filter: (i) =>
-                        i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id,
+                        i.user.id === this.interaction.user.id &&
+                        (i.channel ? i.channel!.id === this.interaction.channel!.id : true),
                     time: 300000
                 });
             } catch (e) {
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 77ff875..f24416a 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -192,7 +192,7 @@
 
 interface TranscriptAuthor {
     username: string;
-    discriminator: number;
+    discriminator: string | undefined;
     nickname?: string;
     id: string;
     iconURL?: string;
@@ -390,7 +390,7 @@
             type: type,
             for: {
                 username: member!.user.username,
-                discriminator: parseInt(member!.user.discriminator),
+                discriminator: member!.user.discriminator === "0" ? undefined : member!.user.discriminator,
                 id: member!.user.id,
                 topRole: {
                     color: member!.roles.highest.color
@@ -404,7 +404,7 @@
             createdTimestamp: Date.now(),
             createdBy: {
                 username: interaction.user.username,
-                discriminator: parseInt(interaction.user.discriminator),
+                discriminator: interaction.user.discriminator === "0" ? undefined : interaction.user.discriminator,
                 id: interaction.user.id,
                 topRole: {
                     color: interactionMember?.roles.highest.color ?? 0x000000
@@ -420,7 +420,7 @@
                 id: message.id,
                 author: {
                     username: message.author.username,
-                    discriminator: parseInt(message.author.discriminator),
+                    discriminator: message.author.discriminator === "0" ? undefined : message.author.discriminator,
                     id: message.author.id,
                     topRole: {
                         color: message.member ? message.member.roles.highest.color : 0x000000
@@ -499,9 +499,7 @@
                     out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
                 } else out += `> [Reply To] ${message.referencedMessage}\n`;
             }
-            out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${
-                message.author.id
-            }) (${message.id})`;
+            out += `${message.author.nickname ?? message.author.username} (${message.author.id}) (${message.id})`;
             out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
             if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
             out += "\n";
diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts
index e96ff5d..b9f2c23 100644
--- a/src/utils/dualCollector.ts
+++ b/src/utils/dualCollector.ts
@@ -54,7 +54,7 @@
     return i.channel!.id === m.channel!.id && i.user.id === user.id;
 }
 function defaultModalFilter(i: ModalSubmitInteraction, user: User, m: Message) {
-    return i.channel!.id === m.channel!.id && i.user.id === user.id;
+    return (i.channel ? i.channel!.id === m.channel!.id : true) && i.user.id === user.id;
 }
 
 export async function modalInteractionCollector(