Merge remote-tracking branch 'refs/remotes/origin/development' into development
diff --git a/package-lock.json b/package-lock.json
index 61e0f8f..0615b7e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,8 @@
         "humanize-duration": "^3.27.1",
         "jshaiku": "file:../haiku",
         "json-diff": "^0.7.1",
+        "node-tesseract": "^0.2.7",
+        "structured-clone": "^0.2.2",
         "tesseract.js": "^2.1.5",
         "typescript": "^4.5.5",
         "unscan": "^1.1.2"
@@ -904,6 +906,39 @@
         }
       }
     },
+    "node_modules/node-tesseract": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/node-tesseract/-/node-tesseract-0.2.7.tgz",
+      "integrity": "sha1-yPAvuDUaQnByc1d4wFGYI/JgG4Q=",
+      "dependencies": {
+        "glob": "^5.0.10",
+        "node-uuid": "^1.4.1"
+      }
+    },
+    "node_modules/node-tesseract/node_modules/glob": {
+      "version": "5.0.15",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+      "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+      "dependencies": {
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "2 || 3",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/node-uuid": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
+      "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=",
+      "deprecated": "Use uuid module instead",
+      "bin": {
+        "uuid": "bin/uuid"
+      }
+    },
     "node_modules/object-inspect": {
       "version": "1.12.0",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
@@ -1116,6 +1151,11 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/structured-clone": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/structured-clone/-/structured-clone-0.2.2.tgz",
+      "integrity": "sha1-rJK2vjGVimQ9sw8TNavGobAt/cI="
+    },
     "node_modules/tesseract.js": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-2.1.5.tgz",
@@ -2004,6 +2044,34 @@
         "whatwg-url": "^5.0.0"
       }
     },
+    "node-tesseract": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/node-tesseract/-/node-tesseract-0.2.7.tgz",
+      "integrity": "sha1-yPAvuDUaQnByc1d4wFGYI/JgG4Q=",
+      "requires": {
+        "glob": "^5.0.10",
+        "node-uuid": "^1.4.1"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "5.0.15",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+          "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+          "requires": {
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "2 || 3",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        }
+      }
+    },
+    "node-uuid": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
+      "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
+    },
     "object-inspect": {
       "version": "1.12.0",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
@@ -2161,6 +2229,11 @@
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
       "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
     },
+    "structured-clone": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/structured-clone/-/structured-clone-0.2.2.tgz",
+      "integrity": "sha1-rJK2vjGVimQ9sw8TNavGobAt/cI="
+    },
     "tesseract.js": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-2.1.5.tgz",
diff --git a/package.json b/package.json
index dd55984..7287aef 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,8 @@
     "humanize-duration": "^3.27.1",
     "jshaiku": "file:../haiku",
     "json-diff": "^0.7.1",
+    "node-tesseract": "^0.2.7",
+    "structured-clone": "^0.2.2",
     "tesseract.js": "^2.1.5",
     "typescript": "^4.5.5",
     "unscan": "^1.1.2"
@@ -35,6 +37,6 @@
     "url": "https://github.com/ClicksMinutePer/Nucleus/issues"
   },
   "homepage": "https://github.com/ClicksMinutePer/Nucleus#readme",
-  "private": true,
+  "private": false,
   "type": "module"
 }
diff --git a/src/automations/guide.ts b/src/automations/guide.ts
index e3cfa25..19a3337 100644
--- a/src/automations/guide.ts
+++ b/src/automations/guide.ts
@@ -1,6 +1,7 @@
 import Discord, { MessageActionRow, MessageButton } from "discord.js";
 import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
+import createPageIndicator from "../utils/createPageIndicator.js";
 
 export default async (guild, interaction?) => {
     let c = guild.publicUpdatesChannel ? guild.publicUpdatesChannel : guild.systemChannel;
@@ -39,7 +40,7 @@
                 `**${getEmojiByName("PUNISH.KICK.RED")} Kick:** The user is removed from the server.\n` +
                 `**${getEmojiByName("PUNISH.SOFTBAN")} Softban:** Kicks the user, deleting their messages from every channel.\n` +
                 `**${getEmojiByName("PUNISH.BAN.RED")} Ban:** The user is removed from the server, and they are unable to rejoin.\n` +
-                `**${getEmojiByName("PUNISH.BAN.GREEN")} Unban:** The user is able to rejoin the server.\n`
+                `**${getEmojiByName("PUNISH.BAN.GREEN")} Unban:** The user is able to rejoin the server.`
             )
             .setEmoji("NUCLEUS.LOGO")
             .setStatus("Danger"),
