Merge branch 'main' into development
diff --git a/.eslintrc.json b/.eslintrc.json
index 4b4e25d..165e759 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -4,7 +4,7 @@
         "es2020": true,
         "node": true
     },
-    "ignorePatterns": ["dist/", "src/Unfinished/"],
+    "ignorePatterns": ["dist/", "src/Unfinished/", "src/config/main.d.ts", "*.js"],
     "extends": ["eslint:recommended", "plugin:@typescript-eslint/strict", "prettier"],
     "parser": "@typescript-eslint/parser",
     "parserOptions": {
diff --git a/.gitignore b/.gitignore
index db17304..b8b8506 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,10 +2,12 @@
 .history/
 node_modules/
 src/config/*
+
+!src/config/*.d.ts
 !src/config/format.ts
 !src/config/default.json
 !src/config/emojis.json
-src/config/main.json
+src/config/main.ts
 .vscode/
 .vim/
 yarn-error.log
diff --git a/.prettierignore b/.prettierignore
index bfedc85..7575e2d 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -5,7 +5,7 @@
 !src/config/format.ts
 !src/config/default.json
 !src/config/emojis.json
-src/config/main.json
+!src/config/main.ts
 .vscode/
 yarn-error.log
 yarn.lock
diff --git a/ClicksMigratingProblems/randomPunishments.js b/ClicksMigratingProblems/randomPunishments.js
index af9c908..432a9f8 100644
--- a/ClicksMigratingProblems/randomPunishments.js
+++ b/ClicksMigratingProblems/randomPunishments.js
@@ -11,7 +11,7 @@
         Math.floor(Math.random() * 9)
     ];
     // Select a random date in the last year
-    let date = new Date(new Date().getTime() - Math.floor(Math.random() * 31536000000));
+    let date = new Date(Date.now() - Math.floor(Math.random() * 31536000000));
     // Add to database
     await collection.insertOne({
         type: type,
diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md
index 4f01364..1298cd4 100644
--- a/SELF_HOSTING.md
+++ b/SELF_HOSTING.md
@@ -23,7 +23,7 @@
 
 ## How to:
 
-We hide the config file with our important data like the bot token. Below you can find a copy of `src/config/main.json`.
+We hide the config file with our important data like the bot token. Below you can find a copy of `src/config/main.ts`.
 Alternatively, you can run `Installer.js` to generate it for you.
 
 ```json
diff --git a/TODO b/TODO
index d2cd1a2..91d025a 100644
--- a/TODO
+++ b/TODO
@@ -1,16 +1,2 @@
-Role all
 Server rules
 verificationRequired on welcome
-// TODO !IMPORTANT! URL + image hash + file hash database
-
-ROLE MENU SETTINGS
-
-**Title**
-> Description
-name: role
-name: role
-name: role
-
-[ Select an option to remove                        ]
-[ Reorder roles                                     ]
-< Previous page | Add role | Delete page | Add page >
diff --git a/TODO.json b/TODO.json
deleted file mode 100644
index 6f0b94e..0000000
--- a/TODO.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
-    "filters": {
-        "images": {
-            "NSFW": false,
-            "size": false
-        },
-        "malware": false,
-        "wordFilter": {
-            "enabled": true,
-            "words": {
-                "strict": [],
-                "loose": []
-            }
-        },
-        "invite": {
-            "enabled": false,
-            "channels": []
-        },
-        "pings": {
-            "mass": 5,
-            "everyone": true,
-            "roles": true
-        }
-    },
-    "roleMenu": [],
-    "tracks": []
-}
diff --git a/events2do b/events2do
deleted file mode 100644
index 15f4cbd..0000000
--- a/events2do
+++ /dev/null
@@ -1,30 +0,0 @@
--rw-r--r--  1 pineapplefan pineapplefan  2735 Dec 29 11:03 channelCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  3661 Dec 29 11:04 channelDelete.ts
--rw-r--r--  1 pineapplefan pineapplefan  7430 Nov 15 20:45 channelUpdate.ts
--rw-r--r--  1 pineapplefan pineapplefan   960 Nov 15 20:39 commandError.ts
--rw-r--r--  1 pineapplefan pineapplefan  1097 Aug  8 21:15 emojiCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1184 Aug  8 21:15 emojiDelete.ts
--rw-r--r--  1 pineapplefan pineapplefan  1183 Aug  8 21:15 emojiUpdate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1849 Dec 29 11:04 guildBanAdd.ts
--rw-r--r--  1 pineapplefan pineapplefan  1558 Dec 29 11:04 guildBanRemove.ts
--rw-r--r--  1 pineapplefan pineapplefan   267 Dec 29 11:04 guildCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  5553 Dec 29 11:04 guildMemberUpdate.ts
--rw-r--r--  1 pineapplefan pineapplefan  3863 Jan  6 17:42 guildUpdate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1710 Jan  6 18:38 interactionCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1482 Dec 29 11:04 inviteCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1471 Dec 29 11:04 inviteDelete.ts
--rw-r--r--  1 pineapplefan pineapplefan  1387 Dec 29 11:04 memberJoin.ts
--rw-r--r--  1 pineapplefan pineapplefan  3260 Jan  2 21:41 memberLeave.ts
--rw-r--r--  1 pineapplefan pineapplefan 14919 Dec 29 11:04 messageCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  4907 Dec 29 11:04 ! messageDelete.ts
--rw-r--r--  1 pineapplefan pineapplefan  4907 Dec 29 11:04 ? messageEdit.ts: Check message publishing
--rw-r--r--  1 pineapplefan pineapplefan  1268 Dec 29 11:04 roleCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1915 Dec 29 11:04 roleDelete.ts
--rw-r--r--  1 pineapplefan pineapplefan  2562 Dec 29 11:04 roleUpdate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1262 Dec 29 11:04 stickerCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1349 Dec 29 11:04 stickerDelete.ts
--rw-r--r--  1 pineapplefan pineapplefan  1272 Dec 29 11:04 stickerUpdate.ts
--rw-r--r--  1 pineapplefan pineapplefan  1967 Dec 29 11:04 threadCreate.ts
--rw-r--r--  1 pineapplefan pineapplefan  2140 Dec 29 11:04 threadDelete.ts
--rw-r--r--  1 pineapplefan pineapplefan  2464 Dec 29 11:04 threadUpdate.ts
--rw-r--r--  1 pineapplefan pineapplefan  6352 Dec 29 11:04 webhookUpdate.ts
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 0d56268..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,5094 +0,0 @@
-{
-    "name": "nucleus",
-    "version": "0.0.1",
-    "lockfileVersion": 3,
-    "requires": true,
-    "packages": {
-        "": {
-            "name": "nucleus",
-            "version": "0.0.1",
-            "license": "SEE LICENSE IN LICENSE",
-            "dependencies": {
-                "@discordjs/rest": "^0.2.0-canary.0",
-                "@hokify/agenda": "^6.2.12",
-                "@tsconfig/node18-strictest-esm": "^1.0.0",
-                "@types/node-cron": "^3.0.1",
-                "@ungap/structured-clone": "^1.0.1",
-                "agenda": "^4.3.0",
-                "ansi-styles": "^6.1.0",
-                "body-parser": "^1.20.0",
-                "chalk": "^5.0.0",
-                "deno": "^0.1.1",
-                "discord.js": "14.7.1",
-                "eslint": "^8.21.0",
-                "express": "^4.18.1",
-                "form-data": "^4.0.0",
-                "fuse.js": "^6.6.2",
-                "humanize-duration": "^3.27.1",
-                "immutable": "^4.1.0",
-                "mongodb": "^4.7.0",
-                "node-cron": "^3.0.0",
-                "node-fetch": "^3.3.0",
-                "node-tesseract-ocr": "^2.2.1",
-                "pastebin-api": "^5.1.1",
-                "structured-clone": "^0.2.2",
-                "systeminformation": "^5.17.3",
-                "typescript": "^5.0.0-dev.20230102",
-                "uuid": "^8.3.2"
-            },
-            "devDependencies": {
-                "@typescript-eslint/eslint-plugin": "^5.32.0",
-                "@typescript-eslint/parser": "^5.32.0",
-                "eslint-config-prettier": "^8.5.0",
-                "prettier": "^2.7.1",
-                "prettier-eslint": "^15.0.1",
-                "tsc-suppress": "^1.0.7"
-            }
-        },
-        "node_modules/@aws-crypto/ie11-detection": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz",
-            "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^1.11.1"
-            }
-        },
-        "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": {
-            "version": "1.14.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-            "license": "0BSD",
-            "optional": true
-        },
-        "node_modules/@aws-crypto/sha256-browser": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz",
-            "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-crypto/ie11-detection": "^2.0.0",
-                "@aws-crypto/sha256-js": "^2.0.0",
-                "@aws-crypto/supports-web-crypto": "^2.0.0",
-                "@aws-crypto/util": "^2.0.0",
-                "@aws-sdk/types": "^3.1.0",
-                "@aws-sdk/util-locate-window": "^3.0.0",
-                "@aws-sdk/util-utf8-browser": "^3.0.0",
-                "tslib": "^1.11.1"
-            }
-        },
-        "node_modules/@aws-crypto/sha256-browser/node_modules/@aws-crypto/sha256-js": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.2.tgz",
-            "integrity": "sha512-iXLdKH19qPmIC73fVCrHWCSYjN/sxaAvZ3jNNyw6FclmHyjLKg0f69WlC9KTnyElxCR5MO9SKaG00VwlJwyAkQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-crypto/util": "^2.0.2",
-                "@aws-sdk/types": "^3.110.0",
-                "tslib": "^1.11.1"
-            }
-        },
-        "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": {
-            "version": "1.14.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-            "license": "0BSD",
-            "optional": true
-        },
-        "node_modules/@aws-crypto/sha256-js": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz",
-            "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-crypto/util": "^2.0.0",
-                "@aws-sdk/types": "^3.1.0",
-                "tslib": "^1.11.1"
-            }
-        },
-        "node_modules/@aws-crypto/sha256-js/node_modules/tslib": {
-            "version": "1.14.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-            "license": "0BSD",
-            "optional": true
-        },
-        "node_modules/@aws-crypto/supports-web-crypto": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz",
-            "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^1.11.1"
-            }
-        },
-        "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": {
-            "version": "1.14.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-            "license": "0BSD",
-            "optional": true
-        },
-        "node_modules/@aws-crypto/util": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz",
-            "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "^3.110.0",
-                "@aws-sdk/util-utf8-browser": "^3.0.0",
-                "tslib": "^1.11.1"
-            }
-        },
-        "node_modules/@aws-crypto/util/node_modules/tslib": {
-            "version": "1.14.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-            "license": "0BSD",
-            "optional": true
-        },
-        "node_modules/@aws-sdk/abort-controller": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.226.0.tgz",
-            "integrity": "sha512-cJVzr1xxPBd08voknXvR0RLgtZKGKt6WyDpH/BaPCu3rfSqWCDZKzwqe940eqosjmKrxC6pUZNKASIqHOQ8xxQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/client-cognito-identity": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.252.0.tgz",
-            "integrity": "sha512-IHdrzMUGEQcUP7vN/wbVbRCHBXhC0nyaRxnnoHbrJfh5fzPSnkwo7qNf0e8ox+GXq8xgM58dEXefA6/TMYQPFQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-crypto/sha256-browser": "2.0.0",
-                "@aws-crypto/sha256-js": "2.0.0",
-                "@aws-sdk/client-sts": "3.252.0",
-                "@aws-sdk/config-resolver": "3.234.0",
-                "@aws-sdk/credential-provider-node": "3.252.0",
-                "@aws-sdk/fetch-http-handler": "3.226.0",
-                "@aws-sdk/hash-node": "3.226.0",
-                "@aws-sdk/invalid-dependency": "3.226.0",
-                "@aws-sdk/middleware-content-length": "3.226.0",
-                "@aws-sdk/middleware-endpoint": "3.226.0",
-                "@aws-sdk/middleware-host-header": "3.226.0",
-                "@aws-sdk/middleware-logger": "3.226.0",
-                "@aws-sdk/middleware-recursion-detection": "3.226.0",
-                "@aws-sdk/middleware-retry": "3.235.0",
-                "@aws-sdk/middleware-serde": "3.226.0",
-                "@aws-sdk/middleware-signing": "3.226.0",
-                "@aws-sdk/middleware-stack": "3.226.0",
-                "@aws-sdk/middleware-user-agent": "3.226.0",
-                "@aws-sdk/node-config-provider": "3.226.0",
-                "@aws-sdk/node-http-handler": "3.226.0",
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/smithy-client": "3.234.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/url-parser": "3.226.0",
-                "@aws-sdk/util-base64": "3.208.0",
-                "@aws-sdk/util-body-length-browser": "3.188.0",
-                "@aws-sdk/util-body-length-node": "3.208.0",
-                "@aws-sdk/util-defaults-mode-browser": "3.234.0",
-                "@aws-sdk/util-defaults-mode-node": "3.234.0",
-                "@aws-sdk/util-endpoints": "3.245.0",
-                "@aws-sdk/util-retry": "3.229.0",
-                "@aws-sdk/util-user-agent-browser": "3.226.0",
-                "@aws-sdk/util-user-agent-node": "3.226.0",
-                "@aws-sdk/util-utf8-browser": "3.188.0",
-                "@aws-sdk/util-utf8-node": "3.208.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/client-sso": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.252.0.tgz",
-            "integrity": "sha512-VgBqJvvCU4y9zAHJwYj5nOeNGcCxKdCO4edUxWQVHcpLsVWu49maOVtWuteq9MOrHYeWfQi8bVWGt8MPvv9+bA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-crypto/sha256-browser": "2.0.0",
-                "@aws-crypto/sha256-js": "2.0.0",
-                "@aws-sdk/config-resolver": "3.234.0",
-                "@aws-sdk/fetch-http-handler": "3.226.0",
-                "@aws-sdk/hash-node": "3.226.0",
-                "@aws-sdk/invalid-dependency": "3.226.0",
-                "@aws-sdk/middleware-content-length": "3.226.0",
-                "@aws-sdk/middleware-endpoint": "3.226.0",
-                "@aws-sdk/middleware-host-header": "3.226.0",
-                "@aws-sdk/middleware-logger": "3.226.0",
-                "@aws-sdk/middleware-recursion-detection": "3.226.0",
-                "@aws-sdk/middleware-retry": "3.235.0",
-                "@aws-sdk/middleware-serde": "3.226.0",
-                "@aws-sdk/middleware-stack": "3.226.0",
-                "@aws-sdk/middleware-user-agent": "3.226.0",
-                "@aws-sdk/node-config-provider": "3.226.0",
-                "@aws-sdk/node-http-handler": "3.226.0",
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/smithy-client": "3.234.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/url-parser": "3.226.0",
-                "@aws-sdk/util-base64": "3.208.0",
-                "@aws-sdk/util-body-length-browser": "3.188.0",
-                "@aws-sdk/util-body-length-node": "3.208.0",
-                "@aws-sdk/util-defaults-mode-browser": "3.234.0",
-                "@aws-sdk/util-defaults-mode-node": "3.234.0",
-                "@aws-sdk/util-endpoints": "3.245.0",
-                "@aws-sdk/util-retry": "3.229.0",
-                "@aws-sdk/util-user-agent-browser": "3.226.0",
-                "@aws-sdk/util-user-agent-node": "3.226.0",
-                "@aws-sdk/util-utf8-browser": "3.188.0",
-                "@aws-sdk/util-utf8-node": "3.208.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/client-sso-oidc": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.252.0.tgz",
-            "integrity": "sha512-OOwfEXFS+UliGZorEleARsXXUp3ObZSXo9/YY+8XF7/8froAqYjKCEi0tflghgYlh7d6qe7wzD7/6gDL1a/qgA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-crypto/sha256-browser": "2.0.0",
-                "@aws-crypto/sha256-js": "2.0.0",
-                "@aws-sdk/config-resolver": "3.234.0",
-                "@aws-sdk/fetch-http-handler": "3.226.0",
-                "@aws-sdk/hash-node": "3.226.0",
-                "@aws-sdk/invalid-dependency": "3.226.0",
-                "@aws-sdk/middleware-content-length": "3.226.0",
-                "@aws-sdk/middleware-endpoint": "3.226.0",
-                "@aws-sdk/middleware-host-header": "3.226.0",
-                "@aws-sdk/middleware-logger": "3.226.0",
-                "@aws-sdk/middleware-recursion-detection": "3.226.0",
-                "@aws-sdk/middleware-retry": "3.235.0",
-                "@aws-sdk/middleware-serde": "3.226.0",
-                "@aws-sdk/middleware-stack": "3.226.0",
-                "@aws-sdk/middleware-user-agent": "3.226.0",
-                "@aws-sdk/node-config-provider": "3.226.0",
-                "@aws-sdk/node-http-handler": "3.226.0",
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/smithy-client": "3.234.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/url-parser": "3.226.0",
-                "@aws-sdk/util-base64": "3.208.0",
-                "@aws-sdk/util-body-length-browser": "3.188.0",
-                "@aws-sdk/util-body-length-node": "3.208.0",
-                "@aws-sdk/util-defaults-mode-browser": "3.234.0",
-                "@aws-sdk/util-defaults-mode-node": "3.234.0",
-                "@aws-sdk/util-endpoints": "3.245.0",
-                "@aws-sdk/util-retry": "3.229.0",
-                "@aws-sdk/util-user-agent-browser": "3.226.0",
-                "@aws-sdk/util-user-agent-node": "3.226.0",
-                "@aws-sdk/util-utf8-browser": "3.188.0",
-                "@aws-sdk/util-utf8-node": "3.208.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/client-sts": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.252.0.tgz",
-            "integrity": "sha512-wzfsWOlDFLdmeML8R7DUJWGl9wcRKf2uiunfB1aWzpdlgms0Z7FkHWgkDYHjCPyYHL6EBm84ajGl1UkE7AcmqQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-crypto/sha256-browser": "2.0.0",
-                "@aws-crypto/sha256-js": "2.0.0",
-                "@aws-sdk/config-resolver": "3.234.0",
-                "@aws-sdk/credential-provider-node": "3.252.0",
-                "@aws-sdk/fetch-http-handler": "3.226.0",
-                "@aws-sdk/hash-node": "3.226.0",
-                "@aws-sdk/invalid-dependency": "3.226.0",
-                "@aws-sdk/middleware-content-length": "3.226.0",
-                "@aws-sdk/middleware-endpoint": "3.226.0",
-                "@aws-sdk/middleware-host-header": "3.226.0",
-                "@aws-sdk/middleware-logger": "3.226.0",
-                "@aws-sdk/middleware-recursion-detection": "3.226.0",
-                "@aws-sdk/middleware-retry": "3.235.0",
-                "@aws-sdk/middleware-sdk-sts": "3.226.0",
-                "@aws-sdk/middleware-serde": "3.226.0",
-                "@aws-sdk/middleware-signing": "3.226.0",
-                "@aws-sdk/middleware-stack": "3.226.0",
-                "@aws-sdk/middleware-user-agent": "3.226.0",
-                "@aws-sdk/node-config-provider": "3.226.0",
-                "@aws-sdk/node-http-handler": "3.226.0",
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/smithy-client": "3.234.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/url-parser": "3.226.0",
-                "@aws-sdk/util-base64": "3.208.0",
-                "@aws-sdk/util-body-length-browser": "3.188.0",
-                "@aws-sdk/util-body-length-node": "3.208.0",
-                "@aws-sdk/util-defaults-mode-browser": "3.234.0",
-                "@aws-sdk/util-defaults-mode-node": "3.234.0",
-                "@aws-sdk/util-endpoints": "3.245.0",
-                "@aws-sdk/util-retry": "3.229.0",
-                "@aws-sdk/util-user-agent-browser": "3.226.0",
-                "@aws-sdk/util-user-agent-node": "3.226.0",
-                "@aws-sdk/util-utf8-browser": "3.188.0",
-                "@aws-sdk/util-utf8-node": "3.208.0",
-                "fast-xml-parser": "4.0.11",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/client-sts/node_modules/fast-xml-parser": {
-            "version": "4.0.11",
-            "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz",
-            "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==",
-            "license": "MIT",
-            "optional": true,
-            "dependencies": {
-                "strnum": "^1.0.5"
-            },
-            "bin": {
-                "fxparser": "src/cli/cli.js"
-            },
-            "funding": {
-                "type": "paypal",
-                "url": "https://paypal.me/naturalintelligence"
-            }
-        },
-        "node_modules/@aws-sdk/config-resolver": {
-            "version": "3.234.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.234.0.tgz",
-            "integrity": "sha512-uZxy4wzllfvgCQxVc+Iqhde0NGAnfmV2hWR6ejadJaAFTuYNvQiRg9IqJy3pkyDPqXySiJ8Bom5PoJfgn55J/A==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/signature-v4": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/util-config-provider": "3.208.0",
-                "@aws-sdk/util-middleware": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-provider-cognito-identity": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.252.0.tgz",
-            "integrity": "sha512-QW3pBYetF06FOQ85FbsFjK6xpon8feF/UOHsL0lMGi4CxUZE6zshV/ectU7tACcc4QV8uMvN7OgcK947CMEEWA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/client-cognito-identity": "3.252.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-provider-env": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.226.0.tgz",
-            "integrity": "sha512-sd8uK1ojbXxaZXlthzw/VXZwCPUtU3PjObOfr3Evj7MPIM2IH8h29foOlggx939MdLQGboJf9gKvLlvKDWtJRA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-provider-imds": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.226.0.tgz",
-            "integrity": "sha512-//z/COQm2AjYFI1Lb0wKHTQSrvLFTyuKLFQGPJsKS7DPoxGOCKB7hmYerlbl01IDoCxTdyL//TyyPxbZEOQD5Q==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/node-config-provider": "3.226.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/url-parser": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-provider-ini": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.252.0.tgz",
-            "integrity": "sha512-OfpU8xMYK7+6XQ2dUO4rN0gUhhb/ZLV7iwSL6Ji2pI9gglGhKdOSfmbn6fBfCB50kzWZRNoiQJVaBu/d0Kr0EQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/credential-provider-env": "3.226.0",
-                "@aws-sdk/credential-provider-imds": "3.226.0",
-                "@aws-sdk/credential-provider-process": "3.226.0",
-                "@aws-sdk/credential-provider-sso": "3.252.0",
-                "@aws-sdk/credential-provider-web-identity": "3.226.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/shared-ini-file-loader": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-provider-node": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.252.0.tgz",
-            "integrity": "sha512-Jt854JnB7izkJ/gb3S0hBFqAQPUNUP3eL8gXX2uqk9A9bQFQdS57/Ci0FXaEPwOXzJwAAPazD8dTf6HXMhnm3w==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/credential-provider-env": "3.226.0",
-                "@aws-sdk/credential-provider-imds": "3.226.0",
-                "@aws-sdk/credential-provider-ini": "3.252.0",
-                "@aws-sdk/credential-provider-process": "3.226.0",
-                "@aws-sdk/credential-provider-sso": "3.252.0",
-                "@aws-sdk/credential-provider-web-identity": "3.226.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/shared-ini-file-loader": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-provider-process": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.226.0.tgz",
-            "integrity": "sha512-iUDMdnrTvbvaCFhWwqyXrhvQ9+ojPqPqXhwZtY1X/Qaz+73S9gXBPJHZaZb2Ke0yKE1Ql3bJbKvmmxC/qLQMng==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/shared-ini-file-loader": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-provider-sso": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.252.0.tgz",
-            "integrity": "sha512-2JGoojMOBjG9/DenctEszjdPechq0uDTpH5nx+z1xxIAugA5+HYG/ncNfpwhmUBCrnOxpRaQViTNqXddEPHlAg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/client-sso": "3.252.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/shared-ini-file-loader": "3.226.0",
-                "@aws-sdk/token-providers": "3.252.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-provider-web-identity": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.226.0.tgz",
-            "integrity": "sha512-CCpv847rLB0SFOHz2igvUMFAzeT2fD3YnY4C8jltuJoEkn0ITn1Hlgt13nTJ5BUuvyti2mvyXZHmNzhMIMrIlw==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/credential-providers": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.252.0.tgz",
-            "integrity": "sha512-aA4kwbvSlEcS9QSSlUWoVyoMYEljhkubNxpRhRnObsl4iT9xS06c38lKyhz3m0XIbCVk0lgYTcpue0dlybKS7Q==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/client-cognito-identity": "3.252.0",
-                "@aws-sdk/client-sso": "3.252.0",
-                "@aws-sdk/client-sts": "3.252.0",
-                "@aws-sdk/credential-provider-cognito-identity": "3.252.0",
-                "@aws-sdk/credential-provider-env": "3.226.0",
-                "@aws-sdk/credential-provider-imds": "3.226.0",
-                "@aws-sdk/credential-provider-ini": "3.252.0",
-                "@aws-sdk/credential-provider-node": "3.252.0",
-                "@aws-sdk/credential-provider-process": "3.226.0",
-                "@aws-sdk/credential-provider-sso": "3.252.0",
-                "@aws-sdk/credential-provider-web-identity": "3.226.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/shared-ini-file-loader": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/fetch-http-handler": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.226.0.tgz",
-            "integrity": "sha512-JewZPMNEBXfi1xVnRa7pVtK/zgZD8/lQ/YnD8pq79WuMa2cwyhDtr8oqCoqsPW+WJT5ScXoMtuHxN78l8eKWgg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/querystring-builder": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/util-base64": "3.208.0",
-                "tslib": "^2.3.1"
-            }
-        },
-        "node_modules/@aws-sdk/hash-node": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.226.0.tgz",
-            "integrity": "sha512-MdlJhJ9/Espwd0+gUXdZRsHuostB2WxEVAszWxobP0FTT9PnicqnfK7ExmW+DUAc0ywxtEbR3e0UND65rlSTVw==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/util-buffer-from": "3.208.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/invalid-dependency": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.226.0.tgz",
-            "integrity": "sha512-QXOYFmap8g9QzRjumcRCIo2GEZkdCwd7ePQW0OABWPhKHzlJ74vvBxywjU3s39EEBEluWXtZ7Iufg6GxZM4ifw==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            }
-        },
-        "node_modules/@aws-sdk/is-array-buffer": {
-            "version": "3.201.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz",
-            "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-content-length": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.226.0.tgz",
-            "integrity": "sha512-ksUzlHJN2JMuyavjA46a4sctvnrnITqt2tbGGWWrAuXY1mel2j+VbgnmJUiwHKUO6bTFBBeft5Vd1TSOb4JmiA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-endpoint": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.226.0.tgz",
-            "integrity": "sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/middleware-serde": "3.226.0",
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/signature-v4": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/url-parser": "3.226.0",
-                "@aws-sdk/util-config-provider": "3.208.0",
-                "@aws-sdk/util-middleware": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-host-header": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.226.0.tgz",
-            "integrity": "sha512-haVkWVh6BUPwKgWwkL6sDvTkcZWvJjv8AgC8jiQuSl8GLZdzHTB8Qhi3IsfFta9HAuoLjxheWBE5Z/L0UrfhLA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-logger": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.226.0.tgz",
-            "integrity": "sha512-m9gtLrrYnpN6yckcQ09rV7ExWOLMuq8mMPF/K3DbL/YL0TuILu9i2T1W+JuxSX+K9FMG2HrLAKivE/kMLr55xA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-recursion-detection": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.226.0.tgz",
-            "integrity": "sha512-mwRbdKEUeuNH5TEkyZ5FWxp6bL2UC1WbY+LDv6YjHxmSMKpAoOueEdtU34PqDOLrpXXxIGHDFmjeGeMfktyEcA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-retry": {
-            "version": "3.235.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.235.0.tgz",
-            "integrity": "sha512-50WHbJGpD3SNp9763MAlHqIhXil++JdQbKejNpHg7HsJne/ao3ub+fDOfx//mMBjpzBV25BGd5UlfL6blrClSg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/service-error-classification": "3.229.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/util-middleware": "3.226.0",
-                "@aws-sdk/util-retry": "3.229.0",
-                "tslib": "^2.3.1",
-                "uuid": "^8.3.2"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-sdk-sts": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.226.0.tgz",
-            "integrity": "sha512-NN9T/qoSD1kZvAT+VLny3NnlqgylYQcsgV3rvi/8lYzw/G/2s8VS6sm/VTWGGZhx08wZRv20MWzYu3bftcyqUg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/middleware-signing": "3.226.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/signature-v4": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-serde": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.226.0.tgz",
-            "integrity": "sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-signing": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.226.0.tgz",
-            "integrity": "sha512-E6HmtPcl+IjYDDzi1xI2HpCbBq2avNWcjvCriMZWuTAtRVpnA6XDDGW5GY85IfS3A8G8vuWqEVPr8JcYUcjfew==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/signature-v4": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/util-middleware": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-stack": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.226.0.tgz",
-            "integrity": "sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/middleware-user-agent": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.226.0.tgz",
-            "integrity": "sha512-N1WnfzCW1Y5yWhVAphf8OPGTe8Df3vmV7/LdsoQfmpkCZgLZeK2o0xITkUQhRj1mbw7yp8tVFLFV3R2lMurdAQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/node-config-provider": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.226.0.tgz",
-            "integrity": "sha512-B8lQDqiRk7X5izFEUMXmi8CZLOKCTWQJU9HQf3ako+sF0gexo4nHN3jhoRWyLtcgC5S3on/2jxpAcqtm7kuY3w==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/shared-ini-file-loader": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/node-http-handler": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.226.0.tgz",
-            "integrity": "sha512-xQCddnZNMiPmjr3W7HYM+f5ir4VfxgJh37eqZwX6EZmyItFpNNeVzKUgA920ka1VPz/ZUYB+2OFGiX3LCLkkaA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/abort-controller": "3.226.0",
-                "@aws-sdk/protocol-http": "3.226.0",
-                "@aws-sdk/querystring-builder": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/property-provider": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.226.0.tgz",
-            "integrity": "sha512-TsljjG+Sg0LmdgfiAlWohluWKnxB/k8xenjeozZfzOr5bHmNHtdbWv6BtNvD/R83hw7SFXxbJHlD5H4u9p2NFg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/protocol-http": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.226.0.tgz",
-            "integrity": "sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/querystring-builder": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.226.0.tgz",
-            "integrity": "sha512-LVurypuNeotO4lmirKXRC4NYrZRAyMJXuwO0f2a5ZAUJCjauwYrifKue6yCfU7bls7gut7nfcR6B99WBYpHs3g==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/util-uri-escape": "3.201.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/querystring-parser": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.226.0.tgz",
-            "integrity": "sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/service-error-classification": {
-            "version": "3.229.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.229.0.tgz",
-            "integrity": "sha512-dnzWWQ0/NoWMUZ5C0DW3dPm0wC1O76Y/SpKbuJzWPkx1EYy6r8p32Ly4D9vUzrKDbRGf48YHIF2kOkBmu21CLg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/shared-ini-file-loader": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.226.0.tgz",
-            "integrity": "sha512-661VQefsARxVyyV2FX9V61V+nNgImk7aN2hYlFKla6BCwZfMng+dEtD0xVGyg1PfRw0qvEv5LQyxMVgHcUSevA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/signature-v4": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.226.0.tgz",
-            "integrity": "sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/is-array-buffer": "3.201.0",
-                "@aws-sdk/types": "3.226.0",
-                "@aws-sdk/util-hex-encoding": "3.201.0",
-                "@aws-sdk/util-middleware": "3.226.0",
-                "@aws-sdk/util-uri-escape": "3.201.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/smithy-client": {
-            "version": "3.234.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.234.0.tgz",
-            "integrity": "sha512-8AtR/k4vsFvjXeQbIzq/Wy7Nbk48Ou0wUEeVYPHWHPSU8QamFWORkOwmKtKMfHAyZvmqiAPeQqHFkq+UJhWyyQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/middleware-stack": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/token-providers": {
-            "version": "3.252.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.252.0.tgz",
-            "integrity": "sha512-xi3pUP31tyKF4lJFCOgtkwSWESE9W1vE23Vybsq53wzXEYfnRql8RP+C9FFkUouAR6ixPHEcEYplB+l92CY49g==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/client-sso-oidc": "3.252.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/shared-ini-file-loader": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/types": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.226.0.tgz",
-            "integrity": "sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/url-parser": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.226.0.tgz",
-            "integrity": "sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/querystring-parser": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            }
-        },
-        "node_modules/@aws-sdk/util-base64": {
-            "version": "3.208.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz",
-            "integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/util-buffer-from": "3.208.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-body-length-browser": {
-            "version": "3.188.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz",
-            "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            }
-        },
-        "node_modules/@aws-sdk/util-body-length-node": {
-            "version": "3.208.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz",
-            "integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-buffer-from": {
-            "version": "3.208.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz",
-            "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/is-array-buffer": "3.201.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-config-provider": {
-            "version": "3.208.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz",
-            "integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-defaults-mode-browser": {
-            "version": "3.234.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.234.0.tgz",
-            "integrity": "sha512-IHMKXjTbOD8XMz5+2oCOsVP94BYb9YyjXdns0aAXr2NAo7k2+RCzXQ2DebJXppGda1F6opFutoKwyVSN0cmbMw==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "bowser": "^2.11.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">= 10.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-defaults-mode-node": {
-            "version": "3.234.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.234.0.tgz",
-            "integrity": "sha512-UGjQ+OjBYYhxFVtUY+jtr0ZZgzZh6OHtYwRhFt8IHewJXFCfZTyfsbX20szBj5y1S4HRIUJ7cwBLIytTqMbI5w==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/config-resolver": "3.234.0",
-                "@aws-sdk/credential-provider-imds": "3.226.0",
-                "@aws-sdk/node-config-provider": "3.226.0",
-                "@aws-sdk/property-provider": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">= 10.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-endpoints": {
-            "version": "3.245.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.245.0.tgz",
-            "integrity": "sha512-UNOFquB1tKx+8RT8n82Zb5tIwDyZHVPBg/m0LB0RsLETjr6krien5ASpqWezsXKIR1hftN9uaxN4bvf2dZrWHg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-hex-encoding": {
-            "version": "3.201.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz",
-            "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-locate-window": {
-            "version": "3.208.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz",
-            "integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-middleware": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.226.0.tgz",
-            "integrity": "sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-retry": {
-            "version": "3.229.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.229.0.tgz",
-            "integrity": "sha512-0zKTqi0P1inD0LzIMuXRIYYQ/8c1lWMg/cfiqUcIAF1TpatlpZuN7umU0ierpBFud7S+zDgg0oemh+Nj8xliJw==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/service-error-classification": "3.229.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">= 14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-uri-escape": {
-            "version": "3.201.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz",
-            "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@aws-sdk/util-user-agent-browser": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.226.0.tgz",
-            "integrity": "sha512-PhBIu2h6sPJPcv2I7ELfFizdl5pNiL4LfxrasMCYXQkJvVnoXztHA1x+CQbXIdtZOIlpjC+6BjDcE0uhnpvfcA==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/types": "3.226.0",
-                "bowser": "^2.11.0",
-                "tslib": "^2.3.1"
-            }
-        },
-        "node_modules/@aws-sdk/util-user-agent-node": {
-            "version": "3.226.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.226.0.tgz",
-            "integrity": "sha512-othPc5Dz/pkYkxH+nZPhc1Al0HndQT8zHD4e9h+EZ+8lkd8n+IsnLfTS/mSJWrfiC6UlNRVw55cItstmJyMe/A==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/node-config-provider": "3.226.0",
-                "@aws-sdk/types": "3.226.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            },
-            "peerDependencies": {
-                "aws-crt": ">=1.0.0"
-            },
-            "peerDependenciesMeta": {
-                "aws-crt": {
-                    "optional": true
-                }
-            }
-        },
-        "node_modules/@aws-sdk/util-utf8-browser": {
-            "version": "3.188.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz",
-            "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "tslib": "^2.3.1"
-            }
-        },
-        "node_modules/@aws-sdk/util-utf8-node": {
-            "version": "3.208.0",
-            "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz",
-            "integrity": "sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ==",
-            "license": "Apache-2.0",
-            "optional": true,
-            "dependencies": {
-                "@aws-sdk/util-buffer-from": "3.208.0",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@discordjs/builders": {
-            "version": "1.4.0",
-            "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.4.0.tgz",
-            "integrity": "sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "@discordjs/util": "^0.1.0",
-                "@sapphire/shapeshift": "^3.7.1",
-                "discord-api-types": "^0.37.20",
-                "fast-deep-equal": "^3.1.3",
-                "ts-mixer": "^6.0.2",
-                "tslib": "^2.4.1"
-            },
-            "engines": {
-                "node": ">=16.9.0"
-            }
-        },
-        "node_modules/@discordjs/collection": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.3.0.tgz",
-            "integrity": "sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg==",
-            "license": "Apache-2.0",
-            "engines": {
-                "node": ">=16.9.0"
-            }
-        },
-        "node_modules/@discordjs/rest": {
-            "version": "0.2.0-canary.0",
-            "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.2.0-canary.0.tgz",
-            "integrity": "sha512-jOxz1aqTEzn9N0qaJcZbHz6FbA0oq+vjpXUKkQzgfMihO6gC+kLlpRnFqG25T/aPYbjaR1UM/lGhrGBB1dutqg==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "@discordjs/collection": "^0.3.2",
-                "@sapphire/async-queue": "^1.1.9",
-                "@sapphire/snowflake": "^3.0.0",
-                "discord-api-types": "^0.25.2",
-                "form-data": "^4.0.0",
-                "node-fetch": "^2.6.5",
-                "tslib": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=16.0.0"
-            }
-        },
-        "node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
-            "version": "0.3.2",
-            "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.3.2.tgz",
-            "integrity": "sha512-dMjLl60b2DMqObbH1MQZKePgWhsNe49XkKBZ0W5Acl5uVV43SN414i2QfZwRI7dXAqIn8pEWD2+XXQFn9KWxqg==",
-            "license": "Apache-2.0",
-            "engines": {
-                "node": ">=16.0.0",
-                "npm": ">=7.0.0"
-            }
-        },
-        "node_modules/@discordjs/rest/node_modules/discord-api-types": {
-            "version": "0.25.2",
-            "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.25.2.tgz",
-            "integrity": "sha512-O243LXxb5gLLxubu5zgoppYQuolapGVWPw3ll0acN0+O8TnPUE2kFp9Bt3sTRYodw8xFIknOVxjSeyWYBpVcEQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=12"
-            }
-        },
-        "node_modules/@discordjs/rest/node_modules/node-fetch": {
-            "version": "2.6.8",
-            "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz",
-            "integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==",
-            "license": "MIT",
-            "dependencies": {
-                "whatwg-url": "^5.0.0"
-            },
-            "engines": {
-                "node": "4.x || >=6.0.0"
-            },
-            "peerDependencies": {
-                "encoding": "^0.1.0"
-            },
-            "peerDependenciesMeta": {
-                "encoding": {
-                    "optional": true
-                }
-            }
-        },
-        "node_modules/@discordjs/rest/node_modules/tr46": {
-            "version": "0.0.3",
-            "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-            "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
-            "license": "MIT"
-        },
-        "node_modules/@discordjs/rest/node_modules/whatwg-url": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-            "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-            "license": "MIT",
-            "dependencies": {
-                "tr46": "~0.0.3",
-                "webidl-conversions": "^3.0.0"
-            }
-        },
-        "node_modules/@discordjs/util": {
-            "version": "0.1.0",
-            "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.1.0.tgz",
-            "integrity": "sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==",
-            "license": "Apache-2.0",
-            "engines": {
-                "node": ">=16.9.0"
-            }
-        },
-        "node_modules/@eslint/eslintrc": {
-            "version": "1.4.1",
-            "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
-            "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
-            "license": "MIT",
-            "dependencies": {
-                "ajv": "^6.12.4",
-                "debug": "^4.3.2",
-                "espree": "^9.4.0",
-                "globals": "^13.19.0",
-                "ignore": "^5.2.0",
-                "import-fresh": "^3.2.1",
-                "js-yaml": "^4.1.0",
-                "minimatch": "^3.1.2",
-                "strip-json-comments": "^3.1.1"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "url": "https://opencollective.com/eslint"
-            }
-        },
-        "node_modules/@hokify/agenda": {
-            "version": "6.3.0",
-            "resolved": "https://registry.npmjs.org/@hokify/agenda/-/agenda-6.3.0.tgz",
-            "integrity": "sha512-fWrKMDe/8QHJXLOdEsMogb6cb213Z82iNsnU7nFrSIMFifEXSkXNTyCZ99FV3KLf+Du1gS/M9/8uTC6FHyWRZQ==",
-            "license": "MIT",
-            "dependencies": {
-                "cron-parser": "^4",
-                "date.js": "~0.3.3",
-                "debug": "~4",
-                "human-interval": "~2",
-                "luxon": "^3",
-                "mongodb": "^4"
-            },
-            "engines": {
-                "node": ">=14.0.0"
-            }
-        },
-        "node_modules/@hokify/agenda/node_modules/cron-parser": {
-            "version": "4.7.1",
-            "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.7.1.tgz",
-            "integrity": "sha512-WguFaoQ0hQ61SgsCZLHUcNbAvlK0lypKXu62ARguefYmjzaOXIVRNrAmyXzabTwUn4sQvQLkk6bjH+ipGfw8bA==",
-            "license": "MIT",
-            "dependencies": {
-                "luxon": "^3.2.1"
-            },
-            "engines": {
-                "node": ">=12.0.0"
-            }
-        },
-        "node_modules/@humanwhocodes/config-array": {
-            "version": "0.11.8",
-            "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
-            "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "@humanwhocodes/object-schema": "^1.2.1",
-                "debug": "^4.1.1",
-                "minimatch": "^3.0.5"
-            },
-            "engines": {
-                "node": ">=10.10.0"
-            }
-        },
-        "node_modules/@humanwhocodes/module-importer": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
-            "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
-            "license": "Apache-2.0",
-            "engines": {
-                "node": ">=12.22"
-            },
-            "funding": {
-                "type": "github",
-                "url": "https://github.com/sponsors/nzakas"
-            }
-        },
-        "node_modules/@humanwhocodes/object-schema": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
-            "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
-            "license": "BSD-3-Clause"
-        },
-        "node_modules/@nodelib/fs.scandir": {
-            "version": "2.1.5",
-            "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
-            "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-            "license": "MIT",
-            "dependencies": {
-                "@nodelib/fs.stat": "2.0.5",
-                "run-parallel": "^1.1.9"
-            },
-            "engines": {
-                "node": ">= 8"
-            }
-        },
-        "node_modules/@nodelib/fs.stat": {
-            "version": "2.0.5",
-            "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
-            "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 8"
-            }
-        },
-        "node_modules/@nodelib/fs.walk": {
-            "version": "1.2.8",
-            "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
-            "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-            "license": "MIT",
-            "dependencies": {
-                "@nodelib/fs.scandir": "2.1.5",
-                "fastq": "^1.6.0"
-            },
-            "engines": {
-                "node": ">= 8"
-            }
-        },
-        "node_modules/@sapphire/async-queue": {
-            "version": "1.5.0",
-            "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz",
-            "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=v14.0.0",
-                "npm": ">=7.0.0"
-            }
-        },
-        "node_modules/@sapphire/shapeshift": {
-            "version": "3.8.1",
-            "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.8.1.tgz",
-            "integrity": "sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw==",
-            "license": "MIT",
-            "dependencies": {
-                "fast-deep-equal": "^3.1.3",
-                "lodash": "^4.17.21"
-            },
-            "engines": {
-                "node": ">=v14.0.0",
-                "npm": ">=7.0.0"
-            }
-        },
-        "node_modules/@sapphire/snowflake": {
-            "version": "3.4.0",
-            "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.4.0.tgz",
-            "integrity": "sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=v14.0.0",
-                "npm": ">=7.0.0"
-            }
-        },
-        "node_modules/@tokenizer/token": {
-            "version": "0.3.0",
-            "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
-            "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
-            "license": "MIT"
-        },
-        "node_modules/@tsconfig/node18-strictest-esm": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.1.tgz",
-            "integrity": "sha512-cHzmAqw7CMbyqROWeBgVhard3F2V6zxOSJnQ4E6SJWruXD5ypuP9/QKekwBdfXQ4oUTaizIICKIwb+v3v33t0w==",
-            "license": "MIT"
-        },
-        "node_modules/@types/eslint": {
-            "version": "8.4.10",
-            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz",
-            "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "@types/estree": "*",
-                "@types/json-schema": "*"
-            }
-        },
-        "node_modules/@types/estree": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
-            "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/@types/json-schema": {
-            "version": "7.0.11",
-            "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
-            "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/@types/node": {
-            "version": "18.11.18",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
-            "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
-            "license": "MIT"
-        },
-        "node_modules/@types/node-cron": {
-            "version": "3.0.7",
-            "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.7.tgz",
-            "integrity": "sha512-9PuLtBboc/+JJ7FshmJWv769gDonTpItN0Ol5TMwclpSQNjVyB2SRxSKBcTtbSysSL5R7Oea06kTTFNciCoYwA==",
-            "license": "MIT"
-        },
-        "node_modules/@types/prettier": {
-            "version": "2.7.2",
-            "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
-            "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/@types/semver": {
-            "version": "7.3.13",
-            "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
-            "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/@types/webidl-conversions": {
-            "version": "7.0.0",
-            "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
-            "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==",
-            "license": "MIT"
-        },
-        "node_modules/@types/whatwg-url": {
-            "version": "8.2.2",
-            "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
-            "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
-            "license": "MIT",
-            "dependencies": {
-                "@types/node": "*",
-                "@types/webidl-conversions": "*"
-            }
-        },
-        "node_modules/@types/ws": {
-            "version": "8.5.4",
-            "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz",
-            "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==",
-            "license": "MIT",
-            "dependencies": {
-                "@types/node": "*"
-            }
-        },
-        "node_modules/@typescript-eslint/eslint-plugin": {
-            "version": "5.48.2",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz",
-            "integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "@typescript-eslint/scope-manager": "5.48.2",
-                "@typescript-eslint/type-utils": "5.48.2",
-                "@typescript-eslint/utils": "5.48.2",
-                "debug": "^4.3.4",
-                "ignore": "^5.2.0",
-                "natural-compare-lite": "^1.4.0",
-                "regexpp": "^3.2.0",
-                "semver": "^7.3.7",
-                "tsutils": "^3.21.0"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/typescript-eslint"
-            },
-            "peerDependencies": {
-                "@typescript-eslint/parser": "^5.0.0",
-                "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
-            },
-            "peerDependenciesMeta": {
-                "typescript": {
-                    "optional": true
-                }
-            }
-        },
-        "node_modules/@typescript-eslint/eslint-plugin/node_modules/tslib": {
-            "version": "1.14.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-            "dev": true
-        },
-        "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": {
-            "version": "3.21.0",
-            "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-            "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
-            "dev": true,
-            "dependencies": {
-                "tslib": "^1.8.1"
-            },
-            "engines": {
-                "node": ">= 6"
-            },
-            "peerDependencies": {
-                "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
-            }
-        },
-        "node_modules/@typescript-eslint/eslint-plugin/node_modules/typescript": {
-            "version": "4.9.4",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
-            "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
-            "dev": true,
-            "peer": true,
-            "bin": {
-                "tsc": "bin/tsc",
-                "tsserver": "bin/tsserver"
-            },
-            "engines": {
-                "node": ">=4.2.0"
-            }
-        },
-        "node_modules/@typescript-eslint/parser": {
-            "version": "5.48.2",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz",
-            "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==",
-            "dev": true,
-            "license": "BSD-2-Clause",
-            "dependencies": {
-                "@typescript-eslint/scope-manager": "5.48.2",
-                "@typescript-eslint/types": "5.48.2",
-                "@typescript-eslint/typescript-estree": "5.48.2",
-                "debug": "^4.3.4"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/typescript-eslint"
-            },
-            "peerDependencies": {
-                "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
-            },
-            "peerDependenciesMeta": {
-                "typescript": {
-                    "optional": true
-                }
-            }
-        },
-        "node_modules/@typescript-eslint/scope-manager": {
-            "version": "5.48.2",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz",
-            "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "@typescript-eslint/types": "5.48.2",
-                "@typescript-eslint/visitor-keys": "5.48.2"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/typescript-eslint"
-            }
-        },
-        "node_modules/@typescript-eslint/type-utils": {
-            "version": "5.48.2",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz",
-            "integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "@typescript-eslint/typescript-estree": "5.48.2",
-                "@typescript-eslint/utils": "5.48.2",
-                "debug": "^4.3.4",
-                "tsutils": "^3.21.0"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/typescript-eslint"
-            },
-            "peerDependencies": {
-                "eslint": "*"
-            },
-            "peerDependenciesMeta": {
-                "typescript": {
-                    "optional": true
-                }
-            }
-        },
-        "node_modules/@typescript-eslint/type-utils/node_modules/tslib": {
-            "version": "1.14.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-            "dev": true
-        },
-        "node_modules/@typescript-eslint/type-utils/node_modules/tsutils": {
-            "version": "3.21.0",
-            "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-            "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
-            "dev": true,
-            "dependencies": {
-                "tslib": "^1.8.1"
-            },
-            "engines": {
-                "node": ">= 6"
-            },
-            "peerDependencies": {
-                "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
-            }
-        },
-        "node_modules/@typescript-eslint/type-utils/node_modules/typescript": {
-            "version": "4.9.4",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
-            "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
-            "dev": true,
-            "peer": true,
-            "bin": {
-                "tsc": "bin/tsc",
-                "tsserver": "bin/tsserver"
-            },
-            "engines": {
-                "node": ">=4.2.0"
-            }
-        },
-        "node_modules/@typescript-eslint/types": {
-            "version": "5.48.2",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz",
-            "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/typescript-eslint"
-            }
-        },
-        "node_modules/@typescript-eslint/typescript-estree": {
-            "version": "5.48.2",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz",
-            "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==",
-            "dev": true,
-            "license": "BSD-2-Clause",
-            "dependencies": {
-                "@typescript-eslint/types": "5.48.2",
-                "@typescript-eslint/visitor-keys": "5.48.2",
-                "debug": "^4.3.4",
-                "globby": "^11.1.0",
-                "is-glob": "^4.0.3",
-                "semver": "^7.3.7",
-                "tsutils": "^3.21.0"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/typescript-eslint"
-            },
-            "peerDependenciesMeta": {
-                "typescript": {
-                    "optional": true
-                }
-            }
-        },
-        "node_modules/@typescript-eslint/typescript-estree/node_modules/tslib": {
-            "version": "1.14.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-            "dev": true
-        },
-        "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": {
-            "version": "3.21.0",
-            "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-            "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
-            "dev": true,
-            "dependencies": {
-                "tslib": "^1.8.1"
-            },
-            "engines": {
-                "node": ">= 6"
-            },
-            "peerDependencies": {
-                "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
-            }
-        },
-        "node_modules/@typescript-eslint/typescript-estree/node_modules/typescript": {
-            "version": "4.9.4",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
-            "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
-            "dev": true,
-            "peer": true,
-            "bin": {
-                "tsc": "bin/tsc",
-                "tsserver": "bin/tsserver"
-            },
-            "engines": {
-                "node": ">=4.2.0"
-            }
-        },
-        "node_modules/@typescript-eslint/utils": {
-            "version": "5.48.2",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz",
-            "integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "@types/json-schema": "^7.0.9",
-                "@types/semver": "^7.3.12",
-                "@typescript-eslint/scope-manager": "5.48.2",
-                "@typescript-eslint/types": "5.48.2",
-                "@typescript-eslint/typescript-estree": "5.48.2",
-                "eslint-scope": "^5.1.1",
-                "eslint-utils": "^3.0.0",
-                "semver": "^7.3.7"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/typescript-eslint"
-            },
-            "peerDependencies": {
-                "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
-            }
-        },
-        "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
-            "version": "5.1.1",
-            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
-            "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
-            "dev": true,
-            "license": "BSD-2-Clause",
-            "dependencies": {
-                "esrecurse": "^4.3.0",
-                "estraverse": "^4.1.1"
-            },
-            "engines": {
-                "node": ">=8.0.0"
-            }
-        },
-        "node_modules/@typescript-eslint/utils/node_modules/estraverse": {
-            "version": "4.3.0",
-            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
-            "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
-            "dev": true,
-            "license": "BSD-2-Clause",
-            "engines": {
-                "node": ">=4.0"
-            }
-        },
-        "node_modules/@typescript-eslint/visitor-keys": {
-            "version": "5.48.2",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz",
-            "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "@typescript-eslint/types": "5.48.2",
-                "eslint-visitor-keys": "^3.3.0"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/typescript-eslint"
-            }
-        },
-        "node_modules/@ungap/structured-clone": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.0.1.tgz",
-            "integrity": "sha512-zKVyTt6rELvPXYwcVPTJcPFtY0AckN5A7xWuc7owBqR0FdtuDYhE9MZZUi6IY1kZUQFSXV1B3UOOIyLkVHYd2w==",
-            "license": "ISC"
-        },
-        "node_modules/accepts": {
-            "version": "1.3.8",
-            "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
-            "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
-            "license": "MIT",
-            "dependencies": {
-                "mime-types": "~2.1.34",
-                "negotiator": "0.6.3"
-            },
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/acorn": {
-            "version": "8.8.1",
-            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
-            "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
-            "license": "MIT",
-            "bin": {
-                "acorn": "bin/acorn"
-            },
-            "engines": {
-                "node": ">=0.4.0"
-            }
-        },
-        "node_modules/acorn-jsx": {
-            "version": "5.3.2",
-            "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
-            "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
-            "license": "MIT",
-            "peerDependencies": {
-                "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
-            }
-        },
-        "node_modules/agenda": {
-            "version": "4.4.0",
-            "resolved": "https://registry.npmjs.org/agenda/-/agenda-4.4.0.tgz",
-            "integrity": "sha512-7fIO4indmmrtkDmj2woOBJnhHIM7jPtkdGR4VOApB46eeBrPGUnO28RFrmjHebc3PMDnKJI0PWFyu9L9VotgJg==",
-            "license": "MIT",
-            "dependencies": {
-                "cron-parser": "^3.0.0",
-                "date.js": "~0.3.3",
-                "debug": "~4.3.0",
-                "human-interval": "~2.0.0",
-                "moment-timezone": "~0.5.37",
-                "mongodb": "^4.1.0"
-            },
-            "engines": {
-                "node": ">=12.9.0"
-            }
-        },
-        "node_modules/ajv": {
-            "version": "6.12.6",
-            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
-            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
-            "license": "MIT",
-            "dependencies": {
-                "fast-deep-equal": "^3.1.1",
-                "fast-json-stable-stringify": "^2.0.0",
-                "json-schema-traverse": "^0.4.1",
-                "uri-js": "^4.2.2"
-            },
-            "funding": {
-                "type": "github",
-                "url": "https://github.com/sponsors/epoberezkin"
-            }
-        },
-        "node_modules/ansi-regex": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-            "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/ansi-styles": {
-            "version": "6.2.1",
-            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
-            "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=12"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-            }
-        },
-        "node_modules/argparse": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-            "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-            "license": "Python-2.0"
-        },
-        "node_modules/array-flatten": {
-            "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
-            "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
-            "license": "MIT"
-        },
-        "node_modules/array-union": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
-            "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/asynckit": {
-            "version": "0.4.0",
-            "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-            "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-            "license": "MIT"
-        },
-        "node_modules/balanced-match": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
-            "license": "MIT"
-        },
-        "node_modules/base64-js": {
-            "version": "1.5.1",
-            "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
-            "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "license": "MIT"
-        },
-        "node_modules/body-parser": {
-            "version": "1.20.1",
-            "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
-            "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
-            "license": "MIT",
-            "dependencies": {
-                "bytes": "3.1.2",
-                "content-type": "~1.0.4",
-                "debug": "2.6.9",
-                "depd": "2.0.0",
-                "destroy": "1.2.0",
-                "http-errors": "2.0.0",
-                "iconv-lite": "0.4.24",
-                "on-finished": "2.4.1",
-                "qs": "6.11.0",
-                "raw-body": "2.5.1",
-                "type-is": "~1.6.18",
-                "unpipe": "1.0.0"
-            },
-            "engines": {
-                "node": ">= 0.8",
-                "npm": "1.2.8000 || >= 1.4.16"
-            }
-        },
-        "node_modules/body-parser/node_modules/debug": {
-            "version": "2.6.9",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-            "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-            "license": "MIT",
-            "dependencies": {
-                "ms": "2.0.0"
-            }
-        },
-        "node_modules/bowser": {
-            "version": "2.11.0",
-            "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
-            "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==",
-            "license": "MIT",
-            "optional": true
-        },
-        "node_modules/brace-expansion": {
-            "version": "1.1.11",
-            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-            "license": "MIT",
-            "dependencies": {
-                "balanced-match": "^1.0.0",
-                "concat-map": "0.0.1"
-            }
-        },
-        "node_modules/braces": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-            "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "fill-range": "^7.0.1"
-            },
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/bson": {
-            "version": "4.7.2",
-            "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz",
-            "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "buffer": "^5.6.0"
-            },
-            "engines": {
-                "node": ">=6.9.0"
-            }
-        },
-        "node_modules/buffer": {
-            "version": "5.7.1",
-            "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
-            "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "license": "MIT",
-            "dependencies": {
-                "base64-js": "^1.3.1",
-                "ieee754": "^1.1.13"
-            }
-        },
-        "node_modules/busboy": {
-            "version": "1.6.0",
-            "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
-            "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
-            "dependencies": {
-                "streamsearch": "^1.1.0"
-            },
-            "engines": {
-                "node": ">=10.16.0"
-            }
-        },
-        "node_modules/bytes": {
-            "version": "3.1.2",
-            "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
-            "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/call-bind": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
-            "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-            "license": "MIT",
-            "dependencies": {
-                "function-bind": "^1.1.1",
-                "get-intrinsic": "^1.0.2"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/callsites": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
-            "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=6"
-            }
-        },
-        "node_modules/chalk": {
-            "version": "5.2.0",
-            "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
-            "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
-            "license": "MIT",
-            "engines": {
-                "node": "^12.17.0 || ^14.13 || >=16.0.0"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/chalk?sponsor=1"
-            }
-        },
-        "node_modules/color-convert": {
-            "version": "1.9.3",
-            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-            "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "color-name": "1.1.3"
-            }
-        },
-        "node_modules/color-name": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-            "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/combined-stream": {
-            "version": "1.0.8",
-            "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
-            "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-            "license": "MIT",
-            "dependencies": {
-                "delayed-stream": "~1.0.0"
-            },
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/common-tags": {
-            "version": "1.8.2",
-            "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
-            "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=4.0.0"
-            }
-        },
-        "node_modules/concat-map": {
-            "version": "0.0.1",
-            "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-            "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
-            "license": "MIT"
-        },
-        "node_modules/content-disposition": {
-            "version": "0.5.4",
-            "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
-            "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
-            "license": "MIT",
-            "dependencies": {
-                "safe-buffer": "5.2.1"
-            },
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/content-type": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
-            "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/cookie": {
-            "version": "0.5.0",
-            "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
-            "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/cookie-signature": {
-            "version": "1.0.6",
-            "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
-            "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
-            "license": "MIT"
-        },
-        "node_modules/cron-parser": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz",
-            "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==",
-            "license": "MIT",
-            "dependencies": {
-                "is-nan": "^1.3.2",
-                "luxon": "^1.26.0"
-            },
-            "engines": {
-                "node": ">=0.8"
-            }
-        },
-        "node_modules/cron-parser/node_modules/luxon": {
-            "version": "1.28.1",
-            "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
-            "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
-            "license": "MIT",
-            "engines": {
-                "node": "*"
-            }
-        },
-        "node_modules/cross-spawn": {
-            "version": "7.0.3",
-            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-            "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
-            "license": "MIT",
-            "dependencies": {
-                "path-key": "^3.1.0",
-                "shebang-command": "^2.0.0",
-                "which": "^2.0.1"
-            },
-            "engines": {
-                "node": ">= 8"
-            }
-        },
-        "node_modules/dafo": {
-            "version": "0.1.1",
-            "resolved": "https://registry.npmjs.org/dafo/-/dafo-0.1.1.tgz",
-            "integrity": "sha512-18XhtDt5qQ5pAfWzo1t3mRXQPanY8diFQFjCxh+/0mko5QHztCv6oLgPL9kKxB8aeYutp8lWC7BqjGzC8GWX1w==",
-            "dependencies": {
-                "noda": "^0.1.2"
-            }
-        },
-        "node_modules/dafo/node_modules/noda": {
-            "version": "0.1.2",
-            "resolved": "https://registry.npmjs.org/noda/-/noda-0.1.2.tgz",
-            "integrity": "sha512-oP5xzQsC4dujsuRlshsnb2R+0BWJmPRJx8MXQlTgbrSSqi5Szeu67Lr8Od1/lkc+8evLPRDmHl9L0xeVJYRGaw==",
-            "license": "ISC"
-        },
-        "node_modules/data-uri-to-buffer": {
-            "version": "4.0.1",
-            "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
-            "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 12"
-            }
-        },
-        "node_modules/date.js": {
-            "version": "0.3.3",
-            "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz",
-            "integrity": "sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==",
-            "license": "MIT",
-            "dependencies": {
-                "debug": "~3.1.0"
-            }
-        },
-        "node_modules/date.js/node_modules/debug": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-            "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-            "license": "MIT",
-            "dependencies": {
-                "ms": "2.0.0"
-            }
-        },
-        "node_modules/debug": {
-            "version": "4.3.4",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-            "license": "MIT",
-            "dependencies": {
-                "ms": "2.1.2"
-            },
-            "engines": {
-                "node": ">=6.0"
-            },
-            "peerDependenciesMeta": {
-                "supports-color": {
-                    "optional": true
-                }
-            }
-        },
-        "node_modules/debug/node_modules/ms": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-            "license": "MIT"
-        },
-        "node_modules/deep-is": {
-            "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
-            "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-            "license": "MIT"
-        },
-        "node_modules/define-properties": {
-            "version": "1.1.4",
-            "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
-            "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
-            "license": "MIT",
-            "dependencies": {
-                "has-property-descriptors": "^1.0.0",
-                "object-keys": "^1.1.1"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/delayed-stream": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-            "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.4.0"
-            }
-        },
-        "node_modules/deno": {
-            "version": "0.1.1",
-            "resolved": "https://registry.npmjs.org/deno/-/deno-0.1.1.tgz",
-            "integrity": "sha512-vdXCOZOXXgRUFPIV2HbyBBT+u1TjKmDueCEJuk9Q+ADLS7jmVfdfGxp0vI5o/gOjbA1OwOYMVfg249+9vjYa7w==",
-            "license": "MIT",
-            "dependencies": {
-                "dafo": "^0.1.1",
-                "noda": "^0.6.0",
-                "qir": "^0.1.0"
-            },
-            "bin": {
-                "install.js": "try-deno"
-            }
-        },
-        "node_modules/depd": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
-            "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/destroy": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
-            "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8",
-                "npm": "1.2.8000 || >= 1.4.16"
-            }
-        },
-        "node_modules/dir-glob": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
-            "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "path-type": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/discord-api-types": {
-            "version": "0.37.28",
-            "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.28.tgz",
-            "integrity": "sha512-K0fw7m7km9th3dCQ2AR90q/FwX3uAj+OLc+Zuo39VY9vCn0Ux/iObM4y1zJYIH3vTc+QlrksVErUvyeONjOKMQ==",
-            "license": "MIT"
-        },
-        "node_modules/discord.js": {
-            "version": "14.7.1",
-            "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.7.1.tgz",
-            "integrity": "sha512-1FECvqJJjjeYcjSm0IGMnPxLqja/pmG1B0W2l3lUY2Gi4KXiyTeQmU1IxWcbXHn2k+ytP587mMWqva2IA87EbA==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "@discordjs/builders": "^1.4.0",
-                "@discordjs/collection": "^1.3.0",
-                "@discordjs/rest": "^1.4.0",
-                "@discordjs/util": "^0.1.0",
-                "@sapphire/snowflake": "^3.2.2",
-                "@types/ws": "^8.5.3",
-                "discord-api-types": "^0.37.20",
-                "fast-deep-equal": "^3.1.3",
-                "lodash.snakecase": "^4.1.1",
-                "tslib": "^2.4.1",
-                "undici": "^5.13.0",
-                "ws": "^8.11.0"
-            },
-            "engines": {
-                "node": ">=16.9.0"
-            }
-        },
-        "node_modules/discord.js/node_modules/@discordjs/rest": {
-            "version": "1.5.0",
-            "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.5.0.tgz",
-            "integrity": "sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "@discordjs/collection": "^1.3.0",
-                "@discordjs/util": "^0.1.0",
-                "@sapphire/async-queue": "^1.5.0",
-                "@sapphire/snowflake": "^3.2.2",
-                "discord-api-types": "^0.37.23",
-                "file-type": "^18.0.0",
-                "tslib": "^2.4.1",
-                "undici": "^5.13.0"
-            },
-            "engines": {
-                "node": ">=16.9.0"
-            }
-        },
-        "node_modules/dlv": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
-            "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/doctrine": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
-            "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "esutils": "^2.0.2"
-            },
-            "engines": {
-                "node": ">=6.0.0"
-            }
-        },
-        "node_modules/ee-first": {
-            "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
-            "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
-            "license": "MIT"
-        },
-        "node_modules/encodeurl": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-            "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/escape-html": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-            "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
-            "license": "MIT"
-        },
-        "node_modules/escape-string-regexp": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-            "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/eslint": {
-            "version": "8.32.0",
-            "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz",
-            "integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==",
-            "license": "MIT",
-            "dependencies": {
-                "@eslint/eslintrc": "^1.4.1",
-                "@humanwhocodes/config-array": "^0.11.8",
-                "@humanwhocodes/module-importer": "^1.0.1",
-                "@nodelib/fs.walk": "^1.2.8",
-                "ajv": "^6.10.0",
-                "chalk": "^4.0.0",
-                "cross-spawn": "^7.0.2",
-                "debug": "^4.3.2",
-                "doctrine": "^3.0.0",
-                "escape-string-regexp": "^4.0.0",
-                "eslint-scope": "^7.1.1",
-                "eslint-utils": "^3.0.0",
-                "eslint-visitor-keys": "^3.3.0",
-                "espree": "^9.4.0",
-                "esquery": "^1.4.0",
-                "esutils": "^2.0.2",
-                "fast-deep-equal": "^3.1.3",
-                "file-entry-cache": "^6.0.1",
-                "find-up": "^5.0.0",
-                "glob-parent": "^6.0.2",
-                "globals": "^13.19.0",
-                "grapheme-splitter": "^1.0.4",
-                "ignore": "^5.2.0",
-                "import-fresh": "^3.0.0",
-                "imurmurhash": "^0.1.4",
-                "is-glob": "^4.0.0",
-                "is-path-inside": "^3.0.3",
-                "js-sdsl": "^4.1.4",
-                "js-yaml": "^4.1.0",
-                "json-stable-stringify-without-jsonify": "^1.0.1",
-                "levn": "^0.4.1",
-                "lodash.merge": "^4.6.2",
-                "minimatch": "^3.1.2",
-                "natural-compare": "^1.4.0",
-                "optionator": "^0.9.1",
-                "regexpp": "^3.2.0",
-                "strip-ansi": "^6.0.1",
-                "strip-json-comments": "^3.1.0",
-                "text-table": "^0.2.0"
-            },
-            "bin": {
-                "eslint": "bin/eslint.js"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "url": "https://opencollective.com/eslint"
-            }
-        },
-        "node_modules/eslint-config-prettier": {
-            "version": "8.6.0",
-            "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz",
-            "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==",
-            "dev": true,
-            "license": "MIT",
-            "bin": {
-                "eslint-config-prettier": "bin/cli.js"
-            },
-            "peerDependencies": {
-                "eslint": ">=7.0.0"
-            }
-        },
-        "node_modules/eslint-scope": {
-            "version": "7.1.1",
-            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
-            "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
-            "license": "BSD-2-Clause",
-            "dependencies": {
-                "esrecurse": "^4.3.0",
-                "estraverse": "^5.2.0"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            }
-        },
-        "node_modules/eslint-utils": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
-            "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
-            "license": "MIT",
-            "dependencies": {
-                "eslint-visitor-keys": "^2.0.0"
-            },
-            "engines": {
-                "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/mysticatea"
-            },
-            "peerDependencies": {
-                "eslint": ">=5"
-            }
-        },
-        "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
-            "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
-            "license": "Apache-2.0",
-            "engines": {
-                "node": ">=10"
-            }
-        },
-        "node_modules/eslint-visitor-keys": {
-            "version": "3.3.0",
-            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
-            "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
-            "license": "Apache-2.0",
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            }
-        },
-        "node_modules/eslint/node_modules/ansi-styles": {
-            "version": "4.3.0",
-            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-            "license": "MIT",
-            "dependencies": {
-                "color-convert": "^2.0.1"
-            },
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-            }
-        },
-        "node_modules/eslint/node_modules/chalk": {
-            "version": "4.1.2",
-            "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-            "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-            "license": "MIT",
-            "dependencies": {
-                "ansi-styles": "^4.1.0",
-                "supports-color": "^7.1.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/chalk?sponsor=1"
-            }
-        },
-        "node_modules/eslint/node_modules/color-convert": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-            "license": "MIT",
-            "dependencies": {
-                "color-name": "~1.1.4"
-            },
-            "engines": {
-                "node": ">=7.0.0"
-            }
-        },
-        "node_modules/eslint/node_modules/color-name": {
-            "version": "1.1.4",
-            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-            "license": "MIT"
-        },
-        "node_modules/espree": {
-            "version": "9.4.1",
-            "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
-            "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
-            "license": "BSD-2-Clause",
-            "dependencies": {
-                "acorn": "^8.8.0",
-                "acorn-jsx": "^5.3.2",
-                "eslint-visitor-keys": "^3.3.0"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "url": "https://opencollective.com/eslint"
-            }
-        },
-        "node_modules/esquery": {
-            "version": "1.4.0",
-            "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
-            "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
-            "license": "BSD-3-Clause",
-            "dependencies": {
-                "estraverse": "^5.1.0"
-            },
-            "engines": {
-                "node": ">=0.10"
-            }
-        },
-        "node_modules/esrecurse": {
-            "version": "4.3.0",
-            "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
-            "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
-            "license": "BSD-2-Clause",
-            "dependencies": {
-                "estraverse": "^5.2.0"
-            },
-            "engines": {
-                "node": ">=4.0"
-            }
-        },
-        "node_modules/estraverse": {
-            "version": "5.3.0",
-            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
-            "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-            "license": "BSD-2-Clause",
-            "engines": {
-                "node": ">=4.0"
-            }
-        },
-        "node_modules/esutils": {
-            "version": "2.0.3",
-            "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
-            "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-            "license": "BSD-2-Clause",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/etag": {
-            "version": "1.8.1",
-            "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
-            "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/express": {
-            "version": "4.18.2",
-            "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
-            "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
-            "license": "MIT",
-            "dependencies": {
-                "accepts": "~1.3.8",
-                "array-flatten": "1.1.1",
-                "body-parser": "1.20.1",
-                "content-disposition": "0.5.4",
-                "content-type": "~1.0.4",
-                "cookie": "0.5.0",
-                "cookie-signature": "1.0.6",
-                "debug": "2.6.9",
-                "depd": "2.0.0",
-                "encodeurl": "~1.0.2",
-                "escape-html": "~1.0.3",
-                "etag": "~1.8.1",
-                "finalhandler": "1.2.0",
-                "fresh": "0.5.2",
-                "http-errors": "2.0.0",
-                "merge-descriptors": "1.0.1",
-                "methods": "~1.1.2",
-                "on-finished": "2.4.1",
-                "parseurl": "~1.3.3",
-                "path-to-regexp": "0.1.7",
-                "proxy-addr": "~2.0.7",
-                "qs": "6.11.0",
-                "range-parser": "~1.2.1",
-                "safe-buffer": "5.2.1",
-                "send": "0.18.0",
-                "serve-static": "1.15.0",
-                "setprototypeof": "1.2.0",
-                "statuses": "2.0.1",
-                "type-is": "~1.6.18",
-                "utils-merge": "1.0.1",
-                "vary": "~1.1.2"
-            },
-            "engines": {
-                "node": ">= 0.10.0"
-            }
-        },
-        "node_modules/express/node_modules/debug": {
-            "version": "2.6.9",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-            "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-            "license": "MIT",
-            "dependencies": {
-                "ms": "2.0.0"
-            }
-        },
-        "node_modules/fast-deep-equal": {
-            "version": "3.1.3",
-            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-            "license": "MIT"
-        },
-        "node_modules/fast-glob": {
-            "version": "3.2.12",
-            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
-            "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "@nodelib/fs.stat": "^2.0.2",
-                "@nodelib/fs.walk": "^1.2.3",
-                "glob-parent": "^5.1.2",
-                "merge2": "^1.3.0",
-                "micromatch": "^4.0.4"
-            },
-            "engines": {
-                "node": ">=8.6.0"
-            }
-        },
-        "node_modules/fast-glob/node_modules/glob-parent": {
-            "version": "5.1.2",
-            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-            "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-            "dev": true,
-            "license": "ISC",
-            "dependencies": {
-                "is-glob": "^4.0.1"
-            },
-            "engines": {
-                "node": ">= 6"
-            }
-        },
-        "node_modules/fast-json-stable-stringify": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
-            "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
-            "license": "MIT"
-        },
-        "node_modules/fast-levenshtein": {
-            "version": "2.0.6",
-            "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-            "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
-            "license": "MIT"
-        },
-        "node_modules/fast-xml-parser": {
-            "version": "4.0.13",
-            "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.13.tgz",
-            "integrity": "sha512-g+OboAw8ol1FgTHhKLR7ZHcItNudceiY04BBrvqa0JBWoPhi/+e5r4H5AeW+EsQCroJLJwsuOP3dD3c6cc5uOg==",
-            "license": "MIT",
-            "dependencies": {
-                "strnum": "^1.0.5"
-            },
-            "bin": {
-                "fxparser": "src/cli/cli.js"
-            },
-            "funding": {
-                "type": "paypal",
-                "url": "https://paypal.me/naturalintelligence"
-            }
-        },
-        "node_modules/fastq": {
-            "version": "1.15.0",
-            "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
-            "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
-            "license": "ISC",
-            "dependencies": {
-                "reusify": "^1.0.4"
-            }
-        },
-        "node_modules/fetch-blob": {
-            "version": "3.2.0",
-            "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
-            "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/jimmywarting"
-                },
-                {
-                    "type": "paypal",
-                    "url": "https://paypal.me/jimmywarting"
-                }
-            ],
-            "license": "MIT",
-            "dependencies": {
-                "node-domexception": "^1.0.0",
-                "web-streams-polyfill": "^3.0.3"
-            },
-            "engines": {
-                "node": "^12.20 || >= 14.13"
-            }
-        },
-        "node_modules/file-entry-cache": {
-            "version": "6.0.1",
-            "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
-            "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
-            "license": "MIT",
-            "dependencies": {
-                "flat-cache": "^3.0.4"
-            },
-            "engines": {
-                "node": "^10.12.0 || >=12.0.0"
-            }
-        },
-        "node_modules/file-type": {
-            "version": "18.2.0",
-            "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.2.0.tgz",
-            "integrity": "sha512-M3RQMWY3F2ykyWZ+IHwNCjpnUmukYhtdkGGC1ZVEUb0ve5REGF7NNJ4Q9ehCUabtQKtSVFOMbFTXgJlFb0DQIg==",
-            "license": "MIT",
-            "dependencies": {
-                "readable-web-to-node-stream": "^3.0.2",
-                "strtok3": "^7.0.0",
-                "token-types": "^5.0.1"
-            },
-            "engines": {
-                "node": ">=14.16"
-            },
-            "funding": {
-                "url": "https://github.com/sindresorhus/file-type?sponsor=1"
-            }
-        },
-        "node_modules/fill-range": {
-            "version": "7.0.1",
-            "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-            "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "to-regex-range": "^5.0.1"
-            },
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/finalhandler": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
-            "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
-            "license": "MIT",
-            "dependencies": {
-                "debug": "2.6.9",
-                "encodeurl": "~1.0.2",
-                "escape-html": "~1.0.3",
-                "on-finished": "2.4.1",
-                "parseurl": "~1.3.3",
-                "statuses": "2.0.1",
-                "unpipe": "~1.0.0"
-            },
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/finalhandler/node_modules/debug": {
-            "version": "2.6.9",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-            "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-            "license": "MIT",
-            "dependencies": {
-                "ms": "2.0.0"
-            }
-        },
-        "node_modules/find-up": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
-            "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
-            "license": "MIT",
-            "dependencies": {
-                "locate-path": "^6.0.0",
-                "path-exists": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/flat-cache": {
-            "version": "3.0.4",
-            "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
-            "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
-            "license": "MIT",
-            "dependencies": {
-                "flatted": "^3.1.0",
-                "rimraf": "^3.0.2"
-            },
-            "engines": {
-                "node": "^10.12.0 || >=12.0.0"
-            }
-        },
-        "node_modules/flatted": {
-            "version": "3.2.7",
-            "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
-            "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
-            "license": "ISC"
-        },
-        "node_modules/form-data": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-            "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-            "license": "MIT",
-            "dependencies": {
-                "asynckit": "^0.4.0",
-                "combined-stream": "^1.0.8",
-                "mime-types": "^2.1.12"
-            },
-            "engines": {
-                "node": ">= 6"
-            }
-        },
-        "node_modules/formdata-polyfill": {
-            "version": "4.0.10",
-            "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
-            "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
-            "license": "MIT",
-            "dependencies": {
-                "fetch-blob": "^3.1.2"
-            },
-            "engines": {
-                "node": ">=12.20.0"
-            }
-        },
-        "node_modules/forwarded": {
-            "version": "0.2.0",
-            "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
-            "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/fresh": {
-            "version": "0.5.2",
-            "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
-            "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/fs.realpath": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-            "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
-            "license": "ISC"
-        },
-        "node_modules/function-bind": {
-            "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-            "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-            "license": "MIT"
-        },
-        "node_modules/fuse.js": {
-            "version": "6.6.2",
-            "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
-            "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==",
-            "license": "Apache-2.0",
-            "engines": {
-                "node": ">=10"
-            }
-        },
-        "node_modules/get-intrinsic": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
-            "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
-            "license": "MIT",
-            "dependencies": {
-                "function-bind": "^1.1.1",
-                "has": "^1.0.3",
-                "has-symbols": "^1.0.3"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/glob": {
-            "version": "7.2.3",
-            "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
-            "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
-            "license": "ISC",
-            "dependencies": {
-                "fs.realpath": "^1.0.0",
-                "inflight": "^1.0.4",
-                "inherits": "2",
-                "minimatch": "^3.1.1",
-                "once": "^1.3.0",
-                "path-is-absolute": "^1.0.0"
-            },
-            "engines": {
-                "node": "*"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/isaacs"
-            }
-        },
-        "node_modules/glob-parent": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
-            "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
-            "license": "ISC",
-            "dependencies": {
-                "is-glob": "^4.0.3"
-            },
-            "engines": {
-                "node": ">=10.13.0"
-            }
-        },
-        "node_modules/globals": {
-            "version": "13.19.0",
-            "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz",
-            "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==",
-            "license": "MIT",
-            "dependencies": {
-                "type-fest": "^0.20.2"
-            },
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/globby": {
-            "version": "11.1.0",
-            "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
-            "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "array-union": "^2.1.0",
-                "dir-glob": "^3.0.1",
-                "fast-glob": "^3.2.9",
-                "ignore": "^5.2.0",
-                "merge2": "^1.4.1",
-                "slash": "^3.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/grapheme-splitter": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
-            "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
-            "license": "MIT"
-        },
-        "node_modules/has": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
-            "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-            "license": "MIT",
-            "dependencies": {
-                "function-bind": "^1.1.1"
-            },
-            "engines": {
-                "node": ">= 0.4.0"
-            }
-        },
-        "node_modules/has-ansi": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-            "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "ansi-regex": "^2.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/has-property-descriptors": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
-            "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
-            "license": "MIT",
-            "dependencies": {
-                "get-intrinsic": "^1.1.1"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/has-symbols": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-            "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.4"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/http-errors": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
-            "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
-            "license": "MIT",
-            "dependencies": {
-                "depd": "2.0.0",
-                "inherits": "2.0.4",
-                "setprototypeof": "1.2.0",
-                "statuses": "2.0.1",
-                "toidentifier": "1.0.1"
-            },
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/human-interval": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz",
-            "integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==",
-            "license": "MIT",
-            "dependencies": {
-                "numbered": "^1.1.0"
-            }
-        },
-        "node_modules/humanize-duration": {
-            "version": "3.28.0",
-            "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.28.0.tgz",
-            "integrity": "sha512-jMAxraOOmHuPbffLVDKkEKi/NeG8dMqP8lGRd6Tbf7JgAeG33jjgPWDbXXU7ypCI0o+oNKJFgbSB9FKVdWNI2A==",
-            "license": "Unlicense"
-        },
-        "node_modules/iconv-lite": {
-            "version": "0.4.24",
-            "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
-            "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
-            "license": "MIT",
-            "dependencies": {
-                "safer-buffer": ">= 2.1.2 < 3"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/ieee754": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
-            "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "license": "BSD-3-Clause"
-        },
-        "node_modules/ignore": {
-            "version": "5.2.4",
-            "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
-            "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 4"
-            }
-        },
-        "node_modules/immutable": {
-            "version": "4.2.2",
-            "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz",
-            "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==",
-            "license": "MIT"
-        },
-        "node_modules/import-fresh": {
-            "version": "3.3.0",
-            "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
-            "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
-            "license": "MIT",
-            "dependencies": {
-                "parent-module": "^1.0.0",
-                "resolve-from": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=6"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/imurmurhash": {
-            "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-            "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.8.19"
-            }
-        },
-        "node_modules/indent-string": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
-            "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/inflight": {
-            "version": "1.0.6",
-            "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-            "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
-            "license": "ISC",
-            "dependencies": {
-                "once": "^1.3.0",
-                "wrappy": "1"
-            }
-        },
-        "node_modules/inherits": {
-            "version": "2.0.4",
-            "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-            "license": "ISC"
-        },
-        "node_modules/ip": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
-            "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
-            "license": "MIT"
-        },
-        "node_modules/ipaddr.js": {
-            "version": "1.9.1",
-            "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
-            "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.10"
-            }
-        },
-        "node_modules/is-extglob": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-            "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/is-glob": {
-            "version": "4.0.3",
-            "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
-            "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-            "license": "MIT",
-            "dependencies": {
-                "is-extglob": "^2.1.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/is-nan": {
-            "version": "1.3.2",
-            "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
-            "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
-            "license": "MIT",
-            "dependencies": {
-                "call-bind": "^1.0.0",
-                "define-properties": "^1.1.3"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/is-number": {
-            "version": "7.0.0",
-            "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-            "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.12.0"
-            }
-        },
-        "node_modules/is-path-inside": {
-            "version": "3.0.3",
-            "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
-            "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/isexe": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-            "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
-            "license": "ISC"
-        },
-        "node_modules/js-sdsl": {
-            "version": "4.2.0",
-            "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
-            "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
-            "license": "MIT",
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/js-sdsl"
-            }
-        },
-        "node_modules/js-yaml": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
-            "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-            "license": "MIT",
-            "dependencies": {
-                "argparse": "^2.0.1"
-            },
-            "bin": {
-                "js-yaml": "bin/js-yaml.js"
-            }
-        },
-        "node_modules/json-schema-traverse": {
-            "version": "0.4.1",
-            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-            "license": "MIT"
-        },
-        "node_modules/json-stable-stringify-without-jsonify": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
-            "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
-            "license": "MIT"
-        },
-        "node_modules/levn": {
-            "version": "0.4.1",
-            "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
-            "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
-            "license": "MIT",
-            "dependencies": {
-                "prelude-ls": "^1.2.1",
-                "type-check": "~0.4.0"
-            },
-            "engines": {
-                "node": ">= 0.8.0"
-            }
-        },
-        "node_modules/locate-path": {
-            "version": "6.0.0",
-            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
-            "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
-            "license": "MIT",
-            "dependencies": {
-                "p-locate": "^5.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/lodash": {
-            "version": "4.17.21",
-            "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-            "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-            "license": "MIT"
-        },
-        "node_modules/lodash.merge": {
-            "version": "4.6.2",
-            "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
-            "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
-            "license": "MIT"
-        },
-        "node_modules/lodash.snakecase": {
-            "version": "4.1.1",
-            "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
-            "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
-            "license": "MIT"
-        },
-        "node_modules/loglevel": {
-            "version": "1.8.1",
-            "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
-            "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6.0"
-            },
-            "funding": {
-                "type": "tidelift",
-                "url": "https://tidelift.com/funding/github/npm/loglevel"
-            }
-        },
-        "node_modules/loglevel-colored-level-prefix": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz",
-            "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "chalk": "^1.1.3",
-                "loglevel": "^1.4.1"
-            }
-        },
-        "node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": {
-            "version": "2.2.1",
-            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-            "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/loglevel-colored-level-prefix/node_modules/chalk": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-            "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "ansi-styles": "^2.2.1",
-                "escape-string-regexp": "^1.0.2",
-                "has-ansi": "^2.0.0",
-                "strip-ansi": "^3.0.0",
-                "supports-color": "^2.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/loglevel-colored-level-prefix/node_modules/escape-string-regexp": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.8.0"
-            }
-        },
-        "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-            "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "ansi-regex": "^2.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/loglevel-colored-level-prefix/node_modules/supports-color": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-            "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.8.0"
-            }
-        },
-        "node_modules/lru-cache": {
-            "version": "6.0.0",
-            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-            "dev": true,
-            "license": "ISC",
-            "dependencies": {
-                "yallist": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            }
-        },
-        "node_modules/luxon": {
-            "version": "3.2.1",
-            "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz",
-            "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=12"
-            }
-        },
-        "node_modules/media-typer": {
-            "version": "0.3.0",
-            "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
-            "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/memory-pager": {
-            "version": "1.5.0",
-            "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
-            "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
-            "license": "MIT",
-            "optional": true
-        },
-        "node_modules/merge-descriptors": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-            "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
-            "license": "MIT"
-        },
-        "node_modules/merge2": {
-            "version": "1.4.1",
-            "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
-            "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">= 8"
-            }
-        },
-        "node_modules/methods": {
-            "version": "1.1.2",
-            "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
-            "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/micromatch": {
-            "version": "4.0.5",
-            "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-            "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "braces": "^3.0.2",
-                "picomatch": "^2.3.1"
-            },
-            "engines": {
-                "node": ">=8.6"
-            }
-        },
-        "node_modules/mime": {
-            "version": "1.6.0",
-            "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
-            "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
-            "license": "MIT",
-            "bin": {
-                "mime": "cli.js"
-            },
-            "engines": {
-                "node": ">=4"
-            }
-        },
-        "node_modules/mime-db": {
-            "version": "1.52.0",
-            "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-            "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/mime-types": {
-            "version": "2.1.35",
-            "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
-            "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-            "license": "MIT",
-            "dependencies": {
-                "mime-db": "1.52.0"
-            },
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/minimatch": {
-            "version": "3.1.2",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-            "license": "ISC",
-            "dependencies": {
-                "brace-expansion": "^1.1.7"
-            },
-            "engines": {
-                "node": "*"
-            }
-        },
-        "node_modules/minimist": {
-            "version": "1.2.7",
-            "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
-            "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
-            "dev": true,
-            "license": "MIT",
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/moment": {
-            "version": "2.29.4",
-            "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
-            "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
-            "license": "MIT",
-            "engines": {
-                "node": "*"
-            }
-        },
-        "node_modules/moment-timezone": {
-            "version": "0.5.40",
-            "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz",
-            "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==",
-            "license": "MIT",
-            "dependencies": {
-                "moment": ">= 2.9.0"
-            },
-            "engines": {
-                "node": "*"
-            }
-        },
-        "node_modules/mongodb": {
-            "version": "4.13.0",
-            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.13.0.tgz",
-            "integrity": "sha512-+taZ/bV8d1pYuHL4U+gSwkhmDrwkWbH1l4aah4YpmpscMwgFBkufIKxgP/G7m87/NUuQzc2Z75ZTI7ZOyqZLbw==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "bson": "^4.7.0",
-                "mongodb-connection-string-url": "^2.5.4",
-                "socks": "^2.7.1"
-            },
-            "engines": {
-                "node": ">=12.9.0"
-            },
-            "optionalDependencies": {
-                "@aws-sdk/credential-providers": "^3.186.0",
-                "saslprep": "^1.0.3"
-            }
-        },
-        "node_modules/mongodb-connection-string-url": {
-            "version": "2.6.0",
-            "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
-            "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
-            "license": "Apache-2.0",
-            "dependencies": {
-                "@types/whatwg-url": "^8.2.1",
-                "whatwg-url": "^11.0.0"
-            }
-        },
-        "node_modules/ms": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-            "license": "MIT"
-        },
-        "node_modules/natural-compare": {
-            "version": "1.4.0",
-            "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-            "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
-            "license": "MIT"
-        },
-        "node_modules/natural-compare-lite": {
-            "version": "1.4.0",
-            "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
-            "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/negotiator": {
-            "version": "0.6.3",
-            "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
-            "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/noda": {
-            "version": "0.6.0",
-            "resolved": "https://registry.npmjs.org/noda/-/noda-0.6.0.tgz",
-            "integrity": "sha512-Zj3eTQ1cL83zGvorxbGmeqNnt/h+2nH3jT/XLI2oXHL9LH6IKoPvFh6feT1e/yFhRRByP3Q+waM+2dcXIdZkqg==",
-            "license": "ISC",
-            "engines": {
-                "node": ">=6"
-            }
-        },
-        "node_modules/node-cron": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz",
-            "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==",
-            "license": "ISC",
-            "dependencies": {
-                "uuid": "8.3.2"
-            },
-            "engines": {
-                "node": ">=6.0.0"
-            }
-        },
-        "node_modules/node-domexception": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
-            "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/jimmywarting"
-                },
-                {
-                    "type": "github",
-                    "url": "https://paypal.me/jimmywarting"
-                }
-            ],
-            "license": "MIT",
-            "engines": {
-                "node": ">=10.5.0"
-            }
-        },
-        "node_modules/node-fetch": {
-            "version": "3.3.0",
-            "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz",
-            "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==",
-            "license": "MIT",
-            "dependencies": {
-                "data-uri-to-buffer": "^4.0.0",
-                "fetch-blob": "^3.1.4",
-                "formdata-polyfill": "^4.0.10"
-            },
-            "engines": {
-                "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/node-fetch"
-            }
-        },
-        "node_modules/node-tesseract-ocr": {
-            "version": "2.2.1",
-            "resolved": "https://registry.npmjs.org/node-tesseract-ocr/-/node-tesseract-ocr-2.2.1.tgz",
-            "integrity": "sha512-Q9cD79JGpPNQBxbi1fV+OAsTxYKLpx22sagsxSyKbu1u+t6UarApf5m32uVc8a5QAP1Wk7fIPN0aJFGGEE9DyQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=10"
-            }
-        },
-        "node_modules/numbered": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz",
-            "integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==",
-            "license": "MIT"
-        },
-        "node_modules/object-inspect": {
-            "version": "1.12.3",
-            "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
-            "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
-            "license": "MIT",
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/object-keys": {
-            "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
-            "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
-        "node_modules/on-finished": {
-            "version": "2.4.1",
-            "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
-            "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
-            "license": "MIT",
-            "dependencies": {
-                "ee-first": "1.1.1"
-            },
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/once": {
-            "version": "1.4.0",
-            "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-            "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-            "license": "ISC",
-            "dependencies": {
-                "wrappy": "1"
-            }
-        },
-        "node_modules/optionator": {
-            "version": "0.9.1",
-            "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
-            "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
-            "license": "MIT",
-            "dependencies": {
-                "deep-is": "^0.1.3",
-                "fast-levenshtein": "^2.0.6",
-                "levn": "^0.4.1",
-                "prelude-ls": "^1.2.1",
-                "type-check": "^0.4.0",
-                "word-wrap": "^1.2.3"
-            },
-            "engines": {
-                "node": ">= 0.8.0"
-            }
-        },
-        "node_modules/p-limit": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
-            "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
-            "license": "MIT",
-            "dependencies": {
-                "yocto-queue": "^0.1.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/p-locate": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
-            "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
-            "license": "MIT",
-            "dependencies": {
-                "p-limit": "^3.0.2"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/parent-module": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
-            "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-            "license": "MIT",
-            "dependencies": {
-                "callsites": "^3.0.0"
-            },
-            "engines": {
-                "node": ">=6"
-            }
-        },
-        "node_modules/parseurl": {
-            "version": "1.3.3",
-            "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
-            "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/pastebin-api": {
-            "version": "5.1.5",
-            "resolved": "https://registry.npmjs.org/pastebin-api/-/pastebin-api-5.1.5.tgz",
-            "integrity": "sha512-MdRsE8crPA5y4M9IkLcohmU+xeLnUxMj091L5LR+Ywvp3s8bPYu7s7G1BEsvTxaJoLrgC/uPs1MgDliyqxMcNA==",
-            "license": "MIT",
-            "dependencies": {
-                "fast-xml-parser": "^4.0.10",
-                "undici": "^5.10.0"
-            },
-            "engines": {
-                "node": ">=14"
-            }
-        },
-        "node_modules/path-exists": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-            "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/path-is-absolute": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-            "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/path-key": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
-            "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/path-to-regexp": {
-            "version": "0.1.7",
-            "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-            "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
-            "license": "MIT"
-        },
-        "node_modules/path-type": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
-            "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/peek-readable": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz",
-            "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=14.16"
-            },
-            "funding": {
-                "type": "github",
-                "url": "https://github.com/sponsors/Borewit"
-            }
-        },
-        "node_modules/picomatch": {
-            "version": "2.3.1",
-            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-            "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=8.6"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/jonschlinkert"
-            }
-        },
-        "node_modules/prelude-ls": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
-            "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8.0"
-            }
-        },
-        "node_modules/prettier": {
-            "version": "2.8.3",
-            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
-            "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
-            "dev": true,
-            "license": "MIT",
-            "bin": {
-                "prettier": "bin-prettier.js"
-            },
-            "engines": {
-                "node": ">=10.13.0"
-            },
-            "funding": {
-                "url": "https://github.com/prettier/prettier?sponsor=1"
-            }
-        },
-        "node_modules/prettier-eslint": {
-            "version": "15.0.1",
-            "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-15.0.1.tgz",
-            "integrity": "sha512-mGOWVHixSvpZWARqSDXbdtTL54mMBxc5oQYQ6RAqy8jecuNJBgN3t9E5a81G66F8x8fsKNiR1HWaBV66MJDOpg==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "@types/eslint": "^8.4.2",
-                "@types/prettier": "^2.6.0",
-                "@typescript-eslint/parser": "^5.10.0",
-                "common-tags": "^1.4.0",
-                "dlv": "^1.1.0",
-                "eslint": "^8.7.0",
-                "indent-string": "^4.0.0",
-                "lodash.merge": "^4.6.0",
-                "loglevel-colored-level-prefix": "^1.0.0",
-                "prettier": "^2.5.1",
-                "pretty-format": "^23.0.1",
-                "require-relative": "^0.8.7",
-                "typescript": "^4.5.4",
-                "vue-eslint-parser": "^8.0.1"
-            },
-            "engines": {
-                "node": ">=10.0.0"
-            }
-        },
-        "node_modules/prettier-eslint/node_modules/typescript": {
-            "version": "4.9.4",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
-            "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
-            "dev": true,
-            "license": "Apache-2.0",
-            "bin": {
-                "tsc": "bin/tsc",
-                "tsserver": "bin/tsserver"
-            },
-            "engines": {
-                "node": ">=4.2.0"
-            }
-        },
-        "node_modules/pretty-format": {
-            "version": "23.6.0",
-            "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
-            "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "ansi-regex": "^3.0.0",
-                "ansi-styles": "^3.2.0"
-            }
-        },
-        "node_modules/pretty-format/node_modules/ansi-regex": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
-            "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=4"
-            }
-        },
-        "node_modules/pretty-format/node_modules/ansi-styles": {
-            "version": "3.2.1",
-            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-            "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "color-convert": "^1.9.0"
-            },
-            "engines": {
-                "node": ">=4"
-            }
-        },
-        "node_modules/proxy-addr": {
-            "version": "2.0.7",
-            "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
-            "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
-            "license": "MIT",
-            "dependencies": {
-                "forwarded": "0.2.0",
-                "ipaddr.js": "1.9.1"
-            },
-            "engines": {
-                "node": ">= 0.10"
-            }
-        },
-        "node_modules/punycode": {
-            "version": "2.2.0",
-            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz",
-            "integrity": "sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=6"
-            }
-        },
-        "node_modules/qir": {
-            "version": "0.1.0",
-            "resolved": "https://registry.npmjs.org/qir/-/qir-0.1.0.tgz",
-            "integrity": "sha512-LJUKDMyPsaog5uDarzqterz+SfdyA3mfvq45kSLV0X5IOSPy/nlueKvLg8nClOSKp57c1E0bU/BFHwHFUoVhcw==",
-            "license": "ISC",
-            "dependencies": {
-                "noda": "^0.6.0"
-            }
-        },
-        "node_modules/qs": {
-            "version": "6.11.0",
-            "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-            "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-            "license": "BSD-3-Clause",
-            "dependencies": {
-                "side-channel": "^1.0.4"
-            },
-            "engines": {
-                "node": ">=0.6"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/queue-microtask": {
-            "version": "1.2.3",
-            "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
-            "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "license": "MIT"
-        },
-        "node_modules/range-parser": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
-            "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/raw-body": {
-            "version": "2.5.1",
-            "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
-            "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
-            "license": "MIT",
-            "dependencies": {
-                "bytes": "3.1.2",
-                "http-errors": "2.0.0",
-                "iconv-lite": "0.4.24",
-                "unpipe": "1.0.0"
-            },
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/readable-stream": {
-            "version": "3.6.0",
-            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
-            "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
-            "license": "MIT",
-            "dependencies": {
-                "inherits": "^2.0.3",
-                "string_decoder": "^1.1.1",
-                "util-deprecate": "^1.0.1"
-            },
-            "engines": {
-                "node": ">= 6"
-            }
-        },
-        "node_modules/readable-web-to-node-stream": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
-            "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
-            "license": "MIT",
-            "dependencies": {
-                "readable-stream": "^3.6.0"
-            },
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "type": "github",
-                "url": "https://github.com/sponsors/Borewit"
-            }
-        },
-        "node_modules/regexpp": {
-            "version": "3.2.0",
-            "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
-            "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/mysticatea"
-            }
-        },
-        "node_modules/require-relative": {
-            "version": "0.8.7",
-            "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
-            "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/resolve-from": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
-            "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=4"
-            }
-        },
-        "node_modules/reusify": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
-            "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
-            "license": "MIT",
-            "engines": {
-                "iojs": ">=1.0.0",
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/rimraf": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-            "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-            "license": "ISC",
-            "dependencies": {
-                "glob": "^7.1.3"
-            },
-            "bin": {
-                "rimraf": "bin.js"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/isaacs"
-            }
-        },
-        "node_modules/run-parallel": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
-            "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "license": "MIT",
-            "dependencies": {
-                "queue-microtask": "^1.2.2"
-            }
-        },
-        "node_modules/safe-buffer": {
-            "version": "5.2.1",
-            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-            "funding": [
-                {
-                    "type": "github",
-                    "url": "https://github.com/sponsors/feross"
-                },
-                {
-                    "type": "patreon",
-                    "url": "https://www.patreon.com/feross"
-                },
-                {
-                    "type": "consulting",
-                    "url": "https://feross.org/support"
-                }
-            ],
-            "license": "MIT"
-        },
-        "node_modules/safer-buffer": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-            "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-            "license": "MIT"
-        },
-        "node_modules/saslprep": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
-            "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
-            "license": "MIT",
-            "optional": true,
-            "dependencies": {
-                "sparse-bitfield": "^3.0.3"
-            },
-            "engines": {
-                "node": ">=6"
-            }
-        },
-        "node_modules/semver": {
-            "version": "7.3.8",
-            "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-            "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
-            "dev": true,
-            "license": "ISC",
-            "dependencies": {
-                "lru-cache": "^6.0.0"
-            },
-            "bin": {
-                "semver": "bin/semver.js"
-            },
-            "engines": {
-                "node": ">=10"
-            }
-        },
-        "node_modules/send": {
-            "version": "0.18.0",
-            "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
-            "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
-            "license": "MIT",
-            "dependencies": {
-                "debug": "2.6.9",
-                "depd": "2.0.0",
-                "destroy": "1.2.0",
-                "encodeurl": "~1.0.2",
-                "escape-html": "~1.0.3",
-                "etag": "~1.8.1",
-                "fresh": "0.5.2",
-                "http-errors": "2.0.0",
-                "mime": "1.6.0",
-                "ms": "2.1.3",
-                "on-finished": "2.4.1",
-                "range-parser": "~1.2.1",
-                "statuses": "2.0.1"
-            },
-            "engines": {
-                "node": ">= 0.8.0"
-            }
-        },
-        "node_modules/send/node_modules/debug": {
-            "version": "2.6.9",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-            "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-            "license": "MIT",
-            "dependencies": {
-                "ms": "2.0.0"
-            }
-        },
-        "node_modules/send/node_modules/debug/node_modules/ms": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-            "license": "MIT"
-        },
-        "node_modules/send/node_modules/ms": {
-            "version": "2.1.3",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-            "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-            "license": "MIT"
-        },
-        "node_modules/serve-static": {
-            "version": "1.15.0",
-            "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
-            "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
-            "license": "MIT",
-            "dependencies": {
-                "encodeurl": "~1.0.2",
-                "escape-html": "~1.0.3",
-                "parseurl": "~1.3.3",
-                "send": "0.18.0"
-            },
-            "engines": {
-                "node": ">= 0.8.0"
-            }
-        },
-        "node_modules/setprototypeof": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
-            "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
-            "license": "ISC"
-        },
-        "node_modules/shebang-command": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
-            "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
-            "license": "MIT",
-            "dependencies": {
-                "shebang-regex": "^3.0.0"
-            },
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/shebang-regex": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
-            "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/side-channel": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
-            "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
-            "license": "MIT",
-            "dependencies": {
-                "call-bind": "^1.0.0",
-                "get-intrinsic": "^1.0.2",
-                "object-inspect": "^1.9.0"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
-        "node_modules/slash": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
-            "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
-            "dev": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/smart-buffer": {
-            "version": "4.2.0",
-            "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
-            "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 6.0.0",
-                "npm": ">= 3.0.0"
-            }
-        },
-        "node_modules/socks": {
-            "version": "2.7.1",
-            "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
-            "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
-            "license": "MIT",
-            "dependencies": {
-                "ip": "^2.0.0",
-                "smart-buffer": "^4.2.0"
-            },
-            "engines": {
-                "node": ">= 10.13.0",
-                "npm": ">= 3.0.0"
-            }
-        },
-        "node_modules/sparse-bitfield": {
-            "version": "3.0.3",
-            "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
-            "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
-            "license": "MIT",
-            "optional": true,
-            "dependencies": {
-                "memory-pager": "^1.0.2"
-            }
-        },
-        "node_modules/statuses": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
-            "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/streamsearch": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
-            "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
-            "engines": {
-                "node": ">=10.0.0"
-            }
-        },
-        "node_modules/string_decoder": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
-            "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-            "license": "MIT",
-            "dependencies": {
-                "safe-buffer": "~5.2.0"
-            }
-        },
-        "node_modules/strip-ansi": {
-            "version": "6.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-            "license": "MIT",
-            "dependencies": {
-                "ansi-regex": "^5.0.1"
-            },
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/strip-ansi/node_modules/ansi-regex": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/strip-json-comments": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
-            "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/strnum": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
-            "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
-            "license": "MIT"
-        },
-        "node_modules/strtok3": {
-            "version": "7.0.0",
-            "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz",
-            "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==",
-            "license": "MIT",
-            "dependencies": {
-                "@tokenizer/token": "^0.3.0",
-                "peek-readable": "^5.0.0"
-            },
-            "engines": {
-                "node": ">=14.16"
-            },
-            "funding": {
-                "type": "github",
-                "url": "https://github.com/sponsors/Borewit"
-            }
-        },
-        "node_modules/structured-clone": {
-            "version": "0.2.2",
-            "resolved": "https://registry.npmjs.org/structured-clone/-/structured-clone-0.2.2.tgz",
-            "integrity": "sha512-SucNWVxwmfAjWrzQ9Xsuv4JIDtS/Qpx+MwZD2NEx2CeMpf3hgqvWKssll34trTu6M7ywd7WZDDKO8hhq0SZiAA==",
-            "license": "MIT"
-        },
-        "node_modules/supports-color": {
-            "version": "7.2.0",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-            "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-            "license": "MIT",
-            "dependencies": {
-                "has-flag": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/systeminformation": {
-            "version": "5.17.3",
-            "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.17.3.tgz",
-            "integrity": "sha512-IAmnUJdeFUWqY+YneAWJ9rceTdRRIaTiwspvd1B6SG7yhqpxLrSosHgGZKiE8lcaBlBYpLQpY3BRLtus4n8PNQ==",
-            "license": "MIT",
-            "os": [
-                "darwin",
-                "linux",
-                "win32",
-                "freebsd",
-                "openbsd",
-                "netbsd",
-                "sunos",
-                "android"
-            ],
-            "bin": {
-                "systeminformation": "lib/cli.js"
-            },
-            "engines": {
-                "node": ">=8.0.0"
-            },
-            "funding": {
-                "type": "Buy me a coffee",
-                "url": "https://www.buymeacoffee.com/systeminfo"
-            }
-        },
-        "node_modules/text-table": {
-            "version": "0.2.0",
-            "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-            "license": "MIT"
-        },
-        "node_modules/to-regex-range": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-            "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "is-number": "^7.0.0"
-            },
-            "engines": {
-                "node": ">=8.0"
-            }
-        },
-        "node_modules/toidentifier": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
-            "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.6"
-            }
-        },
-        "node_modules/token-types": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz",
-            "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==",
-            "license": "MIT",
-            "dependencies": {
-                "@tokenizer/token": "^0.3.0",
-                "ieee754": "^1.2.1"
-            },
-            "engines": {
-                "node": ">=14.16"
-            },
-            "funding": {
-                "type": "github",
-                "url": "https://github.com/sponsors/Borewit"
-            }
-        },
-        "node_modules/tr46": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
-            "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
-            "license": "MIT",
-            "dependencies": {
-                "punycode": "^2.1.1"
-            },
-            "engines": {
-                "node": ">=12"
-            }
-        },
-        "node_modules/ts-mixer": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.2.tgz",
-            "integrity": "sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A==",
-            "license": "MIT"
-        },
-        "node_modules/tsc-suppress": {
-            "version": "1.0.7",
-            "resolved": "https://registry.npmjs.org/tsc-suppress/-/tsc-suppress-1.0.7.tgz",
-            "integrity": "sha512-keT8/tFADvf1nc9CGxvMEfkfCdKp5aF2t1v9GaCRtNegljAtk1Kv0C3KLBFrZTaptgB4OXUF6QkE3eMqy6+VNg==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "chalk": "^4",
-                "minimist": "^1.2.5"
-            },
-            "bin": {
-                "tsc-suppress": "bin/tsc-suppress.js"
-            },
-            "engines": {
-                "node": ">=6.0.0"
-            },
-            "peerDependencies": {
-                "typescript": ">=3.0.0"
-            }
-        },
-        "node_modules/tsc-suppress/node_modules/ansi-styles": {
-            "version": "4.3.0",
-            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "color-convert": "^2.0.1"
-            },
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-            }
-        },
-        "node_modules/tsc-suppress/node_modules/chalk": {
-            "version": "4.1.2",
-            "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-            "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "ansi-styles": "^4.1.0",
-                "supports-color": "^7.1.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/chalk?sponsor=1"
-            }
-        },
-        "node_modules/tsc-suppress/node_modules/color-convert": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "color-name": "~1.1.4"
-            },
-            "engines": {
-                "node": ">=7.0.0"
-            }
-        },
-        "node_modules/tsc-suppress/node_modules/color-name": {
-            "version": "1.1.4",
-            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-            "dev": true,
-            "license": "MIT"
-        },
-        "node_modules/tslib": {
-            "version": "2.4.1",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
-            "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==",
-            "license": "0BSD"
-        },
-        "node_modules/type-check": {
-            "version": "0.4.0",
-            "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
-            "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
-            "license": "MIT",
-            "dependencies": {
-                "prelude-ls": "^1.2.1"
-            },
-            "engines": {
-                "node": ">= 0.8.0"
-            }
-        },
-        "node_modules/type-fest": {
-            "version": "0.20.2",
-            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-            "license": "(MIT OR CC0-1.0)",
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
-        "node_modules/type-is": {
-            "version": "1.6.18",
-            "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
-            "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
-            "license": "MIT",
-            "dependencies": {
-                "media-typer": "0.3.0",
-                "mime-types": "~2.1.24"
-            },
-            "engines": {
-                "node": ">= 0.6"
-            }
-        },
-        "node_modules/typescript": {
-            "version": "5.0.0-dev.20230118",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.0-dev.20230118.tgz",
-            "integrity": "sha512-mLhr9b2PSXo21+f210MSRD3EOdsrOg9NTWghkJNDaY0K7iWVK8E5FsflAsRzi+Rn/CsO7tH3pyl0LeGwVX25Cg==",
-            "bin": {
-                "tsc": "bin/tsc",
-                "tsserver": "bin/tsserver"
-            },
-            "engines": {
-                "node": ">=4.2.0"
-            }
-        },
-        "node_modules/undici": {
-            "version": "5.15.0",
-            "resolved": "https://registry.npmjs.org/undici/-/undici-5.15.0.tgz",
-            "integrity": "sha512-wCAZJDyjw9Myv+Ay62LAoB+hZLPW9SmKbQkbHIhMw/acKSlpn7WohdMUc/Vd4j1iSMBO0hWwU8mjB7a5p5bl8g==",
-            "license": "MIT",
-            "dependencies": {
-                "busboy": "^1.6.0"
-            },
-            "engines": {
-                "node": ">=12.18"
-            }
-        },
-        "node_modules/unpipe": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
-            "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/uri-js": {
-            "version": "4.4.1",
-            "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
-            "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
-            "license": "BSD-2-Clause",
-            "dependencies": {
-                "punycode": "^2.1.0"
-            }
-        },
-        "node_modules/util-deprecate": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-            "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
-            "license": "MIT"
-        },
-        "node_modules/utils-merge": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
-            "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.4.0"
-            }
-        },
-        "node_modules/uuid": {
-            "version": "8.3.2",
-            "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-            "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-            "license": "MIT",
-            "bin": {
-                "uuid": "dist/bin/uuid"
-            }
-        },
-        "node_modules/vary": {
-            "version": "1.1.2",
-            "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
-            "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 0.8"
-            }
-        },
-        "node_modules/vue-eslint-parser": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
-            "integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==",
-            "dev": true,
-            "license": "MIT",
-            "dependencies": {
-                "debug": "^4.3.2",
-                "eslint-scope": "^7.0.0",
-                "eslint-visitor-keys": "^3.1.0",
-                "espree": "^9.0.0",
-                "esquery": "^1.4.0",
-                "lodash": "^4.17.21",
-                "semver": "^7.3.5"
-            },
-            "engines": {
-                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/mysticatea"
-            },
-            "peerDependencies": {
-                "eslint": ">=6.0.0"
-            }
-        },
-        "node_modules/web-streams-polyfill": {
-            "version": "3.2.1",
-            "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
-            "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
-            "license": "MIT",
-            "engines": {
-                "node": ">= 8"
-            }
-        },
-        "node_modules/webidl-conversions": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-            "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
-            "license": "BSD-2-Clause"
-        },
-        "node_modules/whatwg-url": {
-            "version": "11.0.0",
-            "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
-            "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
-            "license": "MIT",
-            "dependencies": {
-                "tr46": "^3.0.0",
-                "webidl-conversions": "^7.0.0"
-            },
-            "engines": {
-                "node": ">=12"
-            }
-        },
-        "node_modules/whatwg-url/node_modules/webidl-conversions": {
-            "version": "7.0.0",
-            "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
-            "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
-            "license": "BSD-2-Clause",
-            "engines": {
-                "node": ">=12"
-            }
-        },
-        "node_modules/which": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
-            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
-            "license": "ISC",
-            "dependencies": {
-                "isexe": "^2.0.0"
-            },
-            "bin": {
-                "node-which": "bin/node-which"
-            },
-            "engines": {
-                "node": ">= 8"
-            }
-        },
-        "node_modules/word-wrap": {
-            "version": "1.2.3",
-            "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-            "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/wrappy": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-            "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
-            "license": "ISC"
-        },
-        "node_modules/ws": {
-            "version": "8.12.0",
-            "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz",
-            "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=10.0.0"
-            },
-            "peerDependencies": {
-                "bufferutil": "^4.0.1",
-                "utf-8-validate": ">=5.0.2"
-            },
-            "peerDependenciesMeta": {
-                "bufferutil": {
-                    "optional": true
-                },
-                "utf-8-validate": {
-                    "optional": true
-                }
-            }
-        },
-        "node_modules/yallist": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-            "dev": true,
-            "license": "ISC"
-        },
-        "node_modules/yocto-queue": {
-            "version": "0.1.0",
-            "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
-            "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
-            "license": "MIT",
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        }
-    }
-}
diff --git a/package.json b/package.json
index 8110b8c..f28878f 100644
--- a/package.json
+++ b/package.json
@@ -3,29 +3,24 @@
         "@discordjs/rest": "^0.2.0-canary.0",
         "@hokify/agenda": "^6.2.12",
         "@tsconfig/node18-strictest-esm": "^1.0.0",
-        "@types/node-cron": "^3.0.1",
         "@ungap/structured-clone": "^1.0.1",
         "agenda": "^4.3.0",
-        "ansi-styles": "^6.1.0",
         "body-parser": "^1.20.0",
-        "chalk": "^5.0.0",
-        "deno": "^0.1.1",
-        "discord.js": "14.7.1",
+        "discord.js": "^14.7.1",
         "eslint": "^8.21.0",
         "express": "^4.18.1",
-        "form-data": "^4.0.0",
         "fuse.js": "^6.6.2",
         "humanize-duration": "^3.27.1",
         "immutable": "^4.1.0",
+        "lodash": "^4.17.21",
         "mongodb": "^4.7.0",
-        "node-cron": "^3.0.0",
         "node-fetch": "^3.3.0",
         "node-tesseract-ocr": "^2.2.1",
-        "pastebin-api": "^5.1.1",
         "structured-clone": "^0.2.2",
-        "systeminformation": "^5.17.3",
-        "typescript": "^5.0.0-dev.20230102",
-        "uuid": "^8.3.2"
+        "systeminformation": "^5.17.3"
+        },
+    "resolutions": {
+        "discord-api-types": "0.37.23"
     },
     "name": "nucleus",
     "version": "0.0.1",
@@ -59,11 +54,13 @@
     "private": false,
     "type": "module",
     "devDependencies": {
+        "@types/lodash": "^4.14.191",
         "@typescript-eslint/eslint-plugin": "^5.32.0",
         "@typescript-eslint/parser": "^5.32.0",
         "eslint-config-prettier": "^8.5.0",
         "prettier": "^2.7.1",
         "prettier-eslint": "^15.0.1",
-        "tsc-suppress": "^1.0.7"
+        "tsc-suppress": "^1.0.7",
+        "typescript": "^4.9.4"
     }
 }
diff --git a/src/Unfinished/all.ts b/src/Unfinished/all.ts
index 9d9e653..eea33f5 100644
--- a/src/Unfinished/all.ts
+++ b/src/Unfinished/all.ts
@@ -5,16 +5,18 @@
     ActionRowBuilder,
     ButtonBuilder,
     SelectMenuBuilder,
-    ButtonStyle
+    ButtonStyle,
+    StringSelectMenuBuilder,
+    APIMessageComponentEmoji
 } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { 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";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder // TODO: DON'T RELEASE THIS
+    builder
         .setName("all")
         .setDescription("Gives or removes a role from everyone");
 
@@ -171,8 +173,8 @@
     const all = true;
     while (true) {
         let count = 0;
-        const affected = [];
-        const members = interaction.guild.members.cache;
+        const affected: GuildMember[] = [];
+        const members = interaction.guild!.members.cache;
         if (all) {
             members.forEach((member) => {
                 let applies = true;
@@ -224,8 +226,8 @@
                     .setStatus("Success")
             ],
             components: [
-                new ActionRowBuilder().addComponents([
-                    new SelectMenuBuilder()
+                new ActionRowBuilder<StringSelectMenuBuilder>().addComponents([
+                    new StringSelectMenuBuilder()
                         .setOptions(
                             filters.map((f, index) => ({
                                 label: (f.inverted ? "(Not) " : "") + f.name,
@@ -237,18 +239,18 @@
                         .setCustomId("select")
                         .setPlaceholder("Remove a filter")
                 ]),
-                new ActionRowBuilder().addComponents([
+                new ActionRowBuilder<ButtonBuilder>().addComponents([
                     new ButtonBuilder()
                         .setLabel("Apply")
                         .setStyle(ButtonStyle.Primary)
                         .setCustomId("apply")
-                        .setEmoji(client.emojis.cache.get(getEmojiByName("CONTROL.TICK", "id")))
+                        .setEmoji(client.emojis.cache.get(getEmojiByName("CONTROL.TICK", "id"))! as APIMessageComponentEmoji)
                         .setDisabled(affected.length === 0),
                     new ButtonBuilder()
                         .setLabel("Add filter")
                         .setStyle(ButtonStyle.Primary)
                         .setCustomId("add")
-                        .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.FILTER", "id")))
+                        .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.FILTER", "id"))! as APIMessageComponentEmoji)
                         .setDisabled(filters.length >= 25)
                 ])
             ]
@@ -260,12 +262,12 @@
 
 const check = async (interaction: CommandInteraction) => {
     const member = interaction.member as GuildMember;
-    const me = interaction.guild.me!;
-    if (!me.permissions.has("MANAGE_ROLES")) throw new Error("I do not have the *Manage Roles* permission");
+    const me = interaction.guild!.members.me!;
+    if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission";
     // Allow the owner to role anyone
-    if (member.id === interaction.guild.ownerId) return true;
+    if (member.id === interaction.guild!.ownerId) return true;
     // Check if the user has manage_roles permission
-    if (!member.permissions.has("MANAGE_ROLES")) throw new Error("You do not have the *Manage Roles* permission");
+    if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
     // Allow role
     return true;
 };
diff --git a/src/Unfinished/categorisationTest.ts b/src/Unfinished/categorizationTest.ts
similarity index 72%
rename from src/Unfinished/categorisationTest.ts
rename to src/Unfinished/categorizationTest.ts
index dc38dfe..ff2d66b 100644
--- a/src/Unfinished/categorisationTest.ts
+++ b/src/Unfinished/categorizationTest.ts
@@ -1,28 +1,30 @@
 import { LoadingEmbed } from "../utils/defaults.js";
-import { CommandInteraction, GuildChannel, ActionRowBuilder, ButtonBuilder, SelectMenuBuilder, ButtonStyle } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import { CommandInteraction, GuildChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, StringSelectMenuBuilder, APIMessageComponentEmoji } from "discord.js";
+import { SlashCommandBuilder } from "discord.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import client from "../utils/client.js";
 import addPlural from "../utils/plurals.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
 
-const command = new SlashCommandBuilder().setName("categorise").setDescription("Categorises your servers channels");
+const command = new SlashCommandBuilder().setName("categorize").setDescription("Categorizes your servers channels");
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const channels = interaction.guild.channels.cache.filter((c) => c.type !== "GUILD_CATEGORY");
-    const categorised = {};
+    const channels = interaction.guild!.channels.cache.filter((c) => c.type !== ChannelType.GuildCategory);
+    const categorized = {};
     await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
     const predicted = {};
     const types = {
-        general: ["general", "muted", "main", "topic", "discuss"],
+        important: ["rule", "announcement", "alert", "info"],
+        general: ["general", "main", "topic", "discuss"],
         commands: ["bot", "command", "music"],
-        images: ["pic", "selfies", "image"],
-        nsfw: ["porn", "nsfw", "sex"],
-        links: ["links"],
-        advertising: ["ads", "advert", "server", "partner"],
-        staff: ["staff", "mod", "admin"],
-        spam: ["spam"],
-        other: ["random"]
+        images: ["pic", "selfies", "image", "gallery", "meme", "media"],
+        nsfw: ["porn", "nsfw", "sex", "lewd", "fetish"],
+        links: ["link"],
+        advertising: ["ads", "advert", "partner", "bump"],
+        staff: ["staff", "mod", "admin", "helper", "train"],
+        spam: ["spam", "count"],
+        logs: ["log"],
+        other: ["random", "starboard"],
     };
     for (const c of channels.values()) {
         for (const type in types) {
@@ -38,14 +40,14 @@
     for (const c of channels) {
         // convert channel to a channel if its a string
         let channel: string | GuildChannel;
-        if (typeof c === "string") channel = interaction.guild.channels.cache.get(channel as string).id;
+        if (typeof c === "string") channel = interaction.guild!.channels.cache.get(c as string)!.id;
         else channel = (c[0] as unknown as GuildChannel).id;
         console.log(channel);
         if (!predicted[channel]) predicted[channel] = [];
         m = await interaction.editReply({
             embeds: [
                 new EmojiEmbed()
-                    .setTitle("Categorise")
+                    .setTitle("Categorize")
                     .setDescription(
                         `Select all types that apply to <#${channel}>.\n\n` +
                             `${addPlural(predicted[channel].length, "Suggestion")}: ${predicted[channel].join(", ")}`
@@ -54,8 +56,8 @@
                     .setStatus("Success")
             ],
             components: [
-                new ActionRowBuilder().addComponents([
-                    new SelectMenuBuilder()
+                new ActionRowBuilder<StringSelectMenuBuilder>().addComponents([
+                    new StringSelectMenuBuilder()
                         .setCustomId("selected")
                         .setMaxValues(Object.keys(types).length)
                         .setMinValues(1)
@@ -67,18 +69,18 @@
                             }))
                         )
                 ]),
-                new ActionRowBuilder().addComponents([
+                new ActionRowBuilder<ButtonBuilder>().addComponents([
                     new ButtonBuilder()
                         .setLabel("Accept Suggestion")
                         .setCustomId("accept")
                         .setStyle(ButtonStyle.Success)
                         .setDisabled(predicted[channel].length === 0)
-                        .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.TICK", "id"))),
+                        .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.TICK", "id")) as APIMessageComponentEmoji),
                     new ButtonBuilder()
                         .setLabel('Use "Other"')
                         .setCustomId("reject")
                         .setStyle(ButtonStyle.Secondary)
-                        .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.CROSS", "id")))
+                        .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.CROSS", "id")) as APIMessageComponentEmoji)
                 ])
             ]
         });
@@ -86,13 +88,13 @@
         try {
             i = 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 await interaction.editReply({
                 embeds: [
                     new EmojiEmbed()
-                        .setTitle("Categorise")
+                        .setTitle("Categorize")
                         .setEmoji("CHANNEL.CATEGORY.DELETE")
                         .setStatus("Danger")
                         .setDescription(
@@ -105,7 +107,7 @@
                 ]
             });
         }
-        i.deferUpdate();
+        await i.deferUpdate();
         let selected;
         if (i.customId === "select") {
             selected = i.values;
@@ -116,9 +118,9 @@
         if (i.customId === "reject") {
             selected = ["other"];
         }
-        categorised[channel] = selected;
+        categorized[channel] = selected;
     }
-    console.log(categorised);
+    console.log(categorized);
 };
 
 const check = () => {
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 };
diff --git a/src/api/index.ts b/src/api/index.ts
index c24327d..9676194 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -57,7 +57,7 @@
                         calculateType: "guildMemberVerify",
                         color: NucleusColors.green,
                         emoji: "CONTROL.BLOCKTICK",
-                        timestamp: new Date().getTime()
+                        timestamp: Date.now()
                     },
                     list: {
                         member: entry(member.id, renderUser(member.user)),
@@ -149,6 +149,49 @@
         return res.sendStatus(404);
     });
 
+    app.get("/transcript/:code/human", jsonParser, async function (req: express.Request, res: express.Response) {
+        const code = req.params.code;
+        if (code === undefined) return res.status(400).send("No code provided");
+        const entry = await client.database.transcripts.read(code);
+        if (entry === null) return res.status(404).send("Could not find a transcript by that code");
+        // Convert to a human readable format
+        const data = client.database.transcripts.toHumanReadable(entry);
+        res.attachment(`${code}.txt`);
+        res.type("txt");
+        return res.status(200).send(data);
+    });
+
+    app.get("/transcript/:code", jsonParser, async function (req: express.Request, res: express.Response) {
+        const code = req.params.code;
+        if (code === undefined) return res.status(400).send("No code provided");
+        const entry = await client.database.transcripts.read(code);
+        if (entry === null) return res.status(404).send("Could not find a transcript by that code");
+        // Convert to a human readable format
+        return res.status(200).send(entry);
+    });
+
+    app.get("/channels/:id", jsonParser, async function (req: express.Request, res: express.Response) {
+        const id = req.params.id;
+        if (id === undefined) return res.status(400).send("No id provided");
+        const channel = await client.channels.fetch(id);
+        if (channel === null) return res.status(404).send("Could not find a channel by that id");
+        if (channel.isDMBased()) return res.status(400).send("Cannot get a DM channel");
+        return res.status(200).send(channel.name);
+    });
+
+    app.get("/users/:id", jsonParser, async function (req: express.Request, res: express.Response) {
+        const id = req.params.id;
+        if (id === undefined) return res.status(400).send("No id provided");
+        let user;
+        try {
+            user = await client.users.fetch(id);
+        } catch (e) {
+            console.log(e)
+            return res.status(404).send("Could not find a user by that id");
+        }
+        return res.status(200).send(user.username);
+    });
+
     app.listen(port);
 };
 
diff --git a/src/commands/help.ts b/src/commands/help.ts
index 767ca46..90ef133 100644
--- a/src/commands/help.ts
+++ b/src/commands/help.ts
@@ -1,23 +1,195 @@
-import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import {
+    ActionRowBuilder,
+    CommandInteraction,
+    StringSelectMenuBuilder,
+    ApplicationCommandOptionType,
+    ApplicationCommandType,
+    StringSelectMenuOptionBuilder,
+    SlashCommandBuilder,
+    StringSelectMenuInteraction,
+    ComponentType,
+    APIMessageComponentEmoji,
+    ApplicationCommandSubGroup,
+    PermissionsBitField,
+    Interaction,
+    ApplicationCommandOption,
+    ApplicationCommandSubCommand
+} from "discord.js";
+import client from "../utils/client.js";
+import EmojiEmbed from "../utils/generateEmojiEmbed.js";
+import { LoadingEmbed } from "../utils/defaults.js";
+import { capitalize } from "../utils/generateKeyValueList.js";
+import { getCommandByName, getCommandMentionByName } from "../utils/getCommandDataByName.js";
+import getEmojiByName from "../utils/getEmojiByName.js";
 
 const command = new SlashCommandBuilder()
     .setName("help")
     .setDescription("Shows help for commands");
 
+const styles: Record<string, {emoji: string}> = {
+    "help": {emoji: "NUCLEUS.LOGO"},
+    "mod": {emoji: "PUNISH.BAN.RED"},
+    "nucleus": {emoji: "NUCLEUS.LOGO"},
+    "privacy": {emoji: "NUCLEUS.LOGO"},
+    "role": {emoji: "GUILD.ROLES.DELETE"},
+    "rolemenu": {emoji: "GUILD.ROLES.DELETE"},
+    "server": {emoji: "GUILD.RED"},
+    "settings": {emoji: "GUILD.SETTINGS.RED"},
+    "tag": {emoji: "PUNISH.NICKNAME.RED"},
+    "tags": {emoji: "PUNISH.NICKNAME.RED"},
+    "ticket": {emoji: "GUILD.TICKET.CLOSE"},
+    "user": {emoji: "MEMBER.LEAVE"},
+    "verify": {emoji: "CONTROL.REDTICK"}
+}
+
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    interaction.reply({components: [new ActionRowBuilder<ButtonBuilder>().addComponents(
-        new ButtonBuilder()
-            .setLabel("Create ticket")
-            .setStyle(ButtonStyle.Primary)
-            .setCustomId("createticket")
-    )]}); // TODO: FINISH THIS FOR RELEASE
+    const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
+    const commands = client.fetchedCommands;
+
+    let closed = false;
+    let currentPath: [string, string, string] = ["", "", ""]
+    do {
+        const commandRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("commandRow")
+                    .setPlaceholder("Select a command")
+                    .addOptions(
+                        ...commands.filter(command => command.type === ApplicationCommandType.ChatInput).map((command) => {
+                            const builder = new StringSelectMenuOptionBuilder()
+                                .setLabel(capitalize(command.name))
+                                .setValue(command.name)
+                                .setDescription(command.description)
+                                .setDefault(currentPath[0] === command.name)
+                            if (styles[command.name]) builder.setEmoji(getEmojiByName(styles[command.name]!.emoji, "id") as APIMessageComponentEmoji)
+                            return builder
+                        })
+                    )
+        );
+        const subcommandGroupRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("subcommandGroupRow")
+            );
+        const subcommandRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("subcommandRow")
+            );
+        const embed = new EmojiEmbed()
+            .setTitle("Help")
+            .setStatus("Danger")
+            .setEmoji("NUCLEUS.LOGO")
+
+        if(currentPath[0] === "" || currentPath[0] === "help") {
+            embed.setDescription(
+                `Welcome to Nucleus\n\n` +
+                `Select a command to get started${
+                    (interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ?
+                        `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : ``
+                    }\n\n\n` +
+                `Nucleus is fully [open source](https://github.com/clicksminuteper/Nucleus), and all currently free features will remain free forever.\n\n` +
+                `You can invite Nucleus to your server using ${getCommandMentionByName("nucleus/invite")}`
+            )
+        } else {
+            const currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/'));
+            const current = commands.find((command) => command.name === currentPath[0])!;
+
+            let optionString = ``
+            let options: (ApplicationCommandOption & {
+                nameLocalized?: string;
+                descriptionLocalized?: string;
+            })[] = [];
+            //options
+            if(currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none") {
+                const Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup
+                const Op2 = Op.options!.find(option => option.name === currentPath[2])!
+                options = Op2.options ?? []
+            } else if(currentPath[1] !== "" && currentPath[1] !== "none") {
+                let Op = current.options.find(option => option.name === currentPath[1])!
+                if(Op.type === ApplicationCommandOptionType.SubcommandGroup) {
+                    options = []
+                } else {
+                    Op = Op as ApplicationCommandSubCommand
+                    options = Op.options ?? []
+                }
+            } else {
+                options = current.options.filter(option => (option.type !== ApplicationCommandOptionType.SubcommandGroup) && (option.type !== ApplicationCommandOptionType.Subcommand));
+            }
+            for(const option of options) {
+                optionString += `> ${option.name} (${ApplicationCommandOptionType[option.type]})- ${option.description}\n`
+            }
+            const APICommand = client.commands["commands/" + currentPath.filter(value => value !== "" && value !== "none").join("/")]![0]
+            let allowedToRun = true;
+            if(APICommand?.check) {
+                allowedToRun = await APICommand.check(interaction as Interaction, true)
+            }
+            embed.setDescription(
+                `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}**\n> ${currentData.mention}\n\n` +
+                `> ${currentData.description}\n\n` +
+                (APICommand ? (`${getEmojiByName(allowedToRun ? "CONTROL.TICK" : "CONTROL.CROSS")} You ${allowedToRun ? "" : "don't "}` +
+                `have permission to use this command\n\n`) : "") +
+                ((optionString.length > 0) ? "**Options:**\n" + optionString : "")
+            )
+            const subcommands = current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand);
+            const subcommandGroups = current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup);
+
+            if(subcommandGroups.length > 0) {
+                subcommandGroupRow.components[0]!
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[1] === "none"),
+                        ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name))
+                    )
+                if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default && option.data.value !== "none")) {
+                    const subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options ?? [];
+                    subcommandRow.components[0]!
+                        .addOptions(
+                            new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[2] === "none"),
+                            ...subsubcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[2] === option.name))
+                        )
+                }
+            }
+            if(subcommands.length > 0) {
+                subcommandGroupRow.components[0]!
+                    .addOptions(
+                        ...subcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name))
+                    )
+            }
+        }
+
+        const cmps = [commandRow];
+        if(subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow);
+        if(subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow);
+
+        await interaction.editReply({ embeds: [embed], components: cmps });
+
+        let i: StringSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent<ComponentType.StringSelect>({filter: (newInteraction) => interaction.user.id === newInteraction.user.id,time: 300000})
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+        await i.deferUpdate();
+        const value = i.values[0]!;
+        switch(i.customId) {
+            case "commandRow": {
+                currentPath = [value, "", ""];
+                break;
+            }
+            case "subcommandGroupRow": {
+                currentPath = [currentPath[0], value , ""];
+                break;
+            }
+            case "subcommandRow": {
+                currentPath[2] = value;
+                break;
+            }
+        }
+
+    } while (!closed);
 };
 
-const check = () => {
-    return true;
-};
 
-export { command };
+export { command as command };
 export { callback };
-export { check };
diff --git a/src/commands/mod/_meta.ts b/src/commands/mod/_meta.ts
index af8006c..c5fcca5 100644
--- a/src/commands/mod/_meta.ts
+++ b/src/commands/mod/_meta.ts
@@ -5,4 +5,4 @@
 
 const subcommand = await command(name, description, `mod`);
 
-export { name, description, subcommand as command };
+export { name, description, subcommand as command };
\ No newline at end of file
diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts
index 130cdbc..0a9d962 100644
--- a/src/commands/mod/about.ts
+++ b/src/commands/mod/about.ts
@@ -3,17 +3,16 @@
 import Discord, {
     CommandInteraction,
     GuildMember,
-    Interaction,
     Message,
     ActionRowBuilder,
     ButtonBuilder,
     MessageComponentInteraction,
     ModalSubmitInteraction,
     ButtonStyle,
-    StringSelectMenuInteraction,
     TextInputStyle,
+    APIMessageComponentEmoji,
+    SlashCommandSubcommandBuilder
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import client from "../../utils/client.js";
@@ -167,8 +166,7 @@
                         .setLabel(value.text)
                         .setValue(key)
                         .setDefault(filteredTypes.includes(key))
-                        // @ts-expect-error
-                        .setEmoji(getEmojiByName(value.emoji, "id"))  // FIXME: This gives a type error but is valid
+                        .setEmoji(getEmojiByName(value.emoji, "id") as APIMessageComponentEmoji)
             )))
         ]);
         components = components.concat([new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
@@ -253,7 +251,7 @@
         try {
             i = 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) {
             interaction.editReply({
@@ -269,9 +267,9 @@
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
-        if (i.customId === "filter") {
-            filteredTypes = (i as StringSelectMenuInteraction).values;
+        await i.deferUpdate();
+        if (i.customId === "filter" && i.isStringSelectMenu()) {
+            filteredTypes = i.values;
             pageIndex = null;
             refresh = true;
         }
@@ -359,7 +357,7 @@
         try {
             i = 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) {
             timedOut = true;
@@ -402,17 +400,12 @@
             });
             let out;
             try {
-                out = await modalInteractionCollector(
-                    m,
-                    (m: Interaction) =>
-                        (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
-                    (m) => m.customId === "modify"
-                );
+                out = await modalInteractionCollector(m, interaction.user);
             } catch (e) {
                 timedOut = true;
                 continue;
             }
-            if (out === null) {
+            if (out === null || out.isButton()) {
                 continue;
             } else if (out instanceof ModalSubmitInteraction) {
                 let toAdd = out.fields.getTextInputValue("note") || null;
@@ -423,7 +416,7 @@
                 continue;
             }
         } else if (i.customId === "history") {
-            i.deferUpdate();
+            await i.deferUpdate();
             if (!(await showHistory(member, interaction))) return;
         }
     }
@@ -436,6 +429,8 @@
     return true;
 };
 
-export { command };
-export { callback };
-export { check };
+export { command, callback, check };
+export const metadata = {
+    longDescription: "Shows the moderation history (all previous bans, kicks, warns etc.), and moderator notes for a user.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index 70e904c..e8309fb 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -1,11 +1,11 @@
-import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle, SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import addPlurals from "../../utils/plurals.js";
 import client from "../../utils/client.js";
 import { LinkWarningFooter } from "../../utils/defaults.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -26,7 +26,7 @@
 const callback = async (interaction: CommandInteraction): Promise<void> => {
     if (!interaction.guild) return;
     const { renderUser } = client.logger;
-    // TODO:[Modals] Replace this with a modal
+    // TODO:[Modals] Replace the command arguments with a modal
     let reason = null;
     let notify = true;
     let confirmation;
@@ -123,17 +123,20 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.red,
                 emoji: "PUNISH.BAN.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(member.user.id, `\`${member.user.id}\``),
                 name: entry(member.user.id, renderUser(member.user)),
-                banned: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+                banned: entry(Date.now().toString(), renderDelta(Date.now())),
                 bannedBy: entry(interaction.user.id, renderUser(interaction.user)),
                 reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"),
                 accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)),
                 serverMemberCount: interaction.guild.memberCount
             },
+            separate: {
+                end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+            },
             hidden: {
                 guild: interaction.guild.id
             }
@@ -166,9 +169,12 @@
     });
 };
 
-const check = async (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has ban_members permission
+    if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
+    if(partial) return true;
     const me = interaction.guild.members.me!;
     let apply = interaction.options.getUser("user") as User | GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
@@ -181,21 +187,23 @@
         apply = apply as User
     }
     // Do not allow banning the owner
-    if (member.id === interaction.guild.ownerId) throw new Error("You cannot ban the owner of the server");
+    if (member.id === interaction.guild.ownerId) return "You cannot ban the owner of the server";
     // Check if Nucleus can ban the member
-    if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member");
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to ban
-    if (!me.permissions.has("BanMembers")) throw new Error("I do not have the *Ban Members* permission");
+    if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission";
     // Do not allow banning Nucleus
-    if (member.id === me.id) throw new Error("I cannot ban myself");
+    if (member.id === me.id) return "I cannot ban myself";
     // Allow the owner to ban anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has ban_members permission
-    if (!member.permissions.has("BanMembers")) throw new Error("You do not have the *Ban Members* permission");
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member");
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow ban
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Removes a member from the server - this will prevent them from rejoining until they are unbanned, and will delete a specified number of days of messages from them.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index 380bcc9..059bdb2 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -1,13 +1,13 @@
 import { LinkWarningFooter } from '../../utils/defaults.js';
-import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
+import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandSubcommandBuilder } from "discord.js";
 // @ts-expect-error
 import humanizeDuration from "humanize-duration";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import type Discord from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -102,8 +102,8 @@
         await client.database.history.create("kick", interaction.guild.id, member.user, interaction.user, reason);
         const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
         const timeInServer = member.joinedTimestamp ? entry(
-            (new Date().getTime() - member.joinedTimestamp).toString(),
-            humanizeDuration(new Date().getTime() - member.joinedTimestamp, {
+            (Date.now() - member.joinedTimestamp).toString(),
+            humanizeDuration(Date.now() - member.joinedTimestamp, {
                 round: true
             })
         ) : entry(null, "*Unknown*")
@@ -114,18 +114,21 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.red,
                 emoji: "PUNISH.KICK.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(member.id, `\`${member.id}\``),
                 name: entry(member.id, renderUser(member.user)),
                 joined: undefined as (unknown | typeof entry),
-                kicked: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+                kicked: entry(Date.now().toString(), renderDelta(Date.now())),
                 kickedBy: entry(interaction.user.id, renderUser(interaction.user)),
                 reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"),
                 timeInServer: timeInServer,
                 serverMemberCount: member.guild.memberCount
             },
+            separate: {
+                end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+            },
             hidden: {
                 guild: member.guild.id
             }
@@ -168,30 +171,37 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
+
     const member = interaction.member as GuildMember;
+    // Check if the user has kick_members permission
+    if (!member.permissions.has("KickMembers")) return "You do not have the *Kick Members* permission";
+    if (partial) return true;
+
     const me = interaction.guild.members.me!;
     const apply = interaction.options.getMember("user") as GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
     const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0;
     const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0;
+    // Check if Nucleus has permission to kick
+    if (!me.permissions.has("KickMembers")) return "I do not have the *Kick Members* permission";
+    // Allow the owner to kick anyone
+    if (member.id === interaction.guild.ownerId) return true;
     // Do not allow kicking the owner
     if (member.id === interaction.guild.ownerId) return "You cannot kick the owner of the server";
     // Check if Nucleus can kick the member
-    if (!(mePos > applyPos)) return "I do not have a role higher than that member";
-    // Check if Nucleus has permission to kick
-    if (!me.permissions.has("KickMembers")) return "I do not have the *Kick Members* permission";
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Do not allow kicking Nucleus
     if (member.id === interaction.guild.members.me!.id) return "I cannot kick myself";
-    // Allow the owner to kick anyone
-    if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has kick_members permission
-    if (!member.permissions.has("KickMembers")) return "You do not have the *Kick Members* permission";
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow kick
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Removes a member from the server. They will be able to rejoin if they have an invite link.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index 86291e5..c795456 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -1,6 +1,6 @@
 import { LinkWarningFooter, LoadingEmbed } from "../../utils/defaults.js";
 import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
@@ -103,7 +103,7 @@
         let component;
         try {
             component = await m.awaitMessageComponent({
-                filter: (m) => m.user.id === interaction.user.id,
+                filter: (i) => {return i.user.id === interaction.user.id && i.channelId === interaction.channelId},
                 time: 300000
             });
         } catch {
@@ -235,8 +235,8 @@
                         .setDescription(
                             `You have been muted in ${interaction.guild.name}` +
                                 (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") + "\n\n" +
-                            `You will be unmuted at: <t:${Math.round(new Date().getTime() / 1000) + muteTime}:D> at ` +
-                            `<t:${Math.round(new Date().getTime() / 1000) + muteTime}:T> (<t:${Math.round(new Date().getTime() / 1000) + muteTime
+                            `You will be unmuted at: <t:${Math.round(Date.now() / 1000) + muteTime}:D> at ` +
+                            `<t:${Math.round(Date.now() / 1000) + muteTime}:T> (<t:${Math.round(Date.now() / 1000) + muteTime
                             }:R>)` + "\n\n" +
                             (createAppealTicket
                                 ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
@@ -267,10 +267,10 @@
             await member.timeout(muteTime * 1000, reason || "*No reason provided*");
             if (config.moderation.mute.role !== null) {
                 await member.roles.add(config.moderation.mute.role);
-                await client.database.eventScheduler.schedule("naturalUnmute", (new Date().getTime() + muteTime * 1000).toString(), {
+                await client.database.eventScheduler.schedule("naturalUnmute", (Date.now() + muteTime * 1000).toString(), {
                     guild: interaction.guild.id,
                     user: member.id,
-                    expires: new Date().getTime() + muteTime * 1000
+                    expires: Date.now() + muteTime * 1000
                 });
             }
         } else {
@@ -282,7 +282,7 @@
     try {
         if (config.moderation.mute.role !== null) {
             await member.roles.add(config.moderation.mute.role);
-            await client.database.eventScheduler.schedule("unmuteRole", (new Date().getTime() + muteTime * 1000).toString(), {
+            await client.database.eventScheduler.schedule("unmuteRole", (Date.now() + muteTime * 1000).toString(), {
                 guild: interaction.guild.id,
                 user: member.id,
                 role: config.moderation.mute.role
@@ -325,19 +325,22 @@
             calculateType: "guildMemberPunish",
             color: NucleusColors.yellow,
             emoji: "PUNISH.WARN.YELLOW",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(member.user.id, `\`${member.user.id}\``),
             name: entry(member.user.id, renderUser(member.user)),
             mutedUntil: entry(
-                (new Date().getTime() + muteTime * 1000).toString(),
-                renderDelta(new Date().getTime() + muteTime * 1000)
+                (Date.now() + muteTime * 1000).toString(),
+                renderDelta(Date.now() + muteTime * 1000)
             ),
-            muted: entry(new Date().getTime.toString(), renderDelta(new Date().getTime() - 1000)),
+            muted: entry(new Date().getTime.toString(), renderDelta(Date.now() - 1000)),
             mutedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
             reason: entry(reason, reason ? reason : "*No reason provided*")
         },
+        separate: {
+            end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+        },
         hidden: {
             guild: interaction.guild.id
         }
@@ -361,9 +364,12 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has moderate_members permission
+    if (!member.permissions.has("ModerateMembers")) return "You do not have the *Moderate Members* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     const apply = interaction.options.getMember("user") as GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
@@ -372,20 +378,21 @@
     // Do not allow muting the owner
     if (member.id === interaction.guild.ownerId) return "You cannot mute the owner of the server";
     // Check if Nucleus can mute the member
-    if (!(mePos > applyPos)) return "I do not have a role higher than that member";
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to mute
     if (!me.permissions.has("ModerateMembers")) return "I do not have the *Moderate Members* permission";
     // Do not allow muting Nucleus
     if (member.id === me.id) return "I cannot mute myself";
     // Allow the owner to mute anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has moderate_members permission
-    if (!member.permissions.has("ModerateMembers"))
-        return "You do not have the *Moderate Members* permission";
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow mute
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Stops a member from being able to send messages or join voice channels for a specified amount of time.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index 9dd9336..5511d19 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -1,16 +1,16 @@
 import { LinkWarningFooter } from './../../utils/defaults.js';
 import { ActionRowBuilder, ButtonBuilder, CommandInteraction, GuildMember, ButtonStyle, Message } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import client from "../../utils/client.js";
 import { areTicketsEnabled, create } from "../../actions/createModActionTicket.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 
 const command = (builder: SlashCommandSubcommandBuilder) => builder
     .setName("nick")
-    // .setNameLocalizations({"ru": "name", "zh-CN": "nickname"})
     .setDescription("Changes a users nickname")
     .addUserOption((option) => option.setName("user").setDescription("The user to change").setRequired(true))
     .addStringOption((option) =>
@@ -156,15 +156,18 @@
             calculateType: "guildMemberUpdate",
             color: NucleusColors.yellow,
             emoji: "PUNISH.NICKNAME.YELLOW",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(member.id, `\`${member.id}\``),
             before: entry(before, before ?? "*No nickname set*"),
             after: entry(nickname ?? null, nickname ?? "*No nickname set*"),
-            updated: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+            updated: entry(Date.now(), renderDelta(Date.now())),
             updatedBy: entry(interaction.user.id, renderUser(interaction.user))
         },
+        separate: {
+            end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+        },
         hidden: {
             guild: interaction.guild!.id
         }
@@ -189,8 +192,11 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, partial: boolean = false) => {
     const member = interaction.member as GuildMember;
+    // Check if the user has manage_nicknames permission
+    if (!member.permissions.has("ManageNicknames")) return "You do not have the *Manage Nicknames* permission";
+    if (partial) return true;
     const me = interaction.guild!.members.me!;
     const apply = interaction.options.getMember("user") as GuildMember;
     const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
@@ -200,20 +206,21 @@
     // Do not allow any changing of the owner
     if (member.id === interaction.guild.ownerId) return "You cannot change the owner's nickname";
     // Check if Nucleus can change the nickname
-    if (!(mePos > applyPos)) return "I do not have a role higher than that member";
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to change the nickname
     if (!me.permissions.has("ManageNicknames")) return "I do not have the *Manage Nicknames* permission";
     // Allow the owner to change anyone's nickname
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has manage_nicknames permission
-    if (!member.permissions.has("ManageNicknames"))
-        return "You do not have the *Manage Nicknames* permission";
     // Allow changing your own nickname
     if (member === apply) return true;
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow change
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Changes the nickname of a member. This is the name that shows in the member list and on messages.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index e6b4670..8644e26 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -1,6 +1,5 @@
-import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from '../../utils/logTranscripts.js';
-import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder, Message } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
@@ -30,7 +29,7 @@
     if (!interaction.guild) return;
     const user = (interaction.options.getMember("user") as GuildMember | null);
     const channel = interaction.channel as GuildChannel;
-    if (channel.isTextBased()) {
+    if (!channel.isTextBased()) {
         return await interaction.reply({
             embeds: [
                 new EmojiEmbed()
@@ -94,7 +93,7 @@
             let component;
             try {
                 component = m.awaitMessageComponent({
-                    filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id,
+                    filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
                     time: 300000
                 });
             } catch (e) {
@@ -148,7 +147,7 @@
                 calculateType: "messageDelete",
                 color: NucleusColors.red,
                 emoji: "CHANNEL.PURGE.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
@@ -161,7 +160,8 @@
             }
         };
         log(data);
-        const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(deleted)!);
+        const newOut = await client.database.transcripts.createTranscript(deleted, interaction, interaction.member as GuildMember);
+        const transcript = client.database.transcripts.toHumanReadable(newOut);
         const attachmentObject = {
             attachment: Buffer.from(transcript),
             name: `purge-${channel.id}-${Date.now()}.txt`,
@@ -188,7 +188,7 @@
         let component;
         try {
             component = await m.awaitMessageComponent({
-                filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id,
+                filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
                 time: 300000
             });
         } catch {
@@ -296,7 +296,7 @@
                 calculateType: "messageDelete",
                 color: NucleusColors.red,
                 emoji: "CHANNEL.PURGE.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
@@ -309,35 +309,17 @@
             }
         };
         log(data);
-        let out = "";
-        messages.reverse().forEach((message) => {
-            if (!message) {
-                out += "Unknown message\n\n"
-            } else {
-                const author = message.author ?? { username: "Unknown", discriminator: "0000", id: "Unknown" };
-                out += `${author.username}#${author.discriminator} (${author.id}) [${new Date(
-                    message.createdTimestamp
-                ).toISOString()}]\n`;
-                if (message.content) {
-                    const lines = message.content.split("\n");
-                    lines.forEach((line) => {
-                        out += `> ${line}\n`;
-                    });
-                }
-                if (message.attachments.size > 0) {
-                    message.attachments.forEach((attachment) => {
-                        out += `Attachment > ${attachment.url}\n`;
-                    });
-                }
-                out += "\n\n";
-            }
-        });
-        const attachmentObject = {
-            attachment: Buffer.from(out),
-            name: `purge-${channel.id}-${Date.now()}.txt`,
-            description: "Purge log"
-        };
-        const m = (await interaction.editReply({
+        const messageArray: Message[] = messages.filter(message => !(
+            message!.components.some(
+                component => component.components.some(
+                    child => child.customId?.includes("transcript") ?? false
+                )
+            )
+        )).map(message => message as Message);
+        const newOut = await client.database.transcripts.createTranscript(messageArray, interaction, interaction.member as GuildMember);
+
+        const code = await client.database.transcripts.create(newOut);
+        await interaction.editReply({
             embeds: [
                 new EmojiEmbed()
                     .setEmoji("CHANNEL.PURGE.GREEN")
@@ -347,62 +329,30 @@
             ],
             components: [
                 new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new Discord.ButtonBuilder()
-                        .setCustomId("download")
-                        .setLabel("Download transcript")
-                        .setStyle(ButtonStyle.Success)
-                        .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
+                    new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`),
                 ])
             ]
-        })) as Discord.Message;
-        let component;
-        try {
-            component = await m.awaitMessageComponent({
-                filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id,
-                time: 300000
-            });
-        } catch {
-            return;
-        }
-        if (component.customId === "download") {
-            interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.PURGE.GREEN")
-                        .setTitle("Purge")
-                        .setDescription("Transcript uploaded above")
-                        .setStatus("Success")
-                ],
-                components: [],
-                files: [attachmentObject]
-            });
-        } else {
-            interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.PURGE.GREEN")
-                        .setTitle("Purge")
-                        .setDescription("Messages cleared")
-                        .setStatus("Success")
-                ],
-                components: []
-            });
-        }
+        });
     }
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return false;
     const member = interaction.member as GuildMember;
+    // Check if the user has manage_messages permission
+    if (!member.permissions.has("ManageMessages")) return "You do not have the *Manage Messages* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     // Check if nucleus has the manage_messages permission
     if (!me.permissions.has("ManageMessages")) return "I do not have the *Manage Messages* permission";
     // Allow the owner to purge
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has manage_messages permission
-    if (!member.permissions.has("ManageMessages")) return "You do not have the *Manage Messages* permission";
     // Allow purge
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Deletes a specified amount of messages from a channel, optionally from a specific user. Without an amount, you can repeatedly choose a number of messages to delete.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts
index 9792827..f282e82 100644
--- a/src/commands/mod/slowmode.ts
+++ b/src/commands/mod/slowmode.ts
@@ -1,7 +1,7 @@
 // @ts-expect-error
 import humanizeDuration from "humanize-duration";
 import type { CommandInteraction, GuildMember, TextChannel } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@@ -47,7 +47,7 @@
             }) + "Are you sure you want to set the slowmode in this channel?"
         )
         .setColor("Danger")
-        .setFailedMessage("No changes were made", "Danger", "CHANNEL.SLOWMODE.ON")
+        .setFailedMessage("No changes were made", "Success", "CHANNEL.SLOWMODE.ON")
         .send();
     if (confirmation.cancelled || !confirmation.success) return;
     try {
@@ -76,14 +76,19 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     const member = interaction.member as GuildMember;
-    // Check if Nucleus can set the slowmode
-    if (!interaction.guild!.members.me!.permissions.has("ManageChannels")) return "I do not have the *Manage Channels* permission";
     // Check if the user has manage_channel permission
     if (!member.permissions.has("ManageChannels")) return "You do not have the *Manage Channels* permission";
+    if (partial) return true;
+    // Check if Nucleus can set the slowmode
+    if (!interaction.guild!.members.me!.permissions.has("ManageChannels")) return "I do not have the *Manage Channels* permission";
     // Allow slowmode
     return true;
 };
 
 export { command, callback, check };
+export const metadata = {
+    longDescription: "Stops members from being able to send messages without waiting a certain amount of time between messages.",
+    premiumOnly: true,
+}
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index 2787e91..1b404c9 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -1,11 +1,12 @@
 import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import addPlurals from "../../utils/plurals.js";
 import client from "../../utils/client.js";
 import { LinkWarningFooter } from "../../utils/defaults.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -124,17 +125,20 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.yellow,
                 emoji: "PUNISH.BAN.YELLOW",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(member.user.id, `\`${member.user.id}\``),
                 name: entry(member.user.id, renderUser(member.user)),
-                softbanned: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+                softbanned: entry(Date.now().toString(), renderDelta(Date.now())),
                 softbannedBy: entry(interaction.user.id, renderUser(interaction.user)),
                 reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"),
                 accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)),
                 serverMemberCount: interaction.guild.memberCount
             },
+            separate: {
+                end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+            },
             hidden: {
                 guild: interaction.guild.id
             }
@@ -167,9 +171,12 @@
     });
 };
 
-const check = async (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has ban_members permission
+    if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     let apply = interaction.options.getUser("user") as User | GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
@@ -182,19 +189,17 @@
         apply = apply as User
     }
     // Do not allow banning the owner
-    if (member.id === interaction.guild.ownerId) throw new Error("You cannot softban the owner of the server");
+    if (member.id === interaction.guild.ownerId) return "You cannot softban the owner of the server";
     // Check if Nucleus can ban the member
-    if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member");
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to ban
-    if (!me.permissions.has("BanMembers")) throw new Error("I do not have the *Ban Members* permission");
+    if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission";
     // Do not allow banning Nucleus
-    if (member.id === me.id) throw new Error("I cannot softban myself");
+    if (member.id === me.id) return "I cannot softban myself";
     // Allow the owner to ban anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has ban_members permission
-    if (!member.permissions.has("BanMembers")) throw new Error("You do not have the *Ban Members* permission");
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member");
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow ban
     return true;
 };
diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts
index 37fee99..40f4504 100644
--- a/src/commands/mod/unban.ts
+++ b/src/commands/mod/unban.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction, GuildMember, User } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
@@ -57,12 +57,12 @@
                     calculateType: "guildMemberPunish",
                     color: NucleusColors.green,
                     emoji: "PUNISH.BAN.GREEN",
-                    timestamp: new Date().getTime()
+                    timestamp: Date.now()
                 },
                 list: {
                     memberId: entry(member.id, `\`${member.id}\``),
                     name: entry(member.id, renderUser(member)),
-                    unbanned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    unbanned: entry(Date.now(), renderDelta(Date.now())),
                     unbannedBy: entry(interaction.user.id, renderUser(interaction.user)),
                     accountCreated: entry(member.createdTimestamp, renderDelta(member.createdTimestamp))
                 },
@@ -107,16 +107,17 @@
     }
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has ban_members permission
+    if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     // Check if Nucleus can unban members
     if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission";
     // Allow the owner to unban anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has ban_members permission
-    if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
     // Allow unban
     return true;
 };
diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts
index e2585e1..8562c4c 100644
--- a/src/commands/mod/unmute.ts
+++ b/src/commands/mod/unmute.ts
@@ -1,9 +1,10 @@
 import type { CommandInteraction, GuildMember } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -105,14 +106,17 @@
             calculateType: "guildMemberPunish",
             color: NucleusColors.green,
             emoji: "PUNISH.MUTE.GREEN",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(member.user.id, `\`${member.user.id}\``),
             name: entry(member.user.id, renderUser(member.user)),
-            unmuted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+            unmuted: entry(Date.now().toString(), renderDelta(Date.now())),
             unmutedBy: entry(interaction.user.id, renderUser(interaction.user))
         },
+        separate: {
+            end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
+        },
         hidden: {
             guild: interaction.guild.id
         }
@@ -131,9 +135,13 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    // Check if the user has moderate_members permission
+    if (!member.permissions.has("ModerateMembers"))
+        return "You do not have the *Moderate Members* permission";
+    if (partial) return true;
     const me = interaction.guild.members.me!;
     const apply = interaction.options.getMember("user") as GuildMember;
     const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
@@ -142,16 +150,13 @@
     // Do not allow unmuting the owner
     if (member.id === interaction.guild.ownerId) return "You cannot unmute the owner of the server";
     // Check if Nucleus can unmute the member
-    if (!(mePos > applyPos)) return "I do not have a role higher than that member";
+    if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`;
     // Check if Nucleus has permission to unmute
     if (!me.permissions.has("ModerateMembers")) return "I do not have the *Moderate Members* permission";
     // Allow the owner to unmute anyone
     if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has moderate_members permission
-    if (!member.permissions.has("ModerateMembers"))
-        return "You do not have the *Moderate Members* permission";
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow unmute
     return true;
 };
diff --git a/src/commands/mod/viewas.ts b/src/commands/mod/viewas.ts
index b176dd4..ef62816 100644
--- a/src/commands/mod/viewas.ts
+++ b/src/commands/mod/viewas.ts
@@ -7,9 +7,10 @@
     ButtonStyle,
     NonThreadGuildBasedChannel,
     StringSelectMenuOptionBuilder,
-    StringSelectMenuBuilder
+    StringSelectMenuBuilder,
+    APIMessageComponentEmoji
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import type { GuildBasedChannel } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
@@ -126,8 +127,7 @@
                     return new StringSelectMenuOptionBuilder()
                         .setLabel(c)
                         .setValue((set * 25 + i).toString())
-                        // @ts-expect-error
-                        .setEmoji(getEmojiByName("ICONS.CHANNEL.CATEGORY", "id"))  // Again, this is valid but TS doesn't think so
+                        .setEmoji(getEmojiByName("ICONS.CHANNEL.CATEGORY", "id") as APIMessageComponentEmoji)  // Again, this is valid but TS doesn't think so
                         .setDefault((set * 25 + i) === page)
                 }))
             )}
@@ -157,19 +157,19 @@
         });
         let i;
         try {
-            i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id, time: 30000});
+            i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 30000});
         } catch (e) {
             closed = true;
             continue;
         }
-        i.deferUpdate();
+        await i.deferUpdate();
         if (i.customId === "back") page--;
         else if (i.customId === "right") page++;
         else if (i.customId === "category" && i.isStringSelectMenu()) page = parseInt(i.values[0]!);
     }
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as GuildMember;
     if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
     return true;
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 38aa4ad..ea4f084 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -1,10 +1,11 @@
 import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import { create, areTicketsEnabled } from "../../actions/createModActionTicket.js";
 import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 import { LinkWarningFooter } from "../../utils/defaults.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -116,7 +117,7 @@
             calculateType: "guildMemberPunish",
             color: NucleusColors.yellow,
             emoji: "PUNISH.WARN.YELLOW",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             user: entry(
@@ -124,7 +125,10 @@
                 renderUser((interaction.options.getMember("user") as GuildMember).user)
             ),
             warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
-            reason: reason ? `\n> ${reason}` : "*No reason provided*"
+            reason: reason ? reason : "*No reason provided*"
+        },
+        separate: {
+            end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
         },
         hidden: {
             guild: interaction.guild.id
@@ -186,7 +190,7 @@
         let component;
         try {
             component = await m.awaitMessageComponent({
-                filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id,
+                filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
                 time: 300000
             });
         } catch (e) {
@@ -275,9 +279,12 @@
     }
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
     if (!interaction.guild) return;
     const member = interaction.member as GuildMember;
+    if (!member.permissions.has("ModerateMembers"))
+        return "You do not have the *Moderate Members* permission";
+    if(partial) return true;
     const apply = interaction.options.getMember("user") as GuildMember | null;
     if (apply === null) return "That member is not in the server";
     const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
@@ -287,10 +294,8 @@
     // Allow the owner to warn anyone
     if (member.id === interaction.guild.ownerId) return true;
     // Check if the user has moderate_members permission
-    if (!member.permissions.has("ModerateMembers"))
-        return "You do not have the *Moderate Members* permission";
     // Check if the user is below on the role list
-    if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
+    if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
     // Allow warn
     return true;
 };
diff --git a/src/commands/nucleus/_meta.ts b/src/commands/nucleus/_meta.ts
index 521b338..bd7fd14 100644
--- a/src/commands/nucleus/_meta.ts
+++ b/src/commands/nucleus/_meta.ts
@@ -3,8 +3,6 @@
 const name = "nucleus";
 const description = "Commands relating to Nucleus itself";
 
-const subcommand = await command(name, description, `nucleus`)
+const subcommand = await command(name, description, `nucleus`, undefined, undefined, undefined, undefined, true);
 
-const allowedInDMs = true;
-
-export { name, description, subcommand as command, allowedInDMs };
+export { name, description, subcommand as command };
diff --git a/src/commands/nucleus/guide.ts b/src/commands/nucleus/guide.ts
index d3370ba..270ee62 100644
--- a/src/commands/nucleus/guide.ts
+++ b/src/commands/nucleus/guide.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from 'discord.js';
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import guide from "../../reflex/guide.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -9,10 +9,5 @@
     guide(interaction.guild!, interaction);
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/invite.ts b/src/commands/nucleus/invite.ts
index fd65e51..b89425a 100644
--- a/src/commands/nucleus/invite.ts
+++ b/src/commands/nucleus/invite.ts
@@ -1,5 +1,5 @@
 import { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
@@ -29,10 +29,5 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/ping.ts b/src/commands/nucleus/ping.ts
index 12f1c6b..3e02a8f 100644
--- a/src/commands/nucleus/ping.ts
+++ b/src/commands/nucleus/ping.ts
@@ -1,6 +1,6 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
@@ -10,9 +10,9 @@
 const callback = async (interaction: CommandInteraction): Promise<void> => {
     // WEBSOCKET | Nucleus -> Discord
     // EDITING   | Nucleus -> discord -> nucleus | edit time / 2
-    const initial = new Date().getTime();
+    const initial = Date.now();
     await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
-    const ping = new Date().getTime() - initial;
+    const ping = Date.now() - initial;
     interaction.editReply({
         embeds: [
             new EmojiEmbed()
@@ -28,10 +28,5 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts
index 745f167..c431c8e 100644
--- a/src/commands/nucleus/premium.ts
+++ b/src/commands/nucleus/premium.ts
@@ -1,32 +1,211 @@
-import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, ComponentType, Message, StringSelectMenuBuilder, StringSelectMenuInteraction } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import client from "../../utils/client.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder.setName("premium").setDescription("Information about Nucleus Premium");
+//TODO: Allow User to remove Premium
+
+const dmcallback = async (interaction: CommandInteraction, firstDescription: string, msg: Message): Promise<void> => {
+    let closed = false;
+    do {
+        const dbUser = await client.database.premium.fetchUser(interaction.user.id);
+        if(!dbUser) {
+            await interaction.editReply({embeds: [
+                new EmojiEmbed()
+                    .setTitle("Premium")
+                    .setDescription(`*You do not have premium! You can't activate premium on any servers.*` + firstDescription)
+                    .setEmoji("NUCLEUS.LOGO")
+                    .setStatus("Danger")
+            ]});
+            return;
+        }
+        const premiumGuilds = dbUser.appliesTo.map((guildID) => {
+            const guild = client.guilds.cache.get(guildID);
+            if(!guild) return undefined;
+            return guild.name;
+        });
+
+        const options = premiumGuilds.filter((guild) => guild !== undefined) as string[];
+
+        const removeRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("currentPremium")
+                    .setPlaceholder("Select a server to remove premium from")
+                    .setDisabled(premiumGuilds.length === 0)
+                    .addOptions(options.slice(0, Math.min(options.length, 24)).map((guild) => {
+                        return {label: guild, value: guild}
+                    }))
+            );
+        const cancel = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("cancel")
+                    .setLabel("Close")
+                    .setStyle(ButtonStyle.Danger)
+            );
+
+        const components: ActionRowBuilder<StringSelectMenuBuilder | ButtonBuilder>[] = [cancel];
+        if(options.length > 0) components.unshift(removeRow);
+        await interaction.editReply(
+        {
+            embeds: [
+                new EmojiEmbed()
+                    .setTitle("Premium")
+                    .setDescription(
+                        `*You have premium on the following servers:*\n\n` +
+                        (options.length > 0 ? options.join(', ') : `You have not activated premium in any guilds`) +
+                        firstDescription)
+                    .setEmoji("NUCLEUS.LOGO")
+                    .setStatus("Success")
+            ],
+            components: components
+        });
+
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            const filter = (i: StringSelectMenuInteraction | ButtonInteraction) => i.user.id === interaction.user.id;
+            i = await msg.awaitMessageComponent<ComponentType.StringSelect | ComponentType.Button>({time: 300000, filter})
+        } catch (e) {
+            await interaction.deleteReply();
+            closed = true;
+            break;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            closed = true;
+        } else {
+            const response = client.database.premium.removePremium(interaction.user.id, i.values[0]!);
+            console.log(response)
+        }
+    } while (!closed);
+    await interaction.deleteReply();
+}
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    interaction.reply({
+    if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {});
+    const m = await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true})
+    const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id).catch(() => {
+        interaction.editReply({ embeds: [
+            new EmojiEmbed()
+                .setTitle("Premium")
+                .setDescription(`*You are not currently in the Clicks Server. To gain access to premium please join.*` + firstDescription)
+                .setEmoji("NUCLEUS.LOGO")
+                .setStatus("Danger")
+        ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel("Join").setURL("https://discord.gg/bPaNnxe"))] });
+    })
+    if (!member) return;
+    const firstDescription = "\n\nPremium allows servers of your choice to get access to extra features for a fixed price per month.\nThis includes:\n" +
+        `${getEmojiByName("MOD.IMAGES.TOOSMALL")}  Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n` +
+        `${getEmojiByName("GUILD.TICKET.ARCHIVED")}  Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n`
+    const dbMember = await client.database.premium.fetchUser(interaction.user.id)
+    let premium = `You do not have premium! You can't activate premium on any servers.`;
+    let count = 0;
+    const {level, appliesTo} = dbMember ?? {level: 0, appliesTo: []}
+    if (level === 99) {
+        premium = `You have Infinite Premium! You have been gifted this by the developers as a thank you. You can give premium to any and all servers you are in.`;
+        count = 200;
+    } else if (level === 1) {
+        premium = `You have Premium tier 1! You can give premium to ${1 - appliesTo.length} more server(s).`;
+        count = 1;
+    } else if (level === 2) {
+        premium = `You have Premium tier 2! You can give premium to ${3 - appliesTo.length} more server(s).`;
+        count = 3;
+    } else if (level === 3) {
+        premium = `You have Premium Mod! You can give premium to ${3 - appliesTo.length} more server(s), as well as automatically giving premium to all servers you have a "manage" permission in.`
+        count = 3;
+    }
+    if (dbMember?.expiresAt) {
+        premium = `**You can't give servers premium anymore because your subscription ended or was cancelled.** To get premium again please subscribe in the Clicks server`
+        count = 0;
+    }
+    if(!interaction.guild) return await dmcallback(interaction, firstDescription, m);
+    const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id);
+    let premiumGuild = ""
+    if (hasPremium) {
+        premiumGuild = `**This server has premium! It was ${hasPremium[2] === 3 && hasPremium[3] ? `automatically applied by <@${hasPremium[1]}>` : `given by <@${hasPremium[1]}>`}**\n\n`
+    }
+
+    const components: ActionRowBuilder<ButtonBuilder>[] = []
+    if (level === 0 || dbMember?.expiresAt) {
+        components.push(
+            new ActionRowBuilder<ButtonBuilder>()
+                .addComponents(
+                    new ButtonBuilder()
+                    .setStyle(ButtonStyle.Link)
+                    .setLabel("Join Clicks")
+                    .setURL("https://discord.gg/bPaNnxe")
+                )
+        )
+    } else {
+        components.push(
+            new ActionRowBuilder<ButtonBuilder>()
+                .addComponents(
+                    new ButtonBuilder()
+                        .setStyle(premiumGuild.length > 0 ? ButtonStyle.Secondary : ButtonStyle.Success)
+                        .setLabel(premiumGuild.length > 0 ? "This server has premium" : "Activate premium here")
+                        .setCustomId("premiumActivate")
+                        .setDisabled(count <= 0 || (hasPremium ? hasPremium[0] : false))
+                )
+        )
+    }
+
+    interaction.editReply({
         embeds: [
             new EmojiEmbed()
                 .setTitle("Premium")
                 .setDescription(
-                    "*Nucleus Premium is currently not available.*\n\n" +
-                        "Premium allows your server to get access to extra features, for a fixed price per month.\nThis includes:\n" +
-                        "- Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n" +
-                        "- Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n"
+                    premiumGuild + premium + firstDescription
                 )
                 .setEmoji("NUCLEUS.LOGO")
                 .setStatus("Danger")
+                .setImage("https://assets.clicks.codes/ads/ads/nucleus-premium.png")
         ],
-        ephemeral: true
+        components: components
     });
-};
 
-const check = () => {
-    return true;
+    const filter = (i: ButtonInteraction) => i.customId === "premiumActivate" && i.user.id === interaction.user.id;
+    let i;
+    try {
+        i = await interaction.channel!.awaitMessageComponent<2>({ filter, time: 60000 });
+    } catch (e) {
+        return;
+    }
+    i.deferUpdate();
+    const guild = i.guild!;
+    if (count - appliesTo.length <= 0) {
+        interaction.editReply({
+            embeds: [
+                new EmojiEmbed()
+                    .setTitle("Premium")
+                    .setDescription(
+                        `You have already activated premium on the maximum amount of servers!` + firstDescription
+                    )
+                    .setEmoji("NUCLEUS.PREMIUMACTIVATE")
+                    .setStatus("Danger")
+            ],
+            components: []
+        });
+    } else {
+        await client.database.premium.addPremium(interaction.user.id, guild.id);
+        interaction.editReply({
+            embeds: [
+                new EmojiEmbed()
+                    .setTitle("Premium")
+                    .setDescription(
+                        `You have activated premium on this server!` + firstDescription
+                    )
+                    .setEmoji("NUCLEUS.LOGO")
+                    .setStatus("Danger")
+            ],
+            components: []
+        });
+    }
 };
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts
index d8b2807..19c0949 100644
--- a/src/commands/nucleus/stats.ts
+++ b/src/commands/nucleus/stats.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 
@@ -13,16 +13,11 @@
                 .setTitle("Stats")
                 .setDescription(`**Servers:** ${client.guilds.cache.size}\n` + `**Ping:** \`${client.ws.ping * 2}ms\``)
                 .setStatus("Success")
-                .setEmoji("GUILD.GRAPHS")
+                .setEmoji("SETTINGS.STATS.GREEN")
         ],
         ephemeral: true
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index de0e69b..6ba3445 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -1,7 +1,7 @@
 import { LoadingEmbed } from '../../utils/defaults.js';
 import { ButtonStyle, CommandInteraction } from "discord.js";
 import Discord from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
@@ -66,10 +66,5 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts
index 9100302..46784f5 100644
--- a/src/commands/privacy.ts
+++ b/src/commands/privacy.ts
@@ -1,6 +1,5 @@
 import { LoadingEmbed } from "../utils/defaults.js";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, SelectMenuOptionBuilder, StringSelectMenuBuilder } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import Discord, { SlashCommandBuilder, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, SelectMenuOptionBuilder, StringSelectMenuBuilder } from "discord.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
 import createPageIndicator from "../utils/createPageIndicator.js";
@@ -22,8 +21,7 @@
                     .setDescription(
                         "Nucleus is a bot that naturally needs to store data about servers.\n" +
                             "We are entirely [open source](https://github.com/ClicksMinutePer/Nucleus), so you can check exactly what we store, and how it works.\n\n" +
-                            "If you are a server administrator, you can view the options page in the dropdown under this message.\n\n" +
-                            "Any questions about Nucleus, how it works and data stored can be asked in [our server](https://discord.gg/bPaNnxe)."
+                            "Any questions about Nucleus, how it works, and what data is stored can be asked in [our server](https://discord.gg/bPaNnxe)."
                     )
                     .setEmoji("NUCLEUS.LOGO")
                     .setStatus("Danger")
@@ -50,39 +48,37 @@
                 new EmojiEmbed()
                     .setTitle("Link scanning and Transcripts")
                     .setDescription(
-                        "**Facebook** - Facebook trackers include data such as your date of birth, and guess your age if not entered, your preferences, who you interact with and more.\n" +
-                            "**AMP** - AMP is a technology that allows websites to be served by Google. This means Google can store and track data, and are pushing this to as many pages as possible.\n\n" +
-                            "Transcripts allow you to store all messages sent in a channel. This could be an issue in some cases, as they are hosted on [Pastebin](https://pastebin.com), so a leaked link could show all messages sent in the channel.\n"  // TODO: Not on pastebin
+                            "Transcripts allow you to store all messages sent in a channel. Transcripts are stored in our database along with the rest of the server's settings but is accessible by anyone with the link, so a leaked link could show all messages sent in the channel.\n"
                     )
                     .setEmoji("NUCLEUS.LOGO")
                     .setStatus("Danger")
             )
             .setTitle("Link scanning and Transcripts")
-            .setDescription("Regarding Facebook and AMP filter types, and ticket transcripts")
+            .setDescription("Information about how links and images are scanned, and transcripts are stored")
             .setPageId(2)
     ].concat(
         (interaction.member as Discord.GuildMember).permissions.has("Administrator")
             ? [
-                  new Embed()
-                      .setEmbed(
-                          new EmojiEmbed()
-                              .setTitle("Options")
-                              .setDescription("Below are buttons for controlling this servers privacy settings")
-                              .setEmoji("NUCLEUS.LOGO")
-                              .setStatus("Danger")
-                      )
-                      .setTitle("Options")
-                      .setDescription("Options")
-                      .setPageId(3)
-                      .setComponents([
-                          new ActionRowBuilder<ButtonBuilder>().addComponents([
-                              new ButtonBuilder()
-                                  .setLabel("Clear all data")
-                                  .setCustomId("clear-all-data")
-                                  .setStyle(ButtonStyle.Danger)
-                          ])
-                      ])
-              ]
+                new Embed()
+                    .setEmbed(
+                        new EmojiEmbed()
+                            .setTitle("Options")
+                            .setDescription("Below are buttons for controlling this servers privacy settings")
+                            .setEmoji("NUCLEUS.LOGO")
+                            .setStatus("Danger")
+                    )
+                    .setTitle("Options")
+                    .setDescription("Options")
+                    .setPageId(3)
+                    .setComponents([
+                        new ActionRowBuilder<ButtonBuilder>().addComponents([
+                            new ButtonBuilder()
+                                .setLabel("Clear all data")
+                                .setCustomId("clear-all-data")
+                                .setStyle(ButtonStyle.Danger)
+                        ])
+                    ])
+            ]
             : []
     );
     const m = await interaction.reply({
@@ -150,14 +146,14 @@
         try {
             i = 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) {
             timedOut = true;
             continue;
         }
         nextFooter = null;
-        i.deferUpdate();
+        await i.deferUpdate();
         if (i.customId === "left") {
             if (page > 0) page--;
             selectPaneOpen = false;
@@ -180,11 +176,12 @@
                 .setColor("Danger")
                 .send(true);
             if (confirmation.cancelled) {
-                break;
+                continue;
             }
             if (confirmation.success) {
                 client.database.guilds.delete(interaction.guild!.id);
                 client.database.history.delete(interaction.guild!.id);
+                client.database.notes.delete(interaction.guild!.id);
                 nextFooter = "All data cleared";
                 continue;
             } else {
@@ -208,10 +205,6 @@
     });
 };
 
-const check = () => {
-    return true;
-};
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/role/_meta.ts b/src/commands/role/_meta.ts
deleted file mode 100644
index f546d51..0000000
--- a/src/commands/role/_meta.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { command } from "../../utils/commandRegistration/slashCommandBuilder.js";
-
-const name = "role";
-const description = "Change roles for users";
-
-const subcommand = await command(name, description, `role`);
-
-export { name, description, subcommand as command };
diff --git a/src/commands/role/user.ts b/src/commands/role/user.ts
deleted file mode 100644
index ad29811..0000000
--- a/src/commands/role/user.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import type { CommandInteraction, GuildMember, Role, User } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import client from "../../utils/client.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import keyValueList from "../../utils/generateKeyValueList.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-        .setName("user")
-        .setDescription("Gives or removes a role from someone")
-        .addUserOption((option) =>
-            option.setName("user").setDescription("The member to give or remove the role from").setRequired(true)
-        )
-        .addRoleOption((option) =>
-            option.setName("role").setDescription("The role to give or remove").setRequired(true)
-        )
-        .addStringOption((option) =>
-            option
-                .setName("action")
-                .setDescription("The action to perform")
-                .setRequired(true)
-                .addChoices(
-                    {name: "Add", value: "give"},
-                    {name: "Remove", value: "remove"}
-                )
-        );
-
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const { renderUser, renderRole } = client.logger;
-    const action = interaction.options.get("action")?.value as string;
-    const role: Role = (await interaction.guild!.roles.fetch(interaction.options.get("role")?.value as string))!;
-    // TODO:[Modals] Replace this with a modal
-    const confirmation = await new confirmationMessage(interaction)
-        .setEmoji("GUILD.ROLES.DELETE")
-        .setTitle("Role")
-        .setDescription(
-            keyValueList({
-                user: renderUser(interaction.options.getUser("user")! as User),
-                role: renderRole(role)
-            }) +
-                `\nAre you sure you want to ${
-                    action === "give" ? "give the role to" : "remove the role from"
-                } ${interaction.options.getUser("user")}?`
-        )
-        .setColor("Danger")
-        .setFailedMessage("No changes were made", "Success", "GUILD.ROLES.CREATE")
-        .send();
-    if (confirmation.cancelled || !confirmation.success) return;
-    try {
-        const member = interaction.options.getMember("user") as GuildMember;
-        if ((interaction.options.get("action")?.value as string) === "give") {
-            member.roles.add(role);
-        } else {
-            member.roles.remove(role);
-        }
-    } catch (e) {
-        return await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Role")
-                    .setDescription("Something went wrong and the role could not be added")
-                    .setStatus("Danger")
-                    .setEmoji("CONTROL.BLOCKCROSS")
-            ],
-            components: []
-        });
-    }
-    return await interaction.editReply({
-        embeds: [
-            new EmojiEmbed()
-                .setTitle("Role")
-                .setDescription(`The role has been ${action === "give" ? "given" : "removed"} successfully`)
-                .setStatus("Success")
-                .setEmoji("GUILD.ROLES.CREATE")
-        ],
-        components: []
-    });
-};
-
-const check = (interaction: CommandInteraction) => {
-    const member = interaction.member as GuildMember;
-    if (!interaction.guild) return
-    const me = interaction.guild.members.me!;
-    const apply = interaction.options.getMember("user") as GuildMember | null;
-    if (apply === null) return "That member is not in the server";
-    // Check if Nucleus has permission to role
-    if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission";
-    // Allow the owner to role anyone
-    if (member.id === interaction.guild.ownerId) return true;
-    // Check if the user has manage_roles permission
-    if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
-    // Allow role
-    return true;
-};
-
-export { command };
-export { callback };
-export { check };
diff --git a/src/commands/rolemenu.ts b/src/commands/rolemenu.ts
index c1ceb2e..2861e05 100644
--- a/src/commands/rolemenu.ts
+++ b/src/commands/rolemenu.ts
@@ -1,5 +1,4 @@
-import type { CommandInteraction } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import { CommandInteraction, SlashCommandBuilder } from "discord.js";
 import { callback as roleMenu } from "../actions/roleMenu.js";
 
 const command = new SlashCommandBuilder()
@@ -10,10 +9,5 @@
     await roleMenu(interaction);
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/server/about.ts b/src/commands/server/about.ts
index 23a53b7..4c88365 100644
--- a/src/commands/server/about.ts
+++ b/src/commands/server/about.ts
@@ -1,5 +1,5 @@
 import { CommandInteraction, GuildMFALevel } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import generateKeyValueList, { toCapitals } from "../../utils/generateKeyValueList.js";
@@ -74,10 +74,5 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts
new file mode 100644
index 0000000..3297616
--- /dev/null
+++ b/src/commands/server/buttons.ts
@@ -0,0 +1,252 @@
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType, CommandInteraction, MessageCreateOptions, ModalBuilder, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
+import type Discord from "discord.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import lodash from "lodash";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
+
+export const command = new SlashCommandSubcommandBuilder()
+    .setName("buttons")
+    .setDescription("Create clickable buttons for verifying, role menus etc.");
+
+interface Data {
+    buttons: string[],
+    title: string | null,
+    description: string | null,
+    color: number,
+    channel: string | null
+}
+
+const colors: Record<string, number> =  {
+    RED: 0xF27878,
+    ORANGE: 0xE5AB71,
+    YELLOW: 0xF2D478,
+    GREEN: 0x65CC76,
+    BLUE: 0x72AEF5,
+    PURPLE: 0xA358B2,
+    PINK: 0xD46899,
+    GRAY: 0x999999,
+}
+
+const buttonNames: Record<string, string> = {
+    verifybutton: "Verify",
+    rolemenu: "Role Menu",
+    createticket: "Create Ticket"
+}
+
+export const callback = async (interaction: CommandInteraction): Promise<void> => {
+
+    const m = await interaction.reply({
+        embeds: LoadingEmbed,
+        fetchReply: true,
+        ephemeral: true
+    });
+
+    let closed = false;
+    const data: Data = {
+        buttons: [],
+        title: null,
+        description: null,
+        color: colors["RED"]!,
+        channel: interaction.channelId
+    }
+    do {
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("edit")
+                    .setLabel("Edit Embed")
+                    .setStyle(ButtonStyle.Secondary),
+                new ButtonBuilder()
+                    .setCustomId("send")
+                    .setLabel("Send")
+                    .setStyle(ButtonStyle.Primary)
+                    .setDisabled(!data.channel)
+            );
+
+        const colorSelect = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("color")
+                    .setPlaceholder("Select a color")
+                    .setMinValues(1)
+                    .addOptions(
+                        Object.keys(colors).map((color: string) => {
+                            return new StringSelectMenuOptionBuilder()
+                                .setLabel(lodash.capitalize(color))
+                                .setValue(color)
+                                .setEmoji(getEmojiByName("COLORS." + color, "id") as APIMessageComponentEmoji)
+                                .setDefault(data.color === colors[color])
+                            }
+                        )
+                    )
+            );
+
+        const buttonSelect = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("button")
+                    .setPlaceholder("Select buttons to add")
+                    .setMinValues(1)
+                    .setMaxValues(3)
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Verify")
+                            .setValue("verifybutton")
+                            .setDescription("Click to get verified in the server")
+                            .setDefault(data.buttons.includes("verifybutton")),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Role Menu")
+                            .setValue("rolemenu")
+                            .setDescription("Click to customize your roles")
+                            .setDefault(data.buttons.includes("rolemenu")),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Ticket")
+                            .setValue("createticket")
+                            .setDescription("Click to create a support ticket")
+                            .setDefault(data.buttons.includes("createticket"))
+                    )
+            )
+
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel")
+                    .setChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement, ChannelType.PublicThread, ChannelType.AnnouncementThread)
+            )
+        let channelName = interaction.guild!.channels.cache.get(data.channel!)?.name;
+        if (data.channel === interaction.channelId) channelName = "this channel";
+        const embed = new EmojiEmbed()
+            .setTitle(data.title ?? "No title set")
+            .setDescription(data.description ?? "*No description set*")
+            .setColor(data.color)
+            .setFooter({text: `Click the button below to edit the embed | The embed will be sent in ${channelName}`});
+
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [colorSelect, buttonSelect, channelMenu, buttons]
+        });
+
+        let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
+        try {
+            i = await interaction.channel!.awaitMessageComponent({
+                filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
+                time: 300000
+            }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "edit": {
+                    await i.showModal(
+                        new ModalBuilder()
+                            .setCustomId("modal")
+                            .setTitle(`Options for ${i.customId}`)
+                            .addComponents(
+                                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("title")
+                                        .setLabel("Title")
+                                        .setMaxLength(256)
+                                        .setRequired(false)
+                                        .setStyle(TextInputStyle.Short)
+                                        .setValue(data.title ?? "")
+                                ),
+                                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("description")
+                                        .setLabel("The text to display below the title")
+                                        .setMaxLength(4000)
+                                        .setRequired(false)
+                                        .setStyle(TextInputStyle.Paragraph)
+                                        .setValue(data.description ?? "")
+                                )
+                            )
+                    );
+                    await interaction.editReply({
+                        embeds: [
+                            new EmojiEmbed()
+                                .setTitle("Button Editor")
+                                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                .setStatus("Success")
+                                .setEmoji("GUILD.TICKET.OPEN")
+                        ],
+                        components: [
+                            new ActionRowBuilder<ButtonBuilder>().addComponents([
+                                new ButtonBuilder()
+                                    .setLabel("Back")
+                                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                    .setStyle(ButtonStyle.Primary)
+                                    .setCustomId("back")
+                            ])
+                        ]
+                    });
+                    let out: Discord.ModalSubmitInteraction | null;
+                    try {
+                        out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
+                    } catch (e) {
+                        closed = true;
+                        continue;
+                    }
+                    if (!out || out.isButton()) continue
+                    data.title = out.fields.getTextInputValue("title").length === 0 ? null : out.fields.getTextInputValue("title");
+                    data.description = out.fields.getTextInputValue("description").length === 0 ? null : out.fields.getTextInputValue("description");
+                    break;
+                }
+                case "send": {
+                    await i.deferUpdate();
+                    const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel;
+                    const components = new ActionRowBuilder<ButtonBuilder>();
+                    for(const button of data.buttons) {
+                        components.addComponents(
+                            new ButtonBuilder()
+                            .setCustomId(button)
+                            .setLabel(buttonNames[button]!)
+                            .setStyle(ButtonStyle.Primary)
+                            );
+                        }
+                    const messageData: MessageCreateOptions = {components: [components]}
+                    if (data.title || data.description) {
+                        const e = new EmojiEmbed()
+                        if(data.title) e.setTitle(data.title);
+                        if(data.description) e.setDescription(data.description);
+                        if(data.color) e.setColor(data.color);
+                        messageData.embeds = [e];
+                    }
+                    await channel.send(messageData);
+                    break;
+                }
+            }
+        } else if(i.isStringSelectMenu()) {
+            try {await i.deferUpdate();} catch (err) {console.log(err)}
+            switch(i.customId) {
+                case "color": {
+                    data.color = colors[i.values[0]!]!;
+                    break;
+                }
+                case "button": {
+                    data.buttons = i.values;
+                    break;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            data.channel = i.values[0]!;
+        }
+
+    } while (!closed);
+    await interaction.deleteReply();
+}
+
+export const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as Discord.GuildMember;
+    if (!member.permissions.has("ManageMessages"))
+        return "You must have the *Manage Messages* permission to use this command";
+    return true;
+};
diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts
new file mode 100644
index 0000000..09b8914
--- /dev/null
+++ b/src/commands/settings/automod.ts
@@ -0,0 +1,922 @@
+import type Discord from "discord.js";
+import { ActionRowBuilder,
+    AnySelectMenuInteraction,
+    APIMessageComponentEmoji,
+    ButtonBuilder,
+    ButtonInteraction,
+    ButtonStyle,
+    ChannelSelectMenuBuilder,
+    ChannelSelectMenuInteraction,
+    CommandInteraction,
+    Message,
+    ModalBuilder,
+    RoleSelectMenuBuilder,
+    RoleSelectMenuInteraction,
+    StringSelectMenuBuilder,
+    StringSelectMenuInteraction,
+    StringSelectMenuOptionBuilder,
+    TextInputBuilder,
+    TextInputStyle,
+    UserSelectMenuBuilder,
+    UserSelectMenuInteraction
+} from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
+import listToAndMore from "../../utils/listToAndMore.js";
+
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder.setName("automod").setDescription("Setting for automatic moderation features");
+
+
+const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id);
+
+const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise<string[]> => {
+
+    const back = new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder().setCustomId("back").setLabel("Back").setStyle(ButtonStyle.Secondary).setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji));
+    let closed;
+    do {
+        let render: string[] = []
+        let mapped: string[] = [];
+        let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder;
+        switch(type) {
+            case "member": {
+                menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25);
+                mapped = await Promise.all(ids.map(async (id) => { return (await client.users.fetch(id).then(user => user.tag)) || "Unknown User" }));
+                render = ids.map(id => client.logger.renderUser(id))
+                break;
+            }
+            case "role": {
+                menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25);
+                mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.roles.fetch(id).then(role => role?.name ?? "Unknown Role"))}));
+                render = ids.map(id => client.logger.renderRole(id, interaction.guild!))
+                break;
+            }
+            case "channel": {
+                menu = new ChannelSelectMenuBuilder().setCustomId("channel").setPlaceholder("Select channels").setMaxValues(25);
+                mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.channels.fetch(id).then(channel => channel?.name ?? "Unknown Role")) }));
+                render = ids.map(id => client.logger.renderChannel(id))
+                break;
+            }
+        }
+        const removeOptions = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("remove")
+                    .setPlaceholder("Remove")
+                    .addOptions(mapped.map((name, i) => new StringSelectMenuOptionBuilder().setLabel(name).setValue(ids[i]!)))
+                    .setDisabled(ids.length === 0)
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle(title)
+            .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN"))
+            .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None"))
+            .setStatus("Success");
+        const components: ActionRowBuilder<
+            StringSelectMenuBuilder |
+            ButtonBuilder |
+            ChannelSelectMenuBuilder |
+            UserSelectMenuBuilder |
+            RoleSelectMenuBuilder
+        >[] = [new ActionRowBuilder<typeof menu>().addComponents(menu)]
+        if(ids.length > 0) components.push(removeOptions);
+        components.push(back);
+
+        await interaction.editReply({embeds: [embed], components: components})
+
+        let i: AnySelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000});
+        } catch(e) {
+            closed = true;
+            continue;
+        }
+
+        if(i.isButton()) {
+            await i.deferUpdate();
+            if(i.customId === "back") {
+                closed = true;
+                break;
+            }
+        } else if(i.isStringSelectMenu()) {
+            await i.deferUpdate();
+            if(i.customId === "remove") {
+                ids = ids.filter(id => id !== (i as StringSelectMenuInteraction).values[0]);
+                if(ids.length === 0) {
+                    menu.data.disabled = true;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            if(i.customId === "user") {
+                ids = ids.concat((i as UserSelectMenuInteraction).values);
+            } else if(i.customId === "role") {
+                ids = ids.concat((i as RoleSelectMenuInteraction).values);
+            } else if(i.customId === "channel") {
+                ids = ids.concat((i as ChannelSelectMenuInteraction).values);
+            }
+        }
+
+    } while(!closed)
+    return ids;
+}
+
+const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
+    NSFW: boolean,
+    size: boolean
+}): Promise<{NSFW: boolean, size: boolean}> => {
+    let closed = false;
+    do {
+        const options = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setLabel("Back")
+                    .setStyle(ButtonStyle.Secondary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("nsfw")
+                    .setLabel("NSFW")
+                    .setStyle(current.NSFW ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.NSFW, "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("size")
+                    .setLabel("Size")
+                    .setStyle(current.size ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.size, "id") as APIMessageComponentEmoji)
+            )
+
+        const embed = new EmojiEmbed()
+            .setTitle("Image Settings")
+            .setDescription(
+                `${emojiFromBoolean(current.NSFW)} **NSFW**\n` +
+                `${emojiFromBoolean(current.size)} **Size**\n`
+            )
+
+        await interaction.editReply({embeds: [embed], components: [options]});
+
+        let i: ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
+        } catch (e) {
+            return current;
+        }
+        await i.deferUpdate();
+        switch(i.customId) {
+            case "back": {
+                closed = true;
+                break;
+            }
+            case "nsfw": {
+                current.NSFW = !current.NSFW;
+                break;
+            }
+            case "size": {
+                current.size = !current.size;
+                break;
+            }
+        }
+    } while(!closed);
+    return current;
+}
+
+const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
+    enabled: boolean,
+    words: {strict: string[], loose: string[]},
+    allowed: {users: string[], roles: string[], channels: string[]}
+}): Promise<{
+    enabled: boolean,
+    words: {strict: string[], loose: string[]},
+    allowed: {users: string[], roles: string[], channels: string[]}
+}> => {
+    let closed = false;
+    do {
+        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("enabled")
+                    .setLabel("Enabled")
+                    .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji),
+            );
+
+        const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("edit")
+                    .setPlaceholder("Edit... ")
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Words")
+                            .setDescription("Edit your list of words to filter")
+                            .setValue("words"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Allowed Users")
+                            .setDescription("Users who will be unaffected by the word filter")
+                            .setValue("allowedUsers"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Allowed Roles")
+                            .setDescription("Roles that will be unaffected by the word filter")
+                            .setValue("allowedRoles"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Allowed Channels")
+                            .setDescription("Channels where the word filter will not apply")
+                            .setValue("allowedChannels")
+                    )
+                    .setDisabled(!current.enabled)
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Word Filters")
+            .setDescription(
+                `${emojiFromBoolean(current.enabled)} **Enabled**\n` +
+                `**Strict Words:** ${listToAndMore(current.words.strict, 5)}\n` +
+                `**Loose Words:** ${listToAndMore(current.words.loose, 5)}\n\n` +
+                `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
+                `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
+                `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+
+        await interaction.editReply({embeds: [embed], components: [selectMenu, buttons]});
+
+        let i: ButtonInteraction | StringSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+
+        if(i.isButton()) {
+            await i.deferUpdate();
+            switch(i.customId) {
+                case "back": {
+                    closed = true;
+                    break;
+                }
+                case "enabled": {
+                    current.enabled = !current.enabled;
+                    break;
+                }
+            }
+        } else {
+            switch(i.values[0]) {
+                case "words": {
+                    await interaction.editReply({embeds: [new EmojiEmbed()
+                        .setTitle("Word Filter")
+                        .setDescription("Modal opened. If you can't see it, click back and try again.")
+                        .setStatus("Success")
+                        .setEmoji("GUILD.SETTINGS.GREEN")
+                    ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
+                        .setLabel("Back")
+                        .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                        .setStyle(ButtonStyle.Primary)
+                        .setCustomId("back")
+                    )]})
+                    const modal = new ModalBuilder()
+                        .setTitle("Word Filter")
+                        .setCustomId("wordFilter")
+                        .addComponents(
+                            new ActionRowBuilder<TextInputBuilder>()
+                                .addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("wordStrict")
+                                        .setLabel("Strict Words")
+                                        .setPlaceholder("Matches anywhere in the message, including surrounded by other characters")
+                                        .setValue(current.words.strict.join(", "))
+                                        .setStyle(TextInputStyle.Paragraph)
+                                        .setRequired(false)
+                                ),
+                            new ActionRowBuilder<TextInputBuilder>()
+                                .addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("wordLoose")
+                                        .setLabel("Loose Words")
+                                        .setPlaceholder("Matches only if the word is by itself, surrounded by spaces or punctuation")
+                                        .setValue(current.words.loose.join(", "))
+                                        .setStyle(TextInputStyle.Paragraph)
+                                        .setRequired(false)
+                                )
+                        )
+
+                    await i.showModal(modal);
+                    let out;
+                    try {
+                        out = await modalInteractionCollector(m, interaction.user);
+                    } catch (e) {
+                        break;
+                    }
+                    if (!out) break;
+                    if(out.isButton()) break;
+                    current.words.strict = out.fields.getTextInputValue("wordStrict")
+                        .split(",").map(s => s.trim()).filter(s => s.length > 0);
+                    current.words.loose = out.fields.getTextInputValue("wordLoose")
+                        .split(",").map(s => s.trim()).filter(s => s.length > 0);
+                    break;
+                }
+                case "allowedUsers": {
+                    await i.deferUpdate();
+                    current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter");
+                    break;
+                }
+                case "allowedRoles": {
+                    await i.deferUpdate();
+                    current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter");
+                    break;
+                }
+                case "allowedChannels": {
+                    await i.deferUpdate();
+                    current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter");
+                    break;
+                }
+            }
+        }
+    } while(!closed);
+    return current;
+}
+
+const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
+    enabled: boolean,
+    allowed: {users: string[], roles: string[], channels: string[]}
+}): Promise<{
+    enabled: boolean,
+    allowed: {users: string[], roles: string[], channels: string[]}
+}> => {
+
+    let closed = false;
+    do {
+        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("enabled")
+                    .setLabel(current.enabled ? "Enabled" : "Disabled")
+                    .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji)
+            );
+        const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
+                .addComponents(
+                    new StringSelectMenuBuilder()
+                        .setCustomId("toEdit")
+                        .setPlaceholder("Edit your allow list")
+                        .addOptions(
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Users")
+                                .setDescription("Users that are allowed to send invites")
+                                .setValue("users"),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Roles")
+                                .setDescription("Roles that are allowed to send invites")
+                                .setValue("roles"),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Channels")
+                                .setDescription("Channels that anyone is allowed to send invites in")
+                                .setValue("channels")
+                        ).setDisabled(!current.enabled)
+                )
+
+        const embed = new EmojiEmbed()
+            .setTitle("Invite Settings")
+            .setDescription(
+                "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + `\n\n` +
+                `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` +
+                `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
+                `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
+                `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+
+
+        await interaction.editReply({embeds: [embed], components: [menu, buttons]});
+
+        let i: ButtonInteraction | StringSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            return current;
+        }
+
+        if(i.isButton()) {
+            await i.deferUpdate();
+            switch(i.customId) {
+                case "back": {
+                    closed = true;
+                    break;
+                }
+                case "enabled": {
+                    current.enabled = !current.enabled;
+                    break;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            switch(i.values[0]) {
+                case "users": {
+                    current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings");
+                    break;
+                }
+                case "roles": {
+                    current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings");
+                    break;
+                }
+                case "channels": {
+                    current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings");
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+    return current;
+}
+
+const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
+    mass: number,
+    everyone: boolean,
+    roles: boolean,
+    allowed: {
+        roles: string[],
+        rolesToMention: string[],
+        users: string[],
+        channels: string[]
+    }
+}): Promise<{
+    mass: number,
+    everyone: boolean,
+    roles: boolean,
+    allowed: {
+        roles: string[],
+        rolesToMention: string[],
+        users: string[],
+        channels: string[]
+    }
+}> => {
+    let closed = false;
+
+    do {
+
+        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("everyone")
+                    .setLabel(current.everyone ? "Everyone" : "No one")
+                    .setStyle(current.everyone ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.everyone, "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("roles")
+                    .setLabel(current.roles ? "Roles" : "No roles")
+                    .setStyle(current.roles ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(emojiFromBoolean(current.roles, "id") as APIMessageComponentEmoji)
+            );
+        const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("toEdit")
+                    .setPlaceholder("Edit mention settings")
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Mass Mention Amount")
+                            .setDescription("The amount of mentions before the bot will delete the message")
+                            .setValue("mass"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Roles")
+                            .setDescription("Roles that are able to be mentioned")
+                            .setValue("roles"),
+                    )
+            )
+
+        const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("allowed")
+                    .setPlaceholder("Edit exceptions")
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                                .setLabel("Users")
+                                .setDescription("Users that are unaffected by the mention filter")
+                                .setValue("users"),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Roles")
+                                .setDescription("Roles that are unaffected by the mention filter")
+                                .setValue("roles"),
+                            new StringSelectMenuOptionBuilder()
+                                .setLabel("Channels")
+                                .setDescription("Channels where anyone is unaffected by the mention filter")
+                                .setValue("channels")
+                    )
+            )
+
+        const embed = new EmojiEmbed()
+            .setTitle("Mention Settings")
+            .setDescription(
+                `Log when members mention:\n` +
+                `${emojiFromBoolean(true)} **${current.mass}+ members** in one message\n` +
+                `${emojiFromBoolean(current.everyone)} **Everyone**\n` +
+                `${emojiFromBoolean(current.roles)} **Roles**\n` +
+                (current.allowed.rolesToMention.length > 0 ? `> *Except for ${listToAndMore(current.allowed.rolesToMention.map(r => `<@&${r}>`), 3)}*\n` : "") +
+                "\n" +
+                `Except if...\n` +
+                `> ${current.allowed.users.length > 0 ? `Member is: ${listToAndMore(current.allowed.users.map(u => `<@${u}>`), 3)}\n` : ""}` +
+                `> ${current.allowed.roles.length > 0 ? `Member has role: ${listToAndMore(current.allowed.roles.map(r => `<@&${r}>`), 3)}\n` : ""}` +
+                `> ${current.allowed.channels.length > 0 ? `In channel: ${listToAndMore(current.allowed.channels.map(c => `<#${c}>`), 3)}\n` : ""}`
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+
+        await interaction.editReply({embeds: [embed], components: [menu, allowedMenu, buttons]});
+
+        let i: ButtonInteraction | StringSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+
+        if(i.isButton()) {
+            await i.deferUpdate();
+            switch (i.customId) {
+                case "back": {
+                    closed = true;
+                    break;
+                }
+                case "everyone": {
+                    current.everyone = !current.everyone;
+                    break;
+                }
+                case "roles": {
+                    current.roles = !current.roles;
+                    break;
+                }
+            }
+        } else {
+            switch (i.customId) {
+                case "toEdit": {
+                    switch (i.values[0]) {
+                        case "mass": {
+                            await interaction.editReply({embeds: [new EmojiEmbed()
+                                .setTitle("Word Filter")
+                                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                .setStatus("Success")
+                                .setEmoji("GUILD.SETTINGS.GREEN")
+                            ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
+                                .setLabel("Back")
+                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                .setStyle(ButtonStyle.Primary)
+                                .setCustomId("back")
+                            )]})
+                            const modal = new ModalBuilder()
+                                .setTitle("Mass Mention Amount")
+                                .setCustomId("mass")
+                                .addComponents(
+                                    new ActionRowBuilder<TextInputBuilder>()
+                                        .addComponents(
+                                            new TextInputBuilder()
+                                                .setCustomId("mass")
+                                                .setPlaceholder("Amount")
+                                                .setMinLength(1)
+                                                .setMaxLength(3)
+                                                .setStyle(TextInputStyle.Short)
+                                        )
+                                )
+                            await i.showModal(modal);
+                            let out;
+                            try {
+                                out = await modalInteractionCollector(m, interaction.user);
+                            } catch (e) {
+                                break;
+                            }
+                            if (!out) break;
+                            if(out.isButton()) break;
+                            current.mass = parseInt(out.fields.getTextInputValue("mass"));
+                            break;
+                        }
+                        case "roles": {
+                            await i.deferUpdate();
+                            current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings");
+                            break;
+                        }
+                    }
+                    break;
+                }
+                case "allowed": {
+                    await i.deferUpdate();
+                    switch (i.values[0]) {
+                        case "users": {
+                            current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
+                            break;
+                        }
+                        case "roles": {
+                            current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
+                            break;
+                        }
+                        case "channels": {
+                            current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings");
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+    return current
+}
+
+const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, current?: {
+    channels?: string[],
+    allowed?: {
+        roles: string[],
+        users: string[]
+    }
+}): Promise<{
+    channels: string[],
+    allowed: {
+        roles: string[],
+        users: string[]
+    }
+}> => {
+    let closed = false;
+    if(!current) current = {channels: [], allowed: {roles: [], users: []}};
+    if(!current.channels) current.channels = [];
+    if(!current.allowed) current.allowed = {roles: [], users: []};
+
+    const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+        .addComponents(
+            new ChannelSelectMenuBuilder()
+                .setCustomId("toAdd")
+                .setPlaceholder("Select a channel")
+        )
+
+    const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+        .addComponents(
+            new StringSelectMenuBuilder()
+                .setCustomId("allowed")
+                .setPlaceholder("Edit exceptions")
+                .addOptions(
+                    new StringSelectMenuOptionBuilder()
+                            .setLabel("Users")
+                            .setDescription("Users that are unaffected by the mention filter")
+                            .setValue("users"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Roles")
+                            .setDescription("Roles that are unaffected by the mention filter")
+                            .setValue("roles")
+                )
+        )
+
+    do {
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("back")
+                    .setLabel("Back")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+            )
+
+        const embed = new EmojiEmbed()
+            .setTitle("Clean Settings")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+            .setDescription(
+                `Current clean channels:\n\n` +
+                `${current.channels.length > 0 ? listToAndMore(current.channels.map(c => `<#${c}>`), 10) : "None"}\n\n`
+            )
+            .setStatus("Success")
+
+
+        await interaction.editReply({embeds: [embed], components: [channelMenu, allowedMenu, buttons]});
+
+        let i: ButtonInteraction | ChannelSelectMenuInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | ChannelSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            break;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    closed = true;
+                    break;
+                }
+            }
+        } else {
+            switch (i.customId) {
+                case "toAdd": {
+                    const channelEmbed = new EmojiEmbed()
+                        .setTitle("Clean Settings")
+                        .setDescription(`Editing <#${i.values[0]}>`)
+                        .setEmoji("GUILD.SETTINGS.GREEN")
+                        .setStatus("Success")
+                    const channelButtons = new ActionRowBuilder<ButtonBuilder>()
+                        .addComponents(
+                            new ButtonBuilder()
+                                .setCustomId("back")
+                                .setLabel("Back")
+                                .setStyle(ButtonStyle.Primary)
+                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id")),
+                            new ButtonBuilder()
+                                .setCustomId("switch")
+                                .setLabel(current.channels.includes(i.values[0]!) ? "Remove" : "Add")
+                                .setStyle(current.channels.includes(i.values[0]!) ? ButtonStyle.Danger : ButtonStyle.Success)
+                        )
+
+                    await i.editReply({embeds: [channelEmbed], components: [channelButtons]});
+                    let j: ButtonInteraction;
+                    try {
+                        j = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
+                    } catch (e) {
+                        closed = true;
+                        break;
+                    }
+                    await j.deferUpdate();
+                    switch (j.customId) {
+                        case "back": {
+                            break;
+                        }
+                        case "switch": {
+                            if(current.channels.includes(i.values[0]!)) {
+                                current.channels.splice(current.channels.indexOf(i.values[0]!), 1);
+                            } else {
+                                current.channels.push(i.values[0]!);
+                            }
+                        }
+                    }
+                    break;
+                }
+                case "allowed": {
+                    switch (i.values[0]) {
+                        case "users": {
+                            current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
+                            break;
+                        }
+                        case "roles": {
+                            current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+
+    return current as {
+        channels: string[],
+        allowed: {
+            roles: string[],
+            users: string[]
+        }
+    };
+
+}
+
+const callback = async (interaction: CommandInteraction): Promise<void> => {
+    if (!interaction.guild) return;
+    const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
+    const config = (await client.database.guilds.read(interaction.guild.id)).filters;
+
+    let closed = false;
+
+    const button = new ActionRowBuilder<ButtonBuilder>()
+        .addComponents(
+            new ButtonBuilder()
+                .setCustomId("save")
+                .setLabel("Save")
+                .setStyle(ButtonStyle.Success)
+        )
+
+    do {
+
+        const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("filter")
+                    .setPlaceholder("Select a filter to edit")
+                    .addOptions(
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Invites")
+                            .setDescription("Automatically delete messages containing server invites")
+                            .setValue("invites"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Mentions")
+                            .setDescription("Deletes messages with excessive mentions")
+                            .setValue("mentions"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Words")
+                            .setDescription("Delete messages containing filtered words")
+                            .setValue("words"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Malware")
+                            .setDescription("Automatically delete files and links containing malware")
+                            .setValue("malware"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Images")
+                            .setDescription("Checks performed on images (NSFW, size checking, etc.)")
+                            .setValue("images"),
+                        new StringSelectMenuOptionBuilder()
+                            .setLabel("Clean")
+                            .setDescription("Automatically delete new messages in specific channels")
+                            .setValue("clean")
+                    )
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Automod Settings")
+            .setDescription(
+                `${emojiFromBoolean(config.invite.enabled)} **Invites**\n` +
+                `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` +
+                `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` +
+                `${emojiFromBoolean(config.malware)} **Malware**\n` +
+                `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n` +
+                `${emojiFromBoolean(config.clean.channels.length > 0)} **Clean**\n`
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.SETTINGS.GREEN")
+
+
+        await interaction.editReply({embeds: [embed], components: [selectMenu, button]});
+
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 300000}) as StringSelectMenuInteraction | ButtonInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            await client.database.guilds.write(interaction.guild.id, {filters: config});
+        } else {
+            switch(i.values[0]) {
+                case "invites": {
+                    config.invite = await inviteMenu(i, m, config.invite);
+                    break;
+                }
+                case "mentions": {
+                    config.pings = await mentionMenu(i, m, config.pings);
+                    break;
+                }
+                case "words": {
+                    config.wordFilter = await wordMenu(i, m, config.wordFilter);
+                    break;
+                }
+                case "malware": {
+                    config.malware = !config.malware;
+                    break;
+                }
+                case "images": {
+                    const next = await imageMenu(i, m, config.images);
+                    config.images = next;
+                    break;
+                }
+                case "clean": {
+                    const next = await cleanMenu(i, m, config.clean);
+                    config.clean = next;
+                    break;
+                }
+            }
+        }
+
+    } while(!closed);
+    await interaction.deleteReply()
+
+};
+
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as Discord.GuildMember;
+    if (!member.permissions.has("ManageMessages"))
+        return "You must have the *Manage Messages* permission to use this command";
+    return true;
+};
+
+export { command };
+export { callback };
+export { check };
diff --git a/src/commands/settings/autopublish.ts b/src/commands/settings/autopublish.ts
new file mode 100644
index 0000000..1dc97e0
--- /dev/null
+++ b/src/commands/settings/autopublish.ts
@@ -0,0 +1,96 @@
+import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js";
+import type Discord from "discord.js";
+import client from "../../utils/client.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import compare from "lodash"
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+
+export const command = new SlashCommandSubcommandBuilder()
+    .setName("autopublish")
+    .setDescription("Automatically publish messages posted in announcement channels");
+
+export const callback = async (interaction: CommandInteraction): Promise<void> => {
+    await interaction.reply({
+        embeds: LoadingEmbed,
+        ephemeral: true,
+        fetchReply: true
+    });
+
+    let closed = false;
+    let config = await client.database.guilds.read(interaction.guild!.id);
+    let data = Object.assign({}, config.autoPublish);
+    do {
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("switch")
+                    .setLabel(data.enabled ? "Disabled" : "Enabled")
+                    .setStyle(data.enabled ? ButtonStyle.Danger : ButtonStyle.Success)
+                    .setEmoji(data.enabled ? "✅" : "❌"),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji("💾")
+                    .setDisabled(compare.isEqual(data, config.autoPublish))
+            );
+
+        const channelSelect = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel")
+                    .setMinValues(1)
+            );
+
+        const embed = new EmojiEmbed()
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [channelSelect, buttons]
+        });
+
+        let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction;
+        try {
+            i = await interaction.channel!.awaitMessageComponent({
+                filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
+                time: 300000
+            }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "switch": {
+                    data.enabled = !data.enabled;
+                    break;
+                }
+                case "save": {
+                    await client.database.guilds.write(interaction.guild!.id, { "autoPublish": data });
+                    config = await client.database.guilds.read(interaction.guild!.id);
+                    data = Object.assign({}, config.autoPublish);
+                    break;
+                }
+            }
+        } else {
+            for(const channel of i.values) {
+                data.channels.includes(channel) ? data.channels.splice(data.channels.indexOf(channel), 1) : data.channels.push(channel);
+            }
+        }
+
+    } while (!closed);
+
+    await interaction.deleteReply();
+}
+
+export const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as Discord.GuildMember;
+    const me = interaction.guild!.members.me!;
+    if (!member.permissions.has("ManageMessages"))
+        return "You must have the *Manage Messages* permission to use this command";
+    if (_partial) return true;
+    if (!me.permissions.has("ManageMessages")) return "I do not have the *Manage Messages* permission";
+    return true;
+};
diff --git a/src/commands/settings/filters.ts b/src/commands/settings/filters.ts
deleted file mode 100644
index 2e6f4c5..0000000
--- a/src/commands/settings/filters.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type Discord from "discord.js";
-import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder.setName("filter").setDescription("Setting for message filters");
-
-const callback = async (_interaction: CommandInteraction): Promise<void> => {
-    console.log("Filters");
-};
-
-const check = (interaction: CommandInteraction) => {
-    const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("ManageMessages"))
-        return "You must have the *Manage Messages* permission to use this command";
-    return true;
-};
-
-export { command };
-export { callback };
-export { check };
diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts
index 2709bee..238b8b9 100644
--- a/src/commands/settings/logs/attachment.ts
+++ b/src/commands/settings/logs/attachment.ts
@@ -1,198 +1,110 @@
 import { LoadingEmbed } from "../../../utils/defaults.js";
-import { ChannelType } from "discord-api-types/v9";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction } from "discord.js";
+import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType } from "discord.js";
 import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../../utils/confirmationMessage.js";
 import getEmojiByName from "../../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../../utils/client.js";
+import { getCommandMentionByName } from "../../../utils/getCommandDataByName.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("attachments")
         .setDescription("Where attachments should be logged to (Premium only)")
-        .addChannelOption((option) =>
-            option
-                .setName("channel")
-                .setDescription("The channel to log attachments in")
-                .addChannelTypes(ChannelType.GuildText)
-                .setRequired(false)
-        );
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const m = (await interaction.reply({
+    if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {});
+    await interaction.reply({
         embeds: LoadingEmbed,
         ephemeral: true,
         fetchReply: true
-    })) as Discord.Message;
-    if (interaction.options.get("channel")?.channel) {
-        let channel;
-        try {
-            channel = interaction.options.get("channel")?.channel;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Attachment Log Channel")
-                        .setDescription("The channel you provided is not a valid channel")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        channel = channel as Discord.TextChannel;
-        if (channel.guild.id !== interaction.guild!.id) {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Attachment Log Channel")
-                        .setDescription("You must choose a channel in this server")
-                        .setStatus("Danger")
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                ]
-            });
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("CHANNEL.TEXT.EDIT")
-            .setTitle("Attachment Log Channel")
-            .setDescription(
-                "This will be the channel all attachments will be sent to.\n\n" +
-                    `Are you sure you want to set the attachment log channel to <#${channel.id}>?`
-            )
-            .setColor("Warning")
-            .setFailedMessage("Attachment log channel not set", "Warning", "CHANNEL.TEXT.DELETE")
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "logging.attachments.channel": channel.id
-                });
-                const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
-                const data = {
-                    meta: {
-                        type: "attachmentChannelUpdate",
-                        displayName: "Attachment Log Channel Updated",
-                        calculateType: "nucleusSettingsUpdated",
-                        color: NucleusColors.yellow,
-                        emoji: "CHANNEL.TEXT.EDIT",
-                        timestamp: new Date().getTime()
-                    },
-                    list: {
-                        memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                        changedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                        channel: entry(channel.id, renderChannel(channel))
-                    },
-                    hidden: {
-                        guild: interaction.guild!.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Attachment Log Channel")
-                            .setDescription("Something went wrong and the attachment log channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Attachment Log Channel")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("CHANNEL.TEXT.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let clicks = 0;
-    const data = await client.database.guilds.read(interaction.guild!.id);
-    let channel = data.logging.staff.channel;
+    })
 
-    let timedOut = false;
-    while (!timedOut) {
-        await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Attachment Log Channel")
-                    .setDescription(
-                        channel
-                            ? `Your attachment log channel is currently set to <#${channel}>`
-                            : "This server does not have an attachment log channel" +
-                                  (await client.database.premium.hasPremium(interaction.guild!.id)
-                                      ? ""
-                                      : "\n\nThis server does not have premium, so this feature is disabled")
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setCustomId("clear")
-                        .setLabel(clicks ? "Click again to confirm" : "Reset channel")
-                        .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!channel)
-                ])
-            ]
-        });
-        let i;
-        try {
-            i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            });
-        } catch (e) {
-            timedOut = true;
-            continue;
-        }
-        i.deferUpdate();
-        if ((i.component as unknown as ButtonInteraction).customId === "clear") {
-            clicks += 1;
-            if (clicks === 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild!.id, null, ["logging.announcements.channel"]);
-                channel = null;
-            }
-        }
-    }
-    await interaction.editReply({
+    if(!await client.database.premium.hasPremium(interaction.guild!.id)) return interaction.editReply({
         embeds: [
             new EmojiEmbed()
-                .setTitle("Attachment Log Channel")
-                .setDescription(
-                    channel
-                        ? `Your attachment log channel is currently set to <#${channel}>`
-                        : "This server does not have an attachment log channel"
-                )
-                .setStatus("Success")
-                .setEmoji("CHANNEL.TEXT.CREATE")
-                .setFooter({ text: "Message closed" })
-        ],
-        components: [
-            new ActionRowBuilder<ButtonBuilder>().addComponents([
+                .setTitle("Premium Required")
+                .setDescription(`This feature is exclusive to ${getCommandMentionByName("nucleus/premium")} servers.`)
+                .setStatus("Danger")
+                .setEmoji("NUCLEUS.PREMIUM")
+        ]
+    });
+
+    let data = await client.database.guilds.read(interaction.guild!.id);
+    let channel = data.logging.staff.channel;
+
+    let closed = false;
+    do {
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel")
+                    .setChannelTypes(ChannelType.GuildText)
+            );
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
                 new ButtonBuilder()
                     .setCustomId("clear")
                     .setLabel("Clear")
-                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                    .setStyle(ButtonStyle.Secondary)
-                    .setDisabled(true)
-            ])
-        ]
-    });
+                    .setStyle(ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as Discord.APIMessageComponentEmoji)
+                    .setDisabled(!channel),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as Discord.APIMessageComponentEmoji)
+                    .setDisabled(channel === data.logging.staff.channel)
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Attachments")
+            .setDescription(
+                `The channel to send all attachments from the server, allowing you to check them if they are deleted\n` +
+                `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n`
+            )
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [channelMenu, buttons]
+        });
+
+        let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction;
+        try {
+            i = (await interaction.channel!.awaitMessageComponent({
+                filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
+                time: 300000
+            })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            switch (i.customId) {
+                case "clear": {
+                    channel = null;
+                    break;
+                }
+                case "save": {
+                    await client.database.guilds.write(interaction.guild!.id, {
+                        "logging.attachments.channel": channel
+                    });
+                    data = await client.database.guilds.read(interaction.guild!.id);
+                    break;
+                }
+            }
+        } else {
+            channel = i.values[0]!;
+        }
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts
deleted file mode 100644
index 992491a..0000000
--- a/src/commands/settings/logs/channel.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-import { LoadingEmbed } from "../../../utils/defaults.js";
-import { ChannelType } from "discord-api-types/v9";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction, ButtonComponent } from "discord.js";
-import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../../utils/confirmationMessage.js";
-import getEmojiByName from "../../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import client from "../../../utils/client.js";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-        .setName("channel")
-        .setDescription("Sets or shows the log channel")
-        .addChannelOption((option) =>
-            option
-                .setName("channel")
-                .setDescription("The channel to set the log channel to")
-                .addChannelTypes(ChannelType.GuildText)
-        );
-
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const m = (await interaction.reply({
-        embeds: LoadingEmbed,
-        ephemeral: true,
-        fetchReply: true
-    })) as Discord.Message;
-    if (interaction.options.get("channel")?.channel) {
-        let channel;
-        try {
-            channel = interaction.options.get("channel")?.channel;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Log Channel")
-                        .setDescription("The channel you provided is not a valid channel")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        channel = channel as Discord.TextChannel;
-        if (channel.guild.id !== interaction.guild!.id) {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Log Channel")
-                        .setDescription("You must choose a channel in this server")
-                        .setStatus("Danger")
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                ]
-            });
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("CHANNEL.TEXT.EDIT")
-            .setTitle("Log Channel")
-            .setDescription(`Are you sure you want to set the log channel to <#${channel.id}>?`)
-            .setColor("Warning")
-            .setFailedMessage("The log channel was not changed", "Danger", "CHANNEL.TEXT.DELETE")
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "logging.logs.channel": channel.id
-                });
-                const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
-                const data = {
-                    meta: {
-                        type: "logChannelUpdate",
-                        displayName: "Log Channel Changed",
-                        calculateType: "nucleusSettingsUpdated",
-                        color: NucleusColors.yellow,
-                        emoji: "CHANNEL.TEXT.EDIT",
-                        timestamp: new Date().getTime()
-                    },
-                    list: {
-                        memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                        changedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                        channel: entry(channel.id, renderChannel(channel))
-                    },
-                    hidden: {
-                        guild: channel.guild.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                console.log(e);
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Log Channel")
-                            .setDescription("Something went wrong and the log channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Log Channel")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("CHANNEL.TEXT.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let clicks = 0;
-    const data = await client.database.guilds.read(interaction.guild!.id);
-    let channel = data.logging.logs.channel;
-    let timedOut = false;
-    while (!timedOut) {
-        await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Log channel")
-                    .setDescription(
-                        channel
-                            ? `Your log channel is currently set to <#${channel}>`
-                            : "This server does not have a log channel"
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setCustomId("clear")
-                        .setLabel(clicks ? "Click again to confirm" : "Reset channel")
-                        .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!channel)
-                ])
-            ]
-        });
-        let i: ButtonInteraction;
-        try {
-            i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            }) as ButtonInteraction;
-        } catch (e) {
-            timedOut = true;
-        }
-        i = i!
-        i.deferUpdate();
-        if ((i.component as ButtonComponent).customId === "clear") {
-            clicks += 1;
-            if (clicks === 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild!.id, null, ["logging.logs.channel"]);
-                channel = null;
-            }
-        }
-    }
-    await interaction.editReply({
-        embeds: [
-            new EmojiEmbed()
-                .setTitle("Log channel")
-                .setDescription(
-                    channel
-                        ? `Your log channel is currently set to <#${channel}>`
-                        : "This server does not have a log channel"
-                )
-                .setStatus("Success")
-                .setEmoji("CHANNEL.TEXT.CREATE")
-                .setFooter({ text: "Message closed" })
-        ],
-        components: [
-            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                new ButtonBuilder()
-                    .setCustomId("clear")
-                    .setLabel("Clear")
-                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                    .setStyle(ButtonStyle.Secondary)
-                    .setDisabled(true)
-            ])
-        ]
-    });
-};
-
-const check = (interaction: CommandInteraction) => {
-    const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("ManageGuild"))
-        return "You must have the *Manage Server* permission to use this command";
-    return true;
-};
-
-export { command };
-export { callback };
-export { check };
diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts
index fbe79fa..eeef8fb 100644
--- a/src/commands/settings/logs/events.ts
+++ b/src/commands/settings/logs/events.ts
@@ -1,9 +1,11 @@
 import { LoadingEmbed } from "../../../utils/defaults.js";
-import Discord, { CommandInteraction, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, EmbedBuilder, StringSelectMenuInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders";
-import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import Discord, { CommandInteraction, ActionRowBuilder, ChannelSelectMenuBuilder, ChannelType, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ButtonInteraction, StringSelectMenuInteraction, ChannelSelectMenuInteraction, APIMessageComponentEmoji } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../../utils/client.js";
+import compare from "lodash";
 import { toHexArray, toHexInteger } from "../../../utils/calculate.js";
+import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../../utils/getEmojiByName.js";
 
 const logs: Record<string, string> = {
     channelUpdate: "Channels created, deleted or modified",
@@ -24,88 +26,138 @@
     webhookUpdate: "Webhooks created or deleted",
     guildMemberVerify: "Member runs verify",
     autoModeratorDeleted: "Messages auto deleted by Nucleus",
-    nucleusSettingsUpdated: "Nucleus' settings updated by a moderator",
-    ticketUpdate: "Tickets created or deleted"
+    ticketUpdate: "Tickets created or deleted",
+    //nucleusSettingsUpdated: "Nucleus' settings updated by a moderator"  // TODO
 };
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder.setName("events").setDescription("Sets what events should be logged");
+    builder
+        .setName("events")
+        .setDescription("The general log channel for the server, and setting what events to show")
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    await interaction.reply({
+    const m = (await interaction.reply({
         embeds: LoadingEmbed,
-        fetchReply: true,
-        ephemeral: true
-    });
-    let m: Message;
-    let timedOut = false;
+        ephemeral: true,
+        fetchReply: true
+    })) as Discord.Message;
+
+    let config = await client.database.guilds.read(interaction.guild!.id);
+    let data = Object.assign({}, config.logging.logs);
+    let closed = false;
+    let show = false;
     do {
-        const config = await client.database.guilds.read(interaction.guild!.id);
-        const converted = toHexArray(config.logging.logs.toLog);
-        const selectPane = new StringSelectMenuBuilder()
-            .setPlaceholder("Set events to log")
-            .setMaxValues(Object.keys(logs).length)
-            .setCustomId("logs")
-            .setMinValues(0)
-        Object.keys(logs).map((e, i) => {
-            selectPane.addOptions(new StringSelectMenuOptionBuilder()
-                .setLabel(logs[e]!)
-                .setValue(i.toString())
-                .setDefault(converted.includes(e))
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel")
+                    .setChannelTypes(ChannelType.GuildText)
+            )
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("switch")
+                    .setLabel(data.enabled ? "Enabled" : "Disabled")
+                    .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName((data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS"), "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("remove")
+                    .setLabel("Remove")
+                    .setStyle(ButtonStyle.Danger)
+                    .setDisabled(!data.channel),
+                new ButtonBuilder()
+                    .setCustomId("show")
+                    .setLabel("Manage Events")
+                    .setStyle(ButtonStyle.Primary),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(compare.isEqual(data, config.logging.logs))
+            )
+
+        const converted = toHexArray(data.toLog);
+        const toLogMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setPlaceholder("Set events to log")
+                    .setMaxValues(Object.keys(logs).length)
+                    .setCustomId("logs")
+                    .setMinValues(0)
+            )
+        Object.keys(logs).map((e) => {
+            toLogMenu.components[0]!.addOptions(
+                new StringSelectMenuOptionBuilder()
+                    .setLabel(logs[e]!)
+                    .setValue(e)
+                    .setDefault(converted.includes(e))
             )
         });
-        m = (await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Logging Events")
-                    .setDescription(
-                        "Below are the events being logged in the server. You can toggle them on and off in the dropdown"
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(selectPane),
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder().setLabel("Select all").setStyle(ButtonStyle.Primary).setCustomId("all"),
-                    new ButtonBuilder().setLabel("Select none").setStyle(ButtonStyle.Danger).setCustomId("none")
-                ])
-            ]
-        })) as Message;
-        let i;
+
+        const embed = new EmojiEmbed()
+            .setTitle("General Log Channel")
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+            .setDescription(
+                `This is the channel that all events you set to be logged will be stored\n` +
+                `**Channel:** ${data.channel ? `<#${data.channel}>` : "None"}\n`
+            )
+
+        const components: ActionRowBuilder<ButtonBuilder | ChannelSelectMenuBuilder | StringSelectMenuBuilder>[] = [channelMenu, buttons];
+        if(show) components.push(toLogMenu);
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: components
+        });
+
+        let i: ButtonInteraction | StringSelectMenuInteraction | ChannelSelectMenuInteraction;
         try {
             i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            });
+                filter: (i) => i.user.id === interaction.user.id,
+                time: 300000
+            }) as ButtonInteraction | StringSelectMenuInteraction | ChannelSelectMenuInteraction;
         } catch (e) {
-            timedOut = true;
+            closed = true;
             continue;
         }
-        i.deferUpdate();
-        if (i.customId === "logs") {
-            const selected = (i as StringSelectMenuInteraction).values;
-            const newLogs = toHexInteger(selected.map((e: string) => Object.keys(logs)[parseInt(e)]!));
-            await client.database.guilds.write(interaction.guild!.id, {
-                "logging.logs.toLog": newLogs
-            });
-        } else if (i.customId === "all") {
-            const newLogs = toHexInteger(Object.keys(logs).map((e) => e));
-            await client.database.guilds.write(interaction.guild!.id, {
-                "logging.logs.toLog": newLogs
-            });
-        } else if (i.customId === "none") {
-            await client.database.guilds.write(interaction.guild!.id, {
-                "logging.logs.toLog": 0
-            });
-        }
-    } while (!timedOut);
 
-    await interaction.editReply({ embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })] });
-    return;
+        await i.deferUpdate();
+
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "show": {
+                    show = !show;
+                    break;
+                }
+                case "switch": {
+                    data.enabled = !data.enabled;
+                    break;
+                }
+                case "save": {
+                    await client.database.guilds.write(interaction.guild!.id, {"logging.logs": data});
+                    config = await client.database.guilds.read(interaction.guild!.id);
+                    data = Object.assign({}, config.logging.logs);
+                    break;
+                }
+                case "remove": {
+                    data.channel = null;
+                    break;
+                }
+            }
+        } else if(i.isStringSelectMenu()) {
+            const hex = toHexInteger(i.values);
+            data.toLog = hex;
+        } else if(i.isChannelSelectMenu()) {
+            data.channel = i.values[0]!;
+        }
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts
deleted file mode 100644
index 13125ef..0000000
--- a/src/commands/settings/logs/staff.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-import { LoadingEmbed } from "../../../utils/defaults.js";
-import { ChannelType } from "discord-api-types/v9";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent } from "discord.js";
-import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../../utils/confirmationMessage.js";
-import getEmojiByName from "../../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import client from "../../../utils/client.js";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-        .setName("staff")
-        .setDescription("Settings for the staff notifications channel")
-        .addChannelOption((option) =>
-            option
-                .setName("channel")
-                .setDescription("The channel to set the staff notifications channel to")
-                .addChannelTypes(ChannelType.GuildText)
-                .setRequired(false)
-        );
-
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    if (!interaction.guild) return;
-    const m = (await interaction.reply({
-        embeds: LoadingEmbed,
-        ephemeral: true,
-        fetchReply: true
-    })) as Discord.Message;
-    if (interaction.options.get("channel")?.channel) {
-        let channel;
-        try {
-            channel = interaction.options.get("channel")?.channel;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Staff Notifications Channel")
-                        .setDescription("The channel you provided is not a valid channel")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        channel = channel as Discord.TextChannel;
-        if (channel.guild.id !== interaction.guild.id) {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Staff Notifications Channel")
-                        .setDescription("You must choose a channel in this server")
-                        .setStatus("Danger")
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                ]
-            });
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("CHANNEL.TEXT.EDIT")
-            .setTitle("Staff Notifications Channel")
-            .setDescription(
-                "This will be the channel all notifications, updates, user reports etc. will be sent to.\n\n" +
-                    `Are you sure you want to set the staff notifications channel to <#${channel.id}>?`
-            )
-            .setColor("Warning")
-            .setFailedMessage("Staff notifications channel not set", "Warning", "CHANNEL.TEXT.DELETE")
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                await client.database.guilds.write(interaction.guild.id, {
-                    "logging.staff.channel": channel.id
-                });
-                const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
-                const data = {
-                    meta: {
-                        type: "staffChannelUpdate",
-                        displayName: "Staff Notifications Channel Updated",
-                        calculateType: "nucleusSettingsUpdated",
-                        color: NucleusColors.yellow,
-                        emoji: "CHANNEL.TEXT.EDIT",
-                        timestamp: new Date().getTime()
-                    },
-                    list: {
-                        memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                        changedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                        channel: entry(channel.id, renderChannel(channel))
-                    },
-                    hidden: {
-                        guild: interaction.guild.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Staff Notifications Channel")
-                            .setDescription("Something went wrong and the staff notifications channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Staff Notifications Channel")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("CHANNEL.TEXT.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let clicks = 0;
-    const data = await client.database.guilds.read(interaction.guild.id);
-    let channel = data.logging.staff.channel;
-    let timedOut = false;
-    while (!timedOut) {
-        await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Staff Notifications channel")
-                    .setDescription(
-                        channel
-                            ? `Your staff notifications channel is currently set to <#${channel}>`
-                            : "This server does not have a staff notifications channel"
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setCustomId("clear")
-                        .setLabel(clicks ? "Click again to confirm" : "Reset channel")
-                        .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!channel)
-                ])
-            ]
-        });
-        let i;
-        try {
-            i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            });
-        } catch (e) {
-            timedOut = true;
-            continue;
-        }
-        i.deferUpdate();
-        if ((i.component as ButtonComponent).customId === "clear") {
-            clicks += 1;
-            if (clicks === 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild.id, null, ["logging.staff.channel"]);
-                channel = null;
-            }
-        }
-    }
-    await interaction.editReply({
-        embeds: [
-            new EmojiEmbed()
-                .setTitle("Staff Notifications channel")
-                .setDescription(
-                    channel
-                        ? `Your staff notifications channel is currently set to <#${channel}>`
-                        : "This server does not have a staff notifications channel"
-                )
-                .setStatus("Success")
-                .setEmoji("CHANNEL.TEXT.CREATE")
-                .setFooter({ text: "Message closed" })
-        ],
-        components: [
-            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                new ButtonBuilder()
-                    .setCustomId("clear")
-                    .setLabel("Clear")
-                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                    .setStyle(ButtonStyle.Secondary)
-                    .setDisabled(true)
-            ])
-        ]
-    });
-};
-
-const check = (interaction: CommandInteraction) => {
-    const member = interaction.member as Discord.GuildMember;
-    if (!member.permissions.has("ManageGuild"))
-        return "You must have the *Manage Server* permission to use this command";
-    return true;
-};
-
-export { command };
-export { callback };
-export { check };
diff --git a/src/commands/settings/logs/warnings.ts b/src/commands/settings/logs/warnings.ts
new file mode 100644
index 0000000..84772e6
--- /dev/null
+++ b/src/commands/settings/logs/warnings.ts
@@ -0,0 +1,104 @@
+import { LoadingEmbed } from "../../../utils/defaults.js";
+import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType } from "discord.js";
+import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../../utils/getEmojiByName.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
+import client from "../../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+        .setName("warnings")
+        .setDescription("Settings for the staff notifications channel")
+
+const callback = async (interaction: CommandInteraction): Promise<unknown> => {
+    if (!interaction.guild) return;
+    await interaction.reply({
+        embeds: LoadingEmbed,
+        ephemeral: true,
+        fetchReply: true
+    })
+
+    let data = await client.database.guilds.read(interaction.guild.id);
+    let channel = data.logging.staff.channel;
+    let closed = false;
+    do {
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+        .addComponents(
+            new ChannelSelectMenuBuilder()
+                .setCustomId("channel")
+                .setPlaceholder("Select a channel")
+                .setChannelTypes(ChannelType.GuildText)
+        );
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("clear")
+                    .setLabel("Clear")
+                    .setStyle(ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as Discord.APIMessageComponentEmoji)
+                    .setDisabled(!channel),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as Discord.APIMessageComponentEmoji)
+                    .setDisabled(channel === data.logging.staff.channel)
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Staff Notifications Channel")
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+            .setDescription(
+                `Logs which require an action from a moderator or administrator will be sent to this channel.\n` +
+                `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n`
+            )
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [channelMenu, buttons]
+        });
+
+        let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction;
+        try {
+            i = (await interaction.channel!.awaitMessageComponent({
+                filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
+                time: 300000
+            })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+        await i.deferUpdate();
+        if(i.isButton()) {
+            switch (i.customId) {
+                case "clear": {
+                    channel = null;
+                    break;
+                }
+                case "save": {
+                    await client.database.guilds.write(interaction.guild!.id, {
+                        "logging.warnings.channel": channel
+                    });
+                    data = await client.database.guilds.read(interaction.guild!.id);
+                    break;
+                }
+            }
+        } else {
+            channel = i.values[0]!;
+        }
+    } while (!closed);
+
+    await interaction.deleteReply()
+};
+
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as Discord.GuildMember;
+    if (!member.permissions.has("ManageGuild"))
+        return "You must have the *Manage Server* permission to use this command";
+    return true;
+};
+
+export { command };
+export { callback };
+export { check };
diff --git a/src/commands/settings/commands.ts b/src/commands/settings/moderation.ts
similarity index 73%
rename from src/commands/settings/commands.ts
rename to src/commands/settings/moderation.ts
index 25034b2..336e53a 100644
--- a/src/commands/settings/commands.ts
+++ b/src/commands/settings/moderation.ts
@@ -1,50 +1,28 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
-import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, Role, ButtonStyle, ButtonComponent, TextInputBuilder } from "discord.js";
+import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent, TextInputBuilder, RoleSelectMenuBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../utils/client.js";
 import { modalInteractionCollector } from "../../utils/dualCollector.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import keyValueList from "../../utils/generateKeyValueList.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
-        .setName("commands")
+        .setName("moderation")
         .setDescription("Links and text shown to a user after a moderator action is performed")
-        .addRoleOption((o) => o.setName("role").setDescription("The role given when a member is muted"));
 
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    await interaction.reply({
+const callback = async (interaction: CommandInteraction): Promise<void> => {
+    const m = await interaction.reply({
         embeds: LoadingEmbed,
         ephemeral: true,
         fetchReply: true
     });
-    let m;
-    let clicked = "";
-    if (interaction.options.get("role")) {
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("GUILD.ROLES.DELETE")
-            .setTitle("Moderation Commands")
-            .setDescription(
-                keyValueList({
-                    role: `<@&${(interaction.options.get("role") as unknown as Role).id}>`
-                })
-            )
-            .setColor("Danger")
-            .send(true);
-        if (confirmation.cancelled) return
-        if (confirmation.success) {
-            await client.database.guilds.write(interaction.guild!.id, {
-                ["moderation.mute.role"]: (interaction.options.get("role") as unknown as Role).id
-            });
-        }
-    }
     let timedOut = false;
     while (!timedOut) {
         const config = await client.database.guilds.read(interaction.guild!.id);
         const moderation = config.moderation;
-        m = await interaction.editReply({
+        console.log(moderation)
+        await interaction.editReply({
             embeds: [
                 new EmojiEmbed()
                     .setTitle("Moderation Commands")
@@ -52,8 +30,7 @@
                     .setStatus("Success")
                     .setDescription(
                         "These links are shown below the message sent in a user's DM when they are punished.\n\n" +
-                            "**Mute Role:** " +
-                            (moderation.mute.role ? `<@&${moderation.mute.role}>` : "*None set*")
+                            "**Mute Role:** " + (moderation.mute.role ? `<@&${moderation.mute.role}>` : "*None set*")
                     )
             ],
             components: [
@@ -93,24 +70,23 @@
                 ]),
                 new ActionRowBuilder<ButtonBuilder>().addComponents([
                     new ButtonBuilder()
-                        .setLabel(clicked === "clearMuteRole" ? "Click again to confirm" : "Clear mute role")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clearMuteRole")
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!moderation.mute.role),
-                    new ButtonBuilder()
                         .setCustomId("timeout")
                         .setLabel("Mute timeout " + (moderation.mute.timeout ? "Enabled" : "Disabled"))
                         .setStyle(moderation.mute.timeout ? ButtonStyle.Success : ButtonStyle.Danger)
                         .setEmoji(getEmojiByName("CONTROL." + (moderation.mute.timeout ? "TICK" : "CROSS"), "id"))
-                ])
+                ]),
+                new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(
+                    new RoleSelectMenuBuilder()
+                        .setCustomId("muteRole")
+                        .setPlaceholder("Select a new mute role")
+                )
             ]
         });
         let i;
         try {
             i = 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) {
             timedOut = true;
@@ -118,20 +94,13 @@
         }
         type modIDs = "mute" | "kick" | "ban" | "softban" | "warn" | "role";
         let chosen = moderation[i.customId as modIDs];
-        if ((i.component as ButtonComponent).customId === "clearMuteRole") {
-            i.deferUpdate();
-            if (clicked === "clearMuteRole") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "moderation.mute.role": null
-                });
-            } else {
-                clicked = "clearMuteRole";
-            }
+        if (i.isRoleSelectMenu()) {
+            await i.deferUpdate();
+            await client.database.guilds.write(interaction.guild!.id, {
+                "moderation.mute.role": i.values[0]!
+            });
             continue;
-        } else {
-            clicked = "";
-        }
-        if ((i.component as ButtonComponent).customId === "timeout") {
+        } else if ((i.component as ButtonComponent).customId === "timeout") {
             await i.deferUpdate();
             await client.database.guilds.write(interaction.guild!.id, {
                 "moderation.mute.timeout": !moderation.mute.timeout
@@ -183,15 +152,11 @@
             });
             let out: Discord.ModalSubmitInteraction | null;
             try {
-                out = await modalInteractionCollector(
-                    m,
-                    (m) => m.channel!.id === interaction.channel!.id,
-                    (_) => true
-                ) as Discord.ModalSubmitInteraction | null;
+                out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
             } catch (e) {
                 continue;
             }
-            if (!out) continue
+            if (!out || out.isButton()) continue
             const buttonText = out.fields.getTextInputValue("name");
             const buttonLink = out.fields.getTextInputValue("url").replace(/{id}/gi, "{id}");
             const current = chosen;
@@ -206,9 +171,10 @@
             }
         }
     }
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts
index b62d962..cccb6f6 100644
--- a/src/commands/settings/rolemenu.ts
+++ b/src/commands/settings/rolemenu.ts
@@ -1,19 +1,478 @@
 import type Discord from "discord.js";
-import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, Message, ModalBuilder, RoleSelectMenuBuilder, RoleSelectMenuInteraction, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import client from "../../utils/client.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+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;
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("rolemenu")
-        .setDescription("rolemenu") // TODO
-        .addRoleOption((option) => option.setName("role").setDescription("The role to give after verifying")); // FIXME FOR FUCK SAKE
+        .setDescription("rolemenu")
+
+interface ObjectSchema {
+    name: string;
+    description: string;
+    min: number;
+    max: number;
+    options: {
+        name: string;
+        description: string | null;
+        role: string;
+    }[];
+}
+
+const defaultRolePageConfig = {
+    name: "Role Menu Page",
+    description: "A new role menu page",
+    min: 0,
+    max: 0,
+    options: [
+        {name: "Role 1", description: null, role: "No role set"}
+    ]
+}
+
+const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, currentObj: ObjectSchema[]) => {
+    const reorderRow = new ActionRowBuilder<StringSelectMenuBuilder>()
+        .addComponents(
+            new StringSelectMenuBuilder()
+                .setCustomId("reorder")
+                .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)
+                        .setValue(i.toString())
+                    )
+                )
+        );
+    const 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("Role Menu")
+                .setDescription("Select pages in the order you want them to appear.")
+                .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;
+    const values = out.values;
+
+    const newOrder: ObjectSchema[] = currentObj.map((_, i) => {
+        const index = values.findIndex(v => v === i.toString());
+        return currentObj[index];
+    }) as ObjectSchema[];
+
+    return newOrder;
+}
+
+const editNameDescription = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => {
+
+    let {name, description} = data;
+    const modal = new ModalBuilder()
+        .setTitle("Edit Name and Description")
+        .setCustomId("editNameDescription")
+        .addComponents(
+            new ActionRowBuilder<TextInputBuilder>()
+                .addComponents(
+                    new TextInputBuilder()
+                        .setLabel("Name")
+                        .setCustomId("name")
+                        .setPlaceholder("The name of the role (e.g. Programmer)")
+                        .setStyle(TextInputStyle.Short)
+                        .setValue(name ?? "")
+                        .setRequired(true)
+                ),
+            new ActionRowBuilder<TextInputBuilder>()
+                .addComponents(
+                    new TextInputBuilder()
+                        .setLabel("Description")
+                        .setCustomId("description")
+                        .setPlaceholder("A short description of the role (e.g. A role for people who code)")
+                        .setStyle(TextInputStyle.Short)
+                        .setValue(description ?? "")
+                )
+        )
+    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("Role Menu")
+                .setDescription("Modal opened. If you can't see it, click back and try again.")
+                .setStatus("Success")
+        ],
+        components: [button]
+    });
+
+    let out: Discord.ModalSubmitInteraction | null;
+    try {
+        out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
+    } catch (e) {
+        console.error(e);
+        out = null;
+    }
+    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;
+    return [name, description]
+
+}
+
+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",
+        min: 0,
+        max: 0,
+        options: []
+    };
+    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")
+                .setStyle(ButtonStyle.Primary)
+                .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+            new ButtonBuilder()
+                .setCustomId("addRole")
+                .setLabel("Add Role")
+                .setStyle(ButtonStyle.Secondary)
+                .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+        );
+
+    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 embed = new EmojiEmbed()
+            .setTitle(`${data.name}`)
+            .setStatus("Success")
+            .setDescription(
+                `**Description:**\n> ${data.description}\n\n` +
+                `**Min:** ${data.min}` + (data.min === 0 ? " (Members will be given a skip button)" : "") + "\n" +
+                `**Max:** ${data.max}\n`
+            )
+
+        interaction.editReply({embeds: [embed], components: [previewSelect, buttons]});
+        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) {
+            back = true;
+            break;
+        }
+
+        if (i.isStringSelectMenu()) {
+            if(i.customId === "roles") {
+                await i.deferUpdate();
+                await createRoleMenuOptionPage(interaction, m, data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0]));
+            }
+        } else if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    await i.deferUpdate();
+                    back = true;
+                    break;
+                }
+                case "edit": {
+                    const [name, description] = await editNameDescription(i, interaction, m, data);
+                    data.name = name ? name : data.name;
+                    data.description = description ? description : data.description;
+                    break;
+                }
+                case "addRole": {
+                    await i.deferUpdate();
+                    data.options.push(await createRoleMenuOptionPage(interaction, m));
+                    break;
+                }
+            }
+        }
+
+    } while (!back);
+    if(isEqual(data, defaultRolePageConfig)) return null;
+    return data;
+}
+
+const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: {name: string; description: string | null; role: string}) => {
+    const { renderRole} = client.logger;
+    if (!data) data = {
+        name: "New role Menu Option",
+        description: null,
+        role: ""
+    };
+    let back = false;
+    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 Details")
+                .setStyle(ButtonStyle.Primary)
+                .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji)
+        );
+    do {
+        const roleSelect = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder(data.role ? "Set role to" : "Set the role");
+        const embed = new EmojiEmbed()
+            .setTitle(`${data.name}`)
+            .setStatus("Success")
+            .setDescription(
+                `**Description:**\n> ${data.description ?? "No description set"}\n\n` +
+                `**Role:** ${data.role ? renderRole((await interaction.guild!.roles.fetch(data.role))!) : "No role set"}\n`
+            )
+
+        interaction.editReply({embeds: [embed], components: [new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(roleSelect), buttons]});
+
+        let i: RoleSelectMenuInteraction | 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 | RoleSelectMenuInteraction;
+        } catch (e) {
+            back = true;
+            break;
+        }
+
+        if (i.isRoleSelectMenu()) {
+            if(i.customId === "role") {
+                await i.deferUpdate();
+                data.role = (i as RoleSelectMenuInteraction).values[0]!;
+            }
+        } else if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    await i.deferUpdate();
+                    back = true;
+                    break;
+                }
+                case "edit": {
+                    await i.deferUpdate();
+                    const [name, description] = await editNameDescription(i, interaction, m, data as {name: string; description: string});
+                    data.name = name ? name : data.name;
+                    data.description = description ? description : data.description;
+                    break;
+                }
+            }
+        }
+    } while (!back);
+    return data;
+}
 
 const callback = async (interaction: CommandInteraction): Promise<void> => {
-    console.log("we changed the charger again because fuck you");
-    await interaction.reply("You're mum");
+    if (!interaction.guild) return;
+    const m = await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true});
+
+    let page = 0;
+    let closed = false;
+    const config = await client.database.guilds.read(interaction.guild.id);
+    let currentObject: ObjectSchema[] = config.roleMenu.options;
+    let modified = false;
+    do {
+        const embed = new EmojiEmbed()
+            .setTitle("Role Menu")
+            .setEmoji("GUILD.GREEN")
+            .setStatus("Success");
+        const noRoleMenus = currentObject.length === 0;
+        let current: ObjectSchema;
+
+        const pageSelect = new StringSelectMenuBuilder()
+            .setCustomId("page")
+            .setPlaceholder("Select a Role Menu page to manage");
+        const actionSelect = new StringSelectMenuBuilder()
+            .setCustomId("action")
+            .setPlaceholder("Perform an action")
+            .addOptions(
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Edit")
+                    .setDescription("Edit this page")
+                    .setValue("edit")
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Delete")
+                    .setDescription("Delete this page")
+                    .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(currentObject).length - 1),
+                new ButtonBuilder()
+                    .setCustomId("add")
+                    .setLabel("New Page")
+                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(Object.keys(currentObject).length >= 24),
+                new ButtonBuilder()
+                    .setCustomId("reorder")
+                    .setLabel("Reorder Pages")
+                    .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(Object.keys(currentObject).length <= 1),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(!modified),
+            );
+        if(noRoleMenus) {
+            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);
+            actionSelect.setDisabled(true);
+            pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+                .setLabel("No role menu pages")
+                .setValue("none")
+            );
+        } else {
+            page = Math.min(page, Object.keys(currentObject).length - 1);
+            current = currentObject[page]!;
+            embed.setDescription(`**Currently Editing:** ${current.name}\n\n` +
+                `**Description:**\n> ${current.description}\n` +
+                `\n\n${createPageIndicator(Object.keys(config.roleMenu.options).length, page)}`
+            );
+
+            pageSelect.addOptions(
+                currentObject.map((key: ObjectSchema, index) => {
+                    return new StringSelectMenuOptionBuilder()
+                        .setLabel(ellipsis(key.name, 50))
+                        .setDescription(ellipsis(key.description, 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;
+            continue;
+        }
+
+        await i.deferUpdate();
+        if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    page--;
+                    break;
+                }
+                case "next": {
+                    page++;
+                    break;
+                }
+                case "add": {
+                    const newPage = await editRoleMenuPage(i, m)
+                    if(!newPage) break;
+                    currentObject.push();
+                    page = currentObject.length - 1;
+                    break;
+                }
+                case "reorder": {
+                    const reordered = await reorderRoleMenuPages(interaction, m, currentObject);
+                    if(!reordered) break;
+                    currentObject = reordered;
+                    break;
+                }
+                case "save": {
+                    client.database.guilds.write(interaction.guild.id, {"roleMenu.options": currentObject});
+                    modified = false;
+                    break;
+                }
+            }
+        } else if (i.isStringSelectMenu()) {
+            switch (i.customId) {
+                case "action": {
+                    switch(i.values[0]) {
+                        case "edit": {
+                            const edited = await editRoleMenuPage(i, m, current!);
+                            if(!edited) break;
+                            currentObject[page] = edited;
+                            modified = true;
+                            break;
+                        }
+                        case "delete": {
+                            if(page === 0 && currentObject.keys.length - 1 > 0) page++;
+                            else page--;
+                            currentObject.splice(page, 1);
+                            break;
+                        }
+                    }
+                    break;
+                }
+                case "page": {
+                    page = parseInt(i.values[0]!);
+                    break;
+                }
+            }
+        }
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageRoles"))
         return "You must have the *Manage Roles* permission to use this command";
diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts
index cdd218b..d46b57e 100644
--- a/src/commands/settings/stats.ts
+++ b/src/commands/settings/stats.ts
@@ -1,249 +1,403 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
-import Discord, { CommandInteraction, Message, ActionRowBuilder, GuildMember, StringSelectMenuBuilder, StringSelectMenuInteraction, AutocompleteInteraction } from "discord.js";
+import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, ModalBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../utils/client.js";
 import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js";
-import { callback as statsChannelAddCallback } from "../../reflex/statsChannelUpdate.js";
 import singleNotify from "../../utils/singleNotify.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import createPageIndicator from "../../utils/createPageIndicator.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
+
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("stats")
         .setDescription("Controls channels which update when someone joins or leaves the server")
-        .addChannelOption((option) => option.setName("channel").setDescription("The channel to modify"))
-        .addStringOption((option) =>
-            option
-                .setName("name")
-                .setDescription("The new channel name | Enter any text or use the extra variables like {memberCount}")
-                .setAutocomplete(true)
-        );
 
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {  // TODO: This command feels unintuitive. Clicking a channel in the select menu deletes it
-    // instead, it should give a submenu to edit the channel, enable/disable or delete it
-    singleNotify("statsChannelDeleted", interaction.guild!.id, true);
-    const m = (await interaction.reply({
-        embeds: LoadingEmbed,
-        ephemeral: true,
-        fetchReply: true
-    })) as Message;
-    let config = await client.database.guilds.read(interaction.guild!.id);
-    if (interaction.options.get("name")?.value as string) {
-        let channel;
-        if (Object.keys(config.stats).length >= 25) {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Stats Channel")
-                        .setDescription("You can only have 25 stats channels in a server")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        try {
-            channel = interaction.options.get("channel")?.channel as Discord.Channel;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                        .setTitle("Stats Channel")
-                        .setDescription("The channel you provided is not a valid channel")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        channel = channel as Discord.TextChannel;
-        if (channel.guild.id !== interaction.guild!.id) {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Stats Channel")
-                        .setDescription("You must choose a channel in this server")
-                        .setStatus("Danger")
-                        .setEmoji("CHANNEL.TEXT.DELETE")
-                ]
-            });
-        }
-        let newName = await convertCurlyBracketString(
-            interaction.options.get("name")?.value as string,
-            "",
-            "",
-            interaction.guild!.name,
-            interaction.guild!.members
-        );
-        if (interaction.options.get("channel")?.channel!.type === Discord.ChannelType.GuildText) {
-            newName = newName.toLowerCase().replace(/[\s]/g, "-");
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("CHANNEL.TEXT.EDIT")
-            .setTitle("Stats Channel")
-            .setDescription(
-                `Are you sure you want to set <#${channel.id}> to a stats channel?\n\n*Preview: ${newName.replace(
-                    /^ +| $/g,
-                    ""
-                )}*`
-            )
-            .setColor("Warning")
-            .setInverted(true)
-            .setFailedMessage(`Could not convert <#${channel.id}> to a stats chanel.`, "Danger", "CHANNEL.TEXT.DELETE")
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                const name = interaction.options.get("name")?.value as string;
-                const channel = interaction.options.get("channel")?.channel as Discord.TextChannel;
-                await client.database.guilds.write(interaction.guild!.id, {
-                    [`stats.${channel.id}`]: { name: name, enabled: true }
-                });
-                const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
-                const data = {
-                    meta: {
-                        type: "statsChannelUpdate",
-                        displayName: "Stats Channel Updated",
-                        calculateType: "nucleusSettingsUpdated",
-                        color: NucleusColors.yellow,
-                        emoji: "CHANNEL.TEXT.EDIT",
-                        timestamp: new Date().getTime()
-                    },
-                    list: {
-                        memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                        changedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                        channel: entry(channel.id, renderChannel(channel)),
-                        name: entry(
-                            interaction.options.get("name")?.value as string,
-                            `\`${interaction.options.get("name")?.value as string}\``
+
+const showModal = async (interaction: MessageComponentInteraction, current: { enabled: boolean; name: string; }) => {
+    await interaction.showModal(
+        new ModalBuilder()
+            .setCustomId("modal")
+            .setTitle(`Stats channel name`)
+            .addComponents(
+                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                    new TextInputBuilder()
+                        .setCustomId("ex1")
+                        .setLabel("Server Info (1/3)")
+                        .setPlaceholder(
+                            `{serverName} - This server's name\n\n` +
+                            `These placeholders will be replaced with the server's name, etc..`
                         )
-                    },
-                    hidden: {
-                        guild: interaction.guild!.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                console.log(e);
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Stats Channel")
-                            .setDescription("Something went wrong and the stats channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Stats Channel")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("CHANNEL.TEXT.CREATE")
-                ],
-                components: []
-            });
-        }
-        await statsChannelAddCallback(client, interaction.member as GuildMember);
-    }
-    let timedOut = false;
-    while (!timedOut) {
-        config = await client.database.guilds.read(interaction.guild!.id);
-        const stats = config.stats;
-        const selectMenu = new StringSelectMenuBuilder()
-            .setCustomId("remove")
-            .setMinValues(1)
-            .setMaxValues(Math.max(1, Object.keys(stats).length));
-        await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Stats Channel")
-                    .setDescription(
-                        "The following channels update when someone joins or leaves the server. You can select a channel to remove it from the list."
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
-                    Object.keys(stats).length
-                        ? [
-                              selectMenu
-                                  .setPlaceholder("Select a stats channel to remove, stopping it updating")
-                                  .addOptions(
-                                      Object.keys(stats).map((key) => ({
-                                          label: interaction.guild!.channels.cache.get(key)!.name,
-                                          value: key,
-                                          description: `${stats[key]!.name}`
-                                      }))
-                                  )
-                          ]
-                        : [
-                              selectMenu
-                                  .setPlaceholder("The server has no stats channels")
-                                  .setDisabled(true)
-                                  .setOptions([
-                                      {
-                                          label: "*Placeholder*",
-                                          value: "placeholder",
-                                          description: "No stats channels"
-                                      }
-                                  ])
-                          ]
+                        .setMaxLength(1)
+                        .setRequired(false)
+                        .setStyle(Discord.TextInputStyle.Paragraph)
+                ),
+                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                    new TextInputBuilder()
+                        .setCustomId("ex2")
+                        .setLabel("Member Counts (2/3) - {MemberCount:...}")
+                        .setPlaceholder(
+                            `{:all} - Total member count\n` +
+                            `{:humans} - Total non-bot users\n` +
+                            `{:bots} - Number of bots\n`
+                        )
+                        .setMaxLength(1)
+                        .setRequired(false)
+                        .setStyle(Discord.TextInputStyle.Paragraph)
+                ),
+                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                    new TextInputBuilder()
+                        .setCustomId("ex3")
+                        .setLabel("Latest Member (3/3) - {member:...}")
+                        .setPlaceholder(
+                                `{:name} - The members name\n`
+                        )
+                        .setMaxLength(1)
+                        .setRequired(false)
+                        .setStyle(Discord.TextInputStyle.Paragraph)
+                ),
+                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                    new TextInputBuilder()
+                        .setCustomId("text")
+                        .setLabel("Channel name input")
+                        .setMaxLength(1000)
+                        .setRequired(true)
+                        .setStyle(Discord.TextInputStyle.Short)
+                        .setValue(current.name)
+                )
+            )
+    );
+}
+
+type ObjectSchema = Record<string, {name: string, enabled: boolean}>
+
+
+const addStatsChannel = async (interaction: CommandInteraction, m: Message, currentObject: ObjectSchema): Promise<ObjectSchema> => {
+    let closed = false;
+    let cancelled = false;
+    const originalObject = Object.fromEntries(Object.entries(currentObject).map(([k, v]) => [k, {...v}]));
+    let newChannel: string | undefined;
+    let newChannelName: string = "{memberCount:all}-members";
+    let newChannelEnabled: boolean = true;
+    do {
+        m = await interaction.editReply({
+            embeds: [new EmojiEmbed()
+                .setTitle("Stats Channel")
+                .setDescription(
+                    `New stats channel` + (newChannel ? ` in <#${newChannel}>` : "") + "\n\n" +
+                    `**Name:** \`${newChannelName}\`\n` +
+                    `**Preview:** ${await convertCurlyBracketString(newChannelName, interaction.user!.id, interaction.user.username, interaction.guild!.name, interaction.guild!.members)}\n` +
+                    `**Enabled:** ${newChannelEnabled ? "Yes" : "No"}\n\n`
+                )
+                .setEmoji("SETTINGS.STATS.GREEN")
+                .setStatus("Success")
+            ], components: [
+                new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
+                    new ChannelSelectMenuBuilder()
+                        .setCustomId("channel")
+                        .setPlaceholder("Select a channel to use")
+                ),
+                new ActionRowBuilder<ButtonBuilder>().addComponents(
+                    new ButtonBuilder()
+                        .setLabel("Cancel")
+                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                        .setStyle(ButtonStyle.Danger)
+                        .setCustomId("back"),
+                    new ButtonBuilder()
+                        .setLabel("Save")
+                        .setEmoji(getEmojiByName("ICONS.SAVE", "id"))
+                        .setStyle(ButtonStyle.Success)
+                        .setCustomId("save"),
+                    new ButtonBuilder()
+                        .setLabel("Edit name")
+                        .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
+                        .setStyle(ButtonStyle.Primary)
+                        .setCustomId("editName"),
+                    new ButtonBuilder()
+                        .setLabel(newChannelEnabled ? "Enabled" : "Disabled")
+                        .setEmoji(getEmojiByName(newChannelEnabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id"))
+                        .setStyle(ButtonStyle.Secondary)
+                        .setCustomId("toggleEnabled")
                 )
             ]
         });
-        let i: StringSelectMenuInteraction;
+        let i: ButtonInteraction | ChannelSelectMenuInteraction;
         try {
-            i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            }) as StringSelectMenuInteraction;
+            i = await m.awaitMessageComponent({ time: 300000, filter: (i) => {
+                return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id;
+            }}) as ButtonInteraction | ChannelSelectMenuInteraction;
         } catch (e) {
-            timedOut = true;
+            closed = true;
+            cancelled = true;
+            break;
+        }
+        if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    await i.deferUpdate();
+                    closed = true;
+                    break;
+                }
+                case "save": {
+                    await i.deferUpdate();
+                    if (newChannel) {
+                        currentObject[newChannel] = {
+                            name: newChannelName,
+                            enabled: newChannelEnabled
+                        }
+                    }
+                    closed = true;
+                    break;
+                }
+                case "editName": {
+                    await interaction.editReply({
+                        embeds: [new EmojiEmbed()
+                                    .setTitle("Stats Channel")
+                                    .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                    .setStatus("Success")
+                                    .setEmoji("SETTINGS.STATS.GREEN")
+                        ],
+                        components: [
+                            new ActionRowBuilder<ButtonBuilder>().addComponents(
+                                new ButtonBuilder()
+                                    .setLabel("Back")
+                                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                    .setStyle(ButtonStyle.Primary)
+                                    .setCustomId("back")
+                            )
+                        ]
+                    });
+                    showModal(i, {name: newChannelName, enabled: newChannelEnabled})
+
+                    const out: Discord.ModalSubmitInteraction | ButtonInteraction| null = await modalInteractionCollector(m, interaction.user);
+                    if (!out) continue;
+                    if (out.isButton()) continue;
+                    newChannelName = out.fields.getTextInputValue("text");
+                    break;
+                }
+                case "toggleEnabled": {
+                    await i.deferUpdate();
+                    newChannelEnabled = !newChannelEnabled;
+                    break;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            if (i.customId === "channel") {
+                newChannel = i.values[0];
+            }
+        }
+    } while (!closed)
+    if (cancelled) return originalObject;
+    if (!(newChannel && newChannelName && newChannelEnabled)) return originalObject;
+    return currentObject;
+}
+const callback = async (interaction: CommandInteraction) => {
+    if (!interaction.guild) return;
+    const { renderChannel } = client.logger;
+    const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
+    let page = 0;
+    let closed = false;
+    const config = await client.database.guilds.read(interaction.guild.id);
+    let currentObject: ObjectSchema = config.stats;
+    let modified = false;
+    do {
+        const embed = new EmojiEmbed()
+            .setTitle("Stats Settings")
+            .setEmoji("SETTINGS.STATS.GREEN")
+            .setStatus("Success");
+        const noStatsChannels = Object.keys(currentObject).length === 0;
+        let current: { enabled: boolean; name: string; };
+
+        const pageSelect = new StringSelectMenuBuilder()
+            .setCustomId("page")
+            .setPlaceholder("Select a stats channel to manage");
+        const actionSelect = new StringSelectMenuBuilder()
+            .setCustomId("action")
+            .setPlaceholder("Perform an action")
+            .addOptions(
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Edit")
+                    .setDescription("Edit the stats channel")
+                    .setValue("edit")
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new StringSelectMenuOptionBuilder()
+                    .setLabel("Delete")
+                    .setDescription("Delete the stats channel")
+                    .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(currentObject).length - 1),
+                new ButtonBuilder()
+                    .setCustomId("add")
+                    .setLabel("Create new")
+                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Secondary)
+                    .setDisabled(Object.keys(currentObject).length >= 24),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Success)
+                    .setDisabled(modified),
+            );
+        if (noStatsChannels) {
+            embed.setDescription("No stats channels 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 stats channels")
+                .setValue("none")
+            );
+        } else {
+            page = Math.min(page, Object.keys(currentObject).length - 1);
+            current = currentObject[Object.keys(config.stats)[page]!]!
+            actionSelect.addOptions(new StringSelectMenuOptionBuilder()
+                .setLabel(current.enabled ? "Disable" : "Enable")
+                .setValue("toggleEnabled")
+                .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`)
+                .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
+            );
+            embed.setDescription(`**Currently Editing:** ${renderChannel(Object.keys(currentObject)[page]!)}\n\n` +
+                `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` +
+                `**Name:** \`${current.name}\`\n` +
+                `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` + '\n\n' +
+                createPageIndicator(Object.keys(config.stats).length, page)
+            );
+            for (const [id, { name, enabled }] of Object.entries(currentObject)) {
+                pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+                    .setLabel(`${name} (${renderChannel(id)})`)
+                    .setEmoji(getEmojiByName(enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
+                    .setDescription(`${enabled ? "Enabled" : "Disabled"}`)
+                    .setValue(id)
+                );
+            }
+        }
+
+        interaction.editReply({embeds: [embed], components: [
+            new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
+            new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
+            buttonRow
+        ]});
+
+        let i: StringSelectMenuInteraction | ButtonInteraction;
+        try {
+            i = await m.awaitMessageComponent({ filter: (interaction) => interaction.user.id === interaction.user.id, time: 60000 }) as StringSelectMenuInteraction | ButtonInteraction;
+        } catch (e) {
+            closed = true;
             continue;
         }
-        i.deferUpdate();
-        if (i.customId === "remove") {
-            const toRemove = i.values;
-            await client.database.guilds.write(
-                interaction.guild!.id,
-                null,
-                toRemove.map((k) => `stats.${k}`)
-            );
+
+        if(i.isStringSelectMenu()) {
+            switch(i.customId) {
+                case "page": {
+                    await i.deferUpdate();
+                    page = Object.keys(currentObject).indexOf(i.values[0]!);
+                    break;
+                }
+                case "action": {
+                    modified = true;
+                    switch(i.values[0]!) {
+                        case "edit": {
+                            showModal(i, current!)
+                            await interaction.editReply({
+                                embeds: [
+                                    new EmojiEmbed()
+                                        .setTitle("Stats Channel")
+                                        .setDescription("Modal opened. If you can't see it, click back and try again.")
+                                        .setStatus("Success")
+                                        .setEmoji("SETTINGS.STATS.GREEN")
+                                ],
+                                components: [
+                                    new ActionRowBuilder<ButtonBuilder>().addComponents(
+                                        new ButtonBuilder()
+                                            .setLabel("Back")
+                                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                                            .setStyle(ButtonStyle.Primary)
+                                            .setCustomId("back")
+                                    )
+                                ]
+                            });
+                            let out: Discord.ModalSubmitInteraction | null;
+                            try {
+                                out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
+                            } catch (e) {
+                                continue;
+                            }
+                            if (!out) continue
+                            if (out.isButton()) continue;
+                            currentObject[Object.keys(currentObject)[page]!]!.name = out.fields.getTextInputValue("text");
+                            break;
+                        }
+                        case "toggleEnabled": {
+                            await i.deferUpdate();
+                            currentObject[Object.keys(currentObject)[page]!]!.enabled = !currentObject[Object.keys(currentObject)[page]!]!.enabled;
+                            modified = true;
+                            break;
+                        }
+                        case "delete": {
+                            await i.deferUpdate();
+                            currentObject = Object.fromEntries(Object.entries(currentObject).filter(([k]) => k !== Object.keys(currentObject)[page]!));
+                            page = Math.min(page, Object.keys(currentObject).length - 1);
+                            modified = true;
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+        } else {
+            await i.deferUpdate();
+            switch(i.customId) {
+                case "back": {
+                    page--;
+                    break;
+                }
+                case "next": {
+                    page++;
+                    break;
+                }
+                case "add": {
+                    currentObject = await addStatsChannel(interaction, m, currentObject);
+                    page = Object.keys(currentObject).length - 1;
+                    break;
+                }
+                case "save": {
+                    client.database.guilds.write(interaction.guild.id, {stats: currentObject});
+                    singleNotify("statsChannelDeleted", interaction.guild.id, true);
+                    modified = false;
+                    break;
+                }
+            }
         }
-    }
-    await interaction.editReply({
-        embeds: [new Discord.EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })],
-        components: []
-    });
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageChannels"))
         return "You must have the *Manage Channels* permission to use this command";
     return true;
 };
 
-const generateStatsChannelAutocomplete = (prompt: string): string[] => {
-    return [prompt];
-};
-
-const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
-    if (!interaction.guild) return [];
-    const prompt = interaction.options.getString("tag");
-    // generateStatsChannelAutocomplete(int.options.getString("name") ?? "")
-    const results = generateStatsChannelAutocomplete(prompt ?? "");
-    return results;
-};
-
-
 
 export { command };
 export { callback };
-export { check };
-export { autocomplete };
\ No newline at end of file
+export { check };
\ No newline at end of file
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index 892a420..2e046bf 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -1,68 +1,38 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
 import Discord, {
     CommandInteraction,
-    GuildChannel,
     Message,
     ActionRowBuilder,
     ButtonBuilder,
-    MessageComponentInteraction,
     StringSelectMenuBuilder,
-    Role,
-    StringSelectMenuInteraction,
     ButtonStyle,
     TextInputBuilder,
     ButtonComponent,
-    StringSelectMenuComponent,
     ModalSubmitInteraction,
-    APIMessageComponentEmoji
+    APIMessageComponentEmoji,
+    RoleSelectMenuBuilder,
+    ChannelSelectMenuBuilder,
+    RoleSelectMenuInteraction,
+    ButtonInteraction,
+    ChannelSelectMenuInteraction,
+    TextInputStyle,
+    ModalBuilder,
+    ChannelType
 } from "discord.js";
-import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders";
-import { ChannelType } from "discord-api-types/v9";
+import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "discord.js";
 import client from "../../utils/client.js";
 import { toHexInteger, toHexArray, tickets as ticketTypes } from "../../utils/calculate.js";
 import { capitalize } from "../../utils/generateKeyValueList.js";
 import { modalInteractionCollector } from "../../utils/dualCollector.js";
 import type { GuildConfig } from "../../utils/database.js";
+import { LinkWarningFooter } from "../../utils/defaults.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("tickets")
-        .setDescription("Shows settings for tickets | Use no arguments to manage custom types")
-        .addStringOption((option) =>
-            option
-                .setName("enabled")
-                .setDescription("If users should be able to create tickets")
-                .setRequired(false)
-                .addChoices(
-                    {name: "Yes", value: "yes"},
-                    {name: "No",value:  "no"}
-                )
-        )
-        .addChannelOption((option) =>
-            option
-                .setName("category")
-                .setDescription("The category where tickets are created")
-                .addChannelTypes(ChannelType.GuildCategory)
-                .setRequired(false)
-        )
-        .addNumberOption((option) =>
-            option
-                .setName("maxticketsperuser")
-                .setDescription("The maximum amount of tickets a user can create | Default: 5")
-                .setRequired(false)
-                .setMinValue(1)
-        )
-        .addRoleOption((option) =>
-            option
-                .setName("supportrole")
-                .setDescription(
-                    "This role will have view access to all tickets and will be pinged when a ticket is created"
-                )
-                .setRequired(false)
-        );
+        .setDescription("Shows settings for tickets")
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
     if (!interaction.guild) return;
@@ -71,392 +41,130 @@
         ephemeral: true,
         fetchReply: true
     })) as Message;
-    const options = {
-        enabled: (interaction.options.get("enabled")?.value as string).startsWith("yes") as boolean | null,
-        category: interaction.options.get("category")?.channel as Discord.CategoryChannel | null,
-        maxtickets: interaction.options.get("maxticketsperuser")?.value as number | null,
-        supportping: interaction.options.get("supportrole")?.role as Role | null
-    };
-    if (options.enabled !== null || options.category || options.maxtickets || options.supportping) {
-        if (options.category) {
-            let channel: GuildChannel | null;
-            try {
-                channel = await interaction.guild.channels.fetch(options.category.id) as GuildChannel;
-            } catch {
-                return await interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                            .setTitle("Tickets > Category")
-                            .setDescription("The channel you provided is not a valid category")
-                            .setStatus("Danger")
-                    ]
-                });
-            }
-            channel = channel as Discord.CategoryChannel;
-            if (channel.guild.id !== interaction.guild.id)
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Tickets > Category")
-                            .setDescription("You must choose a category in this server")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ]
-                });
-        }
-        if (options.maxtickets) {
-            if (options.maxtickets < 1)
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Tickets > Max Tickets")
-                            .setDescription("You must choose a number greater than 0")
-                            .setStatus("Danger")
-                            .setEmoji("CHANNEL.TEXT.DELETE")
-                    ]
-                });
-        }
-        let role: Role | null;
-        if (options.supportping) {
-            try {
-                role = await interaction.guild.roles.fetch(options.supportping.id);
-            } catch {
-                return await interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setEmoji("GUILD.ROLE.DELETE")
-                            .setTitle("Tickets > Support Ping")
-                            .setDescription("The role you provided is not a valid role")
-                            .setStatus("Danger")
-                    ]
-                });
-            }
-            if (!role) return;
-            role = role as Discord.Role;
-            if (role.guild.id !== interaction.guild.id)
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Tickets > Support Ping")
-                            .setDescription("You must choose a role in this server")
-                            .setStatus("Danger")
-                            .setEmoji("GUILD.ROLE.DELETE")
-                    ]
-                });
-        }
-
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("GUILD.TICKET.ARCHIVED")
-            .setTitle("Tickets")
-            .setDescription(
-                (options.category ? `**Category:** ${options.category.name}\n` : "") +
-                    (options.maxtickets ? `**Max Tickets:** ${options.maxtickets}\n` : "") +
-                    (options.supportping ? `**Support Ping:** ${options.supportping.name}\n` : "") +
-                    (options.enabled !== null
-                        ? `**Enabled:** ${
-                            options.enabled
-                                ? `${getEmojiByName("CONTROL.TICK")} Yes`
-                                : `${getEmojiByName("CONTROL.CROSS")} No`
-                        }\n`
-                        : "") +
-                    "\nAre you sure you want to apply these settings?"
-            )
-            .setColor("Warning")
-            .setFailedMessage("Cancelled", "Warning", "GUILD.TICKET.CLOSE") // TODO: Set Actual Message
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            const toUpdate: Record<string, string | boolean | number> = {};
-            if (options.enabled !== null) toUpdate["tickets.enabled"] = options.enabled;
-            if (options.category) toUpdate["tickets.category"] = options.category.id;
-            if (options.maxtickets) toUpdate["tickets.maxTickets"] = options.maxtickets;
-            if (options.supportping) toUpdate["tickets.supportRole"] = options.supportping.id;
-            try {
-                await client.database.guilds.write(interaction.guild.id, toUpdate);
-            } catch (e) {
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Tickets")
-                            .setDescription("Something went wrong and the staff notifications channel could not be set")
-                            .setStatus("Danger")
-                            .setEmoji("GUILD.TICKET.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Tickets")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("GUILD.TICKET.OPEN")
-                ],
-                components: []
-            });
-        }
-    }
     const data = await client.database.guilds.read(interaction.guild.id);
     data.tickets.customTypes = (data.tickets.customTypes ?? []).filter(
         (value: string, index: number, array: string[]) => array.indexOf(value) === index
     );
-    let lastClicked = "";
-    const embed: EmojiEmbed = new EmojiEmbed();
-    const compiledData = {
-        enabled: data.tickets.enabled,
-        category: data.tickets.category,
-        maxTickets: data.tickets.maxTickets,
-        supportRole: data.tickets.supportRole,
-        useCustom: data.tickets.useCustom,
-        types: data.tickets.types,
-        customTypes: data.tickets.customTypes as string[] | null
-    };
+    let ticketData = (await client.database.guilds.read(interaction.guild.id)).tickets
+    let changesMade = false;
     let timedOut = false;
+    let errorMessage = "";
     while (!timedOut) {
-            embed
+        const embed: EmojiEmbed = new EmojiEmbed()
             .setTitle("Tickets")
             .setDescription(
-                `${compiledData.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${
-                    compiledData.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
+                `${ticketData.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${
+                    ticketData.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
                 }\n` +
-                    `${compiledData.category ? "" : getEmojiByName("TICKETS.REPORT")} **Category:** ${
-                        compiledData.category ? `<#${compiledData.category}>` : "*None set*"
-                    }\n` +
-                    `**Max Tickets:** ${compiledData.maxTickets ? compiledData.maxTickets : "*No limit*"}\n` +
-                    `**Support Ping:** ${compiledData.supportRole ? `<@&${compiledData.supportRole}>` : "*None set*"}\n\n` +
-                    (compiledData.useCustom && compiledData.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") +
-                    `${compiledData.useCustom ? "Custom" : "Default"} types in use` +
+                    `${ticketData.category ? "" : getEmojiByName("TICKETS.REPORT")}` +
+                    ((await interaction.guild.channels.fetch(ticketData.category!))!.type === ChannelType.GuildCategory ?
+                    `**Category:** ` : `**Channel:** `) +  // TODO: Notify if permissions are wrong
+                    `${ticketData.category ? `<#${ticketData.category}>` : "*None set*"}\n` +
+                    `**Max Tickets:** ${ticketData.maxTickets ? ticketData.maxTickets : "*No limit*"}\n` +
+                    `**Support Ping:** ${ticketData.supportRole ? `<@&${ticketData.supportRole}>` : "*None set*"}\n\n` +
+                    (ticketData.useCustom && ticketData.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") +
+                    `${ticketData.useCustom ? "Custom" : "Default"} types in use` +
                     "\n\n" +
                     `${getEmojiByName("TICKETS.REPORT")} *Indicates a setting stopping tickets from being used*`
             )
             .setStatus("Success")
             .setEmoji("GUILD.TICKET.OPEN");
+        if (errorMessage) embed.setFooter({text: errorMessage, iconURL: LinkWarningFooter.iconURL});
         m = (await interaction.editReply({
             embeds: [embed],
             components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
+                new ActionRowBuilder<ButtonBuilder>().addComponents(
                     new ButtonBuilder()
-                        .setLabel("Tickets " + (compiledData.enabled ? "enabled" : "disabled"))
-                        .setEmoji(getEmojiByName("CONTROL." + (compiledData.enabled ? "TICK" : "CROSS"), "id"))
-                        .setStyle(compiledData.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                        .setLabel("Tickets " + (ticketData.enabled ? "enabled" : "disabled"))
+                        .setEmoji(getEmojiByName("CONTROL." + (ticketData.enabled ? "TICK" : "CROSS"), "id"))
+                        .setStyle(ticketData.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
                         .setCustomId("enabled"),
                     new ButtonBuilder()
-                        .setLabel(lastClicked === "cat" ? "Click again to confirm" : "Clear category")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setCustomId("clearCategory")
-                        .setDisabled(compiledData.category === null),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked === "max" ? "Click again to confirm" : "Reset max tickets")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setCustomId("clearMaxTickets")
-                        .setDisabled(compiledData.maxTickets === 5),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked === "sup" ? "Click again to confirm" : "Clear support ping")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setCustomId("clearSupportPing")
-                        .setDisabled(compiledData.supportRole === null)
-                ]),
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
+                        .setLabel("Set max tickets")
+                        .setEmoji(getEmojiByName("CONTROL.TICKET", "id"))
+                        .setStyle(ButtonStyle.Primary)
+                        .setCustomId("setMaxTickets")
+                        .setDisabled(!ticketData.enabled),
                     new ButtonBuilder()
                         .setLabel("Manage types")
                         .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
                         .setStyle(ButtonStyle.Secondary)
-                        .setCustomId("manageTypes"),
+                        .setCustomId("manageTypes")
+                        .setDisabled(!ticketData.enabled),
                     new ButtonBuilder()
-                        .setLabel("Add create ticket button")
-                        .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
-                        .setStyle(ButtonStyle.Primary)
-                        .setCustomId("send")
-                ])
+                        .setLabel("Save")
+                        .setEmoji(getEmojiByName("ICONS.SAVE", "id"))
+                        .setStyle(ButtonStyle.Success)
+                        .setCustomId("save")
+                        .setDisabled(!changesMade)
+                ),
+                new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(
+                    new RoleSelectMenuBuilder()
+                        .setCustomId("supportRole")
+                        .setPlaceholder("Select a support role")
+                        .setDisabled(!ticketData.enabled)
+                ),
+                new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
+                    new ChannelSelectMenuBuilder()
+                        .setCustomId("category")
+                        .setPlaceholder("Select a category or channel")
+                        .setDisabled(!ticketData.enabled)
+                )
             ]
-        })) as Message;
-        let i: MessageComponentInteraction;
+        }));
+        let i: RoleSelectMenuInteraction | ButtonInteraction | ChannelSelectMenuInteraction;
         try {
-            i = await m.awaitMessageComponent({
+            i = await m.awaitMessageComponent<2 | 6 | 8>({
                 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) {
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
-        if ((i.component as ButtonComponent).customId === "clearCategory") {
-            if (lastClicked === "cat") {
-                lastClicked = "";
-                await client.database.guilds.write(interaction.guild.id, null, ["tickets.category"]);
-                compiledData.category = null;
-            } else lastClicked = "cat";
-        } else if ((i.component as ButtonComponent).customId === "clearMaxTickets") {
-            if (lastClicked === "max") {
-                lastClicked = "";
-                await client.database.guilds.write(interaction.guild.id, null, ["tickets.maxTickets"]);
-                compiledData.maxTickets = 5;
-            } else lastClicked = "max";
-        } else if ((i.component as ButtonComponent).customId === "clearSupportPing") {
-            if (lastClicked === "sup") {
-                lastClicked = "";
-                await client.database.guilds.write(interaction.guild.id, null, ["tickets.supportRole"]);
-                compiledData.supportRole = null;
-            } else lastClicked = "sup";
-        } else if ((i.component as ButtonComponent).customId === "send") {
-            const ticketMessages = [
-                {
-                    label: "Create ticket",
-                    description: "Click the button below to create a ticket"
-                },
-                {
-                    label: "Issues, questions or feedback?",
-                    description: "Click below to open a ticket and get help from our staff team"
-                },
-                {
-                    label: "Contact Us",
-                    description: "Click the button below to speak to us privately"
+        changesMade = true;
+        if (i.isRoleSelectMenu()) {
+            await i.deferUpdate();
+            ticketData.supportRole = i.values[0] ?? null;
+        } else if (i.isChannelSelectMenu()) {
+            await i.deferUpdate();
+            ticketData.category = i.values[0] ?? null;
+        } else {
+            switch(i.customId) {
+                case "save": {
+                    await i.deferUpdate();
+                    await client.database.guilds.write(interaction.guild.id, { tickets: ticketData });
+                    changesMade = false;
+                    break;
                 }
-            ];
-            let innerTimedOut = false;
-            let templateSelected = false;
-            while (!innerTimedOut && !templateSelected) {
-                const enabled = compiledData.enabled && compiledData.category !== null;
-                await interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Ticket Button")
-                            .setDescription("Select a message template to send in this channel")
-                            .setFooter({
-                                text: enabled
-                                    ? ""
-                                    : "Tickets are not set up correctly so the button may not work for users. Check the main menu to find which options must be set."
-                            })
-                            .setStatus(enabled ? "Success" : "Warning")
-                            .setEmoji("GUILD.ROLES.CREATE")
-                    ],
-                    components: [
-                        new ActionRowBuilder<StringSelectMenuBuilder>().addComponents([
-                            new StringSelectMenuBuilder()
-                                .setOptions(
-                                    ticketMessages.map(
-                                        (
-                                            t: {
-                                                label: string;
-                                                description: string;
-                                                value?: string;
-                                            },
-                                            index
-                                        ) => {
-                                            t.value = index.toString();
-                                            return t as {
-                                                value: string;
-                                                label: string;
-                                                description: string;
-                                            };
-                                        }
-                                    )
-                                )
-                                .setCustomId("template")
-                                .setMaxValues(1)
-                                .setMinValues(1)
-                                .setPlaceholder("Select a message template")
-                        ]),
-                        new ActionRowBuilder<ButtonBuilder>().addComponents([
-                            new ButtonBuilder()
-                                .setCustomId("back")
-                                .setLabel("Back")
-                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
-                                .setStyle(ButtonStyle.Danger),
-                            new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary),
-                            new ButtonBuilder()
-                                .setCustomId("custom")
-                                .setLabel("Custom")
-                                .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
-                                .setStyle(ButtonStyle.Primary)
-                        ])
-                    ]
-                });
-                let i: MessageComponentInteraction;
-                try {
-                    i = await m.awaitMessageComponent({
-                        time: 300000,
-                        filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-                    });
-                } catch (e) {
-                    innerTimedOut = true;
-                    continue;
+                case "enabled": {
+                    await i.deferUpdate();
+                    ticketData.enabled = !ticketData.enabled;
+                    break;
                 }
-                if ((i.component as StringSelectMenuComponent).customId === "template") {
-                    i.deferUpdate();
-                    await interaction.channel!.send({
-                        embeds: [
-                            new EmojiEmbed()
-                                .setTitle(ticketMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.label)
-                                .setDescription(
-                                    ticketMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.description
-                                )
-                                .setStatus("Success")
-                                .setEmoji("GUILD.TICKET.OPEN")
-                        ],
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Create Ticket")
-                                    .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("createticket")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
-                    continue;
-                } else if ((i.component as ButtonComponent).customId === "blank") {
-                    i.deferUpdate();
-                    await interaction.channel!.send({
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Create Ticket")
-                                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("createticket")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
-                    continue;
-                } else if ((i.component as ButtonComponent).customId === "custom") {
+                case "setMaxTickets": {
                     await i.showModal(
-                        new Discord.ModalBuilder()
-                            .setCustomId("modal")
-                            .setTitle("Enter embed details")
+                        new ModalBuilder()
+                            .setCustomId("maxTickets")
+                            .setTitle("Set max tickets")
                             .addComponents(
-                                new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                new ActionRowBuilder<TextInputBuilder>().setComponents(
                                     new TextInputBuilder()
-                                        .setCustomId("title")
-                                        .setLabel("Title")
-                                        .setMaxLength(256)
-                                        .setRequired(true)
-                                        .setStyle(Discord.TextInputStyle.Short)
-                                ),
-                                new ActionRowBuilder<TextInputBuilder>().addComponents(
-                                    new TextInputBuilder()
-                                        .setCustomId("description")
-                                        .setLabel("Description")
-                                        .setMaxLength(4000)
-                                        .setRequired(true)
-                                        .setStyle(Discord.TextInputStyle.Paragraph)
+                                        .setLabel("Max tickets - Leave blank for no limit")
+                                        .setCustomId("maxTickets")
+                                        .setPlaceholder("Enter a number")
+                                        .setRequired(false)
+                                        .setValue(ticketData.maxTickets.toString())
+                                        .setMinLength(1)
+                                        .setMaxLength(3)
+                                        .setStyle(TextInputStyle.Short)
                                 )
                             )
-                    );
-                    await interaction.editReply({
+                    )
+                    await i.editReply({
                         embeds: [
                             new EmojiEmbed()
-                                .setTitle("Ticket Button")
+                                .setTitle("Tickets")
                                 .setDescription("Modal opened. If you can't see it, click back and try again.")
                                 .setStatus("Success")
                                 .setEmoji("GUILD.TICKET.OPEN")
@@ -473,54 +181,33 @@
                     });
                     let out;
                     try {
-                        out = await modalInteractionCollector(
-                            m,
-                            (m) => m.channel!.id === interaction.channel!.id,
-                            (m) => m.customId === "modify"
-                        );
+                        out = await modalInteractionCollector(m, interaction.user);
                     } catch (e) {
-                        innerTimedOut = true;
                         continue;
                     }
+                    if (!out || out.isButton()) continue;
                     out = out as ModalSubmitInteraction;
-                    const title = out.fields.getTextInputValue("title");
-                    const description = out.fields.getTextInputValue("description");
-                    await interaction.channel!.send({
-                        embeds: [
-                            new EmojiEmbed()
-                                .setTitle(title)
-                                .setDescription(description)
-                                .setStatus("Success")
-                                .setEmoji("GUILD.TICKET.OPEN")
-                        ],
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Create Ticket")
-                                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("createticket")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
+                    const toAdd = out.fields.getTextInputValue("maxTickets");
+                    if(isNaN(parseInt(toAdd))) {
+                        errorMessage = "You entered an invalid number - No changes were made";
+                        break;
+                    }
+                    ticketData.maxTickets = toAdd === "" ? 0 : parseInt(toAdd);
+                    break;
+                }
+                case "manageTypes": {
+                    await i.deferUpdate();
+                    ticketData = await manageTypes(interaction, data.tickets, m);
+                    break;
                 }
             }
-        } else if ((i.component as ButtonComponent).customId === "enabled") {
-            await client.database.guilds.write(interaction.guild.id, {
-                "tickets.enabled": !compiledData.enabled
-            });
-            compiledData.enabled = !compiledData.enabled;
-        } else if ((i.component as ButtonComponent).customId === "manageTypes") {
-            data.tickets = await manageTypes(interaction, data.tickets, m as Message);
         }
     }
-    await interaction.editReply({
-        embeds: [ embed.setFooter({ text: "Message timed out" })],
-        components: []
-    });
+    await interaction.deleteReply()
 };
 
+
+
 async function manageTypes(interaction: CommandInteraction, data: GuildConfig["tickets"], m: Message) {
     let timedOut = false;
     let backPressed = false;
@@ -545,7 +232,7 @@
                         .setStatus("Success")
                         .setEmoji("GUILD.TICKET.OPEN")
                 ],
-                components: (customTypes
+                components: (customTypes && customTypes.length > 0
                     ? [
                           new ActionRowBuilder<StringSelectMenuBuilder | ButtonBuilder>().addComponents([
                               new Discord.StringSelectMenuBuilder()
@@ -637,29 +324,23 @@
         try {
             i = 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) {
             timedOut = true;
             continue;
         }
-        if ((i.component as StringSelectMenuComponent).customId === "types") {
-            i.deferUpdate();
-            const types = toHexInteger((i as StringSelectMenuInteraction).values, ticketTypes);
-            await client.database.guilds.write(interaction.guild!.id, {
-                "tickets.types": types
-            });
+        if (i.isStringSelectMenu() && i.customId === "types") {
+            await i.deferUpdate();
+            const types = toHexInteger(i.values, ticketTypes);
             data.types = types;
-        } else if ((i.component as StringSelectMenuComponent).customId === "removeTypes") {
-            i.deferUpdate();
-            const types = (i as StringSelectMenuInteraction).values;
+        } else if (i.isStringSelectMenu() && i.customId === "removeTypes") {
+            await i.deferUpdate();
+            const types = i.values;
             let customTypes = data.customTypes;
             if (customTypes) {
                 customTypes = customTypes.filter((t) => !types.includes(t));
                 customTypes = customTypes.length > 0 ? customTypes : null;
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "tickets.customTypes": customTypes
-                });
                 data.customTypes = customTypes;
             }
         } else if ((i.component as ButtonComponent).customId === "addType") {
@@ -680,7 +361,7 @@
                         )
                     )
             );
-            await interaction.editReply({
+            await i.editReply({
                 embeds: [
                     new EmojiEmbed()
                         .setTitle("Tickets > Types")
@@ -700,14 +381,11 @@
             });
             let out;
             try {
-                out = await modalInteractionCollector(
-                    m,
-                    (m) => m.channel!.id === interaction.channel!.id,
-                    (m) => m.customId === "addType"
-                );
+                out = await modalInteractionCollector(m, interaction.user);
             } catch (e) {
                 continue;
             }
+            if (!out || out.isButton()) continue;
             out = out as ModalSubmitInteraction;
             let toAdd = out.fields.getTextInputValue("type");
             if (!toAdd) {
@@ -715,31 +393,31 @@
             }
             toAdd = toAdd.substring(0, 80);
             try {
-                await client.database.guilds.append(interaction.guild!.id, "tickets.customTypes", toAdd);
+                if(!data.customTypes) data.customTypes = [];
+                data.customTypes.push(toAdd);
             } catch {
                 continue;
             }
-            data.customTypes = data.customTypes ?? [];
             if (!data.customTypes.includes(toAdd)) {
                 data.customTypes.push(toAdd);
             }
         } else if ((i.component as ButtonComponent).customId === "switchToDefault") {
-            i.deferUpdate();
+            await i.deferUpdate();
             await client.database.guilds.write(interaction.guild!.id, { "tickets.useCustom": false }, []);
             data.useCustom = false;
         } else if ((i.component as ButtonComponent).customId === "switchToCustom") {
-            i.deferUpdate();
+            await i.deferUpdate();
             await client.database.guilds.write(interaction.guild!.id, { "tickets.useCustom": true }, []);
             data.useCustom = true;
         } else {
-            i.deferUpdate();
+            await i.deferUpdate();
             backPressed = true;
         }
     }
     return data;
 }
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts
new file mode 100644
index 0000000..d9d485d
--- /dev/null
+++ b/src/commands/settings/tracks.ts
@@ -0,0 +1,459 @@
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, GuildMember, Message, ModalBuilder, ModalSubmitInteraction, PermissionsBitField, 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 { renderRole } = client.logger
+
+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("The name of the track (e.g. Moderators)")
+                        .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, interaction.user) as ModalSubmitInteraction | null;
+    } catch (e) {
+        console.error(e);
+        out = null;
+    }
+    if(!out) return name;
+    if (out.isButton()) 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[]) => {
+    const 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())
+                    )
+                )
+        );
+    const 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;
+    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) => {
+    const isAdmin = (interaction.member!.permissions as PermissionsBitField).has("Administrator");
+    if(!current) {
+        current = {
+            name: "",
+            retainPrevious: false,
+            nullable: false,
+            track: [],
+            manageableBy: []
+        }
+    }
+
+    const roleSelect = new ActionRowBuilder<RoleSelectMenuBuilder>()
+        .addComponents(
+            new RoleSelectMenuBuilder()
+                .setCustomId("addRole")
+                .setPlaceholder("Select a role to add")
+                .setDisabled(!isAdmin)
+        );
+    let closed = false;
+    do {
+        const editableRoles: string[] = current.track.map((r) => {
+            if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position) || interaction.user.id === interaction.guild?.ownerId) return roles.get(r)!.name;
+        }).filter(v => v !== undefined) as string[];
+        const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
+            .addComponents(
+                new StringSelectMenuBuilder()
+                    .setCustomId("removeRole")
+                    .setPlaceholder("Select a role to remove")
+                    .setDisabled(!isAdmin)
+                    .addOptions(
+                        editableRoles.map((r, i) => {
+                            return new StringSelectMenuOptionBuilder()
+                            .setLabel(r)
+                            .setValue(i.toString())}
+                        )
+                    )
+            );
+        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")
+                    .setDisabled(!isAdmin)
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("retainPrevious")
+                    .setLabel("Retain Previous")
+                    .setStyle(current.retainPrevious ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"), "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("nullable")
+                    .setLabel(`Role ${current.nullable ? "Not " : ""}Required`)
+                    .setStyle(current.nullable ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL." + (current.nullable ? "TICK" : "CROSS"), "id") as APIMessageComponentEmoji)
+        );
+
+        const 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 mapped = current.track.map(role => roles.find(aRole => aRole.id === role)!);
+
+        const embed = new EmojiEmbed()
+            .setTitle("Tracks")
+            .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(
+                    mapped.map(role => renderRole(role)), new Array(current.track.length).fill(false), allowed)
+            )
+            .setStatus("Success")
+
+        const comps: ActionRowBuilder<RoleSelectMenuBuilder | ButtonBuilder | StringSelectMenuBuilder>[] = [roleSelect, buttons];
+        if(current.track.length >= 1) comps.splice(1, 0, selectMenu);
+
+        interaction.editReply({embeds: [embed], components: comps});
+
+        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()) {
+            switch(out.customId) {
+                case "back": {
+                    out.deferUpdate();
+                    closed = true;
+                    break;
+                }
+                case "edit": {
+                    current.name = (await editName(out, interaction, message, current.name))!;
+                    break;
+                }
+                case "reorder": {
+                    out.deferUpdate();
+                    current.track = (await reorderTracks(out, message, roles, current.track))!;
+                    break;
+                }
+                case "retainPrevious": {
+                    out.deferUpdate();
+                    current.retainPrevious = !current.retainPrevious;
+                    break;
+                }
+                case "nullable": {
+                    out.deferUpdate();
+                    current.nullable = !current.nullable;
+                    break;
+                }
+            }
+        } 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);
+                    } else {
+                        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();
+
+    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 === 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]!;
+            const mapped = current.track.map(role => roles.find(aRole => aRole.id === role)!);
+            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(mapped.map(role => renderRole(role)), 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;
+            continue;
+        }
+
+        await i.deferUpdate();
+        if (i.isButton()) {
+            switch (i.customId) {
+                case "back": {
+                    page--;
+                    break;
+                }
+                case "next": {
+                    page++;
+                    break;
+                }
+                case "add": {
+                    const 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, {tracks: tracks});
+                    modified = false;
+                    break;
+                }
+            }
+        } else if (i.isStringSelectMenu()) {
+            switch (i.customId) {
+                case "action": {
+                    switch(i.values[0]) {
+                        case "edit": {
+                            const 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);
+    await interaction.deleteReply()
+}
+
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
+    const member = interaction.member as GuildMember;
+    if (!member.permissions.has("ManageRoles"))
+        return "You must have the *Manage Server* permission to use this command";
+    return true;
+};
+
+export { command };
+export { callback };
+export { check };
diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts
index 0f9f4a0..c440b75 100644
--- a/src/commands/settings/verify.ts
+++ b/src/commands/settings/verify.ts
@@ -1,35 +1,25 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
 import Discord, {
     CommandInteraction,
-    Interaction,
     Message,
     ActionRowBuilder,
     ButtonBuilder,
-    MessageComponentInteraction,
-    ModalSubmitInteraction,
-    Role,
     ButtonStyle,
-    StringSelectMenuBuilder,
-    StringSelectMenuComponent,
-    TextInputBuilder,
-    EmbedBuilder,
-    StringSelectMenuInteraction,
-    ButtonComponent
+    RoleSelectMenuBuilder,
+    APIMessageComponentEmoji
 } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import client from "../../utils/client.js";
-import { modalInteractionCollector } from "../../utils/dualCollector.js";
+import { getCommandMentionByName } from "../../utils/getCommandDataByName.js";
+import lodash from "lodash";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("verify")
-        .setDescription("Manage the role given after typing /verify")
-        .addRoleOption((option) =>
-            option.setName("role").setDescription("The role to give after verifying").setRequired(false)
-        );
+        .setDescription("Manage the role given after a user runs /verify")
+
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
     if (!interaction.guild) return;
@@ -38,356 +28,82 @@
         ephemeral: true,
         fetchReply: true
     })) as Message;
-    if (interaction.options.get("role")?.role) {
-        let role: Role;
-        try {
-            role = interaction.options.get("role")?.role as Role;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("GUILD.ROLES.DELETE")
-                        .setTitle("Verify Role")
-                        .setDescription("The role you provided is not a valid role")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        role = role as Discord.Role;
-        if (role.guild.id !== interaction.guild.id) {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Verify Role")
-                        .setDescription("You must choose a role in this server")
-                        .setStatus("Danger")
-                        .setEmoji("GUILD.ROLES.DELETE")
-                ]
-            });
-        }
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("GUILD.ROLES.EDIT")
-            .setTitle("Verify Role")
-            .setDescription(`Are you sure you want to set the verify role to <@&${role.id}>?`)
-            .setColor("Warning")
-            .setFailedMessage("No changes were made", "Warning", "GUILD.ROLES.DELETE")
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                await client.database.guilds.write(interaction.guild.id, {
-                    "verify.role": role.id,
-                    "verify.enabled": true
-                });
-                const { log, NucleusColors, entry, renderUser, renderRole } = client.logger;
-                const data = {
-                    meta: {
-                        type: "verifyRoleChanged",
-                        displayName: "Verify Role Changed",
-                        calculateType: "nucleusSettingsUpdated",
-                        color: NucleusColors.green,
-                        emoji: "CONTROL.BLOCKTICK",
-                        timestamp: new Date().getTime()
-                    },
-                    list: {
-                        memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                        changedBy: entry(interaction.user.id, renderUser(interaction.user)),
-                        role: entry(role.id, renderRole(role))
-                    },
-                    hidden: {
-                        guild: interaction.guild.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                console.log(e);
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Verify Role")
-                            .setDescription("Something went wrong while setting the verify role")
-                            .setStatus("Danger")
-                            .setEmoji("GUILD.ROLES.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Verify Role")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("GUILD.ROLES.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let clicks = 0;
-    const data = await client.database.guilds.read(interaction.guild.id);
-    let role = data.verify.role;
 
-    let timedOut = false;
-    while (!timedOut) {
+    let closed = false;
+    let config = await client.database.guilds.read(interaction.guild.id);
+    let data = Object.assign({}, config.verify);
+    do {
+        const selectMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
+        .addComponents(
+            new RoleSelectMenuBuilder()
+                .setCustomId("role")
+                .setPlaceholder("Select a role")
+        );
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("switch")
+                    .setLabel(data.enabled ? "Enabled" : "Disabled")
+                    .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setDisabled(lodash.isEqual(config.verify, data))
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Verify Role")
+            .setDescription(
+                `Select a role to be given to users after they run ${getCommandMentionByName("verify")}` +
+                `\n\nCurrent role: ${config.verify.role ? `<@&${config.verify.role}>` : "None"}`
+            )
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE");
+
         await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Verify Role")
-                    .setDescription(
-                        role ? `Your verify role is currently set to <@&${role}>` : "You have not set a verify role"
-                    )
-                    .setStatus("Success")
-                    .setEmoji("GUILD.ROLES.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setCustomId("clear")
-                        .setLabel(clicks ? "Click again to confirm" : "Reset role")
-                        .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
-                        .setStyle(ButtonStyle.Danger)
-                        .setDisabled(!role),
-                    new ButtonBuilder()
-                        .setCustomId("send")
-                        .setLabel("Add verify button")
-                        .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
-                        .setStyle(ButtonStyle.Primary)
-                ])
-            ]
+            embeds: [embed],
+            components: [selectMenu, buttons]
         });
-        let i: MessageComponentInteraction;
+
+        let i;
         try {
             i = 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 }
             });
         } catch (e) {
-            timedOut = true;
+            closed = true;
             continue;
         }
-        i.deferUpdate();
-        if ((i.component as ButtonComponent).customId === "clear") {
-            clicks += 1;
-            if (clicks === 2) {
-                clicks = 0;
-                await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"]);
-                role = null;
-            }
-        } else if ((i.component as ButtonComponent).customId === "send") {
-            const verifyMessages = [
-                {
-                    label: "Verify",
-                    description: "Click the button below to get verified"
-                },
-                {
-                    label: "Get verified",
-                    description: "To get access to the rest of the server, click the button below"
-                },
-                {
-                    label: "Ready to verify?",
-                    description: "Click the button below to verify yourself"
+
+        await i.deferUpdate();
+
+        if(i.isButton()) {
+            switch (i.customId) {
+                case "save": {
+                    client.database.guilds.write(interaction.guild.id, {"verify": data} )
+                    config = await client.database.guilds.read(interaction.guild.id);
+                    data = Object.assign({}, config.verify);
+                    break
                 }
-            ];
-            let innerTimedOut = false;
-            let templateSelected = false;
-            while (!innerTimedOut && !templateSelected) {
-                await interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Verify Button")
-                            .setDescription("Select a message template to send in this channel")
-                            .setFooter({
-                                text: role ? "" : "You do no have a verify role set so the button will not work."
-                            })
-                            .setStatus(role ? "Success" : "Warning")
-                            .setEmoji("GUILD.ROLES.CREATE")
-                    ],
-                    components: [
-                        new ActionRowBuilder<StringSelectMenuBuilder>().addComponents([
-                            new StringSelectMenuBuilder()
-                                .setOptions(
-                                    verifyMessages.map(
-                                        (
-                                            t: {
-                                                label: string;
-                                                description: string;
-                                                value?: string;
-                                            },
-                                            index
-                                        ) => {
-                                            t.value = index.toString();
-                                            return t as {
-                                                value: string;
-                                                label: string;
-                                                description: string;
-                                            };
-                                        }
-                                    )
-                                )
-                                .setCustomId("template")
-                                .setMaxValues(1)
-                                .setMinValues(1)
-                                .setPlaceholder("Select a message template")
-                        ]),
-                        new ActionRowBuilder<ButtonBuilder>().addComponents([
-                            new ButtonBuilder()
-                                .setCustomId("back")
-                                .setLabel("Back")
-                                .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
-                                .setStyle(ButtonStyle.Danger),
-                            new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary),
-                            new ButtonBuilder()
-                                .setCustomId("custom")
-                                .setLabel("Custom")
-                                .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
-                                .setStyle(ButtonStyle.Primary)
-                        ])
-                    ]
-                });
-                let i: MessageComponentInteraction;
-                try {
-                    i = await m.awaitMessageComponent({
-                        time: 300000,
-                        filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-                    });
-                } catch (e) {
-                    innerTimedOut = true;
-                    continue;
-                }
-                if ((i.component as StringSelectMenuComponent).customId === "template") {
-                    i.deferUpdate();
-                    await interaction.channel!.send({
-                        embeds: [
-                            new EmojiEmbed()
-                                .setTitle(verifyMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.label)
-                                .setDescription(
-                                    verifyMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.description
-                                )
-                                .setStatus("Success")
-                                .setEmoji("CONTROL.BLOCKTICK")
-                        ],
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Verify")
-                                    .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("verifybutton")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
-                    continue;
-                } else if ((i.component as ButtonComponent).customId === "blank") {
-                    i.deferUpdate();
-                    await interaction.channel!.send({
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Verify")
-                                    .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
-                                    .setStyle(ButtonStyle.Success)
-                                    .setCustomId("verifybutton")
-                            ])
-                        ]
-                    });
-                    templateSelected = true;
-                    continue;
-                } else if ((i.component as ButtonComponent).customId === "custom") {
-                    await i.showModal(
-                        new Discord.ModalBuilder()
-                            .setCustomId("modal")
-                            .setTitle("Enter embed details")
-                            .addComponents(
-                                new ActionRowBuilder<TextInputBuilder>().addComponents(
-                                    new TextInputBuilder()
-                                        .setCustomId("title")
-                                        .setLabel("Title")
-                                        .setMaxLength(256)
-                                        .setRequired(true)
-                                        .setStyle(Discord.TextInputStyle.Short)
-                                ),
-                                new ActionRowBuilder<TextInputBuilder>().addComponents(
-                                    new TextInputBuilder()
-                                        .setCustomId("description")
-                                        .setLabel("Description")
-                                        .setMaxLength(4000)
-                                        .setRequired(true)
-                                        .setStyle(Discord.TextInputStyle.Paragraph)
-                                )
-                            )
-                    );
-                    await interaction.editReply({
-                        embeds: [
-                            new EmojiEmbed()
-                                .setTitle("Verify Button")
-                                .setDescription("Modal opened. If you can't see it, click back and try again.")
-                                .setStatus("Success")
-                                .setEmoji("GUILD.TICKET.OPEN")
-                        ],
-                        components: [
-                            new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                new ButtonBuilder()
-                                    .setLabel("Back")
-                                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
-                                    .setStyle(ButtonStyle.Primary)
-                                    .setCustomId("back")
-                            ])
-                        ]
-                    });
-                    let out;
-                    try {
-                        out = await modalInteractionCollector(
-                            m,
-                            (m: Interaction) =>
-                                (m as MessageComponentInteraction | ModalSubmitInteraction).channelId ===
-                                interaction.channelId,
-                            (m) => m.customId === "modify"
-                        );
-                    } catch (e) {
-                        innerTimedOut = true;
-                        continue;
-                    }
-                    if (out !== null && out instanceof ModalSubmitInteraction) {
-                        const title = out.fields.getTextInputValue("title");
-                        const description = out.fields.getTextInputValue("description");
-                        await interaction.channel!.send({
-                            embeds: [
-                                new EmojiEmbed()
-                                    .setTitle(title)
-                                    .setDescription(description)
-                                    .setStatus("Success")
-                                    .setEmoji("CONTROL.BLOCKTICK")
-                            ],
-                            components: [
-                                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                                    new ButtonBuilder()
-                                        .setLabel("Verify")
-                                        .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
-                                        .setStyle(ButtonStyle.Success)
-                                        .setCustomId("verifybutton")
-                                ])
-                            ]
-                        });
-                        templateSelected = true;
-                    }
+                case "switch": {
+                    data.enabled = !data.enabled;
+                    break
                 }
             }
         } else {
-            i.deferUpdate();
-            break;
+            data.role = i.values[0]!;
         }
-    }
-    await interaction.editReply({
-        embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message closed" })],
-        components: []
-    });
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts
index e7143fb..7584624 100644
--- a/src/commands/settings/welcome.ts
+++ b/src/commands/settings/welcome.ts
@@ -1,307 +1,263 @@
 import { LoadingEmbed } from "../../utils/defaults.js";
 import Discord, {
-    Channel,
     CommandInteraction,
-    Message,
+    AutocompleteInteraction,
     ActionRowBuilder,
     ButtonBuilder,
-    MessageComponentInteraction,
-    Role,
     ButtonStyle,
-    AutocompleteInteraction,
-    GuildChannel,
-    EmbedBuilder
+    APIMessageComponentEmoji,
+    ChannelSelectMenuBuilder,
+    RoleSelectMenuBuilder,
+    RoleSelectMenuInteraction,
+    ChannelSelectMenuInteraction,
+    ButtonInteraction,
+    ModalBuilder,
+    TextInputBuilder,
+    TextInputStyle,
+    ModalSubmitInteraction,
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import generateKeyValueList from "../../utils/generateKeyValueList.js";
-import { ChannelType } from "discord-api-types/v9";
 import getEmojiByName from "../../utils/getEmojiByName.js";
+import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js";
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
         .setName("welcome")
         .setDescription("Messages and roles sent or given when someone joins the server")
-        .addStringOption((option) =>
-            option
-                .setName("message")
-                .setDescription("The message to send when someone joins the server")
-                .setAutocomplete(true)
-        )
-        .addRoleOption((option) =>
-            option.setName("role").setDescription("The role given when someone joins the server")
-        )
-        .addRoleOption((option) =>
-            option.setName("ping").setDescription("The role pinged when someone joins the server")
-        )
-        .addChannelOption((option) =>
-            option
-                .setName("channel")
-                .setDescription("The channel the welcome message should be sent to")
-                .addChannelTypes(ChannelType.GuildText)
-        );
 
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
-    const { renderRole, renderChannel, log, NucleusColors, entry, renderUser } = client.logger;
-    await interaction.reply({
+const callback = async (interaction: CommandInteraction): Promise<void> => {
+    const { renderChannel } = client.logger;
+    const m = await interaction.reply({
         embeds: LoadingEmbed,
         fetchReply: true,
         ephemeral: true
     });
-    let m: Message;
-    if (
-        interaction.options.get("role")?.role ||
-        interaction.options.get("channel")?.channel ||
-        interaction.options.get("message")?.value as string
-    ) {
-        let role: Role | null;
-        let ping: Role | null;
-        let channel: Channel | null;
-        const message: string | null = interaction.options.get("message")?.value as string | null;
-        try {
-            role = interaction.options.get("role")?.role as Role | null;
-            ping = interaction.options.get("ping")?.role as Role | null;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("GUILD.ROLES.DELETE")
-                        .setTitle("Welcome Events")
-                        .setDescription("The role you provided is not a valid role")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        try {
-            channel = interaction.options.get("channel")?.channel as Channel | null;
-        } catch {
-            return await interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setEmoji("GUILD.ROLES.DELETE")
-                        .setTitle("Welcome Events")
-                        .setDescription("The channel you provided is not a valid channel")
-                        .setStatus("Danger")
-                ]
-            });
-        }
-        const options: {
-            role?: string;
-            ping?: string;
-            channel?: string;
-            message?: string;
-        } = {};
-
-        if (role) options.role = renderRole(role);
-        if (ping) options.ping = renderRole(ping);
-        if (channel) options.channel = renderChannel(channel as GuildChannel);
-        if (message) options.message = "\n> " + message;
-        const confirmation = await new confirmationMessage(interaction)
-            .setEmoji("GUILD.ROLES.EDIT")
-            .setTitle("Welcome Events")
-            .setDescription(generateKeyValueList(options))
-            .setColor("Warning")
-            .setFailedMessage("Cancelled", "Warning", "GUILD.ROLES.DELETE") //TODO: Actual Message Needed
-            .setInverted(true)
-            .send(true);
-        if (confirmation.cancelled) return;
-        if (confirmation.success) {
-            try {
-                const toChange: {
-                    "welcome.role"?: string;
-                    "welcome.ping"?: string;
-                    "welcome.channel"?: string;
-                    "welcome.message"?: string;
-                } = {};
-                if (role) toChange["welcome.role"] = role.id;
-                if (ping) toChange["welcome.ping"] = ping.id;
-                if (channel) toChange["welcome.channel"] = channel.id;
-                if (message) toChange["welcome.message"] = message;
-                await client.database.guilds.write(interaction.guild!.id, toChange);
-                const list: {
-                    memberId: ReturnType<typeof entry>;
-                    changedBy: ReturnType<typeof entry>;
-                    role?: ReturnType<typeof entry>;
-                    ping?: ReturnType<typeof entry>;
-                    channel?: ReturnType<typeof entry>;
-                    message?: ReturnType<typeof entry>;
-                } = {
-                    memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
-                    changedBy: entry(interaction.user.id, renderUser(interaction.user))
-                };
-                if (role) list.role = entry(role.id, renderRole(role));
-                if (ping) list.ping = entry(ping.id, renderRole(ping));
-                if (channel) list.channel = entry(channel.id, renderChannel(channel as GuildChannel));
-                if (message) list.message = entry(message, `\`${message}\``);
-                const data = {
-                    meta: {
-                        type: "welcomeSettingsUpdated",
-                        displayName: "Welcome Settings Changed",
-                        calculateType: "nucleusSettingsUpdated",
-                        color: NucleusColors.green,
-                        emoji: "CONTROL.BLOCKTICK",
-                        timestamp: new Date().getTime()
-                    },
-                    list: list,
-                    hidden: {
-                        guild: interaction.guild!.id
-                    }
-                };
-                log(data);
-            } catch (e) {
-                console.log(e);
-                return interaction.editReply({
-                    embeds: [
-                        new EmojiEmbed()
-                            .setTitle("Welcome Events")
-                            .setDescription("Something went wrong while updating welcome settings")
-                            .setStatus("Danger")
-                            .setEmoji("GUILD.ROLES.DELETE")
-                    ],
-                    components: []
-                });
-            }
-        } else {
-            return interaction.editReply({
-                embeds: [
-                    new EmojiEmbed()
-                        .setTitle("Welcome Events")
-                        .setDescription("No changes were made")
-                        .setStatus("Success")
-                        .setEmoji("GUILD.ROLES.CREATE")
-                ],
-                components: []
-            });
-        }
-    }
-    let lastClicked = null;
-    let timedOut = false;
+    let closed = false;
+    let config = await client.database.guilds.read(interaction.guild!.id);
+    let data = Object.assign({}, config.welcome);
     do {
-        const config = await client.database.guilds.read(interaction.guild!.id);
-        m = (await interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Welcome Events")
-                    .setDescription(
-                        `**Message:** ${config.welcome.message ? `\n> ${config.welcome.message}` : "*None set*"}\n` +
-                            `**Role:** ${
-                                config.welcome.role
-                                    ? renderRole((await interaction.guild!.roles.fetch(config.welcome.role))!)
-                                    : "*None set*"
-                            }\n` +
-                            `**Ping:** ${
-                                config.welcome.ping
-                                    ? renderRole((await interaction.guild!.roles.fetch(config.welcome.ping))!)
-                                    : "*None set*"
-                            }\n` +
-                            `**Channel:** ${
-                                config.welcome.channel
-                                    ? config.welcome.channel == "dm"
-                                        ? "DM"
-                                        : renderChannel((await interaction.guild!.channels.fetch(config.welcome.channel))!)
-                                    : "*None set*"
-                            }`
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("switch")
+                    .setLabel(data.enabled ? "Enabled" : "Disabled")
+                    .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("message")
+                    .setLabel((data.message ? "Change" : "Set") + "Message")
+                    .setStyle(ButtonStyle.Primary)
+                    .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("channelDM")
+                    .setLabel("Send in DMs")
+                    .setStyle(ButtonStyle.Primary)
+                    .setDisabled(data.channel === "dm"),
+                new ButtonBuilder()
+                    .setCustomId("role")
+                    .setLabel("Clear Role")
+                    .setStyle(ButtonStyle.Danger)
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji),
+                new ButtonBuilder()
+                    .setCustomId("save")
+                    .setLabel("Save")
+                    .setStyle(ButtonStyle.Success)
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setDisabled(
+                        data.enabled === config.welcome.enabled &&
+                        data.message === config.welcome.message &&
+                        data.role === config.welcome.role &&
+                        data.ping === config.welcome.ping &&
+                        data.channel === config.welcome.channel
                     )
-                    .setStatus("Success")
-                    .setEmoji("CHANNEL.TEXT.CREATE")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setLabel(lastClicked == "clear-message" ? "Click again to confirm" : "Clear Message")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clear-message")
-                        .setDisabled(!config.welcome.message)
-                        .setStyle(ButtonStyle.Danger),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked == "clear-role" ? "Click again to confirm" : "Clear Role")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clear-role")
-                        .setDisabled(!config.welcome.role)
-                        .setStyle(ButtonStyle.Danger),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked == "clear-ping" ? "Click again to confirm" : "Clear Ping")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clear-ping")
-                        .setDisabled(!config.welcome.ping)
-                        .setStyle(ButtonStyle.Danger),
-                    new ButtonBuilder()
-                        .setLabel(lastClicked == "clear-channel" ? "Click again to confirm" : "Clear Channel")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                        .setCustomId("clear-channel")
-                        .setDisabled(!config.welcome.channel)
-                        .setStyle(ButtonStyle.Danger),
-                    new ButtonBuilder()
-                        .setLabel("Set Channel to DM")
-                        .setCustomId("set-channel-dm")
-                        .setDisabled(config.welcome.channel == "dm")
-                        .setStyle(ButtonStyle.Secondary)
-                ])
-            ]
-        })) as Message;
-        let i: MessageComponentInteraction;
+            );
+
+        const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+            .addComponents(
+                new ChannelSelectMenuBuilder()
+                    .setCustomId("channel")
+                    .setPlaceholder("Select a channel to send welcome messages to")
+            );
+        const roleMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
+            .addComponents(
+                new RoleSelectMenuBuilder()
+                    .setCustomId("roleToGive")
+                    .setPlaceholder("Select a role to give to the member when they join the server")
+            );
+        const pingMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
+            .addComponents(
+                new RoleSelectMenuBuilder()
+                    .setCustomId("roleToPing")
+                    .setPlaceholder("Select a role to ping when a member joins the server")
+            );
+
+        const embed = new EmojiEmbed()
+            .setTitle("Welcome Settings")
+            .setStatus("Success")
+            .setDescription(
+                `${getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Welcome messages and roles are ${data.enabled ? "enabled" : "disabled"}\n` +
+                `**Welcome message:** ${data.message ?
+                    `\n> ` +
+                    await convertCurlyBracketString(
+                        data.message,
+                        interaction.user.id,
+                        interaction.user.username,
+                        interaction.guild!.name,
+                        interaction.guild!.members
+                    )
+                    : "*None*"}\n` +
+                `**Send message in:** ` + (data.channel ? (data.channel == "dm" ? "DMs" : renderChannel(data.channel)) : `*None set*`) + `\n` +
+                `**Role to ping:** ` + (data.ping ? `<@&${data.ping}>` : `*None set*`) + `\n` +
+                `**Role given on join:** ` + (data.role ? `<@&${data.role}>` : `*None set*`)
+            )
+
+        await interaction.editReply({
+            embeds: [embed],
+            components: [buttons, channelMenu, roleMenu, pingMenu]
+        });
+
+        let i: RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction;
         try {
             i = await m.awaitMessageComponent({
-                time: 300000,
-                filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
-            });
+                filter: (interaction) => interaction.user.id === interaction.user.id,
+                time: 300000
+            }) as RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction;
         } catch (e) {
-            timedOut = true;
+            closed = true;
             continue;
         }
-        i.deferUpdate();
-        if (i.customId == "clear-message") {
-            if (lastClicked == "clear-message") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "welcome.message": null
-                });
-                lastClicked = null;
-            } else {
-                lastClicked = "clear-message";
+
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "switch": {
+                    await i.deferUpdate();
+                    data.enabled = !data.enabled;
+                    break;
+                }
+                case "message": {
+                    const modal = new ModalBuilder()
+                        .setCustomId("modal")
+                        .setTitle("Welcome Message")
+                        .addComponents(
+                            new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                new TextInputBuilder()
+                                    .setCustomId("ex1")
+                                    .setLabel("Server Info (1/3)")
+                                    .setPlaceholder(
+                                        `{serverName} - This server's name\n\n` +
+                                        `These placeholders will be replaced with the server's name, etc..`
+                                    )
+                                    .setMaxLength(1)
+                                    .setRequired(false)
+                                    .setStyle(TextInputStyle.Paragraph)
+                            ),
+                            new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                new TextInputBuilder()
+                                    .setCustomId("ex2")
+                                    .setLabel("Member Counts (2/3) - {MemberCount:...}")
+                                    .setPlaceholder(
+                                        `{:all} - Total member count\n` +
+                                        `{:humans} - Total non-bot users\n` +
+                                        `{:bots} - Number of bots\n`
+                                    )
+                                    .setMaxLength(1)
+                                    .setRequired(false)
+                                    .setStyle(TextInputStyle.Paragraph)
+                            ),
+                            new ActionRowBuilder<TextInputBuilder>().addComponents(
+                                new TextInputBuilder()
+                                    .setCustomId("ex3")
+                                    .setLabel("Member who joined (3/3) - {member:...}")
+                                    .setPlaceholder(
+                                            `{:name} - The members name\n`
+                                    )
+                                    .setMaxLength(1)
+                                    .setRequired(false)
+                                    .setStyle(TextInputStyle.Paragraph)
+                            ),
+                            new ActionRowBuilder<TextInputBuilder>()
+                                .addComponents(
+                                    new TextInputBuilder()
+                                        .setCustomId("message")
+                                        .setPlaceholder("Enter a message to send when someone joins the server")
+                                        .setValue(data.message ?? "")
+                                        .setLabel("Message")
+                                        .setStyle(TextInputStyle.Paragraph)
+                                )
+                        )
+                    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 i.editReply({
+                        embeds: [
+                            new EmojiEmbed()
+                                .setTitle("Welcome Settings")
+                                .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, interaction.user) as ModalSubmitInteraction | null;
+                    } catch (e) {
+                        console.error(e);
+                        out = null;
+                    }
+                    if(!out) break;
+                    data.message = out.fields.getTextInputValue("message");
+                    break;
+                }
+                case "save": {
+                    await i.deferUpdate();
+                    await client.database.guilds.write(interaction.guild!.id, {"welcome": data});
+                    config = await client.database.guilds.read(interaction.guild!.id);
+                    data = Object.assign({}, config.welcome);
+                    break;
+                }
+                case "channelDM": {
+                    await i.deferUpdate();
+                    data.channel = "dm";
+                    break;
+                }
+                case "role": {
+                    await i.deferUpdate();
+                    data.role = null;
+                    break;
+                }
             }
-        } else if (i.customId == "clear-role") {
-            if (lastClicked == "clear-role") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "welcome.role": null
-                });
-                lastClicked = null;
-            } else {
-                lastClicked = "clear-role";
+        } else if (i.isRoleSelectMenu()) {
+            await i.deferUpdate();
+            switch(i.customId) {
+                case "roleToGive": {
+                    data.role = i.values[0]!;
+                    break
+                }
+                case "roleToPing": {
+                    data.ping = i.values[0]!;
+                    break
+                }
             }
-        } else if (i.customId == "clear-ping") {
-            if (lastClicked == "clear-ping") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "welcome.ping": null
-                });
-                lastClicked = null;
-            } else {
-                lastClicked = "clear-ping";
-            }
-        } else if (i.customId == "clear-channel") {
-            if (lastClicked == "clear-channel") {
-                await client.database.guilds.write(interaction.guild!.id, {
-                    "welcome.channel": null
-                });
-                lastClicked = null;
-            } else {
-                lastClicked = "clear-channel";
-            }
-        } else if (i.customId == "set-channel-dm") {
-            await client.database.guilds.write(interaction.guild!.id, {
-                "welcome.channel": "dm"
-            });
-            lastClicked = null;
+        } else {
+            await i.deferUpdate();
+            data.channel = i.values[0]!;
         }
-    } while (!timedOut);
-    await interaction.editReply({
-        embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })],
-        components: []
-    });
+
+    } while (!closed);
+    await interaction.deleteReply()
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageGuild"))
         return "You must have the *Manage Server* permission to use this command";
@@ -309,7 +265,7 @@
 };
 
 const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
-    const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
+    const validReplacements = ["serverName", "memberCount:all", "memberCount:bots", "memberCount:humans"]
     if (!interaction.guild) return [];
     const prompt = interaction.options.getString("message");
     const autocompletions = [];
@@ -324,7 +280,7 @@
     if (beforeLastOpenBracket !== null) {
         if (afterLastOpenBracket !== null) {
             for (const replacement of validReplacements) {
-                if (replacement.startsWith(afterLastOpenBracket[0].slice(1))) {
+                if (replacement.startsWith(afterLastOpenBracket[0]!.slice(1))) {
                     autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
                 }
             }
@@ -341,4 +297,4 @@
     return autocompletions;
 };
 
-export { command, callback, check, autocomplete };
+export { command, callback, check, autocomplete };
\ No newline at end of file
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index a65947c..6ffecca 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -1,5 +1,4 @@
-import { AutocompleteInteraction, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import { AutocompleteInteraction, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from "discord.js";
 import client from "../utils/client.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import { capitalize } from "../utils/generateKeyValueList.js";
@@ -51,10 +50,6 @@
     return;
 };
 
-const check = () => {
-    return true;
-};
-
 const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
     if (!interaction.guild) return [];
     const prompt = interaction.options.getString("tag");
@@ -65,5 +60,4 @@
 
 export { command };
 export { callback };
-export { check };
 export { autocomplete };
diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts
index 788902e..1a1f695 100644
--- a/src/commands/tags/create.ts
+++ b/src/commands/tags/create.ts
@@ -1,6 +1,6 @@
 import type Discord from "discord.js";
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
@@ -106,7 +106,7 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageMessages"))
         return "You must have the *Manage Messages* permission to use this command";
diff --git a/src/commands/tags/delete.ts b/src/commands/tags/delete.ts
index 18143d3..4fdb10f 100644
--- a/src/commands/tags/delete.ts
+++ b/src/commands/tags/delete.ts
@@ -1,6 +1,6 @@
 import type Discord from "discord.js";
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
@@ -68,7 +68,7 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as Discord.GuildMember;
     if (!member.permissions.has("ManageMessages"))
         return "You must have the *Manage Messages* permission to use this command";
diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts
index e15f9ac..7e297c8 100644
--- a/src/commands/tags/edit.ts
+++ b/src/commands/tags/edit.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction, GuildMember } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
@@ -19,9 +19,9 @@
 
 const callback = async (interaction: CommandInteraction): Promise<unknown> => {
     if (!interaction.guild) return;
-    const name = interaction.options.get("name")?.value as string;
-    const value = interaction.options.get("value")?.value as string;
-    const newname = interaction.options.get("newname")?.value as string;
+    const name = (interaction.options.get("name")?.value ?? "") as string;
+    const value = (interaction.options.get("value")?.value ?? "") as string;
+    const newname = (interaction.options.get("newname")?.value ?? "") as string;
     if (!newname && !value)
         return await interaction.reply({
             embeds: [
@@ -126,7 +126,7 @@
     });
 };
 
-const check = (interaction: CommandInteraction) => {
+const check = (interaction: CommandInteraction, _partial: boolean = false) => {
     const member = interaction.member as GuildMember;
     if (!member.permissions.has("ManageMessages"))
         return "You must have the *Manage Messages* permission to use this command";
diff --git a/src/commands/tags/list.ts b/src/commands/tags/list.ts
index f0563c7..dbb1200 100644
--- a/src/commands/tags/list.ts
+++ b/src/commands/tags/list.ts
@@ -10,7 +10,7 @@
     ButtonComponent,
     StringSelectMenuBuilder
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import client from "../../utils/client.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
@@ -139,13 +139,13 @@
         try {
             i = 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) {
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
+        await i.deferUpdate();
         if ((i.component as ButtonComponent).customId === "left") {
             if (page > 0) page--;
             selectPaneOpen = false;
@@ -173,10 +173,6 @@
     });
 };
 
-const check = () => {
-    return true;
-};
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/ticket/close.ts b/src/commands/ticket/close.ts
index d2ffaf9..ff9da8b 100644
--- a/src/commands/ticket/close.ts
+++ b/src/commands/ticket/close.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import close from "../../actions/tickets/delete.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("close").setDescription("Closes a ticket");
@@ -8,10 +8,5 @@
     await close(interaction);
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/ticket/create.ts b/src/commands/ticket/create.ts
index 91442b5..2f3ddc6 100644
--- a/src/commands/ticket/create.ts
+++ b/src/commands/ticket/create.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import create from "../../actions/tickets/create.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -14,10 +14,6 @@
     await create(interaction);
 };
 
-const check = () => {
-    return true;
-};
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts
index e43ecb7..0eb8580 100644
--- a/src/commands/user/about.ts
+++ b/src/commands/user/about.ts
@@ -10,7 +10,7 @@
     APISelectMenuOption,
     StringSelectMenuBuilder
 } from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import generateKeyValueList from "../../utils/generateKeyValueList.js";
@@ -173,11 +173,8 @@
                         generateKeyValueList({
                             member: renderUser(member.user),
                             id: `\`${member.id}\``,
-                            roles: `${member.roles.cache.size - 1}`  // FIXME
-                        }) +
-                            "\n" +
-                            (s.length > 0 ? s : "*None*") +
-                            "\n"
+                            roles: `${member.roles.cache.size - 1}`
+                        }) + "\n" + (s.length > 0 ? s : "*None*") + "\n"
                     )
             )
             .setTitle("Roles")
@@ -258,13 +255,13 @@
         try {
             i = 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 {
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
+        await i.deferUpdate();
         if (i.customId === "left") {
             if (page > 0) page--;
             selectPaneOpen = false;
@@ -286,11 +283,6 @@
     });
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
 export { userAbout };
\ No newline at end of file
diff --git a/src/commands/user/avatar.ts b/src/commands/user/avatar.ts
index 88b3270..da33f51 100644
--- a/src/commands/user/avatar.ts
+++ b/src/commands/user/avatar.ts
@@ -1,6 +1,6 @@
 import type { CommandInteraction } from "discord.js";
 import type Discord from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import generateKeyValueList from "../../utils/generateKeyValueList.js";
 import client from "../../utils/client.js";
@@ -35,10 +35,6 @@
     });
 };
 
-const check = () => {
-    return true;
-};
 
 export { command };
 export { callback };
-export { check };
diff --git a/src/commands/user/role.ts b/src/commands/user/role.ts
new file mode 100644
index 0000000..41820ac
--- /dev/null
+++ b/src/commands/user/role.ts
@@ -0,0 +1,160 @@
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, GuildMember, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, UserSelectMenuBuilder, UserSelectMenuInteraction } from "discord.js";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
+import client from "../../utils/client.js";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import { LoadingEmbed } from "../../utils/defaults.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import listToAndMore from "../../utils/listToAndMore.js"
+
+const { renderUser } = client.logger;
+
+const canEdit = (role: Role, member: GuildMember, me: GuildMember): [string, boolean] => {
+    if(role.position >= me.roles.highest.position ||
+       role.position >= member.roles.highest.position
+    ) return [`~~<@&${role.id}>~~`, false];
+    return [`<@&${role.id}>`, true];
+};
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+        .setName("role")
+        .setDescription("Gives or removes a role from someone")
+        .addUserOption((option) => option.setName("user").setDescription("The user to give or remove the role from"))
+
+const callback = async (interaction: CommandInteraction): Promise<unknown> => {
+    const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true });
+
+    let member = interaction.options.getMember("user") as GuildMember | null;
+
+    if(!member) {
+        const memberEmbed = new EmojiEmbed()
+            .setTitle("Role")
+            .setDescription(`Please choose a member to edit the roles of.`)
+            .setEmoji("GUILD.ROLES.CREATE")
+            .setStatus("Success");
+        const memberChooser = new ActionRowBuilder<UserSelectMenuBuilder>().addComponents(
+            new UserSelectMenuBuilder()
+                .setCustomId("memberChooser")
+                .setPlaceholder("Select a member")
+        );
+        await interaction.editReply({embeds: [memberEmbed], components: [memberChooser]});
+
+        const filter = (i: UserSelectMenuInteraction) => i.customId === "memberChooser" && i.user.id === interaction.user.id;
+
+        let i: UserSelectMenuInteraction | null;
+        try {
+            i = await m.awaitMessageComponent<5>({ filter, time: 300000});
+        } catch (e) {
+            return;
+        }
+
+        memberEmbed.setDescription(`Editing roles for ${renderUser(i.values[0]!)}`);
+        await i.deferUpdate();
+        await interaction.editReply({ embeds: LoadingEmbed, components: [] })
+        member = await interaction.guild?.members.fetch(i.values[0]!)!;
+
+    }
+
+    let closed = false;
+    let rolesToChange: string[] = [];
+    const roleAdd = new ActionRowBuilder<RoleSelectMenuBuilder>()
+        .addComponents(
+            new RoleSelectMenuBuilder()
+                .setCustomId("roleAdd")
+                .setPlaceholder("Select a role to add")
+                .setMaxValues(25)
+        );
+
+    do {
+
+        const removing = rolesToChange.filter((r) => member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0])
+        const adding = rolesToChange.filter((r) => !member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0])
+        const embed = new EmojiEmbed()
+        .setTitle("Role")
+        .setDescription(
+            `${getEmojiByName("ICONS.EDIT")} Editing roles for <@${member.id}>\n\n` +
+            `Adding:\n` +
+            `${listToAndMore(adding.length > 0 ? adding : ["None"], 5)}\n` +
+            `Removing:\n` +
+            `${listToAndMore(removing.length > 0 ? removing : ["None"], 5)}\n`
+        )
+        .setEmoji("GUILD.ROLES.CREATE")
+        .setStatus("Success");
+
+        const buttons = new ActionRowBuilder<ButtonBuilder>()
+            .addComponents(
+                new ButtonBuilder()
+                    .setCustomId("roleSave")
+                    .setLabel("Apply")
+                    .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Success),
+                new ButtonBuilder()
+                    .setCustomId("roleDiscard")
+                    .setLabel("Reset")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji)
+                    .setStyle(ButtonStyle.Danger)
+            );
+
+        await interaction.editReply({ embeds: [embed], components: [roleAdd, buttons] });
+
+        let i: RoleSelectMenuInteraction | ButtonInteraction | null;
+        try {
+            i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 300000 }) as RoleSelectMenuInteraction | ButtonInteraction;
+        } catch (e) {
+            closed = true;
+            continue;
+        }
+
+        i.deferUpdate();
+        if(i.isButton()) {
+            switch(i.customId) {
+                case "roleSave": {
+                    const roles = rolesToChange.map((r) => interaction.guild?.roles.cache.get(r)!);
+                    await interaction.editReply({ embeds: LoadingEmbed, components: [] });
+                    const rolesToAdd: Role[] = [];
+                    const rolesToRemove: Role[] = [];
+                    for(const role of roles) {
+                        if(!canEdit(role, interaction.member as GuildMember, interaction.guild?.members.me!)[1]) continue;
+                        if(member.roles.cache.has(role.id)) {
+                            rolesToRemove.push(role);
+                        } else {
+                            rolesToAdd.push(role);
+                        }
+                    }
+                    await member.roles.add(rolesToAdd);
+                    await member.roles.remove(rolesToRemove);
+                    rolesToChange = [];
+                    break;
+                }
+                case "roleDiscard": {
+                    rolesToChange = [];
+                    await interaction.editReply({ embeds: LoadingEmbed, components: [] });
+                    break;
+                }
+            }
+        } else {
+            rolesToChange = i.values;
+        }
+
+    } while (!closed);
+
+};
+
+const check = (interaction: CommandInteraction, partial: boolean = false) => {
+    const member = interaction.member as GuildMember;
+    // Check if the user has manage_roles permission
+    if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
+    if (partial) return true;
+    if (!interaction.guild) return
+    const me = interaction.guild.members.me!;
+    // Check if Nucleus has permission to role
+    if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission";
+    // Allow the owner to role anyone
+    if (member.id === interaction.guild.ownerId) return true;
+    // Allow role
+    return true;
+};
+
+export { command };
+export { callback };
+export { check };
diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts
index 0814cfa..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 type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+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 = [
@@ -169,7 +150,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) {
             timedOut = true;
@@ -207,9 +188,9 @@
     }
 };
 
-const check = async (interaction: CommandInteraction) => {
+const check = async (interaction: CommandInteraction, _partial: boolean = false) => {
     const tracks = (await client.database.guilds.read(interaction.guild!.id)).tracks;
-    if (tracks.length === 0) throw new Error("This server does not have any tracks");
+    if (tracks.length === 0) return "This server does not have any tracks";
     const member = interaction.member as GuildMember;
     // Allow the owner to promote anyone
     if (member.id === interaction.guild!.ownerId) return true;
@@ -223,8 +204,7 @@
         break;
     }
     // Check if the user has manage_roles permission
-    if (!managed && !member.permissions.has("ManageRoles"))
-        throw new Error("You do not have the *Manage Roles* permission");
+    if (!managed && !member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
     // Allow track
     return true;
 };
diff --git a/src/commands/verify.ts b/src/commands/verify.ts
index 4fafe69..0dd8b24 100644
--- a/src/commands/verify.ts
+++ b/src/commands/verify.ts
@@ -1,5 +1,5 @@
 import type { CommandInteraction } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import { SlashCommandBuilder } from "discord.js";
 import verify from "../reflex/verify.js";
 
 const command = new SlashCommandBuilder().setName("verify").setDescription("Get verified in the server");
@@ -8,10 +8,5 @@
     verify(interaction);
 };
 
-const check = () => {
-    return true;
-};
-
 export { command };
 export { callback };
-export { check };
diff --git a/src/config/default.json b/src/config/default.json
deleted file mode 100644
index 8e4197c..0000000
--- a/src/config/default.json
+++ /dev/null
@@ -1,100 +0,0 @@
-{
-    "id": "default",
-    "version": 1,
-    "singleEventNotifications": {
-        "statsChannelDeleted": false
-    },
-    "filters": {
-        "images": {
-            "NSFW": false,
-            "size": false
-        },
-        "malware": false,
-        "wordFilter": {
-            "enabled": false,
-            "words": {
-                "strict": [],
-                "loose": []
-            }
-        },
-        "invite": {
-            "enabled": false,
-            "allowed": {
-                "users": [],
-                "roles": [],
-                "channels": []
-            }
-        },
-        "pings": {
-            "mass": 5,
-            "everyone": true,
-            "roles": true
-        }
-    },
-    "welcome": {
-        "enabled": false,
-        "role": null,
-        "ping": null,
-        "channel": null,
-        "message": null
-    },
-    "stats": {},
-    "logging": {
-        "logs": {
-            "enabled": true,
-            "channel": null,
-            "toLog": "3fffff"
-        },
-        "staff": {
-            "channel": null
-        },
-        "attachments": {
-            "channel": null,
-            "saved": {}
-        }
-    },
-    "verify": {
-        "enabled": false,
-        "role": null
-    },
-    "tickets": {
-        "enabled": false,
-        "category": null,
-        "types": "3f",
-        "customTypes": null,
-        "useCustom": false,
-        "supportRole": null,
-        "maxTickets": 5
-    },
-    "moderation": {
-        "mute": {
-            "timeout": true,
-            "role": null,
-            "text": null,
-            "link": null
-        },
-        "kick": {
-            "text": null,
-            "link": null
-        },
-        "ban": {
-            "text": null,
-            "link": null
-        },
-        "softban": {
-            "text": null,
-            "link": null
-        },
-        "warn": {
-            "text": null,
-            "link": null
-        },
-        "nickname": {
-            "text": null,
-            "link": null
-        }
-    },
-    "tracks": [],
-    "roleMenu": [],
-    "tags": {}
-}
diff --git a/src/config/emojis.json b/src/config/emojis.json
index 26d4c98..ecf1858 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -1,6 +1,8 @@
 {
     "NUCLEUS": {
         "LOGO": "953040840945721385",
+        "PREMIUMACTIVATE": "a1067536222764925068",
+        "PREMIUM": "1067928702027042876",
         "LOADING": "a946346549271732234",
         "INFO": {
             "HELP": "751751467014029322",
@@ -22,9 +24,11 @@
         "FILTER": "990242059451514902",
         "ATTACHMENT": "997570687193587812",
         "LOGGING": "999613304446144562",
+        "SAVE": "1065722246322200586",
+        "REORDER": "1069323453909454890",
         "NOTIFY": {
             "ON": "1000726394579464232",
-            "OFF": "1000726363495477368"
+            "OFF": "1078058136092541008"
         },
         "OPP": {
             "ADD": "837355918831124500",
@@ -94,9 +98,8 @@
         "TITLEUPDATE": "729763053620691044",
         "TOPICUPDATE": "729763053477953536",
         "SLOWMODE": {
-            "ON": "777138171301068831",
-            "OFF": "777138171447869480",
-            "// TODO": "Make these green and red respectively"
+            "ON": "973616021304913950",
+            "OFF": "777138171447869480"
         },
         "NSFW": {
             "ON": "729064531208175736",
@@ -210,6 +213,12 @@
             "STOP": "853519660116738078"
         }
     },
+    "SETTINGS": {
+        "STATS": {
+            "GREEN": "752214059159650396",
+            "RED": "1065677252630675556"
+        }
+    },
     "GUILD": {
         "RED": "959779988264079361",
         "YELLOW": "729763053352124529",
@@ -219,8 +228,10 @@
             "EDIT": "729066518549233795",
             "DELETE": "953035210121953320"
         },
-        "GRAPHS": "752214059159650396",
-        "SETTINGS": "752570111063228507",
+        "SETTINGS": {
+            "GREEN": "752570111063228507",
+            "RED": "1068607393728049253"
+        },
         "ICONCHANGE": "729763053612302356",
         "TICKET": {
             "OPEN": "853245836331188264",
@@ -338,7 +349,7 @@
             "TOP": {
                 "ACTIVE": "963122664648630293",
                 "INACTIVE": "963122659862917140",
-                "GREY": {
+                "GRAY": {
                     "ACTIVE": "963123505052934144",
                     "INACTIVE": "963123495221469194"
                 }
@@ -346,7 +357,7 @@
             "MIDDLE": {
                 "ACTIVE": "963122679332880384",
                 "INACTIVE": "963122673246937199",
-                "GREY": {
+                "GRAY": {
                     "ACTIVE": "963123517702955018",
                     "INACTIVE": "963123511927390329"
                 }
@@ -354,7 +365,7 @@
             "BOTTOM": {
                 "ACTIVE": "963122691752218624",
                 "INACTIVE": "963122685691453552",
-                "GREY": {
+                "GRAY": {
                     "ACTIVE": "963123529988059187",
                     "INACTIVE": "963123523742748742"
                 }
@@ -363,10 +374,20 @@
         "SINGLE": {
             "ACTIVE": "963361162215424060",
             "INACTIVE": "963361431758176316",
-            "GREY": {
+            "GRAY": {
                 "ACTIVE": "963361204695334943",
                 "INACTIVE": "963361200828198952"
             }
         }
+    },
+    "COLORS": {
+        "RED": "875822912802803754",
+        "ORANGE": "875822913104785418",
+        "YELLOW": "875822913079611402",
+        "GREEN": "875822913213841418",
+        "BLUE": "875822912777637889",
+        "PURPLE": "875822913213841419",
+        "PINK": "875822913088020541",
+        "GRAY": "875822913117368340"
     }
 }
diff --git a/src/config/format.ts b/src/config/format.ts
index 713a233..e32bef6 100644
--- a/src/config/format.ts
+++ b/src/config/format.ts
@@ -1,8 +1,8 @@
+
 import fs from "fs";
-// @ts-expect-error
 import * as readLine from "node:readline/promises";
 
-const defaultDict: Record<string, string | string[] | boolean> = {
+const defaultDict: Record<string, string | string[] | boolean | Record<string, string>> = {
     developmentToken: "Your development bot token (Used for testing in one server, rather than production)",
     developmentGuildID: "Your development guild ID",
     enableDevelopment: true,
@@ -15,7 +15,17 @@
     userContextFolder: "Your built user context folder (usually dist/context/users)",
     verifySecret:
         "If using verify, enter a code here which matches the secret sent back by your website. You can use a random code if you do not have one already. (Optional)",
-    mongoUrl: "Your Mongo connection string, e.g. mongodb://127.0.0.1:27017",
+    mongoUsername: "Your Mongo username (optional)",
+    mongoPassword: "Your Mongo password (optional)",
+    mongoDatabase: "Your Mongo database name (optional, e.g. Nucleus)",
+    mongoHost: "Your Mongo host (optional, e.g. localhost:27017)",
+    mongoOptions: {
+        username: "",
+        password: "",
+        database: "",
+        host: "",
+        authSource: "",
+    },
     baseUrl: "Your website where buttons such as Verify and Role menu will link to, e.g. https://example.com/",
     pastebinApiKey: "An API key for pastebin (optional)",
     pastebinUsername: "Your pastebin username (optional)",
@@ -51,18 +61,27 @@
         // }
     }
 
-    let json;
+    let json: typeof defaultDict;
     let out = true;
     try {
-        json = JSON.parse(fs.readFileSync("./src/config/main.json", "utf8"));
+        json = await import("./main.js") as unknown as typeof defaultDict;
     } catch (e) {
-        console.log("\x1b[31m⚠ No main.json found, creating one.");
-        console.log("  \x1b[2mYou can edit src/config/main.json directly using template written to the file.\x1b[0m\n");
+        console.log("\x1b[31m⚠ No main.ts found, creating one.");
+        console.log("  \x1b[2mYou can edit src/config/main.ts directly using template written to the file.\x1b[0m\n");
         out = false;
-        json = {};
+        json = {} as typeof defaultDict;
     }
+
+    if (Object.keys(json).length) {
+        if (json["token"] === defaultDict["token"] || json["developmentToken"] === defaultDict["developmentToken"]) {
+            console.log("\x1b[31m⚠ No main.ts found, creating one.");
+            console.log("  \x1b[2mYou can edit src/config/main.ts directly using template written to the file.\x1b[0m\n");
+            json = {};
+        }
+    }
+
     for (const key in defaultDict) {
-        if (!json[key]) {
+        if (Object.keys(json).includes(key)) {
             if (walkthrough) {
                 switch (key) {
                     case "enableDevelopment": {
@@ -88,18 +107,20 @@
                         json[key] = toWrite;
                         break;
                     }
+                    case "mongoOptions": {
+                        break;
+                    }
                     default: {
                         json[key] = await getInput(`\x1b[36m${key} \x1b[0m(\x1b[35m${defaultDict[key]}\x1b[0m) > `);
                     }
                 }
             } else {
-                json[key] = defaultDict[key];
+                json[key] = defaultDict[key]!;
             }
         }
     }
-    if (walkthrough && !json.mongoUrl) json.mongoUrl = "mongodb://127.0.0.1:27017";
-    if (!json.mongoUrl.endsWith("/")) json.mongoUrl += "/";
-    if (!json.baseUrl.endsWith("/")) json.baseUrl += "/";
+    if (walkthrough && !(json["mongoUrl"] ?? false)) json["mongoUrl"] = "mongodb://127.0.0.1:27017";
+    if (!((json["baseUrl"] as string | undefined) ?? "").endsWith("/")) (json["baseUrl"] as string) += "/";
     let hosts;
     try {
         hosts = fs.readFileSync("/etc/hosts", "utf8").toString().split("\n");
@@ -108,16 +129,23 @@
             "\x1b[31m⚠ No /etc/hosts found. Please ensure the file exists and is readable. (Windows is not supported, Mac and Linux users should not experience this error)"
         );
     }
-    let localhost = hosts.find((line) => line.split(" ")[1] === "localhost");
+    let localhost: string | undefined = hosts.find((line) => line.split(" ")[1] === "localhost");
     if (localhost) {
         localhost = localhost.split(" ")[0];
     } else {
         localhost = "127.0.0.1";
     }
-    json.mongoUrl = json.mongoUrl.replace("localhost", localhost);
-    json.baseUrl = json.baseUrl.replace("localhost", localhost);
+    json["mongoUrl"] = (json["mongoUrl"]! as string).replace("localhost", localhost!);
+    json["baseUrl"] = (json["baseUrl"]! as string).replace("localhost", localhost!);
+    json["mongoOptions"] = {
+        username: json["username"] as string,
+        password: json["password"] as string,
+        database: json["database"] as string,
+        host: json["host"] as string,
+        authSource: json["authSource"] as string,
+    };
 
-    fs.writeFileSync("./src/config/main.json", JSON.stringify(json, null, 4));
+    fs.writeFileSync("./src/config/main.ts", "export default " + JSON.stringify(json, null, 4) + ";");
 
     if (walkthrough) {
         console.log("\x1b[32m✓ All properties added.\x1b[0m");
diff --git a/src/config/main.d.ts b/src/config/main.d.ts
new file mode 100644
index 0000000..6549234
--- /dev/null
+++ b/src/config/main.d.ts
@@ -0,0 +1,26 @@
+declare const config: {
+    developmentToken: string,
+    developmentGuildID: string,
+    enableDevelopment: boolean,
+    token: string,
+    managementGuildID: string,
+    owners: string[],
+    commandsFolder: string,
+    eventsFolder: string,
+    messageContextFolder: string,
+    userContextFolder: string,
+    verifySecret: string,
+    mongoOptions: {
+        username: string,
+        password: string,
+        database: string,
+        host: string,
+    },
+    baseUrl: string,
+    pastebinApiKey: string,
+    pastebinUsername: string,
+    pastebinPassword: string,
+    rapidApiKey: string
+};
+
+export default config;
\ No newline at end of file
diff --git a/src/config/main.json b/src/config/main.json
deleted file mode 100644
index 64abe93..0000000
--- a/src/config/main.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-    developmentToken: "Your development bot token (Used for testing in one server, rather than production)",
-    developmentGuildID: "Your development guild ID",
-    enableDevelopment: true,
-    token: "Your bot token",
-    managementGuildID: "Your management guild ID (Used for running management commands on the bot)",
-    owners: [],
-    commandsFolder: "Your built commands folder (usually dist/commands)",
-    eventsFolder: "Your built events folder (usually dist/events)",
-    messageContextFolder: "Your built message context folder (usually dist/context/messages)",
-    userContextFolder: "Your built user context folder (usually dist/context/users)",
-    verifySecret:
-        "If using verify, enter a code here which matches the secret sent back by your website. You can use a random code if you do not have one already. (Optional)",
-    mongoUrl: "Your Mongo connection string, e.g. mongodb://127.0.0.1:27017",
-    baseUrl: "Your website where buttons such as Verify and Role menu will link to, e.g. https://example.com/",
-    pastebinApiKey: "An API key for pastebin (optional)",
-    pastebinUsername: "Your pastebin username (optional)",
-    pastebinPassword: "Your pastebin password (optional)",
-    rapidApiKey: "Your RapidAPI key (optional), used for Unscan"
-}
diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts
index df52e0b..aef159b 100644
--- a/src/context/messages/purgeto.ts
+++ b/src/context/messages/purgeto.ts
@@ -1,10 +1,9 @@
 import confirmationMessage from '../../utils/confirmationMessage.js';
 import EmojiEmbed from '../../utils/generateEmojiEmbed.js';
 import { LoadingEmbed } from '../../utils/defaults.js';
-import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, GuildTextBasedChannel, MessageContextMenuCommandInteraction } from "discord.js";
+import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, GuildMember, GuildTextBasedChannel, Message, MessageContextMenuCommandInteraction } from "discord.js";
 import client from "../../utils/client.js";
-import getEmojiByName from '../../utils/getEmojiByName.js';
-import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from "../../utils/logTranscripts.js";
+import { messageException } from '../../utils/createTemporaryStorage.js';
 
 const command = new ContextMenuCommandBuilder()
     .setName("Purge up to here")
@@ -13,7 +12,7 @@
 async function waitForButton(m: Discord.Message, member: Discord.GuildMember): Promise<boolean> {
     let component;
     try {
-        component = m.awaitMessageComponent({ time: 200000, filter: (i) => i.user.id === member.id && i.channel!.id === m.channel.id });
+        component = m.awaitMessageComponent({ time: 200000, filter: (i) => i.user.id === member.id && i.channel!.id === m.channel.id && i.message.id === m.id });
     } catch (e) {
         return false;
     }
@@ -171,7 +170,7 @@
             calculateType: "messageDelete",
             color: NucleusColors.red,
             emoji: "PUNISH.BAN.RED",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
@@ -184,13 +183,18 @@
         }
     };
     log(data);
-    const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(deleted.map((m) => m as Discord.Message))!);
-    const attachmentObject = {
-        attachment: Buffer.from(transcript),
-        name: `purge-${channel.id}-${Date.now()}.txt`,
-        description: "Purge log"
-    };
-    const m = (await interaction.editReply({
+    const messages: Message[] = deleted.map(m => m).filter(m => m instanceof Message).map(m => m as Message);
+    if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id)
+    const messageArray: Message[] = messages.filter(message => !(
+        message!.components.some(
+            component => component.components.some(
+                child => child.customId?.includes("transcript") ?? false
+            )
+        )
+    )).map(message => message as Message);
+    const transcript = await client.database.transcripts.createTranscript(messageArray, interaction, interaction.member as GuildMember);
+    const code = await client.database.transcripts.create(transcript);
+    await interaction.editReply({
         embeds: [
             new EmojiEmbed()
                 .setEmoji("CHANNEL.PURGE.GREEN")
@@ -200,47 +204,10 @@
         ],
         components: [
             new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
-                new Discord.ButtonBuilder()
-                    .setCustomId("download")
-                    .setLabel("Download transcript")
-                    .setStyle(ButtonStyle.Success)
-                    .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
+                new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`),
             ])
         ]
-    })) as Discord.Message;
-    let component;
-    try {
-        component = await m.awaitMessageComponent({
-            filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id,
-            time: 300000
-        });
-    } catch {
-        return;
-    }
-    if (component.customId === "download") {
-        interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setEmoji("CHANNEL.PURGE.GREEN")
-                    .setTitle("Purge")
-                    .setDescription("Transcript uploaded above")
-                    .setStatus("Success")
-            ],
-            components: [],
-            files: [attachmentObject]
-        });
-    } else {
-        interaction.editReply({
-            embeds: [
-                new EmojiEmbed()
-                    .setEmoji("CHANNEL.PURGE.GREEN")
-                    .setTitle("Purge")
-                    .setDescription("Messages cleared")
-                    .setStatus("Success")
-            ],
-            components: []
-        });
-    }
+    });
 }
 
 const check = async (_interaction: MessageContextMenuCommandInteraction) => {
diff --git a/src/context/users/userinfo.ts b/src/context/users/userinfo.ts
index 3b1a6bd..496f84e 100644
--- a/src/context/users/userinfo.ts
+++ b/src/context/users/userinfo.ts
@@ -5,6 +5,7 @@
     .setName("User info")
 
 const callback = async (interaction: UserContextMenuCommandInteraction) => {
+    console.log("callback")
     const guild = interaction.guild!
     let member = interaction.targetMember
     if (!member) member = await guild.members.fetch(interaction.targetId)
@@ -12,6 +13,7 @@
 }
 
 const check = async (_interaction: UserContextMenuCommandInteraction) => {
+    console.log("check")
     return true;
 }
 
diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts
index dda37af..b42ded7 100644
--- a/src/events/channelCreate.ts
+++ b/src/events/channelCreate.ts
@@ -4,7 +4,8 @@
 export const event = "channelCreate";
 
 export async function callback(client: NucleusClient, channel: GuildBasedChannel) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    if (!await isLogging(channel.guild.id, "channelUpdate")) return;
     const auditLog = (await getAuditLog(channel.guild, AuditLogEvent.ChannelCreate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildBasedChannel)!.id === channel.id)[0];
     if (!auditLog) return;
@@ -62,7 +63,7 @@
             calculateType: "channelUpdate",
             color: NucleusColors.green,
             emoji: emoji,
-            timestamp: channel.createdTimestamp
+            timestamp: channel.createdTimestamp ?? Date.now()
         },
         list: {
             channelId: entry(channel.id, `\`${channel.id}\``),
diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts
index 4780700..890a15f 100644
--- a/src/events/channelDelete.ts
+++ b/src/events/channelDelete.ts
@@ -14,8 +14,8 @@
 export const event = "channelDelete";
 
 export async function callback(client: NucleusClient, channel: GuildBasedChannel) {
-    const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser } = client.logger;
-    // const audit = auditLog.entries.filter((entry: GuildAuditLogsEntry) => entry.target!.id === channel.id).first();
+    const { getAuditLog, log, isLogging, NucleusColors, entry, renderDelta, renderUser } = client.logger;
+    if (!await isLogging(channel.guild.id, "channelUpdate")) return;
     const auditLog = (await getAuditLog(channel.guild, AuditLogEvent.ChannelDelete))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildBasedChannel)!.id === channel.id)[0];
     if (!auditLog) return;
@@ -82,7 +82,7 @@
         ),
         nsfw: null,
         created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp!)),
-        deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+        deleted: entry(Date.now(), renderDelta(Date.now())),
         deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!))
     };
     if ((channel instanceof BaseGuildTextChannel || channel instanceof StageChannel) && channel.topic !== null)
diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts
index 7c80e12..052acc1 100644
--- a/src/events/channelUpdate.ts
+++ b/src/events/channelUpdate.ts
@@ -32,11 +32,11 @@
     topic?: ReturnType<typeof entry>;
     bitrate?: ReturnType<typeof entry>;
     userLimit?: ReturnType<typeof entry>;
-    rateLimitPerUser?: ReturnType<typeof entry>;
     parent?: ReturnType<typeof entry>;
     permissionOverwrites?: ReturnType<typeof entry>;
     region?: ReturnType<typeof entry>;
     maxUsers?: ReturnType<typeof entry>;
+    autoArchiveDuration?: ReturnType<typeof entry>;
 }
 
 
@@ -44,8 +44,9 @@
 export const event = "channelUpdate";
 
 export async function callback(client: NucleusClient, oldChannel: GuildChannel, newChannel: GuildChannel) {
+    const { getAuditLog, log, isLogging, NucleusColors, renderDelta, renderUser, renderChannel } = client.logger;
+    if (!await isLogging(newChannel.guild.id, "channelUpdate")) return;
     const config = await client.memory.readGuildInfo(newChannel.guild.id);
-    const { getAuditLog, log, NucleusColors, renderDelta, renderUser, renderChannel } = client.logger;
     entry = client.logger.entry;
     if (newChannel.parent && newChannel.parent.id === config.tickets.category) return;
 
@@ -60,7 +61,7 @@
     const changes: channelChanges = {
         channelId: entry(newChannel.id, `\`${newChannel.id}\``),
         channel: entry(newChannel.id, renderChannel(newChannel)),
-        edited: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+        edited: entry(Date.now(), renderDelta(Date.now())),
         editedBy: entry(auditLog.executor!.id, renderUser((await newChannel.guild.members.fetch(auditLog.executor!.id)).user)),
     };
     if (oldChannel.name !== newChannel.name) changes.name = entry([oldChannel.name, newChannel.name], `${oldChannel.name} -> ${newChannel.name}`);
@@ -68,12 +69,16 @@
         changes.position = entry([oldChannel.position.toString(), newChannel.position.toString()], `${oldChannel.position} -> ${newChannel.position}`);
 
     switch (newChannel.type) {
+        case ChannelType.PrivateThread:
+        case ChannelType.PublicThread: {
+            return;
+        }
         case ChannelType.GuildText: {
             emoji = "CHANNEL.TEXT.EDIT";
             readableType = "Text";
             displayName = "Text Channel";
-            let oldTopic = (oldChannel as TextChannel).topic,
-                newTopic = (newChannel as TextChannel).topic;
+            let oldTopic = (oldChannel as TextChannel).topic ?? "*None*",
+                newTopic = (oldChannel as TextChannel).topic ?? "*None*";
             if (oldTopic) {
                 if (oldTopic.length > 256)
                     oldTopic = `\`\`\`\n${oldTopic.replace("`", "'").substring(0, 253) + "..."}\n\`\`\``;
@@ -91,14 +96,20 @@
             const nsfw = ["", ""];
             nsfw[0] = (oldChannel as TextChannel).nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
             nsfw[1] = (newChannel as TextChannel).nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
-            if ((oldChannel as TextChannel).topic !== (newChannel as TextChannel).topic)
+            if (oldTopic !== newTopic)
                 changes.description = entry([(oldChannel as TextChannel).topic ?? "", (newChannel as TextChannel).topic ?? ""], `\nBefore: ${oldTopic}\nAfter: ${newTopic}`);
             if ((oldChannel as TextChannel).nsfw !== (newChannel as TextChannel).nsfw) changes.nsfw = entry([(oldChannel as TextChannel).nsfw ? "On" : "Off", (newChannel as TextChannel).nsfw ? "On" : "Off"], `${nsfw[0]} -> ${nsfw[1]}`);
-            if ((oldChannel as TextChannel).rateLimitPerUser !== (newChannel as TextChannel).rateLimitPerUser && (oldChannel as TextChannel).rateLimitPerUser !== 0)
-                changes.rateLimitPerUser = entry(
+            if ((oldChannel as TextChannel).rateLimitPerUser !== (newChannel as TextChannel).rateLimitPerUser)
+                changes.slowmode = entry(
                     [((oldChannel as TextChannel).rateLimitPerUser).toString(), ((newChannel as TextChannel).rateLimitPerUser).toString()],
                     `${humanizeDuration((oldChannel as TextChannel).rateLimitPerUser * 1000)} -> ${humanizeDuration((newChannel as TextChannel).rateLimitPerUser * 1000)}`
                 );
+            if((oldChannel as TextChannel).defaultAutoArchiveDuration !== (newChannel as TextChannel).defaultAutoArchiveDuration) {
+                changes.autoArchiveDuration = entry(
+                    [((oldChannel as TextChannel).defaultAutoArchiveDuration ?? 4320).toString(), ((newChannel as TextChannel).defaultAutoArchiveDuration ?? 4320).toString()],
+                    `${humanizeDuration(((oldChannel as TextChannel).defaultAutoArchiveDuration ?? 4320) * 60 * 1000)} -> ${humanizeDuration(((newChannel as TextChannel).defaultAutoArchiveDuration ?? 4320) * 60 * 1000)}`
+                );
+            }
 
             break;
         }
@@ -122,8 +133,15 @@
             } else {
                 newTopic = "None";
             }
-            if ((oldChannel as TextChannel).nsfw !== (newChannel as TextChannel).nsfw)
+            if ((oldChannel as TextChannel).nsfw !== (newChannel as TextChannel).nsfw) {
                 changes.nsfw = entry([(oldChannel as TextChannel).nsfw ? "On" : "Off", (newChannel as TextChannel).nsfw ? "On" : "Off"], `${(oldChannel as TextChannel).nsfw ? "On" : "Off"} -> ${(newChannel as TextChannel).nsfw ? "On" : "Off"}`);
+            }
+            if((oldChannel as TextChannel).defaultAutoArchiveDuration !== (newChannel as TextChannel).defaultAutoArchiveDuration) {
+                changes.autoArchiveDuration = entry(
+                    [((oldChannel as TextChannel).defaultAutoArchiveDuration ?? 4320).toString(), ((newChannel as TextChannel).defaultAutoArchiveDuration ?? 4320).toString()],
+                    `${humanizeDuration(((oldChannel as TextChannel).defaultAutoArchiveDuration ?? 4320) * 60 * 1000)} -> ${humanizeDuration(((newChannel as TextChannel).defaultAutoArchiveDuration ?? 4320) * 60 * 1000)}`
+                );
+            }
             break;
         }
         case ChannelType.GuildVoice: {
@@ -174,7 +192,7 @@
             if ((oldChannel as StageChannel).rtcRegion !== (newChannel as StageChannel).rtcRegion)
                 changes.region = entry(
                     [(oldChannel as StageChannel).rtcRegion ?? "Automatic", (newChannel as StageChannel).rtcRegion ?? "Automatic"],
-                    `${(oldChannel as StageChannel).rtcRegion?.toUpperCase() ?? "Automatic"} -> ${(newChannel as StageChannel).rtcRegion?.toUpperCase() ?? "Automatic"}`
+                    `${capitalize((oldChannel as StageChannel).rtcRegion?.toLowerCase() ?? "automatic")} -> ${capitalize((newChannel as StageChannel).rtcRegion?.toLowerCase() ?? "automatic")}`
                 );
             break;
         }
diff --git a/src/events/emojiCreate.ts b/src/events/emojiCreate.ts
index 8023abc..2630295 100644
--- a/src/events/emojiCreate.ts
+++ b/src/events/emojiCreate.ts
@@ -4,9 +4,10 @@
 export const event = "emojiCreate";
 
 export async function callback(client: NucleusClient, emoji: GuildEmoji) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger;
+    const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger;
+    if (!await isLogging(emoji.guild.id, "emojiUpdate")) return;
     const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiCreate))
-        .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0];
+    .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0];
     if (!auditLog) return;
     if (auditLog.executor!.id === client.user!.id) return;
     const data = {
diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts
index f607cf4..c4b488e 100644
--- a/src/events/emojiDelete.ts
+++ b/src/events/emojiDelete.ts
@@ -4,8 +4,9 @@
 export const event = "emojiDelete";
 
 export async function callback(client: NucleusClient, emoji: GuildEmoji) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger;
-    const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiCreate))
+    const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger;
+    if (!await isLogging(emoji.guild.id, "emojiUpdate")) return;
+    const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiDelete))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0];
     if (!auditLog) return;
     if (auditLog.executor!.id === client.user!.id) return;
diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts
index 201dd42..98ff558 100644
--- a/src/events/emojiUpdate.ts
+++ b/src/events/emojiUpdate.ts
@@ -4,9 +4,10 @@
 export const event = "emojiUpdate";
 
 export async function callback(client: NucleusClient, oldEmoji: GuildEmoji, newEmoji: GuildEmoji) {
-    const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderEmoji } = client.logger;
+    const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger;
+    if (!(await isLogging(newEmoji.guild.id, "emojiUpdate"))) return;
 
-    const auditLog = (await getAuditLog(newEmoji.guild, AuditLogEvent.EmojiCreate))
+    const auditLog = (await getAuditLog(newEmoji.guild, AuditLogEvent.EmojiUpdate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === newEmoji.id)[0];
     if (!auditLog) return;
     if (auditLog.executor!.id === client.user!.id) return;
diff --git a/src/events/guildBanAdd.ts b/src/events/guildBanAdd.ts
index 3d96245..cac4b41 100644
--- a/src/events/guildBanAdd.ts
+++ b/src/events/guildBanAdd.ts
@@ -7,10 +7,11 @@
 export const event = "guildBanAdd";
 
 export async function callback(client: NucleusClient, ban: GuildBan) {
+    const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger;
     await statsChannelRemove(client, undefined, ban.guild, ban.user);
     purgeByUser(ban.user.id, ban.guild.id);
-    const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger;
-    const auditLog: GuildAuditLogsEntry | undefined = (await getAuditLog(ban.guild, AuditLogEvent.EmojiCreate))
+    if (!(await isLogging(ban.guild.id, "guildMemberPunish"))) return;
+    const auditLog: GuildAuditLogsEntry | undefined = (await getAuditLog(ban.guild, AuditLogEvent.MemberBanAdd))
         .filter((entry: GuildAuditLogsEntry) => ((entry.target! as User).id === ban.user.id))[0];
     if (!auditLog) return;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -22,12 +23,12 @@
             calculateType: "guildMemberPunish",
             color: NucleusColors.red,
             emoji: "PUNISH.BAN.RED",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(ban.user.id, `\`${ban.user.id}\``),
             name: entry(ban.user.id, renderUser(ban.user)),
-            banned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+            banned: entry(Date.now(), renderDelta(Date.now())),
             bannedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
             reason: entry(auditLog.reason, auditLog.reason ? `\n> ${auditLog.reason}` : "*No reason provided.*"),
             accountCreated: entry(ban.user.createdTimestamp, renderDelta(ban.user.createdTimestamp)),
diff --git a/src/events/guildBanRemove.ts b/src/events/guildBanRemove.ts
index bcb70d5..3be4560 100644
--- a/src/events/guildBanRemove.ts
+++ b/src/events/guildBanRemove.ts
@@ -1,14 +1,13 @@
 import type { GuildAuditLogsEntry, GuildBan, User } from "discord.js";
 import { AuditLogEvent } from "discord.js";
-import { purgeByUser } from "../actions/tickets/delete.js";
 import type { NucleusClient } from "../utils/client.js";
 
 export const event = "guildBanRemove";
 
 export async function callback(client: NucleusClient, ban: GuildBan) {
-    purgeByUser(ban.user.id, ban.guild.id);
-    const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger;
-    const auditLog = (await getAuditLog(ban.guild, AuditLogEvent.EmojiCreate))
+    const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger;
+    if (!await isLogging(ban.guild.id, "guildMemberPunish")) return;
+    const auditLog = (await getAuditLog(ban.guild, AuditLogEvent.MemberBanRemove))
         .filter((entry: GuildAuditLogsEntry) => ((entry.target! as User).id === ban.user.id))[0];
     if (!auditLog) return;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -20,12 +19,12 @@
             calculateType: "guildMemberPunish",
             color: NucleusColors.green,
             emoji: "PUNISH.BAN.GREEN",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             memberId: entry(ban.user.id, `\`${ban.user.id}\``),
             name: entry(ban.user.id, renderUser(ban.user)),
-            unbanned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+            unbanned: entry(Date.now(), renderDelta(Date.now())),
             unbannedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
             accountCreated: entry(ban.user.createdTimestamp, renderDelta(ban.user.createdTimestamp))
         },
diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts
index 8889f57..721978f 100644
--- a/src/events/guildMemberUpdate.ts
+++ b/src/events/guildMemberUpdate.ts
@@ -1,11 +1,76 @@
 import { AuditLogEvent, GuildAuditLogsEntry, GuildMember } from "discord.js";
 import type { NucleusClient } from "../utils/client.js";
+import type { LoggerOptions } from "../utils/log.js";
+import { generalException } from "../utils/createTemporaryStorage.js";
 
 export const event = "guildMemberUpdate";
 
 export async function callback(client: NucleusClient, before: GuildMember, after: GuildMember) {
     const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger;
-    const auditLog = (await getAuditLog(after.guild, AuditLogEvent.EmojiCreate))
+    if(before.guild.id === "684492926528651336") {
+        await client.database.premium.checkAllPremium(after)
+    }
+
+    if(!before.roles.cache.equals(after.roles.cache)) {
+        const auditLog = (await getAuditLog(after.guild, AuditLogEvent.MemberRoleUpdate))
+            .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === after.id)[0];
+        if (!auditLog) return;
+        if (client.noLog.includes(`${after.guild.id}${after.id}${auditLog.id}`)) return;
+        generalException(`${after.guild.id}${after.id}${auditLog.id}`);
+        if (auditLog.executor!.id !== client.user!.id) {
+            const rolesAdded = after.roles.cache.filter(role => !before.roles.cache.has(role.id));
+            const rolesRemoved = before.roles.cache.filter(role => !after.roles.cache.has(role.id));
+            let displayName = "Roles Removed";
+            let color = NucleusColors.red;
+            let emoji = "GUILD.ROLES.DELETE";
+            if(rolesAdded.size > 0 && rolesRemoved.size > 0) {displayName = "Roles Changed"; color = NucleusColors.yellow; emoji = "GUILD.ROLES.EDIT";}
+            else if(rolesAdded.size > 0) {displayName = "Roles Added"; color = NucleusColors.green; emoji = "GUILD.ROLES.CREATE";}
+            const removedEntry = rolesRemoved.map(role => role.id);
+            const addedEntry = rolesAdded.map(role => role.id);
+
+            let list = {
+                memberId: entry(after.id, `\`${after.id}\``),
+                name: entry(after.user.id, renderUser(after.user)),
+            };
+
+            if (rolesAdded.size > 0) {
+                list = Object.assign(list, {rolesAdded: entry(addedEntry, addedEntry.map(id => `<@&${id}>`).join(", "))});
+            }
+            if (rolesRemoved.size > 0) {
+                list = Object.assign(list, {rolesRemoved: entry(removedEntry, removedEntry.map(id => `<@&${id}>`).join(", "))});
+            }
+
+            list = Object.assign(list, {
+                changed: entry(Date.now(), renderDelta(Date.now())),
+                changedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!))
+            });
+
+            let data: LoggerOptions = {
+                meta: {
+                    type: "memberUpdate",
+                    displayName: displayName,
+                    calculateType: "guildMemberUpdate",
+                    color: color,
+                    emoji: emoji,
+                    timestamp: Date.now()
+                },
+                list: {},
+                hidden: {
+                    guild: after.guild.id
+                }
+            };
+
+            if(rolesAdded.size > 0) {
+                list = Object.assign(list, {rolesAdded: entry(addedEntry, addedEntry.map(id => `<@&${id}>`).join(", "))});
+            }
+            if(rolesRemoved.size > 0) {
+                list = Object.assign(list, {rolesRemoved: entry(removedEntry, removedEntry.map(id => `<@&${id}>`).join(", "))});
+            }
+            data = Object.assign(data, {list: list});
+            log(data);
+        }
+    }
+    const auditLog = (await getAuditLog(after.guild, AuditLogEvent.MemberUpdate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === after.id)[0];
     if (!auditLog) return;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -26,14 +91,14 @@
                 calculateType: "guildMemberUpdate",
                 color: NucleusColors.yellow,
                 emoji: "PUNISH.NICKNAME.YELLOW",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(after.id, `\`${after.id}\``),
                 name: entry(after.user.id, renderUser(after.user)),
                 before: entry(before.nickname, before.nickname ? before.nickname : "*None*"),
                 after: entry(after.nickname, after.nickname ? after.nickname : "*None*"),
-                changed: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                changed: entry(Date.now(), renderDelta(Date.now())),
                 changedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!))
             },
             hidden: {
@@ -41,9 +106,10 @@
             }
         };
         log(data);
-    } else if (
-        (before.communicationDisabledUntilTimestamp ?? 0) < new Date().getTime() &&
-        (after.communicationDisabledUntil ?? 0) > new Date().getTime() // TODO: test this
+    }
+    if (
+        (before.communicationDisabledUntilTimestamp ?? 0) < Date.now() &&
+        new Date(after.communicationDisabledUntil ?? 0).getTime() > Date.now()
     ) {
         await client.database.history.create(
             "mute",
@@ -62,7 +128,7 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.yellow,
                 emoji: "PUNISH.MUTE.YELLOW",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(after.id, `\`${after.id}\``),
@@ -71,7 +137,7 @@
                     after.communicationDisabledUntilTimestamp,
                     renderDelta(after.communicationDisabledUntilTimestamp!)
                 ),
-                muted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                muted: entry(Date.now(), renderDelta(Date.now())),
                 mutedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
                 reason: entry(auditLog.reason, auditLog.reason ? auditLog.reason : "\n> *No reason provided*")
             },
@@ -85,10 +151,11 @@
             user: after.id,
             expires: after.communicationDisabledUntilTimestamp
         });
-    } else if (
+    }
+    if (
         after.communicationDisabledUntil === null &&
         before.communicationDisabledUntilTimestamp !== null &&
-        new Date().getTime() >= auditLog.createdTimestamp
+        Date.now() >= auditLog.createdTimestamp
     ) {
         await client.database.history.create(
             "unmute",
@@ -107,12 +174,12 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.green,
                 emoji: "PUNISH.MUTE.GREEN",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(after.id, `\`${after.id}\``),
                 name: entry(after.user.id, renderUser(after.user)),
-                unmuted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                unmuted: entry(Date.now(), renderDelta(Date.now())),
                 unmutedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!))
             },
             hidden: {
diff --git a/src/events/guildUpdate.ts b/src/events/guildUpdate.ts
index 8690af2..6b25e48 100644
--- a/src/events/guildUpdate.ts
+++ b/src/events/guildUpdate.ts
@@ -6,7 +6,8 @@
 
 export async function callback(client: NucleusClient, before: Guild, after: Guild) {
     await statsChannelUpdate(client, after.members.me!);
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    if (!await isLogging(after.id, "guildUpdate")) return;
     const auditLog = (await getAuditLog(after, AuditLogEvent.GuildUpdate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as Guild)!.id === after.id)[0]!;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -74,7 +75,7 @@
         );
 
     if (!Object.keys(list).length) return;
-    list["updated"] = entry(new Date().getTime(), renderDelta(new Date().getTime()));
+    list["updated"] = entry(Date.now(), renderDelta(Date.now()));
     list["updatedBy"] = entry(auditLog.executor!.id, renderUser(auditLog.executor!));
     const data = {
         meta: {
@@ -83,7 +84,7 @@
             calculateType: "guildUpdate",
             color: NucleusColors.yellow,
             emoji: "GUILD.YELLOW",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: list,
         hidden: {
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
index a22045b..80c2c1b 100644
--- a/src/events/interactionCreate.ts
+++ b/src/events/interactionCreate.ts
@@ -31,14 +31,14 @@
     await message.fetch();
     if (message.embeds.length === 0) return;
     const embed = message.embeds[0];
-    const newColour = accept ? "Success" : "Danger";
+    const newcolor = accept ? "Success" : "Danger";
     const footer = {text: `Suggestion ${accept ? "accepted" : "denied"} by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL()};
 
     const newEmbed = new EmojiEmbed()
         .setTitle(embed!.title!)
         .setDescription(embed!.description!)
         .setFooter(footer)
-        .setStatus(newColour);
+        .setStatus(newcolor);
 
     await interaction.update({embeds: [newEmbed], components: []});
 }
diff --git a/src/events/inviteCreate.ts b/src/events/inviteCreate.ts
index a267f09..34f66dc 100644
--- a/src/events/inviteCreate.ts
+++ b/src/events/inviteCreate.ts
@@ -7,7 +7,8 @@
 
 export async function callback(client: NucleusClient, invite: Invite) {
     if(!invite.guild) return; // This is a DM invite (not a guild invite
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    if (!await isLogging(invite.guild.id, "guildUpdate")) return;
     const auditLog = (await getAuditLog(invite.guild as Guild, AuditLogEvent.InviteCreate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as Invite)!.code === invite.code)[0]!;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -18,7 +19,7 @@
             calculateType: "guildUpdate",
             color: NucleusColors.green,
             emoji: "INVITE.CREATE",
-            timestamp: invite.createdTimestamp
+            timestamp: invite.createdTimestamp ?? Date.now()
         },
         list: {
             channel: entry(invite.channel!.id, renderChannel(invite.channel as GuildChannel)),
diff --git a/src/events/inviteDelete.ts b/src/events/inviteDelete.ts
index 1ded432..456af90 100644
--- a/src/events/inviteDelete.ts
+++ b/src/events/inviteDelete.ts
@@ -7,7 +7,8 @@
 
 export async function callback(client: NucleusClient, invite: Invite) {
     if(!invite.guild) return; // This is a DM invite (not a guild invite
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    if (!await isLogging(invite.guild.id, "guildUpdate")) return;
     const auditLog = (await getAuditLog(invite.guild as Guild, AuditLogEvent.InviteDelete))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as Invite)!.code === invite.code)[0]!;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -18,14 +19,14 @@
             calculateType: "guildUpdate",
             color: NucleusColors.red,
             emoji: "INVITE.DELETE",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             channel: entry(invite.channel!.id, renderChannel(invite.channel as GuildChannel)),
             link: entry(invite.url, invite.url),
             expires: entry(invite.maxAge, invite.maxAge ? humanizeDuration(invite.maxAge * 1000) : "Never"),
             deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
-            deleted: entry(new Date().getTime(), renderDelta(new Date().getTime()))
+            deleted: entry(Date.now(), renderDelta(Date.now()))
         },
         hidden: {
             guild: invite.guild!.id
diff --git a/src/events/memberJoin.ts b/src/events/memberJoin.ts
index daf195a..77b111f 100644
--- a/src/events/memberJoin.ts
+++ b/src/events/memberJoin.ts
@@ -8,7 +8,8 @@
 export async function callback(client: NucleusClient, member: GuildMember) {
     welcome(client, member);
     statsChannelAdd(client, member);
-    const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    const { log, isLogging, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    if (!await isLogging(member.guild.id, "guildMemberUpdate")) return;
     await client.database.history.create("join", member.guild.id, member.user, null, null);
     const data = {
         meta: {
@@ -17,7 +18,7 @@
             calculateType: "guildMemberUpdate",
             color: NucleusColors.green,
             emoji: "MEMBER" + (member.user.bot ? ".BOT" : "") + ".JOIN",
-            timestamp: member.joinedTimestamp
+            timestamp: member.joinedTimestamp ?? Date.now()
         },
         list: {
             memberId: entry(member.id, `\`${member.id}\``),
diff --git a/src/events/memberLeave.ts b/src/events/memberLeave.ts
index e70fb3c..8b3d1b1 100644
--- a/src/events/memberLeave.ts
+++ b/src/events/memberLeave.ts
@@ -7,22 +7,36 @@
 export const event = "guildMemberRemove";
 
 export async function callback(client: NucleusClient, member: GuildMember) {
+    const startTime = Date.now() - 10 * 1000;
     purgeByUser(member.id, member.guild.id);
     await statsChannelRemove(client, member);
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
-    const auditLog = (await getAuditLog(member.guild as Guild, AuditLogEvent.MemberKick))
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    if (!await isLogging(member.guild.id, "guildMemberUpdate")) return;
+    const kickAuditLog = (await getAuditLog(member.guild as Guild, AuditLogEvent.MemberKick))
+        .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === member.id)[0];
+    const banAuditLog = (await getAuditLog(member.guild as Guild, AuditLogEvent.MemberBanAdd))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === member.id)[0];
     let type = "leave";
-    if (auditLog) {
-        if (auditLog.executor!.id === client.user!.id) return;
-        if (auditLog.createdAt.valueOf() - 100 >= new Date().getTime()) {
+    if (kickAuditLog) {
+        if (kickAuditLog.executor!.id === client.user!.id) return;
+        if (kickAuditLog.createdAt.getTime() >= startTime) {
             type = "kick";
         }
     }
+    if (banAuditLog) {
+        if (banAuditLog.executor!.id === client.user!.id) return;
+        if (banAuditLog.createdAt.getTime() >= startTime) {
+            if (!kickAuditLog) {
+                return
+            } else if (kickAuditLog.createdAt.valueOf() < banAuditLog.createdAt.valueOf()) {
+                return
+            }
+        }
+    }
     let data;
     if (type === "kick") {
-        if (!auditLog) return;
-        await client.database.history.create("kick", member.guild.id, member.user, auditLog.executor, auditLog.reason);
+        if (!kickAuditLog) return;
+        await client.database.history.create("kick", member.guild.id, member.user, kickAuditLog.executor, kickAuditLog.reason);
         data = {
             meta: {
                 type: "memberKick",
@@ -30,15 +44,15 @@
                 calculateType: "guildMemberPunish",
                 color: NucleusColors.red,
                 emoji: "PUNISH.KICK.RED",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(member.id, `\`${member.id}\``),
                 name: entry(member.id, renderUser(member.user)),
                 joined: entry(member.joinedTimestamp, renderDelta(member.joinedTimestamp?.valueOf()!)),
-                kicked: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-                kickedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
-                reason: entry(auditLog.reason, auditLog.reason ? `\n> ${auditLog.reason}` : "*No reason provided.*"),
+                kicked: entry(Date.now(), renderDelta(Date.now())),
+                kickedBy: entry(kickAuditLog.executor!.id, renderUser(kickAuditLog.executor!)),
+                reason: entry(kickAuditLog.reason, kickAuditLog.reason ? `\n> ${kickAuditLog.reason}` : "*No reason provided.*"),
                 accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)),
                 serverMemberCount: member.guild.memberCount
             },
@@ -55,13 +69,13 @@
                 calculateType: "guildMemberUpdate",
                 color: NucleusColors.red,
                 emoji: "MEMBER." + (member.user.bot ? "BOT." : "") + "LEAVE",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: {
                 memberId: entry(member.id, `\`${member.id}\``),
                 name: entry(member.id, renderUser(member.user)),
                 joined: entry(member.joinedTimestamp, renderDelta(member.joinedTimestamp?.valueOf()!)),
-                left: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                left: entry(Date.now(), renderDelta(Date.now())),
                 accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)),
                 serverMemberCount: member.guild.memberCount
             },
diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts
index 69bc542..4f525fc 100644
--- a/src/events/messageCreate.ts
+++ b/src/events/messageCreate.ts
@@ -19,12 +19,23 @@
         console.log(e);
     }
 
-    const { log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
 
     const fileNames = await logAttachment(message);
 
     const content = message.content.toLowerCase() || "";
     const config = await client.memory.readGuildInfo(message.guild.id);
+    if(config.filters.clean.channels.includes(message.channel.id)) {
+        const memberRoles = message.member!.roles.cache.map(role => role.id);
+        const roleAllow = config.filters.clean.allowed.roles.some(role => memberRoles.includes(role));
+        const userAllow = config.filters.clean.allowed.users.includes(message.author.id);
+        if(!roleAllow && !userAllow) return await message.delete();
+    }
+
+    if (config.autoPublish.enabled && config.autoPublish.channels.includes(message.channel.id)) {
+        await message.crosspost();
+    }
+
     const filter = getEmojiByName("ICONS.FILTER");
     let attachmentJump = "";
     if (config.logging.attachments.saved[message.channel.id + message.id]) {
@@ -34,7 +45,7 @@
         messageId: entry(message.id, `\`${message.id}\``),
         sentBy: entry(message.author.id, renderUser(message.author)),
         sentIn: entry(message.channel.id, renderChannel(message.channel)),
-        deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+        deleted: entry(Date.now(), renderDelta(Date.now())),
         mentions: message.mentions.users.size,
         attachments: entry(message.attachments.size, message.attachments.size + attachmentJump),
         repliedTo: entry(
@@ -57,7 +68,7 @@
                         calculateType: "autoModeratorDeleted",
                         color: NucleusColors.red,
                         emoji: "MESSAGE.DELETE",
-                        timestamp: new Date().getTime()
+                        timestamp: Date.now()
                     },
                     separate: {
                         start:
@@ -78,7 +89,8 @@
     if (fileNames.files.length > 0) {
         for (const element of fileNames.files) {
             const url = element.url ? element.url : element.local;
-            if (/\.(jpg|jpeg|png|gif|gifv|webm|webp|mp4|wav|mp3|ogg)$/.test(url)) {
+            if (/\.(j(pe?g|fif)|a?png|gifv?|w(eb[mp]|av)|mp([34]|eg-\d)|ogg|avi|h\.26(4|5)|cda)$/.test(url.toLowerCase())) {
+                // jpg|jpeg|png|apng|gif|gifv|webm|webp|mp4|wav|mp3|ogg|jfif|MPEG-#|avi|h.264|h.265
                 if (
                     config.filters.images.NSFW &&
                     !(message.channel instanceof ThreadChannel ? message.channel.parent?.nsfw : message.channel.nsfw)
@@ -93,7 +105,7 @@
                                 calculateType: "autoModeratorDeleted",
                                 color: NucleusColors.red,
                                 emoji: "MESSAGE.DELETE",
-                                timestamp: new Date().getTime()
+                                timestamp: Date.now()
                             },
                             separate: {
                                 start:
@@ -128,7 +140,7 @@
                                 calculateType: "autoModeratorDeleted",
                                 color: NucleusColors.red,
                                 emoji: "MESSAGE.DELETE",
-                                timestamp: new Date().getTime()
+                                timestamp: Date.now()
                             },
                             separate: {
                                 start:
@@ -158,7 +170,7 @@
                                     calculateType: "autoModeratorDeleted",
                                     color: NucleusColors.red,
                                     emoji: "MESSAGE.DELETE",
-                                    timestamp: new Date().getTime()
+                                    timestamp: Date.now()
                                 },
                                 separate: {
                                     start:
@@ -189,7 +201,7 @@
                             calculateType: "autoModeratorDeleted",
                             color: NucleusColors.red,
                             emoji: "MESSAGE.DELETE",
-                            timestamp: new Date().getTime()
+                            timestamp: Date.now()
                         },
                         separate: {
                             start:
@@ -221,7 +233,7 @@
                 calculateType: "autoModeratorDeleted",
                 color: NucleusColors.red,
                 emoji: "MESSAGE.DELETE",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             separate: {
                 start:
@@ -253,7 +265,7 @@
                     calculateType: "autoModeratorDeleted",
                     color: NucleusColors.red,
                     emoji: "MESSAGE.DELETE",
-                    timestamp: new Date().getTime()
+                    timestamp: Date.now()
                 },
                 separate: {
                     start:
@@ -271,6 +283,7 @@
     }
 
     if (config.filters.pings.everyone && message.mentions.everyone) {
+        if(!await isLogging(message.guild.id, "messageMassPing")) return;
         const data = {
             meta: {
                 type: "everyonePing",
@@ -278,7 +291,7 @@
                 calculateType: "messageMassPing",
                 color: NucleusColors.yellow,
                 emoji: "MESSAGE.PING.EVERYONE",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             separate: {
                 start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*"
@@ -295,6 +308,7 @@
             if (!config.filters.pings.allowed.roles.includes(roleId)) {
                 messageException(message.guild.id, message.channel.id, message.id);
                 await message.delete();
+                if(!await isLogging(message.guild.id, "messageMassPing")) return;
                 const data = {
                     meta: {
                         type: "rolePing",
@@ -302,7 +316,7 @@
                         calculateType: "messageMassPing",
                         color: NucleusColors.yellow,
                         emoji: "MESSAGE.PING.ROLE",
-                        timestamp: new Date().getTime()
+                        timestamp: Date.now()
                     },
                     separate: {
                         start: content
@@ -321,6 +335,7 @@
     if (message.mentions.users.size >= config.filters.pings.mass && config.filters.pings.mass) {
         messageException(message.guild.id, message.channel.id, message.id);
         await message.delete();
+        if(!await isLogging(message.guild.id, "messageMassPing")) return;
         const data = {
             meta: {
                 type: "massPing",
@@ -328,7 +343,7 @@
                 calculateType: "messageMassPing",
                 color: NucleusColors.yellow,
                 emoji: "MESSAGE.PING.MASS",
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             separate: {
                 start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*"
diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts
index f8433fc..aac83f4 100644
--- a/src/events/messageDelete.ts
+++ b/src/events/messageDelete.ts
@@ -4,61 +4,59 @@
 export const event = "messageDelete";
 
 export async function callback(client: NucleusClient, message: Message) {
-    try {
-        if (message.author.id === client.user!.id) return;
-        if (client.noLog.includes(`${message.id}/${message.channel.id}/${message.id}`)) return;
-        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
-        const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd))
-            .filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0];
-        if (auditLog) {
-            if (auditLog.createdTimestamp - 1000 < new Date().getTime()) return;
-        }
-        const replyTo = message.reference;
-        let content = message.cleanContent;
-        content.replace("`", "\\`");
-        if (content.length > 256) content = content.substring(0, 253) + "...";
-        const attachments =
-            message.attachments.size + (
-                message.content.match(
-                    /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi
-                ) ?? []
-            ).length;
-        let attachmentJump = "";
-        const config = (await client.database.guilds.read(message.guild!.id)).logging.attachments.saved[
-            message.channel.id + message.id
-        ];
-        if (config) { attachmentJump = ` [[View attachments]](${config})`; }
-        const data = {
-            meta: {
-                type: "messageDelete",
-                displayName: "Message Deleted",
-                calculateType: "messageDelete",
-                color: NucleusColors.red,
-                emoji: "MESSAGE.DELETE",
-                timestamp: new Date().getTime()
-            },
-            separate: {
-                start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*"
-            },
-            list: {
-                messageId: entry(message.id, `\`${message.id}\``),
-                sentBy: entry(message.author.id, renderUser(message.author)),
-                sentIn: entry(message.channel.id, renderChannel(message.channel as Discord.GuildChannel | Discord.ThreadChannel)),
-                deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-                mentions: message.mentions.users.size,
-                attachments: entry(attachments, attachments + attachmentJump),
-                repliedTo: entry(
-                    replyTo ? replyTo.messageId! : null,
-                    replyTo ? `[[Jump to message]](https://discord.com/channels/${message.guild!.id}/${message.channel.id}/${replyTo.messageId})`
-                            : "None"
-                )
-            },
-            hidden: {
-                guild: message.guild!.id
-            }
-        };
-        log(data);
-    } catch (e) {
-        console.log(e);
+    if (message.author.id === client.user!.id) return;
+    if (message.author.bot) return;
+    if (client.noLog.includes(`${message.guild!.id}/${message.channel.id}/${message.id}`)) return;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    if (!await isLogging(message.guild!.id, "messageDelete")) return;
+    const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd))
+        .filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0];
+    if (auditLog) {
+        if (auditLog.createdTimestamp - 1000 < Date.now()) return;
     }
+    const replyTo = message.reference;
+    let content = message.cleanContent;
+    content.replace("`", "\\`");
+    if (content.length > 256) content = content.substring(0, 253) + "...";
+    const attachments =
+        message.attachments.size + (
+            message.content.match(
+                /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi
+            ) ?? []
+        ).length;
+    let attachmentJump = "";
+    const config = (await client.database.guilds.read(message.guild!.id)).logging.attachments.saved[
+        message.channel.id + message.id
+    ];
+    if (config) { attachmentJump = ` [[View attachments]](${config})`; }
+    const data = {
+        meta: {
+            type: "messageDelete",
+            displayName: "Message Deleted",
+            calculateType: "messageDelete",
+            color: NucleusColors.red,
+            emoji: "MESSAGE.DELETE",
+            timestamp: Date.now()
+        },
+        separate: {
+            start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*"
+        },
+        list: {
+            messageId: entry(message.id, `\`${message.id}\``),
+            sentBy: entry(message.author.id, renderUser(message.author)),
+            sentIn: entry(message.channel.id, renderChannel(message.channel as Discord.GuildChannel | Discord.ThreadChannel)),
+            deleted: entry(Date.now(), renderDelta(Date.now())),
+            mentions: message.mentions.users.size,
+            attachments: entry(attachments, attachments + attachmentJump),
+            repliedTo: entry(
+                replyTo ? replyTo.messageId! : null,
+                replyTo ? `[[Jump to message]](https://discord.com/channels/${message.guild!.id}/${message.channel.id}/${replyTo.messageId})`
+                        : "None"
+            )
+        },
+        hidden: {
+            guild: message.guild!.id
+        }
+    };
+    log(data);
 }
diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts
index 20624fe..f5a28a4 100644
--- a/src/events/messageEdit.ts
+++ b/src/events/messageEdit.ts
@@ -6,8 +6,10 @@
 
 export async function callback(client: NucleusClient, oldMessage: Message, newMessage: Message) {
     if (newMessage.author.id === client.user!.id) return;
+    if (newMessage.author.bot) return;
     if (!newMessage.guild) return;
-    const { log, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = client.logger;
+    const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = client.logger;
+    
     const replyTo: MessageReference | null = newMessage.reference;
     let newContent = newMessage.cleanContent.replaceAll("`", "‘");
     let oldContent = oldMessage.cleanContent.replaceAll("`", "‘");
@@ -18,7 +20,8 @@
     if (config) {
         attachmentJump = ` [[View attachments]](${config})`;
     }
-    if (newContent === oldContent && newMessage.attachments.size === oldMessage.attachments.size) {
+    if (newMessage.crosspostable !== oldMessage.crosspostable) {
+        if(!await isLogging(newMessage.guild.id, "messageAnnounce")) return;
         if (!replyTo) {
             const data = {
                 meta: {
@@ -27,7 +30,7 @@
                     calculateType: "messageAnnounce",
                     color: NucleusColors.yellow,
                     emoji: "MESSAGE.CREATE",
-                    timestamp: newMessage.editedTimestamp
+                    timestamp: newMessage.editedTimestamp ?? Date.now()
                 },
                 separate: {
                     end: `[[Jump to message]](${newMessage.url})`
@@ -57,6 +60,7 @@
             return log(data);
         }
     }
+    if (!await isLogging(newMessage.guild.id, "messageUpdate")) return;
     if (!newMessage.editedTimestamp) {
         return;
     }
diff --git a/src/events/roleCreate.ts b/src/events/roleCreate.ts
index d253ce7..be385f0 100644
--- a/src/events/roleCreate.ts
+++ b/src/events/roleCreate.ts
@@ -4,7 +4,8 @@
 export const event = "roleCreate";
 
 export async function callback(client: NucleusClient, role: Role) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderRole } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderRole } = client.logger;
+    if (!await isLogging(role.guild.id, "guildRoleUpdate")) return;
     if (role.managed) return;
     const auditLog = (await getAuditLog(role.guild as Guild, AuditLogEvent.RoleCreate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === role.id)[0]!;
diff --git a/src/events/roleDelete.ts b/src/events/roleDelete.ts
index f41241b..b207f4f 100644
--- a/src/events/roleDelete.ts
+++ b/src/events/roleDelete.ts
@@ -5,7 +5,8 @@
 export const event = "roleDelete";
 
 export async function callback(client: NucleusClient, role: Role) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    if (!await isLogging(role.guild.id, "guildRoleUpdate")) return;
     if (role.managed) return;
     const auditLog = (await getAuditLog(role.guild as Guild, AuditLogEvent.RoleDelete))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === role.id)[0]!;
@@ -34,7 +35,7 @@
             members: entry(role.members.size, `${role.members.size}`),
             deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
             created: entry(role.createdTimestamp, renderDelta(role.createdTimestamp)),
-            deleted: entry(new Date().getTime(), renderDelta(new Date().getTime()))
+            deleted: entry(Date.now(), renderDelta(Date.now()))
         },
         hidden: {
             guild: role.guild.id
diff --git a/src/events/roleUpdate.ts b/src/events/roleUpdate.ts
index 924ec3e..8d9ef10 100644
--- a/src/events/roleUpdate.ts
+++ b/src/events/roleUpdate.ts
@@ -5,16 +5,17 @@
 export const event = "roleUpdate";
 
 export async function callback(client: NucleusClient, oldRole: Role, newRole: Role) {
-    const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderRole } = client.logger;
-
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderDelta, renderUser, renderRole } = client.logger;
+    if (!await isLogging(newRole.guild.id, "guildRoleUpdate")) return;
     const auditLog = (await getAuditLog(newRole.guild as Guild, AuditLogEvent.RoleUpdate))
-        .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === newRole.id)[0]!;
+        .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === newRole.id)[0];
+    if (!auditLog) return;
     if (auditLog.executor!.id === client.user!.id) return;
 
     const changes: Record<string, ReturnType<typeof entry>> = {
         roleId: entry(newRole.id, `\`${newRole.id}\``),
         role: entry(newRole.id, renderRole(newRole)),
-        edited: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+        edited: entry(Date.now(), renderDelta(Date.now())),
         editedBy: entry(auditLog.executor!.id, renderUser((await newRole.guild.members.fetch(auditLog.executor!.id)).user))
     };
     const mentionable = ["", ""];
@@ -31,6 +32,12 @@
         changes["mentionable"] = entry([oldRole.mentionable, newRole.mentionable], `${mentionable[0]} -> ${mentionable[1]}`);
     if (oldRole.hexColor !== newRole.hexColor)
         changes["color"] = entry([oldRole.hexColor, newRole.hexColor], `\`${oldRole.hexColor}\` -> \`${newRole.hexColor}\``);
+    if (oldRole.permissions.bitfield !== newRole.permissions.bitfield) {
+        changes["permissions"] = entry(
+            [oldRole.permissions.bitfield.toString(), newRole.permissions.bitfield.toString()],
+            `[[Old]](https://discordapi.com/permissions.html#${oldRole.permissions.bitfield.toString()}) -> [[New]](https://discordapi.com/permissions.html#${newRole.permissions.bitfield.toString()})`
+        );
+    }
 
     if (Object.keys(changes).length === 4) return;
 
@@ -47,6 +54,6 @@
         hidden: {
             guild: newRole.guild.id
         }
-    }; // TODO: show perms changed (webpage)
+    }; // TODO: make our own page for this
     log(data);
 }
diff --git a/src/events/stickerCreate.ts b/src/events/stickerCreate.ts
index b341ae9..5d2e443 100644
--- a/src/events/stickerCreate.ts
+++ b/src/events/stickerCreate.ts
@@ -1,13 +1,17 @@
 import type { NucleusClient } from "../utils/client.js";
 import { AuditLogEvent, GuildAuditLogsEntry, Sticker } from "discord.js";
+import { generalException } from "../utils/createTemporaryStorage.js";
 
-export const event = "stickerDelete";
+export const event = "stickerCreate";
 
 export async function callback(client: NucleusClient, sticker: Sticker) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
-    const auditLog = (await getAuditLog(sticker.guild!, AuditLogEvent.EmojiCreate))
-        .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === sticker.id)[0]!;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    if (!await isLogging(sticker.guild!.id, "stickerUpdate")) return;
+    const auditLog = (await getAuditLog(sticker.guild!, AuditLogEvent.StickerCreate))
+    .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === sticker.id)[0]!;
     if (auditLog.executor!.id === client.user!.id) return;
+    if (client.noLog.includes(`${sticker.guild!.id}${auditLog.id}`)) return;
+    generalException(`${sticker.guild!.id}${auditLog.id}`);
     const data = {
         meta: {
             type: "stickerCreate",
diff --git a/src/events/stickerDelete.ts b/src/events/stickerDelete.ts
index ce26a85..d123f44 100644
--- a/src/events/stickerDelete.ts
+++ b/src/events/stickerDelete.ts
@@ -4,7 +4,8 @@
 export const event = "stickerDelete";
 
 export async function callback(client: NucleusClient, sticker: Sticker) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
+    if (!await isLogging(sticker.guild!.id, "stickerUpdate")) return;
     const auditLog = (await getAuditLog(sticker.guild!, AuditLogEvent.StickerDelete))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === sticker.id)[0]!;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -14,7 +15,7 @@
             displayName: "Sticker Deleted",
             calculateType: "stickerUpdate",
             color: NucleusColors.red,
-            sticker: "GUILD.sticker.DELETE",
+            emoji: "GUILD.EMOJI.DELETE",
             timestamp: auditLog.createdTimestamp
         },
         list: {
diff --git a/src/events/stickerUpdate.ts b/src/events/stickerUpdate.ts
index ed01b71..aef28a4 100644
--- a/src/events/stickerUpdate.ts
+++ b/src/events/stickerUpdate.ts
@@ -4,8 +4,8 @@
 export const event = "stickerUpdate";
 
 export async function callback(client: NucleusClient, oldSticker: Sticker, newSticker: Sticker) {
-    const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser } = client.logger;
-
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderDelta, renderUser } = client.logger;
+    if (!await isLogging(newSticker.guild!.id, "stickerUpdate")) return;
     if (oldSticker.name === newSticker.name) return;
     const auditLog = (await getAuditLog(newSticker.guild!, AuditLogEvent.StickerUpdate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === newSticker.id)[0]!;
diff --git a/src/events/threadCreate.ts b/src/events/threadCreate.ts
index 6d3225c..f56e1bb 100644
--- a/src/events/threadCreate.ts
+++ b/src/events/threadCreate.ts
@@ -5,7 +5,8 @@
 export const event = "threadCreate";
 
 export async function callback(client: NucleusClient, thread: ThreadChannel) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    if (!await isLogging(thread.guild.id, "channelUpdate")) return;
     const auditLog = (await getAuditLog(thread.guild, AuditLogEvent.ThreadCreate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as ThreadChannel)!.id === thread.id)[0]!;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -22,7 +23,7 @@
             calculateType: "channelUpdate",
             color: NucleusColors.green,
             emoji: "CHANNEL.TEXT.CREATE",
-            timestamp: thread.createdTimestamp
+            timestamp: thread.createdTimestamp ?? Date.now()
         },
         list: {
             threadId: entry(thread.id, `\`${thread.id}\``),
diff --git a/src/events/threadDelete.ts b/src/events/threadDelete.ts
index 429f63a..bfac75e 100644
--- a/src/events/threadDelete.ts
+++ b/src/events/threadDelete.ts
@@ -5,7 +5,8 @@
 export const event = "threadDelete";
 
 export async function callback(client: NucleusClient, thread: ThreadChannel) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    if (!await isLogging(thread.guild.id, "channelUpdate")) return;
     const auditLog = (await getAuditLog(thread.guild, AuditLogEvent.ThreadDelete))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as ThreadChannel)!.id === thread.id)[0]!;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -22,7 +23,7 @@
             calculateType: "channelUpdate",
             color: NucleusColors.red,
             emoji: "CHANNEL.TEXT.DELETE",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
             threadId: entry(thread.id, `\`${thread.id}\``),
@@ -38,7 +39,7 @@
             membersInThread: entry(thread.memberCount, thread.memberCount!.toString()),
             deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)),
             created: entry(thread.createdTimestamp, renderDelta(thread.createdTimestamp!)),
-            deleted: entry(new Date().getTime(), renderDelta(new Date().getTime()))
+            deleted: entry(Date.now(), renderDelta(Date.now()))
         },
         hidden: {
             guild: thread.guild.id
diff --git a/src/events/threadUpdate.ts b/src/events/threadUpdate.ts
index 555b17f..af792bc 100644
--- a/src/events/threadUpdate.ts
+++ b/src/events/threadUpdate.ts
@@ -6,7 +6,8 @@
 export const event = "threadUpdate";
 
 export async function callback(client: NucleusClient, oldThread: ThreadChannel, newThread: ThreadChannel) {
-    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
+    if (!await isLogging(newThread.guild.id, "channelUpdate")) return;
     const auditLog = (await getAuditLog(newThread.guild, AuditLogEvent.ThreadUpdate))
         .filter((entry: GuildAuditLogsEntry) => (entry.target as ThreadChannel)!.id === newThread.id)[0]!;
     if (auditLog.executor!.id === client.user!.id) return;
@@ -37,7 +38,7 @@
         );
     }
     if (!(Object.keys(list).length - 3)) return;
-    list["updated"] = entry(new Date().getTime(), renderDelta(new Date().getTime()));
+    list["updated"] = entry(Date.now(), renderDelta(Date.now()));
     list["updatedBy"] = entry(auditLog.executor!.id, renderUser(auditLog.executor!));
     const data = {
         meta: {
@@ -46,7 +47,7 @@
             calculateType: "channelUpdate",
             color: NucleusColors.yellow,
             emoji: "CHANNEL.TEXT.EDIT",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: list,
         hidden: {
diff --git a/src/events/webhookUpdate.ts b/src/events/webhookUpdate.ts
index e5f07dd..004f754 100644
--- a/src/events/webhookUpdate.ts
+++ b/src/events/webhookUpdate.ts
@@ -10,18 +10,18 @@
 
 export async function callback(client: NucleusClient, channel: Discord.GuildChannel) {
     try {
-        const { getAuditLog, log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger;
+        const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger;
+        if (!await isLogging(channel.guild.id, "webhookUpdate")) return;
         const auditCreate = (await getAuditLog(channel.guild, AuditLogEvent.WebhookCreate))
-            .filter((entry: GuildAuditLogsEntry) => (entry.target as Webhook)!.id === channel.id)[0]!;
-        const auditDelete = (await getAuditLog(channel.guild, AuditLogEvent.WebhookDelete))
-            .filter((entry: GuildAuditLogsEntry) => (entry.target as Webhook)!.id === channel.id)[0];
-        const auditUpdate = (await getAuditLog(channel.guild, AuditLogEvent.WebhookUpdate))
-            .filter((entry: GuildAuditLogsEntry) => (entry.target as Webhook)!.id === channel.id)[0];
-
-        if (!auditUpdate && !auditDelete) return;
+            .filter((entry: GuildAuditLogsEntry | null) => (entry?.target) ? (entry.target as Webhook)!.channelId === channel.id : false)[0];
+        const auditDelete = (await getAuditLog(channel.guild, AuditLogEvent.WebhookDelete, 0))
+            .filter((entry: GuildAuditLogsEntry | null) => (entry?.target) ? (entry.target as Webhook)!.channelId === channel.id : false)[0];
+        const auditUpdate = (await getAuditLog(channel.guild, AuditLogEvent.WebhookUpdate, 0))
+            .filter((entry: GuildAuditLogsEntry | null) => (entry?.target) ? (entry.target as Webhook)!.channelId === channel.id : false)[0];
+        if (!auditCreate && !auditUpdate && !auditDelete) return;
         let action: "Create" | "Update" | "Delete" = "Create";
         let list: Record<string, ReturnType<typeof entry> | string> = {};
-        const createTimestamp = auditCreate.createdTimestamp;
+        const createTimestamp = auditCreate ? auditCreate.createdTimestamp : 0;
         const deleteTimestamp = auditDelete ? auditDelete.createdTimestamp : 0;
         const updateTimestamp = auditUpdate ? auditUpdate.createdTimestamp : 0;
         if (updateTimestamp > createTimestamp && updateTimestamp > deleteTimestamp && auditUpdate) {
@@ -46,7 +46,7 @@
                 (auditUpdate.target! as Extract<GuildAuditLogsEntry, {createdTimestamp: number}>).createdTimestamp,
                 renderDelta((auditUpdate.target! as Extract<GuildAuditLogsEntry, {createdTimestamp: number}>).createdTimestamp)
             );
-            list["edited"] = entry(after["editedTimestamp"]!, renderDelta(new Date().getTime()));
+            list["edited"] = entry(after["editedTimestamp"]!, renderDelta(Date.now()));
             list["editedBy"] = entry(auditUpdate.executor!.id, renderUser(auditUpdate.executor!));
             action = "Update";
         } else if (deleteTimestamp > createTimestamp && deleteTimestamp > updateTimestamp && auditDelete) {
@@ -61,7 +61,7 @@
                 name: entry(before["name"]!, `${before["name"]}`),
                 channel: entry(before["channel_id"]!, renderChannel((await client.channels.fetch(before["channel_id"]!)) as GuildChannel)),
                 created: entry((auditDelete.target! as Extract<GuildAuditLogsEntry, {createdTimestamp: number}>).createdTimestamp, renderDelta((auditDelete.target! as Extract<GuildAuditLogsEntry, {createdTimestamp: number}>).createdTimestamp)),
-                deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                deleted: entry(Date.now(), renderDelta(Date.now())),
                 deletedBy: entry(
                     auditDelete.executor!.id,
                     renderUser((await channel.guild.members.fetch(auditDelete.executor!.id)).user)
@@ -80,10 +80,10 @@
                 name: entry(before["name"]!, `${before["name"]}`),
                 channel: entry(before["channel_id"]!, renderChannel(await client.channels.fetch(before["channel_id"]!) as GuildChannel)),
                 createdBy: entry(
-                    auditCreate.executor!.id,
-                    renderUser((await channel.guild.members.fetch(auditCreate.executor!.id)).user)
+                    auditCreate!.executor!.id,
+                    renderUser((await channel.guild.members.fetch(auditCreate!.executor!.id)).user)
                 ),
-                created: entry(new Date().getTime(), renderDelta(new Date().getTime()))
+                created: entry(Date.now(), renderDelta(Date.now()))
             };
         }
         const cols = {
@@ -98,7 +98,7 @@
                 calculateType: "webhookUpdate",
                 color: NucleusColors[cols[action] as keyof typeof NucleusColors],
                 emoji: "WEBHOOK." + action.toUpperCase(),
-                timestamp: new Date().getTime()
+                timestamp: Date.now()
             },
             list: list,
             hidden: {
diff --git a/src/index.ts b/src/index.ts
index 362b805..12f6659 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,17 +1,24 @@
 import runServer from "./api/index.js";
 import client from "./utils/client.js";
-import config from "./config/main.json" assert { type: "json" };
+import config from "./config/main.js";
 import register from "./utils/commandRegistration/register.js";
 import { record as recordPerformance } from "./utils/performanceTesting/record.js";
 
-client.on("ready", () => {
+client.on("ready", async () => {
     console.log(`Logged in as ${client.user!.tag}!`);
     register();
     runServer(client);
+    if (config.enableDevelopment) {
+        client.fetchedCommands = await client.guilds.cache.get(config.developmentGuildID)?.commands.fetch()!;
+    } else {
+        client.fetchedCommands = await client.application?.commands.fetch()!;
+    }
+    await client.database.premium.checkAllPremium();
 });
+
 process.on("unhandledRejection", (err) => { console.error(err) });
 process.on("uncaughtException", (err) => { console.error(err) });
 
 await client.login(config.enableDevelopment ? config.developmentToken : config.token)
 
-await recordPerformance();
\ No newline at end of file
+await recordPerformance();
diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts
index 156503a..b2c8391 100644
--- a/src/premium/attachmentLogs.ts
+++ b/src/premium/attachmentLogs.ts
@@ -1,4 +1,4 @@
-import { getCommandMentionByName } from './../utils/getCommandMentionByName.js';
+import { getCommandMentionByName } from './../utils/getCommandDataByName.js';
 import client from "../utils/client.js";
 import keyValueList from "../utils/generateKeyValueList.js";
 import singleNotify from "../utils/singleNotify.js";
@@ -8,12 +8,13 @@
 import type { GuildTextBasedChannel, Message } from "discord.js";
 
 export default async function logAttachment(message: Message): Promise<AttachmentLogSchema> {
+    if (message.guild) client.database.premium.hasPremium(message.guild.id).finally(() => {});
     if (!message.guild) throw new Error("Tried to log an attachment in a non-guild message");
     const { renderUser, renderChannel, renderDelta } = client.logger;
     const attachments = [];
     for (const attachment of message.attachments.values()) {
         attachments.push({
-            local: await saveAttachment(attachment.url),
+            local: (await saveAttachment(attachment.url))[0],
             url: attachment.url,
             height: attachment.height,
             width: attachment.width,
@@ -24,7 +25,7 @@
     for (const link of links) {
         if (link.toLowerCase().match(/\.(jpg|jpeg|png|gif|gifv|webm|webp|mp4|wav|mp3|ogg)$/gi)) {
             attachments.push({
-                local: await saveAttachment(link),
+                local: (await saveAttachment(link))[0],
                 url: link,
                 height: null,
                 width: null
@@ -38,17 +39,17 @@
             singleNotify(
                 "noAttachmentLogChannel",
                 message.guild.id,
-                `No channel set for attachment logging. You can set one with ${await getCommandMentionByName("settings/logs/attachments")}`,
+                `No channel set for attachment logging. You can set one with ${getCommandMentionByName("settings/logs/attachments")}`,
                 "Warning"
             );
             return { files: attachments };
         }
-        const channelObj = await client.channels.fetch(channel);
+        const channelObj = await message.guild.channels.fetch(channel);
         if (!channelObj) {
             singleNotify(
                 "attachmentLogChannelDeleted",
                 message.guild.id,
-                `Your attachment history channel was deleted or is not longer accessible. You can set a new one with ${await getCommandMentionByName("settings/logs/attachments")}`,
+                `Your attachment history channel was deleted or is not longer accessible. You can set a new one with ${getCommandMentionByName("settings/logs/attachments")}`,
                 "Warning"
             );
             return { files: attachments };
@@ -70,7 +71,6 @@
             ],
             files: attachments.map((file) => file.local)
         });
-        // await client.database.guilds.write(interaction.guild.id, {[`tags.${name}`]: value});
         client.database.guilds.write(message.guild.id, {
             [`logging.attachments.saved.${message.channel.id}${message.id}`]: m.url
         });
diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts
index 04fdc08..67aed04 100644
--- a/src/premium/createTranscript.ts
+++ b/src/premium/createTranscript.ts
@@ -7,18 +7,23 @@
     MessageComponentInteraction,
     TextChannel,
     ButtonStyle,
-    User
+    User,
+    ThreadChannel
 } from "discord.js";
 import EmojiEmbed from "../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../utils/getEmojiByName.js";
-import { PasteClient, Publicity, ExpireDate } from "pastebin-api";
 import client from "../utils/client.js";
+import { messageException } from '../utils/createTemporaryStorage.js';
 
-const pbClient = new PasteClient(client.config.pastebinApiKey);
+const noTopic = new EmojiEmbed()
+    .setTitle("User not found")
+    .setDescription("There is no user associated with this ticket.")
+    .setStatus("Danger")
+    .setEmoji("CONTROL.BLOCKCROSS")
 
 export default async function (interaction: CommandInteraction | MessageComponentInteraction) {
     if (interaction.channel === null) return;
-    if (!(interaction.channel instanceof TextChannel)) return;
+    if (!(interaction.channel instanceof TextChannel || interaction.channel instanceof ThreadChannel)) return;
     const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
 
     let messages: Message[] = [];
@@ -29,95 +34,75 @@
         const deleted = await (interaction.channel as TextChannel).bulkDelete(fetched, true);
         deletedCount = deleted.size;
         messages = messages.concat(Array.from(deleted.values() as Iterable<Message>));
+        if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id)
     } while (deletedCount === 100);
+    messages = messages.filter(message => !(
+        message.components.some(
+            component => component.components.some(
+                child => child.customId?.includes("transcript") ?? false
+            )
+        )
+    ));
 
-    let out = "";
-    messages.reverse().forEach((message) => {
-        if (!message.author.bot) {
-            const sentDate = new Date(message.createdTimestamp);
-            out += `${message.author.username}#${message.author.discriminator} (${
-                message.author.id
-            }) [${sentDate.toUTCString()}]\n`;
-            const lines = message.content.split("\n");
-            lines.forEach((line) => {
-                out += `> ${line}\n`;
-            });
-            out += "\n\n";
-        }
-    });
-    const topic = interaction.channel.topic;
-    let member: GuildMember | null = null;
-    if (topic !== null) {
-        const part = topic.split(" ")[0] ?? null;
-        if (part !== null) member = interaction.guild!.members.cache.get(part) ?? null;
-    }
-    let m: Message;
-    if (out !== "") {
-        const url = await pbClient.createPaste({
-            code: out,
-            expireDate: ExpireDate.Never,
-            name:
-                `Ticket Transcript ${
-                    member ? "for " + member.user.username + "#" + member.user.discriminator + " " : ""
-                }` + `(Created at ${new Date(interaction.channel.createdTimestamp).toDateString()})`,
-            publicity: Publicity.Unlisted
-        });
-        const guildConfig = await client.database.guilds.read(interaction.guild!.id);
-        m = (await interaction.reply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Transcript")
-                    .setDescription(
-                        "You can view the transcript using the link below. You can save the link for later" +
-                            (guildConfig.logging.logs.channel
-                                ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.`
-                                : ".")
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CONTROL.DOWNLOAD")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(url),
-                    new ButtonBuilder()
-                        .setLabel("Delete")
-                        .setStyle(ButtonStyle.Danger)
-                        .setCustomId("close")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                ])
-            ],
-            fetchReply: true
-        })) as Message;
+    let topic
+    let member: GuildMember = interaction.guild?.members.me!;
+    if (interaction.channel instanceof TextChannel) {
+        topic = interaction.channel.topic;
+        if (topic === null) return await interaction.reply({ embeds: [noTopic] });
+        const mem = interaction.guild!.members.cache.get(topic.split(" ")[1]!);
+        if (mem) member = mem;
     } else {
-        m = (await interaction.reply({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle("Transcript")
-                    .setDescription(
-                        "The transcript was empty, so no changes were made. To delete this ticket, press the delete button below."
-                    )
-                    .setStatus("Success")
-                    .setEmoji("CONTROL.DOWNLOAD")
-            ],
-            components: [
-                new ActionRowBuilder<ButtonBuilder>().addComponents([
-                    new ButtonBuilder()
-                        .setLabel("Delete")
-                        .setStyle(ButtonStyle.Danger)
-                        .setCustomId("close")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                ])
-            ],
-            fetchReply: true
-        })) as Message;
+        topic = interaction.channel.name;
+        const split = topic.split("-").map(p => p.trim()) as [string, string, string];
+        const mem = interaction.guild!.members.cache.get(split[1])
+        if (mem) member = mem;
     }
+
+    const newOut = await client.database.transcripts.createTranscript(messages, interaction, member);
+
+    const code = await client.database.transcripts.create(newOut);
+    if(!code) return await interaction.reply({
+        embeds: [
+            new EmojiEmbed()
+                .setTitle("Error")
+                .setDescription("An error occurred while creating the transcript.")
+                .setStatus("Danger")
+                .setEmoji("CONTROL.BLOCKCROSS")
+        ]
+    })
+    const guildConfig = await client.database.guilds.read(interaction.guild!.id);
+    const m: Message = (await interaction.reply({
+        embeds: [
+            new EmojiEmbed()
+                .setTitle("Transcript")
+                .setDescription(
+                    "You can view the transcript using the link below. You can save the link for later" +
+                        (guildConfig.logging.logs.channel
+                            ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.`
+                            : ".")
+                )
+                .setStatus("Success")
+                .setEmoji("CONTROL.DOWNLOAD")
+        ],
+        components: [
+            new ActionRowBuilder<ButtonBuilder>().addComponents([
+                new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript/${code}`),
+                new ButtonBuilder()
+                    .setLabel("Delete")
+                    .setStyle(ButtonStyle.Danger)
+                    .setCustomId("close")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+            ])
+        ],
+        fetchReply: true
+    })) as Message;
     let i;
     try {
         i = 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 }
         });
-        i.deferUpdate();
+        await i.deferUpdate();
     } catch {
         return;
     }
@@ -128,12 +113,13 @@
             calculateType: "ticketUpdate",
             color: NucleusColors.red,
             emoji: "GUILD.TICKET.CLOSE",
-            timestamp: new Date().getTime()
+            timestamp: Date.now()
         },
         list: {
-            ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"),
+            ticketFor: entry(member.id, renderUser(member.user)),
             deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)),
-            deleted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime()))
+            deleted: entry(Date.now().toString(), renderDelta(Date.now())),
+            transcript: entry(code, `https://clicks.codes/nucleus/transcript/${code}`)
         },
         hidden: {
             guild: interaction.guild!.id
diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts
index 6829ef2..a3027e4 100644
--- a/src/reflex/guide.ts
+++ b/src/reflex/guide.ts
@@ -1,9 +1,9 @@
+import { getCommandMentionByName } from './../utils/getCommandDataByName.js';
 import { LoadingEmbed } from "../utils/defaults.js";
 import Discord, {
     ActionRowBuilder,
     ButtonBuilder,
     MessageComponentInteraction,
-    StringSelectMenuInteraction,
     Guild,
     CommandInteraction,
     GuildTextBasedChannel,
@@ -19,32 +19,43 @@
 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;
+    ? 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;
     }
+    let m: Message;
+    if (interaction) {
+        m = (await interaction.reply({
+            embeds: LoadingEmbed,
+            fetchReply: true,
+            ephemeral: true
+        })) as Message;
+    } else {
+        m = await c.send({ embeds: LoadingEmbed });
+    }
+    let page = 0;
     const pages = [
         new Embed()
             .setEmbed(
                 new EmojiEmbed()
                     .setTitle("Welcome to Nucleus")
                     .setDescription(
-                        "Thanks for adding Nucleus to your server\n\n" +
-                            "On the next few pages you can find instructions on getting started, and commands you may want to set up\n\n" +
-                            "If you need support, have questions or want features, you can let us know in [Clicks](https://discord.gg/bPaNnxe)"
+                        "Thanks for adding Nucleus to your server!\n\n" +
+                            "The next few pages will show what features Nucleus has to offer, and how to enable them.\n\n" +
+                            "If you need support, have questions or want features, you can let us know in [Clicks](https://discord.gg/bPaNnxe)!"
                     )
                     .setEmoji("NUCLEUS.LOGO")
                     .setStatus("Danger")
@@ -55,15 +66,17 @@
         new Embed()
             .setEmbed(
                 new EmojiEmbed()
-                    .setTitle("Logging")
+                    .setTitle("Logs")
                     .setDescription(
                         "Nucleus can log server events and keep you informed with what content is being posted to your server.\n" +
                             "We have 2 different types of logs, which each can be configured to send to a channel of your choice:\n" +
-                            "**General Logs:** These are events like kicks and channel changes etc.\n" +
-                            "**Warning Logs:** Warnings like NSFW avatars and spam etc. that may require action by a server staff member. " +
-                            "These go to to a separate staff notifications channel.\n\n" +
-                            "A general log channel can be set with `/settings log`\n" +
-                            "A warning log channel can be set with `/settings warnings channel`"
+                            "**General:** These are events like kicks and channel changes etc.\n" +
+                            `> These are standard logs and can be set with ${getCommandMentionByName("settings/logs/general")}\n` +
+                            "**Warnings:** Warnings like NSFW avatars and spam etc. that may require action by a server staff member.\n" +
+                            `> These may require special action by a moderator. You can set the channel with ${getCommandMentionByName("settings/logs/warnings")}\n` +
+                            "**Attachments:** All images sent in the server - Used to keep a record of deleted images\n" +
+                            `> Sent to a separate log channel to avoid spam. This can be set with ${getCommandMentionByName("settings/logs/attachments")}\n` +
+                            `> ${getEmojiByName("NUCLEUS.PREMIUM")} Please note this feature is only available with ${getCommandMentionByName("nucleus/premium")}`
                     )
                     .setEmoji("ICONS.LOGGING")
                     .setStatus("Danger")
@@ -77,27 +90,15 @@
                     .setTitle("Moderation")
                     .setDescription(
                         "Nucleus has a number of commands that can be used to moderate your server.\n" +
-                            "These commands are all found under `/mod`, and they include:\n" +
-                            `**${getEmojiByName(
-                                "PUNISH.WARN.YELLOW"
-                            )} Warn:** The user is warned (via DM) that they violated server rules.\n` +
-                            `**${getEmojiByName(
-                                "PUNISH.CLEARHISTORY"
-                            )} Clear:** Some messages from a user are deleted in a channel.\n` +
-                            `**${getEmojiByName(
-                                "PUNISH.MUTE.YELLOW"
-                            )} Mute:** The user is unable to send messages or join voice chats.\n` +
-                            `**${getEmojiByName(
-                                "PUNISH.MUTE.GREEN"
-                            )} Unmute:** The user is able to send messages in the server.\n` +
-                            `**${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.`
+                            `These commands are all found under ${getCommandMentionByName(("mod"))}, and they include:\n` +
+                            `${getEmojiByName("PUNISH.WARN.YELLOW")} ${getCommandMentionByName("mod/warn")}: The user is warned (via DM) that they violated server rules. More options given if DMs are disabled.\n` +
+                            `${getEmojiByName("PUNISH.CLEARHISTORY")} ${getCommandMentionByName("mod/purge")}: Deletes messages in a channel, giving options to only delete messages by a certain user.\n` +
+                            `${getEmojiByName("PUNISH.MUTE.YELLOW")} ${getCommandMentionByName("mod/mute")}: Stops users sending messages or joining voice chats.\n` +
+                            `${getEmojiByName("PUNISH.MUTE.GREEN")} ${getCommandMentionByName("mod/unmute")}: Allows user to send messages and join voice chats.\n` +
+                            `${getEmojiByName("PUNISH.KICK.RED")} ${getCommandMentionByName("mod/kick")}: Removes a member from the server. They will be able to rejoin.\n` +
+                            `${getEmojiByName("PUNISH.SOFTBAN")} ${getCommandMentionByName("mod/softban")}: Kicks the user, deleting their messages from every channel in a given time frame.\n` +
+                            `${getEmojiByName("PUNISH.BAN.RED")} ${getCommandMentionByName("mod/ban")}: Removes the user from the server, deleting messages from every channel and stops them from rejoining.\n` +
+                            `${getEmojiByName("PUNISH.BAN.GREEN")} ${getCommandMentionByName("mod/unban")}: Allows a member to rejoin the server after being banned.`
                     )
                     .setEmoji("PUNISH.BAN.RED")
                     .setStatus("Danger")
@@ -111,9 +112,9 @@
                     .setTitle("Verify")
                     .setDescription(
                         "Nucleus has a verification system that allows users to prove they aren't bots.\n" +
-                            "This is done by running `/verify` which sends a message only the user can see, giving them a link to a CAPTCHA to verify.\n" +
-                            "After the user complete's the CAPTCHA, they are given a role and can use the permissions accordingly.\n" +
-                            "You can set the role given with `/settings verify`"
+                            `This is done by running ${getCommandMentionByName("verify")} which sends a message only the user can see, giving them a link to a website to verify.\n` +
+                            "After the user complete's the check, they are given a role, which can be set to unlock specific channels.\n" +
+                            `You can set the role given with ${getCommandMentionByName("settings/verify")}`
                     )
                     .setEmoji("CONTROL.REDTICK")
                     .setStatus("Danger")
@@ -127,8 +128,8 @@
                     .setTitle("Content Scanning")
                     .setDescription(
                         "Nucleus has a content scanning system that automatically scans links and images sent by users.\n" +
-                            "Nucleus can detect, delete, and punish users for sending NSFW content, or links to scam or adult sites.\n" +
-                            "You can set the threshold for this in `/settings automation`" // TODO
+                            "The staff team can be notified when an NSFW image is detected, or malicious links are sent.\n" +
+                            `You can check and manage what to moderate in ${getCommandMentionByName("settings/automod")}`
                     )
                     .setEmoji("MOD.IMAGES.TOOSMALL")
                     .setStatus("Danger")
@@ -141,10 +142,12 @@
                 new EmojiEmbed()
                     .setTitle("Tickets")
                     .setDescription(
-                        "Nucleus has a ticket system that allows users to create tickets and have a support team respond to them.\n" +
-                            "Tickets can be created with `/ticket create` and a channel is created, pinging the user and support role.\n" +
-                            "When the ticket is resolved, anyone can run `/ticket close` (or click the button) to close it.\n" +
-                            "Running `/ticket close` again will delete the ticket."
+                        "Nucleus has a ticket system which allows users to create tickets and talk to the server staff or support team.\n" +
+                            `Tickets can be created by users with ${getCommandMentionByName("ticket/create")}, or by clicking a button created by moderators.\n` +
+                            `After being created, a new channel or thread is created, and the user and support team are pinged. \n` +
+                            `The category or channel to create threads in can be set with ${getCommandMentionByName("settings/tickets")}\n` +
+                            `When the ticket is resolved, anyone can run ${getCommandMentionByName("ticket/close")} (or click the button) to close it.\n` +
+                            `Running ${getCommandMentionByName("ticket/close")} again will delete the ticket.`
                     )
                     .setEmoji("GUILD.TICKET.CLOSE")
                     .setStatus("Danger")
@@ -157,11 +160,10 @@
                 new EmojiEmbed()
                     .setTitle("Tags")
                     .setDescription(
-                        "Add a tag system to your server with the `/tag` and `/tags` commands.\n" +
-                            "To create a tag, type `/tags create <tag name> <tag content>`.\n" +
-                            "Tag names and content can be edited with `/tags edit`.\n" +
-                            "To delete a tag, type `/tags delete <tag name>`.\n" +
-                            "To view all tags, type `/tags list`.\n"
+                        "Nucleus allows you to create tags, which allow a message to be sent when a specific tag is typed.\n" +
+                            `Tags can be created with ${getCommandMentionByName("tags/create")}, and can be edited with ${getCommandMentionByName("tags/edit")}\n` +
+                            `Tags can be deleted with ${getCommandMentionByName("tags/delete")}, and can be listed with ${getCommandMentionByName("tags/list")}\n` +
+                            `To use a tag, you can type ${getCommandMentionByName("tag")}, followed by the tag to send`
                     )
                     .setEmoji("PUNISH.NICKNAME.RED")
                     .setStatus("Danger")
@@ -174,29 +176,18 @@
                 new EmojiEmbed()
                     .setTitle("Premium")
                     .setDescription(
-                        "In the near future, we will be releasing extra premium only features.\n" +
-                            "These features will include:\n\n" +
-                            "**Attachment logs**\n> When a message with attachments is edited or deleted, the logs will also include the images sent.\n" +
-                            "\nPremium is not yet available. Check `/nucleus premium` for updates on features and pricing"
+                        "Nucleus Premium allows you to use extra features in your server, which are useful but not essential.\n" +
+                        "**No currently free commands will become premium features.**\n" +
+                        "Premium features include creating ticket transcripts and attachment logs.\n\n" +
+                        "Premium can be purchased in [our server](https://discord.gg/bPaNnxe) in the subscriptions page" // TODO: add a table graphic
                     )
-                    .setEmoji("NUCLEUS.COMMANDS.LOCK")
+                    .setEmoji("NUCLEUS.PREMIUM")
                     .setStatus("Danger")
             )
             .setTitle("Premium")
             .setDescription("Premium features")
             .setPageId(7)
     ];
-    let m: Message;
-    if (interaction) {
-        m = (await interaction.reply({
-            embeds: LoadingEmbed,
-            fetchReply: true,
-            ephemeral: true
-        })) as Message;
-    } else {
-        m = await c.send({ embeds: LoadingEmbed });
-    }
-    let page = 0;
 
     const publicFilter = async (component: MessageComponentInteraction) => {
         return (component.member as Discord.GuildMember).permissions.has("ManageGuild");
@@ -274,7 +265,7 @@
             timedOut = true;
             continue;
         }
-        i.deferUpdate();
+        await i.deferUpdate();
         if (!("customId" in i.component)) {
             continue;
         } else if (i.component.customId === "left") {
@@ -285,8 +276,8 @@
             selectPaneOpen = false;
         } else if (i.component.customId === "select") {
             selectPaneOpen = !selectPaneOpen;
-        } else if (i.component.customId === "page") {
-            page = parseInt((i as StringSelectMenuInteraction).values[0]!);
+        } else if (i.isStringSelectMenu() && i.component.customId === "page") {
+            page = parseInt(i.values[0]!);
             selectPaneOpen = false;
         } else {
             cancelled = true;
diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts
index 9761e4b..cf713e6 100644
--- a/src/reflex/scanners.ts
+++ b/src/reflex/scanners.ts
@@ -1,23 +1,27 @@
 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;
+    errored?: boolean;
 }
 interface MalwareSchema {
     safe: boolean;
+    errored?: boolean;
 }
 
 export async function testNSFW(link: string): Promise<NSFWSchema> {
-    const p = await saveAttachment(link);
-    const data = new FormData();
-    console.log(link);
-    data.append("file", createReadStream(p));
+    const [p, hash] = await saveAttachment(link);
+    const alreadyHaveCheck = await client.database.scanCache.read(hash)
+    if(alreadyHaveCheck) return { nsfw: alreadyHaveCheck.data };
+    const data = new URLSearchParams();
+    const r = createReadStream(p)
+    data.append("file", r.read(fs.statSync(p).size));
     const result = await fetch("https://unscan.p.rapidapi.com/", {
         method: "POST",
         headers: {
@@ -26,20 +30,24 @@
         },
         body: data
     })
-        .then((response) => response.json() as Promise<NSFWSchema>)
+        .then((response) => response.status === 200 ? response.json() as Promise<NSFWSchema> : { nsfw: false, errored: true })
         .catch((err) => {
             console.error(err);
-            return { nsfw: false };
+            return { nsfw: false, errored: true };
         });
-    console.log(result);
+    if(!result.errored) {
+        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));
-    console.log(link);
+    const [p, hash] = await saveAttachment(link);
+    const alreadyHaveCheck = await client.database.scanCache.read(hash)
+    if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data };
+    const data = new URLSearchParams();
+    const f = createReadStream(p);
+    data.append("file", f.read(fs.statSync(p).size));
     const result = await fetch("https://unscan.p.rapidapi.com/malware", {
         method: "POST",
         headers: {
@@ -48,18 +56,21 @@
         },
         body: data
     })
-        .then((response) => response.json() as Promise<MalwareSchema>)
+        .then((response) => response.status === 200 ? response.json() as Promise<MalwareSchema> : { safe: true, errored: true })
         .catch((err) => {
             console.error(err);
-            return { safe: true };
+            return { safe: true, errored: true };
         });
-    console.log(result);
+    if (!result.errored) {
+        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", {
+    const 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,
@@ -72,18 +83,19 @@
             console.error(err);
             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> {
-    const image = (await (await fetch(link)).buffer()).toString("base64");
+export async function saveAttachment(link: string): Promise<[string, string]> {
+    const image = await (await fetch(link)).arrayBuffer()
     const fileName = generateFileName(link.split("/").pop()!.split(".").pop()!);
-    writeFileSync(fileName, image, "base64");
-    return fileName;
+    const enc = new TextDecoder("utf-8");
+    writeFileSync(fileName, new DataView(image), "base64");
+    return [fileName, createHash('sha512').update(enc.decode(image), 'base64').digest('base64')];
 }
 
 const linkTypes = {
@@ -139,8 +151,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/reflex/statsChannelUpdate.ts b/src/reflex/statsChannelUpdate.ts
index db705d9..daa82fd 100644
--- a/src/reflex/statsChannelUpdate.ts
+++ b/src/reflex/statsChannelUpdate.ts
@@ -1,4 +1,4 @@
-import { getCommandMentionByName } from '../utils/getCommandMentionByName.js';
+import { getCommandMentionByName } from '../utils/getCommandDataByName.js';
 import type { Guild, User } from "discord.js";
 import type { NucleusClient } from "../utils/client.js";
 import type { GuildMember } from "discord.js";
@@ -32,7 +32,7 @@
                 return singleNotify(
                     "statsChannelDeleted",
                     guild!.id,
-                    `One or more of your stats channels have been deleted. You can use ${await getCommandMentionByName("settings/stats")}.\n` +
+                    `One or more of your stats channels have been deleted. You can use ${getCommandMentionByName("settings/stats")}.\n` +
                         `The channels name was: ${deleted!.name}`,
                     "Critical"
                 );
diff --git a/src/reflex/verify.ts b/src/reflex/verify.ts
index 573da5e..290e372 100644
--- a/src/reflex/verify.ts
+++ b/src/reflex/verify.ts
@@ -13,6 +13,8 @@
 import { TestString, NSFWCheck } from "./scanners.js";
 import createPageIndicator from "../utils/createPageIndicator.js";
 import client from "../utils/client.js";
+import singleNotify from "../utils/singleNotify.js";
+import { getCommandMentionByName } from "../utils/getCommandDataByName.js";
 
 export interface VerifySchema {
     uID: string;
@@ -182,14 +184,14 @@
     let itt = 0;
     const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
     do {
-        itt += 1;
+        itt ++;
         code = "";
         for (let i = 0; i < length; i++) {
             code += chars.charAt(Math.floor(Math.random() * chars.length));
         }
         if (itt > 1000) {
             itt = 0;
-            length += 1;
+            length ++;
         }
     } while (code in verify);
     const role: Role | null = await interaction.guild!.roles.fetch(config.verify.role);
@@ -206,7 +208,8 @@
                     .setEmoji("CONTROL.BLOCKCROSS")
             ]
         });
-        return; // TODO: SEN
+        singleNotify("verifyRoleDeleted", interaction.guild!.id, `The role given when a member is verified has been deleted. Use ${getCommandMentionByName("settings/verify")} to set a new one`, "Critical")
+        return;
     }
     verify[code] = {
         uID: interaction.member!.user.id,
diff --git a/src/reflex/welcome.ts b/src/reflex/welcome.ts
index 87bb81a..c2eede3 100644
--- a/src/reflex/welcome.ts
+++ b/src/reflex/welcome.ts
@@ -1,4 +1,4 @@
-import { getCommandMentionByName } from './../utils/getCommandMentionByName.js';
+import { getCommandMentionByName } from './../utils/getCommandDataByName.js';
 import type { NucleusClient } from "../utils/client.js";
 import convertCurlyBracketString from "../utils/convertCurlyBracketString.js";
 import client from "../utils/client.js";
@@ -27,19 +27,19 @@
                 });
             } else {
                 const channel: GuildChannel | null = await member.guild.channels.fetch(config.welcome.channel) as GuildChannel | null;
-                if (!channel) return await singleNotify("welcomeChannelDeleted", member.guild.id, `The welcome channel has been deleted or is no longer accessible. Use ${await getCommandMentionByName("settings/welcome")} to set a new one`, "Warning")
+                if (!channel) return await singleNotify("welcomeChannelDeleted", member.guild.id, `The welcome channel has been deleted or is no longer accessible. Use ${getCommandMentionByName("settings/welcome")} to set a new one`, "Warning")
                 if (!(channel instanceof BaseGuildTextChannel)) return;
                 if (channel.guild.id !== member.guild.id) return;
                 try {
                     await channel.send({
                         embeds: [new EmojiEmbed().setDescription(string).setStatus("Success")],
-                        content: (config.welcome.ping ? `<@${config.welcome.ping}>` : "") + `<@${member.id}>`
+                        content: (config.welcome.ping ? `<@&${config.welcome.ping}>` : "") + `<@${member.id}>`
                     });
                 } catch (err) {
                     singleNotify(
                         "welcomeChannelDeleted",
                         member.guild.id,
-                        `The welcome channel has been deleted or is no longer accessible. Use ${await getCommandMentionByName("settings/welcome")} to set a new one`,
+                        `The welcome channel has been deleted or is no longer accessible. Use ${getCommandMentionByName("settings/welcome")} to set a new one`,
                         "Warning"
                     )
                 }
diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts
index 0bd5a9f..fde1340 100644
--- a/src/utils/calculate.ts
+++ b/src/utils/calculate.ts
@@ -17,16 +17,14 @@
     "webhookUpdate",
     "guildMemberVerify",
     "autoModeratorDeleted",
-    "nucleusSettingsUpdated",
-    "ticketUpdate"
+    "ticketUpdate",
+    // "nucleusSettingsUpdated"
 ];
 
 const tickets = ["support", "report", "question", "issue", "suggestion", "other"];
 
 const toHexInteger = (permissions: string[], array?: string[]): string => {
-    if (!array) {
-        array = logs;
-    }
+    if (!array) { array = logs; }
     let int = 0n;
 
     for (const perm of permissions) {
diff --git a/src/utils/client.ts b/src/utils/client.ts
index 46d9f92..b1fa31f 100644
--- a/src/utils/client.ts
+++ b/src/utils/client.ts
@@ -1,11 +1,11 @@
-import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits } from 'discord.js';
+import Discord, { Client, Interaction, AutocompleteInteraction, Collection } from 'discord.js';
 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, Transcript,  } 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" };
+import config from "../config/main.js";
 
 
 class NucleusClient extends Client {
@@ -22,36 +22,33 @@
         premium: Premium;
         eventScheduler: EventScheduler;
         performanceTest: PerformanceTest;
+        scanCache: ScanCache;
+        transcripts: Transcript
     };
     preloadPage: Record<string, {command: string, argument: string}> = {};  // e.g. { channelID: { command: privacy, page: 3}}
-    commands: Record<string, {
+    commands: Record<string, [{
         command: Discord.SlashCommandBuilder |
                 ((builder: Discord.SlashCommandBuilder) => Discord.SlashCommandBuilder) |
                 Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder),
         callback: (interaction: Interaction) => Promise<void>,
-        check: (interaction: Interaction) => Promise<boolean> | boolean,
+        check: (interaction: Interaction, partial: boolean) => Promise<boolean> | boolean,
         autocomplete: (interaction: AutocompleteInteraction) => Promise<string[]>
-    }> = {};
-
+    } | undefined, {name: string, description: string}]> = {};
+    fetchedCommands = new Collection<string, Discord.ApplicationCommand>();
     constructor(database: typeof NucleusClient.prototype.database) {
-        super({ intents: [
-            GatewayIntentBits.Guilds,
-            GatewayIntentBits.GuildMessages,
-            GatewayIntentBits.MessageContent,
-            GatewayIntentBits.GuildPresences,
-            GatewayIntentBits.GuildMembers
-        ]});
+        super({ intents: 0b1100011011011111111111});
         this.database = database;
     }
 }
-
 const client = new NucleusClient({
-    guilds: await new Guilds().setup(),
+    guilds: await new Guilds(),
     history: new History(),
     notes: new ModNotes(),
     premium: new Premium(),
     eventScheduler: new EventScheduler(),
-    performanceTest: new PerformanceTest()
+    performanceTest: new PerformanceTest(),
+    scanCache: new ScanCache(),
+    transcripts: new Transcript()
 });
 
 export default client;
diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts
index 281be18..33c88b0 100644
--- a/src/utils/commandRegistration/register.ts
+++ b/src/utils/commandRegistration/register.ts
@@ -1,12 +1,12 @@
 import type { CommandInteraction } from 'discord.js';
 import Discord, { Interaction, SlashCommandBuilder, ApplicationCommandType } from 'discord.js';
-import config from "../../config/main.json" assert { type: "json" };
+import config from "../../config/main.js";
 import client from "../client.js";
 import fs from "fs";
 import EmojiEmbed from '../generateEmojiEmbed.js';
 import getEmojiByName from '../getEmojiByName.js';
 
-const colours = {
+const colors = {
     red: "\x1b[31m",
     green: "\x1b[32m",
     yellow: "\x1b[33m",
@@ -26,23 +26,26 @@
     for (const file of files) {
         const last = i === files.length - 1 ? "└" : "├";
         if (file.isDirectory()) {
-            console.log(`${last}─ ${colours.yellow}Loading subcommands of ${file.name}${colours.none}`)
-            const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)).command;
-            commands.push(fetched);
+            console.log(`${last}─ ${colors.yellow}Loading subcommands of ${file.name}${colors.none}`)
+            const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`));
+            commands.push(fetched.command);
         } else if (file.name.endsWith(".js")) {
-            console.log(`${last}─ ${colours.yellow}Loading command ${file.name}${colours.none}`)
+            console.log(`${last}─ ${colors.yellow}Loading command ${file.name}${colors.none}`)
             const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`));
             fetched.command.setDMPermission(fetched.allowedInDMs ?? false)
             fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {})
             fetched.command.setDescriptionLocalizations(fetched.descriptionLocalizations ?? {})
-            if (fetched.nameLocalizations || fetched.descriptionLocalizations) console.log("AAAAA")
+            // if (fetched.nameLocalizations || fetched.descriptionLocalizations)
             commands.push(fetched.command);
-            client.commands["commands/" + fetched.command.name] = fetched;
+            client.commands["commands/" + fetched.command.name] = [
+                fetched,
+                {name: fetched.name ?? fetched.command.name, description: fetched.description ?? fetched.command.description}
+            ];
         }
         i++;
-        console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`)
+        console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colors.green}Loaded ${file.name} [${i} / ${files.length}]${colors.none}`)
     }
-    console.log(`${colours.yellow}Loaded ${commands.length} commands, processing...`)
+    console.log(`${colors.yellow}Loaded ${commands.length} commands, processing...`)
     const processed = []
 
     for (const subcommand of commands) {
@@ -53,7 +56,7 @@
         }
     }
 
-    console.log(`${colours.green}Processed ${processed.length} commands`)
+    console.log(`${colors.green}Processed ${processed.length} commands${colors.none}`)
     return processed;
 
 };
@@ -70,15 +73,15 @@
         const last = i === files.length - 1 ? "└" : "├";
         i++;
         try {
-            console.log(`${last}─ ${colours.yellow}Loading event ${file.name}${colours.none}`)
+            console.log(`${last}─ ${colors.yellow}Loading event ${file.name}${colors.none}`)
             const event = (await import(`../../../${config.eventsFolder}/${file.name}`));
 
             client.on(event.event, event.callback.bind(null, client));
 
-            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`)
+            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colors.green}Loaded ${file.name} [${i} / ${files.length}]${colors.none}`)
         } catch (e) {
             errors++;
-            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colours.red}Failed to load ${file.name} [${i} / ${files.length}]${colours.none}`)
+            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colors.red}Failed to load ${file.name} [${i} / ${files.length}]${colors.none}`)
         }
     }
     console.log(`Loaded ${files.length - errors} events (${errors} failed)`)
@@ -101,36 +104,36 @@
         const last = i === totalFiles - 1 ? "└" : "├";
         i++;
         try {
-            console.log(`${last}─ ${colours.yellow}Loading message context menu ${file.name}${colours.none}`)
+            console.log(`${last}─ ${colors.yellow}Loading message context menu ${file.name}${colors.none}`)
             const context = (await import(`../../../${config.messageContextFolder}/${file.name}`));
             context.command.setType(ApplicationCommandType.Message);
             context.command.setDMPermission(context.allowedInDMs ?? false)
             context.command.setNameLocalizations(context.nameLocalizations ?? {})
             commands.push(context.command);
 
-            client.commands["contextCommands/message/" + context.command.name] = context;
+            client.commands["contextCommands/message/" + context.command.name] = [context, {name: context.name ?? context.command.name, description: context.description ?? context.command.description}];
 
-            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colours.green}Loaded ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`)
         } catch (e) {
             errors++;
-            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colours.red}Failed to load ${file.name} [${i} / ${totalFiles}] | ${e}${colours.none}`)
+            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colors.red}Failed to load ${file.name} [${i} / ${totalFiles}] | ${e}${colors.none}`)
         }
     }
     for (const file of userFiles) {
         const last = i === totalFiles - 1 ? "└" : "├";
         i++;
         try {
-            console.log(`${last}─ ${colours.yellow}Loading user context menu ${file.name}${colours.none}`)
+            console.log(`${last}─ ${colors.yellow}Loading user context menu ${file.name}${colors.none}`)
             const context = (await import(`../../../${config.userContextFolder}/${file.name}`));
             context.command.setType(ApplicationCommandType.User);
             commands.push(context.command);
 
             client.commands["contextCommands/user/" + context.command.name] = context;
 
-            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colours.green}Loaded ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`)
         } catch (e) {
             errors++;
-            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colours.red}Failed to load ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+            console.log(`${last.replace("└", " ").replace("├", "│")}  └─ ${colors.red}Failed to load ${file.name} [${i} / ${totalFiles}]${colors.none}`)
         }
     }
 
@@ -142,11 +145,11 @@
     client.on("interactionCreate", async (interaction: Interaction) => {
         if (interaction.isUserContextMenuCommand()) {;
             const commandName = "contextCommands/user/" + interaction.commandName;
-            execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction)
+            execute(client.commands[commandName]![0]?.check, client.commands[commandName]![0]?.callback, interaction)
             return;
         } else if (interaction.isMessageContextMenuCommand()) {
             const commandName = "contextCommands/message/" + interaction.commandName;
-            execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction)
+            execute(client.commands[commandName]![0]?.check, client.commands[commandName]![0]?.callback, interaction)
             return;
         } else if (interaction.isAutocomplete()) {
             const commandName = interaction.commandName;
@@ -155,7 +158,7 @@
 
             const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : "");
 
-            const choices = await client.commands[fullCommandName]?.autocomplete(interaction);
+            const choices = await client.commands[fullCommandName]![0]?.autocomplete(interaction);
 
             const formatted = (choices ?? []).map(choice => {
                 return { name: choice, value: choice }
@@ -168,7 +171,8 @@
 
             const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : "");
 
-            const command = client.commands[fullCommandName];
+            // console.log(fullCommandName, client.commands[fullCommandName])
+            const command = client.commands[fullCommandName]![0];
             const callback = command?.callback;
             const check = command?.check;
             execute(check, callback, interaction);
@@ -177,6 +181,7 @@
 }
 
 async function execute(check: Function | undefined, callback: Function | undefined, data: CommandInteraction) {
+    // console.log(client.commands["contextCommands/user/User info"])
     if (!callback) return;
     if (check) {
         let result;
@@ -189,12 +194,11 @@
         if (typeof result === "string") {
             const { NucleusColors } = client.logger
             return data.reply({embeds: [new EmojiEmbed()
-                .setTitle("")
                 .setDescription(result)
                 .setColor(NucleusColors.red)
                 .setEmoji(getEmojiByName("CONTROL.BLOCKCROSS"))
-            ]});
-        };
+            ], ephemeral: true});
+        }
     }
     callback(data);
 }
@@ -207,17 +211,20 @@
     if (process.argv.includes("--update-commands")) {
         if (config.enableDevelopment) {
             const guild = await client.guilds.fetch(config.developmentGuildID);
-            console.log(`${colours.purple}Registering commands in ${guild!.name}${colours.none}`)
+            console.log(`${colors.purple}Registering commands in ${guild!.name}${colors.none}`)
             await guild.commands.set(commandList);
         } else {
-            console.log(`${colours.blue}Registering commands in production mode${colours.none}`)
+            console.log(`${colors.blue}Registering commands in production mode${colors.none}`)
+            const guild = await client.guilds.fetch(config.developmentGuildID);
+            await guild.commands.set([]);
             await client.application?.commands.set(commandList);
         }
     }
     await registerCommandHandler();
     await registerEvents();
-    console.log(`${colours.green}Registered commands, events and context menus${colours.none}`)
+    console.log(`${colors.green}Registered commands, events and context menus${colors.none}`)
     console.log(
-        (config.enableDevelopment ? `${colours.purple}Bot started in Development mode` :
-        `${colours.blue}Bot started in Production mode`) + colours.none)
+        (config.enableDevelopment ? `${colors.purple}Bot started in Development mode` :
+        `${colors.blue}Bot started in Production mode`) + colors.none
+    )
 };
diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts
index b2927d6..66291b3 100644
--- a/src/utils/commandRegistration/slashCommandBuilder.ts
+++ b/src/utils/commandRegistration/slashCommandBuilder.ts
@@ -1,12 +1,12 @@
-import type { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
+import { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from "discord.js";
 import type { SlashCommandBuilder } from "discord.js";
-import config from "../../config/main.json" assert { type: "json" };
+import config from "../../config/main.js";
 import getSubcommandsInFolder from "./getFilesInFolder.js";
 import client from "../client.js";
 import Discord from "discord.js";
 
 
-const colours = {
+const colors = {
     red: "\x1b[31m",
     green: "\x1b[32m",
     none: "\x1b[0m"
@@ -23,7 +23,7 @@
     // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
     console.log(`│  ├─ Loading group ${name}`)
     const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│  ")
-    console.log(`│  │  └─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colours.none}`)
+    console.log(`│  │  └─ ${fetched.errors ? colors.red : colors.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colors.none}`)
     return (subcommandGroup: SlashCommandSubcommandGroupBuilder) => {
         subcommandGroup
             .setName(name)
@@ -32,7 +32,9 @@
         if (descriptionLocalizations) { subcommandGroup.setDescriptionLocalizations(descriptionLocalizations) }
 
         for (const subcommand of fetched.subcommands) {
-            subcommandGroup.addSubcommand(subcommand.command);
+            const processedCommand = subcommand.command(new SlashCommandSubcommandBuilder());
+            client.commands["commands/" + path + "/" + processedCommand.name] = [subcommand, { name: processedCommand.name, description: processedCommand.description }]
+            subcommandGroup.addSubcommand(processedCommand);
         };
 
         return subcommandGroup;
@@ -52,7 +54,9 @@
     // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
     commandString = "commands/" + (commandString ?? path);
     const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path);
-    console.log(`│  ├─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colours.none}`)
+    console.log(`│  ├─ ${fetched.errors ? colors.red : colors.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colors.none}`)
+    // console.log({name: name, description: description})
+    client.commands[commandString!] = [undefined, { name: name, description: description }]
     return (command: SlashCommandBuilder) => {
         command.setName(name)
         command.setDescription(description)
@@ -68,15 +72,17 @@
         for (const subcommand of fetched.subcommands) {
             let fetchedCommand;
             if (subcommand.command instanceof Function) {
-                fetchedCommand = subcommand.command(new Discord.SlashCommandSubcommandBuilder());
+                fetchedCommand = subcommand.command(new SlashCommandSubcommandBuilder());
             } else {
                 fetchedCommand = subcommand.command;
             }
-            client.commands[commandString! + "/" + fetchedCommand.name] = subcommand
+            client.commands[commandString! + "/" + fetchedCommand.name] = [subcommand, { name: fetchedCommand.name, description: fetchedCommand.description }]
             command.addSubcommand(fetchedCommand);
         }
         for (const group of fetched.subcommandGroups) {
-            command.addSubcommandGroup(group.command);
+            const processedCommand = group.command(new SlashCommandSubcommandGroupBuilder());
+            client.commands[commandString! + "/" + processedCommand.name] = [undefined, { name: processedCommand.name, description: processedCommand.description }]
+            command.addSubcommandGroup(processedCommand);
         };
         return command;
     };
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index 4d90676..f7cccaf 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -1,11 +1,9 @@
-import { TextInputBuilder } from "@discordjs/builders";
+import { TextInputBuilder } from "discord.js";
 import Discord, {
     CommandInteraction,
-    Interaction,
     Message,
     ActionRowBuilder,
     ButtonBuilder,
-    MessageComponentInteraction,
     ModalSubmitInteraction,
     ButtonStyle,
     TextInputStyle
@@ -183,13 +181,12 @@
             let component;
             try {
                 component = await m.awaitMessageComponent({
-                    filter: (m) => m.user.id === this.interaction.user.id && m.channel!.id === this.interaction.channel!.id,
+                    filter: (i) => i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id,
                     time: 300000
                 });
             } catch (e) {
                 success = false;
-                returnComponents = true;
-                continue;
+                break;
             }
             if (component.customId === "yes") {
                 component.deferUpdate();
@@ -247,17 +244,12 @@
                 });
                 let out;
                 try {
-                    out = await modalInteractionCollector(
-                        m,
-                        (m: Interaction) =>
-                            (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === this.interaction.channelId,
-                        (m) => m.customId === "reason"
-                    );
+                    out = await modalInteractionCollector(m, this.interaction.user) as Discord.ModalSubmitInteraction | null;
                 } catch (e) {
                     cancelled = true;
                     continue;
                 }
-                if (out === null) {
+                if (out === null  || out.isButton()) {
                     cancelled = true;
                     continue;
                 }
@@ -277,23 +269,23 @@
         }
         const returnValue: Awaited<ReturnType<typeof this.send>> = {};
 
-        if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
-        if (success !== undefined) returnValue.success = success;
         if (cancelled) {
             await this.timeoutError()
             returnValue.cancelled = true;
         }
-        if (success == false) {
+        if (success === false) {
             await this.interaction.editReply({
                 embeds: [new EmojiEmbed()
                     .setTitle(this.title)
-                    .setDescription(this.failedMessage ?? "")
+                    .setDescription(this.failedMessage ?? "*Message timed out*")
                     .setStatus(this.failedStatus ?? "Danger")
                     .setEmoji(this.failedEmoji ?? this.redEmoji ?? this.emoji)
                 ], components: []
             });
             return {success: false}
         }
+        if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
+        if (success !== undefined) returnValue.success = success;
         if (newReason) returnValue.newReason = newReason;
 
         const typedReturnValue = returnValue as {cancelled: true} |
diff --git a/src/utils/convertCurlyBracketString.ts b/src/utils/convertCurlyBracketString.ts
index 5d2c23d..058ba16 100644
--- a/src/utils/convertCurlyBracketString.ts
+++ b/src/utils/convertCurlyBracketString.ts
@@ -13,7 +13,7 @@
         .replace("{member:mention}", memberID ? `<@${memberID}>` : "{member:mention}")
         .replace("{member:name}", memberName ? `${memberName}` : "{member:name}")
         .replace("{serverName}", serverName ? `${serverName}` : "{serverName}")
-        .replace("{memberCount}", memberCount ? `${memberCount}` : "{memberCount}")
+        .replace("{memberCount:all}", memberCount ? `${memberCount}` : "{memberCount}")
         .replace("{memberCount:bots}", bots ? `${bots}` : "{memberCount:bots}")
         .replace("{memberCount:humans}", memberCount && bots ? `${memberCount - bots}` : "{memberCount:humans}");
 
diff --git a/src/utils/createPageIndicator.ts b/src/utils/createPageIndicator.ts
index ee123d6..6bc86a4 100644
--- a/src/utils/createPageIndicator.ts
+++ b/src/utils/createPageIndicator.ts
@@ -1,17 +1,17 @@
 import getEmojiByName from "./getEmojiByName.js";
 
-function pageIndicator(amount: number, selected: number, showDetails?: boolean | true) {
+function pageIndicator(amount: number, selected: number, showDetails?: boolean, disabled?: boolean | string) {
     let out = "";
-
+    disabled = disabled ? "GRAY." : ""
     if (amount === 1) {
-        out += getEmojiByName("TRACKS.SINGLE." + (selected === 0 ? "ACTIVE" : "INACTIVE"));
+        out += getEmojiByName("TRACKS.SINGLE." + (disabled) + (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")
+                (i === 0 ? "LEFT" : i === amount - 1 ? "RIGHT" : "MIDDLE") +
+                "." + (disabled) +
+                (i === selected ? "ACTIVE" : "INACTIVE")
             );
         }
     }
@@ -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 ? "GRAY." : "";
+    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/createTemporaryStorage.ts b/src/utils/createTemporaryStorage.ts
index a684d9d..e8a8073 100644
--- a/src/utils/createTemporaryStorage.ts
+++ b/src/utils/createTemporaryStorage.ts
@@ -1,6 +1,6 @@
 import client from "./client.js";
 
-function generalException(location: string) {
+export function generalException(location: string) {
     client.noLog.push(location);
     setTimeout(() => {
         client.noLog = client.noLog.filter((i: string) => {
@@ -29,4 +29,4 @@
         })
         client.preloadPage = Object.fromEntries(object);
     }, 60 * 5 * 1000);
-}
\ No newline at end of file
+}
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 1e8e990..2e64320 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -1,32 +1,45 @@
+import { ButtonStyle, CommandInteraction, ComponentType, GuildMember, Message, MessageComponentInteraction } from "discord.js";
 import type Discord from "discord.js";
 import { Collection, MongoClient } from "mongodb";
-import config from "../config/main.json" assert { type: "json" };
-
-const mongoClient = new MongoClient(config.mongoUrl);
+import config from "../config/main.js";
+import client from "../utils/client.js";
+import * as crypto from "crypto";
+import _ from "lodash";
+import defaultData from '../config/default.js';
+// config.mongoOptions.host, {
+//     auth: {
+//         username: config.mongoOptions.username,
+//         password: config.mongoOptions.password
+//     },
+//     authSource: config.mongoOptions.authSource
+// }
+// mongodb://emails:SweetLife2023!!@127.0.0.1:28180/saveEmail?retryWrites=true&w=majority
+const username = encodeURIComponent(config.mongoOptions.username);
+const password = encodeURIComponent(config.mongoOptions.password);
+const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"});
 await mongoClient.connect();
-const database = mongoClient.db("Nucleus");
+const database = mongoClient.db();
+
+const collectionOptions = { authdb: "admin" };
 
 export class Guilds {
     guilds: Collection<GuildConfig>;
-    defaultData: GuildConfig | null;
+    defaultData: GuildConfig;
 
     constructor() {
         this.guilds = database.collection<GuildConfig>("guilds");
-        this.defaultData = null;
-    }
-
-    async setup(): Promise<Guilds> {
-        this.defaultData = (await import("../config/default.json", { assert: { type: "json" } }))
-            .default as unknown as GuildConfig;
-        return this;
+        this.defaultData = defaultData;
     }
 
     async read(guild: string): Promise<GuildConfig> {
+        // console.log("Guild read")
         const entry = await this.guilds.findOne({ id: guild });
-        return Object.assign({}, this.defaultData, entry);
+        const data = _.clone(this.defaultData!);
+        return _.merge(data, entry ?? {});
     }
 
     async write(guild: string, set: object | null, unset: string[] | string = []) {
+        // console.log("Guild write")
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
         const uo: Record<string, any> = {};
         if (!Array.isArray(unset)) unset = [unset];
@@ -41,6 +54,7 @@
 
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
     async append(guild: string, key: string, value: any) {
+        // console.log("Guild append")
         if (Array.isArray(value)) {
             await this.guilds.updateOne(
                 { id: guild },
@@ -67,7 +81,7 @@
         value: any,
         innerKey?: string | null
     ) {
-        console.log(Array.isArray(value));
+        // console.log("Guild remove")
         if (innerKey) {
             await this.guilds.updateOne(
                 { id: guild },
@@ -96,10 +110,255 @@
     }
 
     async delete(guild: string) {
+        // console.log("Guild delete")
         await this.guilds.deleteOne({ id: guild });
     }
 }
 
+interface TranscriptEmbed {
+    title?: string;
+    description?: string;
+    fields?: {
+        name: string;
+        value: string;
+        inline: boolean;
+    }[];
+    footer?: {
+        text: string;
+        iconURL?: string;
+    };
+    color?: number;
+    timestamp?: string;
+    author?: {
+        name: string;
+        iconURL?: string;
+        url?: string;
+    };
+}
+
+interface TranscriptComponent {
+    type: number;
+    style?: ButtonStyle;
+    label?: string;
+    description?: string;
+    placeholder?: string;
+    emojiURL?: string;
+}
+
+interface TranscriptAuthor {
+    username: string;
+    discriminator: number;
+    nickname?: string;
+    id: string;
+    iconURL?: string;
+    topRole: {
+        color: number;
+        badgeURL?: string;
+    };
+    bot: boolean;
+}
+
+interface TranscriptAttachment {
+    url: string;
+    filename: string;
+    size: number;
+    log?: string;
+}
+
+interface TranscriptMessage {
+    id: string;
+    author: TranscriptAuthor;
+    content?: string;
+    embeds?: TranscriptEmbed[];
+    components?: TranscriptComponent[][];
+    editedTimestamp?: number;
+    createdTimestamp: number;
+    flags?: string[];
+    attachments?: TranscriptAttachment[];
+    stickerURLs?: string[];
+    referencedMessage?: string | [string, string, string];  // the message id, the channel id, the guild id
+}
+
+interface TranscriptSchema {
+    code: string;
+    for: TranscriptAuthor;
+    type: "ticket" | "purge"
+    guild: string;
+    channel: string;
+    messages: TranscriptMessage[];
+    createdTimestamp: number;
+    createdBy: TranscriptAuthor;
+}
+
+export class Transcript {
+    transcripts: Collection<TranscriptSchema>;
+
+    constructor() {
+        this.transcripts = database.collection<TranscriptSchema>("transcripts");
+    }
+
+    async create(transcript: Omit<TranscriptSchema, "code">) {
+        // console.log("Transcript create")
+        let code;
+        do {
+            code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
+        } while (await this.transcripts.findOne({ code: code }));
+
+        const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
+        if(doc.acknowledged) return code;
+        else return null;
+    }
+
+    async read(code: string) {
+        // console.log("Transcript read")
+        return await this.transcripts.findOne({ code: code });
+    }
+
+    async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
+        const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
+        const newOut: Omit<TranscriptSchema, "code"> = {
+            type: "ticket",
+            for: {
+                username: member!.user.username,
+                discriminator: parseInt(member!.user.discriminator),
+                id: member!.user.id,
+                topRole: {
+                    color: member!.roles.highest.color
+                },
+                iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
+                bot: member!.user.bot
+            },
+            guild: interaction.guild!.id,
+            channel: interaction.channel!.id,
+            messages: [],
+            createdTimestamp: Date.now(),
+            createdBy: {
+                username: interaction.user.username,
+                discriminator: parseInt(interaction.user.discriminator),
+                id: interaction.user.id,
+                topRole: {
+                    color: interactionMember?.roles.highest.color ?? 0x000000
+                },
+                iconURL: interaction.user.displayAvatarURL({ forceStatic: true}),
+                bot: interaction.user.bot
+            }
+        }
+        if(member.nickname) newOut.for.nickname = member.nickname;
+        if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
+        messages.reverse().forEach((message) => {
+            const msg: TranscriptMessage = {
+                id: message.id,
+                author: {
+                    username: message.author.username,
+                    discriminator: parseInt(message.author.discriminator),
+                    id: message.author.id,
+                    topRole: {
+                        color: message.member!.roles.highest.color
+                    },
+                    iconURL: message.member!.user.displayAvatarURL({ forceStatic: true}),
+                    bot: message.author.bot
+                },
+                createdTimestamp: message.createdTimestamp
+            };
+            if(message.member?.nickname) msg.author.nickname = message.member.nickname;
+            if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
+            if (message.content) msg.content = message.content;
+            if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
+                const obj: TranscriptEmbed = {};
+                if (embed.title) obj.title = embed.title;
+                if (embed.description) obj.description = embed.description;
+                if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
+                    return {
+                        name: field.name,
+                        value: field.value,
+                        inline: field.inline ?? false
+                    }
+                });
+                if (embed.color) obj.color = embed.color;
+                if (embed.timestamp) obj.timestamp = embed.timestamp
+                if (embed.footer) obj.footer = {
+                    text: embed.footer.text,
+                };
+                if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
+                if (embed.author) obj.author = {
+                    name: embed.author.name
+                };
+                if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
+                if (embed.author?.url) obj.author!.url = embed.author.url;
+                return obj;
+            });
+            if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
+                const obj: TranscriptComponent = {
+                    type: child.type
+                }
+                if (child.type === ComponentType.Button) {
+                    obj.style = child.style;
+                    obj.label = child.label ?? "";
+                } else if (child.type > 2) {
+                    obj.placeholder = child.placeholder ?? "";
+                }
+                return obj
+            }));
+            if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
+            msg.flags = message.flags.toArray();
+
+            if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
+            if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
+            newOut.messages.push(msg);
+        });
+        return newOut;
+    }
+
+    toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
+        let out = "";
+        for (const message of transcript.messages) {
+            if (message.referencedMessage) {
+                if (Array.isArray(message.referencedMessage)) {
+                    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 += ` [${new Date(message.createdTimestamp).toISOString()}]`;
+            if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
+            out += "\n";
+            if (message.content) out += `[Content]\n${message.content}\n\n`;
+            if (message.embeds) {
+                for (const embed of message.embeds) {
+                    out += `[Embed]\n`;
+                    if (embed.title) out += `| Title: ${embed.title}\n`;
+                    if (embed.description) out += `| Description: ${embed.description}\n`;
+                    if (embed.fields) {
+                        for (const field of embed.fields) {
+                            out += `| Field: ${field.name} - ${field.value}\n`;
+                        }
+                    }
+                    if (embed.footer) {
+                        out += `|Footer: ${embed.footer.text}\n`;
+                    }
+                    out += "\n";
+                }
+            }
+            if (message.components) {
+                for (const component of message.components) {
+                    out += `[Component]\n`;
+                    for (const button of component) {
+                        out += `| Button: ${button.label ?? button.description}\n`;
+                    }
+                    out += "\n";
+                }
+            }
+            if (message.attachments) {
+                for (const attachment of message.attachments) {
+                    out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
+                }
+            }
+            out += "\n\n"
+        }
+        return out
+    }
+}
+
 export class History {
     histories: Collection<HistorySchema>;
 
@@ -117,6 +376,7 @@
         after?: string | null,
         amount?: string | null
     ) {
+        // console.log("History create");
         await this.histories.insertOne({
             type: type,
             guild: guild,
@@ -127,10 +387,11 @@
             before: before ?? null,
             after: after ?? null,
             amount: amount ?? null
-        });
+        }, collectionOptions);
     }
 
     async read(guild: string, user: string, year: number) {
+        // console.log("History read");
         const entry = (await this.histories
             .find({
                 guild: guild,
@@ -145,10 +406,41 @@
     }
 
     async delete(guild: string) {
+        // console.log("History delete");
         await this.histories.deleteMany({ guild: guild });
     }
 }
 
+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) {
+        // console.log("ScanCache read");
+        return await this.scanCache.findOne({ hash: hash });
+    }
+
+    async write(hash: string, data: boolean, tags?: string[]) {
+        // console.log("ScanCache write");
+        await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions);
+    }
+
+    async cleanup() {
+        // console.log("ScanCache 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>;
 
@@ -157,10 +449,12 @@
     }
 
     async record(data: PerformanceDataSchema) {
+        // console.log("PerformanceTest record");
         data.timestamp = new Date();
-        await this.performanceData.insertOne(data);
+        await this.performanceData.insertOne(data, collectionOptions);
     }
     async read() {
+        // console.log("PerformanceTest read");
         return await this.performanceData.find({}).toArray();
     }
 }
@@ -184,27 +478,161 @@
     }
 
     async create(guild: string, user: string, note: string | null) {
+        // console.log("ModNotes create");
         await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
     }
 
     async read(guild: string, user: string) {
+        // console.log("ModNotes read");
         const entry = await this.modNotes.findOne({ guild: guild, user: user });
         return entry?.note ?? null;
     }
+
+    async delete(guild: string) {
+        // console.log("ModNotes delete");
+        await this.modNotes.deleteMany({ guild: guild });
+    }
 }
 
 export class Premium {
     premium: Collection<PremiumSchema>;
+    cache: Map<string, [boolean, string, number, boolean, Date]>;  // Date indicates the time one hour after it was created
+    cacheTimeout = 1000 * 60 * 60;  // 1 hour
 
     constructor() {
         this.premium = database.collection<PremiumSchema>("premium");
+        this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
     }
 
-    async hasPremium(guild: string) {
+    async updateUser(user: string, level: number) {
+        // console.log("Premium updateUser");
+        if(!(await this.userExists(user))) await this.createUser(user, level);
+        await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
+    }
+
+    async userExists(user: string): Promise<boolean> {
+        // console.log("Premium userExists");
+        const entry = await this.premium.findOne({ user: user });
+        return entry ? true : false;
+    }
+
+    async createUser(user: string, level: number) {
+        // console.log("Premium createUser");
+        await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
+    }
+
+    async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
+        // console.log("Premium hasPremium");
+        // [Has premium, user giving premium, level, is mod: if given automatically]
+        const cached = this.cache.get(guild);
+        if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
+        const entries = await this.premium.find({}).toArray();
+        const members = (await client.guilds.fetch(guild)).members.cache
+        for(const {user} of entries) {
+            const member = members.get(user);
+            if(member) { //TODO: Notify user if they've given premium to a server that has since gotten premium via a mod.
+                const modPerms = //TODO: Create list in config for perms
+                            member.permissions.has("Administrator") ||
+                            member.permissions.has("ManageChannels") ||
+                            member.permissions.has("ManageRoles") ||
+                            member.permissions.has("ManageEmojisAndStickers") ||
+                            member.permissions.has("ManageWebhooks") ||
+                            member.permissions.has("ManageGuild") ||
+                            member.permissions.has("KickMembers") ||
+                            member.permissions.has("BanMembers") ||
+                            member.permissions.has("ManageEvents") ||
+                            member.permissions.has("ManageMessages") ||
+                            member.permissions.has("ManageThreads")
+                const entry = entries.find(e => e.user === member.id);
+                if(entry && (entry.level === 3) && modPerms) {
+                    this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]);
+                    return [true, member.id, entry.level, true];
+                }
+            }
+        }
         const entry = await this.premium.findOne({
-            appliesTo: { $in: [guild] }
+            appliesTo: {
+                $elemMatch: {
+                    $eq: guild
+                }
+            }
         });
-        return entry !== null;
+        this.cache.set(guild, [entry ? true : false, entry?.user ?? "", entry?.level ?? 0, false, new Date(Date.now() + this.cacheTimeout)]);
+        return entry ? [true, entry.user, entry.level, false] : null;
+    }
+
+    async fetchUser(user: string): Promise<PremiumSchema | null> {
+        // console.log("Premium fetchUser");
+        const entry = await this.premium.findOne({ user: user });
+        if (!entry) return null;
+        return entry;
+    }
+
+    async checkAllPremium(member?: GuildMember) {
+        // console.log("Premium checkAllPremium");
+        const entries = await this.premium.find({}).toArray();
+        if(member) {
+            const entry = entries.find(e => e.user === member.id);
+            if(entry) {
+                const expiresAt = entry.expiresAt;
+                if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null;
+            }
+            const roles = member.roles;
+            let level = 0;
+            if (roles.cache.has("1066468879309750313")) {
+                level = 99;
+            } else if (roles.cache.has("1066465491713003520")) {
+                level = 1;
+            } else if (roles.cache.has("1066439526496604194")) {
+                level = 2;
+            } else if (roles.cache.has("1066464134322978912")) {
+                level = 3;
+            }
+            await this.updateUser(member.id, level);
+            if (level > 0) {
+                await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}})
+            } else {
+                await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
+            }
+        } else {
+            const members = await (await client.guilds.fetch('684492926528651336')).members.fetch();
+            for(const {roles, id} of members.values()) {
+                const entry = entries.find(e => e.user === id);
+                if(entry) {
+                    const expiresAt = entry.expiresAt;
+                    if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null;
+                }
+                let level: number = 0;
+                if (roles.cache.has("1066468879309750313")) {
+                    level = 99;
+                } else if (roles.cache.has("1066465491713003520")) {
+                    level = 1;
+                } else if (roles.cache.has("1066439526496604194")) {
+                    level = 2;
+                } else if (roles.cache.has("1066464134322978912")) {
+                    level = 3;
+                }
+                await this.updateUser(id, level);
+                if (level > 0) {
+                    await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}})
+                } else {
+                    await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
+                }
+            }
+        }
+    }
+
+    async addPremium(user: string, guild: string) {
+        // console.log("Premium addPremium");
+        const { level } = (await this.fetchUser(user))!;
+        this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
+        return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
+    }
+
+    removePremium(user: string, guild: string) {
+        // console.log("Premium removePremium");
+        this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
+        return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
     }
 }
 
@@ -249,7 +677,18 @@
                 channels: string[];
             };
         };
+        clean: {
+            channels: string[];
+            allowed: {
+                users: string[];
+                roles: string[];
+            }
+        }
     };
+    autoPublish: {
+        enabled: boolean;
+        channels: string[];
+    }
     welcome: {
         enabled: boolean;
         role: string | null;
@@ -364,6 +803,6 @@
 export interface PremiumSchema {
     user: string;
     level: number;
-    expires: Date;
     appliesTo: string[];
+    expiresAt?: number;
 }
diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts
index 714a2d9..0b05779 100644
--- a/src/utils/dualCollector.ts
+++ b/src/utils/dualCollector.ts
@@ -1,4 +1,4 @@
-import Discord, { Client, Interaction, Message, MessageComponentInteraction } from "discord.js";
+import { ButtonInteraction, Client, User, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js";
 import client from "./client.js";
 
 export default async function (
@@ -10,15 +10,14 @@
     try {
         out = await new Promise((resolve, _reject) => {
             const mes = m
-                .createMessageComponentCollector({
-                    filter: (m) => interactionFilter(m),
-                    time: 300000
-                })
-                .on("collect", (m) => {
+            .createMessageComponentCollector({
+                filter: (m) => interactionFilter(m),
+                time: 300000
+            })
+            .on("collect", (m) => {
                     resolve(m);
                 });
-            const int = m.channel
-                .createMessageCollector({
+            const int = m.channel.createMessageCollector({
                     filter: (m) => messageFilter(m),
                     time: 300000
                 })
@@ -45,35 +44,41 @@
     return out;
 }
 
+function defaultInteractionFilter(i: MessageComponentInteraction, user: User, m: Message) {
+    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
+}
+
+
 export async function modalInteractionCollector(
-    m: Message,
-    modalFilter: (i: Interaction) => boolean | Promise<boolean>,
-    interactionFilter: (i: MessageComponentInteraction) => boolean | Promise<boolean>
-): Promise<null | Interaction> {
-    let out: Interaction;
+    m: Message, user: User,
+    modalFilter?: (i: Interaction) => boolean | Promise<boolean>,
+    interactionFilter?: (i: MessageComponentInteraction) => boolean | Promise<boolean>
+): Promise<null | ButtonInteraction | ModalSubmitInteraction> {
+    let out: ButtonInteraction | ModalSubmitInteraction;
     try {
         out = await new Promise((resolve, _reject) => {
             const int = m
                 .createMessageComponentCollector({
-                    filter: (i: MessageComponentInteraction) => interactionFilter(i),
+                    filter: (i: MessageComponentInteraction) => (interactionFilter ? interactionFilter(i) : true) && defaultInteractionFilter(i, user, m),
                     time: 300000
                 })
-                .on("collect", (i: Interaction) => {
+                .on("collect", async (i: ButtonInteraction) => {
+                    mod.stop();
+                    int.stop();
+                    await i.deferUpdate();
                     resolve(i);
                 });
-            const mod = new Discord.InteractionCollector(client as Client, {
-                filter: (i: Interaction) => modalFilter(i),
+            const mod = new InteractionCollector(client as Client, {
+                filter: (i: Interaction) => (modalFilter ? modalFilter(i) : true) && i.isModalSubmit() && defaultModalFilter(i, user, m),
                 time: 300000
-            }).on("collect", async (i: Interaction) => {
+            }).on("collect", async (i: ModalSubmitInteraction) => {
                 int.stop();
-                (i as Discord.ModalSubmitInteraction).deferUpdate();
-                resolve(i as Discord.ModalSubmitInteraction);
-            });
-            int.on("end", () => {
                 mod.stop();
-            });
-            mod.on("end", () => {
-                int.stop();
+                await i.deferUpdate();
+                resolve(i);
             });
         });
     } catch (e) {
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/eventScheduler.ts b/src/utils/eventScheduler.ts
index 3c9d6ca..a79a260 100644
--- a/src/utils/eventScheduler.ts
+++ b/src/utils/eventScheduler.ts
@@ -2,7 +2,7 @@
 import client from "./client.js";
 import * as fs from "fs";
 import * as path from "path";
-import config from "../config/main.json" assert { type: "json" };
+import config from "../config/main.js";
 
 class EventScheduler {
     private agenda: Agenda;
@@ -10,11 +10,10 @@
     constructor() {
         this.agenda = new Agenda({
             db: {
-                address: config.mongoUrl + "Nucleus",
+                address: config.mongoOptions.host,
                 collection: "eventScheduler"
             }
         });
-
         this.agenda.define("unmuteRole", async (job) => {
             const guild = await client.guilds.fetch(job.attrs.data.guild);
             const user = await guild.members.fetch(job.attrs.data.user);
@@ -43,12 +42,12 @@
                     calculateType: "guildMemberPunish",
                     color: NucleusColors.green,
                     emoji: "PUNISH.MUTE.GREEN",
-                    timestamp: new Date().getTime()
+                    timestamp: Date.now()
                 },
                 list: {
                     memberId: entry(user.user.id, `\`${user.user.id}\``),
                     name: entry(user.user.id, renderUser(user.user)),
-                    unmuted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+                    unmuted: entry(Date.now().toString(), renderDelta(Date.now())),
                     unmutedBy: entry(null, "*Time out ended*")
                 },
                 hidden: {
diff --git a/src/utils/generateEmojiEmbed.ts b/src/utils/generateEmojiEmbed.ts
index c0f17ae..a326fc5 100644
--- a/src/utils/generateEmojiEmbed.ts
+++ b/src/utils/generateEmojiEmbed.ts
@@ -1,4 +1,4 @@
-import { EmbedBuilder } from "@discordjs/builders";
+import { EmbedBuilder } from "discord.js";
 import getEmojiByName from "./getEmojiByName.js";
 
 const colors = {
@@ -13,13 +13,16 @@
     description = "";
 
     _generateTitle() {
+        if (this._emoji && !this._title) return getEmojiByName(this._emoji)
         if (this._emoji) { return `${getEmojiByName(this._emoji)} ${this._title}`; }
-        return this._title;
+        if (this._title) { return this._title };
+        return "";
     }
 
     override setTitle(title: string) {
         this._title = title;
-        super.setTitle(this._generateTitle());
+        const proposedTitle = this._generateTitle();
+        if (proposedTitle) super.setTitle(proposedTitle);
         return this;
     }
     override setDescription(description: string) {
@@ -29,7 +32,8 @@
     }
     setEmoji(emoji: string) {
         this._emoji = emoji;
-        super.setTitle(this._generateTitle());
+        const proposedTitle = this._generateTitle();
+        if (proposedTitle) super.setTitle(proposedTitle);
         return this;
     }
     setStatus(color: "Danger" | "Warning" | "Success") {
diff --git a/src/utils/getCommandDataByName.ts b/src/utils/getCommandDataByName.ts
new file mode 100644
index 0000000..da3e54b
--- /dev/null
+++ b/src/utils/getCommandDataByName.ts
@@ -0,0 +1,28 @@
+import type Discord from "discord.js";
+import client from "./client.js";
+
+
+export const getCommandMentionByName = (name: string): string => {
+    const split = name.replaceAll("/", " ").split(" ")
+    const commandName: string = split[0]!;
+
+    const filterCommand = (command: Discord.ApplicationCommand) => command.name === commandName;
+
+    const command = client.fetchedCommands.filter(c => filterCommand(c))
+    if (command.size === 0) return `\`/${name.replaceAll("/", " ")}\``;
+    const commandID = command.first()!.id;
+    return `</${split.join(" ")}:${commandID}>`;
+}
+
+export const getCommandByName = (name: string): {name: string, description: string, mention: string} => {
+
+    const split = name.replaceAll(" ", "/")
+    const command = client.commands["commands/" + split]!;
+    // console.log(command)
+    const mention = getCommandMentionByName(name);
+    return {
+        name: command[1].name,
+        description: command[1].description,
+        mention: mention
+    }
+}
\ No newline at end of file
diff --git a/src/utils/getCommandMentionByName.ts b/src/utils/getCommandMentionByName.ts
deleted file mode 100644
index b2b9937..0000000
--- a/src/utils/getCommandMentionByName.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type Discord from "discord.js";
-import client from "./client.js";
-import config from "../config/main.json" assert { type: "json"};
-
-
-export const getCommandMentionByName = async (name: string): Promise<string> => {
-    const split = name.replaceAll("/", " ").split(" ")
-    const commandName: string = split[0]!;
-    let commandID: string;
-
-    const filterCommand = (command: Discord.ApplicationCommand) => command.name === commandName;
-
-    if (config.enableDevelopment) {
-        const developmentGuild = client.guilds.cache.get(config.developmentGuildID)!;
-        await developmentGuild.commands.fetch();
-        commandID = developmentGuild.commands.cache.filter(c => filterCommand(c)).first()!.id;
-    } else {
-        await client.application?.commands.fetch();
-        commandID = client.application?.commands.cache.filter(c => filterCommand(c)).first()!.id!;
-    }
-    return `</${split.join(" ")}:${commandID}>`;
-}
\ No newline at end of file
diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts
index 3fa2b53..9df17a4 100644
--- a/src/utils/getEmojiByName.ts
+++ b/src/utils/getEmojiByName.ts
@@ -1,5 +1,7 @@
 import emojis from "../config/emojis.json" assert { type: "json" };
+import lodash from 'lodash';
 
+const isArray = lodash.isArray;
 interface EmojisIndex {
     [key: string]: string | EmojisIndex | EmojisIndex[];
 }
@@ -12,7 +14,7 @@
         if (typeof id === "string" || id === undefined) {
             throw new Error(`Emoji ${name} not found`);
         }
-        if (Array.isArray(id)) {
+        if (isArray(id)) {
             id = id[parseInt(part)];
         } else {
             id = id[part];
@@ -21,6 +23,10 @@
     if (typeof id !== "string" && id !== undefined) {
         throw new Error(`Emoji ${name} not found`);
     }
+    return getEmojiFromId(id, format);
+}
+
+function getEmojiFromId(id: string | undefined, format?: string): string {
     if (format === "id") {
         if (id === undefined) return "0";
         return id.toString();
diff --git a/src/utils/listToAndMore.ts b/src/utils/listToAndMore.ts
new file mode 100644
index 0000000..791ce40
--- /dev/null
+++ b/src/utils/listToAndMore.ts
@@ -0,0 +1,7 @@
+export default (list: string[], max: number) => {
+    // PineappleFan, Coded, Mini (and 10 more)
+    if(list.length > max) {
+        return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`;
+    }
+    return list.join(", ");
+}
\ No newline at end of file
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 54f656a..c6416a1 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -7,10 +7,37 @@
 
 const wait = promisify(setTimeout);
 
+export interface LoggerOptions {
+    meta: {
+        type: string;
+        displayName: string;
+        calculateType: string;
+        color: number;
+        emoji: string;
+        timestamp: number;
+    };
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    list: any;
+    hidden: {
+        guild: string;
+    },
+    separate?: {
+        start?: string;
+        end?: string;
+    }
+}
+
+async function isLogging(guild: string, type: string): Promise<boolean> {
+    const config = await client.database.guilds.read(guild);
+    if (!config.logging.logs.enabled) return false;
+    if (!config.logging.logs.channel) return false;
+    if (!toHexArray(config.logging.logs.toLog).includes(type)) { return false; }
+    return true;
+}
 
 export const Logger = {
     renderUser(user: Discord.User | string) {
-        if (typeof user === "string") return `${user} [<@${user}>]`;
+        if (typeof user === "string") user = client.users.cache.get(user)!;
         return `${user.username} [<@${user.id}>]`;
     },
     renderTime(t: number) {
@@ -29,10 +56,12 @@
         if (typeof value === "number") value = value.toString();
         return { value: value, displayValue: displayValue };
     },
-    renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel) {
+    renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | string) {
+        if (typeof channel === "string") channel = client.channels.cache.get(channel) as Discord.GuildChannel | Discord.ThreadChannel;
         return `${channel.name} [<#${channel.id}>]`;
     },
-    renderRole(role: Discord.Role) {
+    renderRole(role: Discord.Role | string, guild?: Discord.Guild | string) {
+        if (typeof role === "string") role = (typeof guild === "string" ? client.guilds.cache.get(guild) : guild)!.roles.cache.get(role)!;
         return `${role.name} [<@&${role.id}>]`;
     },
     renderEmoji(emoji: Discord.GuildEmoji) {
@@ -43,19 +72,14 @@
         yellow: 0xf2d478,
         green: 0x68d49e
     },
-    async getAuditLog(guild: Discord.Guild, event: Discord.GuildAuditLogsResolvable): Promise<Discord.GuildAuditLogsEntry[]> {
-        await wait(250);
+    async getAuditLog(guild: Discord.Guild, event: Discord.GuildAuditLogsResolvable, delay?: number): Promise<Discord.GuildAuditLogsEntry[]> {
+        await wait(delay ?? 250);
         const auditLog = (await guild.fetchAuditLogs({ type: event })).entries.map(m => m)
         return auditLog as Discord.GuildAuditLogsEntry[];
     },
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    async log(log: any): Promise<void> {
+    async log(log: LoggerOptions): Promise<void> {
+        if (!await isLogging(log.hidden.guild, log.meta.calculateType)) return;
         const config = await client.database.guilds.read(log.hidden.guild);
-        if (!config.logging.logs.enabled) return;
-        if (!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) {
-            console.log("Not logging this type of event");
-            return;
-        }
         if (config.logging.logs.channel) {
             const channel = (await client.channels.fetch(config.logging.logs.channel)) as Discord.TextChannel | null;
             const description: Record<string, string> = {};
@@ -70,7 +94,7 @@
                 }
             });
             if (channel) {
-                log.separate = log.separate || {};
+                log.separate = log.separate ?? {};
                 const embed = new Discord.EmbedBuilder()
                     .setTitle(`${getEmojiByName(log.meta.emoji)} ${log.meta.displayName}`)
                     .setDescription(
@@ -83,8 +107,8 @@
                 channel.send({ embeds: [embed] });
             }
         }
-    }
+    },
+    isLogging
 };
 
-
 export default {};
diff --git a/src/utils/logTranscripts.ts b/src/utils/logTranscripts.ts
deleted file mode 100644
index 0950664..0000000
--- a/src/utils/logTranscripts.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import type Discord from 'discord.js';
-
-export interface JSONTranscriptSchema {
-    messages: {
-        content: string | null;
-        attachments: {
-            url: string;
-            name: string;
-            size: number;
-        }[];
-        authorID: string;
-        authorUsername: string;
-        authorUsernameColor: string;
-        timestamp: string;
-        id: string;
-        edited: boolean;
-    }[];
-    channel: string;
-    guild: string;
-    timestamp: string;
-}
-
-
-export const JSONTranscriptFromMessageArray = (messages: Discord.Message[]): JSONTranscriptSchema | null => {
-    if (messages.length === 0) return null;
-    return {
-        guild: messages[0]!.guild!.id,
-        channel: messages[0]!.channel.id,
-        timestamp: Date.now().toString(),
-        messages: messages.map((message: Discord.Message) => {
-            return {
-                content: message.content,
-                attachments: message.attachments.map((attachment: Discord.Attachment) => {
-                    return {
-                        url: attachment.url,
-                        name: attachment.name!,
-                        size: attachment.size,
-                    };
-                }),
-                authorID: message.author.id,
-                authorUsername: message.author.username + "#" + message.author.discriminator,
-                authorUsernameColor: message.member!.displayHexColor.toString(),
-                timestamp: message.createdTimestamp.toString(),
-                id: message.id,
-                edited: message.editedTimestamp ? true : false,
-            };
-        })
-    };
-}
-
-export const JSONTranscriptToHumanReadable = (data: JSONTranscriptSchema): string => {
-    let out = "";
-
-    for (const message of data.messages) {
-        const date = new Date(parseInt(message.timestamp));
-        out += `${message.authorUsername} (${message.authorID}) [${date}]`;
-        if (message.edited) out += " (edited)";
-        if (message.content) out += "\nContent:\n" + message.content.split("\n").map((line: string) => `\n> ${line}`).join("");
-        if (message.attachments.length > 0) out += "\nAttachments:\n" + message.attachments.map((attachment: { url: string; name: string; size: number; }) => `\n> [${attachment.name}](${attachment.url}) (${attachment.size} bytes)`).join("\n");
-
-        out += "\n\n";
-    }
-    return out;
-}
\ No newline at end of file
diff --git a/src/utils/memory.ts b/src/utils/memory.ts
index 870ffaf..60a6535 100644
--- a/src/utils/memory.ts
+++ b/src/utils/memory.ts
@@ -7,6 +7,7 @@
     logging: GuildConfig["logging"];
     tickets: GuildConfig["tickets"];
     tags: GuildConfig["tags"];
+    autoPublish: GuildConfig["autoPublish"];
 }
 
 class Memory {
@@ -31,7 +32,8 @@
                 filters: guildData.filters,
                 logging: guildData.logging,
                 tickets: guildData.tickets,
-                tags: guildData.tags
+                tags: guildData.tags,
+                autoPublish: guildData.autoPublish
             });
         }
         return this.memory.get(guild)!;
diff --git a/src/utils/performanceTesting/record.ts b/src/utils/performanceTesting/record.ts
index 95761e9..71883c5 100644
--- a/src/utils/performanceTesting/record.ts
+++ b/src/utils/performanceTesting/record.ts
@@ -2,7 +2,7 @@
 import * as CP from 'child_process';
 import * as process from 'process';
 import systeminformation from "systeminformation";
-import config from "../../config/main.json" assert { type: "json" };
+import config from "../../config/main.js";
 import singleNotify from "../singleNotify.js";
 
 
@@ -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
         )
diff --git a/src/utils/singleNotify.ts b/src/utils/singleNotify.ts
index 8e3aa60..6bf63e1 100644
--- a/src/utils/singleNotify.ts
+++ b/src/utils/singleNotify.ts
@@ -1,6 +1,7 @@
 import client from "./client.js";
 import EmojiEmbed from "./generateEmojiEmbed.js";
 import { Record as ImmutableRecord } from "immutable";
+import type { TextChannel, ThreadChannel, NewsChannel } from "discord.js";
 
 const severitiesType = ImmutableRecord({
     Critical: "Danger",
@@ -31,20 +32,20 @@
         const channel = await client.channels.fetch(data.logging.staff.channel);
         if (!channel) return;
         if (!channel.isTextBased()) return;
+        const textChannel = channel as TextChannel | ThreadChannel | NewsChannel;
+        let messageData = {embeds: [
+            new EmojiEmbed()
+                .setTitle(`${severity} notification`)
+                .setDescription(message)
+                .setStatus(severities.get(severity))
+                .setEmoji("CONTROL.BLOCKCROSS")
+        ]}
         if (pings) {
-            await channel.send({
+            messageData = Object.assign(messageData, {
                 content: pings.map((ping) => `<@${ping}>`).join(" ")
             });
         }
-        await channel.send({
-            embeds: [
-                new EmojiEmbed()
-                    .setTitle(`${severity} notification`)
-                    .setDescription(message)
-                    .setStatus(severities.get(severity))
-                    .setEmoji("CONTROL.BLOCKCROSS")
-            ]
-        });
+        await textChannel.send(messageData);
     } catch (err) {
         console.error(err);
     }
diff --git a/src/utils/temp/generateFileName.ts b/src/utils/temp/generateFileName.ts
index 3aab64c..109478d 100644
--- a/src/utils/temp/generateFileName.ts
+++ b/src/utils/temp/generateFileName.ts
@@ -12,7 +12,7 @@
     if (fs.existsSync(`./${fileName}`)) {
         fileName = generateFileName(ending);
     }
-    client.database.eventScheduler.schedule("deleteFile", (new Date().getTime() + 60 * 1000).toString(), {
+    client.database.eventScheduler.schedule("deleteFile", (Date.now() + 60 * 1000).toString(), {
         fileName: `${fileName}.${ending}`
     });
     return path.join(__dirname, fileName + "." + ending);
diff --git a/tsconfig.json b/tsconfig.json
index a39c584..537e3dc 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,8 +11,8 @@
         "resolveJsonModule": true,
         "moduleResolution": "NodeNext",
         "skipLibCheck": true,
-        "noImplicitReturns": false
+        "noImplicitReturns": false,
     },
-    "include": ["src/**/*"],
+    "include": ["src/**/*", "src/*", "src/config/main.d.ts", "src/config/main.ts"],
     "exclude": ["src/Unfinished/**/*"]
 }