Development (#11)

We need this NOW.

---------

Co-authored-by: PineaFan <ash@pinea.dev>
Co-authored-by: pineafan <pineapplefanyt@gmail.com>
Co-authored-by: PineappleFan <PineaFan@users.noreply.github.com>
Co-authored-by: Skyler <skyler3665@gmail.com>
diff --git a/src/actions/createModActionTicket.ts b/src/actions/createModActionTicket.ts
index d6e9cd9..d86c14a 100644
--- a/src/actions/createModActionTicket.ts
+++ b/src/actions/createModActionTicket.ts
@@ -1,4 +1,4 @@
-import { getCommandMentionByName } from './../utils/getCommandMentionByName.js';
+import { getCommandMentionByName } from './../utils/getCommandDataByName.js';
 import Discord, { ActionRowBuilder, ButtonBuilder, OverwriteType, ChannelType, ButtonStyle } from "discord.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
@@ -86,7 +86,7 @@
                             `**Support type:** ${customReason ? customReason : "Appeal submission"}\n` +
                             (reason !== null ? `**Reason:**\n> ${reason}\n` : "") +
                             `**Ticket ID:** \`${c.id}\`\n` +
-                            `Type ${await getCommandMentionByName("ticket/close")} to close this ticket.`
+                            `Type ${getCommandMentionByName("ticket/close")} to close this ticket.`
                         )
                         .setStatus("Success")
                         .setEmoji("GUILD.TICKET.OPEN")
@@ -131,7 +131,7 @@
                             `**Support type:** ${customReason ? customReason : "Appeal submission"}\n` +
                             (reason !== null ? `**Reason:**\n> ${reason}\n` : "") +
                             `**Ticket ID:** \`${c.id}\`\n` +
-                            `Type ${await getCommandMentionByName("ticket/close")} to close this ticket.`
+                            `Type ${getCommandMentionByName("ticket/close")} to close this ticket.`
                         )
                         .setStatus("Success")
                         .setEmoji("GUILD.TICKET.OPEN")
@@ -157,12 +157,12 @@
             calculateType: "ticketUpdate",
             color: NucleusColors.green,
             emoji: "GUILD.TICKET.OPEN",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             ticketFor: entry(user.id, renderUser(user)),
             createdBy: entry(createdBy.id, renderUser(createdBy)),
-            created: entry((new Date().getTime()).toString(), renderDelta(new Date().getTime())),
+            created: entry((Date.now()).toString(), renderDelta(Date.now())),
             ticketChannel: entry(c.id, renderChannel(c))
         },
         hidden: {
diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts
index 7056fe6..be58d99 100644
--- a/src/actions/roleMenu.ts
+++ b/src/actions/roleMenu.ts
@@ -30,6 +30,36 @@
     interaction: CommandInteraction | ButtonInteraction | ContextMenuCommandInteraction;
 }
 
+interface ObjectSchema {
+    name: string;
+    description: string;
+    min: number;
+    max: number;
+    options: {
+        name: string;
+        description: string | null;
+        role: string;
+    }[];
+}
+
+export const configToDropdown = (placeholder: string, currentPageData: ObjectSchema, selectedRoles?: string[]): ActionRowBuilder<StringSelectMenuBuilder> => {
+    return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
+        new StringSelectMenuBuilder()
+            .setCustomId("roles")
+            .setPlaceholder(placeholder)
+            .setMinValues(currentPageData.min)
+            .setMaxValues(currentPageData.max)
+            .addOptions(currentPageData.options.map((option: {name: string; description: string | null; role: string;}) => {
+                const builder = new StringSelectMenuOptionBuilder()
+                    .setLabel(option.name)
+                    .setValue(option.role)
+                    .setDefault(selectedRoles ? selectedRoles.includes(option.role) : false);
+                if (option.description) builder.setDescription(option.description);
+                return builder;
+            }))
+    )
+}
+
 export async function callback(interaction: CommandInteraction | ButtonInteraction) {
     if (!interaction.member) return;
     if (!interaction.guild) return;
@@ -56,7 +86,7 @@
             ],
             ephemeral: true
         });
