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 @@ export async function callback(_, member) { 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 { 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 LinkCheck(message): Promise { export async function NSFWCheck(element): Promise { 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 @@ export function TestString(string, soft, strict): string { } } return "none" +} + +export async function TestImage(element): Promise { + 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 @@ export async function callback(_, member) { } } - if (!config.welcome.verificationRequired.message && config.welcome.channel) { let string = config.welcome.message if (string) { @@ -32,7 +31,7 @@ export async function callback(_, member) { } 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 command = (builder: SlashCommandSubcommandBuilder) => 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 @@ const callback = async (interaction: CommandInteraction) => { .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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { 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 command = (builder: SlashCommandSubcommandBuilder) => 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 @@ const callback = async (interaction: CommandInteraction) => { .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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { } } else { await interaction.reply({embeds: [ - new EmojiEmbed() + new generateEmojiEmbed() .setEmoji("PUNISH.MUTE.GREEN") .setTitle("Mute") .setDescription("Loading...") @@ -114,7 +118,7 @@ const callback = async (interaction: CommandInteraction) => { ], 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 @@ const callback = async (interaction: CommandInteraction) => { .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: at ()`)) .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 @@ const callback = async (interaction: CommandInteraction) => { 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 Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel } f 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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { } 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { } 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { ], 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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const callback = async (interaction: CommandInteraction) => { .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 @@ const callback = async (interaction: CommandInteraction) => { }); 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 @@ const callback = async (interaction: CommandInteraction) => { ], 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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 { CommandInteraction, GuildMember } 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"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -17,7 +17,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => 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 @@ const callback = async (interaction: CommandInteraction) => { .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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const command = (builder: SlashCommandSubcommandBuilder) => 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 Discord, { CommandInteraction, GuildMember, MessageActionRow } from "disc 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 command = (builder: SlashCommandSubcommandBuilder) => 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 @@ const callback = async (interaction: CommandInteraction) => { + `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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { ], 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { } 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 @@ const callback = async (interaction: CommandInteraction) => { 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 @@ const callback = async (interaction: CommandInteraction) => { } } } 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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ import { WrappedCheck } from "jshaiku"; 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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const callback = async (interaction: CommandInteraction) => { } 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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = new SlashCommandBuilder() .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ const command = (builder: SlashCommandSubcommandBuilder) => .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 @@ export async function callback(client, channel) { displayName = "Channel" } } + let list = { + id: entry(channel.id, `\`${channel.id}\``), + name: entry(channel.id, `${channel.name}`), + topic: null, + type: entry(channel.type, readableType), + category: entry(channel.parent ? channel.parent.id : null, channel.parent ? channel.parent.name : "Uncategorised"), + nsfw: null, + created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp)), + deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + deletedBy: entry(audit.executor.id, renderUser(audit.executor)) + } + if (channel.topic != null ?? false) list.topic = entry(channel.topic, `\`\`\`\n${channel.topic.replace('`', "'")}\n\`\`\``); + else delete list.topic; + if (channel.nsfw !== null ?? false) list.nsfw = entry(channel.nsfw, channel.nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`); + else delete list.nsfw; let data = { meta:{ @@ -45,15 +62,7 @@ export async function callback(client, channel) { 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 @@ export async function callback(client, oc, nc) { 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 @@ export async function callback(client, oc, nc) { } 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 @@ export async function callback(client, message) { 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 const event = 'messageUpdate' 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 @@ export async function callback(client, oldMessage, newMessage) { 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 const event = 'roleCreate' export async function callback(client, role) { const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderRole } = role.client.logger + if (role.managed) return; let auditLog = await getAuditLog(role.guild, 'ROLE_CREATE'); let audit = auditLog.entries.filter(entry => entry.target.id == role.id).first(); if (audit.executor.id == client.user.id) return; 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 @@ import getEmojiByName from "../utils/getEmojiByName.js"; 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 @@ await client.registerEventsIn("./events"); 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 @@ class confirmationMessage { 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 @@ class confirmationMessage { 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 - } - let m; - if ( editOnly ) { - m = await this.interaction.editReply(object); - } else { - m = await this.interaction.reply(object) + 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 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 @@ const forceCaps = [ 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 @@ function getEmojiByName(name: string, format?: string): string { return id.toString(); } if (id === undefined) { - return `` + return `` } else if (id.toString().startsWith("a")) { - return `` + return `` } - 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 @@ const wait = promisify(setTimeout); 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 @@ export class Logger { } - 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 { - 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; +}