i have not committed in years
diff --git a/package-lock.json b/package-lock.json
index c7fd036..3d75029 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
         "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"
       }
@@ -128,6 +129,30 @@
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
       "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
     },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+    },
+    "node_modules/blueimp-load-image": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/blueimp-load-image/-/blueimp-load-image-3.0.0.tgz",
+      "integrity": "sha512-Q9rFbd4ZUNvzSFmRXx9MoG0RwWwJeMjjEUbG7WIOJgUg22Jgkow0wL5b35B6qwiBscxACW9OHdrP5s2vQ3x8DQ=="
+    },
+    "node_modules/bmp-js": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
+      "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM="
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
     "node_modules/cli-color": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.1.tgz",
@@ -143,6 +168,14 @@
         "node": ">=0.10"
       }
     },
+    "node_modules/colors": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+      "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+      "engines": {
+        "node": ">=0.1.90"
+      }
+    },
     "node_modules/combined-stream": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -162,6 +195,11 @@
         "node": ">= 12"
       }
     },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
     "node_modules/d": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
@@ -311,6 +349,14 @@
       "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz",
       "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ=="
     },
+    "node_modules/file-type": {
+      "version": "12.4.2",
+      "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
+      "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/form-data": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -324,6 +370,30 @@
         "node": ">= 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/glob": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+      "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"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/heap": {
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
@@ -342,11 +412,63 @@
       "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz",
       "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA=="
     },
+    "node_modules/idb-keyval": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.2.0.tgz",
+      "integrity": "sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ=="
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dependencies": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "node_modules/is-electron": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.1.tgz",
+      "integrity": "sha512-r8EEQQsqT+Gn0aXFx7lTFygYQhILLCB+wn0WCDL5LZRINeLH/Rvw1j2oKodELLXYNImQ3CRlVsY8wW4cGOsyuw=="
+    },
     "node_modules/is-promise": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
       "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
     },
+    "node_modules/is-url": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+      "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
+    },
+    "node_modules/jpeg-autorotate": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/jpeg-autorotate/-/jpeg-autorotate-7.1.1.tgz",
+      "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"
+      },
+      "bin": {
+        "jpeg-autorotate": "src/cli.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/jpeg-js": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz",
+      "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q=="
+    },
     "node_modules/jshaiku": {
       "resolved": "../haiku",
       "link": true
@@ -409,6 +531,17 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/next-tick": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
@@ -433,6 +566,72 @@
         }
       }
     },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/opencollective-postinstall": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
+      "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
+      "bin": {
+        "opencollective-postinstall": "index.js"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/piexifjs": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
+      "integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
+    },
+    "node_modules/regenerator-runtime": {
+      "version": "0.13.9",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+    },
+    "node_modules/resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+      "deprecated": "https://github.com/lydell/resolve-url#deprecated"
+    },
+    "node_modules/tesseract.js": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-2.1.5.tgz",
+      "integrity": "sha512-7CIS3SWr7TXpeaH9+HS7iUtVbCfPFYOO3p6rkRAkdtsOtrbz6496x59na6SCbFAIaZulQxy8BjwSu3qL3AoDRg==",
+      "hasInstallScript": true,
+      "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"
+      }
+    },
+    "node_modules/tesseract.js-core": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-2.2.0.tgz",
+      "integrity": "sha512-a8L+OJTbUipBsEDsJhDPlnLB0TY1MkTZqw5dqUwmiDSjUzwvU7HWLg/2+WDRulKUi4LE+7PnHlaBlW0k+V0U0w=="
+    },
     "node_modules/timers-ext": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
@@ -506,6 +705,11 @@
       "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
       "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
     },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
     "node_modules/ws": {
       "version": "8.5.0",
       "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
@@ -526,6 +730,22 @@
         }
       }
     },
+    "node_modules/yargs-parser": {
+      "version": "20.2.9",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/zlibjs": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
+      "integrity": "sha1-UBl+2yihxCymWcyLTmqd3W1ERVQ=",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/zod": {
       "version": "3.13.4",
       "resolved": "https://registry.npmjs.org/zod/-/zod-3.13.4.tgz",
@@ -602,6 +822,30 @@
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
       "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
     },
+    "balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+    },
+    "blueimp-load-image": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/blueimp-load-image/-/blueimp-load-image-3.0.0.tgz",
+      "integrity": "sha512-Q9rFbd4ZUNvzSFmRXx9MoG0RwWwJeMjjEUbG7WIOJgUg22Jgkow0wL5b35B6qwiBscxACW9OHdrP5s2vQ3x8DQ=="
+    },
+    "bmp-js": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
+      "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM="
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
     "cli-color": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.1.tgz",
@@ -614,6 +858,11 @@
         "timers-ext": "^0.1.7"
       }
     },
+    "colors": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+      "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
+    },
     "combined-stream": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -627,6 +876,11 @@
       "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
       "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
     },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
     "d": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
@@ -756,6 +1010,11 @@
         }
       }
     },
+    "file-type": {
+      "version": "12.4.2",
+      "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
+      "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg=="
+    },
     "form-data": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -766,6 +1025,24 @@
         "mime-types": "^2.1.12"
       }
     },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "glob": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+      "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+      "requires": {
+        "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"
+      }
+    },
     "heap": {
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
@@ -781,11 +1058,57 @@
       "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz",
       "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA=="
     },
+    "idb-keyval": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.2.0.tgz",
+      "integrity": "sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ=="
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "is-electron": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.1.tgz",
+      "integrity": "sha512-r8EEQQsqT+Gn0aXFx7lTFygYQhILLCB+wn0WCDL5LZRINeLH/Rvw1j2oKodELLXYNImQ3CRlVsY8wW4cGOsyuw=="
+    },
     "is-promise": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
       "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
     },
+    "is-url": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+      "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
+    },
+    "jpeg-autorotate": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/jpeg-autorotate/-/jpeg-autorotate-7.1.1.tgz",
+      "integrity": "sha512-ewTZTG/QWOM0D5h/yKcQ3QgyrnQYsr3qmcS+bqoAwgQAY1KBa31aJ+q+FlElaxo/rSYqfF1ixf+8EIgluBkgTg==",
+      "requires": {
+        "colors": "^1.4.0",
+        "glob": "^7.1.6",
+        "jpeg-js": "^0.4.2",
+        "piexifjs": "^1.0.6",
+        "yargs-parser": "^20.2.1"
+      }
+    },
+    "jpeg-js": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz",
+      "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q=="
+    },
     "jshaiku": {
       "version": "file:../haiku",
       "requires": {
@@ -853,6 +1176,14 @@
         "mime-db": "1.51.0"
       }
     },
+    "minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
     "next-tick": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
@@ -866,6 +1197,64 @@
         "whatwg-url": "^5.0.0"
       }
     },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "opencollective-postinstall": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
+      "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="
+    },
+    "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="
+    },
+    "piexifjs": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
+      "integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
+    },
+    "regenerator-runtime": {
+      "version": "0.13.9",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+    },
+    "resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
+    },
+    "tesseract.js": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-2.1.5.tgz",
+      "integrity": "sha512-7CIS3SWr7TXpeaH9+HS7iUtVbCfPFYOO3p6rkRAkdtsOtrbz6496x59na6SCbFAIaZulQxy8BjwSu3qL3AoDRg==",
+      "requires": {
+        "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"
+      }
+    },
+    "tesseract.js-core": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-2.2.0.tgz",
+      "integrity": "sha512-a8L+OJTbUipBsEDsJhDPlnLB0TY1MkTZqw5dqUwmiDSjUzwvU7HWLg/2+WDRulKUi4LE+7PnHlaBlW0k+V0U0w=="
+    },
     "timers-ext": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
@@ -929,12 +1318,27 @@
       "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
       "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
     },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
     "ws": {
       "version": "8.5.0",
       "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
       "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
       "requires": {}
     },
+    "yargs-parser": {
+      "version": "20.2.9",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="
+    },
+    "zlibjs": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
+      "integrity": "sha1-UBl+2yihxCymWcyLTmqd3W1ERVQ="
+    },
     "zod": {
       "version": "3.13.4",
       "resolved": "https://registry.npmjs.org/zod/-/zod-3.13.4.tgz",
diff --git a/package.json b/package.json
index 053b2ee..8e5fc69 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
     "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"
   },
diff --git a/src/automations/statsChannelRemove.ts b/src/automations/statsChannelRemove.ts
index 42ec580..4b24768 100644
--- a/src/automations/statsChannelRemove.ts
+++ b/src/automations/statsChannelRemove.ts
@@ -1,8 +1,9 @@
 import log from '../utils/log.js'
 import readConfig from '../utils/readConfig.js'
 import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
+import singleNotify from '../utils/singleNotify.js';
 
