few changes
diff --git a/clicksminutepernet b/clicksminutepernet
deleted file mode 160000
index 3077ca8..0000000
--- a/clicksminutepernet
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3077ca8f94bae6de555bb404fd288f4059f33caa
diff --git a/package-lock.json b/package-lock.json
index 3d75029..e928ea1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
       "dependencies": {
         "@discordjs/builders": "^0.12.0",
         "discord.js": "^13.6.0",
+        "express": "^4.18.1",
         "humanize": "^0.0.9",
         "humanize-duration": "^3.27.1",
         "jshaiku": "file:../haiku",
@@ -124,6 +125,23 @@
         "@types/node": "*"
       }
     },
+    "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==",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -144,6 +162,29 @@
       "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
       "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM="
     },
+    "node_modules/body-parser": {
+      "version": "1.20.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
+      "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
+      "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.10.3",
+        "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/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -153,6 +194,26 @@
         "concat-map": "0.0.1"
       }
     },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "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==",
+      "dependencies": {
+        "function-bind": "^1.1.1",
+        "get-intrinsic": "^1.0.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/cli-color": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.1.tgz",
@@ -200,6 +261,38 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
     },
+    "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==",
+      "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==",
+      "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==",
+      "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": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
     "node_modules/d": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
@@ -209,6 +302,14 @@
         "type": "^1.0.1"
       }
     },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -217,6 +318,23 @@
         "node": ">=0.4.0"
       }
     },
+    "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==",
+      "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==",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
     "node_modules/difflib": {
       "version": "0.2.4",
       "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz",
@@ -283,6 +401,19 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/es5-ext": {
       "version": "0.10.57",
       "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.57.tgz",
@@ -327,6 +458,19 @@
         "es6-symbol": "^3.1.1"
       }
     },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/event-emitter": {
       "version": "0.3.5",
       "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
@@ -336,6 +480,47 @@
         "es5-ext": "~0.10.14"
       }
     },
+    "node_modules/express": {
+      "version": "4.18.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
+      "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.0",
+        "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.10.3",
+        "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/ext": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz",
@@ -357,6 +542,23 @@
         "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==",
+      "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/form-data": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -370,11 +572,45 @@
         "node": ">= 6"
       }
     },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+      "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": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
+    "node_modules/function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
+      "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+      "dependencies": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/glob": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
@@ -394,11 +630,48 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dependencies": {
+        "function-bind": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "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==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/heap": {
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
       "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
     },
+    "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==",
+      "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/humanize": {
       "version": "0.0.9",
       "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz",
@@ -412,6 +685,17 @@
       "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz",
       "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA=="
     },
+    "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==",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/idb-keyval": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.2.0.tgz",
@@ -431,6 +715,14 @@
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
+    "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==",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/is-electron": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.1.tgz",
@@ -497,6 +789,14 @@
         "es5-ext": "~0.10.2"
       }
     },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/memoizee": {
       "version": "0.4.15",
       "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz",
@@ -512,6 +812,30 @@
         "timers-ext": "^0.1.7"
       }
     },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/mime-db": {
       "version": "1.51.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
@@ -542,6 +866,19 @@
         "node": "*"
       }
     },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "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==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/next-tick": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
@@ -566,6 +903,25 @@
         }
       }
     },
+    "node_modules/object-inspect": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
+      "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "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==",
+      "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",
@@ -582,6 +938,14 @@
         "opencollective-postinstall": "index.js"
       }
     },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "engines": {
+        "node": ">= 0.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",
@@ -590,11 +954,64 @@
         "node": ">=0.10.0"
       }
     },
+    "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": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
     "node_modules/piexifjs": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
       "integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
     },
+    "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==",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.10.3",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+      "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+      "dependencies": {
+        "side-channel": "^1.0.4"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "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==",
+      "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==",
+      "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/regenerator-runtime": {
       "version": "0.13.9",
       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
@@ -606,6 +1023,98 @@
       "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
       "deprecated": "https://github.com/lydell/resolve-url#deprecated"
     },
+    "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"
+        }
+      ]
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "node_modules/send": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "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/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "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==",
+      "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=="
+    },
+    "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==",
+      "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/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/tesseract.js": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-2.1.5.tgz",
@@ -641,6 +1150,14 @@
         "next-tick": "1"
       }
     },
+    "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==",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
     "node_modules/tr46": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -661,6 +1178,18 @@
       "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
       "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
     },
+    "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==",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/typescript": {
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
@@ -673,6 +1202,14 @@
         "node": ">=4.2.0"
       }
     },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/unscan": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/unscan/-/unscan-1.1.2.tgz",
@@ -686,6 +1223,22 @@
         "unscan": "bin/unscan.js"
       }
     },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -817,6 +1370,20 @@
         "@types/node": "*"
       }
     },
+    "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==",
+      "requires": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -837,6 +1404,25 @@
       "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
       "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM="
     },
+    "body-parser": {
+      "version": "1.20.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
+      "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
+      "requires": {
+        "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.10.3",
+        "raw-body": "2.5.1",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      }
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -846,6 +1432,20 @@
         "concat-map": "0.0.1"
       }
     },
+    "bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
+    },
+    "call-bind": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "get-intrinsic": "^1.0.2"
+      }
+    },
     "cli-color": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.1.tgz",
@@ -881,6 +1481,29 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
     },
+    "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==",
+      "requires": {
+        "safe-buffer": "5.2.1"
+      }
+    },
+    "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=="
+    },
+    "cookie": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
     "d": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
@@ -890,11 +1513,29 @@
         "type": "^1.0.1"
       }
     },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
     },
+    "depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+    },
+    "destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
+    },
     "difflib": {
       "version": "0.2.4",
       "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz",
@@ -946,6 +1587,16 @@
         "wordwrap": ">=0.0.2"
       }
     },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
     "es5-ext": {
       "version": "0.10.57",
       "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.57.tgz",
@@ -986,6 +1637,16 @@
         "es6-symbol": "^3.1.1"
       }
     },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
     "event-emitter": {
       "version": "0.3.5",
       "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
@@ -995,6 +1656,44 @@
         "es5-ext": "~0.10.14"
       }
     },
+    "express": {
+      "version": "4.18.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
+      "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
+      "requires": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.0",
+        "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.10.3",
+        "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"
+      }
+    },
     "ext": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz",
@@ -1015,6 +1714,20 @@
       "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
       "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg=="
     },
+    "finalhandler": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "requires": {
+        "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"
+      }
+    },
     "form-data": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -1025,11 +1738,36 @@
         "mime-types": "^2.1.12"
       }
     },
+    "forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "get-intrinsic": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
+      "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1"
+      }
+    },
     "glob": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
@@ -1043,11 +1781,36 @@
         "path-is-absolute": "^1.0.0"
       }
     },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
+    },
     "heap": {
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
       "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
     },
+    "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==",
+      "requires": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      }
+    },
     "humanize": {
       "version": "0.0.9",
       "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz",
@@ -1058,6 +1821,14 @@
       "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz",
       "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA=="
     },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
     "idb-keyval": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.2.0.tgz",
@@ -1077,6 +1848,11 @@
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
+    "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=="
+    },
     "is-electron": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.1.tgz",
@@ -1148,6 +1924,11 @@
         "es5-ext": "~0.10.2"
       }
     },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
     "memoizee": {
       "version": "0.4.15",
       "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz",
@@ -1163,6 +1944,21 @@
         "timers-ext": "^0.1.7"
       }
     },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
     "mime-db": {
       "version": "1.51.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
@@ -1184,6 +1980,16 @@
         "brace-expansion": "^1.1.7"
       }
     },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+    },
     "next-tick": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
@@ -1197,6 +2003,19 @@
         "whatwg-url": "^5.0.0"
       }
     },
+    "object-inspect": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
+      "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
+    },
+    "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==",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -1210,16 +2029,59 @@
       "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
       "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="
     },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
     },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
     "piexifjs": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
       "integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
     },
+    "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==",
+      "requires": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      }
+    },
+    "qs": {
+      "version": "6.10.3",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+      "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+      "requires": {
+        "side-channel": "^1.0.4"
+      }
+    },
+    "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=="
+    },
+    "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==",
+      "requires": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
     "regenerator-runtime": {
       "version": "0.13.9",
       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
@@ -1230,6 +2092,74 @@
       "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
       "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
     },
+    "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=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "send": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "requires": {
+        "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"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+        }
+      }
+    },
+    "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==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.18.0"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+    },
+    "side-channel": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "get-intrinsic": "^1.0.2",
+        "object-inspect": "^1.9.0"
+      }
+    },
+    "statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
+    },
     "tesseract.js": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-2.1.5.tgz",
@@ -1264,6 +2194,11 @@
         "next-tick": "1"
       }
     },
+    "toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
+    },
     "tr46": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -1284,11 +2219,25 @@
       "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
       "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
     },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
     "typescript": {
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
       "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg=="
     },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
     "unscan": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/unscan/-/unscan-1.1.2.tgz",
@@ -1299,6 +2248,16 @@
         "node-fetch": "^2.6.5"
       }
     },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    },
     "webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
diff --git a/package.json b/package.json
index 8e5fc69..dd55984 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,9 @@
 {
   "dependencies": {
     "@discordjs/builders": "^0.12.0",
+    "body-parser": "^1.20.0",
     "discord.js": "^13.6.0",
+    "express": "^4.18.1",
     "humanize": "^0.0.9",
     "humanize-duration": "^3.27.1",
     "jshaiku": "file:../haiku",
@@ -12,7 +14,7 @@
   },
   "name": "nucleus",
   "version": "0.0.1",
-  "description": "Nucleus: The core Clicks Minute Per bot",
+  "description": "Nucleus: The core Clicks bot",
   "main": "dist/index.js",
   "scripts": {
     "build": "tsc",
@@ -23,7 +25,7 @@
     "type": "git",
     "url": "git+ssh://git@github.com/ClicksMinutePer/Nucleus.git"
   },
-  "author": "Clicks Minute Per",
+  "author": "Clicks",
   "contributors": [
     "Minion3665",
     "PineappleFan"
diff --git a/src/api/index.ts b/src/api/index.ts
new file mode 100644
index 0000000..bd15679
--- /dev/null
+++ b/src/api/index.ts
@@ -0,0 +1,70 @@
+import { HaikuClient } from 'jshaiku';
+import express from 'express';
+import bodyParser from 'body-parser';
+import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
+
+const jsonParser = bodyParser.json();
+const app = express();
+const port = 10000;
+
+const runServer = (client: HaikuClient) => {
+    app.get('/', (req, res) => {
+        res.send(client.ws.ping);
+    });
+
+    app.post('/verify/:code', jsonParser, async function (req, res) {
+        const code = req.params.code;
+        const secret = req.body.secret;
+        if (secret === client.config.verifySecret) {
+            let guild = await client.guilds.fetch(client.verify[code].gID);
+            if (!guild) { return res.status(404) }
+            let member = await guild.members.fetch(client.verify[code].uID);
+            if (!member) { return res.status(404) }
+            if (member.roles.cache.has(client.verify[code].rID)) { return res.status(200) }
+            await member.roles.add(client.verify[code].rID);
+
+            let interaction = client.verify[code].interaction;
+            if (interaction) {
+                interaction.editReply({embeds: [new generateEmojiEmbed()
+                    .setTitle("Verify")
+                    .setDescription(`Verification complete`)
+                    .setStatus("Success")
+                    .setEmoji("MEMBER.JOIN")
+                ], components: []});
+            }
+            res.status(200).send();
+        } else {
+            res.status(403).send();
+        }
+    });
+
+    app.patch('/verify/:code', (req, res) => {
+        const code = req.params.code;
+        try {
+            let interaction = client.verify[code].interaction;
+            if (interaction) {
+                interaction.editReply({embeds: [new generateEmojiEmbed()
+                    .setTitle("Verify")
+                    .setDescription(`Verify was opened in another tab or window, please complete the CAPTCHA there to continue`)
+                    .setStatus("Success")
+                    .setEmoji("MEMBER.JOIN")
+                ]});
+            }
+        } catch {}
+        res.status(200).send();
+    })
+
+    app.get('/verify/:code', jsonParser, function (req, res) {
+        const code = req.params.code;
+        if (client.verify[code]) {
+            let data = structuredClone(client.verify[code])
+            delete data.interaction;
+            return res.status(200).send(data);
+        }
+        return res.status(404).send();
+    })
+
+    app.listen(port);
+}
+
+export default runServer;
\ No newline at end of file
diff --git a/src/automations/createModActionTicket.ts b/src/automations/createModActionTicket.ts
index a4d549c..4de2924 100644
--- a/src/automations/createModActionTicket.ts
+++ b/src/automations/createModActionTicket.ts
@@ -1,6 +1,7 @@
-import Discord from 'discord.js';
+import Discord, { MessageActionRow, MessageButton } from 'discord.js';
 import readConfig from '../utils/readConfig.js'
-import generateEmojiEmbed from '../utils/generateEmojiEmbed.js'
+import generateEmojiEmbed from '../utils/generateEmojiEmbed.js';
+import getEmojiByName from "../utils/getEmojiByName.js";
 
 export async function create(guild: Discord.Guild, member: Discord.User, client) {
     let config = await readConfig(guild.id);
@@ -52,7 +53,12 @@
             )
             .setStatus("Success")
             .setEmoji("GUILD.TICKET.OPEN")
-        ]})
+        ], components: [new MessageActionRow().addComponents([new MessageButton()
+            .setLabel("Close")
+            .setStyle("DANGER")
+            .setCustomId("closeticket")
+            .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+        ])]})
         let data = {
             meta:{
                 type: 'ticketCreate',
diff --git a/src/automations/guide.ts b/src/automations/guide.ts
index 66c9eb0..e3cfa25 100644
--- a/src/automations/guide.ts
+++ b/src/automations/guide.ts
@@ -3,8 +3,8 @@
 import getEmojiByName from "../utils/getEmojiByName.js";
 
 export default async (guild, interaction?) => {
-	let c = guild.publicUpdatesChannel ? guild.publicUpdatesChannel : guild.systemChannel;
-	c = c ? c : guild.channels.cache.find(ch => ch.type === "GUILD_TEXT" && ch.permissionsFor(guild.roles.everyone).has("SEND_MESSAGES") && ch.permissionsFor(guild.me).has("EMBED_LINKS"));
+    let c = guild.publicUpdatesChannel ? guild.publicUpdatesChannel : guild.systemChannel;
+    c = c ? c : guild.channels.cache.find(ch => ch.type === "GUILD_TEXT" && ch.permissionsFor(guild.roles.everyone).has("SEND_MESSAGES") && ch.permissionsFor(guild.me).has("EMBED_LINKS"));
     let pages = [
         new generateEmojiEmbed()
             .setTitle("Welcome to Nucleus")
@@ -73,7 +73,7 @@
             .setEmoji("NUCLEUS.LOGO")
             .setStatus("Danger")
     ]
-	let m;
+    let m;
     if (interaction) {
         m = await interaction.reply({embeds: [
             new generateEmojiEmbed()
@@ -90,7 +90,7 @@
                 .setStatus("Danger")
                 .setEmoji("NUCLEUS.LOADING")
         ], fetchReply: true });
-	}
+    }
     let page = 0;
 
     let f = async (component) => {
diff --git a/src/automations/roleMenu.ts b/src/automations/roleMenu.ts
index 2412c70..c7073c5 100644
--- a/src/automations/roleMenu.ts
+++ b/src/automations/roleMenu.ts
@@ -58,7 +58,7 @@
                 new MessageButton()
                     .setLabel("Online")
                     .setStyle("LINK")
-                    .setURL(`https://clicksminuteper.net/nuclues/rolemenu?code=${code}`),
+                    .setURL(`https://clicks.codes/nuclues/rolemenu?code=${code}`),
                 new MessageButton()
                     .setLabel("Manual")
                     .setStyle("PRIMARY")
diff --git a/src/automations/unscan.ts b/src/automations/unscan.ts
index 743374b..b5c5ffb 100644
--- a/src/automations/unscan.ts
+++ b/src/automations/unscan.ts
@@ -40,11 +40,11 @@
 export async function SizeCheck(element): Promise<boolean> {
     if (element.height == undefined || element.width == undefined) return true
     if (element.height < 20 || element.width < 20) return false
-	return true
+    return true
 }
 
 export async function MalwareCheck(element): Promise<boolean> {
-	try {
+    try {
         //@ts-ignore
         return (await scan.testMalware(element)).safe
     } catch {
diff --git a/src/automations/verify.ts b/src/automations/verify.ts
index 80087a4..a60ed07 100644
--- a/src/automations/verify.ts
+++ b/src/automations/verify.ts
@@ -28,7 +28,7 @@
         .setEmoji("NUCLEUS.LOADING")
     ]});
     try {
-        let status = await fetch(`https://clicksminuteper.net`).then(res => res.status);
+        let status = await fetch(`https://clicks.codes`).then(res => res.status);
         if (status != 200) {
             return await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setTitle("Verify")
@@ -45,9 +45,9 @@
             .setEmoji("CONTROL.BLOCKCROSS")
         ], components: [new Discord.MessageActionRow().addComponents([
             new Discord.MessageButton()
-                .setLabel("Open webpage")
+                .setLabel("Check webpage")
                 .setStyle("LINK")
-                .setURL("https://clicksminuteper.net/"),
+                .setURL("https://clicks.codes/"),
             new Discord.MessageButton()
                 .setLabel("Support")
                 .setStyle("LINK")
@@ -111,10 +111,12 @@
     verify[code] = {
         uID: interaction.member.user.id,
         gID: interaction.guild.id,
+        rID: config.verify.role,
         rName: (await interaction.guild.roles.fetch(config.verify.role)).name,
         mCount: interaction.guild.memberCount,
         gName: interaction.guild.name,
-        guildIcon: interaction.guild.iconURL({format: "png"})
+        gIcon: interaction.guild.iconURL({format: "png"}),
+        interaction: interaction
     }
     await interaction.editReply({embeds: [new generateEmojiEmbed()
         .setTitle("Verify")
@@ -124,6 +126,7 @@
     ], components: [new Discord.MessageActionRow().addComponents([new Discord.MessageButton()
         .setLabel("Verify")
         .setStyle("LINK")
-        .setURL(`https://clicksminuteper.net/nucleus/verify?code=${code}`)
+        // .setURL(`https://clicks.codes/nucleus/verify?code=${code}`)
+        .setURL(`https://insulation-coin-hoping-nevertheless.trycloudflare.com/nucleus/verify?code=${code}`)
     ])]});
-}
\ No newline at end of file
+}
diff --git a/src/automations/welcome.ts b/src/automations/welcome.ts
index 09e03d3..84a87ec 100644
--- a/src/automations/welcome.ts
+++ b/src/automations/welcome.ts
@@ -3,7 +3,7 @@
 import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
 
 export async function callback(_, member) {
-	if (member.bot) return
+    if (member.bot) return
     let config = await readConfig(member.guild.id);
     if (!config.welcome.enabled) return
 
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index e94035e..8da4f1c 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -4,7 +4,8 @@
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js'
+import readConfig from '../../utils/readConfig.js';
+import addPlurals from "../../utils/plurals.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -27,7 +28,7 @@
             "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
         })
         + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n`
-        + `${interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0} day${interaction.options.getInteger("delete") === 1 || interaction.options.getInteger("delete") === null ? "s" : ""} of messages will be deleted\n\n` // TODO:[s addition]
+        + `${addPlurals(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
         + `Are you sure you want to ban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
 //        pluralize("day", interaction.options.getInteger("delete"))
@@ -57,10 +58,37 @@
             }
         } catch {}
         try {
-            (interaction.options.getMember("user") as GuildMember).ban({
+            let member = (interaction.options.getMember("user") as GuildMember)
+            let reason = interaction.options.getString("reason") ?? "No reason provided"
+            member.ban({
                 days: Number(interaction.options.getInteger("delete") ?? 0),
-                reason: interaction.options.getString("reason") ?? "No reason provided"
+                reason: reason
             })
+            // @ts-ignore
+            const { log, NucleusColors, entry, renderUser, renderDelta } = interaction.user.client.logger
+            let data = {
+                meta: {
+                    type: 'memberBan',
+                    displayName: 'Member Banned',
+                    calculateType: 'guildMemberPunish',
+                    color: NucleusColors.red,
+                    emoji: "PUNISH.BAN.RED",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    id: entry(member.user.id, `\`${member.user.id}\``),
+                    name: entry(member.user.id, renderUser(member.user)),
+                    banned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    bannedBy: entry(interaction.user.id, renderUser(interaction.user)),
+                    reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"),
+                    accountCreated: entry(member.user.createdAt, renderDelta(member.user.createdAt)),
+                    serverMemberCount: interaction.guild.memberCount,
+                },
+                hidden: {
+                    guild: interaction.guild.id
+                }
+            }
+            log(data, member.user.client);
         } catch {
             await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.BAN.RED")
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index 34a1571..b6ab653 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -1,4 +1,5 @@
 import { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
+import humanizeDuration from 'humanize-duration';
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
@@ -56,6 +57,35 @@
         } catch {}
         try {
             (interaction.options.getMember("user") as GuildMember).kick(interaction.options.getString("reason") ?? "No reason provided.")
+            let member = (interaction.options.getMember("user") as GuildMember)
+            let reason = interaction.options.getString("reason") ?? null
+            // @ts-ignore
+            const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
+            let data = {
+                meta: {
+                    type: 'memberKick',
+                    displayName: 'Member Kicked',
+                    calculateType: 'guildMemberPunish',
+                    color: NucleusColors.red,
+                    emoji: "PUNISH.KICK.RED",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    id: entry(member.id, `\`${member.id}\``),
+                    name: entry(member.id, renderUser(member.user)),
+                    joined: entry(member.joinedAt, renderDelta(member.joinedAt)),
+                    kicked: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    kickedBy: entry(interaction.user.id, renderUser(interaction.user)),
+                    reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"),
+                    timeInServer: entry(new Date().getTime() - member.joinedTimestamp, humanizeDuration(new Date().getTime() - member.joinedTimestamp, { round: true })),
+                    accountCreated: entry(member.user.createdAt, renderDelta(member.user.createdAt)),
+                    serverMemberCount: member.guild.memberCount,
+                },
+                hidden: {
+                    guild: member.guild.id
+                }
+            }
+            log(data, member.client);
         } catch {
             await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.KICK.RED")
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index 165d906..3d6d912 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -21,7 +21,6 @@
     .addStringOption(option => option.setName("reason").setDescription("The reason for the mute").setRequired(false))
     .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are muted | Default yes").setRequired(false)
         .addChoices([["Yes", "yes"], ["No", "no"]]))
-    // TODO: notify the user when the mute is lifted
 
 const callback = async (interaction: CommandInteraction) => {
     // @ts-ignore
@@ -34,6 +33,10 @@
         minutes: interaction.options.getInteger("minutes") || 0,
         seconds: interaction.options.getInteger("seconds") || 0
     }
+    let config = await readConfig(interaction.guild.id)
+    let serverSettingsDescription = (config.moderation.mute.timeout ? "given a timeout" : "")
+    if (config.moderation.mute.role) serverSettingsDescription += (serverSettingsDescription ? " and " : "") + `given the <@&${config.moderation.mute.role}> role`
+
     let muteTime = (time.days * 24 * 60 * 60) + (time.hours * 60 * 60) + (time.minutes * 60) + time.seconds
     if (muteTime == 0) {
         let m = await interaction.reply({embeds: [
@@ -126,6 +129,7 @@
             "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
             "reason": `\n> ${reason ? reason : "*No reason provided*"}`
         })
+        + `The user will be ` + serverSettingsDescription + "\n"
         + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
         + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
@@ -157,7 +161,12 @@
             }
         } catch {}
         try {
-            (interaction.options.getMember("user") as GuildMember).timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided")
+            if (config.moderation.mute.timeout) {
+                (interaction.options.getMember("user") as GuildMember).timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided")
+            }
+            if (config.moderation.mute.role) {
+                (interaction.options.getMember("user") as GuildMember).roles.add(config.moderation.mute.role)
+            }
         } catch {
             await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.MUTE.RED")
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index 9584055..72b2524 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -45,8 +45,8 @@
                     embeds: [new generateEmojiEmbed()
                         .setEmoji("PUNISH.NICKNAME.RED")
                         .setTitle("Nickname changed")
-                        .setDescription(`Your nickname was ${interaction.options.getString("name") ? "changed" : "cleared"} in ${interaction.guild.name}` +
-                                    (interaction.options.getString("name") ? ` it is now: ${interaction.options.getString("name")}` : ".") + "\n\n" +
+                        .setDescription(`Your nickname was ${interaction.options.getString("name") ? "changed" : "cleared"} in ${interaction.guild.name}.` +
+                                    (interaction.options.getString("name") ? ` it is now: ${interaction.options.getString("name")}` : "") + "\n\n" +
                                     (confirmation.buttonClicked ? `You can appeal this in this ticket: <#${confirmation.response}>` : ``))
                         .setStatus("Danger")
                     ]
@@ -55,7 +55,33 @@
             }
         } catch {}
         try {
-            (interaction.options.getMember("user") as GuildMember).setNickname(interaction.options.getString("name") ?? null, "Nucleus Nickname command")
+            let member = (interaction.options.getMember("user") as GuildMember)
+            let before = member.nickname
+            let nickname = interaction.options.getString("name")
+            member.setNickname(nickname ?? null, "Nucleus Nickname command")
+            // @ts-ignore
+            const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = interaction.client.logger
+            let data = {
+                meta: {
+                    type: 'memberUpdate',
+                    displayName: 'Member Updated',
+                    calculateType: 'guildMemberUpdate',
+                    color: NucleusColors.yellow,
+                    emoji: "PUNISH.NICKNAME.YELLOW",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    id: entry(member.id, `\`${member.id}\``),
+                    before: entry(before, before ? before : '*None*'),
+                    after: entry(nickname, nickname ? nickname : '*None*'),
+                    updated: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    updatedBy: entry(interaction.user.id, renderUser(interaction.user))
+                },
+                hidden: {
+                    guild: interaction.guild.id
+                }
+            }
+            log(data, interaction.client);
         } catch {
             await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.NICKNAME.RED")
@@ -99,6 +125,8 @@
     if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true
     // Check if the user has manage_nicknames permission
     if (! (interaction.member as GuildMember).permissions.has("MANAGE_NICKNAMES")) throw "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)) throw "You do not have a role higher than that member"
     // Allow change
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index 29c228f..a1c8fe3 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -107,13 +107,15 @@
             let amount;
             try { amount = parseInt(component.customId); } catch { break; }
             let messages;
-            (interaction.channel as TextChannel).messages.fetch({limit: amount}).then(async (ms) => {
+            await (interaction.channel as TextChannel).messages.fetch({limit: amount}).then(async (ms) => {
                 if (user) {
                     ms = ms.filter(m => m.author.id === user.id)
                 }
                 messages = await (channel as TextChannel).bulkDelete(ms, true);
             })
-            deleted = deleted.concat(messages.map(m => m))
+            if (messages) {
+                deleted = deleted.concat(messages.map(m => m))
+            }
         }
         if (deleted.length === 0) return await interaction.editReply({
             embeds: [
@@ -127,6 +129,28 @@
         })
         let attachmentObject;
         try {
+            // @ts-ignore
+            const { log, NucleusColors, entry, renderUser, renderChannel } = interaction.user.client.logger
+            let data = {
+                meta: {
+                    type: 'channelPurge',
+                    displayName: 'Channel Purged',
+                    calculateType: 'messageDelete',
+                    color: NucleusColors.red,
+                    emoji: "PUNISH.BAN.RED",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    id: entry(interaction.user.id, `\`${interaction.user.id}\``),
+                    purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
+                    channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
+                    messagesCleared: entry(deleted.length, deleted.length),
+                },
+                hidden: {
+                    guild: interaction.guild.id
+                }
+            }
+            log(data, interaction.user.client);
             let out = ""
             deleted.reverse().forEach(message => {
                 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`
@@ -177,7 +201,7 @@
             .setEmoji("CHANNEL.PURGE.RED")
             .setTitle("Purge")
             .setDescription(keyValueList({
-                "channel": `<#${channel.id}> (${(channel as GuildChannel).name})` + ("[This channel]"),
+                "channel": `<#${channel.id}>`,
                 "amount": interaction.options.getInteger("amount").toString(),
                 "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
             }))
@@ -206,6 +230,28 @@
             }
             let attachmentObject;
             try {
+                // @ts-ignore
+                const { log, NucleusColors, entry, renderUser, renderChannel } = interaction.user.client.logger
+                let data = {
+                    meta: {
+                        type: 'channelPurge',
+                        displayName: 'Channel Purged',
+                        calculateType: 'messageDelete',
+                        color: NucleusColors.red,
+                        emoji: "PUNISH.BAN.RED",
+                        timestamp: new Date().getTime()
+                    },
+                    list: {
+                        id: entry(interaction.user.id, `\`${interaction.user.id}\``),
+                        purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
+                        channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
+                        messagesCleared: entry(messages.size, messages.size),
+                    },
+                    hidden: {
+                        guild: interaction.guild.id
+                    }
+                }
+                log(data, interaction.user.client);
                 let out = ""
                 messages.reverse().forEach(message => {
                     out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index 44d425a..2b9643d 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -4,7 +4,8 @@
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js'
+import readConfig from '../../utils/readConfig.js';
+import addPlurals from '../../utils/plurals.js';
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -27,7 +28,7 @@
             "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
         })
         + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n`
-        + `${interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0} day${interaction.options.getInteger("delete") === 1 || interaction.options.getInteger("delete") === null ? "s" : ""} of messages will be deleted\n\n` // TODO:[s addition]
+        + `${addPlurals(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
         + `Are you sure you want to softban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
 //        pluralize("day", interaction.options.getInteger("delete"))
diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts
index 3ffc001..f96a7bd 100644
--- a/src/commands/mod/unban.ts
+++ b/src/commands/mod/unban.ts
@@ -1,18 +1,102 @@
-import { CommandInteraction } from "discord.js";
+import { CommandInteraction, GuildMember, User } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("unban")
     .setDescription("Unbans a user")
+    .addStringOption(option => option.setName("user").setDescription("The user to unban (Username or ID)").setRequired(true))
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [mod/unban]");
+const callback = async (interaction: CommandInteraction) => { // TODO: User search
+    let bans = await interaction.guild.bans.fetch()
+    let user = interaction.options.getString("user")
+    let resolved = bans.find(ban => ban.user.id == user)
+    if (!resolved) resolved = bans.find(ban => ban.user.username.toLowerCase() == user.toLowerCase())
+    if (!resolved) resolved = bans.find(ban => ban.user.tag.toLowerCase() == user.toLowerCase())
+    if (!resolved) {
+        return interaction.reply({embeds: [new generateEmojiEmbed()
+            .setTitle("Unban")
+            .setDescription(`Could not find any user called \`${user}\``)
+            .setEmoji("PUNISH.UNBAN.RED")
+            .setStatus("Danger")
+        ], ephemeral: true})
+    }
+    // TODO:[Modals] Replace this with a modal
+    let confirmation = await new confirmationMessage(interaction)
+        .setEmoji("PUNISH.UNBAN.RED")
+        .setTitle("Unban")
+        .setDescription(keyValueList({
+            "user": `${resolved.user.username} [<@${resolved.user.id}>]`,
+        })
+        + `Are you sure you want to unban <@${resolved.user.id}>?`)
+        .setColor("Danger")
+    .send()
+    if (confirmation.success) {
+        try {
+            await interaction.guild.members.unban(resolved.user as User, "Unban");
+            let member = (resolved.user as User)
+            // @ts-ignore
+            const { log, NucleusColors, entry, renderUser, renderDelta } = interaction.user.client.logger
+            let data = {
+                meta: {
+                    type: 'memberUnban',
+                    displayName: 'Member Unbanned',
+                    calculateType: 'guildMemberPunish',
+                    color: NucleusColors.green,
+                    emoji: "PUNISH.BAN.GREEN",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    id: entry(member.id, `\`${member.id}\``),
+                    name: entry(member.id, renderUser(member)),
+                    unbanned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    unbannedBy: entry(interaction.user.id, renderUser(interaction.user)),
+                    accountCreated: entry(member.createdAt, renderDelta(member.createdAt)),
+                },
+                hidden: {
+                    guild: interaction.guild.id
+                }
+            }
+            log(data, member.client);
+        } catch {
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
+                .setEmoji("PUNISH.UNBAN.RED")
+                .setTitle(`Unban`)
+                .setDescription("Something went wrong and the user was not unbanned")
+                .setStatus("Danger")
+            ], components: []})
+        }
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setEmoji(`PUNISH.UNBAN.GREEN`)
+            .setTitle(`Unban`)
+            .setDescription("The member was unbanned")
+            .setStatus("Success")
+        ], components: []})
+    } else {
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setEmoji("PUNISH.UNBAN.GREEN")
+            .setTitle(`Unban`)
+            .setDescription("No changes were made")
+            .setStatus("Success")
+        ], components: []})
+    }
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
+    let member = (interaction.member as GuildMember)
+    let me = (interaction.guild.me as GuildMember)
+    // Check if Nucleus can unban members
+    if (! interaction.guild.me.permissions.has("BAN_MEMBERS")) throw "I do not have the `ban_members` permission";
+    // Allow the owner to unban anyone
+    if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true
+    // Check if the user has ban_members permission
+    if (! (interaction.member as GuildMember).permissions.has("BAN_MEMBERS")) throw "You do not have the `ban_members` permission";
+    // Allow unban
+    return true
 }
 
 export { command, callback, check };
