worked on scanners, database, tracks, some moving around and cleaning up files.
diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts
index 8796892..9528183 100644
--- a/src/commands/settings/rolemenu.ts
+++ b/src/commands/settings/rolemenu.ts
@@ -8,6 +8,7 @@
 import createPageIndicator from "../../utils/createPageIndicator.js";
 import { configToDropdown } from "../../actions/roleMenu.js";
 import { modalInteractionCollector } from "../../utils/dualCollector.js";
+import ellipsis from "../../utils/ellipsis.js";
 import lodash from 'lodash';
 
 const isEqual = lodash.isEqual;
@@ -44,8 +45,9 @@
         .addComponents(
             new StringSelectMenuBuilder()
                 .setCustomId("reorder")
-                .setPlaceholder("Select a page to move...")
-                .setMinValues(1)
+                .setPlaceholder("Select all pages in the order you want them to appear.")
+                .setMinValues(currentObj.length)
+                .setMaxValues(currentObj.length)
                 .addOptions(
                     currentObj.map((o, i) => new StringSelectMenuOptionBuilder()
                         .setLabel(o.name)
@@ -81,6 +83,7 @@
         out = null;
     }
     if(!out) return;
+    out.deferUpdate();
     if (out.isButton()) return;
     if(!out.values) return;
     const values = out.values;
@@ -160,12 +163,7 @@
 
 }
 
-const ellipsis = (str: string, max: number): string => {
-    if (str.length <= max) return str;
-    return str.slice(0, max - 3) + "...";
-}
-
-const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise<ObjectSchema | null> => {
+const editRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise<ObjectSchema | null> => {
     if (!data) data = {
         name: "Role Menu Page",
         description: "A new role menu page",
@@ -321,7 +319,7 @@
     let modified = false;
     do {
         const embed = new EmojiEmbed()
-            .setTitle("Role Menu Settings")
+            .setTitle("Role Menu")
             .setEmoji("GUILD.GREEN")
             .setStatus("Success");
         const noRoleMenus = currentObject.length === 0;
@@ -377,7 +375,7 @@
                     .setDisabled(!modified),
             );
         if(noRoleMenus) {
-            embed.setDescription("No role menu page have been set up yet. Use the button below to add one.\n\n" +
+            embed.setDescription("No role menu pages have been set up yet. Use the button below to add one.\n\n" +
                 createPageIndicator(1, 1, undefined, true)
             );
             pageSelect.setDisabled(true);
@@ -390,7 +388,7 @@
             page = Math.min(page, Object.keys(currentObject).length - 1);
             current = currentObject[page]!;
             embed.setDescription(`**Currently Editing:** ${current.name}\n\n` +
-                `**Description:** \`${current.description}\`\n` +
+                `**Description:**\n> ${current.description}\n` +
                 `\n\n${createPageIndicator(Object.keys(config.roleMenu.options).length, page)}`
             );
 
@@ -424,7 +422,7 @@
                     page++;
                     break;
                 case "add":
-                    let newPage = await createRoleMenuPage(i, m)
+                    let newPage = await editRoleMenuPage(i, m)
                     if(!newPage) break;
                     currentObject.push();
                     page = currentObject.length - 1;
@@ -444,7 +442,7 @@
                 case "action":
                     switch(i.values[0]) {
                         case "edit":
-                            let edited = await createRoleMenuPage(i, m, current!);
+                            let edited = await editRoleMenuPage(i, m, current!);
                             if(!edited) break;
                             currentObject[page] = edited;
                             modified = true;
diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts
index 0cad55c..782f52f 100644
--- a/src/commands/settings/tracks.ts
+++ b/src/commands/settings/tracks.ts
@@ -1,16 +1,405 @@
-import type { CommandInteraction, GuildMember, SlashCommandSubcommandBuilder } from "discord.js";
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, GuildMember, Message, ModalBuilder, ModalSubmitInteraction, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
 import client from "../../utils/client.js";
-
+import createPageIndicator, { createVerticalTrack } from "../../utils/createPageIndicator.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import ellipsis from "../../utils/ellipsis.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("tracks")
         .setDescription("Manage the tracks for the server")
 
+interface ObjectSchema {
+    name: string;
+    retainPrevious: boolean;
+    nullable: boolean;
+    track: string[];
+    manageableBy: string[];
+}
+
+const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, current?: string) => {
+
+    let name = current ?? "";
+    const modal = new ModalBuilder()
+        .setTitle("Edit Name and Description")
+        .setCustomId("editNameDescription")
+        .addComponents(
+            new ActionRowBuilder<TextInputBuilder>()
+                .addComponents(
+                    new TextInputBuilder()
+                        .setLabel("Name")
+                        .setCustomId("name")
+                        .setPlaceholder("Name here...") // TODO: Make better placeholder
+                        .setStyle(TextInputStyle.Short)
+                        .setValue(name ?? "")
+                        .setRequired(true)
+                )
+        )
+    const button = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+        )
+
+    await i.showModal(modal)
+    await interaction.editReply({
+        embeds: [
+            new EmojiEmbed()
+                .setTitle("Tracks")
+                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                .setStatus("Success")
+        ],
+        components: [button]
+    });
+
+    let out: ModalSubmitInteraction | null;
+    try {
+        out = await modalInteractionCollector(
+            m,
+            (m) => m.channel!.id === interaction.channel!.id,
+            (_) => true
+        ) as ModalSubmitInteraction | null;
+    } catch (e) {
+        console.error(e);
+        out = null;
+    }
+    if(!out) return name;
+    if (out.isButton()) return name;
+    if(!out.fields) return name;
+    name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
+    return name
+
+}
+
+const reorderTracks = async (interaction: ButtonInteraction, m: Message, roles: Collection<string, Role>, currentObj: string[]) => {
+    let reorderRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+        .addComponents(
+            new StringSelectMenuBuilder()
+                .setCustomId("reorder")
+                .setPlaceholder("Select all roles in the order you want users to gain them (Lowest to highest rank).")
+                .setMinValues(currentObj.length)
+                .setMaxValues(currentObj.length)
+                .addOptions(
+                    currentObj.map((o, i) => new StringSelectMenuOptionBuilder()
+                        .setLabel(roles.get(o)!.name)
+                        .setValue(i.toString())
+                    )
+                )
+        );
+    let buttonRow = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+        )
+    await interaction.editReply({
+        embeds: [
+            new EmojiEmbed()
+                .setTitle("Tracks")
+                .setDescription("Select all roles in the order you want users to gain them (Lowest to highest rank).")
+                .setStatus("Success")
+        ],
+        components: [reorderRow, buttonRow]
+    });
+    let out: StringSelectMenuInteraction | ButtonInteraction | null;
+    try {
+        out = await m.awaitMessageComponent({
+            filter: (i) => i.channel!.id === interaction.channel!.id,
+            time: 300000
+        }) as StringSelectMenuInteraction | ButtonInteraction | null;
+    } catch (e) {
+        console.error(e);
+        out = null;
+    }
+    if(!out) return;
+    out.deferUpdate();
+    if (out.isButton()) return;
+    if(!out.values) return;
+    const values = out.values;
+
+    const newOrder: string[] = currentObj.map((_, i) => {
+        const index = values.findIndex(v => v === i.toString());
+        return currentObj[index];
+    }) as string[];
+
+    return newOrder;
+}
+
+const editTrack = async (interaction: ButtonInteraction | StringSelectMenuInteraction, message: Message, roles: Collection<string, Role>, current?: ObjectSchema) => {
+    if(!current) {
+        current = {
+            name: "",
+            retainPrevious: false,
+            nullable: false,
+            track: [],
+            manageableBy: []
+        }
+    }
+    const buttons = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("back")
+                .setLabel("Back")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+            new ButtonBuilder()
+                .setCustomId("edit")
+                .setLabel("Edit Name")
+                .setStyle(ButtonStyle.Primary)
+                .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+            new ButtonBuilder()
+                .setCustomId("reorder")
+                .setLabel("Reorder")
+                .setStyle(ButtonStyle.Primary)
+                .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji),
+        );
+    const roleSelect = new ActionRowBuilder<RoleSelectMenuBuilder>()
+        .addComponents(
+            new RoleSelectMenuBuilder()
+                .setCustomId("addRole")
+                .setPlaceholder("Select a role to add")
+        );
+    let closed = false;
+    do {
+        const editableRoles: string[] = current.track.map((r) => {
+            if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position)) return r;
+        }).filter(v => v !== undefined) as string[];
+        const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("removeRole")
+                    .setPlaceholder("Select a role to remove")
+                    .addOptions(
+                        editableRoles.map((r, i) => {
+                            return new StringSelectMenuOptionBuilder()
+                            .setLabel(r)
+                            .setValue(i.toString())}
+                        )
+                    )
+            );
+        let allowed: boolean[] = [];
+        for (const role of current.track) {
+            const disabled: boolean =
+                roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position;
+            allowed.push(disabled)
+        }
+
+        const embed = new EmojiEmbed()
+            .setTitle("Tracks")
+            .setDescription(
+                `**Currently Editing:** ${current.name}\n\n` +
+                `${getEmojiByName} Members ${current.nullable ? "don't " : ""}need a role in this track` +
+                `${getEmojiByName} Members ${current.retainPrevious ? "don't " : ""}keep all roles below their current highest` +
+                createVerticalTrack(current.track, new Array(current.track.length).fill(false), allowed)
+            )
+
+        interaction.editReply({embeds: [embed], components: [buttons, roleSelect, selectMenu]});
+
+        let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
+
+        try {
+            out = await message.awaitMessageComponent({
+                filter: (i) => i.channel!.id === interaction.channel!.id,
+                time: 300000
+            }) as ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
+        } catch (e) {
+            console.error(e);
+            out = null;
+        }
+
+        if(!out) return;
+        if (out.isButton()) {
+            out.deferUpdate();
+            switch(out.customId) {
+                case "back":
+                    closed = true;
+                    break;
+                case "edit":
+                    current.name = (await editName(out, interaction, message, current.name))!;
+                    break;
+                case "reorder":
+                    current.track = (await reorderTracks(out, message, roles, current.track))!;
+            }
+        } else if (out.isStringSelectMenu()) {
+            out.deferUpdate();
+            switch(out.customId) {
+                case "removeRole":
+                    const index = current.track.findIndex(v => v === editableRoles[parseInt((out! as StringSelectMenuInteraction).values![0]!)]);
+                    current.track.splice(index, 1);
+                    break;
+            }
+        } else {
+            switch(out.customId) {
+                case "addRole":
+                    const role = out.values![0]!;
+                    if(!current.track.includes(role)) {
+                        current.track.push(role);
+                    }
+                    out.reply({content: "That role is already on this track", ephemeral: true})
+                    break;
+            }
+        }
+
+    } while(!closed);
+    return current;
+}
 
 const callback = async (interaction: CommandInteraction) => {
 
-    
+    const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true})
+    const config = await client.database.guilds.read(interaction.guild!.id);
+    const tracks: ObjectSchema[] = config.tracks;
+    const roles = await interaction.guild!.roles.fetch();
+    const memberRoles = interaction.member!.roles;
+    const member = interaction.member as GuildMember;
+
+    let page = 0;
+    let closed = false;
+    let modified = false;
+
+    do {
+        const embed = new EmojiEmbed()
+            .setTitle("Track Settings")
+            .setEmoji("TRACKS.ICON")
+            .setStatus("Success");
+        const noTracks = config.tracks.length === 0;
+        let current: ObjectSchema;
+
+        const pageSelect = new StringSelectMenuBuilder()
+            .setCustomId("page")
+            .setPlaceholder("Select a track to manage");
+        const actionSelect = new StringSelectMenuBuilder()
+            .setCustomId("action")
+            .setPlaceholder("Perform an action")
+            .addOptions(
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Edit")
+                    .setDescription("Edit this track")
+                    .setValue("edit")
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Delete")
+                    .setDescription("Delete this track")
+                    .setValue("delete")
+                    .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
+        );
+        const buttonRow = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+                    .setDisabled(page === 0),
+                new ButtonBuilder()
+                    .setCustomId("next")
+                    .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Primary)
+                    .setDisabled(page === Object.keys(tracks).length - 1),
+                new ButtonBuilder()
+                    .setCustomId("add")
+                    .setLabel("New Track")
+                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(Object.keys(tracks).length >= 24),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(!modified),
+            );
+        if(noTracks) {
+            embed.setDescription("No tracks have been set up yet. Use the button below to add one.\n\n" +
+                createPageIndicator(1, 1, undefined, true)
+            );
+            pageSelect.setDisabled(true);
+            actionSelect.setDisabled(true);
+            pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+                .setLabel("No tracks")
+                .setValue("none")
+            );
+        } else {
+            page = Math.min(page, Object.keys(tracks).length - 1);
+            current = tracks[page]!;
+            embed.setDescription(`**Currently Editing:** ${current.name}\n\n` +
+                `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${current.nullable ? "don't " : ""}need a role in this track\n` +
+                `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${current.retainPrevious ? "" : "don't "}keep all roles below their current highest\n\n` +
+                createVerticalTrack(current.track, new Array(current.track.length).fill(false)) +
+                `\n${createPageIndicator(config.tracks.length, page)}`
+            );
+
+            pageSelect.addOptions(
+                tracks.map((key: ObjectSchema, index) => {
+                    return new StringSelectMenuOptionBuilder()
+                        .setLabel(ellipsis(key.name, 50))
+                        .setDescription(ellipsis(roles.get(key.track[0]!)?.name!, 50))
+                        .setValue(index.toString());
+                })
+            );
+
+        }
+
+        await interaction.editReply({embeds: [embed], components: [new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect), new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect), buttonRow]});
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+
+        await i.deferUpdate();
+        if (i.isButton()) {
+            switch (i.customId) {
+                case "back":
+                    page--;
+                    break;
+                case "next":
+                    page++;
+                    break;
+                case "add":
+                    let newPage = await editTrack(i, m, roles)
+                    if(!newPage) break;
+                    tracks.push();
+                    page = tracks.length - 1;
+                    break;
+                case "save":
+                    // client.database.guilds.write(interaction.guild!.id, {"roleMenu.options": tracks});  // TODO
+                    modified = false;
+                    break;
+            }
+        } else if (i.isStringSelectMenu()) {
+            switch (i.customId) {
+                case "action":
+                    switch(i.values[0]) {
+                        case "edit":
+                            let edited = await editTrack(i, m, roles, current!);
+                            if(!edited) break;
+                            tracks[page] = edited;
+                            modified = true;
+                            break;
+                        case "delete":
+                            if(page === 0 && tracks.keys.length - 1 > 0) page++;
+                            else page--;
+                            tracks.splice(page, 1);
+                            break;
+                    }
+                    break;
+                case "page":
+                    page = parseInt(i.values[0]!);
+                    break;
+            }
+        }
+
+    } while (!closed)
 
 }
 
diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts
index 25a784b..c7f441f 100644
--- a/src/commands/user/track.ts
+++ b/src/commands/user/track.ts
@@ -1,10 +1,11 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
-import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, SelectMenuOptionBuilder, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction } from "discord.js";
+import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction, StringSelectMenuOptionBuilder } from "discord.js";
 import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import addPlural from "../../utils/plurals.js";
 import client from "../../utils/client.js";
+import { createVerticalTrack } from "../../utils/createPageIndicator.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -12,17 +13,8 @@
         .setDescription("Moves a user along a role track")
         .addUserOption((option) => option.setName("user").setDescription("The user to manage").setRequired(true));
 
-const generateFromTrack = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
-    active = active ? "ACTIVE" : "INACTIVE";
-    disabled = disabled ? "GREY." : "";
-    if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
-    if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
-    if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
-    return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
-};
-
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const { renderUser } = client.logger;
+    const { renderUser, renderRole} = client.logger;
     const member = interaction.options.getMember("user") as GuildMember;
     const guild = interaction.guild;
     if (!guild) return;
@@ -44,10 +36,10 @@
         const dropdown = new Discord.StringSelectMenuBuilder()
             .addOptions(
                 config.tracks.map((option, index) => {
-                    const hasRoleInTrack = option.track.some((element: string) => {
+                    const hasRoleInTrack: boolean = option.track.some((element: string) => {
                         return memberRoles.cache.has(element);
                     });
-                    return new SelectMenuOptionBuilder({
+                    return new StringSelectMenuOptionBuilder({
                         default: index === track,
                         label: option.name,
                         value: index.toString(),
@@ -68,33 +60,23 @@
             (data.retainPrevious
                 ? "When promoted, the user keeps previous roles"
                 : "Members will lose their current role when promoted") + "\n";
-        generated +=
-            "\n" +
-            data.track
-                .map((role, index) => {
-                    const allow: boolean =
-                        roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position &&
-                        !managed;
-                    allowed.push(!allow);
-                    return (
-                        getEmojiByName(
-                            generateFromTrack(index, memberRoles.cache.has(role), data.track.length, allow)
-                        ) +
-                        " " +
-                        roles.get(role)!.name +
-                        " [<@&" +
-                        roles.get(role)!.id +
-                        ">]"
-                    );
-                })
-                .join("\n");
+        for (const role of data.track) {
+            const disabled: boolean =
+                roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position && !managed;
+            allowed.push(!disabled)
+        }
+        generated += "\n" + createVerticalTrack(
+            data.track.map((role) => renderRole(roles.get(role)!)),
+            data.track.map((role) => memberRoles.cache.has(role)),
+            allowed.map((allow) => !allow)
+        );
         const selected = [];
         for (const position of data.track) {
             if (memberRoles.cache.has(position)) selected.push(position);
         }
         const conflict = data.retainPrevious ? false : selected.length > 1;
         let conflictDropdown: StringSelectMenuBuilder[] = [];
-        const conflictDropdownOptions: SelectMenuOptionBuilder[] = [];
+        const conflictDropdownOptions: StringSelectMenuOptionBuilder[] = [];
         let currentRoleIndex: number = -1;
         if (conflict) {
             generated += `\n\n${getEmojiByName(`PUNISH.WARN.${managed ? "YELLOW" : "RED"}`)} This user has ${
@@ -106,10 +88,9 @@
                     "In order to promote or demote this user, you must select which role the member should keep.";
                 selected.forEach((role) => {
                     conflictDropdownOptions.push(
-                        new SelectMenuOptionBuilder({
-                            label: roles.get(role)!.name,
-                            value: roles.get(role)!.id
-                        })
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel(roles.get(role)!.name)
+                            .setValue(roles.get(role)!.id)
                     );
                 });
                 conflictDropdown = [
diff --git a/src/config/emojis.json b/src/config/emojis.json
index 1e62149..abeb52a 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -25,7 +25,7 @@
         "ATTACHMENT": "997570687193587812",
         "LOGGING": "999613304446144562",
         "SAVE": "1065722246322200586",
-        "SHUFFLE": "1067913930304921690",
+        "SHUFFLE": "1069323453909454890",
         "NOTIFY": {
             "ON": "1000726394579464232",
             "OFF": "1000726363495477368"
diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts
index f69156a..c8d59f0 100644
--- a/src/reflex/scanners.ts
+++ b/src/reflex/scanners.ts
@@ -1,10 +1,11 @@
 import fetch from "node-fetch";
 import FormData from "form-data";
-import { writeFileSync, createReadStream } from "fs";
+import fs, { writeFileSync, createReadStream } from "fs";
 import generateFileName from "../utils/temp/generateFileName.js";
 import Tesseract from "node-tesseract-ocr";
 import type Discord from "discord.js";
 import client from "../utils/client.js";
+import { createHash } from "crypto";
 
 interface NSFWSchema {
     nsfw: boolean;
@@ -14,7 +15,11 @@
 }
 
 export async function testNSFW(link: string): Promise<NSFWSchema> {
-    const p = await saveAttachment(link);
+    const [p, hash] = await saveAttachment(link);
+    console.log("Checking an image")
+    let alreadyHaveCheck = await client.database.scanCache.read(hash)
+    if(alreadyHaveCheck) return { nsfw: alreadyHaveCheck.data };
+    console.log("Was not in db")
     const data = new FormData();
     console.log(link);
     data.append("file", createReadStream(p));
@@ -32,13 +37,17 @@
             return { nsfw: false };
         });
     console.log(result);
+    client.database.scanCache.write(hash, result.nsfw);
     return { nsfw: result.nsfw };
 }
 
 export async function testMalware(link: string): Promise<MalwareSchema> {
-    const p = await saveAttachment(link);
-    const data = new FormData();
-    data.append("file", createReadStream(p));
+    const [p, hash] = await saveAttachment(link);
+    let alreadyHaveCheck = await client.database.scanCache.read(hash)
+    if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data };
+    const data = new URLSearchParams();
+    let f = createReadStream(p);
+    data.append("file", f.read(fs.statSync(p).size));
     console.log(link);
     const result = await fetch("https://unscan.p.rapidapi.com/malware", {
         method: "POST",
@@ -54,12 +63,15 @@
             return { safe: true };
         });
     console.log(result);
+    client.database.scanCache.write(hash, result.safe);
     return { safe: result.safe };
 }
 
 export async function testLink(link: string): Promise<{ safe: boolean; tags: string[] }> {
     console.log(link);
-    const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/malware", {
+    let alreadyHaveCheck = await client.database.scanCache.read(link)
+    if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data, tags: [] };
+    const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/link", {
         method: "POST",
         headers: {
             "X-RapidAPI-Key": client.config.rapidApiKey,
@@ -73,17 +85,18 @@
             return { safe: true, tags: [] };
         });
     console.log(scanned);
+    client.database.scanCache.write(link, scanned.safe ?? true, []);
     return {
         safe: scanned.safe ?? true,
         tags: scanned.tags ?? []
     };
 }
 
-export async function saveAttachment(link: string): Promise<string> {
+export async function saveAttachment(link: string): Promise<[string, string]> {
     const image = (await fetch(link)).arrayBuffer().toString();
     const fileName = generateFileName(link.split("/").pop()!.split(".").pop()!);
     writeFileSync(fileName, image, "base64");
-    return fileName;
+    return [fileName, createHash('sha512').update(image, 'base64').digest('base64')];
 }
 
 const linkTypes = {
@@ -139,8 +152,7 @@
 
 export async function NSFWCheck(element: string): Promise<boolean> {
     try {
-        const test = await testNSFW(element);
-        return test.nsfw;
+        return (await testNSFW(element)).nsfw;
     } catch {
         return false;
     }
diff --git a/src/utils/client.ts b/src/utils/client.ts
index 2a0702a..41cdbca 100644
--- a/src/utils/client.ts
+++ b/src/utils/client.ts
@@ -2,7 +2,7 @@
 import { Logger } from "../utils/log.js";
 import Memory from "../utils/memory.js";
 import type { VerifySchema } from "../reflex/verify.js";
-import { Guilds, History, ModNotes, Premium, PerformanceTest } from "../utils/database.js";
+import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache } 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" };
@@ -22,6 +22,7 @@
         premium: Premium;
         eventScheduler: EventScheduler;
         performanceTest: PerformanceTest;
+        scanCache: ScanCache;
     };
     preloadPage: Record<string, {command: string, argument: string}> = {};  // e.g. { channelID: { command: privacy, page: 3}}
     commands: Record<string, [{
@@ -51,7 +52,8 @@
     notes: new ModNotes(),
     premium: new Premium(),
     eventScheduler: new EventScheduler(),
-    performanceTest: new PerformanceTest()
+    performanceTest: new PerformanceTest(),
+    scanCache: new ScanCache()
 });
 
 export default client;
diff --git a/src/utils/createPageIndicator.ts b/src/utils/createPageIndicator.ts
index 4ddbae2..29ea83b 100644
--- a/src/utils/createPageIndicator.ts
+++ b/src/utils/createPageIndicator.ts
@@ -21,4 +21,23 @@
     return out;
 }
 
+export const verticalTrackIndicator = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
+    active = active ? "ACTIVE" : "INACTIVE";
+    disabled = disabled ? "GREY." : "";
+    if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
+    if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
+    if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
+    return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
+};
+
+export const createVerticalTrack = (items: string[], active: boolean[], disabled?: boolean[]) => {
+    let out = "";
+    if (!disabled) disabled = new Array(items.length).fill(false);
+    for (let i = 0; i < items.length; i++) {
+        out += getEmojiByName(verticalTrackIndicator(i, active[i] ?? false, items.length, disabled[i] ?? false));
+        out += items[i] + "\n";
+    }
+    return out;
+}
+
 export default pageIndicator;
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 10b0ddb..c7b1777 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -148,6 +148,33 @@
     }
 }
 
+interface ScanCacheSchema {
+    addedAt: Date;
+    hash: string;
+    data: boolean;
+    tags: string[];
+}
+
+export class ScanCache {
+    scanCache: Collection<ScanCacheSchema>;
+
+    constructor() {
+        this.scanCache = database.collection<ScanCacheSchema>("scanCache");
+    }
+
+    async read(hash: string) {
+        return await this.scanCache.findOne({ hash: hash });
+    }
+
+    async write(hash: string, data: boolean, tags?: string[]) {
+        await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() });  // TODO: cleanup function maybe
+    }
+
+    async cleanup() {
+        await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
+    }
+}
+
 export class PerformanceTest {
     performanceData: Collection<PerformanceDataSchema>;
 
diff --git a/src/utils/ellipsis.ts b/src/utils/ellipsis.ts
new file mode 100644
index 0000000..6ec5888
--- /dev/null
+++ b/src/utils/ellipsis.ts
@@ -0,0 +1,4 @@
+export default (str: string, max: number): string => {
+    if (str.length <= max) return str;
+    return str.slice(0, max - 3) + "...";
+}
\ No newline at end of file
diff --git a/src/utils/performanceTesting/record.ts b/src/utils/performanceTesting/record.ts
index 95761e9..17cfb1e 100644
--- a/src/utils/performanceTesting/record.ts
+++ b/src/utils/performanceTesting/record.ts
@@ -39,7 +39,7 @@
         singleNotify(
             "performanceTest",
             config.developmentGuildID,
-            `Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${results.resources.memory}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``,
+            `Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${Math.round(results.resources.memory)}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``,
             "Critical",
             config.owners
         )