@@ -99,8 +100,10 @@
 
     while (true) {
         if (interaction) {
+            let em = new Discord.MessageEmbed(pages[page])
+            em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
             await interaction.editReply({
-                embeds: [pages[page].setFooter({text: `Page ${page + 1}/${pages.length}`})],
+                embeds: [em],
                 components: [new MessageActionRow().addComponents([
                     new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(page === 0),
                     new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(page === pages.length - 1),
@@ -130,7 +133,13 @@
             if (page < pages.length - 1) page++;
         } else if (i.component.customId == "close") {
             if (interaction) {
-                interaction.delete();
+                let em = new Discord.MessageEmbed(pages[page])
+                em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message closed");
+                interaction.editReply({embeds: [em], components: [new MessageActionRow().addComponents([
+                    new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(true),
+                    new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(true),
+                    new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER").setDisabled(true)
+                ])], fetchReply: true});
             } else {
                 m.delete();
             }
@@ -141,8 +150,10 @@
         }
     }
     if (interaction) {
+        let em = new Discord.MessageEmbed(pages[page])
+        em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message timed out");
         await interaction.editReply({
-            embeds: [pages[page].setFooter({text: `Page ${page + 1}/${pages.length} | Message timed out`})],
+            embeds: [em],
             components: [new MessageActionRow().addComponents([
                 new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(true),
                 new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(true),
@@ -150,8 +161,10 @@
             ])]
         });
     } else {
+        let em = new Discord.MessageEmbed(pages[page])
+        em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message timed out");
         await m.edit({
-            embeds: [pages[page].setFooter({text: `Page ${page + 1}/${pages.length} | Message timed out`})],
+            embeds: [em],
             components: [new MessageActionRow().addComponents([
                 new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(true),
                 new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(true),
diff --git a/src/automations/unscan.ts b/src/automations/unscan.ts
index b5c5ffb..d3a2a18 100644
--- a/src/automations/unscan.ts
+++ b/src/automations/unscan.ts
@@ -1,5 +1,5 @@
 import * as scan from '../utils/scanners.js'
-import Tesseract from 'tesseract.js';
+import process from 'tesseract.js'
 
 export async function LinkCheck(message): Promise<boolean> {
     let links = message.content.match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi) ?? []
@@ -70,4 +70,4 @@
 
 export async function TestImage(element): Promise<string> {
     return "";
-}
\ No newline at end of file
+}
diff --git a/src/automations/verify.ts b/src/automations/verify.ts
index a60ed07..3d4e658 100644
--- a/src/automations/verify.ts
+++ b/src/automations/verify.ts
@@ -3,12 +3,18 @@
 import readConfig from "../utils/readConfig.js";
 import fetch from "node-fetch";
 import { TestString, NSFWCheck } from "../automations/unscan.js";
+import createPageIndicator from "../utils/createPageIndicator.js";
+
+function step(i) {
+    return "\n\n" + createPageIndicator(5, i);
+}
 
 export default async function(interaction) {
     // @ts-ignore
     let verify = interaction.client.verify
     await interaction.reply({embeds: [new generateEmojiEmbed()
         .setTitle("Loading")
+        .setDescription(step(-1))
         .setStatus("Danger")
         .setEmoji("NUCLEUS.LOADING")
     ], ephemeral: true, fetchReply: true});
@@ -16,23 +22,23 @@
     if ((interaction.member as GuildMember).roles.cache.has(config.verify.role)) {
         return await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setTitle("Verify")
-            .setDescription(`You already have the <@&${config.verify.role}> role`)
+            .setDescription(`You already have the <@&${config.verify.role}> role` + step(0))
             .setStatus("Danger")
             .setEmoji("CONTROL.BLOCKCROSS")
         ]});
     }
     await interaction.editReply({embeds: [new generateEmojiEmbed()
         .setTitle("Verify")
-        .setDescription(`Checking our servers are up`)
+        .setDescription(`Checking our servers are up` + step(0))
         .setStatus("Warning")
         .setEmoji("NUCLEUS.LOADING")
     ]});
     try {
-        let status = await fetch(`https://clicks.codes`).then(res => res.status);
+        let status = await fetch(`https://clicks.codes/`).then(res => res.status);
         if (status != 200) {
             return await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setTitle("Verify")
-                .setDescription(`Our servers appear to be down, please try again later`)
+                .setDescription(`Our servers appear to be down, please try again later` + step(0))
                 .setStatus("Danger")
                 .setEmoji("CONTROL.BLOCKCROSS")
             ]});
@@ -40,7 +46,7 @@
     } catch {
         return await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setTitle("Verify")
-            .setDescription(`Our servers appear to be down, please try again later`)
+            .setDescription(`Our servers appear to be down, please try again later` + step(0))
             .setStatus("Danger")
             .setEmoji("CONTROL.BLOCKCROSS")
         ], components: [new Discord.MessageActionRow().addComponents([
@@ -57,14 +63,14 @@
     if (config.filters.images.NSFW) {
         await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setTitle("Verify")
-            .setDescription(`Checking your avatar is safe for work`)
+            .setDescription(`Checking your avatar is safe for work` + step(1))
             .setStatus("Warning")
             .setEmoji("NUCLEUS.LOADING")
         ]});
         if (await NSFWCheck((interaction.member as GuildMember).user.avatarURL({format: "png"}))) {
             return await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setTitle("Verify")
-                .setDescription(`Your avatar was detected as NSFW, which we do not allow in this server.\nPlease contact one of our staff members if you believe this is a mistake`)
+                .setDescription(`Your avatar was detected as NSFW, which we do not allow in this server.\nPlease contact one of our staff members if you believe this is a mistake` + step(1))
                 .setStatus("Danger")
                 .setEmoji("CONTROL.BLOCKCROSS")
             ]});
@@ -73,14 +79,14 @@
     if (config.filters.wordFilter) {
         await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setTitle("Verify")
-            .setDescription(`Checking your name is allowed`)
+            .setDescription(`Checking your name is allowed` + step(2))
             .setStatus("Warning")
             .setEmoji("NUCLEUS.LOADING")
         ]});
         if (TestString((interaction.member as Discord.GuildMember).displayName, config.filters.wordFilter.words.loose, config.filters.wordFilter.words.strict) != "none") {
             return await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setTitle("Verify")
-                .setDescription(`Your name contained a word we do not allow in this server.\nPlease contact one of our staff members if you believe this is a mistake`)
+                .setDescription(`Your name contained a word we do not allow in this server.\nPlease contact one of our staff members if you believe this is a mistake` + step(2))
                 .setStatus("Danger")
                 .setEmoji("CONTROL.BLOCKCROSS")
             ]});
@@ -88,7 +94,7 @@
     }
     await interaction.editReply({embeds: [new generateEmojiEmbed()
         .setTitle("Verify")
-        .setDescription(`One moment...`)
+        .setDescription(`One moment...` + step(3))
         .setStatus("Warning")
         .setEmoji("NUCLEUS.LOADING")
     ]});