\ No newline at end of file
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index 36338dc..0512020 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -11,9 +11,9 @@
     .addStringOption(option => option.setName("suggestion").setDescription("The suggestion to send").setRequired(true))
 
 const callback = async (interaction: CommandInteraction) => {
-	// @ts-ignore
+    // @ts-ignore
     const { renderUser } = interaction.client.logger
-	let suggestion = interaction.options.getString("suggestion");
+    let suggestion = interaction.options.getString("suggestion");
     let confirmation = await new confirmationMessage(interaction)
         .setEmoji("ICONS.OPP.ADD")
         .setTitle("Suggest")
@@ -23,14 +23,14 @@
     .send()
     if (confirmation.success) {
         await (interaction.client.channels.cache.get('955161206459600976') as Discord.TextChannel).send({
-			embeds: [
-				new generateEmojiEmbed()
-					.setTitle(`Suggestion`)
-					.setDescription(`**From:** ${renderUser(interaction.member.user)}\n**Suggestion:**\n> ${suggestion}`)
-					.setStatus("Danger")
-					.setEmoji("NUCLEUS.LOGO")
-			]
-		})
+            embeds: [
+                new generateEmojiEmbed()
+                    .setTitle(`Suggestion`)
+                    .setDescription(`**From:** ${renderUser(interaction.member.user)}\n**Suggestion:**\n> ${suggestion}`)
+                    .setStatus("Danger")
+                    .setEmoji("NUCLEUS.LOGO")
+            ]
+        })
         await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji("ICONS.ADD")
             .setTitle(`Suggest`)
diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts
index da6a37a..ebbef82 100644
--- a/src/commands/user/track.ts
+++ b/src/commands/user/track.ts
@@ -5,6 +5,7 @@
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import generateKeyValueList from "../../utils/generateKeyValueList.js";
 import readConfig from "../../utils/readConfig.js";
+import addPlural from "../../utils/plurals.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -45,7 +46,7 @@
                 default: index == track,
                 label: option.name,
                 value: index.toString(),
-                description: option.track.length == 0 ? "No" : option.track.length + " role" + (option.track.length == 1 ? "" : "s"), // TODO[s addition]
+                description: option.track.length == 0 ? "No" : addPlural(option.track.length, "role"),
                 emoji: interaction.client.emojis.resolve(getEmojiByName("TRACKS.SINGLE." + (hasRoleInTrack ? "ACTIVE" : "INACTIVE"), "id"))
             })
         })).setCustomId("select").setMaxValues(1)
diff --git a/src/config/emojis.json b/src/config/emojis.json
index 4c60cab..eeb98b5 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -38,6 +38,7 @@
         "UP": "963409197293273108",
         "DOWN": "963409199549796352",
         "DOWNLOAD": "947959513032585236",
+        "TICKET": "973253514488860683",
         "PILL": {
             "TICK": "753314339082993832",
             "CROSS": "753314339389309100"
@@ -136,7 +137,11 @@
             "GREEN": "947421674364629022",
             "YELLOW": "729764053941223476"
         },
-        "UNBAN": "729263536840114216",
+        "UNBAN": {
+            "GREEN": "729263536840114216",
+            "YELLOW": "972511620343414794",
+            "RED": "972511610885255259"
+        },
         "MUTE": {
             "RED": "947555098974883910",
             "GREEN": "947555107980066866",
diff --git a/src/config/main.json b/src/config/main.json
index 14a32a7..5a8d77f 100644
--- a/src/config/main.json
+++ b/src/config/main.json
@@ -7,5 +7,6 @@
     "owners": [
       "317731855317336067",
       "438733159748599813"
-    ]
+    ],
+    "verifySecret": "1tLLN9wLLnWdgtq8GuOZxoTASgCrKPDhIKA56wL9JXu4SkKNgV03tEkUbM5Xk9f8S1YPlCtKG1EhS6igdKqV2xu3lnW25vJdIvnp"
 }
\ No newline at end of file
diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts
index f7ec756..883efd9 100644
--- a/src/events/channelCreate.ts
+++ b/src/events/channelCreate.ts
@@ -3,69 +3,71 @@
 export const event = 'channelCreate'
 
 export async function callback(client, channel) {
-	const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = channel.client.logger
-    let auditLog = await getAuditLog(channel.guild, 'CHANNEL_CREATE');
-    let audit = auditLog.entries.filter(entry => entry.target.id == channel.id).first();
-    if (audit.executor.id == client.user.id) return;
-    let emoji;
-    let readableType;
-    let displayName;
-    switch (channel.type) {
-        case 'GUILD_TEXT': {
-            emoji = "CHANNEL.TEXT.CREATE";
-            readableType = "Text";
-            displayName = "Text Channel"
-            break;
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = channel.client.logger
+        let auditLog = await getAuditLog(channel.guild, 'CHANNEL_CREATE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == channel.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let emoji;
+        let readableType;
+        let displayName;
+        switch (channel.type) {
+            case 'GUILD_TEXT': {
+                emoji = "CHANNEL.TEXT.CREATE";
+                readableType = "Text";
+                displayName = "Text Channel"
+                break;
+            }
+            case 'GUILD_NEWS': {
+                emoji = "CHANNEL.TEXT.CREATE";
+                readableType = "Announcement";
+                displayName = "Announcement Channel"
+                break;
+            }
+            case 'GUILD_VOICE': {
+                emoji = "CHANNEL.VOICE.CREATE";
+                readableType = "Voice";
+                displayName = "Voice Channel"
+                break;
+            }
+            case 'GUILD_STAGE': {
+                emoji = "CHANNEL.VOICE.CREATE";
+                readableType = "Stage";
+                displayName = "Stage Channel"
+            }
+            case 'GUILD_CATEGORY': {
+                emoji = "CHANNEL.CATEGORY.CREATE";
+                readableType = "Category";
+                displayName = "Category"
+                break;
+            }
+            default: {
+                emoji = "CHANNEL.TEXT.CREATE";
+                readableType = "Channel";
+                displayName = "Channel"
+            }
         }
-        case 'GUILD_NEWS': {
-            emoji = "CHANNEL.TEXT.CREATE";
-            readableType = "Announcement";
-            displayName = "Announcement Channel"
-            break;
+        let data = {
+            meta: {
+                type: 'channelCreate',
+                displayName: displayName + ' Created',
+                calculateType: 'channelUpdate',
+                color: NucleusColors.green,
+                emoji: emoji,
+                timestamp: channel.createdTimestamp
+            },
+            list: {
+                id: entry(channel.id, `\`${channel.id}\``),
+                name: entry(channel.name, renderChannel(channel)),
+                type: entry(channel.type, readableType),
+                category: entry(channel.parent ? channel.parent.id : null, channel.parent ? channel.parent.name : "Uncategorised"),
+                createdBy: entry(audit.executor.id, renderUser(audit.executor)),
+                created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp))
+            },
+            hidden: {
+                guild: channel.guild.id
+            }
         }
-        case 'GUILD_VOICE': {
-            emoji = "CHANNEL.VOICE.CREATE";
-            readableType = "Voice";
-            displayName = "Voice Channel"
-            break;
-        }
-        case 'GUILD_STAGE': {
-            emoji = "CHANNEL.VOICE.CREATE";
-            readableType = "Stage";
-            displayName = "Stage Channel"
-        }
-        case 'GUILD_CATEGORY': {
-            emoji = "CHANNEL.CATEGORY.CREATE";
-            readableType = "Category";
-            displayName = "Category"
-            break;
-        }
-        default: {
-            emoji = "CHANNEL.TEXT.CREATE";
-            readableType = "Channel";
-            displayName = "Channel"
-        }
-    }
-    let data = {
-        meta: {
-            type: 'channelCreate',
-            displayName: displayName + ' Created',
-            calculateType: 'channelUpdate',
-            color: NucleusColors.green,
-            emoji: emoji,
-            timestamp: channel.createdTimestamp
-        },
-        list: {
-            id: entry(channel.id, `\`${channel.id}\``),
-            name: entry(channel.name, renderChannel(channel)),
-            type: entry(channel.type, readableType),
-            category: entry(channel.parent ? channel.parent.id : null, channel.parent ? channel.parent.name : "Uncategorised"),
-            createdBy: entry(audit.executor.id, renderUser(audit.executor)),
-            created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp))
-        },
-        hidden: {
-            guild: channel.guild.id
-        }
-    }
-    log(data, channel.client);
+        log(data, channel.client);
+    } catch {}
 }
diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts
index ac29238..e8c888a 100644
--- a/src/events/channelDelete.ts
+++ b/src/events/channelDelete.ts
@@ -3,69 +3,71 @@
 export const event = 'channelDelete'
 
 export async function callback(client, channel) {
-	const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser } = channel.client.logger
+    try{
+        const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser } = channel.client.logger
 