-export async function callback(_, member) {
+export async function callback(interaction, member) {
     let config = await readConfig(member.guild.id);
 
     config.stats.forEach(async element => {
@@ -13,7 +14,12 @@
 
             let channel = await member.client.channels.fetch(element.channel)
             if (channel.guild.id !== member.guild.id) return
-            if (!channel) return // TODO: Notify mods
+            if (!channel) return await singleNotify(interaction.client,
+                "statsChannelDeleted",
+                member.guild.id,
+                "The stats channel has been deleted. Please set a new channel to use this feature.",
+                "Critical"
+            )
             try {
                 await channel.edit({ name: string })
             } catch (err) {
diff --git a/src/automations/unscan.ts b/src/automations/unscan.ts
index 1fb47f8..743374b 100644
--- a/src/automations/unscan.ts
+++ b/src/automations/unscan.ts
@@ -1,10 +1,12 @@
 import * as scan from '../utils/scanners.js'
+import Tesseract from 'tesseract.js';
 
 export async function LinkCheck(message): Promise<boolean> {
     let links = message.content.match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi) ?? []
     let detections = []
     const promises = links.map(async element => {
         try {
+            if (element.match(/https?:\/\/[a-zA-Z]+\.?discord(app)?\.(com|net)\/?/)) return // Also matches discord.net, not enough of a bug
             element = await scan.testLink(element)
         } catch {}
         detections.push({tags: element.tags || [], safe: element.safe})
@@ -27,9 +29,9 @@
 
 export async function NSFWCheck(element): Promise<boolean> {
     try {
+        let test = (await scan.testNSFW(element))
         //@ts-ignore
-        let test = (await scan.testNSFW(element)).nsfw
-        return test
+        return test.nsfw
     } catch {
         return false
     }
@@ -64,4 +66,8 @@
         }
     }
     return "none"
+}
+
+export async function TestImage(element): Promise<string> {
+    return "";
 }
\ No newline at end of file
diff --git a/src/automations/welcome.ts b/src/automations/welcome.ts
index 6505265..09e03d3 100644
--- a/src/automations/welcome.ts
+++ b/src/automations/welcome.ts
@@ -17,7 +17,6 @@
         }
     }
 
-
     if (!config.welcome.verificationRequired.message && config.welcome.channel) {
         let string = config.welcome.message
         if (string) {
@@ -32,7 +31,7 @@
             } else {
                 let channel = await member.client.channels.fetch(config.welcome.channel)
                 if (channel.guild.id !== member.guild.id) return
-                if (!channel) return // TODO: Notify mods
+                if (!channel) return
                 try {
                     await channel.send(string)
                 } catch (err) {
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index 92ed3a7..e94035e 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -1,9 +1,10 @@
-import { CommandInteraction, GuildMember } from "discord.js";
+import { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
+import readConfig from '../../utils/readConfig.js'
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -18,7 +19,7 @@
 
 const callback = async (interaction: CommandInteraction) => {
     // TODO:[Modals] Replace this with a modal
-    if (await new confirmationMessage(interaction)
+    let confirmation = await new confirmationMessage(interaction)
         .setEmoji("PUNISH.BAN.RED")
         .setTitle("Ban")
         .setDescription(keyValueList({
@@ -31,19 +32,26 @@
         .setColor("Danger")
 //        pluralize("day", interaction.options.getInteger("delete"))
 //        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
-    .send()) {
+    .send()
+    if (confirmation.success) {
         let dmd = false
         let dm;
+        let config = await readConfig(interaction.guild.id);
         try {
             if (interaction.options.getString("notify") != "no") {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
-                    embeds: [new EmojiEmbed()
+                    embeds: [new generateEmojiEmbed()
                         .setEmoji("PUNISH.BAN.RED")
                         .setTitle("Banned")
                         .setDescription(`You have been banned in ${interaction.guild.name}` +
                                     (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : "."))
                         .setStatus("Danger")
-                    ]
+                    ],
+                    components: [new MessageActionRow().addComponents(config.moderation.ban.text ? [new MessageButton()
+                        .setStyle("LINK")
+                        .setLabel(config.moderation.ban.text)
+                        .setURL(config.moderation.ban.link)
+                    ] : [])]
                 })
                 dmd = true
             }
@@ -54,7 +62,7 @@
                 reason: interaction.options.getString("reason") ?? "No reason provided"
             })
         } catch {
-            await interaction.editReply({embeds: [new EmojiEmbed()
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.BAN.RED")
                 .setTitle(`Ban`)
                 .setDescription("Something went wrong and the user was not banned")
@@ -64,14 +72,14 @@
             return
         }
         let failed = (dmd == false && interaction.options.getString("notify") != "no")
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Ban`)
             .setDescription("The member was banned" + (failed ? ", but could not be notified" : ""))
             .setStatus(failed ? "Warning" : "Success")
         ], components: []})
     } else {
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji("PUNISH.BAN.GREEN")
             .setTitle(`Ban`)
             .setDescription("No changes were made")
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index 08beaa5..34a1571 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -1,9 +1,10 @@
-import { CommandInteraction, GuildMember } from "discord.js";
+import { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
+import readConfig from '../../utils/readConfig.js'
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -17,7 +18,7 @@
 
 const callback = async (interaction: CommandInteraction) => {
     // TODO:[Modals] Replace this with a modal
-    if (await new confirmationMessage(interaction)
+    let confirmation = await new confirmationMessage(interaction)
         .setEmoji("PUNISH.KICK.RED")
         .setTitle("Kick")
         .setDescription(keyValueList({
@@ -29,19 +30,26 @@
         .setColor("Danger")
 //        pluralize("day", interaction.options.getInteger("delete"))
 //        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
-    .send()) {
+    .send()
+    if (confirmation.success) {
         let dmd = false
         let dm;
+        let config = await readConfig(interaction.guild.id);
         try {
             if (interaction.options.getString("notify") != "no") {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
-                    embeds: [new EmojiEmbed()
+                    embeds: [new generateEmojiEmbed()
                         .setEmoji("PUNISH.KICK.RED")
                         .setTitle("Kicked")
                         .setDescription(`You have been kicked in ${interaction.guild.name}` +
                                     (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : "."))
                         .setStatus("Danger")
-                    ]
+                    ],
+                    components: [new MessageActionRow().addComponents(config.moderation.kick.text ? [new MessageButton()
+                        .setStyle("LINK")
+                        .setLabel(config.moderation.kick.text)
+                        .setURL(config.moderation.kick.link)
+                    ] : [])]
                 })
                 dmd = true
             }
@@ -49,7 +57,7 @@
         try {
             (interaction.options.getMember("user") as GuildMember).kick(interaction.options.getString("reason") ?? "No reason provided.")
         } catch {
-            await interaction.editReply({embeds: [new EmojiEmbed()
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.KICK.RED")
                 .setTitle(`Kick`)
                 .setDescription("Something went wrong and the user was not kicked")
@@ -59,14 +67,14 @@
             return
         }
         let failed = (dmd == false && interaction.options.getString("notify") != "no")
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji(`PUNISH.KICK.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Kick`)
             .setDescription("The member was kicked" + (failed ? ", but could not be notified" : ""))
             .setStatus(failed ? "Warning" : "Success")
         ], components: []})
     } else {
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji("PUNISH.KICK.GREEN")
             .setTitle(`Kick`)
             .setDescription("No changes were made")
diff --git a/src/commands/mod/lock.ts b/src/commands/mod/lock.ts
index dbc7b6c..9199cd4 100644
--- a/src/commands/mod/lock.ts
+++ b/src/commands/mod/lock.ts
@@ -8,7 +8,7 @@
     .setDescription("Manages a lock on a channel")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [mod/lock]");
+    interaction.reply("This command is not yet finished [mod/lock]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index 02d2531..165d906 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -1,11 +1,13 @@
-import Discord, { CommandInteraction, GuildMember, MessageActionRow } from "discord.js";
+import Discord, { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import humanizeDuration from "humanize-duration";
+import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
+import readConfig from "../../utils/readConfig.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -17,11 +19,13 @@
     .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
     .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
     .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 kicked | Default yes").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
+    const { log, NucleusColors, renderUser, entry } = interaction.client.logger
     const user = interaction.options.getMember("user") as GuildMember
     const reason = interaction.options.getString("reason")
     const time = {
@@ -33,7 +37,7 @@
     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: [
-            new EmojiEmbed()
+            new generateEmojiEmbed()
                 .setEmoji("PUNISH.MUTE.GREEN")
                 .setTitle("Mute")
                 .setDescription("How long should the user be muted")
@@ -88,7 +92,7 @@
             component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
         } catch { return }
         component.deferUpdate();
-        if (component.customId == "cancel") return interaction.editReply({embeds: [new EmojiEmbed()
+        if (component.customId == "cancel") return interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji("PUNISH.MUTE.RED")
             .setTitle("Mute")
             .setDescription("Mute cancelled")
@@ -106,7 +110,7 @@
         }
     } else {
         await interaction.reply({embeds: [
-            new EmojiEmbed()
+            new generateEmojiEmbed()
                 .setEmoji("PUNISH.MUTE.GREEN")
                 .setTitle("Mute")
                 .setDescription("Loading...")
@@ -114,7 +118,7 @@
         ], ephemeral: true, fetchReply: true})
     }
     // TODO:[Modals] Replace this with a modal
-    if (await new confirmationMessage(interaction)
+    let confirmation = await new confirmationMessage(interaction)
         .setEmoji("PUNISH.MUTE.RED")
         .setTitle("Mute")
         .setDescription(keyValueList({
@@ -127,20 +131,27 @@
         .setColor("Danger")
 //        pluralize("day", interaction.options.getInteger("delete"))
 //        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
-    .send(true)) {
+    .send(true)
+    if (confirmation.success) {
         let dmd = false
         let dm;
+        let config = await readConfig(interaction.guild.id);
         try {
             if (interaction.options.getString("notify") != "no") {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
-                    embeds: [new EmojiEmbed()
+                    embeds: [new generateEmojiEmbed()
                         .setEmoji("PUNISH.MUTE.RED")
                         .setTitle("Muted")
                         .setDescription(`You have been muted in ${interaction.guild.name}` +
                                     (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".\n\n" +
                                     `You will be unmuted at: <t:${Math.round((new Date).getTime() / 1000) + muteTime}:D> at <t:${Math.round((new Date).getTime() / 1000) + muteTime}:T> (<t:${Math.round((new Date).getTime() / 1000) + muteTime}:R>)`))
                         .setStatus("Danger")
-                    ]
+                    ],
+                    components: [new MessageActionRow().addComponents(config.moderation.mute.text ? [new MessageButton()
+                        .setStyle("LINK")
+                        .setLabel(config.moderation.mute.text)
+                        .setURL(config.moderation.mute.link)
+                    ] : [])]
                 })
                 dmd = true
             }
@@ -148,24 +159,44 @@
         try {
             (interaction.options.getMember("user") as GuildMember).timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided")
         } catch {
-            await interaction.editReply({embeds: [new EmojiEmbed()
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.MUTE.RED")
                 .setTitle(`Mute`)
-                .setDescription("Something went wrong and the user was not kicked")
+                .setDescription("Something went wrong and the user was not mute")
                 .setStatus("Danger")
             ], components: []})
             if (dmd) await dm.delete()
             return
         }
         let failed = (dmd == false && interaction.options.getString("notify") != "no")
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Mute`)
             .setDescription("The member was muted" + (failed ? ", but could not be notified" : ""))
             .setStatus(failed ? "Warning" : "Success")
         ], components: []})
+        let data = {
+            meta:{
+                type: 'memberMute',
+                displayName: 'Member Muted',
+                calculateType: 'guildMemberPunish',
+                color: NucleusColors.yellow,
+                emoji: 'PUNISH.WARN.YELLOW',
+                timestamp: new Date().getTime()
+            },
+            list: {
+                user: entry((interaction.options.getMember("user") as GuildMember).user.id, renderUser((interaction.options.getMember("user") as GuildMember).user)),
+                mutedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
+                time: entry(muteTime, `${humanizeDuration(muteTime * 1000, {round: true})}`),
+                reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
+            },
+            hidden: {
+                guild: interaction.guild.id
+            }
+        }
+        log(data, interaction.client);
     } else {
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji("PUNISH.MUTE.GREEN")
             .setTitle(`Mute`)
             .setDescription("No changes were made")
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index d23751c..9584055 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -1,25 +1,108 @@
-import { CommandInteraction, GuildMember } from "discord.js";
+import { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
+import readConfig from '../../utils/readConfig.js';
+import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("nick")
     .setDescription("Changes a users nickname")
     .addUserOption(option => option.setName("user").setDescription("The user to change").setRequired(true))
-    .addStringOption(option => option.setName("name").setDescription("The name to set").setRequired(false))
+    .addStringOption(option => option.setName("name").setDescription("The name to set | Leave blank to clear").setRequired(false))
     .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when their nickname is changed | Default no").setRequired(false)
         .addChoices([["Yes", "yes"], ["No", "no"]])
     )
 
 const callback = async (interaction: CommandInteraction) => {
+    // TODO:[Modals] Replace this with a modal
+    let confirmation = await new confirmationMessage(interaction)
+        .setEmoji("PUNISH.NICKNAME.RED")
+        .setTitle("Nickname")
+        .setDescription(keyValueList({
+            "user": `<@!${(interaction.options.getMember("user") as GuildMember).id}> (${(interaction.options.getMember("user") as GuildMember).user.username})`,
+            "new nickname": `${interaction.options.getString("name") ? interaction.options.getString("name") : "*No nickname*"}`
+        })
+        + `The user **will${interaction.options.getString("notify") == "yes" ? '' : ' not'}** be notified\n\n`
+        + `Are you sure you want to ${interaction.options.getString("name") ? "change" : "clear"} <@!${(interaction.options.getMember("user") as GuildMember).id}>'s nickname?`)
+        .setColor("Danger")
+        .addCustomCallback(
+            "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
+            () => { create(interaction.guild, interaction.options.getUser("user"), interaction.client)},
+            "An appeal ticket was created")
+//        pluralize("day", interaction.options.getInteger("delete"))
+//        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
+    .send()
+    if (confirmation.success) {
+        let dmd = false
+        let dm;
+        try {
+            if (interaction.options.getString("notify") == "yes") {
+                dm = await (interaction.options.getMember("user") as GuildMember).send({
+                    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" +
+                                    (confirmation.buttonClicked ? `You can appeal this in this ticket: <#${confirmation.response}>` : ``))
+                        .setStatus("Danger")
+                    ]
+                })
+                dmd = true
+            }
+        } catch {}
+        try {
+            (interaction.options.getMember("user") as GuildMember).setNickname(interaction.options.getString("name") ?? null, "Nucleus Nickname command")
+        } catch {
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
+                .setEmoji("PUNISH.NICKNAME.RED")
+                .setTitle(`Nickname`)
+                .setDescription("Something went wrong and the users nickname could not be changed.")
+                .setStatus("Danger")
+            ], components: []})
+            if (dmd) await dm.delete()
+            return
+        }
+        let failed = (dmd == false && interaction.options.getString("notify") == "yes")
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setEmoji(`PUNISH.NICKNAME.${failed ? "YELLOW" : "GREEN"}`)
+            .setTitle(`Nickname`)
+            .setDescription("The members nickname was changed" + (failed ? ", but was not notified" : ""))
+            .setStatus(failed ? "Warning" : "Success")
+        ], components: []})
+    } else {
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setEmoji("PUNISH.NICKNAME.GREEN")
+            .setTitle(`Nickname`)
+            .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)
+    let apply = (interaction.options.getMember("user") as GuildMember)
+    if (member == null || me == null || apply == null) throw "That member is not in the server"
+    let memberPos = member.roles ? member.roles.highest.position : 0
+    let mePos = me.roles ? me.roles.highest.position : 0
+    let applyPos = apply.roles ? apply.roles.highest.position : 0
+    // Check if Nucleus can change the nickname
+    if (! (mePos > applyPos)) throw "I do not have a role higher than that member"
+    // Check if Nucleus has permission to change the nickname
+    if (! interaction.guild.me.permissions.has("MANAGE_NICKNAMES")) throw "I do not have the `manage_nicknames` permission";
+    // Allow the owner to change anyone's nickname
+    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";
+    // 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
+    return true
 }
 
 export { command, callback, check };
\ No newline at end of file
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index aa2f405..29c228f 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -2,9 +2,10 @@
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
+import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -16,23 +17,16 @@
         .setRequired(false)
         .setMinValue(1)
         .setMaxValue(100))