-    const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
+    const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
     if (config.roleMenu.allowWebUI) {  // TODO: Make rolemenu web ui
         const loginMethods: {webUI: boolean} = {
             webUI: false
@@ -75,7 +105,7 @@
                 const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
                 let valid = false;
                 while (!valid) {
-                    itt += 1;
+                    itt ++;
                     code = "";
                     for (let i = 0; i < length; i++) {
                         code += chars.charAt(Math.floor(Math.random() * chars.length));
@@ -83,7 +113,7 @@
                     if (code in client.roleMenu) continue;
                     if (itt > 1000) {
                         itt = 0;
-                        length += 1;
+                        length ++;
                         continue;
                     }
                     valid = true;
@@ -124,9 +154,10 @@
             try {
                 component = await m.awaitMessageComponent({
                     time: 300000,
-                    filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                    filter: (i) => { return i.user.id === interaction.user.id && i.channelId === interaction.channelId  && i.message.id === m.id}
                 });
             } catch (e) {
+                console.log(e);
                 return;
             }
             component.deferUpdate();
@@ -151,8 +182,7 @@
                 `**${currentPageData.name}**\n` +
                 `> ${currentPageData.description}\n\n` +
                 (currentPageData.min === currentPageData.max ? `Select ${addPlural(currentPageData.min, "role")}` :
-                    `Select between ${currentPageData.min} and ${currentPageData.max} roles` + (
-                        currentPageData.min === 0 ? ` or press next` : "")) + "\n\n" +
+                    `Select between ${currentPageData.min} and ${currentPageData.max} roles then press next`) + "\n\n" +
                 createPageIndicator(maxPage, page)
             )
             .setStatus("Success")
@@ -175,21 +205,7 @@
                     .setCustomId("done")
                     .setDisabled(!complete)
             ),
-            new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
-                new StringSelectMenuBuilder()
-                    .setCustomId("roles")
-                    .setPlaceholder("Select...")
-                    .setMinValues(currentPageData.min)
-                    .setMaxValues(currentPageData.max)
-                    .addOptions(currentPageData.options.map((option) => {
-                        const builder = new StringSelectMenuOptionBuilder()
-                            .setLabel(option.name)
-                            .setValue(option.role)
-                            .setDefault(selectedRoles[page]!.includes(option.role));
-                        if (option.description) builder.setDescription(option.description);
-                        return builder;
-                    }))
-            )
+            configToDropdown("Select...", currentPageData, selectedRoles[page])
         ];
         await interaction.editReply({
             embeds: [embed],
@@ -199,9 +215,10 @@
         try {
             component = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id}
             });
         } catch (e) {
+            console.log(e);
             return;
         }
         component.deferUpdate();
diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts
index 3c2dd2c..237790e 100644
--- a/src/actions/tickets/create.ts
+++ b/src/actions/tickets/create.ts
@@ -3,7 +3,7 @@
 import client from "../../utils/client.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
-import { getCommandMentionByName } from "../../utils/getCommandMentionByName.js";
+import { getCommandMentionByName } from "../../utils/getCommandDataByName.js";
 
 function capitalize(s: string) {
     s = s.replace(/([A-Z])/g, " $1");
@@ -106,7 +106,7 @@
         try {
             component = await m.awaitMessageComponent({
                 time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
+                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
             });
         } catch (e) {
             return;
@@ -225,7 +225,7 @@
                                     chosenType !== null ? emoji + " " + capitalize(chosenType) : "General"
                                 }\n` +
                                 `**Ticket ID:** \`${c.id}\`\n${content ?? ""}\n` +
-                                `Type ${await getCommandMentionByName("ticket/close")} to close this ticket.`
+                                `Type ${getCommandMentionByName("ticket/close")} to close this ticket.`
                         )
                         .setStatus("Success")
                         .setEmoji("GUILD.TICKET.OPEN")
@@ -257,7 +257,7 @@
                                                 type: Discord.ChannelType.PrivateThread,
                                                 reason: "Creating ticket"
                                                 }) as Discord.PrivateThreadChannel;