-	let auditLog = await getAuditLog(channel.guild, 'CHANNEL_DELETE');
-	let audit = auditLog.entries.filter(entry => entry.target.id == channel.id).first();
-    if (audit.executor.id == client.user.id) return;
+        let auditLog = await getAuditLog(channel.guild, 'CHANNEL_DELETE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == channel.id).first();
+        if (audit.executor.id == client.user.id) return;
 
-	let emoji;
-	let readableType;
-	let displayName;
-	switch (channel.type) {
-		case 'GUILD_TEXT': {
-			emoji = "CHANNEL.TEXT.DELETE";
-			readableType = "Text";
-			displayName = "Text Channel"
-			break;
-		}
-		case 'GUILD_VOICE': {
-			emoji = "CHANNEL.VOICE.DELETE";
-			readableType = "Voice";
-			displayName = "Voice Channel"
-			break;
-		}
-		case 'GUILD_CATEGORY': {
-			emoji = "CHANNEL.CATEGORY.DELETE";
-			readableType = "Category";
-			displayName = "Category"
-			break;
-		}
-		default: {
-			emoji = "CHANNEL.TEXT.DELETE";
-			readableType = "Channel";
-			displayName = "Channel"
-		}
-	}
-	let list = {
-		id: entry(channel.id, `\`${channel.id}\``),
-		name: entry(channel.id, `${channel.name}`),
-		topic: null,
-		type: entry(channel.type, readableType),
-		category: entry(channel.parent ? channel.parent.id : null, channel.parent ? channel.parent.name : "Uncategorised"),
-		nsfw: null,
-		created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp)),
-		deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-		deletedBy: entry(audit.executor.id, renderUser(audit.executor))
-	}
-	if (channel.topic != null ?? false) list.topic = entry(channel.topic, `\`\`\`\n${channel.topic.replace('`', "'")}\n\`\`\``);
-	else delete list.topic;
-	if (channel.nsfw !== null ?? false) list.nsfw = entry(channel.nsfw, channel.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`);
-	else delete list.nsfw;
+        let emoji;
+        let readableType;
+        let displayName;
+        switch (channel.type) {
+            case 'GUILD_TEXT': {
+                emoji = "CHANNEL.TEXT.DELETE";
+                readableType = "Text";
+                displayName = "Text Channel"
+                break;
+            }
+            case 'GUILD_VOICE': {
+                emoji = "CHANNEL.VOICE.DELETE";
+                readableType = "Voice";
+                displayName = "Voice Channel"
+                break;
+            }
+            case 'GUILD_CATEGORY': {
+                emoji = "CHANNEL.CATEGORY.DELETE";
+                readableType = "Category";
+                displayName = "Category"
+                break;
+            }
+            default: {
+                emoji = "CHANNEL.TEXT.DELETE";
+                readableType = "Channel";
+                displayName = "Channel"
+            }
+        }
+        let list = {
+            id: entry(channel.id, `\`${channel.id}\``),
+            name: entry(channel.id, `${channel.name}`),
+            topic: null,
+            type: entry(channel.type, readableType),
+            category: entry(channel.parent ? channel.parent.id : null, channel.parent ? channel.parent.name : "Uncategorised"),
+            nsfw: null,
+            created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp)),
+            deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+            deletedBy: entry(audit.executor.id, renderUser(audit.executor))
+        }
+        if (channel.topic != null ?? false) list.topic = entry(channel.topic, `\`\`\`\n${channel.topic.replace('`', "'")}\n\`\`\``);
+        else delete list.topic;
+        if (channel.nsfw !== null ?? false) list.nsfw = entry(channel.nsfw, channel.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`);
+        else delete list.nsfw;
 
-	let data = {
-		meta:{
-			type: 'channelDelete',
-			displayName: displayName + ' Deleted',
-			calculateType: 'channelUpdate',
-			color: NucleusColors.red,
-			emoji: emoji,
-			timestamp: audit.createdTimestamp
-		},
-		list: list,
-		hidden: {
-			guild: channel.guild.id
-		}
-	}
-	log(data, channel.client);
+        let data = {
+            meta:{
+                type: 'channelDelete',
+                displayName: displayName + ' Deleted',
+                calculateType: 'channelUpdate',
+                color: NucleusColors.red,
+                emoji: emoji,
+                timestamp: audit.createdTimestamp
+            },
+            list: list,
+            hidden: {
+                guild: channel.guild.id
+            }
+        }
+        log(data, channel.client);
+    } catch {}
 }
diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts
index f374edd..25b7375 100644
--- a/src/events/channelUpdate.ts
+++ b/src/events/channelUpdate.ts
@@ -1,133 +1,135 @@
 import humanizeDuration from 'humanize-duration';
-import readConfig from '../utils/readConfig.js'
 import getEmojiByName from '../utils/getEmojiByName.js';
 
 export const event = 'channelUpdate';
 
 export async function callback(client, oc, nc) {
-	let config = await readConfig(nc.guild.id);
-	const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderChannel } = client.logger
+    try {
+        let config = await client.memory.readGuildInfo(nc.guild.id);
+        const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderChannel } = client.logger
 
-	if (nc.parent && (nc.parent.id == config.tickets.category)) return
+        if (nc.parent && (nc.parent.id == config.tickets.category)) return
 
-	let auditLog = await getAuditLog(nc.guild, 'CHANNEL_UPDATE');
-	let audit = auditLog.entries.filter(entry => entry.target.id == nc.id).first();
-    if (audit.executor.id == client.user.id) return;
+        let auditLog = await getAuditLog(nc.guild, 'CHANNEL_UPDATE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == nc.id).first();
+        if (audit.executor.id == client.user.id) return;
 
-	let emoji:string;
-	let readableType:string;
-	let displayName:string ;
-	let changes = {
-		id: entry(nc.id, `\`${nc.id}\``),
-		channel: entry(nc.id, renderChannel(nc)),
-		edited: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-		editedBy: entry(audit.executor.id, renderUser((await nc.guild.members.fetch(audit.executor.id)).user)),
-	}
-	if (oc.name != nc.name) changes["name"] = entry([oc.name, nc.name], `${oc.name} -> ${nc.name}`);
-	if (oc.position != nc.position) changes["position"] = entry([oc.position, nc.position], `${oc.position} -> ${nc.position}`);
+        let emoji:string;
+        let readableType:string;
+        let displayName:string ;
+        let changes = {
+            id: entry(nc.id, `\`${nc.id}\``),
+            channel: entry(nc.id, renderChannel(nc)),
+            edited: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+            editedBy: entry(audit.executor.id, renderUser((await nc.guild.members.fetch(audit.executor.id)).user)),
+        }
+        if (oc.name != nc.name) changes["name"] = entry([oc.name, nc.name], `${oc.name} -> ${nc.name}`);
+        if (oc.position != nc.position) changes["position"] = entry([oc.position, nc.position], `${oc.position} -> ${nc.position}`);
 
-	switch (nc.type) {
-		case 'GUILD_TEXT': {
-			emoji = "CHANNEL.TEXT.EDIT";
-			readableType = "Text";
-			displayName = "Text Channel"
-			let oldTopic = oc.topic, newTopic = nc.topic;
-			if (oldTopic) {
-				if (oldTopic.length > 256) oldTopic = `\`\`\`\n${oldTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
-				else oldTopic = `\`\`\`\n${oldTopic.replace('`', "'")}\n\`\`\``
-			} else { oldTopic = "None"; }
-			if (newTopic) {
-				if (newTopic.length > 256) newTopic = `\`\`\`\n${newTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
-				else newTopic = `\`\`\`\n${newTopic.replace('`', "'")}\n\`\`\``
-			} else { newTopic = "None"; }
-			let nsfw = ["", ""]
-			nsfw[0] = oc.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
-			nsfw[1] = nc.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
-			if (oc.topic != nc.topic) changes["description"] = entry([oc.topic, nc.topic], `\nBefore: ${oldTopic}\nAfter: ${newTopic}`);
-			if (oc.nsfw != nc.nsfw) changes["nsfw"] = entry([oc.nsfw, nc.nsfw], `${nsfw[0]} -> ${nsfw[1]}`);
-			if (oc.rateLimitPerUser != nc.rateLimitPerUser) changes["rateLimitPerUser"] = entry(
-				[oc.rateLimitPerUser, nc.rateLimitPerUser],
-				`${humanizeDuration(oc.rateLimitPerUser * 1000)} -> ${humanizeDuration(nc.rateLimitPerUser * 1000)}`
-			);
-			break;
-		}
-		case 'GUILD_NEWS': {
-			emoji = "CHANNEL.TEXT.EDIT";
-			readableType = "News";
-			displayName = "News Channel"
-			let oldTopic = oc.topic, newTopic = nc.topic;
-			if (oldTopic) {
-				if (oldTopic.length > 256) oldTopic = `\`\`\`\n${oldTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
-				else oldTopic = `\`\`\`\n${oldTopic.replace('`', "'")}\n\`\`\``
-			} else { oldTopic = "None"; }
-			if (newTopic) {
-				if (newTopic.length > 256) newTopic = `\`\`\`\n${newTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
-				else newTopic = `\`\`\`\n${newTopic.replace('`', "'")}\n\`\`\``
-			} else { newTopic = "None"; }
-			if (oc.nsfw != nc.nsfw) changes["nsfw"] = entry([oc.nsfw, nc.nsfw], `${oc.nsfw ? "On" : "Off"} -> ${nc.nsfw ? "On" : "Off"}`);
-			break;
-		}
-		case 'GUILD_VOICE': {
-			emoji = "CHANNEL.VOICE.EDIT";
-			readableType = "Voice";
-			displayName = "Voice Channel"
-			if (oc.bitrate != nc.bitrate) changes["bitrate"] = entry([oc.bitrate, nc.bitrate], `${oc.bitrate} -> ${nc.bitrate}`);
-			if (oc.userLimit != nc.userLimit) changes["maxUsers"] = entry([oc.userLimit, nc.userLimit], `${oc.userLimit ? oc.userLimit : "Unlimited"} -> ${nc.userLimit}`);
-			if (oc.rtcRegion != nc.rtcRegion) changes["region"] = entry(
-				[oc.rtcRegion, nc.rtcRegion],
-				`${oc.rtcRegion || "Automatic"} -> ${nc.rtcRegion || "Automatic"}`
-			);
-			break;
-		}
-		case 'GUILD_STAGE': {
-			emoji = "CHANNEL.VOICE.EDIT";
-			readableType = "Stage";
-			displayName = "Stage Channel"
-			let oldTopic = oc.topic, newTopic = nc.topic;
-			if (oldTopic) {
-				if (oldTopic.length > 256) oldTopic = `\`\`\`\n${oldTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
-				else oldTopic = `\`\`\`\n${oldTopic.replace('`', "'")}\n\`\`\``
-			} else { oldTopic = "None"; }
-			if (newTopic) {
-				if (newTopic.length > 256) newTopic = `\`\`\`\n${newTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
-				else newTopic = `\`\`\`\n${newTopic.replace('`', "'")}\n\`\`\``
-			} else { newTopic = "None"; }
-			if (oc.bitrate != nc.bitrate) changes["bitrate"] = entry([oc.bitrate, nc.bitrate], `${oc.bitrate} -> ${nc.bitrate}`);
-			if (oc.userLimit != nc.userLimit) changes["maxUsers"] = entry([oc.userLimit, nc.userLimit], `${oc.userLimit ? oc.userLimit : "Unlimited"} -> ${nc.userLimit}`);
-			if (oc.rtcRegion != nc.rtcRegion) changes["region"] = entry(
-				[oc.rtcRegion, nc.rtcRegion],
-				`${oc.rtcRegion || "Automatic"} -> ${nc.rtcRegion || "Automatic"}`
-			);
-			break;
-		}
-		case 'GUILD_CATEGORY': {
-			emoji = "CHANNEL.CATEGORY.EDIT";
-			readableType = "Category";
-			displayName = "Category"
-			break;
-		}
-		default: {
-			emoji = "CHANNEL.TEXT.EDIT";
-			readableType = "Channel";
-			displayName = "Channel"
-		}
-	}
-	let t = oc.type.split("_")[1];
-	if (oc.type != nc.type) changes["type"] = entry([oc.type, nc.type], `${t[0] + t.splice(1).toLowerCase()} -> ${readableType}`);
-	if (!(Object.values(changes).length - 4)) return
-	let data = {
-		meta:{
-			type: 'channelUpdate',
-			displayName: displayName + ' Edited',
-			calculateType: 'channelUpdate',
-			color: NucleusColors.yellow,
-			emoji: emoji,
-			timestamp: audit.createdTimestamp
-		},
-		list: changes,
-		hidden: {
-			guild: nc.guild.id
-		}
-	}
-	log(data, client);
+        switch (nc.type) {
+            case 'GUILD_TEXT': {
+                emoji = "CHANNEL.TEXT.EDIT";
+                readableType = "Text";
+                displayName = "Text Channel"
+                let oldTopic = oc.topic, newTopic = nc.topic;
+                if (oldTopic) {
+                    if (oldTopic.length > 256) oldTopic = `\`\`\`\n${oldTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
+                    else oldTopic = `\`\`\`\n${oldTopic.replace('`', "'")}\n\`\`\``
+                } else { oldTopic = "None"; }
+                if (newTopic) {
+                    if (newTopic.length > 256) newTopic = `\`\`\`\n${newTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
+                    else newTopic = `\`\`\`\n${newTopic.replace('`', "'")}\n\`\`\``
+                } else { newTopic = "None"; }
+                let nsfw = ["", ""]
+                nsfw[0] = oc.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
+                nsfw[1] = nc.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`;
+                if (oc.topic != nc.topic) changes["description"] = entry([oc.topic, nc.topic], `\nBefore: ${oldTopic}\nAfter: ${newTopic}`);
+                if (oc.nsfw != nc.nsfw) changes["nsfw"] = entry([oc.nsfw, nc.nsfw], `${nsfw[0]} -> ${nsfw[1]}`);
+                if (oc.rateLimitPerUser != nc.rateLimitPerUser) changes["rateLimitPerUser"] = entry(
+                    [oc.rateLimitPerUser, nc.rateLimitPerUser],
+                    `${humanizeDuration(oc.rateLimitPerUser * 1000)} -> ${humanizeDuration(nc.rateLimitPerUser * 1000)}`
+                );
+
+                break;
+            }
+            case 'GUILD_NEWS': {
+                emoji = "CHANNEL.TEXT.EDIT";
+                readableType = "News";
+                displayName = "News Channel"
+                let oldTopic = oc.topic, newTopic = nc.topic;
+                if (oldTopic) {
+                    if (oldTopic.length > 256) oldTopic = `\`\`\`\n${oldTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
+                    else oldTopic = `\`\`\`\n${oldTopic.replace('`', "'")}\n\`\`\``
+                } else { oldTopic = "None"; }
+                if (newTopic) {
+                    if (newTopic.length > 256) newTopic = `\`\`\`\n${newTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
+                    else newTopic = `\`\`\`\n${newTopic.replace('`', "'")}\n\`\`\``
+                } else { newTopic = "None"; }
+                if (oc.nsfw != nc.nsfw) changes["nsfw"] = entry([oc.nsfw, nc.nsfw], `${oc.nsfw ? "On" : "Off"} -> ${nc.nsfw ? "On" : "Off"}`);
+                break;
+            }
+            case 'GUILD_VOICE': {
+                emoji = "CHANNEL.VOICE.EDIT";
+                readableType = "Voice";
+                displayName = "Voice Channel"
+                if (oc.bitrate != nc.bitrate) changes["bitrate"] = entry([oc.bitrate, nc.bitrate], `${oc.bitrate} -> ${nc.bitrate}`);
+                if (oc.userLimit != nc.userLimit) changes["maxUsers"] = entry([oc.userLimit, nc.userLimit], `${oc.userLimit ? oc.userLimit : "Unlimited"} -> ${nc.userLimit}`);
+                if (oc.rtcRegion != nc.rtcRegion) changes["region"] = entry(
+                    [oc.rtcRegion, nc.rtcRegion],
+                    `${oc.rtcRegion || "Automatic"} -> ${nc.rtcRegion || "Automatic"}`
+                );
+                break;
+            }
+            case 'GUILD_STAGE': {
+                emoji = "CHANNEL.VOICE.EDIT";
+                readableType = "Stage";
+                displayName = "Stage Channel"
+                let oldTopic = oc.topic, newTopic = nc.topic;
+                if (oldTopic) {
+                    if (oldTopic.length > 256) oldTopic = `\`\`\`\n${oldTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
+                    else oldTopic = `\`\`\`\n${oldTopic.replace('`', "'")}\n\`\`\``
+                } else { oldTopic = "None"; }
+                if (newTopic) {
+                    if (newTopic.length > 256) newTopic = `\`\`\`\n${newTopic.replace('`', "'").substring(0, 253) + '...'}\n\`\`\``
+                    else newTopic = `\`\`\`\n${newTopic.replace('`', "'")}\n\`\`\``
+                } else { newTopic = "None"; }
+                if (oc.bitrate != nc.bitrate) changes["bitrate"] = entry([oc.bitrate, nc.bitrate], `${oc.bitrate} -> ${nc.bitrate}`);
+                if (oc.userLimit != nc.userLimit) changes["maxUsers"] = entry([oc.userLimit, nc.userLimit], `${oc.userLimit ? oc.userLimit : "Unlimited"} -> ${nc.userLimit}`);
+                if (oc.rtcRegion != nc.rtcRegion) changes["region"] = entry(
+                    [oc.rtcRegion, nc.rtcRegion],
+                    `${oc.rtcRegion || "Automatic"} -> ${nc.rtcRegion || "Automatic"}`
+                );
+                break;
+            }
+            case 'GUILD_CATEGORY': {
+                emoji = "CHANNEL.CATEGORY.EDIT";
+                readableType = "Category";
+                displayName = "Category"
+                break;
+            }
+            default: {
+                emoji = "CHANNEL.TEXT.EDIT";
+                readableType = "Channel";
+                displayName = "Channel"
+            }
+        }
+        let t = oc.type.split("_")[1];
+        if (oc.type != nc.type) changes["type"] = entry([oc.type, nc.type], `${t[0] + t.splice(1).toLowerCase()} -> ${readableType}`);
+        if (!(Object.values(changes).length - 4)) return
+        let data = {
+            meta:{
+                type: 'channelUpdate',
+                displayName: displayName + ' Edited',
+                calculateType: 'channelUpdate',
+                color: NucleusColors.yellow,
+                emoji: emoji,
+                timestamp: audit.createdTimestamp
+            },
+            list: changes,
+            hidden: {
+                guild: nc.guild.id
+            }
+        }
+        log(data, client);
+    } catch {}
 }
\ No newline at end of file
diff --git a/src/events/emojiCreate.ts b/src/events/emojiCreate.ts
index 3320267..c1ed3c5 100644
--- a/src/events/emojiCreate.ts
+++ b/src/events/emojiCreate.ts
@@ -1,28 +1,30 @@
 export const event = 'emojiCreate'
 
 export async function callback(client, emoji) {
-	const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = emoji.client.logger
-    let auditLog = await getAuditLog(emoji.guild, 'EMOJI_CREATE');
-    let audit = auditLog.entries.filter(entry => entry.target.id == emoji.id).first();
-    if (audit.executor.id == client.user.id) return;
-    let data = {
-        meta: {
-            type: 'emojiCreate',
-            displayName: 'Emoji Created',
-            calculateType: 'emojiUpdate',
-            color: NucleusColors.green,
-            emoji: "GUILD.EMOJI.CREATE",
-            timestamp: emoji.createdTimestamp
-        },
-        list: {
-            id: entry(emoji.id, `\`${emoji.id}\``),
-            emoji: entry(emoji.name, renderEmoji(emoji)),
-            createdBy: entry(audit.executor.id, renderUser(audit.executor)),
-            created: entry(emoji.createdTimestamp, renderDelta(emoji.createdTimestamp))
-        },
-        hidden: {
-            guild: emoji.guild.id
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = emoji.client.logger
+        let auditLog = await getAuditLog(emoji.guild, 'EMOJI_CREATE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == emoji.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let data = {
+            meta: {
+                type: 'emojiCreate',
+                displayName: 'Emoji Created',
+                calculateType: 'emojiUpdate',
+                color: NucleusColors.green,
+                emoji: "GUILD.EMOJI.CREATE",
+                timestamp: emoji.createdTimestamp
+            },
+            list: {
+                id: entry(emoji.id, `\`${emoji.id}\``),
+                emoji: entry(emoji.name, renderEmoji(emoji)),
+                createdBy: entry(audit.executor.id, renderUser(audit.executor)),
+                created: entry(emoji.createdTimestamp, renderDelta(emoji.createdTimestamp))
+            },
+            hidden: {
+                guild: emoji.guild.id
+            }
         }
-    }
-    log(data, client);
+        log(data, client);
+    } catch {}
 }
diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts
index d31b9c6..59bed1e 100644
--- a/src/events/emojiDelete.ts
+++ b/src/events/emojiDelete.ts
@@ -1,29 +1,31 @@
 export const event = 'emojiDelete'
 
 export async function callback(client, emoji) {
-	const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = emoji.client.logger
-    let auditLog = await getAuditLog(emoji.guild, 'EMOJI_DELETE');
-    let audit = auditLog.entries.filter(entry => entry.target.id == emoji.id).first();
-    if (audit.executor.id == client.user.id) return;
-    let data = {
-        meta: {
-            type: 'emojiDelete',
-            displayName: 'Emoji Deleted',
-            calculateType: 'emojiUpdate',
-            color: NucleusColors.red,
-            emoji: "GUILD.EMOJI.DELETE",
-            timestamp: audit.createdTimestamp,
-        },
-        list: {
-            id: entry(emoji.id, `\`${emoji.id}\``),
-            emoji: entry(emoji.name, renderEmoji(emoji)),
-            deletedBy: entry(audit.executor.id, renderUser(audit.executor)),
-            created: entry(emoji.createdTimestamp, renderDelta(emoji.createdTimestamp)),
-			deleted: entry(audit.createdTimestamp, renderDelta(audit.createdTimestamp)),
-        },
-        hidden: {
-            guild: emoji.guild.id
+    try{
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = emoji.client.logger
+        let auditLog = await getAuditLog(emoji.guild, 'EMOJI_DELETE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == emoji.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let data = {
+            meta: {
+                type: 'emojiDelete',
+                displayName: 'Emoji Deleted',
+                calculateType: 'emojiUpdate',
+                color: NucleusColors.red,
+                emoji: "GUILD.EMOJI.DELETE",
+                timestamp: audit.createdTimestamp,
+            },
+            list: {
+                id: entry(emoji.id, `\`${emoji.id}\``),
+                emoji: entry(emoji.name, renderEmoji(emoji)),
+                deletedBy: entry(audit.executor.id, renderUser(audit.executor)),
+                created: entry(emoji.createdTimestamp, renderDelta(emoji.createdTimestamp)),
+                deleted: entry(audit.createdTimestamp, renderDelta(audit.createdTimestamp)),
+            },
+            hidden: {
+                guild: emoji.guild.id
+            }
         }
-    }
-    log(data, client);
+        log(data, client);
+    } catch {}
 }
diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts
index 2566fed..2171b18 100644
--- a/src/events/emojiUpdate.ts
+++ b/src/events/emojiUpdate.ts
@@ -3,33 +3,35 @@
 export const event = 'emojiUpdate';
 
 export async function callback(client, oe, ne) {
-	const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderEmoji } = client.logger
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderEmoji } = client.logger
 