-    .addChannelOption(option => option.setName("channel").setDescription("The channel to purge messages from").setRequired(false))
     .addUserOption(option => option.setName("user").setDescription("The user to purge messages from").setRequired(false))
     .addStringOption(option => option.setName("reason").setDescription("The reason for the purge").setRequired(false))
 
 const callback = async (interaction: CommandInteraction) => {
     let user = interaction.options.getMember("user") as GuildMember ?? null
-    let channel = (interaction.options.getChannel("channel") as GuildChannel) ?? interaction.channel
-    let thischannel
-    if ((interaction.options.getChannel("channel") as GuildChannel) == null) {
-        thischannel = true
-    } else {
-        thischannel = (interaction.options.getChannel("channel") as GuildChannel).id == interaction.channel.id
-    }
+    let channel = (interaction.channel as GuildChannel)
     if (!(["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(channel.type.toString()))) {
         return await interaction.reply({
             embeds: [
-                new EmojiEmbed()
+                new generateEmojiEmbed()
                     .setEmoji("CHANNEL.PURGE.RED")
                     .setTitle("Purge")
                     .setDescription("You cannot purge this channel")
@@ -46,7 +40,7 @@
     if ( !interaction.options.getInteger("amount") ) {
         await interaction.reply({
             embeds: [
-                new EmojiEmbed()
+                new generateEmojiEmbed()
                     .setEmoji("CHANNEL.PURGE.RED")
                     .setTitle("Purge")
                     .setDescription("Select how many messages to delete")
@@ -60,7 +54,7 @@
         while (true) {
             let m = await interaction.editReply({
                 embeds: [
-                    new EmojiEmbed()
+                    new generateEmojiEmbed()
                         .setEmoji("CHANNEL.PURGE.RED")
                         .setTitle("Purge")
                         .setDescription("Select how many messages to delete. You can continue clicking until all messages are cleared.")
@@ -119,12 +113,11 @@
                 }
                 messages = await (channel as TextChannel).bulkDelete(ms, true);
             })
-            deleted = deleted.concat(messages.map(m => m)) // TODO: .values doesnt work so using .map
-            // TODO: Support for users
+            deleted = deleted.concat(messages.map(m => m))
         }
         if (deleted.length === 0) return await interaction.editReply({
             embeds: [
-                new EmojiEmbed()
+                new generateEmojiEmbed()
                     .setEmoji("CHANNEL.PURGE.RED")
                     .setTitle("Purge")
                     .setDescription("No messages were deleted")
@@ -147,7 +140,7 @@
                 description: "Purge log"
             }
         } catch {}
-        let m = await interaction.editReply({embeds: [new EmojiEmbed()
+        let m = await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji(`CHANNEL.PURGE.GREEN`)
             .setTitle(`Purge`)
             .setDescription("Messages cleared")
@@ -164,14 +157,14 @@
             component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
         } catch {}
         if (component && component.customId === "download") {
-            interaction.editReply({embeds: [new EmojiEmbed()
+            interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("CHANNEL.PURGE.GREEN")
                 .setTitle(`Purge`)
                 .setDescription("Uploaded")
                 .setStatus("Success")
             ], components: [], files: [attachmentObject]})
         } else {
-            interaction.editReply({embeds: [new EmojiEmbed()
+            interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("CHANNEL.PURGE.GREEN")
                 .setTitle(`Purge`)
                 .setDescription("Messages cleared")
@@ -180,29 +173,31 @@
         }
         return
     } else {
-        if (await new confirmationMessage(interaction)
+        let confirmation = await new confirmationMessage(interaction)
             .setEmoji("CHANNEL.PURGE.RED")
             .setTitle("Purge")
             .setDescription(keyValueList({
-                "channel": `<#${channel.id}> (${(channel as GuildChannel).name})` + (thischannel ? " [This channel]" : ""),
+                "channel": `<#${channel.id}> (${(channel as GuildChannel).name})` + ("[This channel]"),
                 "amount": interaction.options.getInteger("amount").toString(),
                 "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
             }))
             .setColor("Danger")
     //        pluralize("day", interaction.options.getInteger("amount"))
     //        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
-        .send()) {
+        .send()
+        if (confirmation.success) {
             let messages;
             try {
-                (interaction.channel as TextChannel).messages.fetch({limit: interaction.options.getInteger("amount")}).then(async (ms) => {
-                    if (user) {
-                        ms = ms.filter(m => m.author.id === user.id)
-                    }
-                    messages = await (channel as TextChannel).bulkDelete(ms, true);
-                }) // TODO: fix for purge amount by user, not just checking x
+                if (!user) {
+                    let toDelete = await (interaction.channel as TextChannel).messages.fetch({limit: interaction.options.getInteger("amount")})
+                    messages = await (channel as TextChannel).bulkDelete(toDelete, true);
+                } else {
+                    let toDelete = (await (await (interaction.channel as TextChannel).messages.fetch({limit: 100}))
+                        .filter(m => m.author.id === user.id)).first(interaction.options.getInteger("amount"))
+                    messages = await (channel as TextChannel).bulkDelete(toDelete, true);
+                }
             } catch(e) {
-                console.log(e)
-                await interaction.editReply({embeds: [new EmojiEmbed()
+                await interaction.editReply({embeds: [new generateEmojiEmbed()
                     .setEmoji("CHANNEL.PURGE.RED")
                     .setTitle(`Purge`)
                     .setDescription("Something went wrong and no messages were deleted")
@@ -224,7 +219,7 @@
                     description: `Purge log`
                 }
             } catch {}
-            let m = await interaction.editReply({embeds: [new EmojiEmbed()
+            let m = await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji(`CHANNEL.PURGE.GREEN`)
                 .setTitle(`Purge`)
                 .setDescription("Messages cleared")
@@ -241,14 +236,14 @@
                 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
             } catch {}
             if (component && component.customId === "download") {
-                interaction.editReply({embeds: [new EmojiEmbed()
+                interaction.editReply({embeds: [new generateEmojiEmbed()
                     .setEmoji("CHANNEL.PURGE.GREEN")
                     .setTitle(`Purge`)
-                    .setDescription("Uploaded")
+                    .setDescription("Transcript uploaded above")
                     .setStatus("Success")
                 ], components: [], files: [attachmentObject]})
             } else {
-                interaction.editReply({embeds: [new EmojiEmbed()
+                interaction.editReply({embeds: [new generateEmojiEmbed()
                     .setEmoji("CHANNEL.PURGE.GREEN")
                     .setTitle(`Purge`)
                     .setDescription("Messages cleared")
@@ -256,7 +251,7 @@
                 ], components: []})
             }
         } else {
-            await interaction.editReply({embeds: [new EmojiEmbed()
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("CHANNEL.PURGE.GREEN")
                 .setTitle(`Purge`)
                 .setDescription("No changes were made")
diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts
index 52ab1bd..8f6f323 100644
--- a/src/commands/mod/slowmode.ts
+++ b/src/commands/mod/slowmode.ts
@@ -8,7 +8,7 @@
     .setDescription("Manages slowmode in a channel")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [mod/slowmode]");
+    interaction.reply("This command is not yet finished [mod/slowmode]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index 233b7f2..44d425a 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -1,9 +1,10 @@
-import { CommandInteraction, GuildMember } from "discord.js";
+import { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
+import readConfig from '../../utils/readConfig.js'
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -11,14 +12,14 @@
     .setDescription("Kicks a user and deletes their messages")
     .addUserOption(option => option.setName("user").setDescription("The user to softban").setRequired(true))
     .addStringOption(option => option.setName("reason").setDescription("The reason for the softban").setRequired(false))
-    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are banbanned | Default yes").setRequired(false)
+    .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are softbanned | Default yes").setRequired(false)
         .addChoices([["Yes", "yes"], ["No", "no"]])
     )
     .addIntegerOption(option => option.setName("delete").setDescription("The days of messages to delete | Default 0").setMinValue(0).setMaxValue(7).setRequired(false))
 
 const callback = async (interaction: CommandInteraction) => {
     // TODO:[Modals] Replace this with a modal
-    if (await new confirmationMessage(interaction)
+    let confirmation = await new confirmationMessage(interaction)
         .setEmoji("PUNISH.BAN.RED")
         .setTitle("Softban")
         .setDescription(keyValueList({
@@ -31,18 +32,25 @@
         .setColor("Danger")
 //        pluralize("day", interaction.options.getInteger("delete"))
 //        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
-    .send()) {
-        let dmd = false
+    .send()
+    if (confirmation.success) {
+        let dmd = false;
+        let config = await readConfig(interaction.guild.id);
         try {
             if (interaction.options.getString("notify") != "no") {
                 await (interaction.options.getMember("user") as GuildMember).send({
-                    embeds: [new EmojiEmbed()
+                    embeds: [new generateEmojiEmbed()
                         .setEmoji("PUNISH.BAN.RED")
                         .setTitle("Softbanned")
                         .setDescription(`You have been softbanned from ${interaction.guild.name}` +
                                     (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : "."))
                         .setStatus("Danger")
-                    ]
+                    ],
+                    components: [new MessageActionRow().addComponents(config.moderation.ban.text ? [new MessageButton()
+                        .setStyle("LINK")
+                        .setLabel(config.moderation.ban.text)
+                        .setURL(config.moderation.ban.link)
+                    ] : [])]
                 })
                 dmd = true
             }
@@ -54,7 +62,7 @@
             });
             await interaction.guild.members.unban(interaction.options.getMember("user") as GuildMember, "Softban");
         } catch {
-            await interaction.editReply({embeds: [new EmojiEmbed()
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.BAN.RED")
                 .setTitle(`Softban`)
                 .setDescription("Something went wrong and the user was not softbanned")
@@ -62,14 +70,14 @@
             ], components: []})
         }
         let failed = (dmd == false && interaction.options.getString("notify") != "no")
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Softban`)
             .setDescription("The member was softbanned" + (failed ? ", but could not be notified" : ""))
             .setStatus(failed ? "Warning" : "Success")
         ], components: []})
     } else {
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji("PUNISH.BAN.GREEN")
             .setTitle(`Softban`)
             .setDescription("No changes were made")
diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts
index 6477bb3..3ffc001 100644
--- a/src/commands/mod/unban.ts
+++ b/src/commands/mod/unban.ts
@@ -8,7 +8,7 @@
     .setDescription("Unbans a user")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [mod/unban]");
+    interaction.reply("This command is not yet finished [mod/unban]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts
index 2c522a4..65e5e97 100644
--- a/src/commands/mod/unmute.ts
+++ b/src/commands/mod/unmute.ts
@@ -2,7 +2,7 @@
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -17,7 +17,7 @@
 
 const callback = async (interaction: CommandInteraction) => {
     // TODO:[Modals] Replace this with a modal
-    if (await new confirmationMessage(interaction)
+    let confirmation =  await new confirmationMessage(interaction)
         .setEmoji("PUNISH.MUTE.RED")
         .setTitle("Unmute")
         .setDescription(keyValueList({
@@ -29,13 +29,14 @@
         .setColor("Danger")
 //        pluralize("day", interaction.options.getInteger("delete"))
 //        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
-    .send()) {
+    .send()
+    if (confirmation.success) {
         let dmd = false
         let dm;
         try {
             if (interaction.options.getString("notify") != "no") {
                 dm = await (interaction.options.getMember("user") as GuildMember).send({
-                    embeds: [new EmojiEmbed()
+                    embeds: [new generateEmojiEmbed()
                         .setEmoji("PUNISH.MUTE.GREEN")
                         .setTitle("Unmuted")
                         .setDescription(`You have been unmuted in ${interaction.guild.name}` +
@@ -49,7 +50,7 @@
         try {
             (interaction.options.getMember("user") as GuildMember).timeout(0, interaction.options.getString("reason") || "No reason provided")
         } catch {
-            await interaction.editReply({embeds: [new EmojiEmbed()
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.MUTE.RED")
                 .setTitle(`Unmute`)
                 .setDescription("Something went wrong and the user was not unmuted")
@@ -59,14 +60,14 @@
             return
         }
         let failed = (dmd == false && interaction.options.getString("notify") != "no")
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`)
             .setTitle(`Unmute`)
             .setDescription("The member was unmuted" + (failed ? ", but could not be notified" : ""))
             .setStatus(failed ? "Warning" : "Success")
         ], components: []})
     } else {
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji("PUNISH.MUTE.GREEN")
             .setTitle(`Unmute`)
             .setDescription("No changes were made")
diff --git a/src/commands/mod/viewas.ts b/src/commands/mod/viewas.ts
index c7bd4b1..5f7c7de 100644
--- a/src/commands/mod/viewas.ts
+++ b/src/commands/mod/viewas.ts
@@ -1,4 +1,4 @@
-import { CommandInteraction } from "discord.js";
+import { CategoryChannel, CommandInteraction } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 
@@ -6,9 +6,13 @@
     builder
     .setName("viewas")
     .setDescription("View the server as a specific member")
+    .addUserOption(option => option.setName("member").setDescription("The member to view as").setRequired(true))
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [mod/viewas]");
+    let channels = interaction.guild.channels.cache
+        .filter(c => c.type === "GUILD_CATEGORY")
+        .map(c => (c as CategoryChannel).children.map(c => c))
+    console.log(channels)
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 3662896..d89a778 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -2,8 +2,9 @@
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import confirmationMessage from "../../utils/confirmationMessage.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import keyValueList from "../../utils/generateKeyValueList.js";
+import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -17,9 +18,9 @@
 
 const callback = async (interaction: CommandInteraction) => {
     // @ts-ignore
-    const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = interaction.client.logger
+    const { log, NucleusColors, renderUser, entry } = interaction.client.logger
     // TODO:[Modals] Replace this with a modal
-    if (await new confirmationMessage(interaction)
+    let confirmation = await new confirmationMessage(interaction)
         .setEmoji("PUNISH.WARN.RED")
         .setTitle("Warn")
         .setDescription(keyValueList({
@@ -29,25 +30,31 @@
         + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
         + `Are you sure you want to warn <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
         .setColor("Danger")
+        .addCustomCallback(
+            "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
+            () => { create(interaction.guild, interaction.options.getUser("user"), interaction.client)},
+            "An appeal ticket was created")
 //        pluralize("day", interaction.options.getInteger("delete"))
 //        const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
-    .send()) {
+    .send()
+    if (confirmation.success) {
         let dmd = false
         try {
             if (interaction.options.getString("notify") != "no") {
                 await (interaction.options.getMember("user") as GuildMember).send({
-                    embeds: [new EmojiEmbed()
+                    embeds: [new generateEmojiEmbed()
                         .setEmoji("PUNISH.WARN.RED")
                         .setTitle("Warned")
                         .setDescription(`You have been warned in ${interaction.guild.name}` +
-                                    (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : "."))
+                                    (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".") + "\n\n" +
+                                    (confirmation.buttonClicked ? `You can appeal this in this ticket: <#${confirmation.response}>` : ``))
                         .setStatus("Danger")
                     ]
                 })
                 dmd = true
             }
         } catch {
-            await interaction.editReply({embeds: [new EmojiEmbed()
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji("PUNISH.WARN.RED")
                 .setTitle(`Warn`)
                 .setDescription("Something went wrong and the user was not warned")
@@ -64,8 +71,8 @@
                 timestamp: new Date().getTime()
             },
             list: {
-                user: renderUser((interaction.options.getMember("user") as GuildMember).user.id, (interaction.options.getMember("user") as GuildMember).user),
-                warnedBy: renderUser(interaction.member.user.id, interaction.member.user),
+                user: entry((interaction.options.getMember("user") as GuildMember).user.id, renderUser((interaction.options.getMember("user") as GuildMember).user)),
+                warnedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
                 reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
             },
             hidden: {
@@ -75,7 +82,7 @@
         log(data, interaction.client);
         let failed = (dmd == false && interaction.options.getString("notify") != "no")
         if (!failed) {
-            await interaction.editReply({embeds: [new EmojiEmbed()
+            await interaction.editReply({embeds: [new generateEmojiEmbed()
                 .setEmoji(`PUNISH.WARN.GREEN`)
                 .setTitle(`Warn`)
                 .setDescription("The user was warned")
@@ -83,7 +90,7 @@
             ], components: []})
         } else {
             let m = await interaction.editReply({
-                embeds: [new EmojiEmbed()
+                embeds: [new generateEmojiEmbed()
                     .setEmoji(`PUNISH.WARN.RED`)
                     .setTitle(`Warn`)
                     .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
@@ -106,7 +113,7 @@
             try {
                 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
             } catch (e) {
-                return await interaction.editReply({embeds: [new EmojiEmbed()
+                return await interaction.editReply({embeds: [new generateEmojiEmbed()
                     .setEmoji(`PUNISH.WARN.GREEN`)
                     .setTitle(`Warn`)
                     .setDescription("No changes were made")
@@ -115,7 +122,7 @@
             }
             if ( component.customId == "here" ) {
                 await interaction.channel.send({
-                    embeds: [new EmojiEmbed()
+                    embeds: [new generateEmojiEmbed()
                         .setEmoji(`PUNISH.WARN.RED`)
                         .setTitle(`Warn`)
                         .setDescription(`You have been warned` +
@@ -125,14 +132,14 @@
                     content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
                     allowedMentions: {users: [(interaction.options.getMember("user") as GuildMember).id]}
                 })
-                return await interaction.editReply({embeds: [new EmojiEmbed()
+                return await interaction.editReply({embeds: [new generateEmojiEmbed()
                     .setEmoji(`PUNISH.WARN.GREEN`)
                     .setTitle(`Warn`)
                     .setDescription("The user was warned")
                     .setStatus("Success")
                 ], components: []})
             } else {
-                await interaction.editReply({embeds: [new EmojiEmbed()
+                await interaction.editReply({embeds: [new generateEmojiEmbed()
                     .setEmoji(`PUNISH.WARN.GREEN`)
                     .setTitle(`Warn`)
                     .setDescription("The warn was logged")
@@ -141,7 +148,7 @@
             }
         }
     } else {
-        await interaction.editReply({embeds: [new EmojiEmbed()
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
             .setEmoji("PUNISH.WARN.GREEN")
             .setTitle(`Warn`)
             .setDescription("No changes were made")
diff --git a/src/commands/nucleus/ping.ts b/src/commands/nucleus/ping.ts
index 43a5fd7..ab23bf7 100644
--- a/src/commands/nucleus/ping.ts
+++ b/src/commands/nucleus/ping.ts
@@ -1,5 +1,6 @@
 import { CommandInteraction } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import { WrappedCheck } from "jshaiku";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -7,8 +8,27 @@
     .setName("ping")
     .setDescription("Gets the bot's ping time")
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [nucleus/ping]");
+const callback = async (interaction: CommandInteraction) => {
+    // WEBSOCKET | Nucleus -> Discord
+    // EDITING   | Nucleus -> discord -> nucleus | edit time / 2
+    let initial = new Date().getTime();
+    await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("Ping")
+        .setDescription(`Checking ping times...`)
+        .setEmoji("NUCLEUS.LOADING")
+        .setStatus("Danger")
+    ], ephemeral: true});
+    let ping = new Date().getTime() - initial;
+    interaction.editReply({embeds: [new generateEmojiEmbed()
+        .setTitle("Ping")
+        .setDescription(
+            `**Ping:** \`${ping}ms\`\n` +
+            `**To Discord:** \`${interaction.client.ws.ping}ms\`\n` +
+            `**From Expected:** \`±${Math.abs((ping / 2) - interaction.client.ws.ping)}ms\``
+        )
+        .setEmoji("CHANNEL.SLOWMODE.OFF")
+        .setStatus("Danger")
+    ]})
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts
index 7f468fa..c12b950 100644
--- a/src/commands/nucleus/stats.ts
+++ b/src/commands/nucleus/stats.ts
@@ -1,14 +1,26 @@
 import { CommandInteraction } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("stats")
-    .setDescription("Gets the bot's statse")
+    .setDescription("Gets the bot's stats")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [nucleus/stats]");
+    interaction.reply({
+        embeds: [new generateEmojiEmbed()
+            .setTitle("Stats")
+            .setDescription(
+                `**Servers:** ${interaction.client.guilds.cache.size}\n` +
+                `**Ping:** \`${interaction.client.ws.ping*2}ms\``
+            )
+            .setStatus("Success")
+            .setEmoji("GUILD.GRAPHS")
+
+        ], ephemeral: true
+    });
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index e76d49f..36338dc 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -1,14 +1,50 @@
-import { CommandInteraction } from "discord.js";
+import Discord, { CommandInteraction } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("suggest")
     .setDescription("Sends a suggestion to the developers")
+    .addStringOption(option => option.setName("suggestion").setDescription("The suggestion to send").setRequired(true))
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [nucleus/suggest]");
+const callback = async (interaction: CommandInteraction) => {
+	// @ts-ignore
+    const { renderUser } = interaction.client.logger
+	let suggestion = interaction.options.getString("suggestion");
+    let confirmation = await new confirmationMessage(interaction)
+        .setEmoji("ICONS.OPP.ADD")
+        .setTitle("Suggest")
+        .setDescription(`**Suggestion:**\n> ${suggestion}\n`
+        + `Your username and ID will also be sent with your suggestion.\n\nAre you sure you want to send this suggestion?`)
+        .setColor("Danger")
+    .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")
+			]
+		})
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setEmoji("ICONS.ADD")
+            .setTitle(`Suggest`)
+            .setDescription("Your suggestion was sent successfully")
+            .setStatus("Success")
+        ], components: []})
+    } else {
+        await interaction.editReply({embeds: [new generateEmojiEmbed()
+            .setEmoji("ICONS.OPP.ADD")
+            .setTitle(`Suggest`)
+            .setDescription("No changes were made")
+            .setStatus("Danger")
+        ], components: []})
+    }
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts
index 063f195..3174421 100644
--- a/src/commands/privacy.ts
+++ b/src/commands/privacy.ts
@@ -1,4 +1,4 @@
-import { CommandInteraction } from "discord.js";
+import Discord, { CommandInteraction } from "discord.js";
 import { SlashCommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import { testLink, testMalware, testNSFW } from '../utils/scanners.js';
diff --git a/src/commands/role/all.ts b/src/commands/role/all.ts
index 0f4c43f..44e77fd 100644
--- a/src/commands/role/all.ts
+++ b/src/commands/role/all.ts
@@ -8,7 +8,7 @@
     .setDescription("Gives or removes a role from everyone")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [role/all]");
+    interaction.reply("This command is not yet finished [role/all]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/role/user.ts b/src/commands/role/user.ts
index 4229133..b45e1d1 100644
--- a/src/commands/role/user.ts
+++ b/src/commands/role/user.ts
@@ -5,10 +5,10 @@
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("user")
-    .setDescription("Gives or removes a role form someone")
+    .setDescription("Gives or removes a role from someone")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [role/user]");
+    interaction.reply("This command is not yet finished [role/user]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/server.ts b/src/commands/server.ts
deleted file mode 100644
index 27d0373..0000000
--- a/src/commands/server.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = new SlashCommandBuilder()
-    .setName("server")
-    .setDescription("Shows info about the server")
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [server]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/automation.ts b/src/commands/settings/automation.ts
index 3874f7f..0053f76 100644
--- a/src/commands/settings/automation.ts
+++ b/src/commands/settings/automation.ts
@@ -8,7 +8,7 @@
     .setDescription("Shows all automation options")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/automation]");
+    interaction.reply("This command is not yet finished [settings/automation]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/log/channel.ts b/src/commands/settings/log/channel.ts
index af570a7..5843438 100644
--- a/src/commands/settings/log/channel.ts
+++ b/src/commands/settings/log/channel.ts
@@ -8,7 +8,7 @@
     .setDescription("Sets the log channel")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/log/channel]");
+    interaction.reply("This command is not yet finished [settings/log/channel]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/log/events.ts b/src/commands/settings/log/events.ts
index 4f4c9bd..dac200c 100644
--- a/src/commands/settings/log/events.ts
+++ b/src/commands/settings/log/events.ts
@@ -8,7 +8,7 @@
     .setDescription("Sets what events should be logged")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/log/events]");
+    interaction.reply("This command is not yet finished [settings/log/events]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/log/ignore.ts b/src/commands/settings/log/ignore.ts
index eea7048..1b4d245 100644
--- a/src/commands/settings/log/ignore.ts
+++ b/src/commands/settings/log/ignore.ts
@@ -8,7 +8,7 @@
     .setDescription("Sets which users, channels and roles should be ignored")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/log/ignore]");
+    interaction.reply("This command is not yet finished [settings/log/ignore]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/log/ignored.ts b/src/commands/settings/log/ignored.ts
index df5f7b6..bf4a30c 100644
--- a/src/commands/settings/log/ignored.ts
+++ b/src/commands/settings/log/ignored.ts
@@ -8,7 +8,7 @@
     .setDescription("Gets the ignored users, channels and roles")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/log/ignored]");
+    interaction.reply("This command is not yet finished [settings/log/ignored]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/menu.ts b/src/commands/settings/menu.ts
index c7269c7..9950fe4 100644
--- a/src/commands/settings/menu.ts
+++ b/src/commands/settings/menu.ts
@@ -35,13 +35,13 @@
     }
 
     let toLogDropdown = new MessageSelectMenu()
-        .setCustomId("tolog")
+        .setCustomId("log")
         .setMaxValues(22)
         .addOptions()
 
     let embed = new MessageEmbed()
 
-    interaction.reply("Command incomplete [settings/all]");
+    interaction.reply("This command is not yet finished [settings/all]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/mod/_meta.ts b/src/commands/settings/mod/_meta.ts
index 713bb48..9c3cd62 100644
--- a/src/commands/settings/mod/_meta.ts
+++ b/src/commands/settings/mod/_meta.ts
@@ -1,4 +1,4 @@
-const name = "announcements";
-const description = "Settings for mod messages";
+const name = "warnings";
+const description = "Settings for mod warnings";
 
 export { name, description };
\ No newline at end of file
diff --git a/src/commands/settings/mod/channel.ts b/src/commands/settings/mod/channel.ts
index 16230ba..88c8396 100644
--- a/src/commands/settings/mod/channel.ts
+++ b/src/commands/settings/mod/channel.ts
@@ -8,7 +8,7 @@
     .setDescription("Sets the channel for staff messages to go to")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/mod/channel]");
+    interaction.reply("This command is not yet finished [settings/mod/channel]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/mod/events.ts b/src/commands/settings/mod/events.ts
index 3dc5e99..be5de57 100644
--- a/src/commands/settings/mod/events.ts
+++ b/src/commands/settings/mod/events.ts
@@ -8,7 +8,7 @@
     .setDescription("Sets which events mods should be notified about")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/mod/events]");
+    interaction.reply("This command is not yet finished [settings/mod/events]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index 2c2b806..64dc980 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -14,7 +14,7 @@
     .addRoleOption(option => option.setName("supportping").setDescription("The role pinged when a ticket is created").setRequired(false))
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/tickets]");
+    interaction.reply("This command is not yet finished [settings/tickets]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/verify/channel.ts b/src/commands/settings/verify/channel.ts
deleted file mode 100644
index ad881c2..0000000
--- a/src/commands/settings/verify/channel.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
-    builder
-    .setName("channel")
-    .setDescription("Sets the verify channel")
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/verify/channel]");
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/settings/verify/role.ts b/src/commands/settings/verify/role.ts
index 3082c71..c4de7af 100644
--- a/src/commands/settings/verify/role.ts
+++ b/src/commands/settings/verify/role.ts
@@ -8,7 +8,7 @@
     .setDescription("Sets the role given after verifying")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [settings/verify/role]");
+    interaction.reply("This command is not yet finished [settings/verify/role]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index 9a10f5a..a5fcfc1 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -7,7 +7,7 @@
     .setDescription("Get and manage the servers tags")
 
 const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [tag]");
+    interaction.reply("This command is not yet finished [tag]");
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/ticket/close.ts b/src/commands/ticket/close.ts
index 7b9d1c2..237623d 100644
--- a/src/commands/ticket/close.ts
+++ b/src/commands/ticket/close.ts
@@ -1,9 +1,7 @@
-import Discord, { CommandInteraction } from "discord.js";
+import { CommandInteraction } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import readConfig from "../../utils/readConfig.js";
-import getEmojiByName from "../../utils/getEmojiByName.js";
+import close from "../../automations/tickets/delete.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -11,89 +9,7 @@
     .setDescription("Closes a ticket")
 
 const callback = async (interaction: CommandInteraction) => {
-    // @ts-ignore
-    const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = interaction.client.logger
-
-    let config = await readConfig(interaction.guild.id);
-    let channel = (interaction.channel as Discord.TextChannel)
-    if (config.tickets.category != channel.parent.id) {
-        return interaction.reply({embeds: [new EmojiEmbed()
-            .setTitle("Close Ticket")
-            .setDescription("This ticket is not in your tickets category, so cannot be deleted.")
-            .setStatus("Danger")
-            .setEmoji("CONTROL.BLOCKCROSS")
-        ], ephemeral: true});
-    }
-    let status = channel.topic.split(" ")[1];
-    if (status == "Archived") {
-        interaction.reply({embeds: [new EmojiEmbed()
-            .setTitle("Close Ticket")
-            .setDescription("This ticket will be deleted in 3 seconds.")
-            .setStatus("Danger")
-            .setEmoji("GUILD.TICKET.CLOSE")
-        ]});
-        setTimeout(async () => {
-            let data = {
-                meta:{
-                    type: 'ticketClosed',
-                    displayName: 'Ticket Closed',
-                    calculateType: true,
-                    color: NucleusColors.red,
-                    emoji: 'GUILD.TICKET.CLOSE',
-                    timestamp: new Date().getTime()
-                },
-                list: {
-                    ticketFor: entry(channel.topic.split(" ")[0], renderUser((await interaction.guild.members.fetch(channel.topic.split(" ")[0])).user)),
-                    closedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
-                    closedAt: entry(new Date().getTime(), renderDelta(new Date().getTime()))
-                },
-                hidden: {
-                    guild: interaction.guild.id
-                }
-            }
-            log(data, interaction.client);
-            interaction.channel.delete();
-        }, 3000);
-        return;
-    } else if (status == "Active") {
-        interaction.reply({embeds: [new EmojiEmbed()
-            .setTitle("Close Ticket")
-            .setDescription("This ticket will be archived in 3 seconds.")
-            .setStatus("Warning")
-            .setEmoji("GUILD.TICKET.ARCHIVED")
-        ]});
-        setTimeout(async () =>{
-            channel.permissionsFor(await interaction.guild.members.fetch(channel.topic.split(" ")[0])).remove("VIEW_CHANNEL");
-            channel.setTopic(`${channel.topic.split(" ")[0]} Archived`);
-            let data = {
-                meta:{
-                    type: 'ticketArchive',
-                    displayName: 'Ticket Archived',
-                    calculateType: true,
-                    color: NucleusColors.yellow,
-                    emoji: 'GUILD.TICKET.ARCHIVED',
-                    timestamp: new Date().getTime()
-                },
-                list: {
-                    ticketFor: entry(channel.topic.split(" ")[0], renderUser((await interaction.guild.members.fetch(channel.topic.split(" ")[0])).user)),
-                    archivedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
-                    archivedAt: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-                    ticketChannel: entry(channel.id, renderChannel(channel)),
-                },
-                hidden: {
-                    guild: interaction.guild.id
-                }
-            }
-            log(data, interaction.client);
-            await interaction.editReply({embeds: [new EmojiEmbed()
-                .setTitle("Close Ticket")
-                .setDescription("This ticket has been archived.\nType `/ticket close` to delete it.")
-                .setStatus("Warning")
-                .setEmoji("GUILD.TICKET.ARCHIVED") // TODO:[Premium] Add a transcript option  ||\----/|| <- the bridge we will cross when we come to it
-            ]});
-        }, 3000);
-        return;
-    }
+    await close(interaction);
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/ticket/create.ts b/src/commands/ticket/create.ts
index 4f58aa0..6567c49 100644
--- a/src/commands/ticket/create.ts
+++ b/src/commands/ticket/create.ts
@@ -1,15 +1,7 @@
-import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import { CommandInteraction } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
-import { tickets, toHexArray, toHexInteger } from "../../utils/calculate.js";
-import readConfig from "../../utils/readConfig.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import getEmojiByName from "../../utils/getEmojiByName.js";
-
-function capitalize(s: string) {
-    s = s.replace(/([A-Z])/g, ' $1');
-    return s.length < 3 ? s.toUpperCase() : s[0].toUpperCase() + s.slice(1).toLowerCase();
-}
+import create from "../../automations/tickets/create.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -18,155 +10,7 @@
     .addStringOption(option => option.setName("message").setDescription("The content of the ticket").setRequired(false))
 
 const callback = async (interaction: CommandInteraction) => {
-    // @ts-ignore
-    const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = interaction.client.logger
-
-    let config = await readConfig(interaction.guild.id);
-    if (!config.tickets.enabled || !config.tickets.category) {
-        return await interaction.reply({embeds: [new EmojiEmbed()
-            .setTitle("Tickets are disabled")
-            .setDescription("Please enable tickets in the configuration to use this command.")
-            .setStatus("Danger")
-            .setEmoji("CONTROL.BLOCKCROSS")
-        ], ephemeral: true});
-    }
-    let category = interaction.guild.channels.cache.get(config.tickets.category) as Discord.CategoryChannel;
-    let count = 0;
-    category.children.forEach(element => {
-        if (!(element.type == "GUILD_TEXT")) return;
-        if ((element as Discord.TextChannel).topic.includes(`${interaction.member.user.id}`)) {
-            if ((element as Discord.TextChannel).topic.endsWith("Active")) {
-                count++;
-            }
-        }
-    });
-    if (count >= config.tickets.maxTickets) {
-        return await interaction.reply({embeds: [new EmojiEmbed()
-            .setTitle("Create Ticket")
-            .setDescription(`You have reached the maximum amount of tickets (${config.tickets.maxTickets}). Please close one of your active tickets before creating a new one.`)
-            .setStatus("Danger")
-            .setEmoji("CONTROL.BLOCKCROSS")
-        ], ephemeral: true});
-    }
-    let ticketTypes
-    if (config.tickets.customTypes) ticketTypes = config.tickets.customTypes;
-    else if (config.tickets.types) ticketTypes = toHexArray(config.tickets.types, tickets);
-    else ticketTypes = [];
-    let chosenType;
-    if (ticketTypes.length > 0) {
-        let splitFormattedTicketTypes = [];
-        let formattedTicketTypes = [];
-        formattedTicketTypes = ticketTypes.map(type => {
-            return new MessageButton()
-                .setLabel(capitalize(type))
-                .setStyle("PRIMARY")
-                .setCustomId(type)
-                .setEmoji(getEmojiByName(("TICKETS." + type.toString().toUpperCase()), "id"));
-        });
-        for (let i = 0; i < formattedTicketTypes.length; i += 4) {
-            splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 4)));
-        }
-        let m = await interaction.reply({embeds: [new EmojiEmbed()
-            .setTitle("Create Ticket")
-            .setDescription("Please select a ticket type")
-            .setStatus("Success")
-            .setEmoji("GUILD.TICKET.OPEN")
-        ], ephemeral: true, fetchReply: true, components: splitFormattedTicketTypes});
-        let component;
-        try {
-            component = await (m as Discord.Message).awaitMessageComponent({time: 2.5 * 60 * 1000});
-        } catch (e) {
-            return;
-        }
-        component.deferUpdate();
-        chosenType = component.customId;
-    } else {
-        chosenType = null
-        await interaction.reply({embeds: [new EmojiEmbed()
-            .setTitle("Create Ticket")
-            .setEmoji("GUILD.TICKET.OPEN")
-        ], ephemeral: true})
-    }
-    let overwrites = [{
-        id: interaction.member,
-        allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"],
-        type: "member"
-    }] as Discord.OverwriteResolvable[];
-    if (config.tickets.supportRole != null) {
-        overwrites.push({
-            id: interaction.guild.roles.cache.get(config.tickets.supportRole),
-            allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"],
-            type: "role"
-        })
-    }
-
-    let c;
-    try {
-        c = await interaction.guild.channels.create(interaction.member.user.username, {
-            type: "GUILD_TEXT",
-            topic: `${interaction.member.user.id} Active`,
-            parent: config.tickets.category,
-            nsfw: false,
-            permissionOverwrites: (overwrites as Discord.OverwriteResolvable[]),
-            reason: "Creating ticket"
-        })
-    } catch (e) {
-        return await interaction.editReply({embeds: [new EmojiEmbed()
-            .setTitle("Create Ticket")
-            .setDescription("Failed to create ticket")
-            .setStatus("Danger")
-            .setEmoji("CONTROL.BLOCKCROSS")
-        ]});
-    }
-    try {
-        await c.send(
-            {
-                content: (`<@${interaction.member.user.id}>` + (config.tickets.supportRole != null ? ` • <@&${config.tickets.supportRole}>` : "")),
-                allowedMentions: {
-                    users: [(interaction.member as Discord.GuildMember).id],
-                    roles: (config.tickets.supportRole != null ? [config.tickets.supportRole] : [])
-                }
-            }
-        )
-        let content = interaction.options.getString("message") || "";
-        if (content) content = `**Message:**\n> ${content}\n`;
-        await c.send({ embeds: [new EmojiEmbed()
-            .setTitle("New Ticket")
-            .setDescription(
-                `Ticket created by <@${interaction.member.user.id}>\n` +
-                `**Support type:** ${chosenType != null ? (getEmojiByName("TICKETS." + chosenType.toUpperCase()) + " " + capitalize(chosenType)) : "General"}\n` +
-                `**Ticket ID:** \`${c.id}\`\n${content}\n` +
-                `Type \`/ticket close\` to archive this ticket.`,
-            )
-            .setStatus("Success")
-            .setEmoji("GUILD.TICKET.OPEN")
-        ]})
-        let data = {
-            meta:{
-                type: 'ticketCreate',
-                displayName: 'Ticket Created',
-                calculateType: true,
-                color: NucleusColors.green,
-                emoji: 'GUILD.TICKET.OPEN',
-                timestamp: new Date().getTime()
-            },
-            list: {
-                ticketFor: entry(interaction.member.user.id, renderUser(interaction.member.user)),
-                createdAt: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-                ticketChannel: entry(c.id, renderChannel(c)),
-            },
-            hidden: {
-                guild: interaction.guild.id
-            }
-        }
-        log(data, interaction.client);
-    } catch (e) { console.log(e)}
-    await interaction.editReply({embeds: [new EmojiEmbed()
-        .setTitle("Create Ticket")
-        .setDescription(`Ticket created. You can view it here: <#${c.id}>`)
-        .setStatus("Success")
-        .setEmoji("GUILD.TICKET.OPEN")
-    ], components: []});
+    await create(interaction)
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts
index 9015c13..b6e2aee 100644
--- a/src/commands/user/about.ts
+++ b/src/commands/user/about.ts
@@ -1,14 +1,195 @@
-import { CommandInteraction } from "discord.js";
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import generateKeyValueList from "../../utils/generateKeyValueList.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("about")
     .setDescription("Shows info about a user")
+    .addUserOption(option => option.setName("user").setDescription("The user to get info about | Default: Yourself"))
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [user/about]");
+const callback = async (interaction: CommandInteraction) => {
+    // @ts-ignore
+    const { renderUser, renderDelta } = interaction.client.logger
+    let member = (interaction.options.getMember("user") || interaction.member) as Discord.GuildMember;
+    let flags: string[] = [];
+    if ([
+        "438733159748599813", // Pinea
+        "317731855317336067", // Mini
+        "261900651230003201", // Coded
+        "511655498676699136", // Zan
+    ].includes(member.user.id)) { flags.push("NUCLEUSDEVELOPER") }
+    if ((await interaction.client.guilds.cache.get("684492926528651336")?.members.fetch())?.filter(m => m.roles.cache.has("760896837866749972"))?.map(m => m.id).includes(member.user.id)) { flags.push("CLICKSDEVELOPER") }
+    member.user.flags.toArray().map(flag => {
+        flags.push(flag.toString())
+    })
+    if (member.user.bot === true) { flags.push("BOT") }
+    // Check if they are boosting the server
+    if (member.premiumSince) { flags.push("BOOSTER") }
+    let nameReplacements = {
+        "NUCLEUSDEVELOPER": "**Nucleus Developer**",
+        "CLICKSDEVELOPER": "Clicks Developer",
+        "HOUSE_BRAVERY": "Hypesquad Bravery",
+        "HOUSE_BRILLIANCE": "Hypesquad Brilliance",
+        "HOUSE_BALANCE": "Hypesquad Balance",
+        "HYPESQUAD_EVENTS": "Hypesquad Events",
+        "EARLY_SUPPORTER": "Early Supporter",
+        "BUGHUNTER_LEVEL_1": "Bug Hunter Level 1",
+        "BUGHUNTER_LEVEL_2": "Bug Hunter Level 2",
+        "PARTNERED_SERVER_OWNER": "Partnered Server Owner",
+        "DISCORD_EMPLOYEE": "Discord Staff",
+        "EARLY_VERIFIED_BOT_DEVELOPER": "Verified Bot Developer",
+        "BOT": "Bot",
+        "BOOSTER": "Server Booster"
+    }
+    let members = await interaction.guild.members.fetch()
+    let membersArray = [...members.values()]
+    membersArray.sort((a, b) => a.joinedTimestamp - b.joinedTimestamp)
+    let joinPos = membersArray.findIndex(m => m.id === member.user.id)
+
+    let roles = member.roles.cache.filter(r => r.id != interaction.guild.id).sort()
+    let s = "";
+    let count = 0;
+    let ended = false
+    roles.map(item => {
+        if (ended) return;
+        let string = `<@&${item.id}>, `
+        if(s.length + string.length > 1000) {
+            ended = true
+            s += `and ${roles.size - count} more`
+            return
+        };
+        count ++
+        s += string;
+    })
+    if(s.length > 0 && !ended) s = s.slice(0, -2);
+
+    let perms = ""
+    let permsArray = {
+        "ADMINISTRATOR": "Administrator",
+        "MANAGE_GUILD": "Manage Server",
+        "MANAGE_ROLES": "Manage Roles",
+        "MANAGE_CHANNELS": "Manage Channels",
+        "KICK_MEMBERS": "Kick Members",
+        "BAN_MEMBERS": "Ban Members",
+        "MODERATE_MEMBERS": "Moderate Members",
+        "MANAGE_NICKNAMES": "Manage Nicknames",
+        "MANAGE_WEBHOOKS": "Manage Webhooks",
+        "MANAGE_MESSAGES": "Manage Messages",
+        "VIEW_AUDIT_LOG": "View Audit Log",
+        "MENTION_EVERYONE": "Mention Everyone"
+    }
+    Object.keys(permsArray).map(perm => {
+        let hasPerm = member.permissions.has(perm as Discord.PermissionString)
+        perms += `${getEmojiByName("CONTROL." + (hasPerm ? "TICK" : "CROSS"))} ${permsArray[perm]}\n`
+    })
+
+    let embeds = [
+        new generateEmojiEmbed()
+            .setTitle("User Info: General")
+            .setStatus("Success")
+            .setEmoji("MEMBER.JOIN")
+            .setDescription(
+                flags.map(flag => {
+                    if (nameReplacements[flag]) {
+                        return getEmojiByName(`BADGES.${flag}`) + " " + nameReplacements[flag];
+                    }
+                }).join("\n") + "\n\n" +
+                generateKeyValueList({
+                    "member": renderUser(member.user),
+                    "nickname": member.nickname || "*None set*",
+                    "id": `\`${member.id}\``,
+                    "joined the server": renderDelta(member.joinedTimestamp),
+                    "joined discord": renderDelta(member.user.createdTimestamp),
+                    "boost status": member.premiumSince ? `Started boosting ${renderDelta(member.premiumSinceTimestamp)}` : "*Not boosting*",
+                    "join position": `${joinPos + 1}`
+                })
+            )
+            .setThumbnail(await member.user.displayAvatarURL({dynamic: true}))
+            .setImage((await member.user.fetch()).bannerURL({format: "gif"})),
+        new generateEmojiEmbed()
+            .setTitle("User Info: Roles")
+            .setStatus("Success")
+            .setEmoji("GUILD.ROLES.CREATE")
+            .setDescription(
+                generateKeyValueList({
+                    "member": renderUser(member.user),
+                    "id": `\`${member.id}\``,
+                    "roles": `${member.roles.cache.size - 1}`,
+                }) + "\n" +
+                (s.length > 0 ? s : "*None*")
+            )
+            .setThumbnail(await member.user.displayAvatarURL({dynamic: true})),
+        new generateEmojiEmbed()
+            .setTitle("User Info: Key Permissions")
+            .setStatus("Success")
+            .setEmoji("GUILD.ROLES.CREATE")
+            .setDescription(
+                generateKeyValueList({
+                    "member": renderUser(member.user),
+                    "id": `\`${member.id}\``,
+                }) + "\n" + perms
+            )
+            .setThumbnail(await member.user.displayAvatarURL({dynamic: true}))
+    ]
+    let m
+    m = await interaction.reply({embeds: [new generateEmojiEmbed().setTitle("Loading").setEmoji("NUCLEUS.LOADING").setStatus("Danger")], fetchReply: true, ephemeral: true});
+    let page = 0
+    while (true) {
+        await interaction.editReply({
+            embeds: [embeds[page].setFooter({text: `Page ${page + 1} of ${embeds.length}`})],
+            components: [new MessageActionRow().addComponents([
+                new MessageButton()
+                    .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                    .setStyle("SECONDARY")
+                    .setCustomId("left")
+                    .setDisabled(page === 0),
+                new MessageButton()
+                    .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
+                    .setCustomId("right")
+                    .setStyle("SECONDARY")
+                    .setDisabled(page === embeds.length - 1),
+                new MessageButton()
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    .setCustomId("close")
+                    .setStyle("DANGER")
+            ])]
+        })
+        let i
+        try {
+            i = await m.awaitMessageComponent({componentType: "BUTTON", time: 600000});
+        } catch { break }
+        i.deferUpdate()
+        if (i.component.customId == "left") {
+            if (page > 0) page--;
+        } else if (i.component.customId == "right") {
+            if (page < embeds.length - 1) page++;
+        } else if (i.component.customId == "close") {
+            break;
+        } else {
+            break;
+        }
+    }
+    await interaction.editReply({embeds: [m.embeds[0]], components: [new MessageActionRow().addComponents([
+        new MessageButton()
+            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+            .setStyle("SECONDARY")
+            .setCustomId("left")
+            .setDisabled(true),
+        new MessageButton()
+            .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
+            .setCustomId("right")
+            .setStyle("SECONDARY")
+            .setDisabled(true),
+        new MessageButton()
+            .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+            .setCustomId("close")
+            .setStyle("PRIMARY")
+            .setDisabled(true)
+    ])]})
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/user/avatar.ts b/src/commands/user/avatar.ts
index 9e22d0c..eb9042a 100644
--- a/src/commands/user/avatar.ts
+++ b/src/commands/user/avatar.ts
@@ -1,14 +1,32 @@
-import { CommandInteraction } from "discord.js";
+import Discord, { CommandInteraction } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import generateKeyValueList from "../../utils/generateKeyValueList.js";
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("avatar")
-    .setDescription("Shows a users avatar")
+    .setDescription("Shows the avatar of a user")
+    .addUserOption(option => option.setName("user").setDescription("The user to get the avatar of | Default: Yourself"))
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("Command incomplete [user/avatar]");
+const callback = async (interaction: CommandInteraction) => {
+    // @ts-ignore
+    const { renderUser } = interaction.client.logger
+    let member = (interaction.options.getMember("user") || interaction.member) as Discord.GuildMember;
+    await interaction.reply({embeds: [new generateEmojiEmbed()
+        .setTitle("User Info")
+        .setStatus("Success")
+        .setEmoji("MEMBER.JOIN")
+        .setDescription(
+            generateKeyValueList({
+                "member": renderUser(member.user),
+                "url": member.user.displayAvatarURL({dynamic: true}),
+            })
+        )
+        .setImage(await member.user.displayAvatarURL({dynamic: true}))
+    ], ephemeral: true, fetchReply: true});
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/verify.ts b/src/commands/verify.ts
index 489a109..c9abb77 100644
--- a/src/commands/verify.ts
+++ b/src/commands/verify.ts
@@ -1,144 +1,14 @@
-import Discord, { CommandInteraction, GuildMember } from "discord.js";
+import { CommandInteraction } from "discord.js";
 import { SlashCommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
-import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
-import readConfig from "../utils/readConfig.js";
-import fetch from "node-fetch";
-import { TestString, NSFWCheck } from "../automations/unscan.js";
+import verify from "../automations/verify.js";
 
 const command = new SlashCommandBuilder()
     .setName("verify")
     .setDescription("Get verified in the server")
 
 const callback = async (interaction: CommandInteraction) => {
-    // @ts-ignore
-    let verify = interaction.client.verify
-    await interaction.reply({embeds: [new generateEmojiEmbed()
-        .setTitle("Loading")
-        .setStatus("Danger")
-        .setEmoji("NUCLEUS.LOADING")
-    ], ephemeral: true, fetchReply: true});
-    let config = await readConfig(interaction.guild.id);
-    if ((interaction.member as GuildMember).roles.cache.has(config.verify.role)) {
-        return await interaction.editReply({embeds: [new generateEmojiEmbed()
-            .setTitle("Verify")
-            .setDescription(`You already have the <@&${config.verify.role}> role`)
-            .setStatus("Danger")
-            .setEmoji("CONTROL.BLOCKCROSS")
-        ]});
-    } else if (interaction.channel.id != config.verify.channel) {
-        return await interaction.editReply({embeds: [new generateEmojiEmbed()
-            .setTitle("Verify")
-            .setDescription(`You can only use this command in <#${config.verify.channel}>`)
-            .setStatus("Danger")
-            .setEmoji("CONTROL.BLOCKCROSS")
-        ]});
-    }
-    await interaction.editReply({embeds: [new generateEmojiEmbed()
-        .setTitle("Verify")
-        .setDescription(`Checking our servers are up`)
-        .setStatus("Warning")
-        .setEmoji("NUCLEUS.LOADING")
-    ]});
-    try {
-        let status = await fetch(`https://clicksminuteper.net`).then(res => res.status);
-        if (status != 200) {
-            return await interaction.editReply({embeds: [new generateEmojiEmbed()
-                .setTitle("Verify")
-                .setDescription(`Our servers appear to be down, please try again later`)
-                .setStatus("Danger")
-                .setEmoji("CONTROL.BLOCKCROSS")
-            ]});
-        }
-    } catch {
-        return await interaction.editReply({embeds: [new generateEmojiEmbed()
-            .setTitle("Verify")
-            .setDescription(`Our servers appear to be down, please try again later`)
-            .setStatus("Danger")
-            .setEmoji("CONTROL.BLOCKCROSS")
-        ], components: [new Discord.MessageActionRow().addComponents([
-            new Discord.MessageButton()
-                .setLabel("Open webpage")
-                .setStyle("LINK")
-                .setURL("https://clicksminuteper.net/"),
-            new Discord.MessageButton()
-                .setLabel("Support")
-                .setStyle("LINK")
-                .setURL("https://discord.gg/bPaNnxe")
-        ])]});
-    }
-    if (config.filters.images.NSFW) {
-        await interaction.editReply({embeds: [new generateEmojiEmbed()
-            .setTitle("Verify")
-            .setDescription(`Checking your avatar is safe for work`)
-            .setStatus("Warning")
-            .setEmoji("NUCLEUS.LOADING")
-        ]});
-        if (await NSFWCheck((interaction.member as GuildMember).user.avatarURL({format: "png"}))) {
-            return await interaction.editReply({embeds: [new generateEmojiEmbed()
-                .setTitle("Verify")
-                .setDescription(`Your avatar was detected as NSFW, which we do not allow in this server.\nPlease contact one of our staff members if you believe this is a mistake`)
-                .setStatus("Danger")
-                .setEmoji("CONTROL.BLOCKCROSS")
-            ]});
-        }
-    }
-    if (config.filters.wordFilter) {
-        await interaction.editReply({embeds: [new generateEmojiEmbed()
-            .setTitle("Verify")
-            .setDescription(`Checking your name is allowed`)
-            .setStatus("Warning")
-            .setEmoji("NUCLEUS.LOADING")
-        ]});
-        if (TestString((interaction.member as Discord.GuildMember).displayName, config.filters.wordFilter.words.loose, config.filters.wordFilter.words.strict) != "none") {
-            return await interaction.editReply({embeds: [new generateEmojiEmbed()
-                .setTitle("Verify")
-                .setDescription(`Your name contained a word we do not allow in this server.\nPlease contact one of our staff members if you believe this is a mistake`)
-                .setStatus("Danger")
-                .setEmoji("CONTROL.BLOCKCROSS")
-            ]});
-        }
-    }
-    await interaction.editReply({embeds: [new generateEmojiEmbed()
-        .setTitle("Verify")
-        .setDescription(`One moment...`)
-        .setStatus("Warning")
-        .setEmoji("NUCLEUS.LOADING")
-    ]});
-    let code = ""
-    let length = 5
-    let itt = 0
-    const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-    while (true) {
-        itt += 1
-        code = ""
-        for (let i = 0; i < length; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)); }
-        if (code in verify) continue;
-        if (itt > 1000) {
-            itt = 0
-            length += 1
-            continue
-        }
-        break;
-    }
-    verify[code] = {
-        uID: interaction.member.user.id,
-        gID: interaction.guild.id,
-        rName: (await interaction.guild.roles.fetch(config.verify.role)).name,
-        mCount: interaction.guild.memberCount,
-        gName: interaction.guild.name,
-        guildIcon: interaction.guild.iconURL({format: "png"})
-    }
-    await interaction.editReply({embeds: [new generateEmojiEmbed()
-        .setTitle("Verify")
-        .setDescription(`Looking good!\nClick the button below to get verified`)
-        .setStatus("Success")
-        .setEmoji("MEMBER.JOIN")
-    ], components: [new Discord.MessageActionRow().addComponents([new Discord.MessageButton()
-        .setLabel("Verify")
-        .setStyle("LINK")
-        .setURL(`https://clicksminuteper.net/nucleus/verify?code=${code}`)
-    ])]});
+    verify(interaction);
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/config/emojis.json b/src/config/emojis.json
index b759b8a..4c60cab 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -35,6 +35,8 @@
         "BLOCKCROSS": "952261738349330493",
         "LEFT": "947441951148486728",
         "RIGHT": "947441957473488916",
+        "UP": "963409197293273108",
+        "DOWN": "963409199549796352",
         "DOWNLOAD": "947959513032585236",
         "PILL": {
             "TICK": "753314339082993832",
@@ -92,8 +94,7 @@
         },
         "KICK": "729263536785850458",
         "BAN": "729263536643112991",
-        "UNBAN": "729263536840114216",
-        "NICKNAME": "729064531019694090"
+        "UNBAN": "729263536840114216"
     },
     "INVITE": {
         "CREATE": "729064529274601482",
@@ -143,29 +144,28 @@
         },
         "SOFTBAN": "729764053941223476",
         "VOICEMUTE": "729764054855450697",
-        "CLEARHISTORY": "729764062270980096"
+        "CLEARHISTORY": "729764062270980096",
+        "NICKNAME": {
+            "RED": "959762533101731980",
+            "YELLOW": "729064531019694090",
+            "GREEN": "959762533072392202"
+        }
     },
     "BADGES": {
-        "NUCLEUSDEVELOPER": "775783766147858534",
-        "CLICKSDEVELOPER": "776140126156881950",
-        "HYPESQUAD": {
-            "BRILLIANCE": "775783766152577095",
-            "BRAVERY": "775783765930016789",
-            "BALANCE": "775783766303440937",
-            "EVENTS": "775783766194126908"
-        },
-        "EARLYSUPPORTER": "775783766055452693",
-        "BUGHUNTER": [
-            null,
-            "775783766252847154",
-            "775783766130950234"
-        ],
-        "BOOSTER": "775783766131605545",
-        "PARTNER": "775783766178005033",
-        "STAFF": "775783766383788082",
-        "VERIFIEDBOTDEVELOPER": "775783766425600060",
-        "NITRO": "776149266775146546",
-        "BOT": "776375959108190239"
+        "NUCLEUSDEVELOPER": "957722888360853595",
+        "CLICKSDEVELOPER": "957722888314683462",
+        "HOUSE_BRAVERY": "775783765930016789",
+        "HOUSE_BRILLIANCE": "775783766152577095",
+        "HOUSE_BALANCE": "775783766303440937",
+        "HYPESQUAD_EVENTS": "775783766194126908",
+        "EARLY_SUPPORTER": "775783766055452693",
+        "BUGHUNTER_LEVEL_1": "775783766252847154",
+        "BUGHUNTER_LEVEL_2": "775783766130950234",
+        "PARTNERED_SERVER_OWNER": "775783766178005033",
+        "DISCORD_EMPLOYEE": "775783766383788082",
+        "EARLY_VERIFIED_BOT_DEVELOPER": "775783766425600060",
+        "BOT": "776375959108190239",
+        "BOOSTER": "775783766131605545"
     },
     "VOICE": {
         "CONNECT": "784785219391193138",
@@ -185,6 +185,9 @@
         }
     },
     "GUILD": {
+        "RED": "959779988264079361",
+        "YELLOW": "729763053352124529",
+        "GREEN": "959779988503154698",
         "EMOJI": {
             "CREATE": "953035168115982437",
             "EDIT": "729066518549233795",
@@ -193,7 +196,6 @@
         "GRAPHS": "752214059159650396",
         "SETTINGS": "752570111063228507",
         "ICONCHANGE": "729763053612302356",
-        "MODERATIONUPDATE": "729763053352124529",
         "TICKET": {
             "OPEN": "853245836331188264",
             "CLOSE": "853580122506133505",
@@ -289,5 +291,56 @@
         "ISSUE": "952295894412316672",
         "SUGGESTION": "952295894399725588",
         "OTHER": "952295894445883502"
+    },
+    "TRACKS": {
+        "ICON": "963170616444334171",
+        "HORIZONTAL": {
+            "LEFT": {
+                "ACTIVE": "963121920038035506",
+                "INACTIVE": "963121944239153242"
+            },
+            "MIDDLE": {
+                "ACTIVE": "963121925893263420",
+                "INACTIVE": "963121949796597870"
+            },
+            "RIGHT": {
+                "ACTIVE": "963121933384302602",
+                "INACTIVE": "963121956125831168"
+            }
+        },
+        "VERTICAL": {
+            "TOP": {
+                "ACTIVE": "963122664648630293",
+                "INACTIVE": "963122659862917140",
+                "GREY": {
+                    "ACTIVE": "963123505052934144",
+                    "INACTIVE": "963123495221469194"
+                }
+            },
+            "MIDDLE": {
+                "ACTIVE": "963122679332880384",
+                "INACTIVE": "963122673246937199",
+                "GREY": {
+                    "ACTIVE": "963123517702955018",
+                    "INACTIVE": "963123511927390329"
+                }
+            },
+            "BOTTOM": {
+                "ACTIVE": "963122691752218624",
+                "INACTIVE": "963122685691453552",
+                "GREY": {
+                    "ACTIVE": "963123529988059187",
+                    "INACTIVE": "963123523742748742"
+                }
+            }
+        },
+        "SINGLE": {
+            "ACTIVE": "963361162215424060",
+            "INACTIVE": "963361431758176316",
+            "GREY": {
+                "ACTIVE": "963361204695334943",
+                "INACTIVE": "963361200828198952"
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts
index 49bd3c3..ac29238 100644
--- a/src/events/channelDelete.ts
+++ b/src/events/channelDelete.ts
@@ -1,3 +1,5 @@
+import getEmojiByName from "../utils/getEmojiByName.js";
+
 export const event = 'channelDelete'
 
 export async function callback(client, channel) {
@@ -35,6 +37,21 @@
 			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:{
@@ -45,15 +62,7 @@
 			emoji: emoji,
 			timestamp: audit.createdTimestamp
 		},
-		list: { // TODO: Add stuff like nsfw, theres loads missing here
-			id: entry(channel.id, `\`${channel.id}\``),
-			name: entry(channel.id, `${channel.name}`),
-			type: entry(channel.type, readableType),
-			category: entry(channel.parent ? channel.parent.id : null, channel.parent ? channel.parent.name : "Uncategorised"),
-			created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp)),
-			deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
-			deletedBy: entry(audit.executor.id, renderUser(audit.executor))
-		},
+		list: list,
 		hidden: {
 			guild: channel.guild.id
 		}
diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts
index f5d2f4d..f374edd 100644
--- a/src/events/channelUpdate.ts
+++ b/src/events/channelUpdate.ts
@@ -20,7 +20,7 @@
 	let changes = {
 		id: entry(nc.id, `\`${nc.id}\``),
 		channel: entry(nc.id, renderChannel(nc)),
-		edited: entry(nc.createdTimestamp, renderDelta(nc.createdTimestamp)),
+		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}`);
@@ -114,7 +114,7 @@
 	}
 	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',
diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts
index b437b37..4935b4d 100644
--- a/src/events/guildCreate.ts
+++ b/src/events/guildCreate.ts
@@ -1,56 +1,10 @@
 import { MessageActionRow, MessageButton } from "discord.js";
 import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
-import getEmojiByName from "../utils/getEmojiByName";
+import getEmojiByName from "../utils/getEmojiByName.js";
+import guide from "../automations/guide.js";
 
 export const event = 'guildCreate';
 
-export const callback = async (client, guild) => {
-    let pages = [
-        new generateEmojiEmbed()
-            .setTitle("Welcome to Nucleus")
-            .setDescription(
-                "Thanks for adding Nucleus to your server\n\n" +
-                "On the next few pages you can find instructions on getting started, and commands you may want to set up\n\n" +
-                "If you need support, have questions or want features, you can let us know in [Clicks](https://discord.gg/bPaNnxe)"
-            )
-            .setEmoji("NUCLEUS.LOGO")
-            .setStatus("Danger"),
-        new generateEmojiEmbed()
-    ]
-    let m = await guild.systemChannel.send({embeds: [
-        new generateEmojiEmbed()
-            .setTitle("Welcome")
-            .setDescription(`One moment...`)
-            .setStatus("Danger")
-            .setEmoji("NUCLEUS.LOADING")
-    ], fetchReply: true });
-    let page = 0;
-
-    let f = async () => {
-
-    }
-
-    while (true) {
-        // edit interaction with pages[page]
-        await m.edit({
-            embeds: [pages[page].setFooter({text: `Page ${page + 1}/${pages.length}`})],
-            components: [new MessageActionRow().addComponents([
-                new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setDisabled(page === 0),
-                new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setDisabled(page === pages.length - 1)
-            ])],
-            fetchReply: true
-        });
-        // wait for interaction
-        let interaction = await m.awaitMessageComponent({filter:f, componentType: "BUTTON", time: 60000});
-        // change page variable accordingly
-        if (interaction.component.customId == "left") {
-            if (page > 0) page--;
-        } else if (interaction.component.customId == "right") {
-            if (page < pages.length - 1) page++;
-        } else {
-            await m.delete()
-            break;
-        }
-        // break if required
-    }
-}
\ No newline at end of file
+export async function callback(client, guild) {
+    guide(guild)
+}
diff --git a/src/events/memberLeave.ts b/src/events/memberLeave.ts
index 46d7696..ea461ab 100644
--- a/src/events/memberLeave.ts
+++ b/src/events/memberLeave.ts
@@ -1,10 +1,12 @@
 import humanizeDuration from 'humanize-duration';
+import { purgeByUser } from '../automations/tickets/delete.js';
 import { callback as statsChannelRemove } from '../automations/statsChannelRemove.js';
 
 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
     try {
         const { log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
         let data = {
diff --git a/src/events/messageChecks.ts b/src/events/messageChecks.ts
index 71ca965..83cfff1 100644
--- a/src/events/messageChecks.ts
+++ b/src/events/messageChecks.ts
@@ -1,4 +1,4 @@
-import { LinkCheck, MalwareCheck, NSFWCheck, SizeCheck, TestString } from '../automations/unscan.js'
+import { LinkCheck, MalwareCheck, NSFWCheck, SizeCheck, TestString, TestImage } from '../automations/unscan.js'
 import readConfig from '../utils/readConfig.js'
 import { Message } from 'discord.js'
 
diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts
index 3b7c5c2..ccaacb8 100644
--- a/src/events/messageDelete.ts
+++ b/src/events/messageDelete.ts
@@ -16,7 +16,7 @@
             timestamp: new Date().getTime()
         },
         separate: {
-            start: `**Message:**\n\`\`\`${content}\`\`\``
+            start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : '**Message:** *Message had no content*',
         },
         list: {
             id: entry(message.id, `\`${message.id}\``),
diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts
index bb74f8d..318b0ef 100644
--- a/src/events/messageEdit.ts
+++ b/src/events/messageEdit.ts
@@ -2,11 +2,11 @@
 
 export async function callback(client, oldMessage, newMessage) {
     if (newMessage.author.id == client.user.id) return;
-    if (!newMessage.content || !oldMessage.content) return;
 	const { log, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = newMessage.channel.client.logger
     newMessage.reference = newMessage.reference || {}
-    let newContent = newMessage.cleanContent
-    let oldContent = oldMessage.cleanContent
+    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 = {
@@ -19,7 +19,8 @@
             timestamp: newMessage.editedTimestamp
         },
         separate: {
-            start: `**Before:**\n\`\`\`\n${oldContent}\n\`\`\`\n**After:**\n\`\`\`\n${newContent}\n\`\`\``,
+            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: {
diff --git a/src/events/roleCreate.ts b/src/events/roleCreate.ts
index 487a45e..76af433 100644
--- a/src/events/roleCreate.ts
+++ b/src/events/roleCreate.ts
@@ -2,6 +2,7 @@
 
 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;
diff --git a/src/events/roleDelete.ts b/src/events/roleDelete.ts
index 0c6d03e..c5cbe63 100644
--- a/src/events/roleDelete.ts
+++ b/src/events/roleDelete.ts
@@ -3,7 +3,8 @@
 export const event = 'roleDelete'
 
 export async function callback(client, role) {
-	const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderRole } = role.client.logger
+	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;
diff --git a/src/index.ts b/src/index.ts
index 9e2770c..3b735cb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,5 +11,6 @@
 
 client.logger = new Logger()
 client.verify = {}
+client.roleMenu = {}
 
 await client.login();
\ No newline at end of file
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index 7deb5f5..fc8b76c 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -1,5 +1,5 @@
 import Discord, { CommandInteraction, MessageActionRow, Message } from "discord.js";
-import EmojiEmbed from "./generateEmojiEmbed.js"
+import generateEmojiEmbed from "./generateEmojiEmbed.js"
 import getEmojiByName from "./getEmojiByName.js";
 
 class confirmationMessage {
@@ -8,6 +8,12 @@
     emoji: string;
     description: string;
     color: string;
+    customCallback: () => any;
+    customButtonTitle: string;
+    customButtonDisabled: boolean;
+    customCallbackString: string = "";
+    customCallbackClicked: boolean = false;
+    customCallbackResponse: any = null;
 
     constructor(interaction: CommandInteraction) {
         this.interaction = interaction;
@@ -16,54 +22,81 @@
         this.emoji = "";
         this.description = "";
         this.color = "";
+        this.customCallback = () => {}
     }
 
     setTitle(title: string) { this.title = title; return this }
     setEmoji(emoji: string) { this.emoji = emoji; return this }
     setDescription(description: string) { this.description = description; return this }
     setColor(color: string) { this.color = color; return this }
+    addCustomCallback(title: string, disabled: boolean, callback: () => any, callbackClicked: string) {
+        this.customButtonTitle = title;
+        this.customButtonDisabled = disabled;
+        this.customCallback = callback;
+        this.customCallbackString = callbackClicked;
+        return this;
+    }
 
     async send(editOnly?: boolean) {
-        let object = {
-            embeds: [
-                new EmojiEmbed()
-                    .setEmoji(this.emoji)
-                    .setTitle(this.title)
-                    .setDescription(this.description)
-                    .setStatus(this.color)
-            ],
-            components: [
-                new MessageActionRow().addComponents([
-                    new Discord.MessageButton()
-                        .setCustomId("yes")
-                        .setLabel("Yes")
-                        .setStyle("SUCCESS")
-                        .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
-                    new Discord.MessageButton()
-                        .setCustomId("no")
-                        .setLabel("Cancel")
-                        .setStyle("DANGER")
-                        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-                ])
-            ],
-            ephemeral: true,
-            fetchReply: true
+        while (true) {
+            let object = {
+                embeds: [
+                    new generateEmojiEmbed()
+                        .setEmoji(this.emoji)
+                        .setTitle(this.title)
+                        .setDescription(this.description)
+                        .setStatus(this.color)
+                        .setFooter({text: this.customCallbackClicked ? this.customCallbackString : ""})
+                ],
+                components: [
+                    new MessageActionRow().addComponents([
+                        new Discord.MessageButton()
+                            .setCustomId("yes")
+                            .setLabel("Yes")
+                            .setStyle("SUCCESS")
+                            .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
+                        new Discord.MessageButton()
+                            .setCustomId("no")
+                            .setLabel("Cancel")
+                            .setStyle("DANGER")
+                            .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    ].concat(this.customButtonTitle ? [new Discord.MessageButton()
+                        .setCustomId("custom")
+                        .setLabel(this.customButtonTitle)
+                        .setStyle("SECONDARY")
+                        .setDisabled(this.customButtonDisabled)
+                        .setEmoji(getEmojiByName("CONTROL.RIGHT", "id")) // TODO: add an emoji
+                    ] : []))
+                ],
+                ephemeral: true,
+                fetchReply: true
+            }
+            let m;
+            if ( editOnly ) {
+                m = await this.interaction.editReply(object);
+            } else {
+                m = await this.interaction.reply(object)
+            }
+            let component;
+            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
+            }
+            if (component.customId === "yes") {
+                component.deferUpdate();
+                return {success: true, buttonClicked: this.customCallbackClicked, response: this.customCallbackResponse};
+            } else if (component.customId === "no") {
+                component.deferUpdate();
+                return {success: false, buttonClicked: this.customCallbackClicked, response: this.customCallbackResponse};
+            } else if (component.customId === "custom") {
+                component.deferUpdate();
+                this.customCallbackResponse = this.customCallback();
+                this.customCallbackClicked = true;
+                this.customButtonDisabled = true;
+                editOnly = true;
+            }
         }
-        let m;
-        if ( editOnly ) {
-            m = await this.interaction.editReply(object);
-        } else {
-            m = await this.interaction.reply(object)
-        }
-        let component;
-        try {
-            component = await (m as Message).awaitMessageComponent({filter: (m) => m.user.id === this.interaction.user.id, time: 2.5 * 60 * 1000});
-        } catch (e) {
-            return false;  // TODO: Check the type of the error; change the error message here
-        }
-        component.deferUpdate();
-
-        return component.customId === "yes"
     }
 }
 
diff --git a/src/utils/generateKeyValueList.ts b/src/utils/generateKeyValueList.ts
index b3e276f..c9eb0c8 100644
--- a/src/utils/generateKeyValueList.ts
+++ b/src/utils/generateKeyValueList.ts
@@ -5,7 +5,14 @@
 
 export function capitalize(s: string) {
     s = s.replace(/([A-Z])/g, ' $1');
-    return forceCaps.includes(s.toUpperCase()) ? s.toUpperCase() : s[0].toUpperCase() + s.slice(1).toLowerCase();
+    return forceCaps.includes(s.toUpperCase()) ? s.toUpperCase() : s[0]
+        .toUpperCase() + s.slice(1)
+        .toLowerCase()
+        .replace("discord", "Discord");
+}
+
+export function toCapitals(s: string) {
+    return s[0].toUpperCase() + s.slice(1).toLowerCase();
 }
 
 function keyValueList(data) {
diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts
index bbc8f24..80410df 100644
--- a/src/utils/getEmojiByName.ts
+++ b/src/utils/getEmojiByName.ts
@@ -11,11 +11,11 @@
         return id.toString();
     }
     if (id === undefined) {
-        return `<a:a:946346549271732234>`
+        return `<a:_:946346549271732234>`
     } else if (id.toString().startsWith("a")) {
-        return `<a:a:${id.toString().slice(1, id.toString().length)}>`
+        return `<a:_:${id.toString().slice(1, id.toString().length)}>`
     }
-    return `<:a:${id}>`;
+    return `<:_:${id}>`;
 }
 
 export default getEmojiByName;
diff --git a/src/utils/log.ts b/src/utils/log.ts
index d807831..14e9750 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -10,7 +10,8 @@
 
 
 export class Logger {
-	renderUser(user: Discord.User) {
+	renderUser(user: Discord.User | string) {
+		if (typeof user == 'string') return `${user} [<@${user}>]`;
 		return `${user.username} [<@${user.id}>]`;
 	}
 	renderTime(t: number) {
@@ -45,7 +46,6 @@
 
 	}
 
-
 	async getAuditLog(guild: Discord.Guild, event) {
 		await wait(250)
 		let auditLog = await guild.fetchAuditLogs({type: event});
diff --git a/src/utils/readConfig.ts b/src/utils/readConfig.ts
index c53d2cc..fb6835b 100644
--- a/src/utils/readConfig.ts
+++ b/src/utils/readConfig.ts
@@ -1,85 +1,165 @@
 
 export default async function readConfig(guild: string): Promise<any> {
 
-	let config = {
-		filters: {
-			images: {
-				NSFW: true,
-				size: true
-			},
-			malware: true,
-			wordFilter: {
-				enabled: true,
-				words: {
-					strict: [],
-					loose: []
-				},
-				allowed: {
-					users: [],
-					roles: [],
-					channels: []
-				}
-			},
-			invite: {
-				enabled: false,
-				allowed: {
-					users: [],
-					channels: [],
-					roles: []
-				}
-			},
-			pings: {
-				mass: 5,
-				everyone: true,
-				roles: true,
-				allowed: {
-					roles: [],
-					rolesToMention: [],
-					users: [],
-					channels: []
-				}
-			}
-		},
-		welcome: {
-			enabled: true,
-			verificationRequired: {
-				message: false,
-				role: false
-			},
-			welcomeRole: null,
-			channel: '895209752315961344', // null, channel ID or 'dm'
-			message: "Welcome to the server, {@}!"
-		},
-		stats: [
-			{
-				enabled: true,
-				channel: '951910554291818526',
-				text: "{count} members | {count:bots} bots | {count:humans} humans"
-			}
-		],
-		logging: {
-			logs: {
-				enabled: true,
-				channel: '952247098437427260',
-				toLog: "3fffff" // "3ffffe" = - channelUpdate, "3fffff" = all
-			},
-			staff: {}
-		},
-		verify: {
-			enabled: true,
-			channel: '895210691479355392',
-			role: '934941369137524816',
-		},
-		tickets: {
-			enabled: true,
-			category: "952302254302584932",
-			types: "3f",
-			customTypes: null,
-			supportRole: null,
-			maxTickets: 5
-		}
-	};
-
-	return config
-
-}
\ No newline at end of file
+    let config = {
+        singleEventNotifications: {
+            statsChannelDeleted: false
+        },
+        filters: {
+            images: {
+                NSFW: true,
+                size: true
+            },
+            malware: true,
+            wordFilter: {
+                enabled: true,
+                words: {
+                    strict: [],
+                    loose: []
+                },
+                allowed: {
+                    users: [],
+                    roles: [],
+                    channels: []
+                }
+            },
+            invite: {
+                enabled: false,
+                allowed: {
+                    users: [],
+                    channels: [],
+                    roles: []
+                }
+            },
+            pings: {
+                mass: 5,
+                everyone: true,
+                roles: true,
+                allowed: {
+                    roles: [],
+                    rolesToMention: [],
+                    users: [],
+                    channels: []
+                }
+            }
+        },
+        welcome: {
+            enabled: true,
+            verificationRequired: {
+                message: false,
+                role: false
+            },
+            welcomeRole: null,
+            channel: '895209752315961344', // null, channel ID or 'dm'
+            message: "Welcome to the server, {@}!"
+        },
+        stats: [
+            {
+                enabled: true,
+                channel: '951910554291818526',
+                text: "{count} members | {count:bots} bots | {count:humans} humans"
+            }
+        ],
+        logging: {
+            logs: {
+                enabled: true,
+                channel: '952247098437427260',
+                toLog: "3fffff" // "3ffffe" = - channelUpdate, "3fffff" = all
+            },
+            staff: {
+                channel: "895212366252367933"
+            }
+        },
+        verify: {
+            enabled: true,
+            role: '934941369137524816',
+        },
+        tickets: {
+            enabled: true,
+            category: "952302254302584932",
+            types: "3f",
+            customTypes: null,
+            supportRole: null,
+            maxTickets: 5
+        },
+        moderation: {
+            mute: {
+                timeout: true,
+                role: null, // TODO: actually give it
+                text: null,
+                link: null
+            },
+            kick: {
+                text: "Appeal here",
+                link: "https://clicksminuteper.net"
+            },
+            ban: {
+                text: null,
+                link: null
+            },
+            softban: {
+                text: null,
+                link: null
+            },
+            warn: {
+                text: null,
+                link: null
+            },
+            role: {
+                role: "934941369137524816"
+            },
+        },
+        tracks: [
+            {
+                name: "Moderation",
+                retainPrevious: false,
+                nullable: true,
+                track: [
+                    "934941369137524816",
+                    "934941399806246984",
+                    "934941408849186856",
+                    "934941466734764092"
+                ],
+                manageableBy: []
+            },
+            {
+                name: "Verification",
+                retainPrevious: false,
+                nullable: true,
+                track: [
+                    "963166531318067250"
+                ],
+                manageableBy: []
+            }
+        ],
+        roleMenu: {
+            enabled: true,
+            allowWebUI: true,
+            options: [
+                {
+                    name: "Gender",
+                    description: "What's your gender?",
+                    min: 1,
+                    max: 1,
+                    options: [
+                        { name: "Male", role: "959901318019948574" },
+                        { name: "Female", role: "959901346000154674" },
+                        { name: "Non Binary", description: "Better than the others", role: "959901378363420704"}
+                    ]
+                },
+                {
+                    name: "Pick",
+                    min: 0,
+                    max: 4,
+                    options: [
+                        { name: "Test Role 1", role: "934941369137524816" },
+                        { name: "Test Role 2", role: "934941399806246984" },
+                        { name: "Test Role 3", role: "934941408849186856" },
+                        { name: "Test Role 4", role: "934941466734764092" }
+                    ]
+                }
+            ]
+        }
+    };
+    return config;
+}