-        c.members.add(interaction.member!.user.id);  // TODO: When a thread is used, and a support role is added, automatically set channel permissions
+        c.members.add(interaction.member!.user.id);
         try {
             await c.send({
                 content:
@@ -289,7 +289,7 @@
                                     chosenType !== null ? emoji + " " + capitalize(chosenType) : "General"
                                 }\n` +
                                 `**Ticket ID:** \`${c.id}\`\n${content ?? ""}\n` +
-                                `Type ${await getCommandMentionByName("ticket/close")} to close this ticket.`
+                                `Type ${getCommandMentionByName("ticket/close")} to close this ticket.`
                         )
                         .setStatus("Success")
                         .setEmoji("GUILD.TICKET.OPEN")
@@ -323,11 +323,11 @@
             calculateType: "ticketUpdate",
             color: NucleusColors.green,
             emoji: "GUILD.TICKET.OPEN",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             ticketFor: entry(interaction.member!.user.id, renderUser(interaction.member!.user! as Discord.User)),
-            created: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+            created: entry(Date.now(), renderDelta(Date.now())),
             ticketChannel: entry(c.id, renderChannel(c))
         },
         hidden: {
diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts
index 3263580..990b360 100644
--- a/src/actions/tickets/delete.ts
+++ b/src/actions/tickets/delete.ts
@@ -1,15 +1,15 @@
-import { getCommandMentionByName } from '../../utils/getCommandMentionByName.js';
+import { getCommandMentionByName } from '../../utils/getCommandDataByName.js';
 import Discord, { ActionRowBuilder, ButtonBuilder, ButtonInteraction, PrivateThreadChannel, TextChannel, ButtonStyle, CategoryChannel } from "discord.js";
 import client from "../../utils/client.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import { preloadPage } from '../../utils/createTemporaryStorage.js';
+import { LoadingEmbed } from '../../utils/defaults.js';
 
 export default async function (interaction: Discord.CommandInteraction | ButtonInteraction) {
     if (!interaction.guild) return;
     const config = await client.database.guilds.read(interaction.guild.id);
     const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger;
-
     const ticketChannel = config.tickets.category;
     if (!("parent" in interaction.channel!)) {
         return await interaction.reply({
@@ -50,7 +50,7 @@
                 calculateType: "ticketUpdate",
                 color: NucleusColors.red,
                 emoji: "GUILD.TICKET.CLOSE",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 ticketFor: entry(
@@ -58,7 +58,7 @@
                     renderUser((await interaction.guild.members.fetch(uID!)).user)
                 ),
                 closedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
-                closed: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                closed: entry(Date.now(), renderDelta(Date.now())),
                 ticketChannel: entry(channel.id, channel.name)
             },
             hidden: {
@@ -69,8 +69,9 @@
 
         await channel.delete();
     } else if (status === "Active") {
-        // Close the ticket
-
+        await interaction.reply({embeds: LoadingEmbed, fetchReply: true});
+        // Archive the ticket
+        await interaction.channel.fetch()
         if (channel.isThread()) {
             channel.setName(`${channel.name.replace("Active", "Archived")}`);
             channel.members.remove(channel.name.split(" - ")[1]!);
@@ -80,14 +81,14 @@
             await channel.permissionOverwrites.delete(channel.topic!.split(" ")[0]!);
         }
         preloadPage(interaction.channel.id, "privacy", "2")
-        await interaction.reply({
+        const hasPremium = await client.database.premium.hasPremium(interaction.guild.id);
+        await interaction.editReply({
             embeds: [
                 new EmojiEmbed()
                     .setTitle("Archived Ticket")
-                    .setDescription(`This ticket has been Archived. Type ${await getCommandMentionByName("ticket/close")} to delete it.` +
-                        await client.database.premium.hasPremium(interaction.guild.id) ?
-                        `\n\nFor more info on transcripts, check ${await getCommandMentionByName("privacy")}` :
-                        "")
+                    .setDescription(`This ticket has been Archived. Type ${getCommandMentionByName("ticket/close")} to delete it.\n` +
+                        hasPremium ? ("Creating a transcript will delete all messages in this ticket" +
+                        `\n\nFor more info on transcripts, check ${getCommandMentionByName("privacy")}`): "")
                     .setStatus("Warning")
                     .setEmoji("GUILD.TICKET.ARCHIVED")
             ],
@@ -100,7 +101,7 @@
                             .setCustomId("closeticket")
                             .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
                     ].concat(
-                        await client.database.premium.hasPremium(interaction.guild.id)
+                        hasPremium
                             ? [
                                     new ButtonBuilder()
                                         .setLabel("Create Transcript and Delete")
@@ -120,7 +121,7 @@
                 calculateType: "ticketUpdate",
                 color: NucleusColors.yellow,
                 emoji: "GUILD.TICKET.ARCHIVED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 ticketFor: entry(
@@ -128,7 +129,7 @@
                     renderUser((await interaction.guild.members.fetch(uID!)).user)
                 ),
                 archivedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
-                archived: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                archived: entry(Date.now(), renderDelta(Date.now())),
                 ticketChannel: entry(channel.id, renderChannel(channel))
             },
             hidden: {
@@ -183,12 +184,12 @@
             calculateType: "ticketUpdate",
             color: NucleusColors.red,
             emoji: "GUILD.TICKET.DELETE",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             ticketFor: entry(member, renderUser(member)),
             deletedBy: entry(null, "Member left server"),
-            deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+            deleted: entry(Date.now(), renderDelta(Date.now())),
             ticketsDeleted: deleted
         },
         hidden: {
@@ -198,4 +199,4 @@
     log(data);
 }
 
-export { purgeByUser };
\ No newline at end of file
+export { purgeByUser };