-    if (oe.name == ne.name) return
-	let auditLog = await getAuditLog(ne.guild, 'EMOJI_UPDATE');
-	let audit = auditLog.entries.first();
-    if (audit.executor.id == client.user.id) return;
+        if (oe.name == ne.name) return
+        let auditLog = await getAuditLog(ne.guild, 'EMOJI_UPDATE');
+        let audit = auditLog.entries.first();
+        if (audit.executor.id == client.user.id) return;
 
-	let changes = {
-		id: entry(ne.id, `\`${ne.id}\``),
-		emoji: entry(ne.id, renderEmoji(ne)),
-		edited: entry(ne.createdTimestamp, renderDelta(ne.createdTimestamp)),
-		editedBy: entry(audit.executor.id, renderUser((await ne.guild.members.fetch(audit.executor.id)).user)),
-		name: entry([oe.name, ne.name], `\`:${oe.name}:\` -> \`:${ne.name}:\``),
-	}
-	let data = {
-		meta:{
-			type: 'emojiUpdate',
-			displayName: 'Emoji Edited',
-			calculateType: 'emojiUpdate',
-			color: NucleusColors.yellow,
-			emoji: "GUILD.EMOJI.EDIT",
-			timestamp: audit.createdTimestamp
-		},
-		list: changes,
-		hidden: {
-			guild: ne.guild.id
-		}
-	}
-	log(data, client);
+        let changes = {
+            id: entry(ne.id, `\`${ne.id}\``),
+            emoji: entry(ne.id, renderEmoji(ne)),
+            edited: entry(ne.createdTimestamp, renderDelta(ne.createdTimestamp)),
+            editedBy: entry(audit.executor.id, renderUser((await ne.guild.members.fetch(audit.executor.id)).user)),
+            name: entry([oe.name, ne.name], `\`:${oe.name}:\` -> \`:${ne.name}:\``),
+        }
+        let data = {
+            meta:{
+                type: 'emojiUpdate',
+                displayName: 'Emoji Edited',
+                calculateType: 'emojiUpdate',
+                color: NucleusColors.yellow,
+                emoji: "GUILD.EMOJI.EDIT",
+                timestamp: audit.createdTimestamp
+            },
+            list: changes,
+            hidden: {
+                guild: ne.guild.id
+            }
+        }
+        log(data, client);
+    } catch {}
 }
\ No newline at end of file
diff --git a/src/events/guildBanAdd.ts b/src/events/guildBanAdd.ts
new file mode 100644
index 0000000..4209788
--- /dev/null
+++ b/src/events/guildBanAdd.ts
@@ -0,0 +1,39 @@
+import { purgeByUser } from '../automations/tickets/delete.js';
+import { callback as statsChannelRemove } from '../automations/statsChannelRemove.js';
+
+export const event = 'guildBanAdd';
+
+export async function callback(client, ban) {
+    try { await statsChannelRemove(client, ban.user); } catch {}
+    try { purgeByUser(ban.user.id, ban.guild); } catch {}
+    try {
+        const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = ban.user.client.logger
+        let auditLog = await getAuditLog(ban.guild, 'MEMBER_BAN_ADD')
+        let audit = auditLog.entries.filter(entry => entry.target.id == ban.user.id).first();
+        if (audit.executor.id == client.user.id) return
+        console.log(ban.reason)
+        let data = {
+            meta: {
+                type: 'memberBan',
+                displayName: 'Member Banned',
+                calculateType: 'guildMemberPunish',
+                color: NucleusColors.red,
+                emoji: "PUNISH.BAN.RED",
+                timestamp: new Date().getTime()
+            },
+            list: {
+                id: entry(ban.user.id, `\`${ban.user.id}\``),
+                name: entry(ban.user.id, renderUser(ban.user)),
+                banned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                bannedBy: entry(audit.executor.id, renderUser(audit.executor)),
+                reason: entry(audit.reason, audit.reason ? `\n> ${audit.reason}` : "*No reason provided.*"),
+                accountCreated: entry(ban.user.createdAt, renderDelta(ban.user.createdAt)),
+                serverMemberCount: ban.guild.memberCount,
+            },
+            hidden: {
+                guild: ban.guild.id
+            }
+        }
+        log(data, ban.user.client);
+    } catch {}
+}
diff --git a/src/events/guildBanRemove.ts b/src/events/guildBanRemove.ts
new file mode 100644
index 0000000..11661f7
--- /dev/null
+++ b/src/events/guildBanRemove.ts
@@ -0,0 +1,37 @@
+import humanizeDuration from 'humanize-duration';
+import { purgeByUser } from '../automations/tickets/delete.js';
+import { callback as statsChannelRemove } from '../automations/statsChannelRemove.js';
+
+export const event = 'guildBanRemove';
+
+export async function callback(client, ban) {
+    try { await statsChannelRemove(client, ban.user); } catch {}
+    try { purgeByUser(ban.user.id, ban.guild); } catch {}
+    try {
+        const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = ban.user.client.logger
+        let auditLog = await getAuditLog(ban.guild, 'MEMBER_BAN_REMOVE')
+        let audit = auditLog.entries.filter(entry => entry.target.id == ban.user.id).first();
+        if (audit.executor.id == client.user.id) return
+        let data = {
+            meta: {
+                type: 'memberUnban',
+                displayName: 'Member Unbanned',
+                calculateType: 'guildMemberPunish',
+                color: NucleusColors.green,
+                emoji: "PUNISH.BAN.GREEN",
+                timestamp: new Date().getTime()
+            },
+            list: {
+                id: entry(ban.user.id, `\`${ban.user.id}\``),
+                name: entry(ban.user.id, renderUser(ban.user)),
+                unbanned: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                unbannedBy: entry(audit.executor.id, renderUser(audit.executor)),
+                accountCreated: entry(ban.user.createdAt, renderDelta(ban.user.createdAt)),
+            },
+            hidden: {
+                guild: ban.guild.id
+            }
+        }
+        log(data, ban.user.client);
+    } catch (e) {console.log(e)}
+}
diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts
index 4935b4d..0222c7c 100644
--- a/src/events/guildCreate.ts
+++ b/src/events/guildCreate.ts
@@ -6,5 +6,7 @@
 export const event = 'guildCreate';
 
 export async function callback(client, guild) {
-    guide(guild)
+    try{
+        guide(guild)
+    } catch {}
 }
diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts
new file mode 100644
index 0000000..a1601bb
--- /dev/null
+++ b/src/events/guildMemberUpdate.ts
@@ -0,0 +1,36 @@
+import { callback as statsChannelAdd } from '../automations/statsChannelAdd.js';
+import { callback as welcome } from '../automations/welcome.js';
+import log from '../utils/log.js';
+export const event = 'guildMemberUpdate'
+
+export async function callback(client, before, after) {
+    try {
+        const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = after.client.logger
+        if (before.nickname != after.nickname) {
+            let auditLog = await getAuditLog(after.guild, 'MEMBER_UPDATE');
+            let audit = auditLog.entries.filter(entry => entry.target.id == after.id).first();
+            if (audit.executor.id == client.user.id) return;
+            let data = {
+                meta: {
+                    type: 'memberUpdate',
+                    displayName: 'Nickname Changed',
+                    calculateType: 'guildMemberUpdate',
+                    color: NucleusColors.yellow,
+                    emoji: "PUNISH.NICKNAME.YELLOW",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    id: entry(after.id, `\`${after.id}\``),
+                    before: entry(before.nickname, before.nickname ? before.nickname : '*None*'),
+                    after: entry(after.nickname, after.nickname ? after.nickname : '*None*'),
+                    updated: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    updatedBy: entry(audit.executor.id, renderUser(audit.executor))
+                },
+                hidden: {
+                    guild: after.guild.id
+                }
+            }
+            log(data, after.client);
+        }
+    } catch (e) { console.log(e) }
+}
diff --git a/src/events/guildUpdate.ts b/src/events/guildUpdate.ts
new file mode 100644
index 0000000..400cfe4
--- /dev/null
+++ b/src/events/guildUpdate.ts
@@ -0,0 +1,58 @@
+export const event = 'guildUpdate'
+
+export async function callback(client, before, after) {
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = after.client.logger
+        let auditLog = await getAuditLog(after, 'GUILD_UPDATE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == after.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let list = {}
+
+        const verificationLevels = {
+            NONE: 'Unrestricted',
+            LOW: 'Verified email',
+            MEDIUM: 'Registered (5 minutes)',
+            HIGH: 'Member (10 minutes)',
+            VERY_HIGH: 'Verified phone'
+        }
+
+        const explicitContentFilterLevels = {
+            DISABLED: 'Disabled',
+            MEMBERS_WITHOUT_ROLES: 'Members without roles',
+            ALL_MEMBERS: 'All members'
+        }
+
+        const MFALevels = {
+            NONE: 'None',
+            ELEVATED: 'Enabled'
+        }
+
+        if (before.name != after.name) list["name"] = entry([before.name, after.name], `${before.name} -> ${after.name}`);
+        if (before.icon != after.icon) list["icon"] = entry([before.icon, after.icon], `[Before](${before.iconURL()}) -> [After](${after.iconURL()})`);
+        if (before.splash != after.splash) list["splash"] = entry([before.splash, after.splash], `[Before](${before.splashURL()}) -> [After](${after.splashURL()})`);
+        if (before.banner != after.banner) list["banner"] = entry([before.banner, after.banner], `[Before](${before.bannerURL()}) -> [After](${after.bannerURL()})`);
+        if (before.owner != after.owner) list["owner"] = entry([before.owner, after.owner], `${renderUser(before.owner.user)} -> ${renderUser(after.owner.user)}`);
+        if (before.verificationLevel != after.verificationLevel) list["verificationLevel"] = entry([verificationLevels[before.verificationLevel], verificationLevels[after.verificationLevel]], `${verificationLevels[before.verificationLevel]} -> ${verificationLevels[after.verificationLevel]}`);
+        if (before.explicitContentFilter != after.explicitContentFilter) list["explicitContentFilter"] = entry([explicitContentFilterLevels[before.explicitContentFilter], explicitContentFilterLevels[after.explicitContentFilter]], `${explicitContentFilterLevels[before.explicitContentFilter]} -> ${explicitContentFilterLevels[after.explicitContentFilter]}`);
+        if (before.mfaLevel != after.mfaLevel) list["2 factor authentication"] = entry([MFALevels[before.mfaLevel], MFALevels[after.mfaLevel]], `${MFALevels[before.mfaLevel]} -> ${MFALevels[after.mfaLevel]}`);
+
+        if (!(Object.keys(list).length)) return;
+        list["updated"] = entry(new Date().getTime(), renderDelta(new Date().getTime()))
+        list["updatedBy"] = entry(audit.executor.id, renderUser(audit.executor))
+        let data = {
+            meta: {
+                type: 'guildUpdate',
+                displayName: 'Guild Edited',
+                calculateType: 'guildUpdate',
+                color: NucleusColors.yellow,
+                emoji: "GUILD.YELLOW",
+                timestamp: new Date().getTime()
+            },
+            list: list,
+            hidden: {
+                guild: after.id
+            }
+        }
+        log(data, after.client);
+    } catch (e) {}
+}
diff --git a/src/events/inviteCreate.ts b/src/events/inviteCreate.ts
new file mode 100644
index 0000000..bdfeede
--- /dev/null
+++ b/src/events/inviteCreate.ts
@@ -0,0 +1,32 @@
+import humanizeDuration from 'humanize-duration';
+export const event = 'inviteCreate'
+
+export async function callback(client, invite) {
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = invite.client.logger
+        let auditLog = await getAuditLog(invite.guild, 'INVITE_CREATE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == invite.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let data = {
+            meta: {
+                type: 'inviteCreate',
+                displayName: 'Invite Created',
+                calculateType: 'guildUpdate',
+                color: NucleusColors.green,
+                emoji: "INVITE.CREATE",
+                timestamp: invite.createdTimestamp
+            },
+            list: {
+                channel: entry(invite.channel.id, renderChannel(invite.channel)),
+                link: entry(invite.url, invite.url),
+                expires: entry(invite.maxAge, invite.maxAge ? humanizeDuration(invite.maxAge * 1000) : 'Never'),
+                createdBy: entry(audit.executor.id, renderUser(audit.executor)),
+                created: entry(invite.createdTimestamp, renderDelta(invite.createdTimestamp))
+            },
+            hidden: {
+                guild: invite.guild.id
+            }
+        }
+        log(data, invite.client);
+    } catch {}
+}
diff --git a/src/events/inviteDelete.ts b/src/events/inviteDelete.ts
new file mode 100644
index 0000000..dec234f
--- /dev/null
+++ b/src/events/inviteDelete.ts
@@ -0,0 +1,32 @@
+import humanizeDuration from 'humanize-duration';
+export const event = 'inviteDelete'
+
+export async function callback(client, invite) {
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = invite.client.logger
+        let auditLog = await getAuditLog(invite.guild, 'INVITE_DELETE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == invite.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let data = {
+            meta: {
+                type: 'inviteDelete',
+                displayName: 'Invite Deleted',
+                calculateType: 'guildUpdate',
+                color: NucleusColors.red,
+                emoji: "INVITE.DELETE",
+                timestamp: new Date().getTime()
+            },
+            list: {
+                channel: entry(invite.channel.id, renderChannel(invite.channel)),
+                link: entry(invite.url, invite.url),
+                expires: entry(invite.maxAge, invite.maxAge ? humanizeDuration(invite.maxAge * 1000) : 'Never'),
+                deletedBy: entry(audit.executor.id, renderUser(audit.executor)),
+                deleted: entry(new Date().getTime(), renderDelta(new Date().getTime()))
+            },
+            hidden: {
+                guild: invite.guild.id
+            }
+        }
+        log(data, invite.client);
+    } catch {}
+}
diff --git a/src/events/memberLeave.ts b/src/events/memberLeave.ts
index ea461ab..22b7cd3 100644
--- a/src/events/memberLeave.ts
+++ b/src/events/memberLeave.ts
@@ -4,31 +4,67 @@
 
 export const event = 'guildMemberRemove'
 