@@ -120,7 +126,7 @@
     }
     await interaction.editReply({embeds: [new generateEmojiEmbed()
         .setTitle("Verify")
-        .setDescription(`Looking good!\nClick the button below to get verified`)
+        .setDescription(`Looking good!\nClick the button below to get verified` + step(4))
         .setStatus("Success")
         .setEmoji("MEMBER.JOIN")
     ], components: [new Discord.MessageActionRow().addComponents([new Discord.MessageButton()
diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts
index b6e2aee..788054e 100644
--- a/src/commands/user/about.ts
+++ b/src/commands/user/about.ts
@@ -4,6 +4,7 @@
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import generateKeyValueList from "../../utils/generateKeyValueList.js";
+import createPageIndicator from "../../utils/createPageIndicator.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -120,7 +121,7 @@
                     "id": `\`${member.id}\``,
                     "roles": `${member.roles.cache.size - 1}`,
                 }) + "\n" +
-                (s.length > 0 ? s : "*None*")
+                (s.length > 0 ? s : "*None*") + "\n"
             )
             .setThumbnail(await member.user.displayAvatarURL({dynamic: true})),
         new generateEmojiEmbed()
@@ -139,8 +140,10 @@
     m = await interaction.reply({embeds: [new generateEmojiEmbed().setTitle("Loading").setEmoji("NUCLEUS.LOADING").setStatus("Danger")], fetchReply: true, ephemeral: true});
     let page = 0
     while (true) {
+        let em = new Discord.MessageEmbed(embeds[page])
+        em.setDescription(em.description + "\n" + createPageIndicator(embeds.length, page));
         await interaction.editReply({
-            embeds: [embeds[page].setFooter({text: `Page ${page + 1} of ${embeds.length}`})],
+            embeds: [em],
             components: [new MessageActionRow().addComponents([
                 new MessageButton()
                     .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
diff --git a/src/events/commandError.ts b/src/events/commandError.ts
new file mode 100644
index 0000000..ba47426
--- /dev/null
+++ b/src/events/commandError.ts
@@ -0,0 +1,21 @@
+import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
+
+export const event = 'commandError'
+
+export async function callback(client, interaction, error) {
+    if (interaction.replied || interaction.deferred) {
+        await interaction.followUp({embeds: [new generateEmojiEmbed()
+            .setTitle("Something went wrong")
+            .setDescription(error.message ?? error.toString())
+            .setStatus("Danger")
+            .setEmoji("CONTROL.BLOCKCROSS")
+        ], ephemeral: true})
+    } else {
+        await interaction.reply({embeds: [new generateEmojiEmbed()
+            .setTitle("Something went wrong")
+            .setDescription(error.message ?? error.toString())
+            .setStatus("Danger")
+            .setEmoji("CONTROL.BLOCKCROSS")
+        ], ephemeral: true})
+    }
+}
diff --git a/src/utils/createPageIndicator.ts b/src/utils/createPageIndicator.ts
new file mode 100644
index 0000000..8e18e30
--- /dev/null
+++ b/src/utils/createPageIndicator.ts
@@ -0,0 +1,22 @@
+import getEmojiByName from "./getEmojiByName.js";
+
+function pageIndicator(amount: number, selected: number, showDetails?: boolean | true) {
+    let out = "";
+
+    if (amount == 1) {
+        out += getEmojiByName("TRACKS.SINGLE." + (selected == 0 ? "ACTIVE" : "INACTIVE"));
+    } else {
+        for (let i = 0; i < amount; i++) {
+            out += getEmojiByName("TRACKS.HORIZONTAL." +
+                (i == 0 ? "LEFT" : (i == amount - 1 ? "RIGHT" : "MIDDLE")) + "." +
+                (i == selected ? "ACTIVE" : "INACTIVE")
+            );
+        }
+    }
+    if (showDetails) {
+        out += " Page " + selected + " of " + amount;
+    }
+    return out;
+}
+
+export default pageIndicator;
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 715459c..4ae6519 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -423,6 +423,17 @@
     "has" "^1.0.3"
     "has-symbols" "^1.0.1"
 
+"glob@^5.0.10":
+  "integrity" "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E="
+  "resolved" "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz"
+  "version" "5.0.15"
+  dependencies:
+    "inflight" "^1.0.4"
+    "inherits" "2"
+    "minimatch" "2 || 3"
+    "once" "^1.3.0"
+    "path-is-absolute" "^1.0.0"
+
 "glob@^7.1.6":
   "integrity" "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q=="
   "resolved" "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz"
@@ -609,7 +620,7 @@
   "resolved" "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz"
   "version" "1.6.0"
 
-"minimatch@^3.0.4":
+"minimatch@^3.0.4", "minimatch@2 || 3":
   "integrity" "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="
   "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
   "version" "3.1.2"
@@ -643,6 +654,19 @@
   dependencies:
     "whatwg-url" "^5.0.0"
 
+"node-tesseract@^0.2.7":
+  "integrity" "sha1-yPAvuDUaQnByc1d4wFGYI/JgG4Q="
+  "resolved" "https://registry.npmjs.org/node-tesseract/-/node-tesseract-0.2.7.tgz"
+  "version" "0.2.7"
+  dependencies:
+    "glob" "^5.0.10"
+    "node-uuid" "^1.4.1"
+
+"node-uuid@^1.4.1":
+  "integrity" "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
+  "resolved" "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz"
+  "version" "1.4.8"
+
 "object-inspect@^1.9.0":
   "integrity" "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
   "resolved" "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz"
@@ -785,6 +809,11 @@
   "resolved" "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
   "version" "2.0.1"
 
+"structured-clone@^0.2.2":
+  "integrity" "sha1-rJK2vjGVimQ9sw8TNavGobAt/cI="
+  "resolved" "https://registry.npmjs.org/structured-clone/-/structured-clone-0.2.2.tgz"
+  "version" "0.2.2"
+
 "tesseract.js-core@^2.2.0":
   "integrity" "sha512-a8L+OJTbUipBsEDsJhDPlnLB0TY1MkTZqw5dqUwmiDSjUzwvU7HWLg/2+WDRulKUi4LE+7PnHlaBlW0k+V0U0w=="
   "resolved" "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-2.2.0.tgz"