diff --git a/package-lock.json b/package-lock.json index b632a10..7865267 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,14 +11,15 @@ "dependencies": { "@prisma/client": "^4.10.1", "@sapphire/discord.js-utilities": "^6.0.0", - "@sapphire/framework": "^4.0.1", + "@sapphire/framework": "^4.1.0", "@sapphire/plugin-logger": "^3.0.1", - "@sapphire/plugin-scheduled-tasks": "^4.0.0", + "@sapphire/plugin-scheduled-tasks": "^6.0.0", "@sapphire/plugin-subcommands": "^4.0.0", - "@sapphire/stopwatch": "^1.4.1", + "@sapphire/stopwatch": "^1.5.0", + "@sapphire/time-utilities": "^1.7.8", "@sapphire/utilities": "^3.9.2", "@types/node": "^18.0.3", - "bullmq": "^1.89.1", + "bullmq": "^3.6.6", "discord.js": "^14.7.1", "dotenv": "^16.0.1", "ts-node": "^10.8.2", @@ -346,6 +347,18 @@ "npm": ">=7.0.0" } }, + "node_modules/@sapphire/cron": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/cron/-/cron-1.0.0.tgz", + "integrity": "sha512-pKYfpnHiDFknur3yoquA8cqbJZS140y2oqjshwGGmtjiuIbUngQhPHGwdWHNDKDrF6EKbOK06nd2URE+0eUrfQ==", + "dependencies": { + "@sapphire/utilities": "^3.9.3" + }, + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@sapphire/discord-utilities": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sapphire/discord-utilities/-/discord-utilities-3.0.0.tgz", @@ -383,9 +396,9 @@ } }, "node_modules/@sapphire/framework": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@sapphire/framework/-/framework-4.0.2.tgz", - "integrity": "sha512-IoSZGBPJjiINJKJKaBfnpEB1IxPv7yitunnvJ6V5XcTdxP51I/KsVJX2ELxiH7sslg8ZrQQMRIcluGLbVwv4KA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sapphire/framework/-/framework-4.1.0.tgz", + "integrity": "sha512-jtwZPysF13Sn8h2p8nkIPETveGAxRmYmiqxYkd3VXV8VPWwKBG8IriuI4oExpSnuCqxIs5HRpo3M+Gl+f/mdCg==", "dependencies": { "@discordjs/builders": "^1.4.0", "@sapphire/discord-utilities": "^3.0.0", @@ -443,12 +456,12 @@ } }, "node_modules/@sapphire/plugin-scheduled-tasks": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sapphire/plugin-scheduled-tasks/-/plugin-scheduled-tasks-4.0.1.tgz", - "integrity": "sha512-vLxfHBu2vKaJZ9v2f4z+VDZaPeDqS8bm+Sc2minRwJPw1hWAHiPqmxCBPIONY7eOQ9qKayvhKYTIwwruxgO/Mg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/plugin-scheduled-tasks/-/plugin-scheduled-tasks-6.0.0.tgz", + "integrity": "sha512-R9rga1aZk3GSXkmGfBMQR8Ng4ou36l5WGWoDvPaq1xNb56wZJ2zPZPsQHV6lxNoyOT++M8GFhVhh9OUB+xwEXg==", "dependencies": { - "@sapphire/stopwatch": "^1.4.1", - "@sapphire/utilities": "^3.9.3", + "@sapphire/stopwatch": "^1.5.0", + "@sapphire/utilities": "^3.11.0", "tslib": "^2.4.0" }, "engines": { @@ -524,6 +537,21 @@ "npm": ">=7.0.0" } }, + "node_modules/@sapphire/time-utilities": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@sapphire/time-utilities/-/time-utilities-1.7.8.tgz", + "integrity": "sha512-T6X/nwCvKhxmNRexgmA3KwLt3Z+xzlErkre4viflx46hHOmNNb3hoIyQtekgHYrabEaHWNbqW4PW7gC3hBc+ag==", + "dependencies": { + "@sapphire/cron": "^1.0.0", + "@sapphire/duration": "^1.0.0", + "@sapphire/timer-manager": "^1.0.0", + "@sapphire/timestamp": "^1.0.0" + }, + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@sapphire/timer-manager": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@sapphire/timer-manager/-/timer-manager-1.0.0.tgz", @@ -612,9 +640,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.19.tgz", - "integrity": "sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw==" + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" }, "node_modules/@types/semver": { "version": "7.3.13", @@ -631,14 +659,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", - "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz", + "integrity": "sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/type-utils": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/type-utils": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -665,14 +693,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", - "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.52.0.tgz", + "integrity": "sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "debug": "^4.3.4" }, "engines": { @@ -692,13 +720,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", - "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz", + "integrity": "sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0" + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -709,13 +737,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", - "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz", + "integrity": "sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/typescript-estree": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -736,9 +764,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", - "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.52.0.tgz", + "integrity": "sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -749,13 +777,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", - "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz", + "integrity": "sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -776,16 +804,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", - "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.52.0.tgz", + "integrity": "sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -802,12 +830,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", - "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz", + "integrity": "sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/types": "5.52.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -1001,14 +1029,13 @@ } }, "node_modules/bullmq": { - "version": "1.91.1", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-1.91.1.tgz", - "integrity": "sha512-u7dat9I8ZwouZ651AMZkBSvB6NVUPpnAjd4iokd9DM41whqIBnDjuL11h7+kEjcpiDKj6E+wxZiER00FqirZQg==", + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-3.6.6.tgz", + "integrity": "sha512-W71jXrcTdcT3Y5tzMyTx22Cd8O3dTML7vl6KG3YdGVGrO3+UmKRLYfGLn1QwIhIoTQJVvIrSB4qfGs1hgqYRVw==", "dependencies": { "cron-parser": "^4.6.0", - "get-port": "6.1.2", "glob": "^8.0.3", - "ioredis": "^5.2.2", + "ioredis": "^5.3.0", "lodash": "^4.17.21", "msgpackr": "^1.6.2", "semver": "^7.3.7", @@ -1170,9 +1197,9 @@ } }, "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", @@ -1214,9 +1241,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.31", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.31.tgz", - "integrity": "sha512-k9DQQ7Wv+ehiF7901qk/FnP47k6O2MHm3meQFee4gUzi5dfGAVLf7SfLNtb4w7G2dmukJyWQtVJEDF9oMb9yuQ==" + "version": "0.37.33", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.33.tgz", + "integrity": "sha512-ZMH5RU3q1pvYS+2wGUJ5Zvy8jMGTQ4wCpbDlIQDkbIL/k6kJwBPsXnCg81g2GywlOuf0f8ezakxVSe+sZuY6ig==" }, "node_modules/discord.js": { "version": "14.7.1", @@ -1976,17 +2003,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-port": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-6.1.2.tgz", - "integrity": "sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -2296,12 +2312,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", - "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.3", + "get-intrinsic": "^1.2.0", "has": "^1.0.3", "side-channel": "^1.0.4" }, @@ -2310,9 +2326,9 @@ } }, "node_modules/ioredis": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.0.tgz", - "integrity": "sha512-Id9jKHhsILuIZpHc61QkagfVdUj2Rag5GzG1TGEvRNeM7dtTOjICgjC+tvqYxi//PuX2wjQ+Xjva2ONBuf92Pw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.1.tgz", + "integrity": "sha512-C+IBcMysM6v52pTLItYMeV4Hz7uriGtoJdz7SSBDX6u+zwSYGirLdQh3L7t/OItWITcw3gTFMjJReYUwS4zihg==", "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -2741,9 +2757,9 @@ } }, "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2882,9 +2898,9 @@ } }, "node_modules/open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.1.tgz", + "integrity": "sha512-/4b7qZNhv6Uhd7jjnREh1NjnPxlTq+XNWPG88Ydkj5AILcA5m3ajvcg57pB24EQjKv0dK62XnDqk9c/hkIG5Kg==", "dev": true, "dependencies": { "define-lazy-prop": "^2.0.0", @@ -3537,9 +3553,9 @@ } }, "node_modules/ts-mixer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.2.tgz", - "integrity": "sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", + "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" }, "node_modules/ts-node": { "version": "10.9.1", @@ -3687,9 +3703,9 @@ } }, "node_modules/undici": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.18.0.tgz", - "integrity": "sha512-1iVwbhonhFytNdg0P4PqyIAXbdlVZVebtPDvuM36m66mRw4OGrCm2MYynJv/UENFLdP13J1nPVQzVE2zTs1OeA==", + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.19.1.tgz", + "integrity": "sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A==", "dependencies": { "busboy": "^1.6.0" }, @@ -3796,9 +3812,9 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", + "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", "engines": { "node": ">=10.0.0" }, @@ -4052,6 +4068,14 @@ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==" }, + "@sapphire/cron": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/cron/-/cron-1.0.0.tgz", + "integrity": "sha512-pKYfpnHiDFknur3yoquA8cqbJZS140y2oqjshwGGmtjiuIbUngQhPHGwdWHNDKDrF6EKbOK06nd2URE+0eUrfQ==", + "requires": { + "@sapphire/utilities": "^3.9.3" + } + }, "@sapphire/discord-utilities": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sapphire/discord-utilities/-/discord-utilities-3.0.0.tgz", @@ -4077,9 +4101,9 @@ "integrity": "sha512-B+6nKYnBmIlqqbamcR4iBvbQHz6/Kq2JUVM0rA3lQ+aYUYDdcA1Spt66CKtPWwdTYEtSv0VY6Jv27WCtFNYTUg==" }, "@sapphire/framework": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@sapphire/framework/-/framework-4.0.2.tgz", - "integrity": "sha512-IoSZGBPJjiINJKJKaBfnpEB1IxPv7yitunnvJ6V5XcTdxP51I/KsVJX2ELxiH7sslg8ZrQQMRIcluGLbVwv4KA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sapphire/framework/-/framework-4.1.0.tgz", + "integrity": "sha512-jtwZPysF13Sn8h2p8nkIPETveGAxRmYmiqxYkd3VXV8VPWwKBG8IriuI4oExpSnuCqxIs5HRpo3M+Gl+f/mdCg==", "requires": { "@discordjs/builders": "^1.4.0", "@sapphire/discord-utilities": "^3.0.0", @@ -4121,12 +4145,12 @@ } }, "@sapphire/plugin-scheduled-tasks": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sapphire/plugin-scheduled-tasks/-/plugin-scheduled-tasks-4.0.1.tgz", - "integrity": "sha512-vLxfHBu2vKaJZ9v2f4z+VDZaPeDqS8bm+Sc2minRwJPw1hWAHiPqmxCBPIONY7eOQ9qKayvhKYTIwwruxgO/Mg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/plugin-scheduled-tasks/-/plugin-scheduled-tasks-6.0.0.tgz", + "integrity": "sha512-R9rga1aZk3GSXkmGfBMQR8Ng4ou36l5WGWoDvPaq1xNb56wZJ2zPZPsQHV6lxNoyOT++M8GFhVhh9OUB+xwEXg==", "requires": { - "@sapphire/stopwatch": "^1.4.1", - "@sapphire/utilities": "^3.9.3", + "@sapphire/stopwatch": "^1.5.0", + "@sapphire/utilities": "^3.11.0", "tslib": "^2.4.0" } }, @@ -4174,6 +4198,17 @@ "tslib": "^2.4.0" } }, + "@sapphire/time-utilities": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@sapphire/time-utilities/-/time-utilities-1.7.8.tgz", + "integrity": "sha512-T6X/nwCvKhxmNRexgmA3KwLt3Z+xzlErkre4viflx46hHOmNNb3hoIyQtekgHYrabEaHWNbqW4PW7gC3hBc+ag==", + "requires": { + "@sapphire/cron": "^1.0.0", + "@sapphire/duration": "^1.0.0", + "@sapphire/timer-manager": "^1.0.0", + "@sapphire/timestamp": "^1.0.0" + } + }, "@sapphire/timer-manager": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@sapphire/timer-manager/-/timer-manager-1.0.0.tgz", @@ -4246,9 +4281,9 @@ "dev": true }, "@types/node": { - "version": "18.11.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.19.tgz", - "integrity": "sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw==" + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" }, "@types/semver": { "version": "7.3.13", @@ -4265,14 +4300,14 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", - "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz", + "integrity": "sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/type-utils": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/type-utils": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -4283,53 +4318,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", - "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.52.0.tgz", + "integrity": "sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", - "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz", + "integrity": "sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0" + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0" } }, "@typescript-eslint/type-utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", - "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz", + "integrity": "sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/typescript-estree": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", - "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.52.0.tgz", + "integrity": "sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", - "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz", + "integrity": "sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4338,28 +4373,28 @@ } }, "@typescript-eslint/utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", - "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.52.0.tgz", + "integrity": "sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", - "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz", + "integrity": "sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/types": "5.52.0", "eslint-visitor-keys": "^3.3.0" } }, @@ -4492,14 +4527,13 @@ } }, "bullmq": { - "version": "1.91.1", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-1.91.1.tgz", - "integrity": "sha512-u7dat9I8ZwouZ651AMZkBSvB6NVUPpnAjd4iokd9DM41whqIBnDjuL11h7+kEjcpiDKj6E+wxZiER00FqirZQg==", + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-3.6.6.tgz", + "integrity": "sha512-W71jXrcTdcT3Y5tzMyTx22Cd8O3dTML7vl6KG3YdGVGrO3+UmKRLYfGLn1QwIhIoTQJVvIrSB4qfGs1hgqYRVw==", "requires": { "cron-parser": "^4.6.0", - "get-port": "6.1.2", "glob": "^8.0.3", - "ioredis": "^5.2.2", + "ioredis": "^5.3.0", "lodash": "^4.17.21", "msgpackr": "^1.6.2", "semver": "^7.3.7", @@ -4623,9 +4657,9 @@ "dev": true }, "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", "dev": true, "requires": { "has-property-descriptors": "^1.0.0", @@ -4652,9 +4686,9 @@ } }, "discord-api-types": { - "version": "0.37.31", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.31.tgz", - "integrity": "sha512-k9DQQ7Wv+ehiF7901qk/FnP47k6O2MHm3meQFee4gUzi5dfGAVLf7SfLNtb4w7G2dmukJyWQtVJEDF9oMb9yuQ==" + "version": "0.37.33", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.33.tgz", + "integrity": "sha512-ZMH5RU3q1pvYS+2wGUJ5Zvy8jMGTQ4wCpbDlIQDkbIL/k6kJwBPsXnCg81g2GywlOuf0f8ezakxVSe+sZuY6ig==" }, "discord.js": { "version": "14.7.1", @@ -5247,11 +5281,6 @@ "has-symbols": "^1.0.3" } }, - "get-port": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-6.1.2.tgz", - "integrity": "sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==" - }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -5465,20 +5494,20 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", - "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", "dev": true, "requires": { - "get-intrinsic": "^1.1.3", + "get-intrinsic": "^1.2.0", "has": "^1.0.3", "side-channel": "^1.0.4" } }, "ioredis": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.0.tgz", - "integrity": "sha512-Id9jKHhsILuIZpHc61QkagfVdUj2Rag5GzG1TGEvRNeM7dtTOjICgjC+tvqYxi//PuX2wjQ+Xjva2ONBuf92Pw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.1.tgz", + "integrity": "sha512-C+IBcMysM6v52pTLItYMeV4Hz7uriGtoJdz7SSBDX6u+zwSYGirLdQh3L7t/OItWITcw3gTFMjJReYUwS4zihg==", "requires": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -5780,9 +5809,9 @@ } }, "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "ms": { @@ -5886,9 +5915,9 @@ } }, "open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.1.tgz", + "integrity": "sha512-/4b7qZNhv6Uhd7jjnREh1NjnPxlTq+XNWPG88Ydkj5AILcA5m3ajvcg57pB24EQjKv0dK62XnDqk9c/hkIG5Kg==", "dev": true, "requires": { "define-lazy-prop": "^2.0.0", @@ -6308,9 +6337,9 @@ } }, "ts-mixer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.2.tgz", - "integrity": "sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", + "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" }, "ts-node": { "version": "10.9.1", @@ -6410,9 +6439,9 @@ } }, "undici": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.18.0.tgz", - "integrity": "sha512-1iVwbhonhFytNdg0P4PqyIAXbdlVZVebtPDvuM36m66mRw4OGrCm2MYynJv/UENFLdP13J1nPVQzVE2zTs1OeA==", + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.19.1.tgz", + "integrity": "sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A==", "requires": { "busboy": "^1.6.0" } @@ -6495,9 +6524,9 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", + "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", "requires": {} }, "yallist": { diff --git a/package.json b/package.json index 65ab526..6a701a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "arabot", - "version": "0.1.0", + "version": "0.2.1", "description": "A Discord bot for Animal Rights Advocates", "main": "dist/index.js", "scripts": { @@ -31,14 +31,15 @@ "dependencies": { "@prisma/client": "^4.10.1", "@sapphire/discord.js-utilities": "^6.0.0", - "@sapphire/framework": "^4.0.1", + "@sapphire/framework": "^4.1.0", "@sapphire/plugin-logger": "^3.0.1", - "@sapphire/plugin-scheduled-tasks": "^4.0.0", + "@sapphire/plugin-scheduled-tasks": "^6.0.0", "@sapphire/plugin-subcommands": "^4.0.0", - "@sapphire/stopwatch": "^1.4.1", + "@sapphire/stopwatch": "^1.5.0", + "@sapphire/time-utilities": "^1.7.8", "@sapphire/utilities": "^3.9.2", "@types/node": "^18.0.3", - "bullmq": "^1.89.1", + "bullmq": "^3.6.6", "discord.js": "^14.7.1", "dotenv": "^16.0.1", "ts-node": "^10.8.2", diff --git a/prisma/migrations/20230216152545_temp_ban_end_mod/migration.sql b/prisma/migrations/20230216152545_temp_ban_end_mod/migration.sql new file mode 100644 index 0000000..026518f --- /dev/null +++ b/prisma/migrations/20230216152545_temp_ban_end_mod/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "TempBan" ADD COLUMN "endModId" TEXT; + +-- AddForeignKey +ALTER TABLE "TempBan" ADD CONSTRAINT "TempBan_endModId_fkey" FOREIGN KEY ("endModId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index af64e65..3f2c7a6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -49,6 +49,7 @@ model User { BanEndMod Ban[] @relation("endBanMod") TempBanUser TempBan[] @relation("tbanUser") TempBanMod TempBan[] @relation("tbanMod") + TempBanEndMod TempBan[] @relation("endTbanMod") VCMuteUser VCMute[] @relation("vcMuteUser") VCMuteMod VCMute[] @relation("vcMuteMod") } @@ -178,6 +179,8 @@ model TempBan { mod User @relation("tbanMod", fields: [modId], references: [id]) modId String startTime DateTime @default(now()) + endMod User? @relation("endTbanMod", fields: [endModId], references: [id]) + endModId String? endTime DateTime active Boolean @default(true) reason String diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban/ban.ts similarity index 96% rename from src/commands/mod/ban.ts rename to src/commands/mod/ban/ban.ts index d082b24..3a4cdb7 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban/ban.ts @@ -27,8 +27,9 @@ import type { } from 'discord.js'; import { EmbedBuilder } from 'discord.js'; import IDs from '#utils/ids'; -import { addBan, checkActive } from '#utils/database/ban'; +import { addBan, checkBan } from '#utils/database/ban'; import { addEmptyUser, updateUser, userExists } from '#utils/database/dbExistingUser'; +import { checkTempBan, removeTempBan } from '#utils/database/tempBan'; export class BanCommand extends Command { public constructor(context: Command.Context, options: Command.Options) { @@ -149,7 +150,7 @@ export class BanCommand extends Command { return info; } - if (await checkActive(userId)) { + if (await checkBan(userId)) { info.message = `${user} is already banned!`; return info; } @@ -188,6 +189,10 @@ export class BanCommand extends Command { // Add ban to database await addBan(userId, modId, reason); + if (await checkTempBan(userId)) { + await removeTempBan(userId); + } + info.message = `${user} has been banned.`; info.success = true; diff --git a/src/commands/mod/ban/tempBan.ts b/src/commands/mod/ban/tempBan.ts new file mode 100644 index 0000000..760e9eb --- /dev/null +++ b/src/commands/mod/ban/tempBan.ts @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + Animal Rights Advocates Discord Bot + Copyright (C) 2023 Anthony Berg + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import { Command, RegisterBehavior } from '@sapphire/framework'; +import { Duration, DurationFormatter } from '@sapphire/time-utilities'; +import type { + User, + Snowflake, + TextChannel, + Guild, +} from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; +import IDs from '#utils/ids'; +import { addTempBan, checkTempBan } from '#utils/database/tempBan'; +import { addEmptyUser, updateUser, userExists } from '#utils/database/dbExistingUser'; + +export class TempBanCommand extends Command { + public constructor(context: Command.Context, options: Command.Options) { + super(context, { + ...options, + name: 'tempban', + description: 'Bans a user for a certain amount of time', + preconditions: ['RestrictedAccessOnly'], + }); + } + + // Registers that this is a slash command + public override registerApplicationCommands(registry: Command.Registry) { + registry.registerChatInputCommand( + (builder) => builder + .setName(this.name) + .setDescription(this.description) + .addUserOption((option) => option.setName('user') + .setDescription('User to ban') + .setRequired(true)) + .addStringOption((option) => option.setName('duration') + .setDescription('How long to ban the user for') + .setRequired(true)) + .addStringOption((option) => option.setName('reason') + .setDescription('Note about the user') + .setRequired(true)), + { + behaviorWhenNotIdentical: RegisterBehavior.Overwrite, + }, + ); + } + + // Command run + public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { + // Get the arguments + const user = interaction.options.getUser('user', true); + const duration = interaction.options.getString('duration', true); + const reason = interaction.options.getString('reason', true); + const mod = interaction.member; + const { guild } = interaction; + + // Checks if all the variables are of the right type + if (guild === null || mod === null) { + await interaction.reply({ + content: 'Error fetching user!', + ephemeral: true, + fetchReply: true, + }); + return; + } + + const time = new Duration(duration); + + if (Number.isNaN(time.offset)) { + await interaction.reply({ + content: 'Invalid ban duration input', + }); + return; + } + + const ban = await this.ban(user.id, mod.user.id, time, reason, guild); + + await interaction.reply({ content: ban.message }); + } + + private async ban( + userId: Snowflake, + modId: Snowflake, + time: Duration, + reason: string, + guild: Guild, + ) { + const info = { + message: '', + success: false, + }; + + const banLength = new DurationFormatter().format(time.offset); + + let user = guild.client.users.cache.get(userId); + + if (user === undefined) { + user = await guild.client.users.fetch(userId) as User; + } + + // Gets mod's GuildMember + const mod = guild.members.cache.get(modId); + + // Checks if guildMember is null + if (mod === undefined) { + info.message = 'Error fetching mod!'; + return info; + } + + if (await checkTempBan(userId)) { + info.message = `${user} is already temp banned!`; + return info; + } + + // Check if mod is in database + await updateUser(mod); + + // Gets guildMember + let member = guild.members.cache.get(userId); + + if (member === undefined) { + member = await guild.members.fetch(userId) + .catch(() => undefined); + } + + if (member !== undefined) { + // Checks if the user is not restricted + if (member.roles.cache.has(IDs.roles.vegan.vegan)) { + info.message = 'You need to restrict the user first!'; + return info; + } + + await updateUser(member); + + // Send DM for reason of ban + await member.send(`You have been temporarily banned from ARA for ${banLength}. Reason: ${reason}` + + '\n\nhttps://vbcamp.org/ARA') + .catch(() => {}); + + // Ban the user + await member.ban({ reason }); + } else if (!await userExists(userId)) { + await addEmptyUser(userId); + } + + // Add ban to database + await addTempBan(userId, modId, time.fromNow, reason); + + // Create scheduled task to unban + this.container.tasks.create('tempBan', { + userId: user.id, + guildId: guild.id, + }, time.offset); + + info.message = `${user} has been temporarily banned for ${banLength}.`; + info.success = true; + + // Log the ban + let logChannel = guild.channels.cache + .get(IDs.channels.logs.restricted) as TextChannel | undefined; + + if (logChannel === undefined) { + logChannel = await guild.channels + .fetch(IDs.channels.logs.restricted) as TextChannel | undefined; + if (logChannel === undefined) { + this.container.logger.error('Temp Ban Error: Could not fetch log channel'); + info.message = `${user} has been temporarily banned for ${banLength}. ` + + 'This hasn\'t been logged in a text channel as log channel could not be found'; + return info; + } + } + + const log = new EmbedBuilder() + .setColor('#FF0000') + .setAuthor({ name: `Temp Banned ${user.tag}`, iconURL: `${user.avatarURL()}` }) + .addFields( + { name: 'User', value: `${user}`, inline: true }, + { name: 'Moderator', value: `${mod}`, inline: true }, + { name: 'Duration', value: banLength }, + { name: 'Reason', value: reason }, + ) + .setTimestamp() + .setFooter({ text: `ID: ${user.id}` }); + + await logChannel.send({ embeds: [log] }); + + return info; + } +} diff --git a/src/commands/mod/unban.ts b/src/commands/mod/ban/unban.ts similarity index 90% rename from src/commands/mod/unban.ts rename to src/commands/mod/ban/unban.ts index 202aff3..62bf738 100644 --- a/src/commands/mod/unban.ts +++ b/src/commands/mod/ban/unban.ts @@ -28,7 +28,8 @@ import type { } from 'discord.js'; import { EmbedBuilder } from 'discord.js'; import IDs from '#utils/ids'; -import { removeBan, checkActive, addBan } from '#utils/database/ban'; +import { removeBan, checkBan, addBan } from '#utils/database/ban'; +import { checkTempBan, removeTempBan } from '#utils/database/tempBan'; import { addEmptyUser, addExistingUser, userExists } from '#utils/database/dbExistingUser'; export class UnbanCommand extends Command { @@ -135,10 +136,17 @@ export class UnbanCommand extends Command { let user = guild.client.users.cache.get(userId); if (user === undefined) { - user = await guild.client.users.fetch(userId) as User; + user = await guild.client.users.fetch(userId); + if (user === undefined) { + info.message = 'Could not fetch the user!'; + return info; + } } - if (!await checkActive(userId)) { + let dbBan = await checkBan(userId); + const dbTempBan = await checkTempBan(userId); + + if (!dbBan && !dbTempBan) { let ban: GuildBan; try { ban = await guild.bans.fetch(userId); @@ -163,14 +171,19 @@ export class UnbanCommand extends Command { // Add missing ban await addBan(userId, modId, `(Mod who banned is not accurate) - ${reason}`); + dbBan = true; } // Unban the user await guild.members.unban(user) .catch(() => {}); - // Add unban to database - await removeBan(user.id, mod.user.id); + if (dbBan) { + // Add unban to database + await removeBan(user.id, mod.user.id); + } else if (dbTempBan) { + await removeTempBan(user.id, mod.user.id); + } info.message = `${user} has been unbanned.`; info.success = true; diff --git a/src/commands/mod/restriction/restrict.ts b/src/commands/mod/restriction/restrict.ts index f33b34c..cfc3915 100644 --- a/src/commands/mod/restriction/restrict.ts +++ b/src/commands/mod/restriction/restrict.ts @@ -37,7 +37,6 @@ import type { Snowflake, } from 'discord.js'; import IDs from '#utils/ids'; -import { randint } from '#utils/random'; import { addEmptyUser, updateUser, @@ -45,6 +44,7 @@ import { fetchRoles, } from '#utils/database/dbExistingUser'; import { restrict, checkActive } from '#utils/database/restriction'; +import { randint } from '#utils/maths'; export async function restrictRun( userId: Snowflake, @@ -58,6 +58,16 @@ export async function restrictRun( success: false, }; + let user = guild.client.users.cache.get(userId); + + if (user === undefined) { + user = await guild.client.users.fetch(userId); + if (user === undefined) { + info.message = 'Error fetching user'; + return info; + } + } + // Gets mod's GuildMember const mod = guild.members.cache.get(modId); @@ -79,7 +89,8 @@ export async function restrictRun( let member = guild.members.cache.get(userId); if (member === undefined) { - member = await guild.members.fetch(userId); + member = await guild.members.fetch(userId) + .catch(() => undefined); } const restrictRoles = IDs.roles.restrictions.restricted; @@ -97,9 +108,6 @@ export async function restrictRun( await updateUser(member); if (member.roles.cache.has(IDs.roles.vegan.vegan)) { - // TODO remove this error before enabling vegan restricts - info.message = `${member} is vegan, can't restrict them yet 😭`; - return info; section = 5; } @@ -212,10 +220,8 @@ export async function restrictRun( IDs.roles.nonvegan.vegCurious, ]); } else if (!await userExists(userId)) { - // TODO remove this error before replacing other bot role replacement - info.message = `<@${userId}> is not on this server, can't restrict them yet! 😭`; - return info; await addEmptyUser(userId); + } else { const dbRoles = await fetchRoles(userId); if (dbRoles.includes(IDs.roles.vegan.vegan)) { section = 5; @@ -225,7 +231,7 @@ export async function restrictRun( // Restrict the user on the database await restrict(userId, modId, reason, section); - info.message = `Restricted ${member}`; + info.message = `Restricted ${user}`; info.success = true; // Log the ban @@ -237,21 +243,21 @@ export async function restrictRun( .fetch(IDs.channels.logs.restricted) as TextChannel | undefined; if (logChannel === undefined) { container.logger.error('Restrict Error: Could not fetch log channel'); - info.message = `Restricted ${member} but could not find the log channel. This has been logged to the database.`; + info.message = `Restricted ${user} but could not find the log channel. This has been logged to the database.`; return info; } } const message = new EmbedBuilder() .setColor('#FF6700') - .setAuthor({ name: `Restricted ${member.user.tag}`, iconURL: `${member.user.avatarURL()}` }) + .setAuthor({ name: `Restricted ${user.tag}`, iconURL: `${user.avatarURL()}` }) .addFields( - { name: 'User', value: `${member}`, inline: true }, + { name: 'User', value: `${user}`, inline: true }, { name: 'Moderator', value: `${mod}`, inline: true }, { name: 'Reason', value: reason }, ) .setTimestamp() - .setFooter({ text: `ID: ${member.id}` }); + .setFooter({ text: `ID: ${userId}` }); await logChannel.send({ embeds: [message] }); @@ -263,7 +269,7 @@ export class RestrictCommand extends Command { super(context, { ...options, name: 'restrict', - aliases: ['r', 'rest', 'rr'], // TODO add 'rv' when enabling vegan restrictions + aliases: ['r', 'rest', 'rr', 'rv'], description: 'Restricts a user', preconditions: ['ModOnly'], }); diff --git a/src/commands/mod/restriction/unrestrict.ts b/src/commands/mod/restriction/unrestrict.ts index acc48e7..42c3d86 100644 --- a/src/commands/mod/restriction/unrestrict.ts +++ b/src/commands/mod/restriction/unrestrict.ts @@ -35,7 +35,7 @@ export class UnRestrictCommand extends Command { super(context, { ...options, name: 'unrestrict', - aliases: ['ur'], // TODO add urv for when restrict vegan will be implemented + aliases: ['ur', 'urv'], description: 'Unrestricts a user', preconditions: ['ModOnly'], }); @@ -121,6 +121,16 @@ export class UnRestrictCommand extends Command { success: false, }; + let user = guild.client.users.cache.get(userId); + + if (user === undefined) { + user = await guild.client.users.fetch(userId); + if (user === undefined) { + info.message = 'Error fetching user'; + return info; + } + } + // Gets mod's GuildMember const mod = guild.members.cache.get(modId); @@ -131,7 +141,7 @@ export class UnRestrictCommand extends Command { } // Check if mod is in database - if (!await userExists(mod.id)) { + if (!await userExists(modId)) { await addExistingUser(mod); } @@ -139,7 +149,8 @@ export class UnRestrictCommand extends Command { let member = guild.members.cache.get(userId); if (member === undefined) { - member = await guild.members.fetch(userId); + member = await guild.members.fetch(userId) + .catch(() => undefined); } if (member === undefined) { @@ -147,11 +158,16 @@ export class UnRestrictCommand extends Command { return info; } + // Check if mod is in database + if (!await userExists(userId)) { + await addExistingUser(member); + } + const restrictRoles = IDs.roles.restrictions.restricted; // Checks if the user is not restricted if (!member.roles.cache.hasAny(...restrictRoles)) { - info.message = `${member} is not restricted!`; + info.message = `${user} is not restricted!`; return info; } @@ -162,7 +178,7 @@ export class UnRestrictCommand extends Command { await unRestrict(userId, modId); } else { let section = 1; - for (let i = 0; i < restrictRoles.length; i += 0) { + for (let i = 0; i < restrictRoles.length; i += 1) { if (member.roles.cache.has(restrictRoles[i])) { section = i + 1; } @@ -213,24 +229,24 @@ export class UnRestrictCommand extends Command { .fetch(IDs.channels.logs.restricted) as TextChannel | undefined; if (logChannel === undefined) { this.container.logger.error('Restrict Error: Could not fetch log channel'); - info.message = `Unrestricted ${member} but could not find the log channel. This has been logged to the database.`; + info.message = `Unrestricted ${user} but could not find the log channel. This has been logged to the database.`; return info; } } const message = new EmbedBuilder() .setColor('#28A745') - .setAuthor({ name: `Unrestricted ${member.user.tag}`, iconURL: `${member.user.avatarURL()}` }) + .setAuthor({ name: `Unrestricted ${user.tag}`, iconURL: `${user.avatarURL()}` }) .addFields( - { name: 'User', value: `${member}`, inline: true }, + { name: 'User', value: `${user}`, inline: true }, { name: 'Moderator', value: `${mod}`, inline: true }, ) .setTimestamp() - .setFooter({ text: `ID: ${member.id}` }); + .setFooter({ text: `ID: ${userId}` }); await logChannel.send({ embeds: [message] }); - info.message = `Unrestricted ${member}`; + info.message = `Unrestricted ${user}`; return info; } } diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts new file mode 100644 index 0000000..074ed5a --- /dev/null +++ b/src/commands/mod/slowmode.ts @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + Animal Rights Advocates Discord Bot + Copyright (C) 2023 Anthony Berg + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import { Args, Command, RegisterBehavior } from '@sapphire/framework'; +import type { Message, TextBasedChannel } from 'discord.js'; +import { ChannelType } from 'discord.js'; +import { Duration, DurationFormatter } from '@sapphire/time-utilities'; +import { isNumber } from '#utils/maths'; + +export class SlowmodeCommand extends Command { + public constructor(context: Command.Context, options: Command.Options) { + super(context, { + ...options, + name: 'slowmode', + description: 'Sets slowmode for a channel', + preconditions: ['ModOnly'], + }); + } + + // Registers that this is a slash command + public override registerApplicationCommands(registry: Command.Registry) { + registry.registerChatInputCommand( + (builder) => builder + .setName(this.name) + .setDescription(this.description) + .addStringOption((option) => option.setName('duration') + .setDescription('Set the slowmode time') + .setRequired(true)), + { + behaviorWhenNotIdentical: RegisterBehavior.Overwrite, + }, + ); + } + + // Command run + public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { + // Get the arguments + const duration = interaction.options.getString('duration', true); + const { channel } = interaction; + + if (channel === null) { + await interaction.reply({ + content: 'Could not fetch channel!', + ephemeral: true, + fetchReply: true, + }); + return; + } + + const slowmode = await this.slowmode(duration, channel); + + await interaction.reply({ content: slowmode.message }); + } + + public async messageRun(message: Message, args: Args) { + // Get arguments + const duration = args.finished ? null : await args.rest('string'); + const { channel } = message; + + if (duration === null) { + await message.react('❌'); + await message.reply('Slowmode length was not provided!'); + return; + } + + const slowmode = await this.slowmode(duration, channel); + + await message.reply(slowmode.message); + await message.react(slowmode.success ? '✅' : '❌'); + } + + private async slowmode(duration: string, channel: TextBasedChannel) { + const info = { + message: '', + success: false, + }; + if (channel.type !== ChannelType.GuildText) { + info.message = 'Channel is not a text channel!'; + return info; + } + + let durationCheck = duration; + + if (isNumber(durationCheck)) { + durationCheck += 's'; + } + this.container.logger.debug(durationCheck); + + const durationParsed = new Duration(durationCheck); + let time = 0; + + if (Number.isNaN(durationParsed.offset)) { + if (duration !== 'off') { + info.message = 'Invalid time format!'; + return info; + } + time = 0; + } else { + time = durationParsed.offset; + } + + await channel.setRateLimitPerUser(time / 1000); + + info.success = true; + if (time === 0) { + info.message = `${channel} is no longer in slowmode.`; + return info; + } + + info.message = `${channel} has now been set to a post every ${new DurationFormatter().format(time)}.`; + return info; + } +} diff --git a/src/listeners/ban/ban.ts b/src/listeners/ban/ban.ts new file mode 100644 index 0000000..4b9b745 --- /dev/null +++ b/src/listeners/ban/ban.ts @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + Animal Rights Advocates Discord Bot + Copyright (C) 2022 Anthony Berg + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import { Listener } from '@sapphire/framework'; +import type { GuildBan } from 'discord.js'; +import { AuditLogEvent, EmbedBuilder, TextChannel } from 'discord.js'; +import { addBan, checkBan } from '#utils/database/ban'; +import IDs from '#utils/ids'; +import { addEmptyUser, addExistingUser, userExists } from '#utils/database/dbExistingUser'; + +export class BanListener extends Listener { + public constructor(context: Listener.Context, options: Listener.Options) { + super(context, { + ...options, + event: 'guildBanAdd', + }); + } + + public async run(ban: GuildBan) { + if (await checkBan(ban.user.id)) { + return; + } + + // Get the audit logs for the ban + const logs = await ban.guild.fetchAuditLogs({ + limit: 1, + type: AuditLogEvent.MemberBanAdd, + }); + + const banLog = logs.entries.first(); + + if (banLog === undefined) { + this.container.logger.error('BanListener: banLog is undefined.'); + return; + } + + const { executor, target } = banLog; + + if (ban.user !== target) { + this.container.logger.error('BanListener: ban.user !== target.'); + return; + } + + if (executor === null) { + this.container.logger.error('BanListener: mod not found.'); + return; + } + + if (this.container.client.user === null) { + this.container.logger.error('BanListener: client.user is null.'); + return; + } + + // Check if the bot banned the user + if (executor.id === this.container.client.user.id) { + this.container.logger.error('BanListener: got past the checkActive and bot banned this user.'); + return; + } + + const { user } = ban; + const { guild } = ban; + + // Gets mod's GuildMember + let mod = guild.members.cache.get(executor.id); + + // Checks if GuildMember is null + if (mod === undefined) { + mod = await guild.members.fetch(executor.id) + .catch(() => undefined); + if (mod === undefined) { + this.container.logger.error('UnbanListener: Could not fetch moderator.'); + return; + } + } + + // Check if mod is in database + if (!await userExists(mod.id)) { + await addExistingUser(mod); + } + + if (await checkBan(user.id)) { + this.container.logger.error('BanListener: got past the checkActive at the start.'); + return; + } + + // Check if user and mod are on the database + if (!await userExists(user.id)) { + await addEmptyUser(user.id); + } + + let { reason } = banLog; + + if (reason === null) { + reason = 'Was banned without using the bot, reason was not given'; + } + + // Add missing ban + await addBan(user.id, mod.id, `${reason}`); + + // Log the ban + let logChannel = guild.channels.cache + .get(IDs.channels.logs.restricted) as TextChannel | undefined; + + if (logChannel === undefined) { + logChannel = await guild.channels + .fetch(IDs.channels.logs.restricted) as TextChannel | undefined; + if (logChannel === undefined) { + this.container.logger.error('BanListener: Could not fetch log channel'); + return; + } + } + + const log = new EmbedBuilder() + .setColor('#FF0000') + .setAuthor({ name: `Banned ${user.tag} (not done via bot)`, iconURL: `${user.avatarURL()}` }) + .addFields( + { name: 'User', value: `${user}`, inline: true }, + { name: 'Moderator', value: `${mod}`, inline: true }, + { name: 'Reason', value: reason }, + ) + .setTimestamp() + .setFooter({ text: `ID: ${user.id}` }); + + await logChannel.send({ embeds: [log] }); + } +} diff --git a/src/listeners/ban.ts b/src/listeners/ban/banJoin.ts similarity index 82% rename from src/listeners/ban.ts rename to src/listeners/ban/banJoin.ts index 1697ee8..c8182d0 100644 --- a/src/listeners/ban.ts +++ b/src/listeners/ban/banJoin.ts @@ -19,9 +19,10 @@ import { Listener } from '@sapphire/framework'; import type { GuildMember } from 'discord.js'; -import { checkActive, getReason } from '#utils/database/ban'; +import { checkBan, getBanReason } from '#utils/database/ban'; +import { checkTempBan } from '#utils/database/tempBan'; -export class BanJoin extends Listener { +export class BanJoinListener extends Listener { public constructor(context: Listener.Context, options: Listener.Options) { super(context, { ...options, @@ -31,12 +32,13 @@ export class BanJoin extends Listener { public async run(user: GuildMember) { // Check if the user is banned - if (!await checkActive(user.id)) { + if (!await checkBan(user.id) + && !await checkTempBan(user.id)) { return; } // Get reason from database - const reason = await getReason(user.id); + const reason = await getBanReason(user.id); // Send DM for ban reason await user.send(`You have been banned from ARA for: ${reason}` diff --git a/src/listeners/ban/unban.ts b/src/listeners/ban/unban.ts new file mode 100644 index 0000000..bf2d8bd --- /dev/null +++ b/src/listeners/ban/unban.ts @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + Animal Rights Advocates Discord Bot + Copyright (C) 2022 Anthony Berg + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import { Listener } from '@sapphire/framework'; +import type { GuildBan } from 'discord.js'; +import { AuditLogEvent, EmbedBuilder, TextChannel } from 'discord.js'; +import { addBan, checkBan, removeBan } from '#utils/database/ban'; +import IDs from '#utils/ids'; +import { addEmptyUser, addExistingUser, userExists } from '#utils/database/dbExistingUser'; + +export class UnbanListener extends Listener { + public constructor(context: Listener.Context, options: Listener.Options) { + super(context, { + ...options, + event: 'guildBanRemove', + }); + } + + public async run(ban: GuildBan) { + // Check if the bot unbanned the user + const logs = await ban.guild.fetchAuditLogs({ + limit: 1, + type: AuditLogEvent.MemberBanRemove, + }); + + const banLog = logs.entries.first(); + + if (banLog === undefined) { + return; + } + + const { executor, target } = banLog; + + if (ban.user !== target) { + return; + } + + if (executor === null) { + return; + } + + if (this.container.client.user === null) { + this.container.logger.error('UnbanListener: client.user is null.'); + return; + } + + if (executor.id === this.container.client.user.id) { + return; + } + + const { user } = ban; + const { guild } = ban; + + // Gets mod's GuildMember + let mod = guild.members.cache.get(executor.id); + + // Checks if GuildMember is null + if (mod === undefined) { + mod = await guild.members.fetch(executor.id) + .catch(() => undefined); + if (mod === undefined) { + this.container.logger.error('UnbanListener: Could not fetch moderator.'); + return; + } + } + + // Check if mod is in database + if (!await userExists(mod.id)) { + await addExistingUser(mod); + } + + // Check for missing ban on database + if (!await checkBan(user.id)) { + // Check if user and mod are on the database + if (!await userExists(user.id)) { + await addEmptyUser(user.id); + } + // Add missing ban + await addBan(user.id, mod.id, '(Mod who banned is not accurate) - '); + } + + // Add unban to database + await removeBan(user.id, mod.id); + + // Log the ban + let logChannel = guild.channels.cache + .get(IDs.channels.logs.restricted) as TextChannel | undefined; + + if (logChannel === undefined) { + logChannel = await guild.channels + .fetch(IDs.channels.logs.restricted) as TextChannel | undefined; + if (logChannel === undefined) { + this.container.logger.error('UnbanListener: Could not fetch log channel'); + return; + } + } + + const log = new EmbedBuilder() + .setColor('#28A745') + .setAuthor({ name: `Unbanned ${user.tag} (not done via bot)`, iconURL: `${user.avatarURL()}` }) + .addFields( + { name: 'User', value: `${user}`, inline: true }, + { name: 'Moderator', value: `${mod}`, inline: true }, + ) + .setTimestamp() + .setFooter({ text: `ID: ${user.id}` }); + + await logChannel.send({ embeds: [log] }); + } +} diff --git a/src/listeners/verification/joinVC.ts b/src/listeners/verification/joinVC.ts index 4873420..f5489d3 100644 --- a/src/listeners/verification/joinVC.ts +++ b/src/listeners/verification/joinVC.ts @@ -128,7 +128,6 @@ export class VerificationJoinVCListener extends Listener { ]); // Start 15-minute timer if verifier does not join - // @ts-ignore this.container.tasks.create('verifyTimeout', { channelId: channel.id, userId: member.id, diff --git a/src/listeners/verification/leaveVC.ts b/src/listeners/verification/leaveVC.ts index 6d67298..e110d71 100644 --- a/src/listeners/verification/leaveVC.ts +++ b/src/listeners/verification/leaveVC.ts @@ -25,7 +25,7 @@ import { time, ChannelType, PermissionsBitField } from 'discord.js'; import { maxVCs, leaveBan } from '#utils/verificationConfig'; import { getUser, checkFinish, countIncomplete } from '#utils/database/verification'; import { fetchRoles } from '#utils/database/dbExistingUser'; -import { fibonacci } from '#utils/mathsSeries'; +import { fibonacci } from '#utils/maths'; import IDs from '#utils/ids'; export class VerificationLeaveVCListener extends Listener { @@ -88,7 +88,6 @@ export class VerificationLeaveVCListener extends Listener { // Creates the length of the time for the ban const banLength = fibonacci(incompleteCount) * 3600_000; - // @ts-ignore this.container.tasks.create('verifyUnblock', { userId: user.id, guildId: guild.id, diff --git a/src/scheduled-tasks/diversityMon.ts b/src/scheduled-tasks/messages/diversityMon.ts similarity index 98% rename from src/scheduled-tasks/diversityMon.ts rename to src/scheduled-tasks/messages/diversityMon.ts index 1755b65..53e0c8b 100644 --- a/src/scheduled-tasks/diversityMon.ts +++ b/src/scheduled-tasks/messages/diversityMon.ts @@ -26,7 +26,7 @@ export class DiversityMonMessageTask extends ScheduledTask { public constructor(context: ScheduledTask.Context, options: ScheduledTask.Options) { super(context, { ...options, - cron: '0 15 * * 1', + pattern: '0 15 * * 1', }); } @@ -53,6 +53,6 @@ export class DiversityMonMessageTask extends ScheduledTask { declare module '@sapphire/plugin-scheduled-tasks' { interface ScheduledTasks { - cron: never; + pattern: never; } } diff --git a/src/scheduled-tasks/diversityWed.ts b/src/scheduled-tasks/messages/diversityWed.ts similarity index 98% rename from src/scheduled-tasks/diversityWed.ts rename to src/scheduled-tasks/messages/diversityWed.ts index 9e5e597..f40ee0b 100644 --- a/src/scheduled-tasks/diversityWed.ts +++ b/src/scheduled-tasks/messages/diversityWed.ts @@ -26,7 +26,7 @@ export class DiversityWedMessageTask extends ScheduledTask { public constructor(context: ScheduledTask.Context, options: ScheduledTask.Options) { super(context, { ...options, - cron: '0 15 * * 3', + pattern: '0 15 * * 3', }); } @@ -54,6 +54,6 @@ export class DiversityWedMessageTask extends ScheduledTask { declare module '@sapphire/plugin-scheduled-tasks' { interface ScheduledTasks { - cron: never; + pattern: never; } } diff --git a/src/scheduled-tasks/restrictedMessage.ts b/src/scheduled-tasks/messages/restrictedMessage.ts similarity index 97% rename from src/scheduled-tasks/restrictedMessage.ts rename to src/scheduled-tasks/messages/restrictedMessage.ts index 2840f4c..d24f1a0 100644 --- a/src/scheduled-tasks/restrictedMessage.ts +++ b/src/scheduled-tasks/messages/restrictedMessage.ts @@ -26,7 +26,7 @@ export class RestrictedMessageTask extends ScheduledTask { public constructor(context: ScheduledTask.Context, options: ScheduledTask.Options) { super(context, { ...options, - cron: '0 17 * * *', + pattern: '0 17 * * *', }); } @@ -49,6 +49,6 @@ export class RestrictedMessageTask extends ScheduledTask { declare module '@sapphire/plugin-scheduled-tasks' { interface ScheduledTasks { - cron: never; + pattern: never; } } diff --git a/src/scheduled-tasks/standup.ts b/src/scheduled-tasks/messages/standup.ts similarity index 97% rename from src/scheduled-tasks/standup.ts rename to src/scheduled-tasks/messages/standup.ts index 5f49584..6731464 100644 --- a/src/scheduled-tasks/standup.ts +++ b/src/scheduled-tasks/messages/standup.ts @@ -26,7 +26,7 @@ export class StandupTask extends ScheduledTask { public constructor(context: ScheduledTask.Context, options: ScheduledTask.Options) { super(context, { ...options, - cron: '0 12 * * 1', + pattern: '0 12 * * 1', }); } @@ -42,6 +42,6 @@ export class StandupTask extends ScheduledTask { declare module '@sapphire/plugin-scheduled-tasks' { interface ScheduledTasks { - cron: never; + pattern: never; } } diff --git a/src/scheduled-tasks/verifyReminder.ts b/src/scheduled-tasks/messages/verifyReminder.ts similarity index 97% rename from src/scheduled-tasks/verifyReminder.ts rename to src/scheduled-tasks/messages/verifyReminder.ts index 5fc6228..9054c52 100644 --- a/src/scheduled-tasks/verifyReminder.ts +++ b/src/scheduled-tasks/messages/verifyReminder.ts @@ -26,7 +26,7 @@ export class VerifyReminder extends ScheduledTask { public constructor(context: ScheduledTask.Context, options: ScheduledTask.Options) { super(context, { ...options, - cron: '0 */1 * * *', + pattern: '0 */1 * * *', }); } @@ -43,6 +43,6 @@ export class VerifyReminder extends ScheduledTask { declare module '@sapphire/plugin-scheduled-tasks' { interface VerifyReminder { - cron: never; + pattern: never; } } diff --git a/src/scheduled-tasks/tempBan.ts b/src/scheduled-tasks/tempBan.ts new file mode 100644 index 0000000..1b49818 --- /dev/null +++ b/src/scheduled-tasks/tempBan.ts @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + Animal Rights Advocates Discord Bot + Copyright (C) 2023 Anthony Berg + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks'; +import IDs from '#utils/ids'; +import { + TextChannel, + EmbedBuilder, +} from 'discord.js'; +import { checkBan } from '#utils/database/ban'; +import { checkTempBan, removeTempBan } from '#utils/database/tempBan'; + +export class TempBan extends ScheduledTask { + public constructor(context: ScheduledTask.Context, options: ScheduledTask.Options) { + super(context, options); + } + + public async run(payload: { userId: string, guildId: string }) { + this.container.logger.debug('Temp Unban Task: Currently running unban'); + // Get the guild where the user is in + let guild = this.container.client.guilds.cache.get(payload.guildId); + if (guild === undefined) { + guild = await this.container.client.guilds.fetch(payload.guildId); + if (guild === undefined) { + this.container.logger.error('Temp Unban Task: Guild not found!'); + return; + } + } + + const { userId } = payload; + + let user = guild.client.users.cache.get(userId); + + if (user === undefined) { + user = await guild.client.users.fetch(userId); + if (user === undefined) { + this.container.logger.error('Temp Unban Task: Could not fetch banned user!'); + return; + } + } + + if (await checkBan(userId) + || !await checkTempBan(userId)) { + this.container.logger.debug('Temp Unban Task: User is either permanently banned or no longer temporarily banned.'); + return; + } + + // Unban the user + await guild.members.unban(user) + .catch(() => {}); + + await removeTempBan(userId); + + // Log unban + let logChannel = guild.channels.cache + .get(IDs.channels.logs.restricted) as TextChannel | undefined; + + if (logChannel === undefined) { + logChannel = await guild.channels + .fetch(IDs.channels.logs.restricted) as TextChannel | undefined; + if (logChannel === undefined) { + this.container.logger.error(`Temp Ban Listener: Could not fetch log channel. User Snowflake: ${userId}`); + return; + } + } + + const log = new EmbedBuilder() + .setColor('#28A745') + .setAuthor({ name: `Unbanned ${user.tag} (tempban)`, iconURL: `${user.avatarURL()}` }) + .addFields( + { name: 'User', value: `${user}`, inline: true }, + ) + .setTimestamp() + .setFooter({ text: `ID: ${user.id}` }); + + await logChannel.send({ embeds: [log] }); + } +} + +declare module '@sapphire/plugin-scheduled-tasks' { + interface ScheduledTasks { + tempBan: never; + } +} diff --git a/src/scheduled-tasks/verifyTimeout.ts b/src/scheduled-tasks/verifyTimeout.ts index bbf452c..a0bd253 100644 --- a/src/scheduled-tasks/verifyTimeout.ts +++ b/src/scheduled-tasks/verifyTimeout.ts @@ -51,6 +51,6 @@ export class VerifyTimeout extends ScheduledTask { declare module '@sapphire/plugin-scheduled-tasks' { interface ScheduledTasks { - verifyUnblock: never; + verifyTimeout: never; } } diff --git a/src/utils/database/ban.ts b/src/utils/database/ban.ts index 5ad7222..2319d57 100644 --- a/src/utils/database/ban.ts +++ b/src/utils/database/ban.ts @@ -46,7 +46,7 @@ export async function removeBan(userId: string, modId: string) { }); } -export async function checkActive(userId: string) { +export async function checkBan(userId: string) { const ban = await container.database.ban.findFirst({ where: { userId, @@ -63,7 +63,7 @@ export async function checkActive(userId: string) { return ban.active; } -export async function getReason(userId: string) { +export async function getBanReason(userId: string) { const ban = await container.database.ban.findFirst({ where: { userId, diff --git a/src/utils/database/tempBan.ts b/src/utils/database/tempBan.ts new file mode 100644 index 0000000..00d73e8 --- /dev/null +++ b/src/utils/database/tempBan.ts @@ -0,0 +1,98 @@ +import { container } from '@sapphire/framework'; +import type { Snowflake } from 'discord.js'; + +export async function addTempBan( + userId: Snowflake, + modId: Snowflake, + endTime: Date, + reason: string, +) { + // Add the user to the database + await container.database.tempBan.create({ + data: { + user: { + connect: { + id: userId, + }, + }, + mod: { + connect: { + id: modId, + }, + }, + endTime, + reason, + }, + }); +} + +export async function removeTempBan(userId: Snowflake, modId?: Snowflake) { + const ban = await container.database.tempBan.findFirst({ + where: { + userId, + }, + orderBy: { + id: 'desc', + }, + }); + + if (ban === null) { + return; + } + + if (modId !== undefined) { + await container.database.tempBan.update({ + where: { + id: ban.id, + }, + data: { + endModId: modId, + active: false, + }, + }); + return; + } + + await container.database.tempBan.update({ + where: { + id: ban.id, + }, + data: { + active: false, + }, + }); +} + +export async function checkTempBan(userId: Snowflake) { + const ban = await container.database.tempBan.findFirst({ + where: { + userId, + }, + orderBy: { + id: 'desc', + }, + }); + + if (ban === null) { + return false; + } + + return ban.active; +} + +export async function getTempBanReason(userId: Snowflake) { + const ban = await container.database.tempBan.findFirst({ + where: { + userId, + }, + orderBy: { + id: 'desc', + }, + }); + + if (ban === null) { + return ''; + } + + return ban.reason; +} diff --git a/src/utils/database/verification.ts b/src/utils/database/verification.ts index 065f555..d1c11e8 100644 --- a/src/utils/database/verification.ts +++ b/src/utils/database/verification.ts @@ -21,7 +21,7 @@ import type { GuildMember } from 'discord.js'; import { container } from '@sapphire/framework'; import { updateUser } from '#utils/database/dbExistingUser'; import { leaveBan } from '#utils/verificationConfig'; -import { fibonacci } from '#utils/mathsSeries'; +import { fibonacci } from '#utils/maths'; export async function joinVerification(channelId: string, user: GuildMember) { // Update the user on the database with the current roles they have diff --git a/src/utils/devIDs.ts b/src/utils/devIDs.ts index c1fcd61..f8e2fb4 100644 --- a/src/utils/devIDs.ts +++ b/src/utils/devIDs.ts @@ -43,7 +43,7 @@ const devIDs = { '999431674997788676', // Restricted 2 '999431674997788675', // Restricted 3 '999431674997788674', // Restricted 4 - // '999431674997788677', // Restricted Vegan + '1075952207091994726', // Restricted Vegan ], }, staff: { diff --git a/src/utils/ids.ts b/src/utils/ids.ts index ba55abb..e09e7d2 100644 --- a/src/utils/ids.ts +++ b/src/utils/ids.ts @@ -46,7 +46,7 @@ let IDs = { '872482843304001566', // Restricted 2 '856582673258774538', // Restricted 3 '872472182888992858', // Restricted 4 - // '809769217477050369', // Restricted Vegan + '1075951477379567646', // Restricted Vegan ], }, staff: { diff --git a/src/utils/mathsSeries.ts b/src/utils/maths.ts similarity index 65% rename from src/utils/mathsSeries.ts rename to src/utils/maths.ts index 2a99459..cbb9859 100644 --- a/src/utils/mathsSeries.ts +++ b/src/utils/maths.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* Animal Rights Advocates Discord Bot - Copyright (C) 2022 Anthony Berg + Copyright (C) 2023 Anthony Berg This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,6 +17,25 @@ along with this program. If not, see . */ +/** + * Checks if any parsed value is a number. + * @param number check if variable is a number + * @returns {boolean} true if it is a number + */ +export function isNumber(number: any) { + return !Number.isNaN(+number); +} + +/** + * Creates a (PRNG) random integer between minimum and maximum both inclusive + * @param min minimum integer + * @param max maximum integer + * @returns number a random integer between min and max + */ +export function randint(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + // Created because Stove loves Fibonacci sequences // A fibonacci sequence where n = 0 => 1 export function fibonacci(position: number) { diff --git a/src/utils/random.ts b/src/utils/random.ts deleted file mode 100644 index d214d7a..0000000 --- a/src/utils/random.ts +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - Animal Rights Advocates Discord Bot - Copyright (C) 2023 Anthony Berg - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// Random integer between min and max -export function randint(min: number, max: number) { - return Math.floor(Math.random() * (max - min + 1)) + min; -}