-export async function callback(_, member) {
-    try { await statsChannelRemove(_, member); } catch {}
-    try { purgeByUser(member.id, member.guild); } catch {} // TODO: add this to ban as well
+export async function callback(client, member) {
+    try { await statsChannelRemove(client, member); } catch {}
+    try { purgeByUser(member.id, member.guild); } catch {}
     try {
-        const { log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
-        let data = {
-            meta: {
-                type: 'memberLeave',
-                displayName: 'Member Left',
-                calculateType: 'guildMemberUpdate',
-                color: NucleusColors.red,
-                emoji: "MEMBER" + (member.user.bot ? ".BOT" : "") + ".LEAVE",
-                timestamp: new Date().getTime()
-            },
-            list: {
-                id: entry(member.id, `\`${member.id}\``),
-                name: entry(member.id, renderUser(member.user)),
-                joined: entry(member.joinedAt, renderDelta(member.joinedAt)),
-                left: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-                timeInServer: entry(new Date().getTime() - member.joinedAt, humanizeDuration(new Date().getTime() - member.joinedAt, { round: true })),
-                accountCreated: entry(member.user.createdAt, renderDelta(member.user.createdAt)),
-                serverMemberCount: member.guild.memberCount,
-            },
-            hidden: {
-                guild: member.guild.id
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
+        let auditLog = await getAuditLog(member.guild, 'MEMBER_KICK');
+        let audit = auditLog.entries.filter(entry => entry.target.id == member.id).first();
+        let type = "kick"
+        if (audit) {
+            if (audit.createdAt - 100 < new Date().getTime()) {
+                type = "leave"
+            } else if (audit.executor.id == client.user.id) return
+        }
+        let data
+        if (type == "kick") {
+            data = {
+                meta: {
+                    type: 'memberKick',
+                    displayName: 'Member Kicked',
+                    calculateType: 'guildMemberPunish',
+                    color: NucleusColors.red,
+                    emoji: "PUNISH.KICK.RED",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    id: entry(member.id, `\`${member.id}\``),
+                    name: entry(member.id, renderUser(member.user)),
+                    joined: entry(member.joinedAt, renderDelta(member.joinedAt)),
+                    kicked: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    kickedBy: entry(audit.executor.id, renderUser(audit.executor)),
+                    reason: entry(audit.reason, audit.reason ? `\n> ${audit.reason}` : "*No reason provided.*"),
+                    timeInServer: entry(new Date().getTime() - member.joinedAt, humanizeDuration(new Date().getTime() - member.joinedAt, { round: true })),
+                    accountCreated: entry(member.user.createdAt, renderDelta(member.user.createdAt)),
+                    serverMemberCount: member.guild.memberCount,
+                },
+                hidden: {
+                    guild: member.guild.id
+                }
+            }
+        } else {
+            data = {
+                meta: {
+                    type: 'memberLeave',
+                    displayName: 'Member Left',
+                    calculateType: 'guildMemberUpdate',
+                    color: NucleusColors.red,
+                    emoji: "MEMBER." + (member.bot ? "BOT." : "") + "LEAVE",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    id: entry(member.id, `\`${member.id}\``),
+                    name: entry(member.id, renderUser(member.user)),
+                    joined: entry(member.joinedTimestamp, renderDelta(member.joinedAt)),
+                    left: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    timeInServer: entry(new Date().getTime() - member.joinedTimestamp, humanizeDuration(new Date().getTime() - member.joinedAt, { round: true })),
+                    accountCreated: entry(member.user.createdAt, renderDelta(member.user.createdAt)),
+                    serverMemberCount: member.guild.memberCount,
+                },
+                hidden: {
+                    guild: member.guild.id
+                }
             }
         }
         log(data, member.client);
diff --git a/src/events/messageChecks.ts b/src/events/messageChecks.ts
index 83cfff1..4e99c61 100644
--- a/src/events/messageChecks.ts
+++ b/src/events/messageChecks.ts
@@ -1,101 +1,100 @@
 import { LinkCheck, MalwareCheck, NSFWCheck, SizeCheck, TestString, TestImage } from '../automations/unscan.js'
-import readConfig from '../utils/readConfig.js'
 import { Message } from 'discord.js'
 
 export const event = 'messageCreate'
 
-export async function callback(_, message) {
-	if (message.author.bot) return
-	if (message.channel.type === 'dm') return
+export async function callback(client, message) {
+    if (message.author.bot) return
+    if (message.channel.type === 'dm') return
 
-	let content = message.content.toLowerCase() || ''
-	let config = await readConfig(message.guild.id);
+    let content = message.content.toLowerCase() || ''
+    let config = await client.memory.readGuildInfo(message.guild.id);
 
-	if (config.filters.invite.enabled) {
-		if (!config.filters.invite.allowed.users.includes(message.author.id) ||
-			!config.filters.invite.allowed.channels.includes(message.channel.id) ||
-			!message.author.roles.cache.some(role => config.filters.invite.allowed.roles.includes(role.id))
-		) {
-			if ((/(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/.test(content))) {
-				message.delete();
-				return toLog(message, 'invite', content.match(/(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/))
-			}
-		}
-	}
+    if (config.filters.invite.enabled) {
+        if (!config.filters.invite.allowed.users.includes(message.author.id) ||
+            !config.filters.invite.allowed.channels.includes(message.channel.id) ||
+            !message.author.roles.cache.some(role => config.filters.invite.allowed.roles.includes(role.id))
+        ) {
+            if ((/(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/.test(content))) {
+                message.delete();
+                return toLog(message, 'invite', content.match(/(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/))
+            }
+        }
+    }
 
-	let attachments = message.attachments.map(element => element)
-	attachments = [...attachments, ...content.match(
-		/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi
-	) ?? []].filter(element => (element.url ? element.url : element))
-	if (attachments.length > 0) {
-		attachments.forEach(async element => {
-			if(!message) return;
-			let url = element.url ? element.url : element
-			if (url != undefined) {
-				if(/\.+(webp|png|jpg|jpeg|bmp)/.test(url)) {
-					if (config.filters.images.NSFW && !message.channel.nsfw) {
-						if (await NSFWCheck(url)) {
-							await message.delete()
-							return toLog(message, 'NSFW', url)
-						}
-					}
-					if (config.filters.images.size) {
-						if(!url.match(/\.+(webp|png|jpg)$/gi)) return
-						if(!await SizeCheck(element)) {
-							await message.delete()
-							return toLog(message, 'size', url)
-						}
-					}
-				}
-				if (config.filters.malware) {
-					if (!MalwareCheck(url)) {
-						await message.delete()
-						return toLog(message, 'malware', url)
-					}
-				}
-			}
-		});
-	}
-	if(!message) return;
+    let attachments = message.attachments.map(element => element)
+    attachments = [...attachments, ...content.match(
+        /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi
+    ) ?? []].filter(element => (element.url ? element.url : element))
+    if (attachments.length > 0) {
+        attachments.forEach(async element => {
+            if(!message) return;
+            let url = element.url ? element.url : element
+            if (url != undefined) {
+                if(/\.+(webp|png|jpg|jpeg|bmp)/.test(url)) {
+                    if (config.filters.images.NSFW && !message.channel.nsfw) {
+                        if (await NSFWCheck(url)) {
+                            await message.delete()
+                            return toLog(message, 'NSFW', url)
+                        }
+                    }
+                    if (config.filters.images.size) {
+                        if(!url.match(/\.+(webp|png|jpg)$/gi)) return
+                        if(!await SizeCheck(element)) {
+                            await message.delete()
+                            return toLog(message, 'size', url)
+                        }
+                    }
+                }
+                if (config.filters.malware) {
+                    if (!MalwareCheck(url)) {
+                        await message.delete()
+                        return toLog(message, 'malware', url)
+                    }
+                }
+            }
+        });
+    }
+    if(!message) return;
 
-	if (await LinkCheck(message)) {
-		await message.delete()
-		return toLog(message, 'link')
-	}
+    if (await LinkCheck(message)) {
+        await message.delete()
+        return toLog(message, 'link')
+    }
 
-	if (config.filters.wordFilter.enabled) {
-		let check = TestString(content, config.filters.wordFilter.words.loose, config.filters.wordFilter.words.strict)
-		if(check != "none") {
-			await message.delete()
-			return toLog(message, 'wordFilter', content)
-		}
-	}
+    if (config.filters.wordFilter.enabled) {
+        let check = TestString(content, config.filters.wordFilter.words.loose, config.filters.wordFilter.words.strict)
+        if(check != "none") {
+            await message.delete()
+            return toLog(message, 'wordFilter', content)
+        }
+    }
 
-	if (!config.filters.pings.allowed.users.includes(message.author.id) ||
-		!config.filters.pings.allowed.channels.includes(message.channel.id) ||
-		!message.author.roles.cache.some(role => config.filters.pings.allowed.roles.includes(role.id))
-	) {
-		if (config.filters.pings.everyone && message.mentions.everyone) {
-			message.delete();
-			return toLog(message, 'mention everyone')
-		}
-		if (config.filters.pings.roles) {
-			for(let role of message.mentions.roles) {
-				if(!message) return;
-				if (!config.filters.pings.allowed.roles.includes(role.id)) {
-					message.delete();
-					return toLog(message, 'mention role')
-				}
-			}
-		}
-		if(!message) return;
-		if (message.mentions.users.size >= config.filters.pings.mass && config.filters.pings.mass) {
-			message.delete();
-			return toLog(message, 'Mass Pings')
-		}
-	}
+    if (!config.filters.pings.allowed.users.includes(message.author.id) ||
+        !config.filters.pings.allowed.channels.includes(message.channel.id) ||
+        !message.author.roles.cache.some(role => config.filters.pings.allowed.roles.includes(role.id))
+    ) {
+        if (config.filters.pings.everyone && message.mentions.everyone) {
+            message.delete();
+            return toLog(message, 'mention everyone')
+        }
+        if (config.filters.pings.roles) {
+            for(let role of message.mentions.roles) {
+                if(!message) return;
+                if (!config.filters.pings.allowed.roles.includes(role.id)) {
+                    message.delete();
+                    return toLog(message, 'mention role')
+                }
+            }
+        }
+        if(!message) return;
+        if (message.mentions.users.size >= config.filters.pings.mass && config.filters.pings.mass) {
+            message.delete();
+            return toLog(message, 'Mass Pings')
+        }
+    }
 }
 
 async function toLog(message: Message, reason: string, data?: any) {
-	// log(message.guild.id, {type: reason, data: data})
+    // log(message.guild.id, {type: reason, data: data})
 }
diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts
index ccaacb8..f0f5e00 100644
--- a/src/events/messageDelete.ts
+++ b/src/events/messageDelete.ts
@@ -1,38 +1,45 @@
 export const event = 'messageDelete'
 
 export async function callback(client, message) {
-    if (message.author.id == client.user.id) return;
-	const { log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = message.channel.client.logger
-    message.reference = message.reference || {}
-    let content = message.cleanContent
-    if (content.length > 256) content = content.substring(0, 253) + '...'
-    let 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: {
-            id: 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(message.createdTimestamp), renderDelta(new Date(message.createdTimestamp))),
-            mentions: message.mentions.users.size,
-            attachments: message.attachments.size,
-            repliedTo: entry(
-                message.reference.messageId || null,
-                message.reference.messageId ? `[[Jump to message]](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.reference.messageId})` : "None"
-            )
-        },
-        hidden: {
-            guild: message.channel.guild.id
+    try {
+        if (message.author.id == client.user.id) return;
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = message.channel.client.logger
+        let auditLog = await getAuditLog(message.guild, 'MEMBER_BAN_ADD')
+        let audit = auditLog.entries.filter(entry => entry.target.id == message.author.id).first();
+        if (audit) {
+            if (audit.createdAt - 100 < new Date().getTime()) return;
         }
-    }
-    log(data, client);
+        message.reference = message.reference || {}
+        let content = message.cleanContent
+        if (content.length > 256) content = content.substring(0, 253) + '...'
+        let 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: {
+                id: 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())),
+                mentions: message.mentions.users.size,
+                attachments: message.attachments.size,
+                repliedTo: entry(
+                    message.reference.messageId || null,
+                    message.reference.messageId ? `[[Jump to message]](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.reference.messageId})` : "None"
+                )
+            },
+            hidden: {
+                guild: message.channel.guild.id
+            }
+        }
+        log(data, client);
+    } catch {}
 }
diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts
index 318b0ef..ec713a2 100644
--- a/src/events/messageEdit.ts
+++ b/src/events/messageEdit.ts
@@ -1,44 +1,46 @@
 export const event = 'messageUpdate'
 
 export async function callback(client, oldMessage, newMessage) {
-    if (newMessage.author.id == client.user.id) return;
-	const { log, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = newMessage.channel.client.logger
-    newMessage.reference = newMessage.reference || {}
-    let newContent = newMessage.cleanContent.replaceAll("`", "‘")
-    let oldContent = oldMessage.cleanContent.replaceAll("`", "‘")
-    if (newContent == oldContent) return;
-    if (newContent.length > 256) newContent = newContent.substring(0, 253) + '...'
-    if (oldContent.length > 256) oldContent = oldContent.substring(0, 253) + '...'
-    let data = {
-        meta: {
-            type: 'messageUpdate',
-            displayName: 'Message Edited',
-            calculateType: 'messageUpdate',
-            color: NucleusColors.yellow,
-            emoji: 'MESSAGE.EDIT',
-            timestamp: newMessage.editedTimestamp
-        },
-        separate: {
-            start: (oldContent ? `**Before:**\n\`\`\`\n${oldContent}\n\`\`\`\n` : '**Before:** *Message had no content*\n') +
-                   (newContent ? `**After:**\n\`\`\`\n${newContent}\n\`\`\`` : '**After:** *Message had no content*'),
-            end: `[[Jump to message]](${newMessage.url})`
-        },
-        list: {
-            id: entry(newMessage.id, `\`${newMessage.id}\``),
-            sentBy: entry(newMessage.author.id, renderUser(newMessage.author)),
-            sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel)),
-            sent: entry(new Date(newMessage.createdTimestamp), renderDelta(new Date(newMessage.createdTimestamp))),
-            edited: entry(new Date(newMessage.editedTimestamp), renderDelta(new Date(newMessage.editedTimestamp))),
-            mentions: renderNumberDelta(oldMessage.mentions.users.size, newMessage.mentions.users.size),
-            attachments: renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size),
-            repliedTo: entry(
-                newMessage.reference.messageId || null,
-                newMessage.reference.messageId ? `[[Jump to message]](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${newMessage.reference.messageId})` : "None"
-            )
-        },
-        hidden: {
-            guild: newMessage.channel.guild.id
+    try {
+        if (newMessage.author.id == client.user.id) return;
+        const { log, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = newMessage.channel.client.logger
+        newMessage.reference = newMessage.reference || {}
+        let newContent = newMessage.cleanContent.replaceAll("`", "‘")
+        let oldContent = oldMessage.cleanContent.replaceAll("`", "‘")
+        if (newContent == oldContent) return;
+        if (newContent.length > 256) newContent = newContent.substring(0, 253) + '...'
+        if (oldContent.length > 256) oldContent = oldContent.substring(0, 253) + '...'
+        let data = {
+            meta: {
+                type: 'messageUpdate',
+                displayName: 'Message Edited',
+                calculateType: 'messageUpdate',
+                color: NucleusColors.yellow,
+                emoji: 'MESSAGE.EDIT',
+                timestamp: newMessage.editedTimestamp
+            },
+            separate: {
+                start: (oldContent ? `**Before:**\n\`\`\`\n${oldContent}\n\`\`\`\n` : '**Before:** *Message had no content*\n') +
+                    (newContent ? `**After:**\n\`\`\`\n${newContent}\n\`\`\`` : '**After:** *Message had no content*'),
+                end: `[[Jump to message]](${newMessage.url})`
+            },
+            list: {
+                id: entry(newMessage.id, `\`${newMessage.id}\``),
+                sentBy: entry(newMessage.author.id, renderUser(newMessage.author)),
+                sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel)),
+                sent: entry(new Date(newMessage.createdTimestamp), renderDelta(new Date(newMessage.createdTimestamp))),
+                edited: entry(new Date(newMessage.editedTimestamp), renderDelta(new Date(newMessage.editedTimestamp))),
+                mentions: renderNumberDelta(oldMessage.mentions.users.size, newMessage.mentions.users.size),
+                attachments: renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size),
+                repliedTo: entry(
+                    newMessage.reference.messageId || null,
+                    newMessage.reference.messageId ? `[[Jump to message]](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${newMessage.reference.messageId})` : "None"
+                )
+            },
+            hidden: {
+                guild: newMessage.channel.guild.id
+            }
         }
-    }
-    log(data, client);
+        log(data, client);
+    } catch {}
 }
diff --git a/src/events/roleCreate.ts b/src/events/roleCreate.ts
index 76af433..2664790 100644
--- a/src/events/roleCreate.ts
+++ b/src/events/roleCreate.ts
@@ -1,29 +1,31 @@
 export const event = 'roleCreate'
 
 export async function callback(client, role) {
-	const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderRole } = role.client.logger
-    if (role.managed) return;
-    let auditLog = await getAuditLog(role.guild, 'ROLE_CREATE');
-    let audit = auditLog.entries.filter(entry => entry.target.id == role.id).first();
-    if (audit.executor.id == client.user.id) return;
-    let data = {
-        meta: {
-            type: 'roleCreate',
-            displayName: 'Role Created',
-            calculateType: 'guildRoleUpdate',
-            color: NucleusColors.green,
-            emoji: "GUILD.ROLES.CREATE",
-            timestamp: role.createdTimestamp
-        },
-        list: {
-            id: entry(role.id, `\`${role.id}\``),
-            role: entry(role.name, renderRole(role)),
-            createdBy: entry(audit.executor.id, renderUser(audit.executor)),
-            created: entry(role.createdTimestamp, renderDelta(role.createdTimestamp))
-        },
-        hidden: {
-            guild: role.guild.id
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderRole } = role.client.logger
+        if (role.managed) return;
+        let auditLog = await getAuditLog(role.guild, 'ROLE_CREATE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == role.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let data = {
+            meta: {
+                type: 'roleCreate',
+                displayName: 'Role Created',
+                calculateType: 'guildRoleUpdate',
+                color: NucleusColors.green,
+                emoji: "GUILD.ROLES.CREATE",
+                timestamp: role.createdTimestamp
+            },
+            list: {
+                id: entry(role.id, `\`${role.id}\``),
+                role: entry(role.name, renderRole(role)),
+                createdBy: entry(audit.executor.id, renderUser(audit.executor)),
+                created: entry(role.createdTimestamp, renderDelta(role.createdTimestamp))
+            },
+            hidden: {
+                guild: role.guild.id
+            }
         }
-    }
-    log(data, client);
+        log(data, client);
+    } catch {}
 }
diff --git a/src/events/roleDelete.ts b/src/events/roleDelete.ts
index c5cbe63..6edfeed 100644
--- a/src/events/roleDelete.ts
+++ b/src/events/roleDelete.ts
@@ -3,34 +3,36 @@
 export const event = 'roleDelete'
 
 export async function callback(client, role) {
-	const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = role.client.logger
-    if (role.managed) return;
-    let auditLog = await getAuditLog(role.guild, 'ROLE_DELETE');
-    let audit = auditLog.entries.filter(entry => entry.target.id == role.id).first();
-    if (audit.executor.id == client.user.id) return;
-    let data = {
-        meta: {
-            type: 'roleDelete',
-            displayName: 'Role Deleted',
-            calculateType: 'guildRoleUpdate',
-            color: NucleusColors.red,
-            emoji: "GUILD.ROLES.DELETE",
-            timestamp: audit.createdTimestamp,
-        },
-        list: {
-            id: entry(role.id, `\`${role.id}\``),
-            role: entry(role.name, role.name),
-            color: entry(role.hexColor, `\`${role.hexColor}\``),
-            showInMemberList: entry(role.hoist, role.hoist ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`),
-            mentionable: entry(role.mentionable, role.mentionable ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`),
-            members: entry(role.members.size, `${role.members.size}`),
-            deletedBy: entry(audit.executor.id, renderUser(audit.executor)),
-            created: entry(role.createdTimestamp, renderDelta(role.createdTimestamp)),
-            deleted: entry(new Date().getTime(), renderDelta(new Date().getTime()))
-        },
-        hidden: {
-            guild: role.guild.id
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = role.client.logger
+        if (role.managed) return;
+        let auditLog = await getAuditLog(role.guild, 'ROLE_DELETE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == role.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let data = {
+            meta: {
+                type: 'roleDelete',
+                displayName: 'Role Deleted',
+                calculateType: 'guildRoleUpdate',
+                color: NucleusColors.red,
+                emoji: "GUILD.ROLES.DELETE",
+                timestamp: audit.createdTimestamp,
+            },
+            list: {
+                id: entry(role.id, `\`${role.id}\``),
+                role: entry(role.name, role.name),
+                color: entry(role.hexColor, `\`${role.hexColor}\``),
+                showInMemberList: entry(role.hoist, role.hoist ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`),
+                mentionable: entry(role.mentionable, role.mentionable ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`),
+                members: entry(role.members.size, `${role.members.size}`),
+                deletedBy: entry(audit.executor.id, renderUser(audit.executor)),
+                created: entry(role.createdTimestamp, renderDelta(role.createdTimestamp)),
+                deleted: entry(new Date().getTime(), renderDelta(new Date().getTime()))
+            },
+            hidden: {
+                guild: role.guild.id
+            }
         }
-    }
-    log(data, client);
+        log(data, client);
+    } catch {}
 }
diff --git a/src/events/roleUpdate.ts b/src/events/roleUpdate.ts
index 28afe0e..faf3237 100644
--- a/src/events/roleUpdate.ts
+++ b/src/events/roleUpdate.ts
@@ -3,43 +3,45 @@
 export const event = 'roleUpdate';
 
 export async function callback(client, or, nr) {
-	const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderRole } = client.logger
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderRole } = client.logger
 
-	let auditLog = await getAuditLog(nr.guild, 'ROLE_UPDATE');
-	let audit = auditLog.entries.first();
-    if (audit.executor.id == client.user.id) return;
+        let auditLog = await getAuditLog(nr.guild, 'ROLE_UPDATE');
+        let audit = auditLog.entries.first();
+        if (audit.executor.id == client.user.id) return;
 
-	let changes = {
-		id: entry(nr.id, `\`${nr.id}\``),
-		role: entry(nr.id, renderRole(nr)),
-		edited: entry(nr.createdTimestamp, renderDelta(nr.createdTimestamp)),
-		editedBy: entry(audit.executor.id, renderUser((await nr.guild.members.fetch(audit.executor.id)).user)),
-	}
-    let mentionable = ["", ""]
-    let hoist = ["", ""]
-    mentionable[0] = or.mentionable ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
-    mentionable[1] = nr.mentionable ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
-    hoist[0] = or.hoist ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
-    hoist[1] = nr.hoist ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
-	if (or.name != nr.name) changes["name"] = entry([or.name, nr.name], `${or.name} -> ${nr.name}`);
-	if (or.position != nr.position) changes["position"] = entry([or.position, nr.position], `${or.position} -> ${nr.position}`);
-    if (or.hoist != nr.hoist) changes["showInMemberList"] = entry([or.hoist, nr.hoist], `${hoist[0]} -> ${hoist[1]}`);
-    if (or.mentionable != nr.mentionable) changes["mentionable"] = entry([or.mentionable, nr.mentionable], `${mentionable[0]} -> ${mentionable[1]}`);
-    if (or.hexColor != nr.hexColor) changes["color"] = entry([or.hexColor, nr.hexColor], `\`${or.hexColor}\` -> \`${nr.hexColor}\``);
+        let changes = {
+            id: entry(nr.id, `\`${nr.id}\``),
+            role: entry(nr.id, renderRole(nr)),
+            edited: entry(nr.createdTimestamp, renderDelta(nr.createdTimestamp)),
+            editedBy: entry(audit.executor.id, renderUser((await nr.guild.members.fetch(audit.executor.id)).user)),
+        }
+        let mentionable = ["", ""]
+        let hoist = ["", ""]
+        mentionable[0] = or.mentionable ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
+        mentionable[1] = nr.mentionable ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
+        hoist[0] = or.hoist ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
+        hoist[1] = nr.hoist ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
+        if (or.name != nr.name) changes["name"] = entry([or.name, nr.name], `${or.name} -> ${nr.name}`);
+        if (or.position != nr.position) changes["position"] = entry([or.position, nr.position], `${or.position} -> ${nr.position}`);
+        if (or.hoist != nr.hoist) changes["showInMemberList"] = entry([or.hoist, nr.hoist], `${hoist[0]} -> ${hoist[1]}`);
+        if (or.mentionable != nr.mentionable) changes["mentionable"] = entry([or.mentionable, nr.mentionable], `${mentionable[0]} -> ${mentionable[1]}`);
+        if (or.hexColor != nr.hexColor) changes["color"] = entry([or.hexColor, nr.hexColor], `\`${or.hexColor}\` -> \`${nr.hexColor}\``);
 
-	let data = {
-		meta:{
-			type: 'roleUpdate',
-			displayName: 'Role Edited',
-			calculateType: 'guildRoleUpdate',
-			color: NucleusColors.yellow,
-			emoji: "GUILD.ROLES.EDIT",
-			timestamp: audit.createdTimestamp
-		},
-		list: changes,
-		hidden: {
-			guild: nr.guild.id
-		}
-	}
-	log(data, client);
+        let data = {
+            meta:{
+                type: 'roleUpdate',
+                displayName: 'Role Edited',
+                calculateType: 'guildRoleUpdate',
+                color: NucleusColors.yellow,
+                emoji: "GUILD.ROLES.EDIT",
+                timestamp: audit.createdTimestamp
+            },
+            list: changes,
+            hidden: {
+                guild: nr.guild.id
+            }
+        }
+        log(data, client);
+    } catch {}
 }
\ No newline at end of file
diff --git a/src/events/threadCreate.ts b/src/events/threadCreate.ts
new file mode 100644
index 0000000..77e9dea
--- /dev/null
+++ b/src/events/threadCreate.ts
@@ -0,0 +1,34 @@
+import humanizeDuration from 'humanize-duration';
+export const event = 'threadCreate'
+
+export async function callback(client, thread) {
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = thread.client.logger
+        let auditLog = await getAuditLog(thread.guild, 'THREAD_CREATE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == thread.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let data = {
+            meta: {
+                type: 'channelCreate',
+                displayName: 'Thread Created',
+                calculateType: 'channelUpdate',
+                color: NucleusColors.green,
+                emoji: "CHANNEL.TEXT.CREATE",
+                timestamp: thread.createdTimestamp
+            },
+            list: {
+                id: entry(thread.id, `\`${thread.id}\``),
+                name: entry(thread.name, renderChannel(thread)),
+                parentChannel: entry(thread.parentId, renderChannel(thread.parent)),
+                category: entry(thread.parent.parent ? thread.parent.parent.name : 'None', thread.parent.parent ? renderChannel(thread.parent.parent) : 'None'),
+                autoArchiveDuration: entry(thread.autoArchiveDuration, humanizeDuration(thread.autoArchiveDuration * 60 * 1000, { round: true })),
+                createdBy: entry(audit.executor.id, renderUser(audit.executor)),
+                created: entry(thread.createdTimestamp, renderDelta(thread.createdTimestamp))
+            },
+            hidden: {
+                guild: thread.guild.id
+            }
+        }
+        log(data, thread.client);
+    } catch {}
+}
diff --git a/src/events/threadDelete.ts b/src/events/threadDelete.ts
new file mode 100644
index 0000000..7bf8537
--- /dev/null
+++ b/src/events/threadDelete.ts
@@ -0,0 +1,36 @@
+import humanizeDuration from 'humanize-duration';
+export const event = 'threadDelete'
+
+export async function callback(client, thread) {
+    try {
+    const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = thread.client.logger
+    let auditLog = await getAuditLog(thread.guild, 'THREAD_UPDATE');
+    let audit = auditLog.entries.filter(entry => entry.target.id == thread.id).first();
+    if (audit.executor.id == client.user.id) return;
+    let data = {
+        meta: {
+            type: 'channelDelete',
+            displayName: 'Thread Deleted',
+            calculateType: 'channelUpdate',
+            color: NucleusColors.red,
+            emoji: "CHANNEL.TEXT.DELETE",
+            timestamp: new Date().getTime()
+        },
+        list: {
+            id: entry(thread.id, `\`${thread.id}\``),
+            name: entry(thread.name, thread.name),
+            parentChannel: entry(thread.parentId, renderChannel(thread.parent)),
+            category: entry(thread.parent.parent ? thread.parent.parent.name : 'None', thread.parent.parent ? renderChannel(thread.parent.parent) : 'None'),
+            autoArchiveDuration: entry(thread.autoArchiveDuration, humanizeDuration(thread.autoArchiveDuration * 60 * 1000, { round: true })),
+            membersInThread: entry(thread.memberCount, thread.memberCount),
+            deletedBy: entry(audit.executor.id, renderUser(audit.executor)),
+            created: entry(thread.createdTimestamp, renderDelta(thread.createdTimestamp)),
+            deleted: entry(new Date().getTime(), renderDelta(new Date().getTime()))
+        },
+        hidden: {
+            guild: thread.guild.id
+        }
+    }
+    log(data, thread.client);
+    } catch {}
+}
diff --git a/src/events/threadUpdate.ts b/src/events/threadUpdate.ts
new file mode 100644
index 0000000..4067eba
--- /dev/null
+++ b/src/events/threadUpdate.ts
@@ -0,0 +1,43 @@
+import humanizeDuration from 'humanize-duration';
+export const event = 'threadUpdate'
+
+export async function callback(client, before, after) {
+    try {
+        const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = after.client.logger
+        let auditLog = await getAuditLog(after.guild, 'THREAD_UPDATE');
+        let audit = auditLog.entries.filter(entry => entry.target.id == after.id).first();
+        if (audit.executor.id == client.user.id) return;
+        let list = {
+            id: entry(after.id, `\`${after.id}\``),
+            thread: entry(after.name, renderChannel(after)),
+            parentChannel: entry(after.parentId, renderChannel(after.parent)),
+        }
+        if (before.name != after.name) {
+            list["name"] = entry([before.name, after.name], `${before.name} -> ${after.name}`);
+        }
+        if (before.autoArchiveDuration != after.autoArchiveDuration) {
+            list["autoArchiveDuration"] = entry([before.autoArchiveDuration, after.autoArchiveDuration], `${humanizeDuration(before.autoArchiveDuration * 60 * 1000, { round: true })} -> ${humanizeDuration(after.autoArchiveDuration * 60 * 1000, { round: true })}`);
+        }
+        if (before.rateLimitPerUser != after.rateLimitPerUser) {
+            list["slowmode"] = entry([before.rateLimitPerUser, after.rateLimitPerUser], `${humanizeDuration(before.rateLimitPerUser * 1000)} -> ${humanizeDuration(after.rateLimitPerUser * 1000)}`);
+        }
+        if (!(Object.keys(list).length - 3)) return;
+        list["updated"] = entry(new Date().getTime(), renderDelta(new Date().getTime()))
+        list["updatedBy"] = entry(audit.executor.id, renderUser(audit.executor))
+        let data = {
+            meta: {
+                type: 'channelUpdate',
+                displayName: 'Thread Edited',
+                calculateType: 'channelUpdate',
+                color: NucleusColors.yellow,
+                emoji: "CHANNEL.TEXT.EDIT",
+                timestamp: new Date().getTime()
+            },
+            list: list,
+            hidden: {
+                guild: after.guild.id
+            }
+        }
+        log(data, after.client);
+    } catch {}
+}
diff --git a/src/events:TODO/channelPinsUpdate.ts b/src/events:TODO/channelPinsUpdate.ts
deleted file mode 100644
index 8a86b6d..0000000
--- a/src/events:TODO/channelPinsUpdate.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import log from '../utils/log.js'
-import * as JsonDiff from 'json-diff'
-
-export const name = ''
-export const once = false
-export async function execute(channel) {
-    let pins = (await channel.messages.fetchPinned()).map(m => m.id);
-    let oldPins = require(`../data/guilds/${channel.guild.id}/pins.json`);
-
-    let data = JsonDiff.diff(oldPins, pins, {full: true});
-
-    addLog(channel.guild.id, data)
-}
diff --git a/src/events:TODO/guildBanAdd.ts b/src/events:TODO/guildBanAdd.ts
deleted file mode 100644
index c165805..0000000
--- a/src/events:TODO/guildBanAdd.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import log from '../utils/log.js'
-
-export const name = 'guildBanAdd'
-export const once = false
-export async function execute(ban) {
-    let logs = await ban.guild.fetchAuditLogs({'type': 'MEMBER_BAN_CREATE'});
-    let log = logs.entries.find(log => log.target.id === ban.user.id)
-
-    let data = {
-        id: ban.user.id,
-        username: ban.user.username,
-        reason: ban.reason,
-        bannedAt: log.createdTimestamp,
-        bannedBy: log.executor.id
-    }
-
-    log(ban.guild.id, data);
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildBanRemove.ts b/src/events:TODO/guildBanRemove.ts
deleted file mode 100644
index 614f0f5..0000000
--- a/src/events:TODO/guildBanRemove.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-module.exports = {
-    name:'guilidBanRemove',
-    once:false,
-    async execute(ban) {
-        let logs = await ban.guild.fetchAuditLogs({'type': 'MEMBER_BAN_REMOVE'});
-        let log = logs.entries.find(log => log.target.id === ban.user.id);
-
-        let data = {
-            id: ban.user.id,
-            username: ban.user.username,
-            reason: ban.reason,
-            unbannedAt: log.createdTimestamp,
-            unbannedBy: log.executor.id
-        }
-
-        addLog(ban.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildDelete.ts b/src/events:TODO/guildDelete.ts
deleted file mode 100644
index 107a6db..0000000
--- a/src/events:TODO/guildDelete.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
-    name:'guildDelete',
-    once:false,
-    async execute(guild) {
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildIntegrationsUpdate.ts b/src/events:TODO/guildIntegrationsUpdate.ts
deleted file mode 100644
index 863b67a..0000000
--- a/src/events:TODO/guildIntegrationsUpdate.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-module.exports = {
-    name:'guildIntegrationsUpdate',
-    once:false,
-    async execute(args) {
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildMemberAvailable.ts b/src/events:TODO/guildMemberAvailable.ts
deleted file mode 100644
index 92c2c80..0000000
--- a/src/events:TODO/guildMemberAvailable.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-module.exports = {
-    name:'guildMemberAvailable',
-    once:false,
-    async execute(args) {
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildMemberUpdate.ts b/src/events:TODO/guildMemberUpdate.ts
deleted file mode 100644
index 238bf3c..0000000
--- a/src/events:TODO/guildMemberUpdate.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-
-module.exports = {
-    name:'guildMemberUpdate',
-    once:false,
-    async execute(oldMember, newMember) {
-
-        let oMem = {
-            id: oldMember.id,
-            username: oldMember.user.username,
-            nick: oldMember.nickname,
-            roles: oldMember.roles.cache.map(r => r.id),
-            displayAvatarUrl: oldMember.displayAvatarUrl,
-            communicationDisabledUntil: oldMember.communicationDisabledUntilTimestamp
-        }
-
-        let nMem = {
-            id: newMember.id,
-            username: newMember.user.username,
-            nick: newMember.nickname,
-            roles: newMember.roles.cache.map(r => r.id),
-            displayAvatarUrl: newMember.displayAvatarUrl,
-            communicationDisabledUntil: newMember.communicationDisabledUntilTimestamp
-        }
-
-        let data = JsonDiff.diff(oMem, nMem, {full: true});
-
-        addLog(newMember.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildScheduledEventCreate.ts b/src/events:TODO/guildScheduledEventCreate.ts
deleted file mode 100644
index 489dd2f..0000000
--- a/src/events:TODO/guildScheduledEventCreate.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'guildScheduledEventCreate',
-    once:false,
-    async execute(event) {
-        let data = {
-            id: event.id,
-            name: event.name,
-            description: event.description,
-            channel: event.channel.id,
-            time: {
-                start: event.scheduledStartTimestamp,
-                end: event.scheduledEndTimestamp
-            },
-            date: event.date,
-            createdBy: event.creator.id,
-            createdAt: event.createdTimestamp,
-            privacyLevel: event.privacyLevel
-        }
-
-        addLog(event.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildScheduledEventDelete.ts b/src/events:TODO/guildScheduledEventDelete.ts
deleted file mode 100644
index a3e85db..0000000
--- a/src/events:TODO/guildScheduledEventDelete.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'guildScheduledEventDelete',
-    once:false,
-    async execute(event) {
-
-        let logs = await event.guild.fetchAuditLogs({'type': 'GUILD_SCHEDULED_EVENT_DELETE'});
-        let log = logs.entries.find(log => log.target.id === event.id);
-
-        let data = {
-            id: event.id,
-            name: event.name,
-            deletedAt: log.createdTimestamp,
-            deletedBy: log.executor.id
-        }
-
-        addLog(event.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildScheduledEventUpdate.ts b/src/events:TODO/guildScheduledEventUpdate.ts
deleted file mode 100644
index cc37a73..0000000
--- a/src/events:TODO/guildScheduledEventUpdate.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-
-module.exports = {
-    name:'guildScheduledEventUpdate',
-    once:false,
-    async execute(oldEvent, newEvent) {
-        let oe = {
-            id: oldEvent.id,
-            name: oldEvent.name,
-            description: oldEvent.description,
-            channel: oldEvent.channel ? oldEvent.channel.id : null,
-            time: {
-                start: oldEvent.scheduledStartTimestamp,
-                end: oldEvent.scheduledEndTimestamp
-            },
-            date: oldEvent.date,
-            privacyLevel: oldEvent.privacyLevel,
-            entityType: oldEvent.entityType,
-            entityMetadata: oldEvent.entityMetadata,
-            status: oldEvent.status
-        }
-        let ne = {
-            id: newEvent.id,
-            name: newEvent.name,
-            description: newEvent.description,
-            channel: newEvent.channel ? newEvent.channel.id : null,
-            time: {
-                start: newEvent.scheduledStartTimestamp,
-                end: newEvent.scheduledEndTimestamp
-            },
-            date: newEvent.date,
-            privacyLevel: newEvent.privacyLevel,
-            entityType: newEvent.entityType,
-            entityMetadata: newEvent.entityMetadata,
-            status: newEvent.status
-        }
-
-        let data = JsonDiff.diff(oe, ne, {full: true});
-
-        addLog(newEvent.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildScheduledEventUserAdd.ts b/src/events:TODO/guildScheduledEventUserAdd.ts
deleted file mode 100644
index caadd25..0000000
--- a/src/events:TODO/guildScheduledEventUserAdd.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'guildScheduledEventUserAdd',
-    once:false,
-    async execute(event, member) {
-        let data = {
-            event: {
-                id: event.id,
-                name: event.name,
-            },
-            user: {
-                id: member.id,
-                username: member.username,
-                joinedEventAt: Date.now()
-            }
-        }
-        addLog(event.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildScheduledEventUserRemove.ts b/src/events:TODO/guildScheduledEventUserRemove.ts
deleted file mode 100644
index 9977709..0000000
--- a/src/events:TODO/guildScheduledEventUserRemove.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'guildScheduledEventUserRemove',
-    once:false,
-    async execute(event, member) {
-        let data = {
-            event: {
-                id: event.id,
-                name: event.name,
-            },
-            user: {
-                id: member.id,
-                username: member.username,
-                leftEventAt: Date.now()
-            }
-        }
-        addLog(event.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/guildUpdate.ts b/src/events:TODO/guildUpdate.ts
deleted file mode 100644
index 2e93f5b..0000000
--- a/src/events:TODO/guildUpdate.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-
-module.exports = {
-    name:'guildUpdate',
-    once:false,
-    async execute(oldGuild, newGuild) {
-
-        if(!newGuild.available) {
-            return addLog(newGuild.id, {id:newGuild.id,unavailable:true});
-        }
-
-        let og = {
-            afkChannel: oldGuild.afkChannel ? oldGuild.afkChannel.id : null,
-            afkTimeout: oldGuild.afkTimeout,
-            available: oldGuild.available,
-            banner: oldGuild.banner,
-            description: oldGuild.description,
-            discoverySplash: oldGuild.discoverySplash,
-            explicitContentFilter: oldGuild.explicitContentFilter,
-            features: oldGuild.features,
-            icon: oldGuild.icon,
-            id: oldGuild.id,
-            large: oldGuild.large,
-            maximumBitrate: oldGuild.maximumBitrate,
-            maximumMembers: oldGuild.maximumMembers,
-            mfaLevel: oldGuild.mfaLevel,
-            name: oldGuild.name,
-            nsfwLevel: oldGuild.nsfwLevel,
-            ownerid: oldGuild.ownerId,
-            partnered: oldGuild.partnered,
-            preferredLocale: oldGuild.preferredLocale,
-            premiumProgressBarEnabled: oldGuild.premiumProgressBarEnabled,
-            premiumSubscriptionCount: oldGuild.premiumSubscriptionCount,
-            premiumTier: oldGuild.premiumTier,
-            publicUpdatesChannel: oldGuild.publicUpdatesChannel ? oldGuild.publicUpdatesChannel.id : null,
-            rulesChannel: oldGuild.rulesChannel ? oldGuild.rulesChannel.id : null,
-            splash: oldGuild.splash,
-            systemChannel: oldGuild.systemChannel ? oldGuild.systemChannel.id : null,
-            vanityURLCode: oldGuild.vanityURLCode,
-            verificationLevel: oldGuild.verificationLevel,
-            verified: oldGuild.verified,
-            widgetChannel: oldGuild.widgetChannel ? oldGuild.widgetChannel.id : null,
-            widgetEnabled: oldGuild.widgetEnabled,
-        }
-        let ng = {
-            afkChannel: newGuild.afkChannel ? newGuild.afkChannel.id : null,
-            afkTimeout: newGuild.afkTimeout,
-            available: newGuild.available,
-            banner: newGuild.banner,
-            description: newGuild.description,
-            discoverySplash: newGuild.discoverySplash,
-            explicitContentFilter: newGuild.explicitContentFilter,
-            features: newGuild.features,
-            icon: newGuild.icon,
-            id: newGuild.id,
-            large: newGuild.large,
-            maximumBitrate: newGuild.maximumBitrate,
-            maximumMembers: newGuild.maximumMembers,
-            mfaLevel: newGuild.mfaLevel,
-            name: newGuild.name,
-            nsfwLevel: newGuild.nsfwLevel,
-            ownerid: newGuild.ownerId,
-            partnered: newGuild.partnered,
-            preferredLocale: newGuild.preferredLocale,
-            premiumProgressBarEnabled: newGuild.premiumProgressBarEnabled,
-            premiumSubscriptionCount: newGuild.premiumSubscriptionCount,
-            premiumTier: newGuild.premiumTier,
-            publicUpdatesChannel: newGuild.publicUpdatesChannel ? newGuild.publicUpdatesChannel.id : null,
-            rulesChannel: newGuild.rulesChannel ? newGuild.rulesChannel.id : null,
-            splash: newGuild.splash,
-            systemChannel: newGuild.systemChannel ? newGuild.systemChannel.id : null,
-            vanityURLCode: newGuild.vanityURLCode,
-            verificationLevel: newGuild.verificationLevel,
-            verified: newGuild.verified,
-            widgetChannel: newGuild.widgetChannel ? newGuild.widgetChannel.id : null,
-            widgetEnabled: newGuild.widgetEnabled,
-        }
-
-        let data = JsonDiff.diff(og, ng, {full:true});
-        addLog(newGuild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/inviteCreate.ts b/src/events:TODO/inviteCreate.ts
deleted file mode 100644
index 596bf58..0000000
--- a/src/events:TODO/inviteCreate.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'inviteCreate',
-    once:false,
-    async execute(invite) {
-
-        const i = await invite.guild.invites.fetch(invite.code)
-
-        let data = {
-            channel: invite.channel.id,
-            code: invite.code,
-            createdAt: invite.createdTimestamp,
-            expiresAt: invite.expiresTimestamp,
-            createdBy: invite.inviter.id,
-            maxUsage: i.maxUses,
-            maxAge: i.maxAge
-        }
-
-        addLog(invite.guild.id, data)
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/inviteDelete.ts b/src/events:TODO/inviteDelete.ts
deleted file mode 100644
index 609ce6f..0000000
--- a/src/events:TODO/inviteDelete.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'inviteDelete',
-    once:false,
-    async execute(invite) {
-
-        let logs = await invite.guild.fetchAuditLogs({type: 'INVITE_DELETE'});
-        let entry = logs.entries.find(e => e.target.code === invite.code);
-
-        let data = {
-            channel: invite.channel.id,
-            code: invite.code,
-            deletedAt: invite.deletedTimestamp,
-            deletedBy: entry.executor.id
-        }
-
-        addLog(invite.guild.id, data)
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/messageCreate.ts b/src/events:TODO/messageCreate.ts
deleted file mode 100644
index 46e8795..0000000
--- a/src/events:TODO/messageCreate.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'messageCreate',
-    once:false,
-    async execute(message) {
-
-        const guildConfig = require(`../data/guilds/${message.guild.id}/config.json`);
-        if(guildConfig.images.enabled) {
-
-        }
-        if(guildConfig.wordFilter.enabled) {
-            for(word of guildConfig.wordFilter.words.strict) {
-                if(message.content.toLowerCase().includes(word)) {
-                    message.delete();
-                    // message.channel.send(`${message.author} has been warned for using a banned word.`);
-                    break;
-                }
-            }
-            for(word of message.content.split(' ')) {
-                if(guildConfig.wordFilter.words.soft.includes(word)) {
-                    message.delete();
-                    // message.channel.send(`${message.author} has been warned for using a banned word.`);
-                    break;
-                }
-            }
-            
-        }
-
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/messageDeleteBulk.ts b/src/events:TODO/messageDeleteBulk.ts
deleted file mode 100644
index 92d9134..0000000
--- a/src/events:TODO/messageDeleteBulk.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'messageDeleteBulk',
-    once:false,
-    async execute(messages) {
-
-        let logs = await messages.first().guild.fetchAuditLogs({type: 'MESSAGE_DELETE_BULK'});
-        let entry = logs.entries.first();
-
-        let data = {
-            messages:messages.map(message=>{
-                return {
-                    id:message.id,
-                    channel:message.channel.id,
-                    content:message.content
-                }
-            }),
-            deletedBy:entry.executor.id,
-            deletedAt:entry.createdAt
-        }
-
-        addLog(messages.first().guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/messageReactionAdd.ts b/src/events:TODO/messageReactionAdd.ts
deleted file mode 100644
index c59a16f..0000000
--- a/src/events:TODO/messageReactionAdd.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'messageReactionAdd',
-    once:false,
-    async execute(messageReaction, user) {
-        let data = {
-            messageReaction: {
-                messageID: messageReaction.message.id,
-                reactionEmoji: {
-                    name: messageReaction.emoji.name,
-                    id: messageReaction.emoji.id
-                },
-                addedAt: Date.now()
-            },
-            user: {
-                id: user.id,
-                username: user.username
-            }
-        }
-
-        addLog(messageReaction.message.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/messageReactionRemove.ts b/src/events:TODO/messageReactionRemove.ts
deleted file mode 100644
index 685ad37..0000000
--- a/src/events:TODO/messageReactionRemove.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'messageReactionRemove',
-    once:false,
-    async execute(messageReaction, user) {
-        let data = {
-            messageReaction: {
-                messageID: messageReaction.message.id,
-                reactionEmoji: {
-                    name: messageReaction.emoji.name,
-                    id: messageReaction.emoji.id
-                },
-                removedAt: Date.now()
-            },
-            user: {
-                id: user.id,
-                username: user.username
-            }
-        }
-
-        addLog(messageReaction.message.guild.id, data);
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/messageReactionRemoveAll.ts b/src/events:TODO/messageReactionRemoveAll.ts
deleted file mode 100644
index e4254da..0000000
--- a/src/events:TODO/messageReactionRemoveAll.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'messageReactionRemoveAll',
-    once:false,
-    async execute(message, reactions) {
-        let data = {
-            messageID: message.id,
-            reactions: reactions.map(r => {
-                return {
-                    name: r.emoji.name,
-                    id: r.emoji.id
-                }
-            }),
-            removedAt: Date.now()
-        }
-
-        addLog(message.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/messageReactionRemoveEmoji.ts b/src/events:TODO/messageReactionRemoveEmoji.ts
deleted file mode 100644
index 9a709f8..0000000
--- a/src/events:TODO/messageReactionRemoveEmoji.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'messageReactionRemoveEmoji',
-    once:false,
-    async execute(messageReaction) {
-        let data = {
-            name: messageReaction.emoji.name,
-            id: messageReaction.emoji.id,
-            removedAt: Date.now()
-        }
-
-        addLog(messageReaction.message.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/stageInstanceCreate.ts b/src/events:TODO/stageInstanceCreate.ts
deleted file mode 100644
index 1f56418..0000000
--- a/src/events:TODO/stageInstanceCreate.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'stageInstanceCreate',
-    once:false,
-    async execute(stageInstance) {
-        let data = {
-            id: stageInstance.id,
-            channel: stageInstance.channel.id,
-            channelName: stageInstance.channel.name,
-            createdAt: stageInstance.createdTimestamp,
-            topic: stageInstance.topic,
-            discoverable: !stageInstance.discoverableDisabled,
-            privacy: stageInstance.privacyLevel
-        }
-
-        addLog(role.guild.id, data);
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/stageInstanceDelete.ts b/src/events:TODO/stageInstanceDelete.ts
deleted file mode 100644
index e8e0e78..0000000
--- a/src/events:TODO/stageInstanceDelete.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-
-module.exports = {
-    name:'stageInstanceDelete',
-    once:false,
-    async execute(stageInstance) {
-
-        let logs = await stageInstance.guild.fetchAuditLogs({type: 'STAGE_INSTANCE_DELETE'});
-        let entry = logs.entries.find(e => e.target.id === stageInstance.id);
-
-        let data = {
-            id: stageInstance.id,
-            channel: stageInstance.channel.id,
-            channelName: stageInstance.channel.name,
-            deletedAt: entry.createdTimestamp,
-            deletedBy: entry.deletedBy,
-            topic: stageInstance.topic,
-            discoverable: !stageInstance.discoverableDisabled,
-            privacy: stageInstance.privacyLevel
-        }
-
-        addLog(role.guild.id, data);
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/stageInstanceUpdate.ts b/src/events:TODO/stageInstanceUpdate.ts
deleted file mode 100644
index e516915..0000000
--- a/src/events:TODO/stageInstanceUpdate.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-
-module.exports = {
-    name:'stageInstanceUpdate',
-    once:false,
-    async execute(oldStage, newStage) {
-        let os = {
-            id: oldStage.id,
-            channel: oldStage.channel.id,
-            channelName: oldStage.channel.name,
-            topic: oldStage.topic,
-            discoverable: !oldStage.discoverableDisabled,
-            privacy: oldStage.privacyLevel
-        }
-
-        let ns = {
-            id: newStage.id,
-            channel: newStage.channel.id,
-            channelName: newStage.channel.name,
-            topic: newStage.topic,
-            discoverable: !newStage.discoverableDisabled,
-            privacy: newStage.privacyLevel
-        }
-
-        let data = JsonDiff.diff(os, ns, {full: true});
-
-        addLog(role.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/stickerCreate.ts b/src/events:TODO/stickerCreate.ts
deleted file mode 100644
index c968c2a..0000000
--- a/src/events:TODO/stickerCreate.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-module.exports = {
-    name:'stickerCreate',
-    once:false,
-    async execute(sticker) {
-        let data = {
-            id: sticker.id,
-            createdAt: sticker.createdTimestamp,
-            description: sticker.description,
-            name: sticker.name,
-            type: sticker.type,
-            tags: sticker.tags,
-            createdBy: sticker.user.id
-        }
-
-        addLog(role.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/stickerDelete.ts b/src/events:TODO/stickerDelete.ts
deleted file mode 100644
index 67654f3..0000000
--- a/src/events:TODO/stickerDelete.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-module.exports = {
-    name:'stickerDelete',
-    once:false,
-    async execute(sticker) {
-
-        let logs = await sticker.guild.fetchAuditLogs({type: 'STICKER_DELETE'});
-        let entry = logs.entries.find(e => e.target.id === sticker.id);
-
-        let data = {
-            id: sticker.id,
-            deletedAt: entry.createdTimestamp,
-            description: sticker.description,
-            name: sticker.name,
-            type: sticker.type,
-            tags: sticker.tags,
-            deletedBy: entry.executor.id
-        }
-
-        addLog(role.guild.id, data);
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/stickerUpdate.ts b/src/events:TODO/stickerUpdate.ts
deleted file mode 100644
index 33e4cdf..0000000
--- a/src/events:TODO/stickerUpdate.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-module.exports = {
-    name:'stickerUpdate',
-    once:false,
-    async execute(oldSticker, newSticker) {
-        let os = {
-            id: sticker.id,
-            description: sticker.description,
-            name: sticker.name,
-            type: sticker.type,
-            tags: sticker.tags,
-        }
-
-        let ns = {
-            id: sticker.id,
-            description: sticker.description,
-            name: sticker.name,
-            type: sticker.type,
-            tags: sticker.tags,
-        }
-
-        let data = JsonDiff.diff(os, ns, {full: true});
-
-        addLog(role.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/threadCreate.ts b/src/events:TODO/threadCreate.ts
deleted file mode 100644
index 63c29a4..0000000
--- a/src/events:TODO/threadCreate.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-module.exports = {
-    name:'threadCreate',
-    once:false,
-    async execute(thread) {
-
-        let data = {
-            autoArchiveDuration: thread.autoArchiveDuration,
-            id: thread.id,
-            locked: thread.locked,
-            name: thread.name,
-            parentChannel: thread.parent.id,
-            slowmode: thread.rateLimitPerUser,
-            type: thread.type,
-            createdAt: thread.createdTimestamp,
-            createdBy: thread.ownerId
-        }
-
-        addLog(thread.guild.id, data);
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/threadDelete.ts b/src/events:TODO/threadDelete.ts
deleted file mode 100644
index 43cba66..0000000
--- a/src/events:TODO/threadDelete.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-module.exports = {
-    name:'threadDelete',
-    once:false,
-    async execute(thread) {
-
-        let logs = await thread.guild.fetchAuditLogs({type: 'THREAD_DELETE'});
-        let entry = logs.entries.find(e => e.target.id === thread.id);
-
-        let data = {
-            autoArchiveDuration: thread.autoArchiveDuration,
-            id: thread.id,
-            locked: thread.locked,
-            name: thread.name,
-            parentChannel: thread.parent.id,
-            slowmode: thread.rateLimitPerUser,
-            type: thread.type,
-            deletedAt: entry.createdTimestamp,
-            deletedBy: entry.executor.id
-        }
-
-        addLog(thread.guild.id, data);
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/threadMembersUpdate.ts b/src/events:TODO/threadMembersUpdate.ts
deleted file mode 100644
index fab4ef5..0000000
--- a/src/events:TODO/threadMembersUpdate.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-module.exports = {
-    name:'threadMembersUpdate',
-    once:false,
-    async execute(oldMembers, newMembers) {
-        
-        let om = oldMembers.map(m => m.id);
-        let nm = newMembers.map(m => m.id);
-        let data = JsonDiff.diff(om, nm);
-
-        addLog(newMembers.first().guild.id, data);
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/threadUpdate.ts b/src/events:TODO/threadUpdate.ts
deleted file mode 100644
index cfaed14..0000000
--- a/src/events:TODO/threadUpdate.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-module.exports = {
-    name:'threadUpdate',
-    once:false,
-    async execute(oldThread, newThread) {
-        let ot = {
-            autoArchiveDuration: oldThread.autoArchiveDuration,
-            id: oldThread.id,
-            locked: oldThread.locked,
-            name: oldThread.name,
-            parentChannel: oldThread.parent.id,
-            slowmode: oldThread.rateLimitPerUser,
-            type: oldThread.type,
-        }
-        let nt = {
-            autoArchiveDuration: newThread.autoArchiveDuration,
-            id: newThread.id,
-            locked: newThread.locked,
-            name: newThread.name,
-            parentChannel: newThread.parent.id,
-            slowmode: newThread.rateLimitPerUser,
-            type: newThread.type,
-        }
-
-        let data = JsonDiff.diff(ot, nt, {full: true});
-
-        addLog(newThread.guild.id, data);
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/userUpdate.ts b/src/events:TODO/userUpdate.ts
deleted file mode 100644
index a6735c8..0000000
--- a/src/events:TODO/userUpdate.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-module.exports = {
-    name:'userUpdate',
-    once:false,
-    async execute(args) {
-
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/voiceStateUpdate.ts b/src/events:TODO/voiceStateUpdate.ts
deleted file mode 100644
index 3754f0c..0000000
--- a/src/events:TODO/voiceStateUpdate.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-module.exports = {
-    name:'voiceStateUpdate',
-    once:false,
-    async execute(oldState, newState) {
-        let os = {
-            channel:oldState.channel ? oldState.channel.id : null,
-            serverDeaf:oldState.serverDeaf,
-            serverMute:oldState.serverMute,
-            selfDeaf:oldState.selfDeaf,
-            selfMute:oldState.selfMute,
-            selfVideo:oldState.selfVideo,
-            streaming:oldState.streaming,
-            id:oldState.id,
-            requestToSpeakTimestamp:oldState.requestToSpeakTimestamp
-        }
-
-        let ns = {
-            channel:newState.channel ? newState.channel.id : null,
-            serverDeaf:newState.serverDeaf,
-            serverMute:newState.serverMute,
-            selfDeaf:newState.selfDeaf,
-            selfMute:newState.selfMute,
-            selfVideo:newState.selfVideo,
-            streaming:newState.streaming,
-            id:newState.id,
-            requestToSpeakTimestamp:newState.requestToSpeakTimestamp
-        }
-
-        let data = JsonDiff.diff(os, ns, {full: true});
-
-        addLog(oldState.guild.id, data);
-    }
-}
\ No newline at end of file
diff --git a/src/events:TODO/webhookUpdate.ts b/src/events:TODO/webhookUpdate.ts
deleted file mode 100644
index 56affc4..0000000
--- a/src/events:TODO/webhookUpdate.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-const {addLog} = require('../scripts/addLogs');
-const JsonDiff = require('json-diff');
-module.exports = {
-    name:'webhookUpdate',
-    once:false,
-    async execute(args) {
-
-    }
-}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 3b735cb..306c5ee 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,15 +2,22 @@
 import { Intents } from 'discord.js';
 import config from './config/main.json' assert {type: 'json'};
 import { Logger } from './utils/log.js';
+import runServer from './api/index.js';
+import Memory from './utils/memory.js';
+
 const client = new HaikuClient({
     intents: new Intents(32767).bitfield,  // This is a way of specifying all intents w/o having to type them out
 }, config);
 
 await client.registerCommandsIn("./commands");
 await client.registerEventsIn("./events");
+client.on("ready", () => {
+    runServer(client);
+});
 
 client.logger = new Logger()
 client.verify = {}
 client.roleMenu = {}
+client.memory = new Memory()
 
 await client.login();
\ No newline at end of file
diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts
index 174c401..26c231c 100644
--- a/src/utils/calculate.ts
+++ b/src/utils/calculate.ts
@@ -59,8 +59,8 @@
 }
 
 export {
-	toHexInteger,
-	toHexArray,
+    toHexInteger,
+    toHexArray,
     tickets,
     logs
 }
\ No newline at end of file
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index fc8b76c..f5c322b 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -65,7 +65,7 @@
                         .setLabel(this.customButtonTitle)
                         .setStyle("SECONDARY")
                         .setDisabled(this.customButtonDisabled)
-                        .setEmoji(getEmojiByName("CONTROL.RIGHT", "id")) // TODO: add an emoji
+                        .setEmoji(getEmojiByName("CONTROL.TICKET", "id"))
                     ] : []))
                 ],
                 ephemeral: true,
@@ -81,7 +81,7 @@
             try {
                 component = await (m as Message).awaitMessageComponent({filter: (m) => m.user.id === this.interaction.user.id, time: 2.5 * 60 * 1000});
             } catch (e) {
-                return {success: false, buttonClicked: this.customCallbackClicked, response: this.customCallbackResponse};  // TODO: Check the type of the error; change the error message here
+                return {success: false, buttonClicked: this.customCallbackClicked, response: this.customCallbackResponse};
             }
             if (component.customId === "yes") {
                 component.deferUpdate();
diff --git a/src/utils/generateConfig.ts b/src/utils/generateConfig.ts
index 6dc4e52..39b28b0 100644
--- a/src/utils/generateConfig.ts
+++ b/src/utils/generateConfig.ts
@@ -48,43 +48,43 @@
             messageType: "embed",
         },
         filters: {
-			images: {
-				NSFW: true,
-				size: true
-			},
-			malware: true,
-			wordFilter: {
-				enabled: true,
-				words: {
-					strict: [],
-					loose: []
-				},
-				allowed: {
-					users: [],
-					roles: [],
-					channels: []
-				}
-			},
-			invite: {
-				enabled: true,
-				allowed: {
-					users: [],
-					channels: [],
-					roles: []
-				}
-			},
-			pings: {
-				mass: 5,
-				everyone: true,
-				roles: true,
-				allowed: {
-					roles: [],
-					rolesToMention: [],
-					users: [],
-					channels: []
-				}
-			}
-		},
+            images: {
+                NSFW: true,
+                size: true
+            },
+            malware: true,
+            wordFilter: {
+                enabled: true,
+                words: {
+                    strict: [],
+                    loose: []
+                },
+                allowed: {
+                    users: [],
+                    roles: [],
+                    channels: []
+                }
+            },
+            invite: {
+                enabled: true,
+                allowed: {
+                    users: [],
+                    channels: [],
+                    roles: []
+                }
+            },
+            pings: {
+                mass: 5,
+                everyone: true,
+                roles: true,
+                allowed: {
+                    roles: [],
+                    rolesToMention: [],
+                    users: [],
+                    channels: []
+                }
+            }
+        },
         tags: {}
     }));
 }
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 14e9750..238b6f4 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -10,80 +10,82 @@
 
 
 export class Logger {
-	renderUser(user: Discord.User | string) {
-		if (typeof user == 'string') return `${user} [<@${user}>]`;
-		return `${user.username} [<@${user.id}>]`;
-	}
-	renderTime(t: number) {
-		t = Math.floor(t /= 1000)
-		return `<t:${t}:D> at <t:${t}:T>`;
-	}
-	renderDelta(t: number) {
-		t = Math.floor(t /= 1000)
-		return `<t:${t}:R> (<t:${t}:D> at <t:${t}:T>)`;
-	}
-	renderNumberDelta(num1, num2) {
-		let delta = num2 - num1;
-		return `${num1} -> ${num2} (${delta > 0 ? '+' : ''}${delta})`;
-	}
-	entry(value, displayValue) {
-		return { value: value, displayValue: displayValue }
-	}
-	renderChannel(channel: Discord.GuildChannel) {
-		return `${channel.name} [<#${channel.id}>]`;
-	}
-	renderRole(role: Discord.Role) {
-		return `${role.name} [<@&${role.id}>]`;
-	}
-	renderEmoji(emoji: Discord.GuildEmoji) {
-		return `<${emoji.animated ? 'a' : ''}:${emoji.name}:${emoji.id}> [\`:${emoji.name}:\`]`;
-	}
+    renderUser(user: Discord.User | string) {
+        if (typeof user == 'string') return `${user} [<@${user}>]`;
+        return `${user.username} [<@${user.id}>]`;
+    }
+    renderTime(t: number) {
+        t = Math.floor(t /= 1000)
+        return `<t:${t}:D> at <t:${t}:T>`;
+    }
+    renderDelta(t: number) {
+        t = Math.floor(t /= 1000)
+        return `<t:${t}:R> (<t:${t}:D> at <t:${t}:T>)`;
+    }
+    renderNumberDelta(num1, num2) {
+        let delta = num2 - num1;
+        return `${num1} -> ${num2} (${delta > 0 ? '+' : ''}${delta})`;
+    }
+    entry(value, displayValue) {
+        return { value: value, displayValue: displayValue }
+    }
+    renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | Discord.NewsChannel) {
+        return `${channel.name} [<#${channel.id}>]`;
+    }
+    renderRole(role: Discord.Role) {
+        return `${role.name} [<@&${role.id}>]`;
+    }
+    renderEmoji(emoji: Discord.GuildEmoji) {
+        return `<${emoji.animated ? 'a' : ''}:${emoji.name}:${emoji.id}> [\`:${emoji.name}:\`]`;
+    }
 
-	public readonly NucleusColors = {
-		red: 0xF27878,
-		yellow: 0xF2D478,
-		green: 0x68D49E,
+    public readonly NucleusColors = {
+        red: 0xF27878,
+        yellow: 0xF2D478,
+        green: 0x68D49E,
 
-	}
+    }
 
-	async getAuditLog(guild: Discord.Guild, event) {
-		await wait(250)
-		let auditLog = await guild.fetchAuditLogs({type: event});
-		return auditLog;
-	}
+    async getAuditLog(guild: Discord.Guild, event) {
+        await wait(250)
+        let auditLog = await guild.fetchAuditLogs({type: event});
+        return auditLog;
+    }
 
-	async log(log: any, client): Promise<void> {
-		let config = await readConfig(log.hidden.guild);
-		if (!config.logging.logs.enabled) return;
-		if (!(log.meta.calculateType == true)) if(!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) return console.log('Not logging this type of event');
-		if (config.logging.logs.channel) {
-			let channel = await client.channels.fetch(config.logging.logs.channel) as Discord.TextChannel;
-			let description = {};
-			Object.entries(log.list).map(entry => {
-				let key = entry[0];
-				let value:any = entry[1];
-				if(value.displayValue) {
-					description[key] = value.displayValue;
-				} else {
-					description[key] = value;
-				}
-			})
-			if (channel) {
-				log.separate = log.separate || {};
-				let embed = new Discord.MessageEmbed()
-					.setTitle(`${getEmojiByName(log.meta.emoji)} ${log.meta.displayName}`)
-					.setDescription(
-						(log.separate.start ? log.separate.start + "\n" : "") +
-						generateKeyValueList(description) +
-						(log.separate.end ? "\n" + log.separate.end : "")
-					)
-					.setTimestamp(log.meta.timestamp)
-					.setColor(log.meta.color);
-				channel.send({embeds: [embed]});
-			}
-		}
-		saveLog(log);
-	}
+    async log(log: any, client): Promise<void> {
+        let config = await readConfig(log.hidden.guild);
+        if (!config.logging.logs.enabled) return;
+        if (!(log.meta.calculateType == true)) {
+            if(!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) return console.log('Not logging this type of event');
+        }
+        if (config.logging.logs.channel) {
+            let channel = await client.channels.fetch(config.logging.logs.channel) as Discord.TextChannel;
+            let description = {};
+            Object.entries(log.list).map(entry => {
+                let key = entry[0];
+                let value:any = entry[1];
+                if(value.displayValue) {
+                    description[key] = value.displayValue;
+                } else {
+                    description[key] = value;
+                }
+            })
+            if (channel) {
+                log.separate = log.separate || {};
+                let embed = new Discord.MessageEmbed()
+                    .setTitle(`${getEmojiByName(log.meta.emoji)} ${log.meta.displayName}`)
+                    .setDescription(
+                        (log.separate.start ? log.separate.start + "\n" : "") +
+                        generateKeyValueList(description) +
+                        (log.separate.end ? "\n" + log.separate.end : "")
+                    )
+                    .setTimestamp(log.meta.timestamp)
+                    .setColor(log.meta.color);
+                channel.send({embeds: [embed]});
+            }
+        }
+        saveLog(log);
+    }
 }
 
 
@@ -93,9 +95,9 @@
 }
 
 export function readLogs(guild: string) {
-	
+    
 }
 
 export function readSpecificLog(guild: string, id: number) {
-	
+    
 }
diff --git a/src/utils/memory.ts b/src/utils/memory.ts
new file mode 100644
index 0000000..a4f9501
--- /dev/null
+++ b/src/utils/memory.ts
@@ -0,0 +1,22 @@
+import readConfig from "./readConfig.js";
+
+class Memory {
+    memory: {};
+    constructor() {
+        this.memory = {};
+    }
+
+    async readGuildInfo(guild: string): Promise<object> {
+        if (!this.memory[guild]) {
+            let guildData = await readConfig(guild);
+            this.memory[guild] = {
+                filters: guildData.filters,
+                logging: guildData.logging,
+                tickets: guildData.tickets,
+            }; // TODO: REMOVE GUILD FROM MEMORY WHEN THESE UPDATE
+        }
+        return this.memory[guild];
+    }
+}
+
+export default Memory;
\ No newline at end of file
diff --git a/src/utils/plurals.ts b/src/utils/plurals.ts
new file mode 100644
index 0000000..f956057
--- /dev/null
+++ b/src/utils/plurals.ts
@@ -0,0 +1,6 @@
+function addPlural(amount: any, unit: string) {
+    if (amount === '1') return `${amount} ${unit}`
+    return `${amount} ${unit}s`
+}
+
+export default addPlural;
\ No newline at end of file
diff --git a/src/utils/readConfig.ts b/src/utils/readConfig.ts
index fb6835b..50cb6c6 100644
--- a/src/utils/readConfig.ts
+++ b/src/utils/readConfig.ts
@@ -85,13 +85,13 @@
         moderation: {
             mute: {
                 timeout: true,
-                role: null, // TODO: actually give it
+                role: "934941369137524816", // TODO: Remove this role after the time
                 text: null,
                 link: null
             },
             kick: {
                 text: "Appeal here",
-                link: "https://clicksminuteper.net"
+                link: "https://clicks.codes"
             },
             ban: {
                 text: null,
diff --git a/src/utils/scanners.ts b/src/utils/scanners.ts
index 97e9bf4..5abd726 100644
--- a/src/utils/scanners.ts
+++ b/src/utils/scanners.ts
@@ -8,23 +8,23 @@
 const __dirname = path.dirname(__filename);
 
 export async function testNSFW(link: string): Promise<JSON> {
-	const image = (await (await fetch(link)).buffer()).toString('base64')
-	let fileName = generateFileName(link.split('/').pop().split('.').pop())
-	let p = path.join(__dirname, '/temp', fileName)
-	writeFileSync(p, image, 'base64')
-	let result = await us.nsfw.file(p)
+    const image = (await (await fetch(link)).buffer()).toString('base64')
+    let fileName = generateFileName(link.split('/').pop().split('.').pop())
+    let p = path.join(__dirname, '/temp', fileName)
+    writeFileSync(p, image, 'base64')
+    let result = await us.nsfw.file(p)
     return result
 }
 
 export async function testMalware(link: string): Promise<JSON> {
-	const file = (await (await fetch(link)).buffer()).toString('base64')
-	let fileName = generateFileName(link.split('/').pop().split('.').pop())
-	let p = path.join(__dirname, '/temp', fileName)
-	writeFileSync(p, file, 'base64')
-	let result = await us.malware.file(p)
+    const file = (await (await fetch(link)).buffer()).toString('base64')
+    let fileName = generateFileName(link.split('/').pop().split('.').pop())
+    let p = path.join(__dirname, '/temp', fileName)
+    writeFileSync(p, file, 'base64')
+    let result = await us.malware.file(p)
     return result
 }
 
 export async function testLink(link: string): Promise<JSON> {
-	return await us.link.scan(link)
+    return await us.link.scan(link)
 }
diff --git a/yarn-error.log b/yarn-error.log
new file mode 100644
index 0000000..372c536
--- /dev/null
+++ b/yarn-error.log
@@ -0,0 +1,74 @@
+Arguments: 
+  /home/pineapplefan/.nvm/versions/node/v18.0.0/bin/node /usr/bin/yarn add bodyparser
+
+PATH: 
+  /home/pineapplefan/.pyenv/shims:/home/pineapplefan/.pyenv/bin:/home/pineapplefan/.nvm/versions/node/v18.0.0/bin:/home/pineapplefan/.pyenv/shims:/home/pineapplefan/.pyenv/bin:/home/pineapplefan/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
+
+Yarn version: 
+  1.22.18
+
+Node version: 
+  18.0.0
+
+Platform: 
+  linux x64
+
+Trace: 
+  Error: https://registry.yarnpkg.com/bodyparser: Not found
+      at params.callback [as _callback] (/usr/lib/node_modules/yarn/lib/cli.js:66138:18)
+      at self.callback (/usr/lib/node_modules/yarn/lib/cli.js:140883:22)
+      at Request.emit (node:events:527:28)
+      at Request.<anonymous> (/usr/lib/node_modules/yarn/lib/cli.js:141855:10)
+      at Request.emit (node:events:527:28)
+      at IncomingMessage.<anonymous> (/usr/lib/node_modules/yarn/lib/cli.js:141777:12)
+      at Object.onceWrapper (node:events:641:28)
+      at IncomingMessage.emit (node:events:539:35)
+      at endReadableNT (node:internal/streams/readable:1344:12)
+      at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
+
+npm manifest: 
+  {
+    "dependencies": {
+      "@discordjs/builders": "^0.12.0",
+      "discord.js": "^13.6.0",
+      "express": "^4.18.1",
+      "humanize": "^0.0.9",
+      "humanize-duration": "^3.27.1",
+      "jshaiku": "file:../haiku",
+      "json-diff": "^0.7.1",
+      "tesseract.js": "^2.1.5",
+      "typescript": "^4.5.5",
+      "unscan": "^1.1.2"
+    },
+    "name": "nucleus",
+    "version": "0.0.1",
+    "description": "Nucleus: The core Clicks bot",
+    "main": "dist/index.js",
+    "scripts": {
+      "build": "tsc",
+      "start": "node --experimental-json-modules dist/index.js",
+      "dev": "rm -rf dist && tsc && node --experimental-json-modules dist/index.js"
+    },
+    "repository": {
+      "type": "git",
+      "url": "git+ssh://git@github.com/ClicksMinutePer/Nucleus.git"
+    },
+    "author": "Clicks",
+    "contributors": [
+      "Minion3665",
+      "PineappleFan"
+    ],
+    "license": "SEE LICENSE IN LICENSE",
+    "bugs": {
+      "url": "https://github.com/ClicksMinutePer/Nucleus/issues"
+    },
+    "homepage": "https://github.com/ClicksMinutePer/Nucleus#readme",
+    "private": true,
+    "type": "module"
+  }
+
+yarn manifest: 
+  No manifest
+
+Lockfile: 
+  No lockfile
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..5681bac
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,989 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@discordjs/builders@^0.11.0":
+  version "0.11.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-0.11.0.tgz#4102abe3e0cd093501f3f71931df43eb92f5b0cc"
+  integrity sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg==
+  dependencies:
+    "@sindresorhus/is" "^4.2.0"
+    discord-api-types "^0.26.0"
+    ts-mixer "^6.0.0"
+    tslib "^2.3.1"
+    zod "^3.11.6"
+
+"@discordjs/builders@^0.12.0":
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-0.12.0.tgz#5f6d95d1b231fa975a7e28ca3f8098e517676887"
+  integrity sha512-Vx2MjUZd6QVo1uS2uWt708Fd6cHWGFblAvbpL5EBO+kLl0BADmPwwvts+YJ/VfSywed6Vsk6K2cEooR/Ytjhjw==
+  dependencies:
+    "@sindresorhus/is" "^4.3.0"
+    discord-api-types "^0.26.1"
+    ts-mixer "^6.0.0"
+    tslib "^2.3.1"
+    zod "^3.11.6"
+
+"@discordjs/collection@^0.3.2":
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.3.2.tgz#3c271dd8a93dad89b186d330e24dbceaab58424a"
+  integrity sha512-dMjLl60b2DMqObbH1MQZKePgWhsNe49XkKBZ0W5Acl5uVV43SN414i2QfZwRI7dXAqIn8pEWD2+XXQFn9KWxqg==
+
+"@discordjs/collection@^0.4.0":
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.4.0.tgz#b6488286a1cc7b41b644d7e6086f25a1c1e6f837"
+  integrity sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==
+
+"@discordjs/rest@^0.2.0-canary.0":
+  version "0.2.0-canary.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-0.2.0-canary.0.tgz#5ac955614453d02808c4df35a5cfb28d146566b2"
+  integrity sha512-jOxz1aqTEzn9N0qaJcZbHz6FbA0oq+vjpXUKkQzgfMihO6gC+kLlpRnFqG25T/aPYbjaR1UM/lGhrGBB1dutqg==
+  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"
+
+"@sapphire/async-queue@^1.1.9":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.3.1.tgz#9d861e626dbffae02d808e13f823d4510e450a78"
+  integrity sha512-FFTlPOWZX1kDj9xCAsRzH5xEJfawg1lNoYAA+ecOWJMHOfiZYb1uXOI3ne9U4UILSEPwfE68p3T9wUHwIQfR0g==
+
+"@sapphire/snowflake@^3.0.0":
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.2.2.tgz#faacdc1b5f7c43145a71eddba917de2b707ef780"
+  integrity sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ==
+
+"@sindresorhus/is@^4.2.0", "@sindresorhus/is@^4.3.0":
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
+  integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
+
+"@types/node-cron@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.1.tgz#e01a874d4c2aa1a02ebc64cfd1cd8ebdbad7a996"
+  integrity sha512-BkMHHonDT8NJUE/pQ3kr5v2GLDKm5or9btLBoBx4F2MB2cuqYC748LYMDC55VlrLI5qZZv+Qgc3m4P3dBPcmeg==
+
+"@types/node-fetch@^2.5.12":
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975"
+  integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==
+  dependencies:
+    "@types/node" "*"
+    form-data "^3.0.0"
+
+"@types/node@*":
+  version "17.0.31"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.31.tgz#a5bb84ecfa27eec5e1c802c6bbf8139bdb163a5d"
+  integrity sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==
+
+"@types/ws@^8.2.2":
+  version "8.5.3"
+  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
+  integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==
+  dependencies:
+    "@types/node" "*"
+
+accepts@~1.3.8:
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+  integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+  dependencies:
+    mime-types "~2.1.34"
+    negotiator "0.6.3"
+
+ansi-styles@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
+  integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
+
+array-flatten@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+  integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
+
+asynckit@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+  integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+
+balanced-match@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+blueimp-load-image@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/blueimp-load-image/-/blueimp-load-image-3.0.0.tgz#d71c39440a7d2f1a83e3e86a625e329116a51705"
+  integrity sha512-Q9rFbd4ZUNvzSFmRXx9MoG0RwWwJeMjjEUbG7WIOJgUg22Jgkow0wL5b35B6qwiBscxACW9OHdrP5s2vQ3x8DQ==
+
+bmp-js@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233"
+  integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM=
+
+body-parser@1.20.0, body-parser@^1.20.0:
+  version "1.20.0"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5"
+  integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==
+  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.10.3"
+    raw-body "2.5.1"
+    type-is "~1.6.18"
+    unpipe "1.0.0"
+
+brace-expansion@^1.1.7:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+bytes@3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+  integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+call-bind@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+  integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+  dependencies:
+    function-bind "^1.1.1"
+    get-intrinsic "^1.0.2"
+
+chalk@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6"
+  integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==
+
+cli-color@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.2.tgz#e295addbae470800def0254183c648531cdf4e3f"
+  integrity sha512-g4JYjrTW9MGtCziFNjkqp3IMpGhnJyeB0lOtRPjQkYhXzKYr6tYnXKyEVnMzITxhpbahsEW9KsxOYIDKwcsIBw==
+  dependencies:
+    d "^1.0.1"
+    es5-ext "^0.10.59"
+    es6-iterator "^2.0.3"
+    memoizee "^0.4.15"
+    timers-ext "^0.1.7"
+
+colors@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
+  integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
+
+combined-stream@^1.0.8:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+  integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+  dependencies:
+    delayed-stream "~1.0.0"
+
+commander@^8.3.0:
+  version "8.3.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
+  integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
+
+concat-map@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+content-disposition@0.5.4:
+  version "0.5.4"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+  integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+  dependencies:
+    safe-buffer "5.2.1"
+
+content-type@~1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+  integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
+
+cookie-signature@1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+  integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
+
+cookie@0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
+  integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+
+d@1, d@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
+  integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
+  dependencies:
+    es5-ext "^0.10.50"
+    type "^1.0.1"
+
+debug@2.6.9:
+  version "2.6.9"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+  integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+  dependencies:
+    ms "2.0.0"
+
+delayed-stream@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+  integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+
+depd@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+  integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+destroy@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+  integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+
+difflib@~0.2.1:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e"
+  integrity sha1-teMDYabbAjF21WKJLbhZQKcY9H4=
+  dependencies:
+    heap ">= 0.2.0"
+
+discord-api-types@^0.25.2:
+  version "0.25.2"
+  resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.25.2.tgz#e50ed152e6d48fe7963f5de1002ca6f2df57c61b"
+  integrity sha512-O243LXxb5gLLxubu5zgoppYQuolapGVWPw3ll0acN0+O8TnPUE2kFp9Bt3sTRYodw8xFIknOVxjSeyWYBpVcEQ==
+
+discord-api-types@^0.26.0, discord-api-types@^0.26.1:
+  version "0.26.1"
+  resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.26.1.tgz#726f766ddc37d60da95740991d22cb6ef2ed787b"
+  integrity sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==
+
+discord.js@^13.5.1, discord.js@^13.6.0:
+  version "13.6.0"
+  resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.6.0.tgz#d8a8a591dbf25cbcf9c783d5ddf22c4694860475"
+  integrity sha512-tXNR8zgsEPxPBvGk3AQjJ9ljIIC6/LOPjzKwpwz8Y1Q2X66Vi3ZqFgRHYwnHKC0jC0F+l4LzxlhmOJsBZDNg9g==
+  dependencies:
+    "@discordjs/builders" "^0.11.0"
+    "@discordjs/collection" "^0.4.0"
+    "@sapphire/async-queue" "^1.1.9"
+    "@types/node-fetch" "^2.5.12"
+    "@types/ws" "^8.2.2"
+    discord-api-types "^0.26.0"
+    form-data "^4.0.0"
+    node-fetch "^2.6.1"
+    ws "^8.4.0"
+
+dreamopt@~0.8.0:
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/dreamopt/-/dreamopt-0.8.0.tgz#5bcc80be7097e45fc489c342405ab68140a8c1d9"
+  integrity sha1-W8yAvnCX5F/EicNCQFq2gUCowdk=
+  dependencies:
+    wordwrap ">=0.0.2"
+
+ee-first@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+  integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
+
+encodeurl@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+  integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+
+es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.59, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
+  version "0.10.61"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.61.tgz#311de37949ef86b6b0dcea894d1ffedb909d3269"
+  integrity sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==
+  dependencies:
+    es6-iterator "^2.0.3"
+    es6-symbol "^3.1.3"
+    next-tick "^1.1.0"
+
+es6-iterator@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+  integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
+  dependencies:
+    d "1"
+    es5-ext "^0.10.35"
+    es6-symbol "^3.1.1"
+
+es6-symbol@^3.1.1, es6-symbol@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
+  integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
+  dependencies:
+    d "^1.0.1"
+    ext "^1.1.2"
+
+es6-weak-map@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53"
+  integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==
+  dependencies:
+    d "1"
+    es5-ext "^0.10.46"
+    es6-iterator "^2.0.3"
+    es6-symbol "^3.1.1"
+
+escape-html@~1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+  integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
+
+etag@~1.8.1:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+  integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+
+event-emitter@^0.3.5:
+  version "0.3.5"
+  resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+  integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+
+express@^4.18.1:
+  version "4.18.1"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf"
+  integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==
+  dependencies:
+    accepts "~1.3.8"
+    array-flatten "1.1.1"
+    body-parser "1.20.0"
+    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.10.3"
+    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"
+
+ext@^1.1.2:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52"
+  integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==
+  dependencies:
+    type "^2.5.0"
+
+file-type@^12.4.1:
+  version "12.4.2"
+  resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9"
+  integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==
+
+finalhandler@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
+  integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
+  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"
+
+form-data@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
+  integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.8"
+    mime-types "^2.1.12"
+
+form-data@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+  integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.8"
+    mime-types "^2.1.12"
+
+forwarded@0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
+  integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
+
+fresh@0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+  integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+
+fs.realpath@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+function-bind@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+  integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+get-intrinsic@^1.0.2:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
+  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
+  dependencies:
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
+
+glob@^7.1.6:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+  integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+has-symbols@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+  integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+has@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+  dependencies:
+    function-bind "^1.1.1"
+
+"heap@>= 0.2.0":
+  version "0.2.7"
+  resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
+  integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
+
+http-errors@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+  integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+  dependencies:
+    depd "2.0.0"
+    inherits "2.0.4"
+    setprototypeof "1.2.0"
+    statuses "2.0.1"
+    toidentifier "1.0.1"
+
+humanize-duration@^3.27.1:
+  version "3.27.1"
+  resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.27.1.tgz#2cd4ea4b03bd92184aee6d90d77a8f3d7628df69"
+  integrity sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA==
+
+humanize@^0.0.9:
+  version "0.0.9"
+  resolved "https://registry.yarnpkg.com/humanize/-/humanize-0.0.9.tgz#1994ffaecdfe9c441ed2bdac7452b7bb4c9e41a4"
+  integrity sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ=
+
+iconv-lite@0.4.24:
+  version "0.4.24"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+  integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3"
+
+idb-keyval@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-3.2.0.tgz#cbbf354deb5684b6cdc84376294fc05932845bd6"
+  integrity sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ==
+
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits@2, inherits@2.0.4:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ipaddr.js@1.9.1:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+  integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+is-electron@^2.2.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.1.tgz#751b1dd8a74907422faa5c35aaa0cf66d98086e9"
+  integrity sha512-r8EEQQsqT+Gn0aXFx7lTFygYQhILLCB+wn0WCDL5LZRINeLH/Rvw1j2oKodELLXYNImQ3CRlVsY8wW4cGOsyuw==
+
+is-promise@^2.2.2:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
+  integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
+
+is-url@^1.2.4:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
+  integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
+
+jpeg-autorotate@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/jpeg-autorotate/-/jpeg-autorotate-7.1.1.tgz#c57905c6afd3b54373a6a1d0249ed6e07f7b043b"
+  integrity sha512-ewTZTG/QWOM0D5h/yKcQ3QgyrnQYsr3qmcS+bqoAwgQAY1KBa31aJ+q+FlElaxo/rSYqfF1ixf+8EIgluBkgTg==
+  dependencies:
+    colors "^1.4.0"
+    glob "^7.1.6"
+    jpeg-js "^0.4.2"
+    piexifjs "^1.0.6"
+    yargs-parser "^20.2.1"
+
+jpeg-js@^0.4.2:
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b"
+  integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==
+
+"jshaiku@file:../haiku":
+  version "1.0.0"
+  dependencies:
+    "@discordjs/builders" "^0.11.0"
+    "@discordjs/rest" "^0.2.0-canary.0"
+    "@types/node-cron" "^3.0.1"
+    ansi-styles "^6.1.0"
+    chalk "^5.0.0"
+    discord-api-types "^0.26.1"
+    discord.js "^13.5.1"
+    node-cron "^3.0.0"
+
+json-diff@^0.7.1:
+  version "0.7.4"
+  resolved "https://registry.yarnpkg.com/json-diff/-/json-diff-0.7.4.tgz#b9089e2d29dd1b99cf3529dc1a5b72ca2ac7a8dc"
+  integrity sha512-FJ2P+ShDbzu9epF+kCKgoSUhPIUW7Ta7A4XlIT0L5LzgaR/z1TBF1mm0XhRGj8RlA3Xm0j+c/FsWOHDtuoYejA==
+  dependencies:
+    cli-color "^2.0.0"
+    difflib "~0.2.1"
+    dreamopt "~0.8.0"
+
+lru-queue@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
+  integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=
+  dependencies:
+    es5-ext "~0.10.2"
+
+media-typer@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+  integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+
+memoizee@^0.4.15:
+  version "0.4.15"
+  resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72"
+  integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==
+  dependencies:
+    d "^1.0.1"
+    es5-ext "^0.10.53"
+    es6-weak-map "^2.0.3"
+    event-emitter "^0.3.5"
+    is-promise "^2.2.2"
+    lru-queue "^0.1.0"
+    next-tick "^1.1.0"
+    timers-ext "^0.1.7"
+
+merge-descriptors@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+  integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+
+methods@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+  integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+
+mime-db@1.52.0:
+  version "1.52.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+  integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34:
+  version "2.1.35"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+  integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+  dependencies:
+    mime-db "1.52.0"
+
+mime@1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+  integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+minimatch@^3.0.4:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+moment-timezone@^0.5.31:
+  version "0.5.34"
+  resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
+  integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
+  dependencies:
+    moment ">= 2.9.0"
+
+"moment@>= 2.9.0":
+  version "2.29.3"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3"
+  integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==
+
+ms@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+  integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+negotiator@0.6.3:
+  version "0.6.3"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+  integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+next-tick@1, next-tick@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
+  integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
+
+node-cron@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.0.tgz#b33252803e430f9cd8590cf85738efa1497a9522"
+  integrity sha512-DDwIvvuCwrNiaU7HEivFDULcaQualDv7KoNlB/UU1wPW0n1tDEmBJKhEIE6DlF2FuoOHcNbLJ8ITL2Iv/3AWmA==
+  dependencies:
+    moment-timezone "^0.5.31"
+
+node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5:
+  version "2.6.7"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
+  integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
+  dependencies:
+    whatwg-url "^5.0.0"
+
+object-inspect@^1.9.0:
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
+  integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
+
+on-finished@2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+  integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+  dependencies:
+    ee-first "1.1.1"
+
+once@^1.3.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+  dependencies:
+    wrappy "1"
+
+opencollective-postinstall@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
+  integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
+
+parseurl@~1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+  integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-is-absolute@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+path-to-regexp@0.1.7:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+  integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+
+piexifjs@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/piexifjs/-/piexifjs-1.0.6.tgz#883811d73f447218d0d06e9ed7866d04533e59e0"
+  integrity sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag==
+
+proxy-addr@~2.0.7:
+  version "2.0.7"
+  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
+  integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+  dependencies:
+    forwarded "0.2.0"
+    ipaddr.js "1.9.1"
+
+qs@6.10.3:
+  version "6.10.3"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
+  integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
+  dependencies:
+    side-channel "^1.0.4"
+
+range-parser@~1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+  integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@2.5.1:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
+  integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
+  dependencies:
+    bytes "3.1.2"
+    http-errors "2.0.0"
+    iconv-lite "0.4.24"
+    unpipe "1.0.0"
+
+regenerator-runtime@^0.13.3:
+  version "0.13.9"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
+  integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+
+resolve-url@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
+  integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
+
+safe-buffer@5.2.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+"safer-buffer@>= 2.1.2 < 3":
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+  integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+send@0.18.0:
+  version "0.18.0"
+  resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
+  integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
+  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"
+
+serve-static@1.15.0:
+  version "1.15.0"
+  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
+  integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
+  dependencies:
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    parseurl "~1.3.3"
+    send "0.18.0"
+
+setprototypeof@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+  integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+side-channel@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+  integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+  dependencies:
+    call-bind "^1.0.0"
+    get-intrinsic "^1.0.2"
+    object-inspect "^1.9.0"
+
+statuses@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+  integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+tesseract.js-core@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.2.0.tgz#6ef78051272a381969fac3e45a226e85022cffef"
+  integrity sha512-a8L+OJTbUipBsEDsJhDPlnLB0TY1MkTZqw5dqUwmiDSjUzwvU7HWLg/2+WDRulKUi4LE+7PnHlaBlW0k+V0U0w==
+
+tesseract.js@^2.1.5:
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-2.1.5.tgz#2f757ff059f249721096fe9f94029c349650902c"
+  integrity sha512-7CIS3SWr7TXpeaH9+HS7iUtVbCfPFYOO3p6rkRAkdtsOtrbz6496x59na6SCbFAIaZulQxy8BjwSu3qL3AoDRg==
+  dependencies:
+    blueimp-load-image "^3.0.0"
+    bmp-js "^0.1.0"
+    file-type "^12.4.1"
+    idb-keyval "^3.2.0"
+    is-electron "^2.2.0"
+    is-url "^1.2.4"
+    jpeg-autorotate "^7.1.1"
+    node-fetch "^2.6.0"
+    opencollective-postinstall "^2.0.2"
+    regenerator-runtime "^0.13.3"
+    resolve-url "^0.2.1"
+    tesseract.js-core "^2.2.0"
+    zlibjs "^0.3.1"
+
+timers-ext@^0.1.7:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6"
+  integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==
+  dependencies:
+    es5-ext "~0.10.46"
+    next-tick "1"
+
+toidentifier@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+  integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+tr46@~0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+  integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
+
+ts-mixer@^6.0.0:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.1.tgz#7c2627fb98047eb5f3c7f2fee39d1521d18fe87a"
+  integrity sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg==
+
+tslib@^2.3.1:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
+  integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
+
+type-is@~1.6.18:
+  version "1.6.18"
+  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+  integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+  dependencies:
+    media-typer "0.3.0"
+    mime-types "~2.1.24"
+
+type@^1.0.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
+  integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
+
+type@^2.5.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/type/-/type-2.6.0.tgz#3ca6099af5981d36ca86b78442973694278a219f"
+  integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==
+
+typescript@^4.5.5:
+  version "4.6.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
+  integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
+
+unpipe@1.0.0, unpipe@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+  integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
+
+unscan@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/unscan/-/unscan-1.1.2.tgz#c9597178adc059de61a644d2dbf01aefa0067671"
+  integrity sha512-a5RcGaBFMO9l78QWKffeWUo2cvfqUv05JCXuphE8MFOA92qyqp1Da7isnR+zjJspi45+yS8tTSuhd0vV3asWdA==
+  dependencies:
+    commander "^8.3.0"
+    form-data "^4.0.0"
+    node-fetch "^2.6.5"
+
+utils-merge@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+  integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
+
+vary@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+  integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+
+webidl-conversions@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+  integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
+
+whatwg-url@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+  integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
+  dependencies:
+    tr46 "~0.0.3"
+    webidl-conversions "^3.0.0"
+
+wordwrap@>=0.0.2:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+  integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+ws@^8.4.0:
+  version "8.6.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
+  integrity sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==
+
+yargs-parser@^20.2.1:
+  version "20.2.9"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
+zlibjs@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/zlibjs/-/zlibjs-0.3.1.tgz#50197edb28a1c42ca659cc8b4e6a9ddd6d444554"
+  integrity sha1-UBl+2yihxCymWcyLTmqd3W1ERVQ=
+
+zod@^3.11.6:
+  version "3.14.4"
+  resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.4.tgz#e678fe9e5469f4663165a5c35c8f3dc66334a5d6"
+  integrity sha512-U9BFLb2GO34Sfo9IUYp0w3wJLlmcyGoMd75qU9yf+DrdGA4kEx6e+l9KOkAlyUO0PSQzZCa3TR4qVlcmwqSDuw==